diff options
168 files changed, 6058 insertions, 1383 deletions
diff --git a/.gitignore b/.gitignore index 6669bf0c6c..10aee94760 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ /git-remote-ftps /git-remote-fd /git-remote-ext +/git-remote-testgit /git-remote-testpy /git-remote-testsvn /git-repack diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 7e4d5716a6..559d5f9ebf 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -237,8 +237,10 @@ For Python scripts: Writing Documentation: - Most (if not all) of the documentation pages are written in AsciiDoc - and processed into HTML output and manpages. + Most (if not all) of the documentation pages are written in the + AsciiDoc format in *.txt files (e.g. Documentation/git.txt), and + processed into HTML and manpages (e.g. git.html and git.1 in the + same directory). Every user-visible change should be reflected in the documentation. The same general rule as for code applies -- imitate the existing diff --git a/Documentation/RelNotes/1.8.3.txt b/Documentation/RelNotes/1.8.3.txt index 468947cc1a..ead568e7f1 100644 --- a/Documentation/RelNotes/1.8.3.txt +++ b/Documentation/RelNotes/1.8.3.txt @@ -42,8 +42,11 @@ Updates since v1.8.2 Foreign interface * remote-hg and remote-bzr helpers (in contrib/ since v1.8.2) have - been updated; especially, the latter has been accelerated to help - Emacs folks, whose primary SCM seems to be stagnating. + been updated; especially, the latter has been done in an + accelerated schedule (read: we may not have merged to this release + if we were following the usual "cook sufficiently in next before + unleashing it to the world" workflow) in order to help Emacs folks, + whose primary SCM seems to be stagnating. UI, Workflows & Features @@ -111,9 +114,10 @@ UI, Workflows & Features of erroneous inputs was suboptimal and has been improved. * When the interactive access to git-shell is not enabled, it issues - a message meant to help the system administrator to enable it. - An explicit way to help the end users who connect to the service by - issuing custom messages to refuse such an access has been added. + a message meant to help the system administrator to enable it. An + explicit way has been added to issue custom messages to refuse an + access over the network to help the end users who connect to the + service expecting an interactive shell. * In addition to the case where the user edits the log message with the "e)dit" option of "am -i", replace the "Applying: this patch" @@ -123,8 +127,8 @@ UI, Workflows & Features * "git status" suggests users to look into using --untracked=no option when it takes too long. - * "git status" shows a bit more information during a - rebase/bisect session. + * "git status" shows a bit more information during a rebase/bisect + session. * "git fetch" learned to fetch a commit at the tip of an unadvertised ref by specifying a raw object name from the command line when the @@ -241,7 +245,6 @@ details). * Various subcommands of "git remote" simply ignored extraneous command line arguments instead of diagnosing them as errors. - (merge b17dd3f tr/remote-tighten-commandline-parsing later to maint). * When receive-pack detects an error in the pack header it received in order to decide which of unpack-objects or index-pack to run, it @@ -264,7 +267,6 @@ details). buffer around as human readable object names. This was not a huge problem but was exposed by a new change that uses these names in error output. - (merge 70d26c6 tr/copy-revisions-from-stdin later to maint). * Smart-capable HTTP servers were not restricted via the GIT_NAMESPACE mechanism when talking with commit-walking clients, @@ -309,11 +311,9 @@ details). * Fix a 1.8.1.x regression that stopped matching "dir" (without a trailing slash) to a directory "dir". - (merge efa5f82 jc/directory-attrs-regression-fix later to maint-1.8.1). * "git apply --whitespace=fix" was not prepared to see a line getting longer after fixing whitespaces (e.g. tab-in-indent aka Python). - (merge 329b26e jc/apply-ws-fix-tab-in-indent later to maint-1.8.1). * The prompt string generator (in contrib/completion/) did not notice when we are in a middle of a "git revert" session. diff --git a/Documentation/RelNotes/1.8.4.txt b/Documentation/RelNotes/1.8.4.txt new file mode 100644 index 0000000000..14835d1539 --- /dev/null +++ b/Documentation/RelNotes/1.8.4.txt @@ -0,0 +1,121 @@ +Git v1.8.4 Release Notes +======================== + +Updates since v1.8.3 +-------------------- + +Foreign interface + + * Remote transport helper has been updated to report errors and + maintain ref hierarchy used to keep track of its own state better. + + +UI, Workflows & Features + + * "check-ignore" (new feature since 1.8.2) has been updated to work + more like "check-attr" over bidi-pipes. + + * "git describe" learned "--first-parent" option to limit its closest + tagged commmit search to the first-parent chain. + + * "git merge foo" that might have meant "git merge origin/foo" is + diagnosed with a more informative error message. + + * "git log -L<line>,<range>:<filename>" has been added. This may + still have leaks and rough edges, though. + + * We used the approxidate() parser for "--expire=<timestamp>" options + of various commands, but it is better to treat --expire=all and + --expire=now a bit more specially than using the current timestamp. + "git gc" and "git reflog" have been updated with a new parsing + function for expiry dates. + + * Updates to completion (both bash and zsh) helpers. + + * "git fetch origin master" unlike "git fetch origin" or "git fetch" + did not update "refs/remotes/origin/master"; this was an early + design decision to keep the update of remote tracking branches + predictable, but in practice it turns out that people find it more + convenient to opportunisticly update them whenever we have a + chance, and we have been updating them when we run "git push" which + already breaks the original "predictability" anyway. + + +Performance, Internal Implementation, etc. + + * The codepath to read from marks files in fast-import/export did not + have to accept anything but 40-hex representation of the object + name. Further, fast-export did not need full in-core object + representation to have parsed wen reading from them. These + codepaths have been optimized by taking advantage of these access + patterns. + + * Object lookup logic, when the object hashtable starts to become + crowded, has been optimized. + + * When TEST_OUTPUT_DIRECTORY setting is used, it was handled somewhat + inconsistently between the test framework and t/Makefile, and logic + to summarize the results looked at a wrong place. + + * Many warnings from sparse source checker in compat/ area has been + squelched. + + * The code to reading and updating packed-refs file has been updated, + correcting corner case bugs. + + +Also contains various documentation updates and code clean-ups. + + +Fixes since v1.8.3 +------------------ + +Unless otherwise noted, all the fixes since v1.8.3 in the maintenance +track are contained in this release (see release notes to them for +details). + + * "git merge @{-1}~22" was rewritten to "git merge frotz@{1}~22" + incorrectly when your previous branch was "frotz" (it should be + rewritten to "git merge frotz~22" instead). + (merge 84cf246 jc/strbuf-branchname-fix later to maint). + + * "git diff -c -p" was not showing a deleted line from a hunk when + another hunk immediately begins where the earlier one ends. + (merge aac3857 mk/combine-diff-context-horizon-fix later to maint). + + * "git log --ancestry-path A...B" did not work as expected, as it did + not pay attention to the fact that the merge base between A and B + was the bottom of the range being specified. + (merge a765499 kb/ancestry-path-threedots later to maint). + + * Mac OS X does not like to write(2) more than INT_MAX number of + bytes; work it around by chopping write(2) into smaller pieces. + (merge 6c642a8 fc/macos-x-clipped-write later to maint). + + * Newer MacOS X encourages the programs to compile and link with + their CommonCrypto, not with OpenSSL. + (merge be4c828 da/darwin later to maint). + + * "git clone foo/bar:baz" cannot be a request to clone from a remote + over git-over-ssh specified in the scp style. This case is now + detected and clones from a local repository at "foo/bar:baz". + (merge 6000334 nd/clone-local-with-colon later to maint). + + * When $HOME is misconfigured to point at an unreadable directory, we + used to complain and die. Loosen the check. + (merge 4698c8f jn/config-ignore-inaccessible later to maint). + + * "git subtree" (in contrib/) had one codepath with loose error + checks to lose data at the remote side. + (merge 3212d56 jk/subtree-do-not-push-if-split-fails later to maint). + + * "git fetch" into a shallow repository from a repository that does + not know about the shallow boundary commits (e.g. a different fork + from the repository the current shallow repository was cloned from) + did not work correctly. + (merge 71d5f93 mh/fetch-into-shallow later to maint). + + * "git checkout foo" DWIMs the intended "upstream" and turns it into + "git checkout -t -b foo remotes/origin/foo". This codepath has been + updated to correctly take existing remote definitions into account. + (merge 229177a jh/checkout-auto-tracking later to maint). diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index b0d31df0e7..e9f984ba01 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -9,28 +9,11 @@ --show-stats:: Include additional statistics at the end of blame output. --L <start>,<end>:: +-L <start>,<end>, -L :<regex>:: Annotate only the given line range. <start> and <end> can take one of these forms: - - number -+ -If <start> or <end> is a number, it specifies an -absolute line number (lines count from 1). -+ - -- /regex/ -+ -This form will use the first line matching the given -POSIX regex. If <end> is a regex, it will search -starting at the line given by <start>. -+ - -- +offset or -offset -+ -This is only valid for <end> and will specify a number -of lines before or after the line given by <start>. -+ +include::line-range-format.txt[] -l:: Show long rev (Default: off). diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 104579dc75..b8a9b86375 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -480,7 +480,7 @@ endif::git-format-patch[] --ignore-submodules[=<when>]:: Ignore changes to submodules in the diff generation. <when> can be - either "none", "untracked", "dirty" or "all", which is the default + either "none", "untracked", "dirty" or "all", which is the default. Using "none" will consider the submodule modified when it either contains untracked or modified files or its HEAD differs from the commit recorded in the superproject and can be used to override any settings of the diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 19d57a80f5..5bbe7b6d10 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -9,12 +9,12 @@ git-am - Apply a series of patches from a mailbox SYNOPSIS -------- [verse] -'git am' [--signoff] [--keep] [--keep-cr | --no-keep-cr] [--utf8 | --no-utf8] +'git am' [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8] [--3way] [--interactive] [--committer-date-is-author-date] [--ignore-date] [--ignore-space-change | --ignore-whitespace] [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>] [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet] - [--scissors | --no-scissors] + [--[no-]scissors] [(<mbox> | <Maildir>)...] 'git am' (--continue | --skip | --abort) @@ -43,8 +43,7 @@ OPTIONS --keep-non-patch:: Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]). ---keep-cr:: ---no-keep-cr:: +--[no-]keep-cr:: With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1]) with the same option, to prevent it from stripping CR at the end of lines. `am.keepcr` configuration variable can be used to specify the diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 250e5228a3..b97aaab4ed 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git archive' [--format=<fmt>] [--list] [--prefix=<prefix>/] [<extra>] - [-o | --output=<file>] [--worktree-attributes] + [-o <file> | --output=<file>] [--worktree-attributes] [--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish> [<path>...] diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index 9a05c2b3d2..6cea7f1ce1 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -8,9 +8,9 @@ git-blame - Show what revision and author last modified each line of a file SYNOPSIS -------- [verse] -'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m] - [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--abbrev=<n>] - [<rev> | --contents <file> | --reverse <rev>] [--] <file> +'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] + [-L n,m | -L :fn] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] + [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file> DESCRIPTION ----------- diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt index 5abdbaa51c..a7be80d48b 100644 --- a/Documentation/git-check-attr.txt +++ b/Documentation/git-check-attr.txt @@ -56,6 +56,11 @@ being queried and <info> can be either: 'set';; when the attribute is defined as true. <value>;; when a value has been assigned to the attribute. +Buffering happens as documented under the `GIT_FLUSH` option in +linkgit:git[1]. The caller is responsible for avoiding deadlocks +caused by overfilling an input buffer or reading from an empty output +buffer. + EXAMPLES -------- diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt index 854e4d0c42..8e1f7ab7ea 100644 --- a/Documentation/git-check-ignore.txt +++ b/Documentation/git-check-ignore.txt @@ -39,6 +39,12 @@ OPTIONS below). If `--stdin` is also given, input paths are separated with a NUL character instead of a linefeed character. +-n, --non-matching:: + Show given paths which don't match any pattern. This only + makes sense when `--verbose` is enabled, otherwise it would + not be possible to distinguish between paths which match a + pattern and those which don't. + OUTPUT ------ @@ -65,6 +71,20 @@ are also used instead of colons and hard tabs: <source> <NULL> <linenum> <NULL> <pattern> <NULL> <pathname> <NULL> +If `-n` or `--non-matching` are specified, non-matching pathnames will +also be output, in which case all fields in each output record except +for <pathname> will be empty. This can be useful when running +non-interactively, so that files can be incrementally streamed to +STDIN of a long-running check-ignore process, and for each of these +files, STDOUT will indicate whether that file matched a pattern or +not. (Without this option, it would be impossible to tell whether the +absence of output for a given file meant that it didn't match any +pattern, or that the output hadn't been generated yet.) + +Buffering happens as documented under the `GIT_FLUSH` option in +linkgit:git[1]. The caller is responsible for avoiding deadlocks +caused by overfilling an input buffer or reading from an empty output +buffer. EXIT STATUS ----------- diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index ec1739a896..a49be1bab4 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -83,8 +83,7 @@ typed the branch name. OPTIONS ------- ---allow-onelevel:: ---no-allow-onelevel:: +--[no-]allow-onelevel:: Controls whether one-level refnames are accepted (i.e., refnames that do not contain multiple `/`-separated components). The default is `--no-allow-onelevel`. diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 23a9413525..ca118ac6bf 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -131,9 +131,9 @@ entries; instead, unmerged entries are ignored. "--track" in linkgit:git-branch[1] for details. + If no '-b' option is given, the name of the new branch will be -derived from the remote-tracking branch. If "remotes/" or "refs/remotes/" -is prefixed it is stripped away, and then the part up to the -next slash (which would be the nickname of the remote) is removed. +derived from the remote-tracking branch, by looking at the local part of +the refspec configured for the corresponding remote, and then stripping +the initial part up to the "*". This would tell us to use "hack" as the local branch when branching off of "origin/hack" (or "remotes/origin/hack", or even "refs/remotes/origin/hack"). If the given name has no slash, or the above diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 5c16e317f6..a0727d7759 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -14,7 +14,7 @@ SYNOPSIS [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>] [--separate-git-dir <git dir>] [--depth <depth>] [--[no-]single-branch] - [--recursive|--recurse-submodules] [--] <repository> + [--recursive | --recurse-submodules] [--] <repository> [<directory>] DESCRIPTION @@ -188,7 +188,7 @@ objects from the source repository into a pack in the cloned repository. with a long history, and would want to send in fixes as patches. ---single-branch:: +--[no-]single-branch:: Clone only the history leading to the tip of a single branch, either specified by the `--branch` option or the primary branch remote's `HEAD` points at. When creating a shallow diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 8172938653..1a7616c73a 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -12,7 +12,7 @@ SYNOPSIS [--dry-run] [(-c | -C | --fixup | --squash) <commit>] [-F <file> | -m <msg>] [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>] - [--date=<date>] [--cleanup=<mode>] [--status | --no-status] + [--date=<date>] [--cleanup=<mode>] [--[no-]status] [-i | -o] [-S[<keyid>]] [--] [<file>...] DESCRIPTION diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 9ae2508f3f..d88a6fcb29 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -186,8 +186,7 @@ See also <<FILES>>. Opens an editor to modify the specified config file; either '--system', '--global', or repository (default). ---includes:: ---no-includes:: +--[no-]includes:: Respect `include.*` directives in config files when looking up values. Defaults to on. diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index bfb106cccd..223f731523 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -16,8 +16,10 @@ SYNOPSIS [--reuseaddr] [--detach] [--pid-file=<file>] [--enable=<service>] [--disable=<service>] [--allow-override=<service>] [--forbid-override=<service>] - [--access-hook=<path>] - [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>] [--user=<user> [--group=<group>]] + [--access-hook=<path>] [--[no-]informative-errors] + [--inetd | + [--listen=<host_or_ipaddr>] [--port=<n>] + [--user=<user> [--group=<group>]]] [<directory>...] DESCRIPTION @@ -169,8 +171,7 @@ Git configuration files in that directory are readable by `<user>`. repository configuration. By default, all the services are overridable. ---informative-errors:: ---no-informative-errors:: +--[no-]informative-errors:: When informative errors are turned on, git-daemon will report more verbose errors to the client, differentiating conditions like "no such repository" from "repository not exported". This diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 28e5ec0e2c..9439cd6d56 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -88,6 +88,11 @@ OPTIONS --always:: Show uniquely abbreviated commit object as fallback. +--first-parent:: + Follow only the first parent commit upon seeing a merge commit. + This is useful when you wish to not match tags on branches merged + in the history of the target commit. + EXAMPLES -------- @@ -149,7 +154,9 @@ is found, its name will be output and searching will stop. If an exact match was not found, 'git describe' will walk back through the commit history to locate an ancestor commit which has been tagged. The ancestor's tag will be output along with an -abbreviation of the input committish's SHA-1. +abbreviation of the input committish's SHA-1. If '--first-parent' was +specified then the walk will only consider the first parent of each +commit. If multiple tags were found during the walk then the tag which has the fewest commits different from the input committish will be diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt index c0b7c581ad..a86cf62e68 100644 --- a/Documentation/git-diff-index.txt +++ b/Documentation/git-diff-index.txt @@ -3,7 +3,7 @@ git-diff-index(1) NAME ---- -git-diff-index - Compares content and mode of blobs between the index and repository +git-diff-index - Compare a tree to the working tree or index SYNOPSIS @@ -13,11 +13,11 @@ SYNOPSIS DESCRIPTION ----------- -Compares the content and mode of the blobs found via a tree -object with the content of the current index and, optionally -ignoring the stat state of the file on disk. When paths are -specified, compares only those named paths. Otherwise all -entries in the index are compared. +Compares the content and mode of the blobs found in a tree object +with the corresponding tracked files in the working tree, or with the +corresponding paths in the index. When <path> arguments are present, +compares only paths matching those patterns. Otherwise all tracked +files are compared. OPTIONS ------- diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 8361e6e4e3..11887e63a0 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -69,8 +69,7 @@ with custom merge tool commands and has the same value as `$MERGED`. --tool-help:: Print a list of diff tools that may be used with `--tool`. ---symlinks:: ---no-symlinks:: +--[no-]symlinks:: 'git difftool''s default behavior is create symlinks to the working tree when run in `--dir-diff` mode and the right-hand side of the comparison yields the same content as the file in diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index 03fc8c39d8..efb03806f5 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -106,11 +106,11 @@ marks the same across runs. different from the commit's first parent). [<git-rev-list-args>...]:: - A list of arguments, acceptable to 'git rev-parse' and - 'git rev-list', that specifies the specific objects and references - to export. For example, `master~10..master` causes the - current master reference to be exported along with all objects - added since its 10th ancestor commit. + A list of arguments, acceptable to 'git rev-parse' and + 'git rev-list', that specifies the specific objects and references + to export. For example, `master~10..master` causes the + current master reference to be exported along with all objects + added since its 10th ancestor commit. EXAMPLES -------- diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index b81e90d8e7..1e71754347 100644 --- a/Documentation/git-fetch-pack.txt +++ b/Documentation/git-fetch-pack.txt @@ -10,9 +10,9 @@ SYNOPSIS -------- [verse] 'git fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] - [--upload-pack=<git-upload-pack>] - [--depth=<n>] [--no-progress] - [-v] [<host>:]<directory> [<refs>...] + [--upload-pack=<git-upload-pack>] + [--depth=<n>] [--no-progress] + [-v] [<host>:]<directory> [<refs>...] DESCRIPTION ----------- diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt index 3a0f55ec8e..bb1232a52c 100644 --- a/Documentation/git-fmt-merge-msg.txt +++ b/Documentation/git-fmt-merge-msg.txt @@ -35,8 +35,7 @@ OPTIONS Do not list one-line descriptions from the actual commits being merged. ---summary:: ---no-summary:: +--[no-]summary:: Synonyms to --log and --no-log; these are deprecated and will be removed in the future. diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index e5878bd97b..25c431d3c5 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -30,8 +30,7 @@ index file, all SHA-1 references in `refs` namespace, and all reflogs Print out objects that exist but that aren't reachable from any of the reference nodes. ---dangling:: ---no-dangling:: +--[no-]dangling:: Print objects that exist but that are never 'directly' used (default). `--no-dangling` can be used to omit this information from the output. @@ -78,8 +77,7 @@ index file, all SHA-1 references in `refs` namespace, and all reflogs a blob, the contents are written into the file, rather than its object name. ---progress:: ---no-progress:: +--[no-]progress:: Progress status is reported on the standard error stream by default when it is attached to a terminal, unless --no-progress or --verbose is specified. --progress forces diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index b370b025b8..2402ed6828 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -62,8 +62,9 @@ automatic consolidation of packs. --prune=<date>:: Prune loose objects older than date (default is 2 weeks ago, - overridable by the config variable `gc.pruneExpire`). This - option is on by default. + overridable by the config variable `gc.pruneExpire`). + --prune=all prunes loose objects regardless of their age. + --prune is on by default. --no-prune:: Do not prune any loose objects. diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 50d46e1a7b..8497aa4494 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -25,7 +25,7 @@ SYNOPSIS [-W | --function-context] [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...] - [ [--exclude-standard] [--cached | --no-index | --untracked] | <tree>...] + [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | <tree>...] [--] [<pathspec>...] DESCRIPTION diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index a976534ab8..4687fe8192 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -62,6 +62,19 @@ produced by --stat etc. Note that only message is considered, if also a diff is shown its size is not included. +-L <start>,<end>:<file>, -L :<regex>:<file>:: + + Trace the evolution of the line range given by "<start>,<end>" + (or the funcname regex <regex>) within the <file>. You may + not give any pathspec limiters. This is currently limited to + a walk starting from a single revision, i.e., you may only + give zero or one positive revision arguments. + You can specify this option more than once. ++ +<start> and <end> can take one of these forms: + +include::line-range-format.txt[] + <revision range>:: Show only commits in the specified revision range. When no <revision range> is specified, it defaults to `HEAD` (i.e. the @@ -140,6 +153,11 @@ Examples This makes sense only when following a strict policy of merging all topic branches when staying on a single integration branch. +git log -L '/int main/',/^}/:main.c:: + + Shows how the function `main()` in the file 'main.c' evolved + over time. + `git log -3`:: Limits the number of commits to show to 3. diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 97e7a8e9e7..164a3c6ede 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -9,7 +9,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message SYNOPSIS -------- [verse] -'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch> +'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch> DESCRIPTION diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 42391f2ae7..67ca99cd92 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -76,8 +76,7 @@ The 'git fmt-merge-msg' command can be used to give a good default for automated 'git merge' invocations. ---rerere-autoupdate:: ---no-rerere-autoupdate:: +--[no-]rerere-autoupdate:: Allow the rerere mechanism to update the index with the result of auto-conflict resolution if possible. diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 6b563c500f..07137f252b 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -8,7 +8,7 @@ git-mergetool - Run merge conflict resolution tools to resolve merge conflicts SYNOPSIS -------- [verse] -'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>...] +'git mergetool' [--tool=<tool>] [-y | --[no-]prompt] [<file>...] DESCRIPTION ----------- diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index eb2883c94c..d51481394c 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -162,8 +162,7 @@ useful if you write an alias or script around 'git push'. linkgit:git-pull[1] and other commands. For more information, see 'branch.<name>.merge' in linkgit:git-config[1]. ---thin:: ---no-thin:: +--[no-]thin:: These options are passed to linkgit:git-send-pack[1]. A thin transfer significantly reduces the amount of sent data when the sender and receiver share many of the same objects in common. The default is diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index fb8697ea4c..70791b9fd8 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -67,14 +67,19 @@ them. --expire=<time>:: Entries older than this time are pruned. Without the option it is taken from configuration `gc.reflogExpire`, - which in turn defaults to 90 days. + which in turn defaults to 90 days. --expire=all prunes + entries regardless of their age; --expire=never turns off + pruning of reachable entries (but see --expire-unreachable). --expire-unreachable=<time>:: Entries older than this time and not reachable from the current tip of the branch are pruned. Without the option it is taken from configuration `gc.reflogExpireUnreachable`, which in turn defaults to - 30 days. + 30 days. --expire-unreachable=all prunes unreachable + entries regardless of their age; --expire-unreachable=never + turns off early pruning of unreachable entries (but see + --expire). --all:: Instead of listing <refs> explicitly, prune all refs. diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 7a6f354680..581bb4c413 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git remote' [-v | --verbose] -'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url> +'git remote add' [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=<fetch|push>] <name> <url> 'git remote rename' <old> <new> 'git remote remove' <name> 'git remote set-head' <name> (-a | -d | <branch>) diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 70152e8b1e..f79c9d8583 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -8,7 +8,7 @@ git-revert - Revert some existing commits SYNOPSIS -------- [verse] -'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>... +'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] <commit>... 'git revert' --continue 'git revert' --quit 'git revert' --abort diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 58b6d540ca..aad452f169 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -271,13 +271,15 @@ first have already been pushed into SVN. Create a tag by using the tags_subdir instead of the branches_subdir specified during git svn init. --d;; ---destination;; +-d<path>;; +--destination=<path>;; + If more than one --branches (or --tags) option was given to the 'init' or 'clone' command, you must provide the location of the branch (or - tag) you wish to create in the SVN repository. The value of this - option must match one of the paths specified by a --branches (or - --tags) option. You can see these paths with the commands + tag) you wish to create in the SVN repository. <path> specifies which + path to use to create the branch or tag and should match the pattern + on the left-hand side of one of the configured branches or tags + refspecs. You can see these refspecs with the commands + git config --get-all svn-remote.<name>.branches git config --get-all svn-remote.<name>.tags @@ -298,6 +300,11 @@ where <name> is the name of the SVN repository as specified by the -R option to git config --get-all svn-remote.<name>.commiturl + +--parents;; + Create parent folders. This parameter is equivalent to the parameter + --parents on svn cp commands and is useful for non-standard repository + layouts. + 'tag':: Create a tag in the SVN repository. This is a shorthand for 'branch -t'. @@ -1032,6 +1039,25 @@ comma-separated list of names within braces. For example: tags = tags/{1.0,2.0}/src:refs/remotes/tags/* ------------------------------------------------------------------------ +Multiple fetch, branches, and tags keys are supported: + +------------------------------------------------------------------------ +[svn-remote "messy-repo"] + url = http://server.org/svn + fetch = trunk/project-a:refs/remotes/project-a/trunk + fetch = branches/demos/june-project-a-demo:refs/remotes/project-a/demos/june-demo + branches = branches/server/*:refs/remotes/project-a/branches/* + branches = branches/demos/2011/*:refs/remotes/project-a/2011-demos/* + tags = tags/server/*:refs/remotes/project-a/tags/* +------------------------------------------------------------------------ + +Creating a branch in such a configuration requires disambiguating which +location to use using the -d or --destination flag: + +------------------------------------------------------------------------ +$ git svn branch -d branches/server release-2-3-0 +------------------------------------------------------------------------ + Note that git-svn keeps track of the highest revision in which a branch or tag has appeared. If the subset of branches or tags is changed after fetching, then .git/svn/.metadata must be manually edited to remove (or diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 670e9fb2c2..e0a87029cd 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -14,8 +14,8 @@ SYNOPSIS [--refresh] [-q] [--unmerged] [--ignore-missing] [(--cacheinfo <mode> <object> <file>)...] [--chmod=(+|-)x] - [--assume-unchanged | --no-assume-unchanged] - [--skip-worktree | --no-skip-worktree] + [--[no-]assume-unchanged] + [--[no-]skip-worktree] [--ignore-submodules] [--really-refresh] [--unresolve] [--again | -g] [--info-only] [--index-info] @@ -77,8 +77,7 @@ OPTIONS --chmod=(+|-)x:: Set the execute permissions on the updated files. ---assume-unchanged:: ---no-assume-unchanged:: +--[no-]assume-unchanged:: When these flags are specified, the object names recorded for the paths are not updated. Instead, these options set and unset the "assume unchanged" bit for the @@ -102,8 +101,7 @@ you will need to handle the situation manually. Like '--refresh', but checks stat information unconditionally, without regard to the "assume unchanged" setting. ---skip-worktree:: ---no-skip-worktree:: +--[no-]skip-worktree:: When one of these flags is specified, the object name recorded for the paths are not updated. Instead, these options set and unset the "skip-worktree" bit for the paths. See diff --git a/Documentation/git.txt b/Documentation/git.txt index 9e302b0a60..68f1ee60cf 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,12 +43,17 @@ unreleased) version of Git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: +* link:v1.8.3/git.html[documentation for release 1.8.3] + +* release notes for + link:RelNotes/1.8.3.txt[1.8.3]. + * link:v1.8.2.3/git.html[documentation for release 1.8.2.3] * release notes for - link:RelNotes/1.8.2.3.txt[1.8.2.3]. - link:RelNotes/1.8.2.2.txt[1.8.2.2]. - link:RelNotes/1.8.2.1.txt[1.8.2.1]. + link:RelNotes/1.8.2.3.txt[1.8.2.3], + link:RelNotes/1.8.2.2.txt[1.8.2.2], + link:RelNotes/1.8.2.1.txt[1.8.2.1], link:RelNotes/1.8.2.txt[1.8.2]. * link:v1.8.1.6/git.html[documentation for release 1.8.1.6] @@ -811,8 +816,9 @@ for further details. 'GIT_FLUSH':: If this environment variable is set to "1", then commands such as 'git blame' (in incremental mode), 'git rev-list', 'git log', - and 'git whatchanged' will force a flush of the output stream - after each commit-oriented record have been flushed. If this + 'git check-attr', 'git check-ignore', and 'git whatchanged' will + force a flush of the output stream after each record have been + flushed. If this variable is set to "0", the output of these commands will be done using completely buffered I/O. If this environment variable is not set, Git will choose buffered or record-oriented flushing diff --git a/Documentation/gitremote-helpers.txt b/Documentation/gitremote-helpers.txt index da746419b3..0827f69139 100644 --- a/Documentation/gitremote-helpers.txt +++ b/Documentation/gitremote-helpers.txt @@ -159,11 +159,11 @@ Miscellaneous capabilities carried out. 'refspec' <refspec>:: - This modifies the 'import' capability, allowing the produced - fast-import stream to modify refs in a private namespace - instead of writing to refs/heads or refs/remotes directly. + For remote helpers that implement 'import' or 'export', this capability + allows the refs to be constrained to a private namespace, instead of + writing to refs/heads or refs/remotes directly. It is recommended that all importers providing the 'import' - capability use this. + capability use this. It's mandatory for 'export'. + A helper advertising the capability `refspec refs/heads/*:refs/svn/origin/branches/*` @@ -174,8 +174,8 @@ ref. This capability can be advertised multiple times. The first applicable refspec takes precedence. The left-hand of refspecs advertised with this capability must cover all refs reported by -the list command. If a helper does not need a specific 'refspec' -capability then it should advertise `refspec *:*`. +the list command. If no 'refspec' capability is advertised, +there is an implied `refspec *:*`. 'bidi-import':: This modifies the 'import' capability. diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 68a18e1497..db2a74df93 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -400,12 +400,13 @@ should not be combined with other pathspec. <<def_ref,ref>> and local ref. [[def_remote_tracking_branch]]remote-tracking branch:: - A regular Git <<def_branch,branch>> that is used to follow changes from - another <<def_repository,repository>>. A remote-tracking - branch should not contain direct modifications or have local commits - made to it. A remote-tracking branch can usually be - identified as the right-hand-side <<def_ref,ref>> in a Pull: - <<def_refspec,refspec>>. + A <<def_ref,ref>> that is used to follow changes from another + <<def_repository,repository>>. It typically looks like + 'refs/remotes/foo/bar' (indicating that it tracks a branch named + 'bar' in a remote named 'foo'), and matches the right-hand-side of + a configured fetch <<def_refspec,refspec>>. A remote-tracking + branch should not contain direct modifications or have local + commits made to it. [[def_repository]]repository:: A collection of <<def_ref,refs>> together with an diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt new file mode 100644 index 0000000000..3e7ce72daa --- /dev/null +++ b/Documentation/line-range-format.txt @@ -0,0 +1,25 @@ +- number ++ +If <start> or <end> is a number, it specifies an +absolute line number (lines count from 1). ++ + +- /regex/ ++ +This form will use the first line matching the given +POSIX regex. If <end> is a regex, it will search +starting at the line given by <start>. ++ + +- +offset or -offset ++ +This is only valid for <end> and will specify a number +of lines before or after the line given by <start>. ++ + +- :regex ++ +If the option's argument is of the form :regex, it denotes the range +from the first funcname line that matches <regex>, up to the next +funcname line. ++ diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 2adccf8fec..afba8d4f3b 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -8,12 +8,13 @@ failed and do not autocommit, to give the user a chance to inspect and further tweak the merge result before committing. --edit:: +-e:: --no-edit:: Invoke an editor before committing successful mechanical merge to further edit the auto-generated merge message, so that the user can explain and justify the merge. The `--no-edit` option can be used to accept the auto-generated message (this is generally - discouraged). The `--edit` option is still useful if you are + discouraged). The `--edit` (or `-e`) option is still useful if you are giving a draft message with the `-m` option from the command line and want to edit it in the editor. + diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt index 94a9d32f1d..18cffc25b8 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -68,6 +68,11 @@ Some short-cut notations are also supported. + * `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`; it requests fetching everything up to the given tag. -* A parameter <ref> without a colon is equivalent to - <ref>: when pulling/fetching, so it merges <ref> into the current - branch without storing the remote branch anywhere locally +ifndef::git-pull[] +* A parameter <ref> without a colon fetches that ref into FETCH_HEAD, +endif::git-pull[] +ifdef::git-pull[] +* A parameter <ref> without a colon merges <ref> into the current + branch, +endif::git-pull[] + and updates the remote-tracking branches (if any). diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index 32ddc1cf13..1317db4d6c 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -41,6 +41,8 @@ The parse-options API allows: * Boolean long options can be 'negated' (or 'unset') by prepending `no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely, options that begin with `no-` can be 'negated' by removing it. + Other long options can be unset (e.g., set string to NULL, set + integer to 0) by prepending `no-`. * Options and non-option arguments can clearly be separated using the `--` option, e.g. `-a -b --option -- --this-is-a-file` indicates that @@ -174,6 +176,10 @@ There are some macros to easily define options: Introduce an option with date argument, see `approxidate()`. The timestamp is put into `int_var`. +`OPT_EXPIRY_DATE(short, long, &int_var, description)`:: + Introduce an option with expiry date argument, see `parse_expiry_date()`. + The timestamp is put into `int_var`. + `OPT_CALLBACK(short, long, &var, arg_str, description, func_ptr)`:: Introduce an option with argument. The argument will be fed into the function given by `func_ptr` diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index f1a51edf47..b898e97988 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -228,8 +228,7 @@ obtained through ref discovery. The client MUST write all obj-ids which it only has shallow copies of (meaning that it does not have the parents of a commit) as 'shallow' lines so that the server is aware of the limitations of -the client's history. Clients MUST NOT mention an obj-id which -it does not know exists on the server. +the client's history. The client now sends the maximum commit history depth it wants for this transaction, which is the number of commits it wants from the diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 3ca122faed..476e3381c5 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -23,6 +23,12 @@ An alternative scp-like syntax may also be used with the ssh protocol: - {startsb}user@{endsb}host.xz:path/to/repo.git/ +This syntax is only recognized if there are no slashes before the +first colon. This helps differentiate a local path that contains a +colon. For example the local path `foo:bar` could be specified as an +absolute path or `./foo:bar` to avoid being misinterpreted as an ssh +url. + The ssh and git protocols additionally support ~username expansion: - ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/ diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index d44408d0cf..390782fa12 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.8.3-rc2 +DEF_VER=v1.8.3.GIT LF=' ' @@ -69,6 +69,9 @@ all:: # Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt # doesn't support GNU extensions like --check and --statistics # +# Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than +# INT_MAX bytes at once (e.g. MacOS X). +# # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH # it specifies. # @@ -137,6 +140,10 @@ all:: # specify your own (or DarwinPort's) include directories and # library directories by defining CFLAGS and LDFLAGS appropriately. # +# Define NO_APPLE_COMMON_CRYPTO if you are building on Darwin/Mac OS X +# and do not want to use Apple's CommonCrypto library. This allows you +# to provide your own OpenSSL library, for example from MacPorts. +# # Define BLK_SHA1 environment variable to make use of the bundled # optimized C SHA1 routine. # @@ -460,6 +467,7 @@ SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh SCRIPT_SH += git-rebase.sh +SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-repack.sh SCRIPT_SH += git-request-pull.sh SCRIPT_SH += git-stash.sh @@ -673,6 +681,8 @@ LIB_H += help.h LIB_H += http.h LIB_H += kwset.h LIB_H += levenshtein.h +LIB_H += line-log.h +LIB_H += line-range.h LIB_H += list-objects.h LIB_H += ll-merge.h LIB_H += log-tree.h @@ -684,7 +694,6 @@ LIB_H += notes-cache.h LIB_H += notes-merge.h LIB_H += notes.h LIB_H += object.h -LIB_H += pack-refs.h LIB_H += pack-revindex.h LIB_H += pack.h LIB_H += parse-options.h @@ -801,6 +810,8 @@ LIB_OBJS += hex.o LIB_OBJS += ident.o LIB_OBJS += kwset.o LIB_OBJS += levenshtein.o +LIB_OBJS += line-log.o +LIB_OBJS += line-range.o LIB_OBJS += list-objects.o LIB_OBJS += ll-merge.o LIB_OBJS += lockfile.o @@ -817,7 +828,6 @@ LIB_OBJS += notes-cache.o LIB_OBJS += notes-merge.o LIB_OBJS += object.o LIB_OBJS += pack-check.o -LIB_OBJS += pack-refs.o LIB_OBJS += pack-revindex.o LIB_OBJS += pack-write.o LIB_OBJS += pager.o @@ -1054,6 +1064,11 @@ ifeq ($(uname_S),Darwin) BASIC_LDFLAGS += -L/opt/local/lib endif endif + ifndef NO_APPLE_COMMON_CRYPTO + APPLE_COMMON_CRYPTO = YesPlease + COMPAT_CFLAGS += -DAPPLE_COMMON_CRYPTO + endif + NO_REGEX = YesPlease PTHREAD_LIBS = endif @@ -1388,10 +1403,16 @@ ifdef PPC_SHA1 LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o LIB_H += ppc/sha1.h else +ifdef APPLE_COMMON_CRYPTO + COMPAT_CFLAGS += -DCOMMON_DIGEST_FOR_OPENSSL + SHA1_HEADER = <CommonCrypto/CommonDigest.h> +else SHA1_HEADER = <openssl/sha.h> EXTLIBS += $(LIB_4_CRYPTO) endif endif +endif + ifdef NO_PERL_MAKEMAKER export NO_PERL_MAKEMAKER endif @@ -1466,6 +1487,11 @@ ifndef NO_MSGFMT_EXTENDED_OPTIONS MSGFMT += --check --statistics endif +ifdef NEEDS_CLIPPED_WRITE + BASIC_CFLAGS += -DNEEDS_CLIPPED_WRITE + COMPAT_OBJS += compat/clipped-write.o +endif + ifneq (,$(XDL_FAST_HASH)) BASIC_CFLAGS += -DXDL_FAST_HASH endif @@ -2004,6 +2030,7 @@ endif ifdef USE_NED_ALLOCATOR compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \ -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR +compat/nedmalloc/nedmalloc.sp: SPARSE_FLAGS += -Wno-non-pointer-null endif git-%$X: %.o GIT-LDFLAGS $(GITLIBS) @@ -2159,6 +2186,9 @@ GIT-BUILD-OPTIONS: FORCE @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@ @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@ @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@ +ifdef TEST_OUTPUT_DIRECTORY + @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@ +endif ifdef GIT_TEST_OPTS @echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@ endif @@ -2443,7 +2473,7 @@ profile-clean: $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs))) $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs))) -clean: profile-clean +clean: profile-clean coverage-clean $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \ builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X @@ -2524,29 +2554,34 @@ check-builtins:: ### Test suite coverage testing # -.PHONY: coverage coverage-clean coverage-build coverage-report +.PHONY: coverage coverage-clean coverage-compile coverage-test coverage-report +.PHONY: coverage-clean-results coverage: - $(MAKE) coverage-build - $(MAKE) coverage-report + $(MAKE) coverage-test + $(MAKE) coverage-untested-functions object_dirs := $(sort $(dir $(OBJECTS))) -coverage-clean: +coverage-clean-results: $(RM) $(addsuffix *.gcov,$(object_dirs)) $(RM) $(addsuffix *.gcda,$(object_dirs)) - $(RM) $(addsuffix *.gcno,$(object_dirs)) $(RM) coverage-untested-functions $(RM) -r cover_db/ $(RM) -r cover_db_html/ +coverage-clean: coverage-clean-results + $(RM) $(addsuffix *.gcno,$(object_dirs)) + COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs COVERAGE_LDFLAGS = $(CFLAGS) -O0 -lgcov GCOVFLAGS = --preserve-paths --branch-probabilities --all-blocks -coverage-build: coverage-clean +coverage-compile: $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all + +coverage-test: coverage-clean-results coverage-compile $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" \ - -j1 test + DEFAULT_TEST_TARGET=test -j1 test coverage-report: $(QUIET_GCOV)for dir in $(object_dirs); do \ @@ -1 +1 @@ -Documentation/RelNotes/1.8.3.txt
\ No newline at end of file +Documentation/RelNotes/1.8.4.txt
\ No newline at end of file @@ -197,6 +197,21 @@ int validate_new_branchname(const char *name, struct strbuf *ref, return 1; } +static int check_tracking_branch(struct remote *remote, void *cb_data) +{ + char *tracking_branch = cb_data; + struct refspec query; + memset(&query, 0, sizeof(struct refspec)); + query.dst = tracking_branch; + return !(remote_find_tracking(remote, &query) || + prefixcmp(query.src, "refs/heads/")); +} + +static int validate_remote_tracking_branch(char *ref) +{ + return !for_each_remote(check_tracking_branch, ref); +} + static const char upstream_not_branch[] = N_("Cannot setup tracking information; starting point '%s' is not a branch."); static const char upstream_missing[] = @@ -259,7 +274,7 @@ void create_branch(const char *head, case 1: /* Unique completion -- good, only if it is a real branch */ if (prefixcmp(real_ref, "refs/heads/") && - prefixcmp(real_ref, "refs/remotes/")) { + validate_remote_tracking_branch(real_ref)) { if (explicit_tracking) die(_(upstream_not_branch), start_name); else diff --git a/builtin/blame.c b/builtin/blame.c index 57a487e052..079dcd3407 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -21,6 +21,7 @@ #include "parse-options.h" #include "utf8.h" #include "userdiff.h" +#include "line-range.h" static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file"); @@ -566,11 +567,16 @@ static void dup_entry(struct blame_entry *dst, struct blame_entry *src) dst->score = 0; } -static const char *nth_line(struct scoreboard *sb, int lno) +static const char *nth_line(struct scoreboard *sb, long lno) { return sb->final_buf + sb->lineno[lno]; } +static const char *nth_line_cb(void *data, long lno) +{ + return nth_line((struct scoreboard *)data, lno); +} + /* * It is known that lines between tlno to same came from parent, and e * has an overlap with that range. it also is known that parent's @@ -1932,83 +1938,6 @@ static const char *add_prefix(const char *prefix, const char *path) } /* - * Parsing of (comma separated) one item in the -L option - */ -static const char *parse_loc(const char *spec, - struct scoreboard *sb, long lno, - long begin, long *ret) -{ - char *term; - const char *line; - long num; - int reg_error; - regex_t regexp; - regmatch_t match[1]; - - /* Allow "-L <something>,+20" to mean starting at <something> - * for 20 lines, or "-L <something>,-5" for 5 lines ending at - * <something>. - */ - if (1 < begin && (spec[0] == '+' || spec[0] == '-')) { - num = strtol(spec + 1, &term, 10); - if (term != spec + 1) { - if (spec[0] == '-') - num = 0 - num; - if (0 < num) - *ret = begin + num - 2; - else if (!num) - *ret = begin; - else - *ret = begin + num; - return term; - } - return spec; - } - num = strtol(spec, &term, 10); - if (term != spec) { - *ret = num; - return term; - } - if (spec[0] != '/') - return spec; - - /* it could be a regexp of form /.../ */ - for (term = (char *) spec + 1; *term && *term != '/'; term++) { - if (*term == '\\') - term++; - } - if (*term != '/') - return spec; - - /* try [spec+1 .. term-1] as regexp */ - *term = 0; - begin--; /* input is in human terms */ - line = nth_line(sb, begin); - - if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) && - !(reg_error = regexec(®exp, line, 1, match, 0))) { - const char *cp = line + match[0].rm_so; - const char *nline; - - while (begin++ < lno) { - nline = nth_line(sb, begin); - if (line <= cp && cp < nline) - break; - line = nline; - } - *ret = begin; - regfree(®exp); - *term++ = '/'; - return term; - } - else { - char errbuf[1024]; - regerror(reg_error, ®exp, errbuf, 1024); - die("-L parameter '%s': %s", spec + 1, errbuf); - } -} - -/* * Parsing of -L option */ static void prepare_blame_range(struct scoreboard *sb, @@ -2016,15 +1945,7 @@ static void prepare_blame_range(struct scoreboard *sb, long lno, long *bottom, long *top) { - const char *term; - - term = parse_loc(bottomtop, sb, lno, 1, bottom); - if (*term == ',') { - term = parse_loc(term + 1, sb, lno, *bottom + 1, top); - if (*term) - usage(blame_usage); - } - if (*term) + if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path)) usage(blame_usage); } @@ -2574,10 +2495,6 @@ parse_done: bottom = top = 0; if (bottomtop) prepare_blame_range(&sb, bottomtop, lno, &bottom, &top); - if (bottom && top && top < bottom) { - long tmp; - tmp = top; top = bottom; bottom = tmp; - } if (bottom < 1) bottom = 1; if (top < 1) diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 854a88a056..4a8fc707c7 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -5,7 +5,7 @@ #include "pathspec.h" #include "parse-options.h" -static int quiet, verbose, stdin_paths; +static int quiet, verbose, stdin_paths, show_non_matching; static const char * const check_ignore_usage[] = { "git check-ignore [options] pathname...", "git check-ignore [options] --stdin < <list-of-paths>", @@ -22,21 +22,28 @@ static const struct option check_ignore_options[] = { N_("read file names from stdin")), OPT_BOOLEAN('z', NULL, &null_term_line, N_("input paths are terminated by a null character")), + OPT_BOOLEAN('n', "non-matching", &show_non_matching, + N_("show non-matching input paths")), OPT_END() }; static void output_exclude(const char *path, struct exclude *exclude) { - char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : ""; - char *slash = exclude->flags & EXC_FLAG_MUSTBEDIR ? "/" : ""; + char *bang = (exclude && exclude->flags & EXC_FLAG_NEGATIVE) ? "!" : ""; + char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : ""; if (!null_term_line) { if (!verbose) { write_name_quoted(path, stdout, '\n'); } else { - quote_c_style(exclude->el->src, NULL, stdout, 0); - printf(":%d:%s%s%s\t", - exclude->srcpos, - bang, exclude->pattern, slash); + if (exclude) { + quote_c_style(exclude->el->src, NULL, stdout, 0); + printf(":%d:%s%s%s\t", + exclude->srcpos, + bang, exclude->pattern, slash); + } + else { + printf("::\t"); + } quote_c_style(path, NULL, stdout, 0); fputc('\n', stdout); } @@ -44,30 +51,26 @@ static void output_exclude(const char *path, struct exclude *exclude) if (!verbose) { printf("%s%c", path, '\0'); } else { - printf("%s%c%d%c%s%s%s%c%s%c", - exclude->el->src, '\0', - exclude->srcpos, '\0', - bang, exclude->pattern, slash, '\0', - path, '\0'); + if (exclude) + printf("%s%c%d%c%s%s%s%c%s%c", + exclude->el->src, '\0', + exclude->srcpos, '\0', + bang, exclude->pattern, slash, '\0', + path, '\0'); + else + printf("%c%c%c%s%c", '\0', '\0', '\0', path, '\0'); } } } -static int check_ignore(const char *prefix, const char **pathspec) +static int check_ignore(struct dir_struct *dir, + const char *prefix, const char **pathspec) { - struct dir_struct dir; const char *path, *full_path; char *seen; int num_ignored = 0, dtype = DT_UNKNOWN, i; struct exclude *exclude; - /* read_cache() is only necessary so we can watch out for submodules. */ - if (read_cache() < 0) - die(_("index file corrupt")); - - memset(&dir, 0, sizeof(dir)); - setup_standard_excludes(&dir); - if (!pathspec || !*pathspec) { if (!quiet) fprintf(stderr, "no pathspec given.\n"); @@ -86,28 +89,26 @@ static int check_ignore(const char *prefix, const char **pathspec) ? strlen(prefix) : 0, path); full_path = check_path_for_gitlink(full_path); die_if_path_beyond_symlink(full_path, prefix); + exclude = NULL; if (!seen[i]) { - exclude = last_exclude_matching(&dir, full_path, &dtype); - if (exclude) { - if (!quiet) - output_exclude(path, exclude); - num_ignored++; - } + exclude = last_exclude_matching(dir, full_path, &dtype); } + if (!quiet && (exclude || show_non_matching)) + output_exclude(path, exclude); + if (exclude) + num_ignored++; } free(seen); - clear_directory(&dir); return num_ignored; } -static int check_ignore_stdin_paths(const char *prefix) +static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix) { struct strbuf buf, nbuf; - char **pathspec = NULL; - size_t nr = 0, alloc = 0; + char *pathspec[2] = { NULL, NULL }; int line_termination = null_term_line ? 0 : '\n'; - int num_ignored; + int num_ignored = 0; strbuf_init(&buf, 0); strbuf_init(&nbuf, 0); @@ -118,23 +119,19 @@ static int check_ignore_stdin_paths(const char *prefix) die("line is badly quoted"); strbuf_swap(&buf, &nbuf); } - ALLOC_GROW(pathspec, nr + 1, alloc); - pathspec[nr] = xcalloc(strlen(buf.buf) + 1, sizeof(*buf.buf)); - strcpy(pathspec[nr++], buf.buf); + pathspec[0] = buf.buf; + num_ignored += check_ignore(dir, prefix, (const char **)pathspec); + maybe_flush_or_die(stdout, "check-ignore to stdout"); } - ALLOC_GROW(pathspec, nr + 1, alloc); - pathspec[nr] = NULL; - num_ignored = check_ignore(prefix, (const char **)pathspec); - maybe_flush_or_die(stdout, "attribute to stdout"); strbuf_release(&buf); strbuf_release(&nbuf); - free(pathspec); return num_ignored; } int cmd_check_ignore(int argc, const char **argv, const char *prefix) { int num_ignored; + struct dir_struct dir; git_config(git_default_config, NULL); @@ -156,13 +153,24 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) if (verbose) die(_("cannot have both --quiet and --verbose")); } + if (show_non_matching && !verbose) + die(_("--non-matching is only valid with --verbose")); + + /* read_cache() is only necessary so we can watch out for submodules. */ + if (read_cache() < 0) + die(_("index file corrupt")); + + memset(&dir, 0, sizeof(dir)); + setup_standard_excludes(&dir); if (stdin_paths) { - num_ignored = check_ignore_stdin_paths(prefix); + num_ignored = check_ignore_stdin_paths(&dir, prefix); } else { - num_ignored = check_ignore(prefix, argv); + num_ignored = check_ignore(&dir, prefix, argv); maybe_flush_or_die(stdout, "ignore to stdout"); } + clear_directory(&dir); + return !num_ignored; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 81b4419da5..f5b50e520f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -825,38 +825,40 @@ static int git_checkout_config(const char *var, const char *value, void *cb) } struct tracking_name_data { - const char *name; - char *remote; + /* const */ char *src_ref; + char *dst_ref; + unsigned char *dst_sha1; int unique; }; -static int check_tracking_name(const char *refname, const unsigned char *sha1, - int flags, void *cb_data) +static int check_tracking_name(struct remote *remote, void *cb_data) { struct tracking_name_data *cb = cb_data; - const char *slash; - - if (prefixcmp(refname, "refs/remotes/")) - return 0; - slash = strchr(refname + 13, '/'); - if (!slash || strcmp(slash + 1, cb->name)) + struct refspec query; + memset(&query, 0, sizeof(struct refspec)); + query.src = cb->src_ref; + if (remote_find_tracking(remote, &query) || + get_sha1(query.dst, cb->dst_sha1)) return 0; - if (cb->remote) { + if (cb->dst_ref) { cb->unique = 0; return 0; } - cb->remote = xstrdup(refname); + cb->dst_ref = xstrdup(query.dst); return 0; } -static const char *unique_tracking_name(const char *name) +static const char *unique_tracking_name(const char *name, unsigned char *sha1) { - struct tracking_name_data cb_data = { NULL, NULL, 1 }; - cb_data.name = name; - for_each_ref(check_tracking_name, &cb_data); + struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 }; + char src_ref[PATH_MAX]; + snprintf(src_ref, PATH_MAX, "refs/heads/%s", name); + cb_data.src_ref = src_ref; + cb_data.dst_sha1 = sha1; + for_each_remote(check_tracking_name, &cb_data); if (cb_data.unique) - return cb_data.remote; - free(cb_data.remote); + return cb_data.dst_ref; + free(cb_data.dst_ref); return NULL; } @@ -919,8 +921,8 @@ static int parse_branchname_arg(int argc, const char **argv, if (dwim_new_local_branch_ok && !check_filename(NULL, arg) && argc == 1) { - const char *remote = unique_tracking_name(arg); - if (!remote || get_sha1(remote, rev)) + const char *remote = unique_tracking_name(arg, rev); + if (!remote) return argcount; *new_branch = arg; arg = remote; diff --git a/builtin/clone.c b/builtin/clone.c index 035ab64950..b6ffc6b4fe 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -18,7 +18,6 @@ #include "transport.h" #include "strbuf.h" #include "dir.h" -#include "pack-refs.h" #include "sigchain.h" #include "branch.h" #include "remote.h" @@ -783,6 +782,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) is_local = option_local != 0 && path && !is_bundle; if (is_local && option_depth) warning(_("--depth is ignored in local clones; use file:// instead.")); + if (option_local > 0 && !is_local) + warning(_("--local is ignored")); if (argc == 2) dir = xstrdup(argv[1]); diff --git a/builtin/config.c b/builtin/config.c index 33c9bf9d84..19ffcaf187 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -379,8 +379,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) */ die("$HOME not set"); - if (access_or_warn(user_config, R_OK) && - xdg_config && !access_or_warn(xdg_config, R_OK)) + if (access_or_warn(user_config, R_OK, 0) && + xdg_config && !access_or_warn(xdg_config, R_OK, 0)) given_config_file = xdg_config; else given_config_file = user_config; diff --git a/builtin/describe.c b/builtin/describe.c index 6636a68cd9..ad8471626a 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -21,6 +21,7 @@ static int debug; /* Display lots of verbose info */ static int all; /* Any valid ref can be used */ static int tags; /* Allow lightweight tags */ static int longformat; +static int first_parent; static int abbrev = -1; /* unspecified */ static int max_candidates = 10; static struct hash_table names; @@ -336,6 +337,9 @@ static void describe(const char *arg, int last_one) commit_list_insert_by_date(p, &list); p->object.flags |= c->object.flags; parents = parents->next; + + if (first_parent) + break; } } @@ -404,6 +408,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "all", &all, N_("use any ref")), OPT_BOOLEAN(0, "tags", &tags, N_("use any tag, even unannotated")), OPT_BOOLEAN(0, "long", &longformat, N_("always use long format")), + OPT_BOOLEAN(0, "first-parent", &first_parent, N_("only follow first parent")), OPT__ABBREV(&abbrev), OPT_SET_INT(0, "exact-match", &max_candidates, N_("only output exact matches"), 0), diff --git a/builtin/fast-export.c b/builtin/fast-export.c index d60d675f6f..d1d68e9fc6 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -613,6 +613,8 @@ static void import_marks(char *input_file) char *line_end, *mark_end; unsigned char sha1[20]; struct object *object; + struct commit *commit; + enum object_type type; line_end = strchr(line, '\n'); if (line[0] != ':' || !line_end) @@ -621,23 +623,29 @@ static void import_marks(char *input_file) mark = strtoumax(line + 1, &mark_end, 10); if (!mark || mark_end == line + 1 - || *mark_end != ' ' || get_sha1(mark_end + 1, sha1)) + || *mark_end != ' ' || get_sha1_hex(mark_end + 1, sha1)) die("corrupt mark line: %s", line); if (last_idnum < mark) last_idnum = mark; - object = parse_object(sha1); - if (!object) + type = sha1_object_info(sha1, NULL); + if (type < 0) + die("object not found: %s", sha1_to_hex(sha1)); + + if (type != OBJ_COMMIT) + /* only commits */ continue; + commit = lookup_commit(sha1); + if (!commit) + die("not a commit? can't happen: %s", sha1_to_hex(sha1)); + + object = &commit->object; + if (object->flags & SHOWN) error("Object %s already has a mark", sha1_to_hex(sha1)); - if (object->type != OBJ_COMMIT) - /* only commits */ - continue; - mark_object(object, mark); object->flags |= SHOWN; diff --git a/builtin/fetch.c b/builtin/fetch.c index 4b6b1dfe66..d15a7343d8 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -119,7 +119,7 @@ static void add_merge_config(struct ref **head, for (rm = *head; rm; rm = rm->next) { if (branch_merge_matches(branch, i, rm->name)) { - rm->merge = 1; + rm->fetch_head_status = FETCH_HEAD_MERGE; break; } } @@ -140,7 +140,7 @@ static void add_merge_config(struct ref **head, refspec.src = branch->merge[i]->src; get_fetch_map(remote_refs, &refspec, tail, 1); for (rm = *old_tail; rm; rm = rm->next) - rm->merge = 1; + rm->fetch_head_status = FETCH_HEAD_MERGE; } } @@ -160,6 +160,8 @@ static struct ref *get_ref_map(struct transport *transport, const struct ref *remote_refs = transport_get_remote_refs(transport); if (ref_count || tags == TAGS_SET) { + struct ref **old_tail; + for (i = 0; i < ref_count; i++) { get_fetch_map(remote_refs, &refs[i], &tail, 0); if (refs[i].dst && refs[i].dst[0]) @@ -167,9 +169,23 @@ static struct ref *get_ref_map(struct transport *transport, } /* Merge everything on the command line, but not --tags */ for (rm = ref_map; rm; rm = rm->next) - rm->merge = 1; + rm->fetch_head_status = FETCH_HEAD_MERGE; if (tags == TAGS_SET) get_fetch_map(remote_refs, tag_refspec, &tail, 0); + + /* + * For any refs that we happen to be fetching via command-line + * arguments, take the opportunity to update their configured + * counterparts. However, we do not want to mention these + * entries in FETCH_HEAD at all, as they would simply be + * duplicates of existing entries. + */ + old_tail = tail; + for (i = 0; i < transport->remote->fetch_refspec_nr; i++) + get_fetch_map(ref_map, &transport->remote->fetch[i], + &tail, 1); + for (rm = *old_tail; rm; rm = rm->next) + rm->fetch_head_status = FETCH_HEAD_IGNORE; } else { /* Use the defaults */ struct remote *remote = transport->remote; @@ -186,7 +202,7 @@ static struct ref *get_ref_map(struct transport *transport, *autotags = 1; if (!i && !has_merge && ref_map && !remote->fetch[0].pattern) - ref_map->merge = 1; + ref_map->fetch_head_status = FETCH_HEAD_MERGE; } /* * if the remote we're fetching from is the same @@ -202,7 +218,7 @@ static struct ref *get_ref_map(struct transport *transport, ref_map = get_remote_ref(remote_refs, "HEAD"); if (!ref_map) die(_("Couldn't find remote ref HEAD")); - ref_map->merge = 1; + ref_map->fetch_head_status = FETCH_HEAD_MERGE; tail = &ref_map->next; } } @@ -389,7 +405,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, const char *what, *kind; struct ref *rm; char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); - int want_merge; + int want_status; fp = fopen(filename, "a"); if (!fp) @@ -407,19 +423,22 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } /* - * The first pass writes objects to be merged and then the - * second pass writes the rest, in order to allow using - * FETCH_HEAD as a refname to refer to the ref to be merged. + * We do a pass for each fetch_head_status type in their enum order, so + * merged entries are written before not-for-merge. That lets readers + * use FETCH_HEAD as a refname to refer to the ref to be merged. */ - for (want_merge = 1; 0 <= want_merge; want_merge--) { + for (want_status = FETCH_HEAD_MERGE; + want_status <= FETCH_HEAD_IGNORE; + want_status++) { for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; + const char *merge_status_marker = ""; commit = lookup_commit_reference_gently(rm->old_sha1, 1); if (!commit) - rm->merge = 0; + rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; - if (rm->merge != want_merge) + if (rm->fetch_head_status != want_status) continue; if (rm->peer_ref) { @@ -465,16 +484,26 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_addf(¬e, "%s ", kind); strbuf_addf(¬e, "'%s' of ", what); } - fprintf(fp, "%s\t%s\t%s", - sha1_to_hex(rm->old_sha1), - rm->merge ? "" : "not-for-merge", - note.buf); - for (i = 0; i < url_len; ++i) - if ('\n' == url[i]) - fputs("\\n", fp); - else - fputc(url[i], fp); - fputc('\n', fp); + switch (rm->fetch_head_status) { + case FETCH_HEAD_NOT_FOR_MERGE: + merge_status_marker = "not-for-merge"; + /* fall-through */ + case FETCH_HEAD_MERGE: + fprintf(fp, "%s\t%s\t%s", + sha1_to_hex(rm->old_sha1), + merge_status_marker, + note.buf); + for (i = 0; i < url_len; ++i) + if ('\n' == url[i]) + fputs("\\n", fp); + else + fputc(url[i], fp); + fputc('\n', fp); + break; + default: + /* do not write anything to FETCH_HEAD */ + break; + } strbuf_reset(¬e); if (ref) { diff --git a/builtin/log.c b/builtin/log.c index 6e56a50002..9e2123295f 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -19,6 +19,7 @@ #include "remote.h" #include "string-list.h" #include "parse-options.h" +#include "line-log.h" #include "branch.h" #include "streaming.h" #include "version.h" @@ -42,6 +43,12 @@ static const char * const builtin_log_usage[] = { NULL }; +struct line_opt_callback_data { + struct rev_info *rev; + const char *prefix; + struct string_list args; +}; + static int parse_decoration_style(const char *var, const char *value) { switch (git_config_maybe_bool(var, value)) { @@ -76,6 +83,19 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse return 0; } +static int log_line_range_callback(const struct option *option, const char *arg, int unset) +{ + struct line_opt_callback_data *data = option->value; + + if (!arg) + return -1; + + data->rev->line_level_traverse = 1; + string_list_append(&data->args, arg); + + return 0; +} + static void cmd_log_init_defaults(struct rev_info *rev) { if (fmt_pretty) @@ -98,6 +118,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, { struct userformat_want w; int quiet = 0, source = 0, mailmap = 0; + static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP}; const struct option builtin_log_options[] = { OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")), @@ -105,9 +126,15 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, + OPT_CALLBACK('L', NULL, &line_cb, "n,m:file", + "Process line range n,m in file, counting from 1", + log_line_range_callback), OPT_END() }; + line_cb.rev = rev; + line_cb.prefix = prefix; + mailmap = use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, @@ -161,6 +188,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, rev->show_decorations = 1; load_ref_decorations(decoration_style); } + + if (rev->line_level_traverse) + line_log_init(rev, line_cb.prefix, &line_cb.args); + setup_pager(); } diff --git a/builtin/merge.c b/builtin/merge.c index 3e2daa37c3..2ebe732896 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1054,7 +1054,8 @@ static struct commit_list *collect_parents(struct commit *head_commit, for (i = 0; i < argc; i++) { struct commit *commit = get_merge_parent(argv[i]); if (!commit) - die(_("%s - not something we can merge"), argv[i]); + help_unknown_ref(argv[i], "merge", + "not something we can merge"); remotes = &commit_list_insert(commit, remotes)->next; } *remotes = NULL; diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c index b5a0f88eb8..b20b1ec4c1 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -1,6 +1,6 @@ #include "builtin.h" #include "parse-options.h" -#include "pack-refs.h" +#include "refs.h" static char const * const pack_refs_usage[] = { N_("git pack-refs [options]"), diff --git a/builtin/prune.c b/builtin/prune.c index 85843d4f17..b90e5cc361 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -132,8 +132,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix) OPT__DRY_RUN(&show_only, N_("do not remove, show only")), OPT__VERBOSE(&verbose, N_("report pruned objects")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), - OPT_DATE(0, "expire", &expire, - N_("expire objects older than <time>")), + OPT_EXPIRY_DATE(0, "expire", &expire, + N_("expire objects older than <time>")), OPT_END() }; char *s; diff --git a/builtin/reflog.c b/builtin/reflog.c index 72a0af70c3..54184b3d13 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -496,11 +496,9 @@ static int parse_expire_cfg_value(const char *var, const char *value, unsigned l { if (!value) return config_error_nonbool(var); - if (!strcmp(value, "never") || !strcmp(value, "false")) { - *expire = 0; - return 0; - } - *expire = approxidate(value); + if (parse_expiry_date(value, expire)) + return error(_("%s' for '%s' is not a valid timestamp"), + value, var); return 0; } @@ -614,11 +612,13 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) cb.dry_run = 1; else if (!prefixcmp(arg, "--expire=")) { - cb.expire_total = approxidate(arg + 9); + if (parse_expiry_date(arg + 9, &cb.expire_total)) + die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_TOTAL; } else if (!prefixcmp(arg, "--expire-unreachable=")) { - cb.expire_unreachable = approxidate(arg + 21); + if (parse_expiry_date(arg + 21, &cb.expire_unreachable)) + die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_UNREACH; } else if (!strcmp(arg, "--stale-fix")) @@ -910,6 +910,7 @@ void show_date_relative(unsigned long time, int tz, const struct timeval *now, struct strbuf *timebuf); int parse_date(const char *date, char *buf, int bufsize); int parse_date_basic(const char *date, unsigned long *timestamp, int *offset); +int parse_expiry_date(const char *date, unsigned long *timestamp); void datestamp(char *buf, int bufsize); #define approxidate(s) approxidate_careful((s), NULL) unsigned long approxidate_careful(const char *, int *); @@ -1024,9 +1025,21 @@ struct ref { unsigned int force:1, forced_update:1, - merge:1, deletion:1, matched:1; + + /* + * Order is important here, as we write to FETCH_HEAD + * in numeric order. And the default NOT_FOR_MERGE + * should be 0, so that xcalloc'd structures get it + * by default. + */ + enum { + FETCH_HEAD_MERGE = -1, + FETCH_HEAD_NOT_FOR_MERGE = 0, + FETCH_HEAD_IGNORE = 1 + } fetch_head_status; + enum { REF_STATUS_NONE = 0, REF_STATUS_OK, diff --git a/combine-diff.c b/combine-diff.c index 77d7872aaf..3e8bb17831 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -518,8 +518,11 @@ static int give_context(struct sline *sline, unsigned long cnt, int num_parent) unsigned long k; /* Paint a few lines before the first interesting line. */ - while (j < i) - sline[j++].flag |= mark | no_pre_delete; + while (j < i) { + if (!(sline[j].flag & mark)) + sline[j].flag |= no_pre_delete; + sline[j++].flag |= mark; + } again: /* we know up to i is to be included. where does the diff --git a/compat/clipped-write.c b/compat/clipped-write.c new file mode 100644 index 0000000000..b8f98ff77f --- /dev/null +++ b/compat/clipped-write.c @@ -0,0 +1,13 @@ +#include "../git-compat-util.h" +#undef write + +/* + * Version of write that will write at most INT_MAX bytes. + * Workaround a xnu bug on Mac OS X + */ +ssize_t clipped_write(int fildes, const void *buf, size_t nbyte) +{ + if (nbyte > INT_MAX) + nbyte = INT_MAX; + return write(fildes, buf, nbyte); +} diff --git a/compat/fnmatch/fnmatch.c b/compat/fnmatch/fnmatch.c index 5ef0685135..378c467401 100644 --- a/compat/fnmatch/fnmatch.c +++ b/compat/fnmatch/fnmatch.c @@ -25,6 +25,7 @@ # define _GNU_SOURCE 1 #endif +#include <stddef.h> #include <errno.h> #include <fnmatch.h> #include <ctype.h> @@ -121,7 +122,7 @@ whose names are inconsistent. */ # if !defined _LIBC && !defined getenv -extern char *getenv (); +extern char *getenv (const char *name); # endif # ifndef errno diff --git a/compat/mingw.c b/compat/mingw.c index b673625580..b295e2f6a9 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -841,8 +841,8 @@ struct pinfo_t { struct pinfo_t *next; pid_t pid; HANDLE proc; -} pinfo_t; -struct pinfo_t *pinfo = NULL; +}; +static struct pinfo_t *pinfo = NULL; CRITICAL_SECTION pinfo_cs; static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, @@ -1253,7 +1253,7 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service, else sin->sin_addr.s_addr = INADDR_LOOPBACK; ai->ai_addr = (struct sockaddr *)sin; - ai->ai_next = 0; + ai->ai_next = NULL; return 0; } diff --git a/compat/mingw.h b/compat/mingw.h index 685cd2c3d4..bd0a88bc1d 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -334,13 +334,20 @@ char **make_augmented_environ(const char *const *vars); void free_environ(char **env); /* + * A critical section used in the implementation of the spawn + * functions (mingw_spawnv[p]e()) and waitpid(). Intialised in + * the replacement main() macro below. + */ +extern CRITICAL_SECTION pinfo_cs; + +/* * A replacement of main() that ensures that argv[0] has a path * and that default fmode and std(in|out|err) are in binary mode */ #define main(c,v) dummy_decl_mingw_main(); \ -static int mingw_main(); \ -int main(int argc, const char **argv) \ +static int mingw_main(c,v); \ +int main(int argc, char **argv) \ { \ extern CRITICAL_SECTION pinfo_cs; \ _fmode = _O_BINARY; \ diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h index 1401a67274..5a44dead9d 100644 --- a/compat/nedmalloc/malloc.c.h +++ b/compat/nedmalloc/malloc.c.h @@ -484,6 +484,10 @@ MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP #define DLMALLOC_VERSION 20804 #endif /* DLMALLOC_VERSION */ +#if defined(linux) +#define _GNU_SOURCE 1 +#endif + #ifndef WIN32 #ifdef _WIN32 #define WIN32 1 @@ -1802,7 +1806,7 @@ struct win32_mlock_t static MLOCK_T malloc_global_mutex = { 0, 0, 0}; -static FORCEINLINE long win32_getcurrentthreadid() { +static FORCEINLINE long win32_getcurrentthreadid(void) { #ifdef _MSC_VER #if defined(_M_IX86) long *threadstruct=(long *)__readfsdword(0x18); diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c index 91c4e7f27b..609ebba125 100644 --- a/compat/nedmalloc/nedmalloc.c +++ b/compat/nedmalloc/nedmalloc.c @@ -159,8 +159,8 @@ struct mallinfo nedmallinfo(void) THROWSPEC { return nedpmallinfo(0); } #endif int nedmallopt(int parno, int value) THROWSPEC { return nedpmallopt(0, parno, value); } int nedmalloc_trim(size_t pad) THROWSPEC { return nedpmalloc_trim(0, pad); } -void nedmalloc_stats() THROWSPEC { nedpmalloc_stats(0); } -size_t nedmalloc_footprint() THROWSPEC { return nedpmalloc_footprint(0); } +void nedmalloc_stats(void) THROWSPEC { nedpmalloc_stats(0); } +size_t nedmalloc_footprint(void) THROWSPEC { return nedpmalloc_footprint(0); } void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC { return nedpindependent_calloc(0, elemsno, elemsize, chunks); } void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC { return nedpindependent_comalloc(0, elems, sizes, chunks); } diff --git a/compat/poll/poll.c b/compat/poll/poll.c index 7d226ecb29..44103103a4 100644 --- a/compat/poll/poll.c +++ b/compat/poll/poll.c @@ -576,7 +576,7 @@ restart: { /* It's a socket. */ WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev); - WSAEventSelect ((SOCKET) h, 0, 0); + WSAEventSelect ((SOCKET) h, NULL, 0); /* If we're lucky, WSAEnumNetworkEvents already provided a way to distinguish FD_READ and FD_ACCEPT; this saves a recv later. */ diff --git a/compat/regex/regexec.c b/compat/regex/regexec.c index 0194965c5d..0cd6e0ef98 100644 --- a/compat/regex/regexec.c +++ b/compat/regex/regexec.c @@ -2313,7 +2313,7 @@ transit_state (reg_errcode_t *err, re_match_context_t *mctx, } /* Update the state_log if we need */ -re_dfastate_t * +static re_dfastate_t * internal_function merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx, re_dfastate_t *next_state) @@ -2326,7 +2326,7 @@ merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx, mctx->state_log[cur_idx] = next_state; mctx->state_log_top = cur_idx; } - else if (mctx->state_log[cur_idx] == 0) + else if (mctx->state_log[cur_idx] == NULL) { mctx->state_log[cur_idx] = next_state; } @@ -2392,7 +2392,7 @@ merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx, /* Skip bytes in the input that correspond to part of a multi-byte match, then look in the log for a state from which to restart matching. */ -re_dfastate_t * +static re_dfastate_t * internal_function find_recover_state (reg_errcode_t *err, re_match_context_t *mctx) { diff --git a/compat/unsetenv.c b/compat/unsetenv.c index eb29f5e084..4ea18569c2 100644 --- a/compat/unsetenv.c +++ b/compat/unsetenv.c @@ -2,7 +2,6 @@ void gitunsetenv (const char *name) { - extern char **environ; int src, dst; size_t nmln; diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c index 010e875ec4..e18f5c6e2e 100644 --- a/compat/win32/pthread.c +++ b/compat/win32/pthread.c @@ -52,7 +52,7 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr) pthread_t pthread_self(void) { - pthread_t t = { 0 }; + pthread_t t = { NULL }; t.tid = GetCurrentThreadId(); return t; } diff --git a/compat/win32mmap.c b/compat/win32mmap.c index 61d2ef8e46..80a8c9af4f 100644 --- a/compat/win32mmap.c +++ b/compat/win32mmap.c @@ -21,8 +21,8 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of if (!(flags & MAP_PRIVATE)) die("Invalid usage of mmap when built with USE_WIN32_MMAP"); - hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), 0, PAGE_WRITECOPY, - 0, 0, 0); + hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), NULL, + PAGE_WRITECOPY, 0, 0, NULL); if (!hmap) return MAP_FAILED; @@ -58,7 +58,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc path = buf.buf; } - if (!access_or_die(path, R_OK)) { + if (!access_or_die(path, R_OK, 0)) { if (++inc->depth > MAX_INCLUDE_DEPTH) die(include_depth_advice, MAX_INCLUDE_DEPTH, path, cf && cf->name ? cf->name : "the command line"); @@ -566,7 +566,20 @@ static int git_default_core_config(const char *var, const char *value) trust_ctime = git_config_bool(var, value); return 0; } - if (!strcmp(var, "core.statinfo")) { + if (!strcmp(var, "core.statinfo") || + !strcmp(var, "core.checkstat")) { + /* + * NEEDSWORK: statinfo was a typo in v1.8.2 that has + * never been advertised. we will remove it at Git + * 2.0 boundary. + */ + if (!strcmp(var, "core.statinfo")) { + static int warned; + if (!warned++) { + warning("'core.statinfo' will be removed in Git 2.0; " + "use 'core.checkstat' instead."); + } + } if (!strcasecmp(value, "default")) check_stat = 1; else if (!strcasecmp(value, "minimal")) @@ -954,23 +967,23 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) home_config_paths(&user_config, &xdg_config, "config"); - if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK)) { + if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) { ret += git_config_from_file(fn, git_etc_gitconfig(), data); found += 1; } - if (xdg_config && !access_or_die(xdg_config, R_OK)) { + if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) { ret += git_config_from_file(fn, xdg_config, data); found += 1; } - if (user_config && !access_or_die(user_config, R_OK)) { + if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) { ret += git_config_from_file(fn, user_config, data); found += 1; } - if (repo_config && !access_or_die(repo_config, R_OK)) { + if (repo_config && !access_or_die(repo_config, R_OK, 0)) { ret += git_config_from_file(fn, repo_config, data); found += 1; } diff --git a/config.mak.uname b/config.mak.uname index d78fd3df5b..174703b67c 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -95,6 +95,7 @@ ifeq ($(uname_S),Darwin) NO_MEMMEM = YesPlease USE_ST_TIMESPEC = YesPlease HAVE_DEV_TTY = YesPlease + NEEDS_CLIPPED_WRITE = YesPlease COMPAT_OBJS += compat/precompose_utf8.o BASIC_CFLAGS += -DPRECOMPOSE_UNICODE endif @@ -551,8 +551,11 @@ struct child_process *git_connect(int fd[2], const char *url_orig, path = strchr(end, c); if (path && !has_dos_drive_prefix(end)) { if (c == ':') { - protocol = PROTO_SSH; - *path++ = '\0'; + if (path < strchrnul(host, '/')) { + protocol = PROTO_SSH; + *path++ = '\0'; + } else /* '/' in the host part, assume local path */ + path = end; } } else path = end; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a98c2fd2de..91234d47ad 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -252,106 +252,50 @@ __gitcomp_file () # since tilde expansion is not applied. # This means that COMPREPLY will be empty and Bash default # completion will be used. - COMPREPLY=($(compgen -P "${2-}" -W "$1" -- "${3-$cur}")) + __gitcompadd "$1" "${2-}" "${3-$cur}" "" - # Tell Bash that compspec generates filenames. - compopt -o filenames 2>/dev/null + # use a hack to enable file mode in bash < 4 + compopt -o filenames +o nospace 2>/dev/null || + compgen -f /non-existing-dir/ > /dev/null } -__git_index_file_list_filter_compat () -{ - local path - - while read -r path; do - case "$path" in - ?*/*) echo "${path%%/*}/" ;; - *) echo "$path" ;; - esac - done -} - -__git_index_file_list_filter_bash () -{ - local path - - while read -r path; do - case "$path" in - ?*/*) - # XXX if we append a slash to directory names when using - # `compopt -o filenames`, Bash will append another slash. - # This is pretty stupid, and this the reason why we have to - # define a compatible version for this function. - echo "${path%%/*}" ;; - *) - echo "$path" ;; - esac - done -} - -# Process path list returned by "ls-files" and "diff-index --name-only" -# commands, in order to list only file names relative to a specified -# directory, and append a slash to directory names. -__git_index_file_list_filter () -{ - # Default to Bash >= 4.x - __git_index_file_list_filter_bash -} - -# Execute git ls-files, returning paths relative to the directory -# specified in the first argument, and using the options specified in -# the second argument. +# Execute 'git ls-files', unless the --committable option is specified, in +# which case it runs 'git diff-index' to find out the files that can be +# committed. It return paths relative to the directory specified in the first +# argument, and using the options specified in the second argument. __git_ls_files_helper () { ( test -n "${CDPATH+set}" && unset CDPATH - # NOTE: $2 is not quoted in order to support multiple options - cd "$1" && git ls-files --exclude-standard $2 + cd "$1" + if [ "$2" == "--committable" ]; then + git diff-index --name-only --relative HEAD + else + # NOTE: $2 is not quoted in order to support multiple options + git ls-files --exclude-standard $2 + fi ) 2>/dev/null } -# Execute git diff-index, returning paths relative to the directory -# specified in the first argument, and using the tree object id -# specified in the second argument. -__git_diff_index_helper () -{ - ( - test -n "${CDPATH+set}" && unset CDPATH - cd "$1" && git diff-index --name-only --relative "$2" - ) 2>/dev/null -} - # __git_index_files accepts 1 or 2 arguments: # 1: Options to pass to ls-files (required). -# Supported options are --cached, --modified, --deleted, --others, -# and --directory. # 2: A directory path (optional). # If provided, only files within the specified directory are listed. # Sub directories are never recursed. Path must have a trailing # slash. __git_index_files () { - local dir="$(__gitdir)" root="${2-.}" + local dir="$(__gitdir)" root="${2-.}" file if [ -d "$dir" ]; then - __git_ls_files_helper "$root" "$1" | __git_index_file_list_filter | - sort | uniq - fi -} - -# __git_diff_index_files accepts 1 or 2 arguments: -# 1) The id of a tree object. -# 2) A directory path (optional). -# If provided, only files within the specified directory are listed. -# Sub directories are never recursed. Path must have a trailing -# slash. -__git_diff_index_files () -{ - local dir="$(__gitdir)" root="${2-.}" - - if [ -d "$dir" ]; then - __git_diff_index_helper "$root" "$1" | __git_index_file_list_filter | - sort | uniq + __git_ls_files_helper "$root" "$1" | + while read -r file; do + case "$file" in + ?*/*) echo "${file%%/*}" ;; + *) echo "$file" ;; + esac + done | sort | uniq fi } @@ -552,44 +496,23 @@ __git_complete_revlist_file () } -# __git_complete_index_file requires 1 argument: the options to pass to -# ls-file +# __git_complete_index_file requires 1 argument: +# 1: the options to pass to ls-file +# +# The exception is --committable, which finds the files appropriate commit. __git_complete_index_file () { - local pfx cur_="$cur" + local pfx="" cur_="$cur" case "$cur_" in ?*/*) pfx="${cur_%/*}" cur_="${cur_##*/}" pfx="${pfx}/" - - __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_" - ;; - *) - __gitcomp_file "$(__git_index_files "$1")" "" "$cur_" ;; esac -} - -# __git_complete_diff_index_file requires 1 argument: the id of a tree -# object -__git_complete_diff_index_file () -{ - local pfx cur_="$cur" - case "$cur_" in - ?*/*) - pfx="${cur_%/*}" - cur_="${cur_##*/}" - pfx="${pfx}/" - - __gitcomp_file "$(__git_diff_index_files "$1" "$pfx")" "$pfx" "$cur_" - ;; - *) - __gitcomp_file "$(__git_diff_index_files "$1")" "" "$cur_" - ;; - esac + __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_" } __git_complete_file () @@ -1213,7 +1136,7 @@ _git_commit () esac if git rev-parse --verify --quiet HEAD >/dev/null; then - __git_complete_diff_index_file "HEAD" + __git_complete_index_file "--committable" else # This is the first commit __git_complete_index_file "--cached" @@ -1831,7 +1754,7 @@ _git_config () local remote="${prev#remote.}" remote="${remote%.fetch}" if [ -z "$cur" ]; then - __gitcompadd "refs/heads/" "" "" "" + __gitcomp_nl "refs/heads/" "" "" "" return fi __gitcomp_nl "$(__git_refs_remotes "$remote")" @@ -2663,7 +2586,7 @@ if [[ -n ${ZSH_VERSION-} ]]; then --*=*|*.) ;; *) c="$c " ;; esac - array[$#array+1]="$c" + array+=("$c") done compset -P '*[=:]' compadd -Q -S '' -p "${2-}" -a -- array && _ret=0 @@ -2689,35 +2612,19 @@ if [[ -n ${ZSH_VERSION-} ]]; then compadd -Q -p "${2-}" -f -- ${=1} && _ret=0 } - __git_zsh_helper () - { - emulate -L ksh - local cur cword prev - cur=${words[CURRENT-1]} - prev=${words[CURRENT-2]} - let cword=CURRENT-1 - __${service}_main - } - _git () { - emulate -L zsh - local _ret=1 - __git_zsh_helper - let _ret && _default -S '' && _ret=0 + local _ret=1 cur cword prev + cur=${words[CURRENT]} + prev=${words[CURRENT-1]} + let cword=CURRENT-1 + emulate ksh -c __${service}_main + let _ret && _default && _ret=0 return _ret } compdef _git git gitk return -elif [[ -n ${BASH_VERSION-} ]]; then - if ((${BASH_VERSINFO[0]} < 4)); then - # compopt is not supported - __git_index_file_list_filter () - { - __git_index_file_list_filter_compat - } - fi fi __git_func_wrap () diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index 2565d2eef4..fac5e711eb 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh @@ -4,18 +4,17 @@ # # Copyright (c) 2012-2013 Felipe Contreras <felipe.contreras@gmail.com> # -# You need git's bash completion script installed somewhere, by default on the -# same directory as this script. +# You need git's bash completion script installed somewhere, by default it +# would be the location bash-completion uses. # -# If your script is on ~/.git-completion.sh instead, you can configure it on -# your ~/.zshrc: +# If your script is somewhere else, you can configure it on your ~/.zshrc: # # zstyle ':completion:*:*:git:*' script ~/.git-completion.sh # -# The recommended way to install this script is to copy to -# '~/.zsh/completion/_git', and then add the following to your ~/.zshrc file: +# The recommended way to install this script is to copy to '~/.zsh/_git', and +# then add the following to your ~/.zshrc file: # -# fpath=(~/.zsh/completion $fpath) +# fpath=(~/.zsh $fpath) complete () { @@ -27,7 +26,19 @@ zstyle -T ':completion:*:*:git:*' tag-order && \ zstyle ':completion:*:*:git:*' tag-order 'common-commands' zstyle -s ":completion:*:*:git:*" script script -test -z "$script" && script="$(dirname ${funcsourcetrace[1]%:*})"/git-completion.bash +if [ -z "$script" ]; then + local -a locations + local e + locations=( + '/etc/bash_completion.d/git' # fedora, old debian + '/usr/share/bash-completion/completions/git' # arch, ubuntu, new debian + '/usr/share/bash-completion/git' # gentoo + $(dirname ${funcsourcetrace[1]%:*})/git-completion.bash + ) + for e in $locations; do + test -f $e && script="$e" && break + done +fi ZSH_VERSION='' . "$script" __gitcomp () diff --git a/contrib/remote-helpers/git-remote-bzr b/contrib/remote-helpers/git-remote-bzr index ad42317ca0..10300c63d1 100755 --- a/contrib/remote-helpers/git-remote-bzr +++ b/contrib/remote-helpers/git-remote-bzr @@ -31,6 +31,7 @@ import bzrlib.transport import bzrlib.errors import bzrlib.ui import bzrlib.urlutils +import bzrlib.branch import sys import os @@ -788,7 +789,7 @@ def get_remote_branch(origin, remote_branch, name): return branch def find_branches(repo, wanted): - transport = repo.user_transport + transport = repo.bzrdir.root_transport for fn in transport.iter_files_recursive(): if not fn.endswith('.bzr/branch-format'): @@ -837,6 +838,8 @@ def get_repo(url, alias): bdir.destroy_repository() except bzrlib.errors.NotBranchError: pass + except bzrlib.errors.NoRepositoryPresent: + pass try: repo = origin.open_repository() @@ -922,7 +925,8 @@ def main(args): if not os.path.exists(dirname): os.makedirs(dirname) - bzrlib.ui.ui_factory.be_quiet(True) + if hasattr(bzrlib.ui.ui_factory, 'be_quiet'): + bzrlib.ui.ui_factory.be_quiet(True) repo = get_repo(url, alias) diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg index beb864b57e..1dd3d7030e 100755 --- a/contrib/remote-helpers/git-remote-hg +++ b/contrib/remote-helpers/git-remote-hg @@ -25,9 +25,6 @@ import atexit import urlparse, hashlib # -# If you want to switch to hg-git compatibility mode: -# git config --global remote-hg.hg-git-compat true -# # If you are not in hg-git-compat mode and want to disable the tracking of # named branches: # git config --global remote-hg.track-branches false @@ -36,7 +33,10 @@ import urlparse, hashlib # git config --global remote-hg.force-push false # # If you want the equivalent of hg's clone/pull--insecure option: -# git config remote-hg.insecure true +# git config --global remote-hg.insecure true +# +# If you want to switch to hg-git compatibility mode: +# git config --global remote-hg.hg-git-compat true # # git: # Sensible defaults for git. @@ -954,6 +954,10 @@ def main(args): marks_path = os.path.join(dirname, 'marks-hg') marks = Marks(marks_path) + if sys.platform == 'win32': + import msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + parser = Parser(repo) for line in parser: if parser.check('capabilities'): diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 8a23f58ba0..10daa8b0eb 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -715,7 +715,8 @@ cmd_push() repository=$1 refspec=$2 echo "git push using: " $repository $refspec - git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec + localrev=$(git subtree split --prefix="$prefix") || die + git push $repository $localrev:refs/heads/$refspec else die "'$dir' must already exist. Try 'git subtree add'." fi diff --git a/credential-store.c b/credential-store.c index 26f7589a60..f9146e576f 100644 --- a/credential-store.c +++ b/credential-store.c @@ -114,7 +114,7 @@ static int lookup_credential(const char *fn, struct credential *c) return c->username && c->password; } -int main(int argc, const char **argv) +int main(int argc, char **argv) { const char * const usage[] = { "git credential-store [options] <action>", @@ -131,7 +131,7 @@ int main(int argc, const char **argv) umask(077); - argc = parse_options(argc, argv, NULL, options, usage, 0); + argc = parse_options(argc, (const char **)argv, NULL, options, usage, 0); if (argc != 1) usage_with_options(usage, options); op = argv[0]; @@ -711,6 +711,28 @@ int parse_date_basic(const char *date, unsigned long *timestamp, int *offset) return 0; /* success */ } +int parse_expiry_date(const char *date, unsigned long *timestamp) +{ + int errors = 0; + + if (!strcmp(date, "never") || !strcmp(date, "false")) + *timestamp = 0; + else if (!strcmp(date, "all") || !strcmp(date, "now")) + /* + * We take over "now" here, which usually translates + * to the current timestamp. This is because the user + * really means to expire everything she has done in + * the past, and by definition reflogs are the record + * of the past, and there is nothing from the future + * to be kept. + */ + *timestamp = ULONG_MAX; + else + *timestamp = approxidate_careful(date, &errors); + + return errors; +} + int parse_date(const char *date, char *result, int maxlen) { unsigned long timestamp; @@ -821,6 +821,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) dir->basebuf, stk->baselen - 1, dir->basebuf + current, &dt); dir->basebuf[stk->baselen - 1] = '/'; + if (dir->exclude && + dir->exclude->flags & EXC_FLAG_NEGATIVE) + dir->exclude = NULL; if (dir->exclude) { dir->basebuf[stk->baselen] = 0; dir->exclude_stack = stk; @@ -1542,9 +1545,9 @@ void setup_standard_excludes(struct dir_struct *dir) home_config_paths(NULL, &xdg_path, "ignore"); excludes_file = xdg_path; } - if (!access_or_warn(path, R_OK)) + if (!access_or_warn(path, R_OK, 0)) add_excludes_from_file(dir, path); - if (excludes_file && !access_or_warn(excludes_file, R_OK)) + if (excludes_file && !access_or_warn(excludes_file, R_OK, 0)) add_excludes_from_file(dir, excludes_file); } diff --git a/fast-import.c b/fast-import.c index 5f539d7d8f..23f625f561 100644 --- a/fast-import.c +++ b/fast-import.c @@ -297,7 +297,7 @@ static int failure; static FILE *pack_edges; static unsigned int show_stats = 1; static int global_argc; -static const char **global_argv; +static char **global_argv; /* Memory pools */ static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool); @@ -1822,7 +1822,7 @@ static void read_marks(void) *end = 0; mark = strtoumax(line + 1, &end, 10); if (!mark || end == line + 1 - || *end != ' ' || get_sha1(end + 1, sha1)) + || *end != ' ' || get_sha1_hex(end + 1, sha1)) die("corrupt mark line: %s", line); e = find_object(sha1); if (!e) { @@ -3347,7 +3347,7 @@ static void parse_argv(void) read_marks(); } -int main(int argc, const char **argv) +int main(int argc, char **argv) { unsigned int i; diff --git a/git-compat-util.h b/git-compat-util.h index e955bb5e8b..660b7f012a 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -185,6 +185,11 @@ int get_st_mode_bits(const char *path, int *mode); #define probe_utf8_pathname_composition(a,b) #endif +#ifdef NEEDS_CLIPPED_WRITE +ssize_t clipped_write(int fildes, const void *buf, size_t nbyte); +#define write(x,y,z) clipped_write((x),(y),(z)) +#endif + #ifdef MKDIR_WO_TRAILING_SLASH #define mkdir(a,b) compat_mkdir_wo_trailing_slash((a),(b)) extern int compat_mkdir_wo_trailing_slash(const char*, mode_t); @@ -692,8 +697,9 @@ int remove_or_warn(unsigned int mode, const char *path); * Call access(2), but warn for any error except "missing file" * (ENOENT or ENOTDIR). */ -int access_or_warn(const char *path, int mode); -int access_or_die(const char *path, int mode); +#define ACCESS_EACCES_OK (1U << 0) +int access_or_warn(const char *path, int mode, unsigned flag); +int access_or_die(const char *path, int mode, unsigned flag); /* Warn on an inaccessible file that ought to be accessible */ void warn_on_inaccessible(const char *path); diff --git a/git-difftool.perl b/git-difftool.perl index 67802922cc..8a75205537 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -92,6 +92,12 @@ sub use_wt_file return 0; } + if (! -e "$workdir/$file") { + # If the file doesn't exist in the working tree, we cannot + # use it. + return (0, $null_sha1); + } + my $wt_sha1 = $repo->command_oneline('hash-object', "$workdir/$file"); my $use = ($sha1 eq $null_sha1) || ($sha1 eq $wt_sha1); return ($use, $wt_sha1); diff --git a/git-remote-testgit b/git-remote-testgit.sh index e7ed3a33e6..2109070d00 100755 --- a/git-remote-testgit +++ b/git-remote-testgit.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh # Copyright (c) 2012 Felipe Contreras alias=$1 @@ -23,7 +23,6 @@ then testgitmarks="$dir/testgit.marks" test -e "$gitmarks" || >"$gitmarks" test -e "$testgitmarks" || >"$testgitmarks" - testgitmarks_args=( "--"{import,export}"-marks=$testgitmarks" ) fi while read line @@ -62,24 +61,55 @@ do echo "feature import-marks=$gitmarks" echo "feature export-marks=$gitmarks" fi + + if test -n "$GIT_REMOTE_TESTGIT_FAILURE" + then + echo "feature done" + exit 1 + fi + echo "feature done" - git fast-export "${testgitmarks_args[@]}" $refs | + git fast-export \ + ${testgitmarks:+"--import-marks=$testgitmarks"} \ + ${testgitmarks:+"--export-marks=$testgitmarks"} \ + $refs | sed -e "s#refs/heads/#${prefix}/heads/#g" echo "done" ;; export) - before=$(git for-each-ref --format='%(refname) %(objectname)') + if test -n "$GIT_REMOTE_TESTGIT_FAILURE" + then + # consume input so fast-export doesn't get SIGPIPE; + # git would also notice that case, but we want + # to make sure we are exercising the later + # error checks + while read line; do + test "done" = "$line" && break + done + exit 1 + fi - git fast-import "${testgitmarks_args[@]}" --quiet + before=$(git for-each-ref --format=' %(refname) %(objectname) ') - after=$(git for-each-ref --format='%(refname) %(objectname)') + git fast-import \ + ${testgitmarks:+"--import-marks=$testgitmarks"} \ + ${testgitmarks:+"--export-marks=$testgitmarks"} \ + --quiet # figure out which refs were updated - join -e 0 -o '0 1.2 2.2' -a 2 <(echo "$before") <(echo "$after") | - while read ref a b + git for-each-ref --format='%(refname) %(objectname)' | + while read ref a do - test $a == $b && continue - echo "ok $ref" + case "$before" in + *" $ref $a "*) + continue ;; # unchanged + esac + if test -z "$GIT_REMOTE_TESTGIT_PUSH_ERROR" + then + echo "ok $ref" + else + echo "error $ref $GIT_REMOTE_TESTGIT_PUSH_ERROR" + fi done echo diff --git a/git-svn.perl b/git-svn.perl index ccabe065f3..d070de012c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -113,7 +113,7 @@ my ($_stdin, $_help, $_edit, $_template, $_shared, $_version, $_fetch_all, $_no_rebase, $_fetch_parent, $_before, $_after, - $_merge, $_strategy, $_preserve_merges, $_dry_run, $_local, + $_merge, $_strategy, $_preserve_merges, $_dry_run, $_parents, $_local, $_prefix, $_no_checkout, $_url, $_verbose, $_commit_url, $_tag, $_merge_info, $_interactive); @@ -203,6 +203,7 @@ my %cmd = ( { 'message|m=s' => \$_message, 'destination|d=s' => \$_branch_dest, 'dry-run|n' => \$_dry_run, + 'parents' => \$_parents, 'tag|t' => \$_tag, 'username=s' => \$Git::SVN::Prompt::_username, 'commit-url=s' => \$_commit_url } ], @@ -211,6 +212,7 @@ my %cmd = ( { 'message|m=s' => \$_message, 'destination|d=s' => \$_branch_dest, 'dry-run|n' => \$_dry_run, + 'parents' => \$_parents, 'username=s' => \$Git::SVN::Prompt::_username, 'commit-url=s' => \$_commit_url } ], 'set-tree' => [ \&cmd_set_tree, @@ -1172,6 +1174,10 @@ sub cmd_branch { $ctx->ls($dst, 'HEAD', 0); } and die "branch ${branch_name} already exists\n"; + if ($_parents) { + mk_parent_dirs($ctx, $dst); + } + print "Copying ${src} at r${rev} to ${dst}...\n"; $ctx->copy($src, $rev, $dst) unless $_dry_run; @@ -1179,6 +1185,17 @@ sub cmd_branch { $gs->fetch_all; } +sub mk_parent_dirs { + my ($ctx, $parent) = @_; + $parent =~ s{/[^/]*$}{}; + + if (!eval{$ctx->ls($parent, 'HEAD', 0)}) { + mk_parent_dirs($ctx, $parent); + print "Creating parent folder ${parent} ...\n"; + $ctx->mkdir($parent) unless $_dry_run; + } +} + sub cmd_find_rev { my $revision_or_hash = shift or die "SVN or git revision required ", "as a command-line argument\n"; @@ -507,8 +507,9 @@ static int run_argv(int *argcp, const char ***argv) } -int main(int argc, const char **argv) +int main(int argc, char **av) { + const char **argv = (const char **) av; const char *cmd; startup_info = &git_startup_info; diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po index 8cc98dc079..df95e01b90 100644 --- a/gitk-git/po/sv.po +++ b/gitk-git/po/sv.po @@ -1,16 +1,16 @@ # Swedish translation for gitk -# Copyright (C) 2005-2012 Paul Mackerras +# Copyright (C) 2005-2013 Paul Mackerras # This file is distributed under the same license as the gitk package. # # Mikael Magnusson <mikachu@gmail.com>, 2008. -# Peter Krefting <peter@softwolves.pp.se>, 2008, 2009, 2010, 2012. +# Peter Krefting <peter@softwolves.pp.se>, 2008, 2009, 2010, 2012, 2013. # msgid "" msgstr "" "Project-Id-Version: sv\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-10-03 08:09+0100\n" -"PO-Revision-Date: 2012-10-03 08:13+0100\n" +"POT-Creation-Date: 2013-05-16 08:06+0100\n" +"PO-Revision-Date: 2013-05-16 08:12+0100\n" "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n" "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" "Language: sv\n" @@ -22,11 +22,11 @@ msgstr "" msgid "Couldn't get list of unmerged files:" msgstr "Kunde inte hämta lista över ej sammanslagna filer:" -#: gitk:210 gitk:2317 +#: gitk:210 gitk:2334 msgid "Color words" msgstr "Färga ord" -#: gitk:215 gitk:2317 gitk:7888 gitk:7921 +#: gitk:215 gitk:2334 gitk:7977 gitk:8010 msgid "Markup words" msgstr "Märk upp ord" @@ -60,11 +60,11 @@ msgstr "Fel vid körning av git log:" msgid "Reading" msgstr "Läser" -#: gitk:484 gitk:4353 +#: gitk:484 gitk:4409 msgid "Reading commits..." msgstr "Läser incheckningar..." -#: gitk:487 gitk:1625 gitk:4356 +#: gitk:487 gitk:1625 gitk:4412 msgid "No commits selected" msgstr "Inga incheckningar markerade" @@ -80,278 +80,286 @@ msgstr "Ingen incheckningsinformation är tillgänglig" msgid "mc" msgstr "mc" -#: gitk:1911 gitk:4146 gitk:9282 gitk:10824 gitk:11100 +#: gitk:1911 gitk:4202 gitk:9437 gitk:10979 gitk:11258 msgid "OK" msgstr "OK" -#: gitk:1913 gitk:4148 gitk:8871 gitk:8950 gitk:9065 gitk:9114 gitk:9284 -#: gitk:10825 gitk:11101 +#: gitk:1913 gitk:4204 gitk:8964 gitk:9043 gitk:9159 gitk:9208 gitk:9439 +#: gitk:10980 gitk:11259 msgid "Cancel" msgstr "Avbryt" -#: gitk:2040 +#: gitk:2048 msgid "Update" msgstr "Uppdatera" -#: gitk:2041 +#: gitk:2049 msgid "Reload" msgstr "Ladda om" -#: gitk:2042 +#: gitk:2050 msgid "Reread references" msgstr "Läs om referenser" -#: gitk:2043 +#: gitk:2051 msgid "List references" msgstr "Visa referenser" -#: gitk:2045 +#: gitk:2053 msgid "Start git gui" msgstr "Starta git gui" -#: gitk:2047 +#: gitk:2055 msgid "Quit" msgstr "Avsluta" -#: gitk:2039 +#: gitk:2047 msgid "File" msgstr "Arkiv" -#: gitk:2051 +#: gitk:2059 msgid "Preferences" msgstr "Inställningar" -#: gitk:2050 +#: gitk:2058 msgid "Edit" msgstr "Redigera" -#: gitk:2055 +#: gitk:2063 msgid "New view..." msgstr "Ny vy..." -#: gitk:2056 +#: gitk:2064 msgid "Edit view..." msgstr "Ändra vy..." -#: gitk:2057 +#: gitk:2065 msgid "Delete view" msgstr "Ta bort vy" -#: gitk:2059 +#: gitk:2067 msgid "All files" msgstr "Alla filer" -#: gitk:2054 gitk:3899 +#: gitk:2062 gitk:3955 msgid "View" msgstr "Visa" -#: gitk:2064 gitk:2074 gitk:2869 +#: gitk:2072 gitk:2082 gitk:2925 msgid "About gitk" msgstr "Om gitk" -#: gitk:2065 gitk:2079 +#: gitk:2073 gitk:2087 msgid "Key bindings" msgstr "Tangentbordsbindningar" -#: gitk:2063 gitk:2078 +#: gitk:2071 gitk:2086 msgid "Help" msgstr "Hjälp" -#: gitk:2156 gitk:8330 +#: gitk:2164 gitk:8420 msgid "SHA1 ID:" msgstr "SHA1-id:" -#: gitk:2192 +#: gitk:2208 msgid "Row" msgstr "Rad" -#: gitk:2230 +#: gitk:2246 msgid "Find" msgstr "Sök" -#: gitk:2231 +#: gitk:2247 msgid "next" msgstr "nästa" -#: gitk:2232 +#: gitk:2248 msgid "prev" msgstr "föreg" -#: gitk:2233 +#: gitk:2249 msgid "commit" msgstr "incheckning" -#: gitk:2236 gitk:2238 gitk:4514 gitk:4537 gitk:4561 gitk:6528 gitk:6600 -#: gitk:6685 +#: gitk:2252 gitk:2254 gitk:4570 gitk:4593 gitk:4617 gitk:6592 gitk:6664 +#: gitk:6749 msgid "containing:" msgstr "som innehÃ¥ller:" -#: gitk:2239 gitk:3381 gitk:3386 gitk:4590 +#: gitk:2255 gitk:3437 gitk:3442 gitk:4646 msgid "touching paths:" msgstr "som rör sökväg:" -#: gitk:2240 gitk:4604 +#: gitk:2256 gitk:4660 msgid "adding/removing string:" msgstr "som lägger/till tar bort sträng:" -#: gitk:2249 gitk:2251 gitk:4593 +#: gitk:2257 gitk:4662 +msgid "changing lines matching:" +msgstr "ändrar rader som matchar:" + +#: gitk:2266 gitk:2268 gitk:4649 msgid "Exact" msgstr "Exakt" -#: gitk:2251 gitk:4679 gitk:6496 +#: gitk:2268 gitk:4737 gitk:6560 msgid "IgnCase" msgstr "IgnVersaler" -#: gitk:2251 gitk:4563 gitk:4677 gitk:6492 +#: gitk:2268 gitk:4619 gitk:4735 gitk:6556 msgid "Regexp" msgstr "Reg.uttr." -#: gitk:2253 gitk:2254 gitk:4699 gitk:4729 gitk:4736 gitk:6621 gitk:6689 +#: gitk:2270 gitk:2271 gitk:4757 gitk:4787 gitk:4794 gitk:6685 gitk:6753 msgid "All fields" msgstr "Alla fält" -#: gitk:2254 gitk:4696 gitk:4729 gitk:6559 +#: gitk:2271 gitk:4754 gitk:4787 gitk:6623 msgid "Headline" msgstr "Rubrik" -#: gitk:2255 gitk:4696 gitk:6559 gitk:6689 gitk:7126 +#: gitk:2272 gitk:4754 gitk:6623 gitk:6753 gitk:7221 msgid "Comments" msgstr "Kommentarer" -#: gitk:2255 gitk:4696 gitk:4701 gitk:4736 gitk:6559 gitk:7061 gitk:8505 -#: gitk:8520 +#: gitk:2272 gitk:4754 gitk:4759 gitk:4794 gitk:6623 gitk:7156 gitk:8598 +#: gitk:8613 msgid "Author" msgstr "Författare" -#: gitk:2255 gitk:4696 gitk:6559 gitk:7063 +#: gitk:2272 gitk:4754 gitk:6623 gitk:7158 msgid "Committer" msgstr "Incheckare" -#: gitk:2286 +#: gitk:2303 msgid "Search" msgstr "Sök" -#: gitk:2294 +#: gitk:2311 msgid "Diff" msgstr "Diff" -#: gitk:2296 +#: gitk:2313 msgid "Old version" msgstr "Gammal version" -#: gitk:2298 +#: gitk:2315 msgid "New version" msgstr "Ny version" -#: gitk:2300 +#: gitk:2317 msgid "Lines of context" msgstr "Rader sammanhang" -#: gitk:2310 +#: gitk:2327 msgid "Ignore space change" msgstr "Ignorera ändringar i blanksteg" -#: gitk:2314 gitk:2316 gitk:7646 gitk:7874 +#: gitk:2331 gitk:2333 gitk:7735 gitk:7963 msgid "Line diff" msgstr "Rad-diff" -#: gitk:2379 +#: gitk:2397 msgid "Patch" msgstr "Patch" -#: gitk:2381 +#: gitk:2399 msgid "Tree" msgstr "Träd" -#: gitk:2540 gitk:2559 +#: gitk:2557 gitk:2577 msgid "Diff this -> selected" msgstr "Diff denna -> markerad" -#: gitk:2541 gitk:2560 +#: gitk:2558 gitk:2578 msgid "Diff selected -> this" msgstr "Diff markerad -> denna" -#: gitk:2542 gitk:2561 +#: gitk:2559 gitk:2579 msgid "Make patch" msgstr "Skapa patch" -#: gitk:2543 gitk:8929 +#: gitk:2560 gitk:9022 msgid "Create tag" msgstr "Skapa tagg" -#: gitk:2544 gitk:9045 +#: gitk:2561 gitk:9139 msgid "Write commit to file" msgstr "Skriv incheckning till fil" -#: gitk:2545 gitk:9102 +#: gitk:2562 gitk:9196 msgid "Create new branch" msgstr "Skapa ny gren" -#: gitk:2546 +#: gitk:2563 msgid "Cherry-pick this commit" msgstr "Plocka denna incheckning" -#: gitk:2547 +#: gitk:2564 msgid "Reset HEAD branch to here" msgstr "Ã…terställ HEAD-grenen hit" -#: gitk:2548 +#: gitk:2565 msgid "Mark this commit" msgstr "Markera denna incheckning" -#: gitk:2549 +#: gitk:2566 msgid "Return to mark" msgstr "Ã…tergÃ¥ till markering" -#: gitk:2550 +#: gitk:2567 msgid "Find descendant of this and mark" msgstr "Hitta efterföljare till denna och markera" -#: gitk:2551 +#: gitk:2568 msgid "Compare with marked commit" msgstr "Jämför med markerad incheckning" -#: gitk:2552 gitk:2562 +#: gitk:2569 gitk:2580 msgid "Diff this -> marked commit" msgstr "Diff denna -> markerad incheckning" -#: gitk:2553 gitk:2563 +#: gitk:2570 gitk:2581 msgid "Diff marked commit -> this" msgstr "Diff markerad incheckning -> denna" -#: gitk:2569 +#: gitk:2571 +msgid "Revert this commit" +msgstr "Ã…ngra denna incheckning" + +#: gitk:2587 msgid "Check out this branch" msgstr "Checka ut denna gren" -#: gitk:2570 +#: gitk:2588 msgid "Remove this branch" msgstr "Ta bort denna gren" -#: gitk:2577 +#: gitk:2595 msgid "Highlight this too" msgstr "Markera även detta" -#: gitk:2578 +#: gitk:2596 msgid "Highlight this only" msgstr "Markera bara detta" -#: gitk:2579 +#: gitk:2597 msgid "External diff" msgstr "Extern diff" -#: gitk:2580 +#: gitk:2598 msgid "Blame parent commit" msgstr "Klandra föräldraincheckning" -#: gitk:2587 +#: gitk:2605 msgid "Show origin of this line" msgstr "Visa ursprunget för den här raden" -#: gitk:2588 +#: gitk:2606 msgid "Run git gui blame on this line" msgstr "Kör git gui blame pÃ¥ den här raden" -#: gitk:2871 +#: gitk:2927 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -367,302 +375,302 @@ msgstr "" "\n" "Använd och vidareförmedla enligt villkoren i GNU General Public License" -#: gitk:2879 gitk:2944 gitk:9468 +#: gitk:2935 gitk:3000 gitk:9623 msgid "Close" msgstr "Stäng" -#: gitk:2900 +#: gitk:2956 msgid "Gitk key bindings" msgstr "Tangentbordsbindningar för Gitk" -#: gitk:2903 +#: gitk:2959 msgid "Gitk key bindings:" msgstr "Tangentbordsbindningar för Gitk:" -#: gitk:2905 +#: gitk:2961 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tAvsluta" -#: gitk:2906 +#: gitk:2962 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tStäng fönster" -#: gitk:2907 +#: gitk:2963 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tGÃ¥ till första incheckning" -#: gitk:2908 +#: gitk:2964 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tGÃ¥ till sista incheckning" -#: gitk:2909 +#: gitk:2965 msgid "<Up>, p, k\tMove up one commit" msgstr "<Upp>, p, k\tGÃ¥ en incheckning upp" -#: gitk:2910 +#: gitk:2966 msgid "<Down>, n, j\tMove down one commit" msgstr "<Ned>, n, j\tGÃ¥ en incheckning ned" -#: gitk:2911 +#: gitk:2967 msgid "<Left>, z, h\tGo back in history list" msgstr "<Vänster>, z, h\tGÃ¥ bakÃ¥t i historiken" -#: gitk:2912 +#: gitk:2968 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Höger>, x, l\tGÃ¥ framÃ¥t i historiken" -#: gitk:2913 +#: gitk:2969 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tGÃ¥ upp en sida i incheckningslistan" -#: gitk:2914 +#: gitk:2970 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tGÃ¥ ned en sida i incheckningslistan" -#: gitk:2915 +#: gitk:2971 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tRulla till början av incheckningslistan" -#: gitk:2916 +#: gitk:2972 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tRulla till slutet av incheckningslistan" -#: gitk:2917 +#: gitk:2973 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg" -#: gitk:2918 +#: gitk:2974 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg" -#: gitk:2919 +#: gitk:2975 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida" -#: gitk:2920 +#: gitk:2976 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida" -#: gitk:2921 +#: gitk:2977 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Skift-Upp>\tSök bakÃ¥t (uppÃ¥t, senare incheckningar)" -#: gitk:2922 +#: gitk:2978 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Skift-Ned>\tSök framÃ¥t (nedÃ¥t, tidigare incheckningar)" -#: gitk:2923 +#: gitk:2979 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tRulla diffvisningen upp en sida" -#: gitk:2924 +#: gitk:2980 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Baksteg>\tRulla diffvisningen upp en sida" -#: gitk:2925 +#: gitk:2981 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Blanksteg>\tRulla diffvisningen ned en sida" -#: gitk:2926 +#: gitk:2982 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tRulla diffvisningen upp 18 rader" -#: gitk:2927 +#: gitk:2983 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tRulla diffvisningen ned 18 rader" -#: gitk:2928 +#: gitk:2984 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tSök" -#: gitk:2929 +#: gitk:2985 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tGÃ¥ till nästa sökträff" -#: gitk:2930 +#: gitk:2986 msgid "<Return>\tMove to next find hit" msgstr "<Return>\t\tGÃ¥ till nästa sökträff" -#: gitk:2931 +#: gitk:2987 msgid "/\t\tFocus the search box" msgstr "/\t\tFokusera sökrutan" -#: gitk:2932 +#: gitk:2988 msgid "?\t\tMove to previous find hit" msgstr "?\t\tGÃ¥ till föregÃ¥ende sökträff" -#: gitk:2933 +#: gitk:2989 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tRulla diffvisningen till nästa fil" -#: gitk:2934 +#: gitk:2990 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tGÃ¥ till nästa sökträff i diffvisningen" -#: gitk:2935 +#: gitk:2991 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tGÃ¥ till föregÃ¥ende sökträff i diffvisningen" -#: gitk:2936 +#: gitk:2992 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-Num+>\tÖka teckenstorlek" -#: gitk:2937 +#: gitk:2993 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tÖka teckenstorlek" -#: gitk:2938 +#: gitk:2994 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-Num->\tMinska teckenstorlek" -#: gitk:2939 +#: gitk:2995 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tMinska teckenstorlek" -#: gitk:2940 +#: gitk:2996 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tUppdatera" -#: gitk:3395 gitk:3404 +#: gitk:3451 gitk:3460 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Fel vid skapande av temporär katalog %s:" -#: gitk:3417 +#: gitk:3473 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Fel vid hämtning av \"%s\" frÃ¥n %s:" -#: gitk:3480 +#: gitk:3536 msgid "command failed:" msgstr "kommando misslyckades:" -#: gitk:3629 +#: gitk:3685 msgid "No such commit" msgstr "Incheckning saknas" -#: gitk:3643 +#: gitk:3699 msgid "git gui blame: command failed:" msgstr "git gui blame: kommando misslyckades:" -#: gitk:3674 +#: gitk:3730 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Kunde inte läsa sammanslagningshuvud: %s" -#: gitk:3682 +#: gitk:3738 #, tcl-format msgid "Error reading index: %s" msgstr "Fel vid läsning av index: %s" -#: gitk:3707 +#: gitk:3763 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Kunde inte starta git blame: %s" -#: gitk:3710 gitk:6527 +#: gitk:3766 gitk:6591 msgid "Searching" msgstr "Söker" -#: gitk:3742 +#: gitk:3798 #, tcl-format msgid "Error running git blame: %s" msgstr "Fel vid körning av git blame: %s" -#: gitk:3770 +#: gitk:3826 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Raden kommer frÃ¥n incheckningen %s, som inte finns i denna vy" -#: gitk:3784 +#: gitk:3840 msgid "External diff viewer failed:" msgstr "Externt diff-verktyg misslyckades:" -#: gitk:3902 +#: gitk:3958 msgid "Gitk view definition" msgstr "Definition av Gitk-vy" -#: gitk:3906 +#: gitk:3962 msgid "Remember this view" msgstr "Spara denna vy" -#: gitk:3907 +#: gitk:3963 msgid "References (space separated list):" msgstr "Referenser (blankstegsavdelad lista):" -#: gitk:3908 +#: gitk:3964 msgid "Branches & tags:" msgstr "Grenar & taggar:" -#: gitk:3909 +#: gitk:3965 msgid "All refs" msgstr "Alla referenser" -#: gitk:3910 +#: gitk:3966 msgid "All (local) branches" msgstr "Alla (lokala) grenar" -#: gitk:3911 +#: gitk:3967 msgid "All tags" msgstr "Alla taggar" -#: gitk:3912 +#: gitk:3968 msgid "All remote-tracking branches" msgstr "Alla fjärrspÃ¥rande grenar" -#: gitk:3913 +#: gitk:3969 msgid "Commit Info (regular expressions):" msgstr "Incheckningsinfo (reguljära uttryck):" -#: gitk:3914 +#: gitk:3970 msgid "Author:" msgstr "Författare:" -#: gitk:3915 +#: gitk:3971 msgid "Committer:" msgstr "Incheckare:" -#: gitk:3916 +#: gitk:3972 msgid "Commit Message:" msgstr "Incheckningsmeddelande:" -#: gitk:3917 +#: gitk:3973 msgid "Matches all Commit Info criteria" msgstr "Motsvarar alla kriterier för incheckningsinfo" -#: gitk:3918 +#: gitk:3974 msgid "Changes to Files:" msgstr "Ändringar av filer:" -#: gitk:3919 +#: gitk:3975 msgid "Fixed String" msgstr "Fast sträng" -#: gitk:3920 +#: gitk:3976 msgid "Regular Expression" msgstr "Reguljärt uttryck" -#: gitk:3921 +#: gitk:3977 msgid "Search string:" msgstr "Söksträng:" -#: gitk:3922 +#: gitk:3978 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -670,197 +678,201 @@ msgstr "" "Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -#: gitk:3923 +#: gitk:3979 msgid "Since:" msgstr "FrÃ¥n:" -#: gitk:3924 +#: gitk:3980 msgid "Until:" msgstr "Till:" -#: gitk:3925 +#: gitk:3981 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):" -#: gitk:3926 +#: gitk:3982 msgid "Number to show:" msgstr "Antal att visa:" -#: gitk:3927 +#: gitk:3983 msgid "Number to skip:" msgstr "Antal att hoppa över:" -#: gitk:3928 +#: gitk:3984 msgid "Miscellaneous options:" msgstr "Diverse alternativ:" -#: gitk:3929 +#: gitk:3985 msgid "Strictly sort by date" msgstr "Strikt datumsortering" -#: gitk:3930 +#: gitk:3986 msgid "Mark branch sides" msgstr "Markera sidogrenar" -#: gitk:3931 +#: gitk:3987 msgid "Limit to first parent" msgstr "Begränsa till första förälder" -#: gitk:3932 +#: gitk:3988 msgid "Simple history" msgstr "Enkel historik" -#: gitk:3933 +#: gitk:3989 msgid "Additional arguments to git log:" msgstr "Ytterligare argument till git log:" -#: gitk:3934 +#: gitk:3990 msgid "Enter files and directories to include, one per line:" msgstr "Ange filer och kataloger att ta med, en per rad:" -#: gitk:3935 +#: gitk:3991 msgid "Command to generate more commits to include:" msgstr "Kommando för att generera fler incheckningar att ta med:" -#: gitk:4059 +#: gitk:4115 msgid "Gitk: edit view" msgstr "Gitk: redigera vy" -#: gitk:4067 +#: gitk:4123 msgid "-- criteria for selecting revisions" msgstr " - kriterier för val av revisioner" -#: gitk:4072 +#: gitk:4128 msgid "View Name" msgstr "Namn pÃ¥ vy" -#: gitk:4147 +#: gitk:4203 msgid "Apply (F5)" msgstr "Använd (F5)" -#: gitk:4185 +#: gitk:4241 msgid "Error in commit selection arguments:" msgstr "Fel i argument för val av incheckningar:" -#: gitk:4238 gitk:4290 gitk:4749 gitk:4763 gitk:6027 gitk:11849 gitk:11850 +#: gitk:4294 gitk:4346 gitk:4807 gitk:4821 gitk:6087 gitk:12041 gitk:12042 msgid "None" msgstr "Inget" -#: gitk:4846 gitk:4851 +#: gitk:4904 gitk:4909 msgid "Descendant" msgstr "Avkomling" -#: gitk:4847 +#: gitk:4905 msgid "Not descendant" msgstr "Inte avkomling" -#: gitk:4854 gitk:4859 +#: gitk:4912 gitk:4917 msgid "Ancestor" msgstr "Förfader" -#: gitk:4855 +#: gitk:4913 msgid "Not ancestor" msgstr "Inte förfader" -#: gitk:5145 +#: gitk:5203 msgid "Local changes checked in to index but not committed" msgstr "Lokala ändringar sparade i indexet men inte incheckade" -#: gitk:5181 +#: gitk:5239 msgid "Local uncommitted changes, not checked in to index" msgstr "Lokala ändringar, ej sparade i indexet" -#: gitk:6882 +#: gitk:6971 +msgid "and many more" +msgstr "med mÃ¥nga flera" + +#: gitk:6974 msgid "many" msgstr "mÃ¥nga" -#: gitk:7065 +#: gitk:7160 msgid "Tags:" msgstr "Taggar:" -#: gitk:7082 gitk:7088 gitk:8500 +#: gitk:7177 gitk:7183 gitk:8593 msgid "Parent" msgstr "Förälder" -#: gitk:7093 +#: gitk:7188 msgid "Child" msgstr "Barn" -#: gitk:7102 +#: gitk:7197 msgid "Branch" msgstr "Gren" -#: gitk:7105 +#: gitk:7200 msgid "Follows" msgstr "Följer" -#: gitk:7108 +#: gitk:7203 msgid "Precedes" msgstr "FöregÃ¥r" -#: gitk:7653 +#: gitk:7742 #, tcl-format msgid "Error getting diffs: %s" msgstr "Fel vid hämtning av diff: %s" -#: gitk:8328 +#: gitk:8418 msgid "Goto:" msgstr "GÃ¥ till:" -#: gitk:8349 +#: gitk:8439 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Förkortat SHA1-id %s är tvetydigt" -#: gitk:8356 +#: gitk:8446 #, tcl-format msgid "Revision %s is not known" msgstr "Revisionen %s är inte känd" -#: gitk:8366 +#: gitk:8456 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA-id:t %s är inte känt" -#: gitk:8368 +#: gitk:8458 #, tcl-format msgid "Revision %s is not in the current view" msgstr "Revisionen %s finns inte i den nuvarande vyn" -#: gitk:8507 gitk:8522 +#: gitk:8600 gitk:8615 msgid "Date" msgstr "Datum" -#: gitk:8510 +#: gitk:8603 msgid "Children" msgstr "Barn" -#: gitk:8573 +#: gitk:8666 #, tcl-format msgid "Reset %s branch to here" msgstr "Ã…terställ grenen %s hit" -#: gitk:8575 +#: gitk:8668 msgid "Detached head: can't reset" msgstr "FrÃ¥nkopplad head: kan inte Ã¥terställa" -#: gitk:8680 gitk:8686 +#: gitk:8773 gitk:8779 msgid "Skipping merge commit " msgstr "Hoppar över sammanslagningsincheckning " -#: gitk:8695 gitk:8700 +#: gitk:8788 gitk:8793 msgid "Error getting patch ID for " msgstr "Fel vid hämtning av patch-id för " -#: gitk:8696 gitk:8701 +#: gitk:8789 gitk:8794 msgid " - stopping\n" msgstr " - stannar\n" -#: gitk:8706 gitk:8709 gitk:8717 gitk:8731 gitk:8740 +#: gitk:8799 gitk:8802 gitk:8810 gitk:8824 gitk:8833 msgid "Commit " msgstr "Incheckning " -#: gitk:8710 +#: gitk:8803 msgid "" " is the same patch as\n" " " @@ -868,7 +880,7 @@ msgstr "" " är samma patch som\n" " " -#: gitk:8718 +#: gitk:8811 msgid "" " differs from\n" " " @@ -876,7 +888,7 @@ msgstr "" " skiljer sig frÃ¥n\n" " " -#: gitk:8720 +#: gitk:8813 msgid "" "Diff of commits:\n" "\n" @@ -884,131 +896,131 @@ msgstr "" "Skillnad mellan incheckningar:\n" "\n" -#: gitk:8732 gitk:8741 +#: gitk:8825 gitk:8834 #, tcl-format msgid " has %s children - stopping\n" msgstr " har %s barn - stannar\n" -#: gitk:8760 +#: gitk:8853 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Fel vid skrivning av incheckning till fil: %s" -#: gitk:8766 +#: gitk:8859 #, tcl-format msgid "Error diffing commits: %s" msgstr "Fel vid jämförelse av incheckningar: %s" -#: gitk:8812 +#: gitk:8905 msgid "Top" msgstr "Topp" -#: gitk:8813 +#: gitk:8906 msgid "From" msgstr "FrÃ¥n" -#: gitk:8818 +#: gitk:8911 msgid "To" msgstr "Till" -#: gitk:8842 +#: gitk:8935 msgid "Generate patch" msgstr "Generera patch" -#: gitk:8844 +#: gitk:8937 msgid "From:" msgstr "FrÃ¥n:" -#: gitk:8853 +#: gitk:8946 msgid "To:" msgstr "Till:" -#: gitk:8862 +#: gitk:8955 msgid "Reverse" msgstr "Vänd" -#: gitk:8864 gitk:9059 +#: gitk:8957 gitk:9153 msgid "Output file:" msgstr "Utdatafil:" -#: gitk:8870 +#: gitk:8963 msgid "Generate" msgstr "Generera" -#: gitk:8908 +#: gitk:9001 msgid "Error creating patch:" msgstr "Fel vid generering av patch:" -#: gitk:8931 gitk:9047 gitk:9104 +#: gitk:9024 gitk:9141 gitk:9198 msgid "ID:" msgstr "Id:" -#: gitk:8940 +#: gitk:9033 msgid "Tag name:" msgstr "Taggnamn:" -#: gitk:8943 +#: gitk:9036 msgid "Tag message is optional" msgstr "Taggmeddelandet är valfritt" -#: gitk:8945 +#: gitk:9038 msgid "Tag message:" msgstr "Taggmeddelande:" -#: gitk:8949 gitk:9113 +#: gitk:9042 gitk:9207 msgid "Create" msgstr "Skapa" -#: gitk:8967 +#: gitk:9060 msgid "No tag name specified" msgstr "Inget taggnamn angavs" -#: gitk:8971 +#: gitk:9064 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Taggen \"%s\" finns redan" -#: gitk:8981 +#: gitk:9074 msgid "Error creating tag:" msgstr "Fel vid skapande av tagg:" -#: gitk:9056 +#: gitk:9150 msgid "Command:" msgstr "Kommando:" -#: gitk:9064 +#: gitk:9158 msgid "Write" msgstr "Skriv" -#: gitk:9082 +#: gitk:9176 msgid "Error writing commit:" msgstr "Fel vid skrivning av incheckning:" -#: gitk:9109 +#: gitk:9203 msgid "Name:" msgstr "Namn:" -#: gitk:9132 +#: gitk:9226 msgid "Please specify a name for the new branch" msgstr "Ange ett namn för den nya grenen" -#: gitk:9137 +#: gitk:9231 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Grenen \"%s\" finns redan. Skriva över?" -#: gitk:9204 +#: gitk:9298 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "Incheckningen %s finns redan pÃ¥ grenen %s -- skall den verkligen appliceras " "pÃ¥ nytt?" -#: gitk:9209 +#: gitk:9303 msgid "Cherry-picking" msgstr "Plockar" -#: gitk:9218 +#: gitk:9312 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1018,7 +1030,7 @@ msgstr "" "Checka in, Ã¥terställ eller spara undan (stash) dina ändringar och försök " "igen." -#: gitk:9224 +#: gitk:9318 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1026,32 +1038,59 @@ msgstr "" "Cherry-pick misslyckades pÃ¥ grund av en sammanslagningskonflikt.\n" "Vill du köra git citool för att lösa den?" -#: gitk:9240 +#: gitk:9334 gitk:9392 msgid "No changes committed" msgstr "Inga ändringar incheckade" -#: gitk:9266 +#: gitk:9361 +#, tcl-format +msgid "Commit %s is not included in branch %s -- really revert it?" +msgstr "Incheckningen %s finns inte pÃ¥ grenen %s -- vill du verkligen Ã¥ngra?" + +#: gitk:9366 +msgid "Reverting" +msgstr "Ã…ngrar" + +#: gitk:9374 +#, tcl-format +msgid "" +"Revert failed because of local changes to the following files:%s Please " +"commit, reset or stash your changes and try again." +msgstr "" +"Misslyckades med att Ã¥ngra pÃ¥ grund av lokala ändringar i följande filer:%s. " +"Checka in, Ã¥terställ eller spara undan (stash) dina ändringar och försök " +"igen." + +#: gitk:9378 +msgid "" +"Revert failed because of merge conflict.\n" +" Do you wish to run git citool to resolve it?" +msgstr "" +"Misslyckades med att Ã¥ngra pÃ¥ grund av en sammanslagningskonflikt.\n" +" Vill du köra git citool för att lösa den?" + +#: gitk:9421 msgid "Confirm reset" msgstr "Bekräfta Ã¥terställning" -#: gitk:9268 +#: gitk:9423 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Ã…terställa grenen %s till %s?" -#: gitk:9270 +#: gitk:9425 msgid "Reset type:" msgstr "Typ av Ã¥terställning:" -#: gitk:9273 +#: gitk:9428 msgid "Soft: Leave working tree and index untouched" msgstr "Mjuk: Rör inte utcheckning och index" -#: gitk:9276 +#: gitk:9431 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Blandad: Rör inte utcheckning, Ã¥terställ index" -#: gitk:9279 +#: gitk:9434 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1059,19 +1098,19 @@ msgstr "" "HÃ¥rd: Ã…terställ utcheckning och index\n" "(förkastar ALLA lokala ändringar)" -#: gitk:9296 +#: gitk:9451 msgid "Resetting" msgstr "Ã…terställer" -#: gitk:9356 +#: gitk:9511 msgid "Checking out" msgstr "Checkar ut" -#: gitk:9409 +#: gitk:9564 msgid "Cannot delete the currently checked-out branch" msgstr "Kan inte ta bort den just nu utcheckade grenen" -#: gitk:9415 +#: gitk:9570 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1080,16 +1119,16 @@ msgstr "" "Incheckningarna pÃ¥ grenen %s existerar inte pÃ¥ nÃ¥gon annan gren.\n" "Vill du verkligen ta bort grenen %s?" -#: gitk:9446 +#: gitk:9601 #, tcl-format msgid "Tags and heads: %s" msgstr "Taggar och huvuden: %s" -#: gitk:9461 +#: gitk:9616 msgid "Filter" msgstr "Filter" -#: gitk:9757 +#: gitk:9912 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1097,210 +1136,214 @@ msgstr "" "Fel vid läsning av information om incheckningstopologi; information om " "grenar och föregÃ¥ende/senare taggar kommer inte vara komplett." -#: gitk:10744 +#: gitk:10899 msgid "Tag" msgstr "Tagg" -#: gitk:10744 +#: gitk:10899 msgid "Id" msgstr "Id" -#: gitk:10793 +#: gitk:10948 msgid "Gitk font chooser" msgstr "Teckensnittsväljare för Gitk" -#: gitk:10810 +#: gitk:10965 msgid "B" msgstr "F" -#: gitk:10813 +#: gitk:10968 msgid "I" msgstr "K" -#: gitk:10931 +#: gitk:11086 msgid "Commit list display options" msgstr "Alternativ för incheckningslistvy" -#: gitk:10934 +#: gitk:11089 msgid "Maximum graph width (lines)" msgstr "Maximal grafbredd (rader)" -#: gitk:10937 +#: gitk:11092 #, tcl-format msgid "Maximum graph width (% of pane)" msgstr "Maximal grafbredd (% av ruta)" -#: gitk:10940 +#: gitk:11095 msgid "Show local changes" msgstr "Visa lokala ändringar" -#: gitk:10943 +#: gitk:11098 msgid "Auto-select SHA1 (length)" msgstr "Välj SHA1 (längd) automatiskt" -#: gitk:10947 +#: gitk:11102 msgid "Hide remote refs" msgstr "Dölj fjärr-referenser" -#: gitk:10951 +#: gitk:11106 msgid "Diff display options" msgstr "Alternativ för diffvy" -#: gitk:10953 +#: gitk:11108 msgid "Tab spacing" msgstr "Blanksteg för tabulatortecken" -#: gitk:10956 -msgid "Display nearby tags" -msgstr "Visa närliggande taggar" +#: gitk:11111 +msgid "Display nearby tags/heads" +msgstr "Visa närliggande taggar/huvuden" + +#: gitk:11114 +msgid "Maximum # tags/heads to show" +msgstr "Maximalt antal taggar/huvuden att visa" -#: gitk:10959 +#: gitk:11117 msgid "Limit diffs to listed paths" msgstr "Begränsa diff till listade sökvägar" -#: gitk:10962 +#: gitk:11120 msgid "Support per-file encodings" msgstr "Stöd för filspecifika teckenkodningar" -#: gitk:10968 gitk:11115 +#: gitk:11126 gitk:11273 msgid "External diff tool" msgstr "Externt diff-verktyg" -#: gitk:10969 +#: gitk:11127 msgid "Choose..." msgstr "Välj..." -#: gitk:10974 +#: gitk:11132 msgid "General options" msgstr "Allmänna inställningar" -#: gitk:10977 +#: gitk:11135 msgid "Use themed widgets" msgstr "Använd tema pÃ¥ fönsterelement" -#: gitk:10979 +#: gitk:11137 msgid "(change requires restart)" msgstr "(ändringen kräver omstart)" -#: gitk:10981 +#: gitk:11139 msgid "(currently unavailable)" msgstr "(för närvarande inte tillgängligt)" -#: gitk:10992 +#: gitk:11150 msgid "Colors: press to choose" msgstr "Färger: tryck för att välja" -#: gitk:10995 +#: gitk:11153 msgid "Interface" msgstr "Gränssnitt" -#: gitk:10996 +#: gitk:11154 msgid "interface" msgstr "gränssnitt" -#: gitk:10999 +#: gitk:11157 msgid "Background" msgstr "Bakgrund" -#: gitk:11000 gitk:11030 +#: gitk:11158 gitk:11188 msgid "background" msgstr "bakgrund" -#: gitk:11003 +#: gitk:11161 msgid "Foreground" msgstr "Förgrund" -#: gitk:11004 +#: gitk:11162 msgid "foreground" msgstr "förgrund" -#: gitk:11007 +#: gitk:11165 msgid "Diff: old lines" msgstr "Diff: gamla rader" -#: gitk:11008 +#: gitk:11166 msgid "diff old lines" msgstr "diff gamla rader" -#: gitk:11012 +#: gitk:11170 msgid "Diff: new lines" msgstr "Diff: nya rader" -#: gitk:11013 +#: gitk:11171 msgid "diff new lines" msgstr "diff nya rader" -#: gitk:11017 +#: gitk:11175 msgid "Diff: hunk header" msgstr "Diff: delhuvud" -#: gitk:11019 +#: gitk:11177 msgid "diff hunk header" msgstr "diff delhuvud" -#: gitk:11023 +#: gitk:11181 msgid "Marked line bg" msgstr "Markerad rad bakgrund" -#: gitk:11025 +#: gitk:11183 msgid "marked line background" msgstr "markerad rad bakgrund" -#: gitk:11029 +#: gitk:11187 msgid "Select bg" msgstr "Markerad bakgrund" -#: gitk:11038 +#: gitk:11196 msgid "Fonts: press to choose" msgstr "Teckensnitt: tryck för att välja" -#: gitk:11040 +#: gitk:11198 msgid "Main font" msgstr "Huvudteckensnitt" -#: gitk:11041 +#: gitk:11199 msgid "Diff display font" msgstr "Teckensnitt för diffvisning" -#: gitk:11042 +#: gitk:11200 msgid "User interface font" msgstr "Teckensnitt för användargränssnitt" -#: gitk:11064 +#: gitk:11222 msgid "Gitk preferences" msgstr "Inställningar för Gitk" -#: gitk:11073 +#: gitk:11231 msgid "General" msgstr "Allmänt" -#: gitk:11074 +#: gitk:11232 msgid "Colors" msgstr "Färger" -#: gitk:11075 +#: gitk:11233 msgid "Fonts" msgstr "Teckensnitt" -#: gitk:11125 +#: gitk:11283 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: välj färg för %s" -#: gitk:11745 +#: gitk:11937 msgid "Cannot find a git repository here." msgstr "Hittar inget git-arkiv här." -#: gitk:11792 +#: gitk:11984 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Tvetydigt argument \"%s\": bÃ¥de revision och filnamn" -#: gitk:11804 +#: gitk:11996 msgid "Bad arguments to gitk:" msgstr "Felaktiga argument till gitk:" -#: gitk:11907 +#: gitk:12099 msgid "Command line" msgstr "Kommandorad" @@ -7,6 +7,7 @@ #include "string-list.h" #include "column.h" #include "version.h" +#include "refs.h" void add_cmdname(struct cmdnames *cmds, const char *name, int len) { @@ -404,3 +405,52 @@ int cmd_version(int argc, const char **argv, const char *prefix) printf("git version %s\n", git_version_string); return 0; } + +struct similar_ref_cb { + const char *base_ref; + struct string_list *similar_refs; +}; + +static int append_similar_ref(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) +{ + struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data); + char *branch = strrchr(refname, '/') + 1; + /* A remote branch of the same name is deemed similar */ + if (!prefixcmp(refname, "refs/remotes/") && + !strcmp(branch, cb->base_ref)) + string_list_append(cb->similar_refs, + refname + strlen("refs/remotes/")); + return 0; +} + +static struct string_list guess_refs(const char *ref) +{ + struct similar_ref_cb ref_cb; + struct string_list similar_refs = STRING_LIST_INIT_NODUP; + + ref_cb.base_ref = ref; + ref_cb.similar_refs = &similar_refs; + for_each_ref(append_similar_ref, &ref_cb); + return similar_refs; +} + +void help_unknown_ref(const char *ref, const char *cmd, const char *error) +{ + int i; + struct string_list suggested_refs = guess_refs(ref); + + fprintf_ln(stderr, _("%s: %s - %s"), cmd, ref, error); + + if (suggested_refs.nr > 0) { + fprintf_ln(stderr, + Q_("\nDid you mean this?", + "\nDid you mean one of these?", + suggested_refs.nr)); + for (i = 0; i < suggested_refs.nr; i++) + fprintf(stderr, "\t%s\n", suggested_refs.items[i].string); + } + + string_list_clear(&suggested_refs, 0); + exit(1); +} @@ -27,4 +27,9 @@ extern void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes); extern int is_in_cmdlist(struct cmdnames *cmds, const char *name); extern void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds); +/* + * call this to die(), when it is suspected that the user mistyped a + * ref to the command, to give suggested "correct" refs. + */ +extern void help_unknown_ref(const char *ref, const char *cmd, const char *error); #endif /* HELP_H */ diff --git a/imap-send.c b/imap-send.c index d9bcfb44dc..d6b65e204c 100644 --- a/imap-send.c +++ b/imap-send.c @@ -29,8 +29,18 @@ #ifdef NO_OPENSSL typedef void *SSL; #else +#ifdef APPLE_COMMON_CRYPTO +#include <CommonCrypto/CommonHMAC.h> +#define HMAC_CTX CCHmacContext +#define HMAC_Init(hmac, key, len, algo) CCHmacInit(hmac, algo, key, len) +#define HMAC_Update CCHmacUpdate +#define HMAC_Final(hmac, hash, ptr) CCHmacFinal(hmac, hash) +#define HMAC_CTX_cleanup(ignore) +#define EVP_md5() kCCHmacAlgMD5 +#else #include <openssl/evp.h> #include <openssl/hmac.h> +#endif #include <openssl/x509v3.h> #endif diff --git a/line-log.c b/line-log.c new file mode 100644 index 0000000000..4bbb09be59 --- /dev/null +++ b/line-log.c @@ -0,0 +1,1273 @@ +#include "git-compat-util.h" +#include "line-range.h" +#include "cache.h" +#include "tag.h" +#include "blob.h" +#include "tree.h" +#include "diff.h" +#include "commit.h" +#include "decorate.h" +#include "revision.h" +#include "xdiff-interface.h" +#include "strbuf.h" +#include "log-tree.h" +#include "graph.h" +#include "userdiff.h" +#include "line-log.h" + +static void range_set_grow(struct range_set *rs, size_t extra) +{ + ALLOC_GROW(rs->ranges, rs->nr + extra, rs->alloc); +} + +/* Either initialization would be fine */ +#define RANGE_SET_INIT {0} + +static void range_set_init(struct range_set *rs, size_t prealloc) +{ + rs->alloc = rs->nr = 0; + rs->ranges = NULL; + if (prealloc) + range_set_grow(rs, prealloc); +} + +static void range_set_release(struct range_set *rs) +{ + free(rs->ranges); + rs->alloc = rs->nr = 0; + rs->ranges = NULL; +} + +/* dst must be uninitialized! */ +static void range_set_copy(struct range_set *dst, struct range_set *src) +{ + range_set_init(dst, src->nr); + memcpy(dst->ranges, src->ranges, src->nr*sizeof(struct range_set)); + dst->nr = src->nr; +} +static void range_set_move(struct range_set *dst, struct range_set *src) +{ + range_set_release(dst); + dst->ranges = src->ranges; + dst->nr = src->nr; + dst->alloc = src->alloc; + src->ranges = NULL; + src->alloc = src->nr = 0; +} + +/* tack on a _new_ range _at the end_ */ +static void range_set_append_unsafe(struct range_set *rs, long a, long b) +{ + assert(a <= b); + range_set_grow(rs, 1); + rs->ranges[rs->nr].start = a; + rs->ranges[rs->nr].end = b; + rs->nr++; +} + +static void range_set_append(struct range_set *rs, long a, long b) +{ + assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a); + range_set_append_unsafe(rs, a, b); +} + +static int range_cmp(const void *_r, const void *_s) +{ + const struct range *r = _r; + const struct range *s = _s; + + /* this could be simply 'return r.start-s.start', but for the types */ + if (r->start == s->start) + return 0; + if (r->start < s->start) + return -1; + return 1; +} + +/* + * Check that the ranges are non-empty, sorted and non-overlapping + */ +static void range_set_check_invariants(struct range_set *rs) +{ + int i; + + if (!rs) + return; + + if (rs->nr) + assert(rs->ranges[0].start < rs->ranges[0].end); + + for (i = 1; i < rs->nr; i++) { + assert(rs->ranges[i-1].end < rs->ranges[i].start); + assert(rs->ranges[i].start < rs->ranges[i].end); + } +} + +/* + * In-place pass of sorting and merging the ranges in the range set, + * to establish the invariants when we get the ranges from the user + */ +static void sort_and_merge_range_set(struct range_set *rs) +{ + int i; + int o = 1; /* output cursor */ + + qsort(rs->ranges, rs->nr, sizeof(struct range), range_cmp); + + for (i = 1; i < rs->nr; i++) { + if (rs->ranges[i].start <= rs->ranges[o-1].end) { + rs->ranges[o-1].end = rs->ranges[i].end; + } else { + rs->ranges[o].start = rs->ranges[i].start; + rs->ranges[o].end = rs->ranges[i].end; + o++; + } + } + assert(o <= rs->nr); + rs->nr = o; + + range_set_check_invariants(rs); +} + +/* + * Union of range sets (i.e., sets of line numbers). Used to merge + * them when searches meet at a common ancestor. + * + * This is also where the ranges are consolidated into canonical form: + * overlapping and adjacent ranges are merged, and empty ranges are + * removed. + */ +static void range_set_union(struct range_set *out, + struct range_set *a, struct range_set *b) +{ + int i = 0, j = 0, o = 0; + struct range *ra = a->ranges; + struct range *rb = b->ranges; + /* cannot make an alias of out->ranges: it may change during grow */ + + assert(out->nr == 0); + while (i < a->nr || j < b->nr) { + struct range *new; + if (i < a->nr && j < b->nr) { + if (ra[i].start < rb[j].start) + new = &ra[i++]; + else if (ra[i].start > rb[j].start) + new = &rb[j++]; + else if (ra[i].end < rb[j].end) + new = &ra[i++]; + else + new = &rb[j++]; + } else if (i < a->nr) /* b exhausted */ + new = &ra[i++]; + else /* a exhausted */ + new = &rb[j++]; + if (new->start == new->end) + ; /* empty range */ + else if (!o || out->ranges[o-1].end < new->start) { + range_set_grow(out, 1); + out->ranges[o].start = new->start; + out->ranges[o].end = new->end; + o++; + } else if (out->ranges[o-1].end < new->end) { + out->ranges[o-1].end = new->end; + } + } + out->nr = o; +} + +/* + * Difference of range sets (out = a \ b). Pass the "interesting" + * ranges as 'a' and the target side of the diff as 'b': it removes + * the ranges for which the commit is responsible. + */ +static void range_set_difference(struct range_set *out, + struct range_set *a, struct range_set *b) +{ + int i, j = 0; + for (i = 0; i < a->nr; i++) { + long start = a->ranges[i].start; + long end = a->ranges[i].end; + while (start < end) { + while (j < b->nr && start >= b->ranges[j].end) + /* + * a: |------- + * b: ------| + */ + j++; + if (j >= b->nr || end < b->ranges[j].start) { + /* + * b exhausted, or + * a: ----| + * b: |---- + */ + range_set_append(out, start, end); + break; + } + if (start >= b->ranges[j].start) { + /* + * a: |--???? + * b: |------| + */ + start = b->ranges[j].end; + } else if (end > b->ranges[j].start) { + /* + * a: |-----| + * b: |--????? + */ + if (start < b->ranges[j].start) + range_set_append(out, start, b->ranges[j].start); + start = b->ranges[j].end; + } + } + } +} + +static void diff_ranges_init(struct diff_ranges *diff) +{ + range_set_init(&diff->parent, 0); + range_set_init(&diff->target, 0); +} + +static void diff_ranges_release(struct diff_ranges *diff) +{ + range_set_release(&diff->parent); + range_set_release(&diff->target); +} + +void line_log_data_init(struct line_log_data *r) +{ + memset(r, 0, sizeof(struct line_log_data)); + range_set_init(&r->ranges, 0); +} + +static void line_log_data_clear(struct line_log_data *r) +{ + range_set_release(&r->ranges); + if (r->pair) + diff_free_filepair(r->pair); +} + +static void free_line_log_data(struct line_log_data *r) +{ + while (r) { + struct line_log_data *next = r->next; + line_log_data_clear(r); + free(r); + r = next; + } +} + +static struct line_log_data * +search_line_log_data(struct line_log_data *list, const char *path, + struct line_log_data **insertion_point) +{ + struct line_log_data *p = list; + if (insertion_point) + *insertion_point = NULL; + while (p) { + int cmp = strcmp(p->path, path); + if (!cmp) + return p; + if (insertion_point && cmp < 0) + *insertion_point = p; + p = p->next; + } + return NULL; +} + +/* + * Note: takes ownership of 'path', which happens to be what the only + * caller needs. + */ +static void line_log_data_insert(struct line_log_data **list, + char *path, + long begin, long end) +{ + struct line_log_data *ip; + struct line_log_data *p = search_line_log_data(*list, path, &ip); + + if (p) { + range_set_append_unsafe(&p->ranges, begin, end); + sort_and_merge_range_set(&p->ranges); + free(path); + return; + } + + p = xcalloc(1, sizeof(struct line_log_data)); + p->path = path; + range_set_append(&p->ranges, begin, end); + if (ip) { + p->next = ip->next; + ip->next = p; + } else { + p->next = *list; + *list = p; + } +} + +struct collect_diff_cbdata { + struct diff_ranges *diff; +}; + +static int collect_diff_cb(long start_a, long count_a, + long start_b, long count_b, + void *data) +{ + struct collect_diff_cbdata *d = data; + + if (count_a >= 0) + range_set_append(&d->diff->parent, start_a, start_a + count_a); + if (count_b >= 0) + range_set_append(&d->diff->target, start_b, start_b + count_b); + + return 0; +} + +static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out) +{ + struct collect_diff_cbdata cbdata = {NULL}; + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + + memset(&xpp, 0, sizeof(xpp)); + memset(&xecfg, 0, sizeof(xecfg)); + xecfg.ctxlen = xecfg.interhunkctxlen = 0; + + cbdata.diff = out; + xecfg.hunk_func = collect_diff_cb; + memset(&ecb, 0, sizeof(ecb)); + ecb.priv = &cbdata; + xdi_diff(parent, target, &xpp, &xecfg, &ecb); +} + +/* + * These are handy for debugging. Removing them with #if 0 silences + * the "unused function" warning. + */ +#if 0 +static void dump_range_set(struct range_set *rs, const char *desc) +{ + int i; + printf("range set %s (%d items):\n", desc, rs->nr); + for (i = 0; i < rs->nr; i++) + printf("\t[%ld,%ld]\n", rs->ranges[i].start, rs->ranges[i].end); +} + +static void dump_line_log_data(struct line_log_data *r) +{ + char buf[4096]; + while (r) { + snprintf(buf, 4096, "file %s\n", r->path); + dump_range_set(&r->ranges, buf); + r = r->next; + } +} + +static void dump_diff_ranges(struct diff_ranges *diff, const char *desc) +{ + int i; + assert(diff->parent.nr == diff->target.nr); + printf("diff ranges %s (%d items):\n", desc, diff->parent.nr); + printf("\tparent\ttarget\n"); + for (i = 0; i < diff->parent.nr; i++) { + printf("\t[%ld,%ld]\t[%ld,%ld]\n", + diff->parent.ranges[i].start, + diff->parent.ranges[i].end, + diff->target.ranges[i].start, + diff->target.ranges[i].end); + } +} +#endif + + +static int ranges_overlap(struct range *a, struct range *b) +{ + return !(a->end <= b->start || b->end <= a->start); +} + +/* + * Given a diff and the set of interesting ranges, determine all hunks + * of the diff which touch (overlap) at least one of the interesting + * ranges in the target. + */ +static void diff_ranges_filter_touched(struct diff_ranges *out, + struct diff_ranges *diff, + struct range_set *rs) +{ + int i, j = 0; + + assert(out->target.nr == 0); + + for (i = 0; i < diff->target.nr; i++) { + while (diff->target.ranges[i].start > rs->ranges[j].end) { + j++; + if (j == rs->nr) + return; + } + if (ranges_overlap(&diff->target.ranges[i], &rs->ranges[j])) { + range_set_append(&out->parent, + diff->parent.ranges[i].start, + diff->parent.ranges[i].end); + range_set_append(&out->target, + diff->target.ranges[i].start, + diff->target.ranges[i].end); + } + } +} + +/* + * Adjust the line counts in 'rs' to account for the lines + * added/removed in the diff. + */ +static void range_set_shift_diff(struct range_set *out, + struct range_set *rs, + struct diff_ranges *diff) +{ + int i, j = 0; + long offset = 0; + struct range *src = rs->ranges; + struct range *target = diff->target.ranges; + struct range *parent = diff->parent.ranges; + + for (i = 0; i < rs->nr; i++) { + while (j < diff->target.nr && src[i].start >= target[j].start) { + offset += (parent[j].end-parent[j].start) + - (target[j].end-target[j].start); + j++; + } + range_set_append(out, src[i].start+offset, src[i].end+offset); + } +} + +/* + * Given a diff and the set of interesting ranges, map the ranges + * across the diff. That is: observe that the target commit takes + * blame for all the + (target-side) ranges. So for every pair of + * ranges in the diff that was touched, we remove the latter and add + * its parent side. + */ +static void range_set_map_across_diff(struct range_set *out, + struct range_set *rs, + struct diff_ranges *diff, + struct diff_ranges **touched_out) +{ + struct diff_ranges *touched = xmalloc(sizeof(*touched)); + struct range_set tmp1 = RANGE_SET_INIT; + struct range_set tmp2 = RANGE_SET_INIT; + + diff_ranges_init(touched); + diff_ranges_filter_touched(touched, diff, rs); + range_set_difference(&tmp1, rs, &touched->target); + range_set_shift_diff(&tmp2, &tmp1, diff); + range_set_union(out, &tmp2, &touched->parent); + range_set_release(&tmp1); + range_set_release(&tmp2); + + *touched_out = touched; +} + +static struct commit *check_single_commit(struct rev_info *revs) +{ + struct object *commit = NULL; + int found = -1; + int i; + + for (i = 0; i < revs->pending.nr; i++) { + struct object *obj = revs->pending.objects[i].item; + if (obj->flags & UNINTERESTING) + continue; + while (obj->type == OBJ_TAG) + obj = deref_tag(obj, NULL, 0); + if (obj->type != OBJ_COMMIT) + die("Non commit %s?", revs->pending.objects[i].name); + if (commit) + die("More than one commit to dig from: %s and %s?", + revs->pending.objects[i].name, + revs->pending.objects[found].name); + commit = obj; + found = i; + } + + if (!commit) + die("No commit specified?"); + + return (struct commit *) commit; +} + +static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec) +{ + unsigned mode; + unsigned char sha1[20]; + + if (get_tree_entry(commit->object.sha1, spec->path, + sha1, &mode)) + die("There is no path %s in the commit", spec->path); + fill_filespec(spec, sha1, 1, mode); + + return; +} + +static void fill_line_ends(struct diff_filespec *spec, long *lines, + unsigned long **line_ends) +{ + int num = 0, size = 50; + long cur = 0; + unsigned long *ends = NULL; + char *data = NULL; + + if (diff_populate_filespec(spec, 0)) + die("Cannot read blob %s", sha1_to_hex(spec->sha1)); + + ends = xmalloc(size * sizeof(*ends)); + ends[cur++] = 0; + data = spec->data; + while (num < spec->size) { + if (data[num] == '\n' || num == spec->size - 1) { + ALLOC_GROW(ends, (cur + 1), size); + ends[cur++] = num; + } + num++; + } + + /* shrink the array to fit the elements */ + ends = xrealloc(ends, cur * sizeof(*ends)); + *lines = cur-1; + *line_ends = ends; +} + +struct nth_line_cb { + struct diff_filespec *spec; + long lines; + unsigned long *line_ends; +}; + +static const char *nth_line(void *data, long line) +{ + struct nth_line_cb *d = data; + assert(d && line <= d->lines); + assert(d->spec && d->spec->data); + + if (line == 0) + return (char *)d->spec->data; + else + return (char *)d->spec->data + d->line_ends[line] + 1; +} + +static struct line_log_data * +parse_lines(struct commit *commit, const char *prefix, struct string_list *args) +{ + long lines = 0; + unsigned long *ends = NULL; + struct nth_line_cb cb_data; + struct string_list_item *item; + struct line_log_data *ranges = NULL; + + for_each_string_list_item(item, args) { + const char *name_part, *range_part; + char *full_name; + struct diff_filespec *spec; + long begin = 0, end = 0; + + name_part = skip_range_arg(item->string); + if (!name_part || *name_part != ':' || !name_part[1]) + die("-L argument '%s' not of the form start,end:file", + item->string); + range_part = xstrndup(item->string, name_part - item->string); + name_part++; + + full_name = prefix_path(prefix, prefix ? strlen(prefix) : 0, + name_part); + + spec = alloc_filespec(full_name); + fill_blob_sha1(commit, spec); + fill_line_ends(spec, &lines, &ends); + cb_data.spec = spec; + cb_data.lines = lines; + cb_data.line_ends = ends; + + if (parse_range_arg(range_part, nth_line, &cb_data, + lines, &begin, &end, + full_name)) + die("malformed -L argument '%s'", range_part); + if (begin < 1) + begin = 1; + if (end < 1) + end = lines; + begin--; + if (lines < end || lines < begin) + die("file %s has only %ld lines", name_part, lines); + line_log_data_insert(&ranges, full_name, begin, end); + + free_filespec(spec); + free(ends); + ends = NULL; + } + + return ranges; +} + +static struct line_log_data *line_log_data_copy_one(struct line_log_data *r) +{ + struct line_log_data *ret = xmalloc(sizeof(*ret)); + + assert(r); + line_log_data_init(ret); + range_set_copy(&ret->ranges, &r->ranges); + + ret->path = xstrdup(r->path); + + return ret; +} + +static struct line_log_data * +line_log_data_copy(struct line_log_data *r) +{ + struct line_log_data *ret = NULL; + struct line_log_data *tmp = NULL, *prev = NULL; + + assert(r); + ret = tmp = prev = line_log_data_copy_one(r); + r = r->next; + while (r) { + tmp = line_log_data_copy_one(r); + prev->next = tmp; + prev = tmp; + r = r->next; + } + + return ret; +} + +/* merge two range sets across files */ +static struct line_log_data *line_log_data_merge(struct line_log_data *a, + struct line_log_data *b) +{ + struct line_log_data *head = NULL, **pp = &head; + + while (a || b) { + struct line_log_data *src; + struct line_log_data *src2 = NULL; + struct line_log_data *d; + int cmp; + if (!a) + cmp = 1; + else if (!b) + cmp = -1; + else + cmp = strcmp(a->path, b->path); + if (cmp < 0) { + src = a; + a = a->next; + } else if (cmp == 0) { + src = a; + a = a->next; + src2 = b; + b = b->next; + } else { + src = b; + b = b->next; + } + d = xmalloc(sizeof(struct line_log_data)); + line_log_data_init(d); + d->path = xstrdup(src->path); + *pp = d; + pp = &d->next; + if (src2) + range_set_union(&d->ranges, &src->ranges, &src2->ranges); + else + range_set_copy(&d->ranges, &src->ranges); + } + + return head; +} + +static void add_line_range(struct rev_info *revs, struct commit *commit, + struct line_log_data *range) +{ + struct line_log_data *old = NULL; + struct line_log_data *new = NULL; + + old = lookup_decoration(&revs->line_log_data, &commit->object); + if (old && range) { + new = line_log_data_merge(old, range); + free_line_log_data(old); + } else if (range) + new = line_log_data_copy(range); + + if (new) + add_decoration(&revs->line_log_data, &commit->object, new); +} + +static void clear_commit_line_range(struct rev_info *revs, struct commit *commit) +{ + struct line_log_data *r; + r = lookup_decoration(&revs->line_log_data, &commit->object); + if (!r) + return; + free_line_log_data(r); + add_decoration(&revs->line_log_data, &commit->object, NULL); +} + +static struct line_log_data *lookup_line_range(struct rev_info *revs, + struct commit *commit) +{ + struct line_log_data *ret = NULL; + struct line_log_data *d; + + ret = lookup_decoration(&revs->line_log_data, &commit->object); + + for (d = ret; d; d = d->next) + range_set_check_invariants(&d->ranges); + + return ret; +} + +void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args) +{ + struct commit *commit = NULL; + struct line_log_data *range; + + commit = check_single_commit(rev); + range = parse_lines(commit, prefix, args); + add_line_range(rev, commit, range); + + if (!rev->diffopt.detect_rename) { + int i, count = 0; + struct line_log_data *r = range; + const char **paths; + while (r) { + count++; + r = r->next; + } + paths = xmalloc((count+1)*sizeof(char *)); + r = range; + for (i = 0; i < count; i++) { + paths[i] = xstrdup(r->path); + r = r->next; + } + paths[count] = NULL; + init_pathspec(&rev->diffopt.pathspec, paths); + free(paths); + } +} + +static void load_tree_desc(struct tree_desc *desc, void **tree, + const unsigned char *sha1) +{ + unsigned long size; + *tree = read_object_with_reference(sha1, tree_type, &size, NULL); + if (!*tree) + die("Unable to read tree (%s)", sha1_to_hex(sha1)); + init_tree_desc(desc, *tree, size); +} + +static int count_parents(struct commit *commit) +{ + struct commit_list *parents = commit->parents; + int count = 0; + while (parents) { + count++; + parents = parents->next; + } + return count; +} + +static void move_diff_queue(struct diff_queue_struct *dst, + struct diff_queue_struct *src) +{ + assert(src != dst); + memcpy(dst, src, sizeof(struct diff_queue_struct)); + DIFF_QUEUE_CLEAR(src); +} + +static void filter_diffs_for_paths(struct line_log_data *range, int keep_deletions) +{ + int i; + struct diff_queue_struct outq; + DIFF_QUEUE_CLEAR(&outq); + + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p = diff_queued_diff.queue[i]; + struct line_log_data *rg = NULL; + + if (!DIFF_FILE_VALID(p->two)) { + if (keep_deletions) + diff_q(&outq, p); + else + diff_free_filepair(p); + continue; + } + for (rg = range; rg; rg = rg->next) { + if (!strcmp(rg->path, p->two->path)) + break; + } + if (rg) + diff_q(&outq, p); + else + diff_free_filepair(p); + } + free(diff_queued_diff.queue); + diff_queued_diff = outq; +} + +static inline int diff_might_be_rename(void) +{ + int i; + for (i = 0; i < diff_queued_diff.nr; i++) + if (!DIFF_FILE_VALID(diff_queued_diff.queue[i]->one)) { + /* fprintf(stderr, "diff_might_be_rename found creation of: %s\n", */ + /* diff_queued_diff.queue[i]->two->path); */ + return 1; + } + return 0; +} + +static void queue_diffs(struct line_log_data *range, + struct diff_options *opt, + struct diff_queue_struct *queue, + struct commit *commit, struct commit *parent) +{ + void *tree1 = NULL, *tree2 = NULL; + struct tree_desc desc1, desc2; + + assert(commit); + load_tree_desc(&desc2, &tree2, commit->tree->object.sha1); + if (parent) + load_tree_desc(&desc1, &tree1, parent->tree->object.sha1); + else + init_tree_desc(&desc1, "", 0); + + DIFF_QUEUE_CLEAR(&diff_queued_diff); + diff_tree(&desc1, &desc2, "", opt); + if (opt->detect_rename) { + filter_diffs_for_paths(range, 1); + if (diff_might_be_rename()) + diffcore_std(opt); + filter_diffs_for_paths(range, 0); + } + move_diff_queue(queue, &diff_queued_diff); + + if (tree1) + free(tree1); + if (tree2) + free(tree2); +} + +static char *get_nth_line(long line, unsigned long *ends, void *data) +{ + if (line == 0) + return (char *)data; + else + return (char *)data + ends[line] + 1; +} + +static void print_line(const char *prefix, char first, + long line, unsigned long *ends, void *data, + const char *color, const char *reset) +{ + char *begin = get_nth_line(line, ends, data); + char *end = get_nth_line(line+1, ends, data); + int had_nl = 0; + + if (end > begin && end[-1] == '\n') { + end--; + had_nl = 1; + } + + fputs(prefix, stdout); + fputs(color, stdout); + putchar(first); + fwrite(begin, 1, end-begin, stdout); + fputs(reset, stdout); + putchar('\n'); + if (!had_nl) + fputs("\\ No newline at end of file\n", stdout); +} + +static char *output_prefix(struct diff_options *opt) +{ + char *prefix = ""; + + if (opt->output_prefix) { + struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data); + prefix = sb->buf; + } + + return prefix; +} + +static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range) +{ + int i, j = 0; + long p_lines, t_lines; + unsigned long *p_ends = NULL, *t_ends = NULL; + struct diff_filepair *pair = range->pair; + struct diff_ranges *diff = &range->diff; + + struct diff_options *opt = &rev->diffopt; + char *prefix = output_prefix(opt); + const char *c_reset = diff_get_color(opt->use_color, DIFF_RESET); + const char *c_frag = diff_get_color(opt->use_color, DIFF_FRAGINFO); + const char *c_meta = diff_get_color(opt->use_color, DIFF_METAINFO); + const char *c_old = diff_get_color(opt->use_color, DIFF_FILE_OLD); + const char *c_new = diff_get_color(opt->use_color, DIFF_FILE_NEW); + const char *c_plain = diff_get_color(opt->use_color, DIFF_PLAIN); + + if (!pair || !diff) + return; + + if (pair->one->sha1_valid) + fill_line_ends(pair->one, &p_lines, &p_ends); + fill_line_ends(pair->two, &t_lines, &t_ends); + + printf("%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset); + printf("%s%s--- %s%s%s\n", prefix, c_meta, + pair->one->sha1_valid ? "a/" : "", + pair->one->sha1_valid ? pair->one->path : "/dev/null", + c_reset); + printf("%s%s+++ b/%s%s\n", prefix, c_meta, pair->two->path, c_reset); + for (i = 0; i < range->ranges.nr; i++) { + long p_start, p_end; + long t_start = range->ranges.ranges[i].start; + long t_end = range->ranges.ranges[i].end; + long t_cur = t_start; + int j_last; + + while (j < diff->target.nr && diff->target.ranges[j].end < t_start) + j++; + if (j == diff->target.nr || diff->target.ranges[j].start > t_end) + continue; + + /* Scan ahead to determine the last diff that falls in this range */ + j_last = j; + while (j_last < diff->target.nr && diff->target.ranges[j_last].start < t_end) + j_last++; + if (j_last > j) + j_last--; + + /* + * Compute parent hunk headers: we know that the diff + * has the correct line numbers (but not all hunks). + * So it suffices to shift the start/end according to + * the line numbers of the first/last hunk(s) that + * fall in this range. + */ + if (t_start < diff->target.ranges[j].start) + p_start = diff->parent.ranges[j].start - (diff->target.ranges[j].start-t_start); + else + p_start = diff->parent.ranges[j].start; + if (t_end > diff->target.ranges[j_last].end) + p_end = diff->parent.ranges[j_last].end + (t_end-diff->target.ranges[j_last].end); + else + p_end = diff->parent.ranges[j_last].end; + + if (!p_start && !p_end) { + p_start = -1; + p_end = -1; + } + + /* Now output a diff hunk for this range */ + printf("%s%s@@ -%ld,%ld +%ld,%ld @@%s\n", + prefix, c_frag, + p_start+1, p_end-p_start, t_start+1, t_end-t_start, + c_reset); + while (j < diff->target.nr && diff->target.ranges[j].start < t_end) { + int k; + for (; t_cur < diff->target.ranges[j].start; t_cur++) + print_line(prefix, ' ', t_cur, t_ends, pair->two->data, + c_plain, c_reset); + for (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++) + print_line(prefix, '-', k, p_ends, pair->one->data, + c_old, c_reset); + for (; t_cur < diff->target.ranges[j].end && t_cur < t_end; t_cur++) + print_line(prefix, '+', t_cur, t_ends, pair->two->data, + c_new, c_reset); + j++; + } + for (; t_cur < t_end; t_cur++) + print_line(prefix, ' ', t_cur, t_ends, pair->two->data, + c_plain, c_reset); + } + + free(p_ends); + free(t_ends); +} + +/* + * NEEDSWORK: manually building a diff here is not the Right + * Thing(tm). log -L should be built into the diff pipeline. + */ +static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range) +{ + puts(output_prefix(&rev->diffopt)); + while (range) { + dump_diff_hacky_one(rev, range); + range = range->next; + } +} + +/* + * Unlike most other functions, this destructively operates on + * 'range'. + */ +static int process_diff_filepair(struct rev_info *rev, + struct diff_filepair *pair, + struct line_log_data *range, + struct diff_ranges **diff_out) +{ + struct line_log_data *rg = range; + struct range_set tmp; + struct diff_ranges diff; + mmfile_t file_parent, file_target; + + assert(pair->two->path); + while (rg) { + assert(rg->path); + if (!strcmp(rg->path, pair->two->path)) + break; + rg = rg->next; + } + + if (!rg) + return 0; + if (rg->ranges.nr == 0) + return 0; + + assert(pair->two->sha1_valid); + diff_populate_filespec(pair->two, 0); + file_target.ptr = pair->two->data; + file_target.size = pair->two->size; + + if (pair->one->sha1_valid) { + diff_populate_filespec(pair->one, 0); + file_parent.ptr = pair->one->data; + file_parent.size = pair->one->size; + } else { + file_parent.ptr = ""; + file_parent.size = 0; + } + + diff_ranges_init(&diff); + collect_diff(&file_parent, &file_target, &diff); + + /* NEEDSWORK should apply some heuristics to prevent mismatches */ + free(rg->path); + rg->path = xstrdup(pair->one->path); + + range_set_init(&tmp, 0); + range_set_map_across_diff(&tmp, &rg->ranges, &diff, diff_out); + range_set_release(&rg->ranges); + range_set_move(&rg->ranges, &tmp); + + diff_ranges_release(&diff); + + return ((*diff_out)->parent.nr > 0); +} + +static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair) +{ + struct diff_filepair *new = xmalloc(sizeof(struct diff_filepair)); + new->one = pair->one; + new->two = pair->two; + new->one->count++; + new->two->count++; + return new; +} + +static void free_diffqueues(int n, struct diff_queue_struct *dq) +{ + int i, j; + for (i = 0; i < n; i++) + for (j = 0; j < dq[i].nr; j++) + diff_free_filepair(dq[i].queue[j]); + free(dq); +} + +static int process_all_files(struct line_log_data **range_out, + struct rev_info *rev, + struct diff_queue_struct *queue, + struct line_log_data *range) +{ + int i, changed = 0; + + *range_out = line_log_data_copy(range); + + for (i = 0; i < queue->nr; i++) { + struct diff_ranges *pairdiff = NULL; + struct diff_filepair *pair = queue->queue[i]; + if (process_diff_filepair(rev, pair, *range_out, &pairdiff)) { + /* + * Store away the diff for later output. We + * tuck it in the ranges we got as _input_, + * since that's the commit that caused the + * diff. + * + * NEEDSWORK not enough when we get around to + * doing something interesting with merges; + * currently each invocation on a merge parent + * trashes the previous one's diff. + * + * NEEDSWORK tramples over data structures not owned here + */ + struct line_log_data *rg = range; + changed++; + while (rg && strcmp(rg->path, pair->two->path)) + rg = rg->next; + assert(rg); + rg->pair = diff_filepair_dup(queue->queue[i]); + memcpy(&rg->diff, pairdiff, sizeof(struct diff_ranges)); + } + } + + return changed; +} + +int line_log_print(struct rev_info *rev, struct commit *commit) +{ + struct line_log_data *range = lookup_line_range(rev, commit); + + show_log(rev); + dump_diff_hacky(rev, range); + return 1; +} + +static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *commit, + struct line_log_data *range) +{ + struct commit *parent = NULL; + struct diff_queue_struct queue; + struct line_log_data *parent_range; + int changed; + + if (commit->parents) + parent = commit->parents->item; + + queue_diffs(range, &rev->diffopt, &queue, commit, parent); + changed = process_all_files(&parent_range, rev, &queue, range); + if (parent) + add_line_range(rev, parent, parent_range); + return changed; +} + +static int process_ranges_merge_commit(struct rev_info *rev, struct commit *commit, + struct line_log_data *range) +{ + struct diff_queue_struct *diffqueues; + struct line_log_data **cand; + struct commit **parents; + struct commit_list *p; + int i; + int nparents = count_parents(commit); + + diffqueues = xmalloc(nparents * sizeof(*diffqueues)); + cand = xmalloc(nparents * sizeof(*cand)); + parents = xmalloc(nparents * sizeof(*parents)); + + p = commit->parents; + for (i = 0; i < nparents; i++) { + parents[i] = p->item; + p = p->next; + queue_diffs(range, &rev->diffopt, &diffqueues[i], commit, parents[i]); + } + + for (i = 0; i < nparents; i++) { + int changed; + cand[i] = NULL; + changed = process_all_files(&cand[i], rev, &diffqueues[i], range); + if (!changed) { + /* + * This parent can take all the blame, so we + * don't follow any other path in history + */ + add_line_range(rev, parents[i], cand[i]); + clear_commit_line_range(rev, commit); + commit->parents = xmalloc(sizeof(struct commit_list)); + commit->parents->item = parents[i]; + commit->parents->next = NULL; + free(parents); + free(cand); + free_diffqueues(nparents, diffqueues); + /* NEEDSWORK leaking like a sieve */ + return 0; + } + } + + /* + * No single parent took the blame. We add the candidates + * from the above loop to the parents. + */ + for (i = 0; i < nparents; i++) { + add_line_range(rev, parents[i], cand[i]); + } + + clear_commit_line_range(rev, commit); + free(parents); + free(cand); + free_diffqueues(nparents, diffqueues); + return 1; + + /* NEEDSWORK evil merge detection stuff */ + /* NEEDSWORK leaking like a sieve */ +} + +static int process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit) +{ + struct line_log_data *range = lookup_line_range(rev, commit); + int changed = 0; + + if (range) { + if (!commit->parents || !commit->parents->next) + changed = process_ranges_ordinary_commit(rev, commit, range); + else + changed = process_ranges_merge_commit(rev, commit, range); + } + + if (!changed) + commit->object.flags |= TREESAME; + + return changed; +} + +static enum rewrite_result line_log_rewrite_one(struct rev_info *rev, struct commit **pp) +{ + for (;;) { + struct commit *p = *pp; + if (p->parents && p->parents->next) + return rewrite_one_ok; + if (p->object.flags & UNINTERESTING) + return rewrite_one_ok; + if (!(p->object.flags & TREESAME)) + return rewrite_one_ok; + if (!p->parents) + return rewrite_one_noparents; + *pp = p->parents->item; + } +} + +int line_log_filter(struct rev_info *rev) +{ + struct commit *commit; + struct commit_list *list = rev->commits; + struct commit_list *out = NULL, **pp = &out; + + while (list) { + struct commit_list *to_free = NULL; + commit = list->item; + if (process_ranges_arbitrary_commit(rev, commit)) { + *pp = list; + pp = &list->next; + } else + to_free = list; + list = list->next; + free(to_free); + } + *pp = NULL; + + for (list = out; list; list = list->next) + rewrite_parents(rev, list->item, line_log_rewrite_one); + + rev->commits = out; + + return 0; +} diff --git a/line-log.h b/line-log.h new file mode 100644 index 0000000000..8bea45fd78 --- /dev/null +++ b/line-log.h @@ -0,0 +1,53 @@ +#ifndef LINE_LOG_H +#define LINE_LOG_H + +#include "diffcore.h" + +struct rev_info; +struct commit; + +/* A range [start,end]. Lines are numbered starting at 0, and the + * ranges include start but exclude end. */ +struct range { + long start, end; +}; + +/* A set of ranges. The ranges must always be disjoint and sorted. */ +struct range_set { + int alloc, nr; + struct range *ranges; +}; + +/* A diff, encoded as the set of pre- and post-image ranges where the + * files differ. A pair of ranges corresponds to a hunk. */ +struct diff_ranges { + struct range_set parent; + struct range_set target; +}; + +/* Linked list of interesting files and their associated ranges. The + * list must be kept sorted by path. + * + * For simplicity, even though this is highly redundant, each + * line_log_data owns its 'path'. + */ +struct line_log_data { + struct line_log_data *next; + char *path; + char status; + struct range_set ranges; + int arg_alloc, arg_nr; + const char **args; + struct diff_filepair *pair; + struct diff_ranges diff; +}; + +extern void line_log_data_init(struct line_log_data *r); + +extern void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args); + +extern int line_log_filter(struct rev_info *rev); + +extern int line_log_print(struct rev_info *rev, struct commit *commit); + +#endif /* LINE_LOG_H */ diff --git a/line-range.c b/line-range.c new file mode 100644 index 0000000000..8faf943745 --- /dev/null +++ b/line-range.c @@ -0,0 +1,243 @@ +#include "git-compat-util.h" +#include "line-range.h" +#include "xdiff-interface.h" +#include "strbuf.h" +#include "userdiff.h" + +/* + * Parse one item in the -L option + */ +static const char *parse_loc(const char *spec, nth_line_fn_t nth_line, + void *data, long lines, long begin, long *ret) +{ + char *term; + const char *line; + long num; + int reg_error; + regex_t regexp; + regmatch_t match[1]; + + /* Allow "-L <something>,+20" to mean starting at <something> + * for 20 lines, or "-L <something>,-5" for 5 lines ending at + * <something>. + */ + if (1 < begin && (spec[0] == '+' || spec[0] == '-')) { + num = strtol(spec + 1, &term, 10); + if (term != spec + 1) { + if (!ret) + return term; + if (spec[0] == '-') + num = 0 - num; + if (0 < num) + *ret = begin + num - 2; + else if (!num) + *ret = begin; + else + *ret = begin + num; + return term; + } + return spec; + } + num = strtol(spec, &term, 10); + if (term != spec) { + if (ret) + *ret = num; + return term; + } + if (spec[0] != '/') + return spec; + + /* it could be a regexp of form /.../ */ + for (term = (char *) spec + 1; *term && *term != '/'; term++) { + if (*term == '\\') + term++; + } + if (*term != '/') + return spec; + + /* in the scan-only case we are not interested in the regex */ + if (!ret) + return term+1; + + /* try [spec+1 .. term-1] as regexp */ + *term = 0; + begin--; /* input is in human terms */ + line = nth_line(data, begin); + + if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) && + !(reg_error = regexec(®exp, line, 1, match, 0))) { + const char *cp = line + match[0].rm_so; + const char *nline; + + while (begin++ < lines) { + nline = nth_line(data, begin); + if (line <= cp && cp < nline) + break; + line = nline; + } + *ret = begin; + regfree(®exp); + *term++ = '/'; + return term; + } + else { + char errbuf[1024]; + regerror(reg_error, ®exp, errbuf, 1024); + die("-L parameter '%s': %s", spec + 1, errbuf); + } +} + +static int match_funcname(xdemitconf_t *xecfg, const char *bol, const char *eol) +{ + if (xecfg) { + char buf[1]; + return xecfg->find_func(bol, eol - bol, buf, 1, + xecfg->find_func_priv) >= 0; + } + + if (bol == eol) + return 0; + if (isalpha(*bol) || *bol == '_' || *bol == '$') + return 1; + return 0; +} + +static const char *find_funcname_matching_regexp(xdemitconf_t *xecfg, const char *start, + regex_t *regexp) +{ + int reg_error; + regmatch_t match[1]; + while (1) { + const char *bol, *eol; + reg_error = regexec(regexp, start, 1, match, 0); + if (reg_error == REG_NOMATCH) + return NULL; + else if (reg_error) { + char errbuf[1024]; + regerror(reg_error, regexp, errbuf, 1024); + die("-L parameter: regexec() failed: %s", errbuf); + } + /* determine extent of line matched */ + bol = start+match[0].rm_so; + eol = start+match[0].rm_eo; + while (bol > start && *bol != '\n') + bol--; + if (*bol == '\n') + bol++; + while (*eol && *eol != '\n') + eol++; + if (*eol == '\n') + eol++; + /* is it a funcname line? */ + if (match_funcname(xecfg, (char*) bol, (char*) eol)) + return bol; + start = eol; + } +} + +static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_cb, + void *cb_data, long lines, long *begin, long *end, + const char *path) +{ + char *pattern; + const char *term; + struct userdiff_driver *drv; + xdemitconf_t *xecfg = NULL; + const char *start; + const char *p; + int reg_error; + regex_t regexp; + + assert(*arg == ':'); + term = arg+1; + while (*term && *term != ':') { + if (*term == '\\' && *(term+1)) + term++; + term++; + } + if (term == arg+1) + return NULL; + if (!begin) /* skip_range_arg case */ + return term; + + pattern = xstrndup(arg+1, term-(arg+1)); + + start = nth_line_cb(cb_data, 0); + + drv = userdiff_find_by_path(path); + if (drv && drv->funcname.pattern) { + const struct userdiff_funcname *pe = &drv->funcname; + xecfg = xcalloc(1, sizeof(*xecfg)); + xdiff_set_find_func(xecfg, pe->pattern, pe->cflags); + } + + reg_error = regcomp(®exp, pattern, REG_NEWLINE); + if (reg_error) { + char errbuf[1024]; + regerror(reg_error, ®exp, errbuf, 1024); + die("-L parameter '%s': %s", pattern, errbuf); + } + + p = find_funcname_matching_regexp(xecfg, (char*) start, ®exp); + if (!p) + die("-L parameter '%s': no match", pattern); + *begin = 0; + while (p > nth_line_cb(cb_data, *begin)) + (*begin)++; + + if (*begin >= lines) + die("-L parameter '%s' matches at EOF", pattern); + + *end = *begin+1; + while (*end < lines) { + const char *bol = nth_line_cb(cb_data, *end); + const char *eol = nth_line_cb(cb_data, *end+1); + if (match_funcname(xecfg, bol, eol)) + break; + (*end)++; + } + + regfree(®exp); + free(xecfg); + free(pattern); + + /* compensate for 1-based numbering */ + (*begin)++; + + return term; +} + +int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb, + void *cb_data, long lines, long *begin, long *end, + const char *path) +{ + if (*arg == ':') { + arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path); + if (!arg || *arg) + return -1; + return 0; + } + + arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin); + + if (*arg == ',') + arg = parse_loc(arg + 1, nth_line_cb, cb_data, lines, *begin + 1, end); + + if (*arg) + return -1; + + return 0; +} + +const char *skip_range_arg(const char *arg) +{ + if (*arg == ':') + return parse_range_funcname(arg, NULL, NULL, 0, NULL, NULL, NULL); + + arg = parse_loc(arg, NULL, NULL, 0, -1, NULL); + + if (*arg == ',') + arg = parse_loc(arg+1, NULL, NULL, 0, 0, NULL); + + return arg; +} diff --git a/line-range.h b/line-range.h new file mode 100644 index 0000000000..ae3d0123b4 --- /dev/null +++ b/line-range.h @@ -0,0 +1,36 @@ +#ifndef LINE_RANGE_H +#define LINE_RANGE_H + +/* + * Parse one item in an -L begin,end option w.r.t. the notional file + * object 'cb_data' consisting of 'lines' lines. + * + * The 'nth_line_cb' callback is used to determine the start of the + * line 'lno' inside the 'cb_data'. The caller is expected to already + * have a suitable map at hand to make this a constant-time lookup. + * + * Returns 0 in case of success and -1 if there was an error. The + * actual range is stored in *begin and *end. The counting starts + * at 1! In case of error, the caller should show usage message. + */ + +typedef const char *(*nth_line_fn_t)(void *data, long lno); + +extern int parse_range_arg(const char *arg, + nth_line_fn_t nth_line_cb, + void *cb_data, long lines, + long *begin, long *end, + const char *path); + +/* + * Scan past a range argument that could be parsed by + * 'parse_range_arg', to help the caller determine the start of the + * filename in '-L n,m:file' syntax. + * + * Returns a pointer to the first character after the 'n,m' part, or + * NULL in case the argument is obviously malformed. + */ + +extern const char *skip_range_arg(const char *arg); + +#endif /* LINE_RANGE_H */ diff --git a/log-tree.c b/log-tree.c index 1946e9ce8d..2eb69bcfed 100644 --- a/log-tree.c +++ b/log-tree.c @@ -10,6 +10,7 @@ #include "color.h" #include "gpg-interface.h" #include "sequencer.h" +#include "line-log.h" struct decoration name_decoration = { "object names" }; @@ -796,6 +797,9 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit) log.parent = NULL; opt->loginfo = &log; + if (opt->line_level_traverse) + return line_log_print(opt, commit); + shown = log_tree_diff(opt, commit, &log); if (!shown && opt->loginfo && opt->always_show_header) { log.parent = NULL; @@ -71,13 +71,13 @@ static unsigned int hashtable_index(const unsigned char *sha1) struct object *lookup_object(const unsigned char *sha1) { - unsigned int i; + unsigned int i, first; struct object *obj; if (!obj_hash) return NULL; - i = hashtable_index(sha1); + first = i = hashtable_index(sha1); while ((obj = obj_hash[i]) != NULL) { if (!hashcmp(sha1, obj->sha1)) break; @@ -85,6 +85,16 @@ struct object *lookup_object(const unsigned char *sha1) if (i == obj_hash_size) i = 0; } + if (obj && i != first) { + /* + * Move object to where we started to look for it so + * that we do not need to walk the hash table the next + * time we look for it. + */ + struct object *tmp = obj_hash[i]; + obj_hash[i] = obj_hash[first]; + obj_hash[first] = tmp; + } return obj; } diff --git a/pack-refs.c b/pack-refs.c deleted file mode 100644 index 4461f71a37..0000000000 --- a/pack-refs.c +++ /dev/null @@ -1,148 +0,0 @@ -#include "cache.h" -#include "refs.h" -#include "tag.h" -#include "pack-refs.h" - -struct ref_to_prune { - struct ref_to_prune *next; - unsigned char sha1[20]; - char name[FLEX_ARRAY]; -}; - -struct pack_refs_cb_data { - unsigned int flags; - 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; - struct object *o; - int is_tag_ref; - - /* Do not pack the symbolic refs */ - if ((flags & REF_ISSYMREF)) - return 0; - is_tag_ref = !prefixcmp(path, "refs/tags/"); - - /* ALWAYS pack refs that were already packed or are tags */ - if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref && !(flags & REF_ISPACKED)) - return 0; - - fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); - - o = parse_object_or_die(sha1, path); - if (o->type == OBJ_TAG) { - o = deref_tag(o, path, 0); - if (o) - fprintf(cb->refs_file, "^%s\n", - sha1_to_hex(o->sha1)); - } - - if ((cb->flags & PACK_REFS_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; -} - -/* - * Remove empty parents, but spare refs/ and immediate subdirs. - * Note: munges *name. - */ -static void try_remove_empty_parents(char *name) -{ - char *p, *q; - int i; - p = name; - for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */ - while (*p && *p != '/') - p++; - /* tolerate duplicate slashes; see check_refname_format() */ - while (*p == '/') - p++; - } - for (q = p; *q; q++) - ; - while (1) { - while (q > p && *q != '/') - q--; - while (q > p && *(q-1) == '/') - q--; - if (q == p) - break; - *q = '\0'; - if (rmdir(git_path("%s", name))) - break; - } -} - -/* 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_or_warn(git_path("%s", r->name)); - unlock_ref(lock); - try_remove_empty_parents(r->name); - } -} - -static void prune_refs(struct ref_to_prune *r) -{ - while (r) { - prune_ref(r); - r = r->next; - } -} - -static struct lock_file packed; - -int pack_refs(unsigned int flags) -{ - int fd; - struct pack_refs_cb_data cbdata; - - memset(&cbdata, 0, sizeof(cbdata)); - cbdata.flags = flags; - - fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), - LOCK_DIE_ON_ERROR); - cbdata.refs_file = fdopen(fd, "w"); - if (!cbdata.refs_file) - die_errno("unable to create ref-pack file structure"); - - /* perhaps other traits later as well */ - fprintf(cbdata.refs_file, "# pack-refs with: peeled fully-peeled \n"); - - for_each_ref(handle_one_ref, &cbdata); - if (ferror(cbdata.refs_file)) - die("failed to write ref-pack file"); - if (fflush(cbdata.refs_file) || fsync(fd) || fclose(cbdata.refs_file)) - die_errno("failed to write ref-pack file"); - /* - * Since the lock file was fdopen()'ed and then fclose()'ed above, - * assign -1 to the lock file descriptor so that commit_lock_file() - * won't try to close() it. - */ - packed.fd = -1; - if (commit_lock_file(&packed) < 0) - die_errno("unable to overwrite old ref-pack file"); - prune_refs(cbdata.ref_to_prune); - return 0; -} diff --git a/pack-refs.h b/pack-refs.h deleted file mode 100644 index 518acfb370..0000000000 --- a/pack-refs.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef PACK_REFS_H -#define PACK_REFS_H - -/* - * Flags for controlling behaviour of pack_refs() - * PACK_REFS_PRUNE: Prune loose refs after packing - * PACK_REFS_ALL: Pack _all_ refs, not just tags and already packed refs - */ -#define PACK_REFS_PRUNE 0x0001 -#define PACK_REFS_ALL 0x0002 - -/* - * Write a packed-refs file for the current repository. - * flags: Combination of the above PACK_REFS_* flags. - */ -int pack_refs(unsigned int flags); - -#endif /* PACK_REFS_H */ diff --git a/parse-options-cb.c b/parse-options-cb.c index 0de5fb168a..be8c413cfe 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -33,6 +33,12 @@ int parse_opt_approxidate_cb(const struct option *opt, const char *arg, return 0; } +int parse_opt_expiry_date_cb(const struct option *opt, const char *arg, + int unset) +{ + return parse_expiry_date(arg, (unsigned long *)opt->value); +} + int parse_opt_color_flag_cb(const struct option *opt, const char *arg, int unset) { diff --git a/parse-options.h b/parse-options.h index 1c8bd8d5a0..c378b75b13 100644 --- a/parse-options.h +++ b/parse-options.h @@ -140,6 +140,9 @@ struct option { #define OPT_DATE(s, l, v, h) \ { OPTION_CALLBACK, (s), (l), (v), N_("time"),(h), 0, \ parse_opt_approxidate_cb } +#define OPT_EXPIRY_DATE(s, l, v, h) \ + { OPTION_CALLBACK, (s), (l), (v), N_("expiry date"),(h), 0, \ + parse_opt_expiry_date_cb } #define OPT_CALLBACK(s, l, v, a, h, f) \ { OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) } #define OPT_NUMBER_CALLBACK(v, h, f) \ @@ -219,6 +222,7 @@ extern int parse_options_concat(struct option *dst, size_t, struct option *src); /*----- some often used options -----*/ extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int); +extern int parse_opt_expiry_date_cb(const struct option *, const char *, int); extern int parse_opt_color_flag_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); @@ -109,7 +109,20 @@ struct ref_entry; * (ref_entry->flag & REF_DIR) is zero. */ struct ref_value { + /* + * The name of the object to which this reference resolves + * (which may be a tag object). If REF_ISBROKEN, this is + * null. If REF_ISSYMREF, then this is the name of the object + * referred to by the last reference in the symlink chain. + */ unsigned char sha1[20]; + + /* + * If REF_KNOWS_PEELED, then this field holds the peeled value + * of this reference, or null if the reference is known not to + * be peelable. See the documentation for peel_ref() for an + * exact definition of "peelable". + */ unsigned char peeled[20]; }; @@ -158,7 +171,17 @@ struct ref_dir { struct ref_entry **entries; }; -/* ISSYMREF=0x01, ISPACKED=0x02, and ISBROKEN=0x04 are public interfaces */ +/* + * Bit values for ref_entry::flag. REF_ISSYMREF=0x01, + * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see + * refs.h. + */ + +/* + * The field ref_entry->u.value.peeled of this value entry contains + * the correct peeled value for the reference, which might be + * null_sha1 if the reference is not a tag or if it is broken. + */ #define REF_KNOWS_PEELED 0x08 /* ref_entry represents a directory of references */ @@ -343,18 +366,17 @@ static int ref_entry_cmp_sslice(const void *key_, const void *ent_) } /* - * Return the entry with the given refname from the ref_dir - * (non-recursively), sorting dir if necessary. Return NULL if no - * such entry is found. dir must already be complete. + * Return the index of the entry with the given refname from the + * ref_dir (non-recursively), sorting dir if necessary. Return -1 if + * no such entry is found. dir must already be complete. */ -static struct ref_entry *search_ref_dir(struct ref_dir *dir, - const char *refname, size_t len) +static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len) { struct ref_entry **r; struct string_slice key; if (refname == NULL || !dir->nr) - return NULL; + return -1; sort_ref_dir(dir); key.len = len; @@ -363,9 +385,9 @@ static struct ref_entry *search_ref_dir(struct ref_dir *dir, ref_entry_cmp_sslice); if (r == NULL) - return NULL; + return -1; - return *r; + return r - dir->entries; } /* @@ -379,8 +401,9 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir, const char *subdirname, size_t len, int mkdir) { - struct ref_entry *entry = search_ref_dir(dir, subdirname, len); - if (!entry) { + int entry_index = search_ref_dir(dir, subdirname, len); + struct ref_entry *entry; + if (entry_index == -1) { if (!mkdir) return NULL; /* @@ -391,6 +414,8 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir, */ entry = create_dir_entry(dir->ref_cache, subdirname, len, 0); add_entry_to_dir(dir, entry); + } else { + entry = dir->entries[entry_index]; } return get_ref_dir(entry); } @@ -429,12 +454,67 @@ static struct ref_dir *find_containing_dir(struct ref_dir *dir, */ static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname) { + int entry_index; struct ref_entry *entry; dir = find_containing_dir(dir, refname, 0); if (!dir) return NULL; - entry = search_ref_dir(dir, refname, strlen(refname)); - return (entry && !(entry->flag & REF_DIR)) ? entry : NULL; + entry_index = search_ref_dir(dir, refname, strlen(refname)); + if (entry_index == -1) + return NULL; + entry = dir->entries[entry_index]; + return (entry->flag & REF_DIR) ? NULL : entry; +} + +/* + * Remove the entry with the given name from dir, recursing into + * subdirectories as necessary. If refname is the name of a directory + * (i.e., ends with '/'), then remove the directory and its contents. + * If the removal was successful, return the number of entries + * remaining in the directory entry that contained the deleted entry. + * If the name was not found, return -1. Please note that this + * function only deletes the entry from the cache; it does not delete + * it from the filesystem or ensure that other cache entries (which + * might be symbolic references to the removed entry) are updated. + * Nor does it remove any containing dir entries that might be made + * empty by the removal. dir must represent the top-level directory + * and must already be complete. + */ +static int remove_entry(struct ref_dir *dir, const char *refname) +{ + int refname_len = strlen(refname); + int entry_index; + struct ref_entry *entry; + int is_dir = refname[refname_len - 1] == '/'; + if (is_dir) { + /* + * refname represents a reference directory. Remove + * the trailing slash; otherwise we will get the + * directory *representing* refname rather than the + * one *containing* it. + */ + char *dirname = xmemdupz(refname, refname_len - 1); + dir = find_containing_dir(dir, dirname, 0); + free(dirname); + } else { + dir = find_containing_dir(dir, refname, 0); + } + if (!dir) + return -1; + entry_index = search_ref_dir(dir, refname, refname_len); + if (entry_index == -1) + return -1; + entry = dir->entries[entry_index]; + + memmove(&dir->entries[entry_index], + &dir->entries[entry_index + 1], + (dir->nr - entry_index - 1) * sizeof(*dir->entries) + ); + dir->nr--; + if (dir->sorted > entry_index) + dir->sorted--; + free_ref_entry(entry); + return dir->nr; } /* @@ -503,27 +583,64 @@ static void sort_ref_dir(struct ref_dir *dir) dir->sorted = dir->nr = i; } -#define DO_FOR_EACH_INCLUDE_BROKEN 01 +/* Include broken references in a do_for_each_ref*() iteration: */ +#define DO_FOR_EACH_INCLUDE_BROKEN 0x01 + +/* + * Return true iff the reference described by entry can be resolved to + * an object in the database. Emit a warning if the referred-to + * object does not exist. + */ +static int ref_resolves_to_object(struct ref_entry *entry) +{ + if (entry->flag & REF_ISBROKEN) + return 0; + if (!has_sha1_file(entry->u.value.sha1)) { + error("%s does not point to a valid object!", entry->name); + return 0; + } + return 1; +} +/* + * current_ref is a performance hack: when iterating over references + * using the for_each_ref*() functions, current_ref is set to the + * current reference's entry before calling the callback function. If + * the callback function calls peel_ref(), then peel_ref() first + * checks whether the reference to be peeled is the current reference + * (it usually is) and if so, returns that reference's peeled version + * if it is available. This avoids a refname lookup in a common case. + */ static struct ref_entry *current_ref; -static int do_one_ref(const char *base, each_ref_fn fn, int trim, - int flags, void *cb_data, struct ref_entry *entry) +typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data); + +struct ref_entry_cb { + const char *base; + int trim; + int flags; + each_ref_fn *fn; + void *cb_data; +}; + +/* + * Handle one reference in a do_for_each_ref*()-style iteration, + * calling an each_ref_fn for each entry. + */ +static int do_one_ref(struct ref_entry *entry, void *cb_data) { + struct ref_entry_cb *data = cb_data; int retval; - if (prefixcmp(entry->name, base)) + if (prefixcmp(entry->name, data->base)) + return 0; + + if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) && + !ref_resolves_to_object(entry)) return 0; - if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) { - if (entry->flag & REF_ISBROKEN) - return 0; /* ignore broken refs e.g. dangling symref */ - if (!has_sha1_file(entry->u.value.sha1)) { - error("%s does not point to a valid object!", entry->name); - return 0; - } - } current_ref = entry; - retval = fn(entry->name + trim, entry->u.value.sha1, entry->flag, cb_data); + retval = data->fn(entry->name + data->trim, entry->u.value.sha1, + entry->flag, data->cb_data); current_ref = NULL; return retval; } @@ -532,11 +649,11 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, * Call fn for each reference in dir that has index in the range * offset <= index < dir->nr. Recurse into subdirectories that are in * that index range, sorting them before iterating. This function - * does not sort dir itself; it should be sorted beforehand. + * does not sort dir itself; it should be sorted beforehand. fn is + * called for all references, including broken ones. */ -static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset, - const char *base, - each_ref_fn fn, int trim, int flags, void *cb_data) +static int do_for_each_entry_in_dir(struct ref_dir *dir, int offset, + each_ref_entry_fn fn, void *cb_data) { int i; assert(dir->sorted == dir->nr); @@ -546,10 +663,9 @@ static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset, if (entry->flag & REF_DIR) { struct ref_dir *subdir = get_ref_dir(entry); sort_ref_dir(subdir); - retval = do_for_each_ref_in_dir(subdir, 0, - base, fn, trim, flags, cb_data); + retval = do_for_each_entry_in_dir(subdir, 0, fn, cb_data); } else { - retval = do_one_ref(base, fn, trim, flags, cb_data, entry); + retval = fn(entry, cb_data); } if (retval) return retval; @@ -562,12 +678,12 @@ static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset, * by refname. Recurse into subdirectories. If a value entry appears * in both dir1 and dir2, then only process the version that is in * dir2. The input dirs must already be sorted, but subdirs will be - * sorted as needed. + * sorted as needed. fn is called for all references, including + * broken ones. */ -static int do_for_each_ref_in_dirs(struct ref_dir *dir1, - struct ref_dir *dir2, - const char *base, each_ref_fn fn, int trim, - int flags, void *cb_data) +static int do_for_each_entry_in_dirs(struct ref_dir *dir1, + struct ref_dir *dir2, + each_ref_entry_fn fn, void *cb_data) { int retval; int i1 = 0, i2 = 0; @@ -578,12 +694,10 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1, struct ref_entry *e1, *e2; int cmp; if (i1 == dir1->nr) { - return do_for_each_ref_in_dir(dir2, i2, - base, fn, trim, flags, cb_data); + return do_for_each_entry_in_dir(dir2, i2, fn, cb_data); } if (i2 == dir2->nr) { - return do_for_each_ref_in_dir(dir1, i1, - base, fn, trim, flags, cb_data); + return do_for_each_entry_in_dir(dir1, i1, fn, cb_data); } e1 = dir1->entries[i1]; e2 = dir2->entries[i2]; @@ -595,14 +709,13 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1, struct ref_dir *subdir2 = get_ref_dir(e2); sort_ref_dir(subdir1); sort_ref_dir(subdir2); - retval = do_for_each_ref_in_dirs( - subdir1, subdir2, - base, fn, trim, flags, cb_data); + retval = do_for_each_entry_in_dirs( + subdir1, subdir2, fn, cb_data); i1++; i2++; } else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) { /* Both are references; ignore the one from dir1. */ - retval = do_one_ref(base, fn, trim, flags, cb_data, e2); + retval = fn(e2, cb_data); i1++; i2++; } else { @@ -621,23 +734,15 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1, if (e->flag & REF_DIR) { struct ref_dir *subdir = get_ref_dir(e); sort_ref_dir(subdir); - retval = do_for_each_ref_in_dir( - subdir, 0, - base, fn, trim, flags, cb_data); + retval = do_for_each_entry_in_dir( + subdir, 0, fn, cb_data); } else { - retval = do_one_ref(base, fn, trim, flags, cb_data, e); + retval = fn(e, cb_data); } } if (retval) return retval; } - if (i1 < dir1->nr) - return do_for_each_ref_in_dir(dir1, i1, - base, fn, trim, flags, cb_data); - if (i2 < dir2->nr) - return do_for_each_ref_in_dir(dir2, i2, - base, fn, trim, flags, cb_data); - return 0; } /* @@ -661,14 +766,13 @@ struct name_conflict_cb { const char *conflicting_refname; }; -static int name_conflict_fn(const char *existingrefname, const unsigned char *sha1, - int flags, void *cb_data) +static int name_conflict_fn(struct ref_entry *entry, void *cb_data) { struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data; - if (data->oldrefname && !strcmp(data->oldrefname, existingrefname)) + if (data->oldrefname && !strcmp(data->oldrefname, entry->name)) return 0; - if (names_conflict(data->refname, existingrefname)) { - data->conflicting_refname = existingrefname; + if (names_conflict(data->refname, entry->name)) { + data->conflicting_refname = entry->name; return 1; } return 0; @@ -676,7 +780,7 @@ static int name_conflict_fn(const char *existingrefname, const unsigned char *sh /* * Return true iff a reference named refname could be created without - * conflicting with the name of an existing reference in array. If + * conflicting with the name of an existing reference in dir. If * oldrefname is non-NULL, ignore potential conflicts with oldrefname * (e.g., because oldrefname is scheduled for deletion in the same * operation). @@ -690,9 +794,7 @@ static int is_refname_available(const char *refname, const char *oldrefname, data.conflicting_refname = NULL; sort_ref_dir(dir); - if (do_for_each_ref_in_dir(dir, 0, "", name_conflict_fn, - 0, DO_FOR_EACH_INCLUDE_BROKEN, - &data)) { + if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) { error("'%s' exists; cannot create '%s'", data.conflicting_refname, refname); return 0; @@ -708,9 +810,13 @@ static struct ref_cache { struct ref_cache *next; struct ref_entry *loose; struct ref_entry *packed; - /* The submodule name, or "" for the main repo. */ - char name[FLEX_ARRAY]; -} *ref_cache; + /* + * The submodule name, or "" for the main repo. We allocate + * length 1 rather than FLEX_ARRAY so that the main ref_cache + * is initialized correctly. + */ + char name[1]; +} ref_cache, *submodule_ref_caches; static void clear_packed_ref_cache(struct ref_cache *refs) { @@ -748,18 +854,18 @@ static struct ref_cache *create_ref_cache(const char *submodule) */ static struct ref_cache *get_ref_cache(const char *submodule) { - struct ref_cache *refs = ref_cache; - if (!submodule) - submodule = ""; - while (refs) { + struct ref_cache *refs; + + if (!submodule || !*submodule) + return &ref_cache; + + for (refs = submodule_ref_caches; refs; refs = refs->next) if (!strcmp(submodule, refs->name)) return refs; - refs = refs->next; - } refs = create_ref_cache(submodule); - refs->next = ref_cache; - ref_cache = refs; + refs->next = submodule_ref_caches; + submodule_ref_caches = refs; return refs; } @@ -770,6 +876,16 @@ void invalidate_ref_cache(const char *submodule) clear_loose_ref_cache(refs); } +/* The length of a peeled reference line in packed-refs, including EOL: */ +#define PEELED_LINE_LENGTH 42 + +/* + * The packed-refs header line that we write out. Perhaps other + * traits will be added later. The trailing space is required. + */ +static const char PACKED_REFS_HEADER[] = + "# pack-refs with: peeled fully-peeled \n"; + /* * Parse one line from a packed-refs file. Write the SHA1 to sha1. * Return a pointer to the refname within the line (null-terminated), @@ -862,8 +978,8 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir) } if (last && refline[0] == '^' && - strlen(refline) == 42 && - refline[41] == '\n' && + strlen(refline) == PEELED_LINE_LENGTH && + refline[PEELED_LINE_LENGTH - 1] == '\n' && !get_sha1_hex(refline + 1, sha1)) { hashcpy(last->u.value.peeled, sha1); /* @@ -898,8 +1014,8 @@ static struct ref_dir *get_packed_refs(struct ref_cache *refs) void add_packed_ref(const char *refname, const unsigned char *sha1) { - add_ref(get_packed_refs(get_ref_cache(NULL)), - create_ref_entry(refname, sha1, REF_ISPACKED, 1)); + add_ref(get_packed_refs(&ref_cache), + create_ref_entry(refname, sha1, REF_ISPACKED, 1)); } /* @@ -1069,18 +1185,12 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sh } /* - * Try to read ref from the packed references. On success, set sha1 - * and return 0; otherwise, return -1. + * Return the ref_entry for the given refname from the packed + * references. If it does not exist, return NULL. */ -static int get_packed_ref(const char *refname, unsigned char *sha1) +static struct ref_entry *get_packed_ref(const char *refname) { - struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL)); - struct ref_entry *entry = find_ref(packed, refname); - if (entry) { - hashcpy(sha1, entry->u.value.sha1); - return 0; - } - return -1; + return find_ref(get_packed_refs(&ref_cache), refname); } const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag) @@ -1108,13 +1218,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea git_snpath(path, sizeof(path), "%s", refname); if (lstat(path, &st) < 0) { + struct ref_entry *entry; + if (errno != ENOENT) return NULL; /* * The loose reference file does not exist; * check for a packed reference. */ - if (!get_packed_ref(refname, sha1)) { + entry = get_packed_ref(refname); + if (entry) { + hashcpy(sha1, entry->u.value.sha1); if (flag) *flag |= REF_ISPACKED; return refname; @@ -1231,54 +1345,130 @@ static int filter_refs(const char *refname, const unsigned char *sha1, int flags return filter->fn(refname, sha1, flags, filter->cb_data); } +enum peel_status { + /* object was peeled successfully: */ + PEEL_PEELED = 0, + + /* + * object cannot be peeled because the named object (or an + * object referred to by a tag in the peel chain), does not + * exist. + */ + PEEL_INVALID = -1, + + /* object cannot be peeled because it is not a tag: */ + PEEL_NON_TAG = -2, + + /* ref_entry contains no peeled value because it is a symref: */ + PEEL_IS_SYMREF = -3, + + /* + * ref_entry cannot be peeled because it is broken (i.e., the + * symbolic reference cannot even be resolved to an object + * name): + */ + PEEL_BROKEN = -4 +}; + +/* + * Peel the named object; i.e., if the object is a tag, resolve the + * tag recursively until a non-tag is found. If successful, store the + * result to sha1 and return PEEL_PEELED. If the object is not a tag + * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively, + * and leave sha1 unchanged. + */ +static enum peel_status peel_object(const unsigned char *name, unsigned char *sha1) +{ + struct object *o = lookup_unknown_object(name); + + if (o->type == OBJ_NONE) { + int type = sha1_object_info(name, NULL); + if (type < 0) + return PEEL_INVALID; + o->type = type; + } + + if (o->type != OBJ_TAG) + return PEEL_NON_TAG; + + o = deref_tag_noverify(o); + if (!o) + return PEEL_INVALID; + + hashcpy(sha1, o->sha1); + return PEEL_PEELED; +} + +/* + * Peel the entry (if possible) and return its new peel_status. If + * repeel is true, re-peel the entry even if there is an old peeled + * value that is already stored in it. + * + * It is OK to call this function with a packed reference entry that + * might be stale and might even refer to an object that has since + * been garbage-collected. In such a case, if the entry has + * REF_KNOWS_PEELED then leave the status unchanged and return + * PEEL_PEELED or PEEL_NON_TAG; otherwise, return PEEL_INVALID. + */ +static enum peel_status peel_entry(struct ref_entry *entry, int repeel) +{ + enum peel_status status; + + if (entry->flag & REF_KNOWS_PEELED) { + if (repeel) { + entry->flag &= ~REF_KNOWS_PEELED; + hashclr(entry->u.value.peeled); + } else { + return is_null_sha1(entry->u.value.peeled) ? + PEEL_NON_TAG : PEEL_PEELED; + } + } + if (entry->flag & REF_ISBROKEN) + return PEEL_BROKEN; + if (entry->flag & REF_ISSYMREF) + return PEEL_IS_SYMREF; + + status = peel_object(entry->u.value.sha1, entry->u.value.peeled); + if (status == PEEL_PEELED || status == PEEL_NON_TAG) + entry->flag |= REF_KNOWS_PEELED; + return status; +} + int peel_ref(const char *refname, unsigned char *sha1) { int flag; unsigned char base[20]; - struct object *o; if (current_ref && (current_ref->name == refname - || !strcmp(current_ref->name, refname))) { - if (current_ref->flag & REF_KNOWS_PEELED) { - if (is_null_sha1(current_ref->u.value.peeled)) - return -1; - hashcpy(sha1, current_ref->u.value.peeled); - return 0; - } - hashcpy(base, current_ref->u.value.sha1); - goto fallback; + || !strcmp(current_ref->name, refname))) { + if (peel_entry(current_ref, 0)) + return -1; + hashcpy(sha1, current_ref->u.value.peeled); + return 0; } if (read_ref_full(refname, base, 1, &flag)) return -1; - if ((flag & REF_ISPACKED)) { - struct ref_dir *dir = get_packed_refs(get_ref_cache(NULL)); - struct ref_entry *r = find_ref(dir, refname); - - if (r != NULL && r->flag & REF_KNOWS_PEELED) { + /* + * If the reference is packed, read its ref_entry from the + * cache in the hope that we already know its peeled value. + * We only try this optimization on packed references because + * (a) forcing the filling of the loose reference cache could + * be expensive and (b) loose references anyway usually do not + * have REF_KNOWS_PEELED. + */ + if (flag & REF_ISPACKED) { + struct ref_entry *r = get_packed_ref(refname); + if (r) { + if (peel_entry(r, 0)) + return -1; hashcpy(sha1, r->u.value.peeled); return 0; } } -fallback: - o = lookup_unknown_object(base); - if (o->type == OBJ_NONE) { - int type = sha1_object_info(base, NULL); - if (type < 0) - return -1; - o->type = type; - } - - if (o->type == OBJ_TAG) { - o = deref_tag_noverify(o); - if (o) { - hashcpy(sha1, o->sha1); - return 0; - } - } - return -1; + return peel_object(base, sha1); } struct warn_if_dangling_data { @@ -1316,10 +1506,16 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname) for_each_rawref(warn_if_dangling_symref, &data); } -static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn, - int trim, int flags, void *cb_data) +/* + * Call fn for each reference in the specified ref_cache, omitting + * references not in the containing_dir of base. fn is called for all + * references, including broken ones. If fn ever returns a non-zero + * value, stop the iteration and return that value; otherwise, return + * 0. + */ +static int do_for_each_entry(struct ref_cache *refs, const char *base, + each_ref_entry_fn fn, void *cb_data) { - struct ref_cache *refs = get_ref_cache(submodule); struct ref_dir *packed_dir = get_packed_refs(refs); struct ref_dir *loose_dir = get_loose_refs(refs); int retval = 0; @@ -1332,24 +1528,43 @@ static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn if (packed_dir && loose_dir) { sort_ref_dir(packed_dir); sort_ref_dir(loose_dir); - retval = do_for_each_ref_in_dirs( - packed_dir, loose_dir, - base, fn, trim, flags, cb_data); + retval = do_for_each_entry_in_dirs( + packed_dir, loose_dir, fn, cb_data); } else if (packed_dir) { sort_ref_dir(packed_dir); - retval = do_for_each_ref_in_dir( - packed_dir, 0, - base, fn, trim, flags, cb_data); + retval = do_for_each_entry_in_dir( + packed_dir, 0, fn, cb_data); } else if (loose_dir) { sort_ref_dir(loose_dir); - retval = do_for_each_ref_in_dir( - loose_dir, 0, - base, fn, trim, flags, cb_data); + retval = do_for_each_entry_in_dir( + loose_dir, 0, fn, cb_data); } return retval; } +/* + * Call fn for each reference in the specified ref_cache for which the + * refname begins with base. If trim is non-zero, then trim that many + * characters off the beginning of each refname before passing the + * refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to include + * broken references in the iteration. If fn ever returns a non-zero + * value, stop the iteration and return that value; otherwise, return + * 0. + */ +static int do_for_each_ref(struct ref_cache *refs, const char *base, + each_ref_fn fn, int trim, int flags, void *cb_data) +{ + struct ref_entry_cb data; + data.base = base; + data.trim = trim; + data.flags = flags; + data.fn = fn; + data.cb_data = cb_data; + + return do_for_each_entry(refs, base, do_one_ref, &data); +} + static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data) { unsigned char sha1[20]; @@ -1380,23 +1595,23 @@ int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) int for_each_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref(NULL, "", fn, 0, 0, cb_data); + return do_for_each_ref(&ref_cache, "", fn, 0, 0, cb_data); } int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) { - return do_for_each_ref(submodule, "", fn, 0, 0, cb_data); + return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data); } int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) { - return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data); + return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data); } int for_each_ref_in_submodule(const char *submodule, const char *prefix, each_ref_fn fn, void *cb_data) { - return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data); + return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data); } int for_each_tag_ref(each_ref_fn fn, void *cb_data) @@ -1431,7 +1646,7 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c int for_each_replace_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data); + return do_for_each_ref(&ref_cache, "refs/replace/", fn, 13, 0, cb_data); } int head_ref_namespaced(each_ref_fn fn, void *cb_data) @@ -1454,7 +1669,7 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data) struct strbuf buf = STRBUF_INIT; int ret; strbuf_addf(&buf, "%srefs/", get_git_namespace()); - ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data); + ret = do_for_each_ref(&ref_cache, buf.buf, fn, 0, 0, cb_data); strbuf_release(&buf); return ret; } @@ -1496,7 +1711,7 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data) int for_each_rawref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref(NULL, "", fn, 0, + return do_for_each_ref(&ref_cache, "", fn, 0, DO_FOR_EACH_INCLUDE_BROKEN, cb_data); } @@ -1702,7 +1917,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, * name is a proper prefix of our refname. */ if (missing && - !is_refname_available(refname, NULL, get_packed_refs(get_ref_cache(NULL)))) { + !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) { last_errno = ENOTDIR; goto error_return; } @@ -1754,47 +1969,224 @@ struct ref_lock *lock_any_ref_for_update(const char *refname, return lock_ref_sha1_basic(refname, old_sha1, flags, NULL); } -struct repack_without_ref_sb { - const char *refname; - int fd; -}; - -static int repack_without_ref_fn(const char *refname, const unsigned char *sha1, - int flags, void *cb_data) +/* + * Write an entry to the packed-refs file for the specified refname. + * If peeled is non-NULL, write it as the entry's peeled value. + */ +static void write_packed_entry(int fd, char *refname, unsigned char *sha1, + unsigned char *peeled) { - struct repack_without_ref_sb *data = cb_data; char line[PATH_MAX + 100]; int len; - if (!strcmp(data->refname, refname)) - return 0; len = snprintf(line, sizeof(line), "%s %s\n", sha1_to_hex(sha1), refname); /* this should not happen but just being defensive */ if (len > sizeof(line)) die("too long a refname '%s'", refname); - write_or_die(data->fd, line, len); + write_or_die(fd, line, len); + + if (peeled) { + if (snprintf(line, sizeof(line), "^%s\n", + sha1_to_hex(peeled)) != PEELED_LINE_LENGTH) + die("internal error"); + write_or_die(fd, line, PEELED_LINE_LENGTH); + } +} + +struct ref_to_prune { + struct ref_to_prune *next; + unsigned char sha1[20]; + char name[FLEX_ARRAY]; +}; + +struct pack_refs_cb_data { + unsigned int flags; + struct ref_to_prune *ref_to_prune; + int fd; +}; + +static int pack_one_ref(struct ref_entry *entry, void *cb_data) +{ + struct pack_refs_cb_data *cb = cb_data; + enum peel_status peel_status; + int is_tag_ref = !prefixcmp(entry->name, "refs/tags/"); + + /* ALWAYS pack refs that were already packed or are tags */ + if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref && + !(entry->flag & REF_ISPACKED)) + return 0; + + /* Do not pack symbolic or broken refs: */ + if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry)) + return 0; + + peel_status = peel_entry(entry, 1); + if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG) + die("internal error peeling reference %s (%s)", + entry->name, sha1_to_hex(entry->u.value.sha1)); + write_packed_entry(cb->fd, entry->name, entry->u.value.sha1, + peel_status == PEEL_PEELED ? + entry->u.value.peeled : NULL); + + /* If the ref was already packed, there is no need to prune it. */ + if ((cb->flags & PACK_REFS_PRUNE) && !(entry->flag & REF_ISPACKED)) { + int namelen = strlen(entry->name) + 1; + struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen); + hashcpy(n->sha1, entry->u.value.sha1); + strcpy(n->name, entry->name); + n->next = cb->ref_to_prune; + cb->ref_to_prune = n; + } return 0; } +/* + * Remove empty parents, but spare refs/ and immediate subdirs. + * Note: munges *name. + */ +static void try_remove_empty_parents(char *name) +{ + char *p, *q; + int i; + p = name; + for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */ + while (*p && *p != '/') + p++; + /* tolerate duplicate slashes; see check_refname_format() */ + while (*p == '/') + p++; + } + for (q = p; *q; q++) + ; + while (1) { + while (q > p && *q != '/') + q--; + while (q > p && *(q-1) == '/') + q--; + if (q == p) + break; + *q = '\0'; + if (rmdir(git_path("%s", name))) + break; + } +} + +/* 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_or_warn(git_path("%s", r->name)); + unlock_ref(lock); + try_remove_empty_parents(r->name); + } +} + +static void prune_refs(struct ref_to_prune *r) +{ + while (r) { + prune_ref(r); + r = r->next; + } +} + static struct lock_file packlock; -static int repack_without_ref(const char *refname) +int pack_refs(unsigned int flags) { - struct repack_without_ref_sb data; - struct ref_cache *refs = get_ref_cache(NULL); - struct ref_dir *packed = get_packed_refs(refs); - if (find_ref(packed, refname) == NULL) + struct pack_refs_cb_data cbdata; + + memset(&cbdata, 0, sizeof(cbdata)); + cbdata.flags = flags; + + cbdata.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), + LOCK_DIE_ON_ERROR); + + write_or_die(cbdata.fd, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER)); + + do_for_each_entry(&ref_cache, "", pack_one_ref, &cbdata); + if (commit_lock_file(&packlock) < 0) + die_errno("unable to overwrite old ref-pack file"); + prune_refs(cbdata.ref_to_prune); + return 0; +} + +static int repack_ref_fn(struct ref_entry *entry, void *cb_data) +{ + int *fd = cb_data; + enum peel_status peel_status; + + if (entry->flag & REF_ISBROKEN) { + /* This shouldn't happen to packed refs. */ + error("%s is broken!", entry->name); return 0; - data.refname = refname; - data.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0); - if (data.fd < 0) { + } + if (!has_sha1_file(entry->u.value.sha1)) { + unsigned char sha1[20]; + int flags; + + if (read_ref_full(entry->name, sha1, 0, &flags)) + /* We should at least have found the packed ref. */ + die("Internal error"); + if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) + /* + * This packed reference is overridden by a + * loose reference, so it is OK that its value + * is no longer valid; for example, it might + * refer to an object that has been garbage + * collected. For this purpose we don't even + * care whether the loose reference itself is + * invalid, broken, symbolic, etc. Silently + * omit the packed reference from the output. + */ + return 0; + /* + * There is no overriding loose reference, so the fact + * that this reference doesn't refer to a valid object + * indicates some kind of repository corruption. + * Report the problem, then omit the reference from + * the output. + */ + error("%s does not point to a valid object!", entry->name); + return 0; + } + + peel_status = peel_entry(entry, 0); + write_packed_entry(*fd, entry->name, entry->u.value.sha1, + peel_status == PEEL_PEELED ? + entry->u.value.peeled : NULL); + + return 0; +} + +static int repack_without_ref(const char *refname) +{ + int fd; + struct ref_dir *packed; + + if (!get_packed_ref(refname)) + return 0; /* refname does not exist in packed refs */ + + fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0); + if (fd < 0) { unable_to_lock_error(git_path("packed-refs"), errno); return error("cannot delete '%s' from packed refs", refname); } - clear_packed_ref_cache(refs); - packed = get_packed_refs(refs); - do_for_each_ref_in_dir(packed, 0, "", repack_without_ref_fn, 0, 0, &data); + clear_packed_ref_cache(&ref_cache); + packed = get_packed_refs(&ref_cache); + /* Remove refname from the cache. */ + if (remove_entry(packed, refname) == -1) { + /* + * The packed entry disappeared while we were + * acquiring the lock. + */ + rollback_lock_file(&packlock); + return 0; + } + write_or_die(fd, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER)); + do_for_each_entry_in_dir(packed, 0, repack_ref_fn, &fd); return commit_lock_file(&packlock); } @@ -1823,7 +2215,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt) ret |= repack_without_ref(lock->ref_name); unlink_or_warn(git_path("logs/%s", lock->ref_name)); - invalidate_ref_cache(NULL); + clear_loose_ref_cache(&ref_cache); unlock_ref(lock); return ret; } @@ -1845,7 +2237,6 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms struct stat loginfo; int log = !lstat(git_path("logs/%s", oldrefname), &loginfo); const char *symref = NULL; - struct ref_cache *refs = get_ref_cache(NULL); if (log && S_ISLNK(loginfo.st_mode)) return error("reflog for %s is a symlink", oldrefname); @@ -1857,10 +2248,10 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms if (!symref) return error("refname %s not found", oldrefname); - if (!is_refname_available(newrefname, oldrefname, get_packed_refs(refs))) + if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache))) return 1; - if (!is_refname_available(newrefname, oldrefname, get_loose_refs(refs))) + if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache))) return 1; if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG))) @@ -2116,7 +2507,7 @@ int write_ref_sha1(struct ref_lock *lock, unlock_ref(lock); return -1; } - clear_loose_ref_cache(get_ref_cache(NULL)); + clear_loose_ref_cache(&ref_cache); if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 || (strcmp(lock->ref_name, lock->orig_ref_name) && log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) { @@ -10,8 +10,21 @@ struct ref_lock { int force_write; }; +/* + * Bit values set in the flags argument passed to each_ref_fn(): + */ + +/* Reference is a symbolic reference. */ #define REF_ISSYMREF 0x01 + +/* Reference is a packed reference. */ #define REF_ISPACKED 0x02 + +/* + * Reference cannot be resolved to an object name: dangling symbolic + * reference (directly or indirectly), corrupt reference file, or + * symbolic reference refers to ill-formatted reference name. + */ #define REF_ISBROKEN 0x04 /* @@ -59,8 +72,30 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn */ extern void add_packed_ref(const char *refname, const unsigned char *sha1); +/* + * Flags for controlling behaviour of pack_refs() + * PACK_REFS_PRUNE: Prune loose refs after packing + * PACK_REFS_ALL: Pack _all_ refs, not just tags and already packed refs + */ +#define PACK_REFS_PRUNE 0x0001 +#define PACK_REFS_ALL 0x0002 + +/* + * Write a packed-refs file for the current repository. + * flags: Combination of the above PACK_REFS_* flags. + */ +int pack_refs(unsigned int flags); + extern int ref_exists(const char *); +/* + * If refname is a non-symbolic reference that refers to a tag object, + * and the tag can be (recursively) dereferenced to a non-tag object, + * store the SHA1 of the referred-to object to sha1 and return 0. If + * any of these conditions are not met, return a non-zero value. + * Symbolic references are considered unpeelable, even if they + * ultimately resolve to a peelable tag. + */ extern int peel_ref(const char *refname, unsigned char *sha1); /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/ diff --git a/remote-testsvn.c b/remote-testsvn.c index 5ddf11cc61..d7cd5d272f 100644 --- a/remote-testsvn.c +++ b/remote-testsvn.c @@ -286,7 +286,7 @@ static int do_command(struct strbuf *line) return 0; } -int main(int argc, const char **argv) +int main(int argc, char **argv) { struct strbuf buf = STRBUF_INIT, url_sb = STRBUF_INIT, private_ref_sb = STRBUF_INIT, marksfilename_sb = STRBUF_INIT, diff --git a/revision.c b/revision.c index a67b615bfc..518cd08693 100644 --- a/revision.c +++ b/revision.c @@ -13,6 +13,7 @@ #include "decorate.h" #include "log-tree.h" #include "string-list.h" +#include "line-log.h" #include "mailmap.h" volatile show_early_output_fn_t show_early_output; @@ -915,6 +916,19 @@ static void add_rev_cmdline(struct rev_info *revs, info->nr++; } +static void add_rev_cmdline_list(struct rev_info *revs, + struct commit_list *commit_list, + int whence, + unsigned flags) +{ + while (commit_list) { + struct object *object = &commit_list->item->object; + add_rev_cmdline(revs, object, sha1_to_hex(object->sha1), + whence, flags); + commit_list = commit_list->next; + } +} + struct all_refs_cb { int all_flags; int warned_bad_reflog; @@ -1092,6 +1106,7 @@ static void prepare_show_merge(struct rev_info *revs) add_pending_object(revs, &head->object, "HEAD"); add_pending_object(revs, &other->object, "MERGE_HEAD"); bases = get_merge_bases(head, other, 1); + add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING); add_pending_commit_list(revs, bases, UNINTERESTING); free_commit_list(bases); head->object.flags |= SYMMETRIC_LEFT; @@ -1179,6 +1194,9 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi if (symmetric) { exclude = get_merge_bases(a, b, 1); + add_rev_cmdline_list(revs, exclude, + REV_CMD_MERGE_BASE, + flags_exclude); add_pending_commit_list(revs, exclude, flags_exclude); free_commit_list(exclude); @@ -1897,6 +1915,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->combine_merges) revs->ignore_merges = 0; revs->diffopt.abbrev = revs->abbrev; + + if (revs->line_level_traverse) { + revs->limited = 1; + revs->topo_order = 1; + } + diff_setup_done(&revs->diffopt); grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED, @@ -2168,6 +2192,8 @@ int prepare_revision_walk(struct rev_info *revs) return -1; if (revs->topo_order) sort_in_topological_order(&revs->commits, revs->lifo); + if (revs->line_level_traverse) + line_log_filter(revs); if (revs->simplify_merges) simplify_merges(revs); if (revs->children.name) @@ -2175,12 +2201,6 @@ int prepare_revision_walk(struct rev_info *revs) return 0; } -enum rewrite_result { - rewrite_one_ok, - rewrite_one_noparents, - rewrite_one_error -}; - static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp) { struct commit_list *cache = NULL; @@ -2202,12 +2222,13 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp } } -static int rewrite_parents(struct rev_info *revs, struct commit *commit) +int rewrite_parents(struct rev_info *revs, struct commit *commit, + rewrite_parent_fn_t rewrite_parent) { struct commit_list **pp = &commit->parents; while (*pp) { struct commit_list *parent = *pp; - switch (rewrite_one(revs, &parent->item)) { + switch (rewrite_parent(revs, &parent->item)) { case rewrite_one_ok: break; case rewrite_one_noparents: @@ -2373,7 +2394,7 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit) if (action == commit_show && !revs->show_all && revs->prune && revs->dense && want_ancestry(revs)) { - if (rewrite_parents(revs, commit) < 0) + if (rewrite_parents(revs, commit, rewrite_one) < 0) return commit_error; } return action; diff --git a/revision.h b/revision.h index 01bd2b7c07..a313a13405 100644 --- a/revision.h +++ b/revision.h @@ -35,6 +35,7 @@ struct rev_cmdline_info { REV_CMD_PARENTS_ONLY, REV_CMD_LEFT, REV_CMD_RIGHT, + REV_CMD_MERGE_BASE, REV_CMD_REV } whence; unsigned flags; @@ -96,7 +97,8 @@ struct rev_info { cherry_mark:1, bisect:1, ancestry_path:1, - first_parent_only:1; + first_parent_only:1, + line_level_traverse:1; /* Diff flags */ unsigned int diff:1, @@ -175,6 +177,9 @@ struct rev_info { int count_left; int count_right; int count_same; + + /* line level range that we are chasing */ + struct decoration line_log_data; }; #define REV_TREE_SAME 0 @@ -241,4 +246,14 @@ enum commit_action { extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit); extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit); +enum rewrite_result { + rewrite_one_ok, + rewrite_one_noparents, + rewrite_one_error +}; + +typedef enum rewrite_result (*rewrite_parent_fn_t)(struct rev_info *revs, struct commit **pp); + +extern int rewrite_parents(struct rev_info *revs, struct commit *commit, + rewrite_parent_fn_t rewrite_parent); #endif diff --git a/sha1_file.c b/sha1_file.c index 67e815b2db..b114cc922d 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2138,7 +2138,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, if (!data) die("failed to apply delta"); - free (delta_data); + free(delta_data); } *final_type = type; diff --git a/sha1_name.c b/sha1_name.c index 3820f28ae7..371a49d98d 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -1055,9 +1055,13 @@ int interpret_branch_name(const char *name, struct strbuf *buf) int strbuf_branchname(struct strbuf *sb, const char *name) { int len = strlen(name); - if (interpret_branch_name(name, sb) == len) + int used = interpret_branch_name(name, sb); + + if (used == len) return 0; - strbuf_add(sb, name, len); + if (used < 0) + used = 0; + strbuf_add(sb, name + used, len - used); return len; } diff --git a/t/Makefile b/t/Makefile index 44ca7d32dc..2373a04f7a 100644 --- a/t/Makefile +++ b/t/Makefile @@ -15,9 +15,16 @@ PROVE ?= prove DEFAULT_TEST_TARGET ?= test TEST_LINT ?= test-lint-duplicates test-lint-executable +ifdef TEST_OUTPUT_DIRECTORY +TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results +else +TEST_RESULTS_DIRECTORY = test-results +endif + # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) +TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh)) @@ -36,10 +43,10 @@ $(T): @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) pre-clean: - $(RM) -r test-results + $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' clean-except-prove-cache: - $(RM) -r 'trash directory'.* test-results + $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)' $(RM) -r valgrind/bin clean: clean-except-prove-cache @@ -65,7 +72,7 @@ aggregate-results-and-cleanup: $(T) $(MAKE) clean aggregate-results: - for f in test-results/t*-*.counts; do \ + for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \ echo "$$f"; \ done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh @@ -324,6 +324,9 @@ Don't: use 'test_must_fail git cmd'. This will signal a failure if git dies in an unexpected way (e.g. segfault). + On the other hand, don't use test_must_fail for running regular + platform commands; just use '! cmd'. + - use perl without spelling it as "$PERL_PATH". This is to help our friends on Windows where the platform Perl often adds CR before the end of line, and they bundle Git with a version of Perl that diff --git a/t/perf/p4211-line-log.sh b/t/perf/p4211-line-log.sh new file mode 100755 index 0000000000..3d074b0e41 --- /dev/null +++ b/t/perf/p4211-line-log.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +test_description='Tests log -L performance' +. ./perf-lib.sh + +test_perf_default_repo + +# Pick a file to log pseudo-randomly. The sort key is the blob hash, +# so it is stable. +test_expect_success 'select a file' ' + git ls-tree HEAD | grep ^100644 | + sort -k 3 | head -1 | cut -f 2 >filelist +' + +file=$(cat filelist) +export file + +test_perf 'git rev-list --topo-order (baseline)' ' + git rev-list --topo-order HEAD >/dev/null +' + +test_perf 'git log --follow (baseline for -M)' ' + git log --oneline --follow -- "$file" >/dev/null +' + +test_perf 'git log -L' ' + git log -L 1:"$file" >/dev/null +' + +test_perf 'git log -M -L' ' + git log -M -L 1:"$file" >/dev/null +' + +test_done diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh index 9c1bde1fd6..a56db804cb 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -66,16 +66,23 @@ test_check_ignore () { init_vars && rm -f "$HOME/stdout" "$HOME/stderr" "$HOME/cmd" && - echo git $global_args check-ignore $quiet_opt $verbose_opt $args \ + echo git $global_args check-ignore $quiet_opt $verbose_opt $non_matching_opt $args \ >"$HOME/cmd" && + echo "$expect_code" >"$HOME/expected-exit-code" && test_expect_code "$expect_code" \ - git $global_args check-ignore $quiet_opt $verbose_opt $args \ + git $global_args check-ignore $quiet_opt $verbose_opt $non_matching_opt $args \ >"$HOME/stdout" 2>"$HOME/stderr" && test_cmp "$HOME/expected-stdout" "$HOME/stdout" && stderr_empty_on_success "$expect_code" } -# Runs the same code with 3 different levels of output verbosity, +# Runs the same code with 4 different levels of output verbosity: +# +# 1. with -q / --quiet +# 2. with default verbosity +# 3. with -v / --verbose +# 4. with -v / --verbose, *and* -n / --non-matching +# # expecting success each time. Takes advantage of the fact that # check-ignore --verbose output is the same as normal output except # for the extra first column. @@ -83,7 +90,9 @@ test_check_ignore () { # Arguments: # - (optional) prereqs for this test, e.g. 'SYMLINKS' # - test name -# - output to expect from -v / --verbose mode +# - output to expect from the fourth verbosity mode (the output +# from the other verbosity modes is automatically inferred +# from this value) # - code to run (should invoke test_check_ignore) test_expect_success_multi () { prereq= @@ -92,8 +101,9 @@ test_expect_success_multi () { prereq=$1 shift fi - testname="$1" expect_verbose="$2" code="$3" + testname="$1" expect_all="$2" code="$3" + expect_verbose=$( echo "$expect_all" | grep -v '^:: ' ) expect=$( echo "$expect_verbose" | sed -e 's/.* //' ) test_expect_success $prereq "$testname" ' @@ -101,23 +111,40 @@ test_expect_success_multi () { eval "$code" ' - for quiet_opt in '-q' '--quiet' - do - test_expect_success $prereq "$testname${quiet_opt:+ with $quiet_opt}" " + # --quiet is only valid when a single pattern is passed + if test $( echo "$expect_all" | wc -l ) = 1 + then + for quiet_opt in '-q' '--quiet' + do + test_expect_success $prereq "$testname${quiet_opt:+ with $quiet_opt}" " expect '' && $code " - done - quiet_opt= + done + quiet_opt= + fi for verbose_opt in '-v' '--verbose' do - test_expect_success $prereq "$testname${verbose_opt:+ with $verbose_opt}" " - expect '$expect_verbose' && - $code - " + for non_matching_opt in '' ' -n' ' --non-matching' + do + if test -n "$non_matching_opt" + then + my_expect="$expect_all" + else + my_expect="$expect_verbose" + fi + + test_code=" + expect '$my_expect' && + $code + " + opts="$verbose_opt$non_matching_opt" + test_expect_success $prereq "$testname${opts:+ with $opts}" "$test_code" + done done verbose_opt= + non_matching_opt= } test_expect_success 'setup' ' @@ -178,7 +205,7 @@ test_expect_success 'setup' ' # # test invalid inputs -test_expect_success_multi '. corner-case' '' ' +test_expect_success_multi '. corner-case' ':: .' ' test_check_ignore . 1 ' @@ -189,11 +216,7 @@ test_expect_success_multi 'empty command line' '' ' test_expect_success_multi '--stdin with empty STDIN' '' ' test_check_ignore "--stdin" 1 </dev/null && - if test -n "$quiet_opt"; then - test_stderr "" - else - test_stderr "no pathspec given." - fi + test_stderr "" ' test_expect_success '-q with multiple args' ' @@ -276,27 +299,39 @@ do where="in subdir $subdir" fi - test_expect_success_multi "non-existent file $where not ignored" '' " - test_check_ignore '${subdir}non-existent' 1 - " + test_expect_success_multi "non-existent file $where not ignored" \ + ":: ${subdir}non-existent" \ + "test_check_ignore '${subdir}non-existent' 1" test_expect_success_multi "non-existent file $where ignored" \ - ".gitignore:1:one ${subdir}one" " - test_check_ignore '${subdir}one' - " + ".gitignore:1:one ${subdir}one" \ + "test_check_ignore '${subdir}one'" - test_expect_success_multi "existing untracked file $where not ignored" '' " - test_check_ignore '${subdir}not-ignored' 1 - " + test_expect_success_multi "existing untracked file $where not ignored" \ + ":: ${subdir}not-ignored" \ + "test_check_ignore '${subdir}not-ignored' 1" - test_expect_success_multi "existing tracked file $where not ignored" '' " - test_check_ignore '${subdir}ignored-but-in-index' 1 - " + test_expect_success_multi "existing tracked file $where not ignored" \ + ":: ${subdir}ignored-but-in-index" \ + "test_check_ignore '${subdir}ignored-but-in-index' 1" test_expect_success_multi "existing untracked file $where ignored" \ - ".gitignore:2:ignored-* ${subdir}ignored-and-untracked" " - test_check_ignore '${subdir}ignored-and-untracked' - " + ".gitignore:2:ignored-* ${subdir}ignored-and-untracked" \ + "test_check_ignore '${subdir}ignored-and-untracked'" + + test_expect_success_multi "mix of file types $where" \ +":: ${subdir}non-existent +.gitignore:1:one ${subdir}one +:: ${subdir}not-ignored +:: ${subdir}ignored-but-in-index +.gitignore:2:ignored-* ${subdir}ignored-and-untracked" \ + "test_check_ignore ' + ${subdir}non-existent + ${subdir}one + ${subdir}not-ignored + ${subdir}ignored-but-in-index + ${subdir}ignored-and-untracked' + " done # Having established the above, from now on we mostly test against @@ -391,7 +426,7 @@ test_expect_success 'cd to ignored sub-directory with -v' ' # # test handling of symlinks -test_expect_success_multi SYMLINKS 'symlink' '' ' +test_expect_success_multi SYMLINKS 'symlink' ':: a/symlink' ' test_check_ignore "a/symlink" 1 ' @@ -574,37 +609,34 @@ cat <<-\EOF >stdin globaltwo b/globaltwo ../b/globaltwo + c/not-ignored EOF -cat <<-\EOF >expected-default - ../one - one - b/on - b/one - b/one one - b/one two - "b/one\"three" - b/two - b/twooo - ../globaltwo - globaltwo - b/globaltwo - ../b/globaltwo -EOF -cat <<-EOF >expected-verbose +# N.B. we deliberately end STDIN with a non-matching pattern in order +# to test that the exit code indicates that one or more of the +# provided paths is ignored - in other words, that it represents an +# aggregation of all the results, not just the final result. + +cat <<-EOF >expected-all .gitignore:1:one ../one + :: ../not-ignored .gitignore:1:one one + :: not-ignored a/b/.gitignore:8:!on* b/on a/b/.gitignore:8:!on* b/one a/b/.gitignore:8:!on* b/one one a/b/.gitignore:8:!on* b/one two a/b/.gitignore:8:!on* "b/one\"three" a/b/.gitignore:9:!two b/two + :: b/not-ignored a/.gitignore:1:two* b/twooo $global_excludes:2:!globaltwo ../globaltwo $global_excludes:2:!globaltwo globaltwo $global_excludes:2:!globaltwo b/globaltwo $global_excludes:2:!globaltwo ../b/globaltwo + :: c/not-ignored EOF +grep -v '^:: ' expected-all >expected-verbose +sed -e 's/.* //' expected-verbose >expected-default sed -e 's/^"//' -e 's/\\//' -e 's/"$//' stdin | \ tr "\n" "\0" >stdin0 @@ -629,6 +661,14 @@ test_expect_success '--stdin from subdirectory with -v' ' ) ' +test_expect_success '--stdin from subdirectory with -v -n' ' + expect_from_stdin <expected-all && + ( + cd a && + test_check_ignore "--stdin -v -n" <../stdin + ) +' + for opts in '--stdin -z' '-z --stdin' do test_expect_success "$opts from subdirectory" ' @@ -648,5 +688,23 @@ do ' done +test_expect_success PIPE 'streaming support for --stdin' ' + mkfifo in out && + (git check-ignore -n -v --stdin <in >out &) && + + # We cannot just "echo >in" because check-ignore would get EOF + # after echo exited; instead we open the descriptor in our + # shell, and then echo to the fd. We make sure to close it at + # the end, so that the subprocess does get EOF and dies + # properly. + exec 9>in && + test_when_finished "exec 9>&-" && + echo >&9 one && + read response <out && + echo "$response" | grep "^\.gitignore:1:one one" && + echo >&9 two && + read response <out && + echo "$response" | grep "^:: two" +' test_done diff --git a/t/t0100-previous.sh b/t/t0100-previous.sh index 315b9b3f10..e0a6940232 100755 --- a/t/t0100-previous.sh +++ b/t/t0100-previous.sh @@ -27,6 +27,7 @@ test_expect_success 'merge @{-1}' ' test_commit B && git checkout A && test_commit C && + test_commit D && git branch -f master B && git branch -f other && git checkout other && @@ -35,14 +36,24 @@ test_expect_success 'merge @{-1}' ' git cat-file commit HEAD | grep "Merge branch '\''other'\''" ' -test_expect_success 'merge @{-1} when there is not enough switches yet' ' +test_expect_success 'merge @{-1}~1' ' + git checkout master && + git reset --hard B && + git checkout other && + git checkout master && + git merge @{-1}~1 && + git cat-file commit HEAD >actual && + grep "Merge branch '\''other'\''" actual +' + +test_expect_success 'merge @{-100} before checking out that many branches yet' ' git reflog expire --expire=now && git checkout -f master && git reset --hard B && git branch -f other C && git checkout other && git checkout master && - test_must_fail git merge @{-12} + test_must_fail git merge @{-100} ' test_done diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh new file mode 100755 index 0000000000..dee55e428f --- /dev/null +++ b/t/t2024-checkout-dwim.sh @@ -0,0 +1,167 @@ +#!/bin/sh + +test_description='checkout <branch> + +Ensures that checkout on an unborn branch does what the user expects' + +. ./test-lib.sh + +# Is the current branch "refs/heads/$1"? +test_branch () { + printf "%s\n" "refs/heads/$1" >expect.HEAD && + git symbolic-ref HEAD >actual.HEAD && + test_cmp expect.HEAD actual.HEAD +} + +# Is branch "refs/heads/$1" set to pull from "$2/$3"? +test_branch_upstream () { + printf "%s\n" "$2" "refs/heads/$3" >expect.upstream && + { + git config "branch.$1.remote" && + git config "branch.$1.merge" + } >actual.upstream && + test_cmp expect.upstream actual.upstream +} + +test_expect_success 'setup' ' + test_commit my_master && + git init repo_a && + ( + cd repo_a && + test_commit a_master && + git checkout -b foo && + test_commit a_foo && + git checkout -b bar && + test_commit a_bar + ) && + git init repo_b && + ( + cd repo_b && + test_commit b_master && + git checkout -b foo && + test_commit b_foo && + git checkout -b baz && + test_commit b_baz + ) && + git remote add repo_a repo_a && + git remote add repo_b repo_b && + git config remote.repo_b.fetch \ + "+refs/heads/*:refs/remotes/other_b/*" && + git fetch --all +' + +test_expect_success 'checkout of non-existing branch fails' ' + git checkout -B master && + test_might_fail git branch -D xyzzy && + + test_must_fail git checkout xyzzy && + test_must_fail git rev-parse --verify refs/heads/xyzzy && + test_branch master +' + +test_expect_success 'checkout of branch from multiple remotes fails #1' ' + git checkout -B master && + test_might_fail git branch -D foo && + + test_must_fail git checkout foo && + test_must_fail git rev-parse --verify refs/heads/foo && + test_branch master +' + +test_expect_success 'checkout of branch from a single remote succeeds #1' ' + git checkout -B master && + test_might_fail git branch -D bar && + + git checkout bar && + test_branch bar && + test_cmp_rev remotes/repo_a/bar HEAD && + test_branch_upstream bar repo_a bar +' + +test_expect_success 'checkout of branch from a single remote succeeds #2' ' + git checkout -B master && + test_might_fail git branch -D baz && + + git checkout baz && + test_branch baz && + test_cmp_rev remotes/other_b/baz HEAD && + test_branch_upstream baz repo_b baz +' + +test_expect_success '--no-guess suppresses branch auto-vivification' ' + git checkout -B master && + test_might_fail git branch -D bar && + + test_must_fail git checkout --no-guess bar && + test_must_fail git rev-parse --verify refs/heads/bar && + test_branch master +' + +test_expect_success 'setup more remotes with unconventional refspecs' ' + git checkout -B master && + git init repo_c && + ( + cd repo_c && + test_commit c_master && + git checkout -b bar && + test_commit c_bar + git checkout -b spam && + test_commit c_spam + ) && + git init repo_d && + ( + cd repo_d && + test_commit d_master && + git checkout -b baz && + test_commit f_baz + git checkout -b eggs && + test_commit c_eggs + ) && + git remote add repo_c repo_c && + git config remote.repo_c.fetch \ + "+refs/heads/*:refs/remotes/extra_dir/repo_c/extra_dir/*" && + git remote add repo_d repo_d && + git config remote.repo_d.fetch \ + "+refs/heads/*:refs/repo_d/*" && + git fetch --all +' + +test_expect_success 'checkout of branch from multiple remotes fails #2' ' + git checkout -B master && + test_might_fail git branch -D bar && + + test_must_fail git checkout bar && + test_must_fail git rev-parse --verify refs/heads/bar && + test_branch master +' + +test_expect_success 'checkout of branch from multiple remotes fails #3' ' + git checkout -B master && + test_might_fail git branch -D baz && + + test_must_fail git checkout baz && + test_must_fail git rev-parse --verify refs/heads/baz && + test_branch master +' + +test_expect_success 'checkout of branch from a single remote succeeds #3' ' + git checkout -B master && + test_might_fail git branch -D spam && + + git checkout spam && + test_branch spam && + test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD && + test_branch_upstream spam repo_c spam +' + +test_expect_success 'checkout of branch from a single remote succeeds #4' ' + git checkout -B master && + test_might_fail git branch -D eggs && + + git checkout eggs && + test_branch eggs && + test_cmp_rev refs/repo_d/eggs HEAD && + test_branch_upstream eggs repo_d eggs +' + +test_done diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index 4e3735f0cb..f0421c09c7 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -175,6 +175,24 @@ test_expect_success 'negated exclude matches can override previous ones' ' grep "^a.1" output ' +test_expect_success 'excluded directory overrides content patterns' ' + + git ls-files --others --exclude="one" --exclude="!one/a.1" >output && + if grep "^one/a.1" output + then + false + fi +' + +test_expect_success 'negated directory doesn'\''t affect content patterns' ' + + git ls-files --others --exclude="!one" --exclude="one/a.1" >output && + if grep "^one/a.1" output + then + false + fi +' + test_expect_success 'subdirectory ignore (setup)' ' mkdir -p top/l1/l2 && ( diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index d969f0ecd8..44ec6a45f4 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -317,13 +317,13 @@ test_expect_success 'test tracking setup (non-wildcard, matching)' ' test $(git config branch.my4.merge) = refs/heads/master ' -test_expect_success 'test tracking setup (non-wildcard, not matching)' ' +test_expect_success 'tracking setup fails on non-matching refspec' ' git config remote.local.url . && git config remote.local.fetch refs/heads/s:refs/remotes/local/s && (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch --track my5 local/master && - ! test "$(git config branch.my5.remote)" = local && - ! test "$(git config branch.my5.merge)" = refs/heads/master + test_must_fail git branch --track my5 local/master && + test_must_fail git config branch.my5.remote && + test_must_fail git config branch.my5.merge ' test_expect_success 'test tracking setup via config' ' diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index cd04361df8..1a2080e3dc 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -118,4 +118,37 @@ test_expect_success 'pack, prune and repack' ' test_cmp all-of-them again ' +test_expect_success 'explicit pack-refs with dangling packed reference' ' + git commit --allow-empty -m "soon to be garbage-collected" && + git pack-refs --all && + git reset --hard HEAD^ && + git reflog expire --expire=all --all && + git prune --expire=all && + git pack-refs --all 2>result && + test_cmp /dev/null result +' + +test_expect_success 'delete ref with dangling packed version' ' + git checkout -b lamb && + git commit --allow-empty -m "future garbage" && + git pack-refs --all && + git reset --hard HEAD^ && + git checkout master && + git reflog expire --expire=all --all && + git prune --expire=all && + git branch -d lamb 2>result && + test_cmp /dev/null result +' + +test_expect_success 'delete ref while another dangling packed ref' ' + git branch lamb && + git commit --allow-empty -m "future garbage" && + git pack-refs --all && + git reset --hard HEAD^ && + git reflog expire --expire=all --all && + git prune --expire=all && + git branch -d lamb 2>result && + test_cmp /dev/null result +' + test_done diff --git a/t/t3211-peel-ref.sh b/t/t3211-peel-ref.sh index d4d7792eae..3b7caca421 100755 --- a/t/t3211-peel-ref.sh +++ b/t/t3211-peel-ref.sh @@ -61,4 +61,13 @@ test_expect_success 'refs are peeled outside of refs/tags (old packed)' ' test_cmp expect actual ' +test_expect_success 'peeled refs survive deletion of packed ref' ' + git pack-refs --all && + cp .git/packed-refs fully-peeled && + git branch yadda && + git pack-refs --all && + git branch -d yadda && + test_cmp fully-peeled .git/packed-refs +' + test_done diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index 1261dbbdf5..1019d7b35f 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -353,4 +353,52 @@ test_expect_failure 'combine diff coalesce three parents' ' compare_diff_patch expected actual ' +# Test for a bug reported at +# http://thread.gmane.org/gmane.comp.version-control.git/224410 +# where a delete lines were missing from combined diff output when they +# occurred exactly before the context lines of a later change. +test_expect_success 'combine diff missing delete bug' ' + git commit -m initial --allow-empty && + cat <<-\EOF >test && + 1 + 2 + 3 + 4 + EOF + git add test && + git commit -a -m side1 && + git checkout -B side1 && + git checkout HEAD^ && + cat <<-\EOF >test && + 0 + 1 + 2 + 3 + 4modified + EOF + git add test && + git commit -m side2 && + git branch -f side2 && + test_must_fail git merge --no-commit side1 && + cat <<-\EOF >test && + 1 + 2 + 3 + 4modified + EOF + git add test && + git commit -a -m merge && + git diff-tree -c -p HEAD >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + - 0 + 1 + 2 + 3 + -4 + +4modified + EOF + compare_diff_patch expected actual +' + test_done diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh new file mode 100755 index 0000000000..7776f93e3d --- /dev/null +++ b/t/t4211-line-log.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +test_description='test log -L' +. ./test-lib.sh + +test_expect_success 'setup (import history)' ' + git fast-import < "$TEST_DIRECTORY"/t4211/history.export && + git reset --hard +' + +canned_test_1 () { + test_expect_$1 "$2" " + git log $2 >actual && + test_cmp \"\$TEST_DIRECTORY\"/t4211/expect.$3 actual + " +} + +canned_test () { + canned_test_1 success "$@" +} +canned_test_failure () { + canned_test_1 failure "$@" +} + +test_bad_opts () { + test_expect_success "invalid args: $1" " + test_must_fail git log $1 2>errors && + grep '$2' errors + " +} + +canned_test "-L 4,12:a.c simple" simple-f +canned_test "-L 4,+9:a.c simple" simple-f +canned_test "-L '/long f/,/^}/:a.c' simple" simple-f +canned_test "-L :f:a.c simple" simple-f-to-main + +canned_test "-L '/main/,/^}/:a.c' simple" simple-main +canned_test "-L :main:a.c simple" simple-main-to-end + +canned_test "-L 1,+4:a.c simple" beginning-of-file + +canned_test "-L 20:a.c simple" end-of-file + +canned_test "-L '/long f/',/^}/:a.c -L /main/,/^}/:a.c simple" two-ranges +canned_test "-L 24,+1:a.c simple" vanishes-early + +canned_test "-M -L '/long f/,/^}/:b.c' move-support" move-support-f +canned_test "-M -L ':f:b.c' parallel-change" parallel-change-f-to-main + +canned_test "-L 4,12:a.c -L :main:a.c simple" multiple +canned_test "-L 4,18:a.c -L :main:a.c simple" multiple-overlapping +canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping +canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset +canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset + +test_bad_opts "-L" "switch.*requires a value" +test_bad_opts "-L b.c" "argument.*not of the form" +test_bad_opts "-L 1:" "argument.*not of the form" +test_bad_opts "-L 1:nonexistent" "There is no path" +test_bad_opts "-L 1:simple" "There is no path" +test_bad_opts "-L '/foo:b.c'" "argument.*not of the form" +test_bad_opts "-L 1000:b.c" "has only.*lines" +test_bad_opts "-L 1,1000:b.c" "has only.*lines" +test_bad_opts "-L :b.c" "argument.*not of the form" +test_bad_opts "-L :foo:b.c" "no match" + +test_done diff --git a/t/t4211/expect.beginning-of-file b/t/t4211/expect.beginning-of-file new file mode 100644 index 0000000000..91b4054898 --- /dev/null +++ b/t/t4211/expect.beginning-of-file @@ -0,0 +1,43 @@ +commit 4a23ae5c98d59a58c6da036156959f2dc9f472ad +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:47:40 2013 +0100 + + change at very beginning + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -1,3 +1,4 @@ ++#include <unistd.h> + #include <stdio.h> + + long f(long x) + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -1,3 +1,3 @@ + #include <stdio.h> + +-int f(int x) ++long f(long x) + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +1,3 @@ ++#include <stdio.h> ++ ++int f(int x) diff --git a/t/t4211/expect.end-of-file b/t/t4211/expect.end-of-file new file mode 100644 index 0000000000..bd25bb2f59 --- /dev/null +++ b/t/t4211/expect.end-of-file @@ -0,0 +1,62 @@ +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -20,3 +20,5 @@ + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} ++ ++/* incomplete lines are bad! */ + +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -20,3 +20,3 @@ + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -19,3 +19,3 @@ +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +18,3 @@ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/expect.move-support-f b/t/t4211/expect.move-support-f new file mode 100644 index 0000000000..c905e01bc2 --- /dev/null +++ b/t/t4211/expect.move-support-f @@ -0,0 +1,80 @@ +commit 6ce3c4ff690136099bb17e1a8766b75764726ea7 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:49:50 2013 +0100 + + another simple change + +diff --git a/b.c b/b.c +--- a/b.c ++++ b/b.c +@@ -4,9 +4,9 @@ + long f(long x) + { + int s = 0; + while (x) { +- x >>= 1; ++ x /= 2; + s++; + } + return s; + } + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,9 +3,9 @@ +-int f(int x) ++long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + +commit f04fb20f2c77850996cba739709acc6faecc58f7 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:55 2013 +0100 + + change f() + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,8 +3,9 @@ + int f(int x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } ++ return s; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +3,8 @@ ++int f(int x) ++{ ++ int s = 0; ++ while (x) { ++ x >>= 1; ++ s++; ++ } ++} diff --git a/t/t4211/expect.multiple b/t/t4211/expect.multiple new file mode 100644 index 0000000000..76ad5b598c --- /dev/null +++ b/t/t4211/expect.multiple @@ -0,0 +1,104 @@ +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,7 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} ++ ++/* incomplete lines are bad! */ + +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,5 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,9 +3,9 @@ +-int f(int x) ++long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } +@@ -17,5 +17,5 @@ + int main () + { +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } + +commit f04fb20f2c77850996cba739709acc6faecc58f7 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:55 2013 +0100 + + change f() + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,8 +3,9 @@ + int f(int x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } ++ return s; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +3,8 @@ ++int f(int x) ++{ ++ int s = 0; ++ while (x) { ++ x >>= 1; ++ s++; ++ } ++} diff --git a/t/t4211/expect.multiple-overlapping b/t/t4211/expect.multiple-overlapping new file mode 100644 index 0000000000..d930b6eec4 --- /dev/null +++ b/t/t4211/expect.multiple-overlapping @@ -0,0 +1,187 @@ +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -4,19 +4,21 @@ + long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* + * This is only an example! + */ + + int main () + { + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} ++ ++/* incomplete lines are bad! */ + +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -4,19 +4,19 @@ + long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* + * This is only an example! + */ + + int main () + { + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:41 2013 +0100 + + touch comment + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,19 +3,19 @@ + long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* +- * A comment. ++ * This is only an example! + */ + + int main () + { + printf("%ld\n", f(15)); + return 0; + } + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,19 +3,19 @@ +-int f(int x) ++long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* + * A comment. + */ + + int main () + { +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } + +commit f04fb20f2c77850996cba739709acc6faecc58f7 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:55 2013 +0100 + + change f() + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,18 +3,19 @@ + int f(int x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } ++ return s; + } + + /* + * A comment. + */ + + int main () + { + printf("%d\n", f(15)); + return 0; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +3,18 @@ ++int f(int x) ++{ ++ int s = 0; ++ while (x) { ++ x >>= 1; ++ s++; ++ } ++} ++ ++/* ++ * A comment. ++ */ ++ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/expect.multiple-superset b/t/t4211/expect.multiple-superset new file mode 100644 index 0000000000..a1f5bc49c8 --- /dev/null +++ b/t/t4211/expect.multiple-superset @@ -0,0 +1,59 @@ +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,9 +3,9 @@ +-int f(int x) ++long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + +commit f04fb20f2c77850996cba739709acc6faecc58f7 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:55 2013 +0100 + + change f() + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,8 +3,9 @@ + int f(int x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } ++ return s; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +3,8 @@ ++int f(int x) ++{ ++ int s = 0; ++ while (x) { ++ x >>= 1; ++ s++; ++ } ++} diff --git a/t/t4211/expect.parallel-change-f-to-main b/t/t4211/expect.parallel-change-f-to-main new file mode 100644 index 0000000000..052def8074 --- /dev/null +++ b/t/t4211/expect.parallel-change-f-to-main @@ -0,0 +1,160 @@ +commit 0469c60bc4837d52d97b1f081dec5f98dea20fed +Merge: ba227c6 6ce3c4f +Author: Thomas Rast <trast@inf.ethz.ch> +Date: Fri Apr 12 16:16:24 2013 +0200 + + Merge across the rename + + +commit 6ce3c4ff690136099bb17e1a8766b75764726ea7 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:49:50 2013 +0100 + + another simple change + +diff --git a/b.c b/b.c +--- a/b.c ++++ b/b.c +@@ -4,14 +4,14 @@ + long f(long x) + { + int s = 0; + while (x) { +- x >>= 1; ++ x /= 2; + s++; + } + return s; + } + + /* + * This is only an example! + */ + + +commit ba227c6632349700fbb957dec2b50f5e2358be3f +Author: Thomas Rast <trast@inf.ethz.ch> +Date: Fri Apr 12 16:15:57 2013 +0200 + + change on another line of history while rename happens + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -4,14 +4,14 @@ + long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* +- * This is only an example! ++ * This is only a short example! + */ + + +commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:41 2013 +0100 + + touch comment + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,14 +3,14 @@ + long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* +- * A comment. ++ * This is only an example! + */ + + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,14 +3,14 @@ +-int f(int x) ++long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* + * A comment. + */ + + +commit f04fb20f2c77850996cba739709acc6faecc58f7 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:55 2013 +0100 + + change f() + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,13 +3,14 @@ + int f(int x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } ++ return s; + } + + /* + * A comment. + */ + + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +3,13 @@ ++int f(int x) ++{ ++ int s = 0; ++ while (x) { ++ x >>= 1; ++ s++; ++ } ++} ++ ++/* ++ * A comment. ++ */ ++ diff --git a/t/t4211/expect.simple-f b/t/t4211/expect.simple-f new file mode 100644 index 0000000000..a1f5bc49c8 --- /dev/null +++ b/t/t4211/expect.simple-f @@ -0,0 +1,59 @@ +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,9 +3,9 @@ +-int f(int x) ++long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + +commit f04fb20f2c77850996cba739709acc6faecc58f7 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:55 2013 +0100 + + change f() + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,8 +3,9 @@ + int f(int x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } ++ return s; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +3,8 @@ ++int f(int x) ++{ ++ int s = 0; ++ while (x) { ++ x >>= 1; ++ s++; ++ } ++} diff --git a/t/t4211/expect.simple-f-to-main b/t/t4211/expect.simple-f-to-main new file mode 100644 index 0000000000..a475768710 --- /dev/null +++ b/t/t4211/expect.simple-f-to-main @@ -0,0 +1,100 @@ +commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:41 2013 +0100 + + touch comment + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,14 +3,14 @@ + long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* +- * A comment. ++ * This is only an example! + */ + + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,14 +3,14 @@ +-int f(int x) ++long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* + * A comment. + */ + + +commit f04fb20f2c77850996cba739709acc6faecc58f7 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:55 2013 +0100 + + change f() + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,13 +3,14 @@ + int f(int x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } ++ return s; + } + + /* + * A comment. + */ + + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +3,13 @@ ++int f(int x) ++{ ++ int s = 0; ++ while (x) { ++ x >>= 1; ++ s++; ++ } ++} ++ ++/* ++ * A comment. ++ */ ++ diff --git a/t/t4211/expect.simple-main b/t/t4211/expect.simple-main new file mode 100644 index 0000000000..39ce39bebe --- /dev/null +++ b/t/t4211/expect.simple-main @@ -0,0 +1,68 @@ +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,5 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} + +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,5 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -17,5 +17,5 @@ + int main () + { +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/expect.simple-main-to-end b/t/t4211/expect.simple-main-to-end new file mode 100644 index 0000000000..8480bd9cc4 --- /dev/null +++ b/t/t4211/expect.simple-main-to-end @@ -0,0 +1,70 @@ +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,7 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} ++ ++/* incomplete lines are bad! */ + +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,5 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -17,5 +17,5 @@ + int main () + { +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/expect.two-ranges b/t/t4211/expect.two-ranges new file mode 100644 index 0000000000..6109aa0dce --- /dev/null +++ b/t/t4211/expect.two-ranges @@ -0,0 +1,102 @@ +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,5 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} + +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,5 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,9 +3,9 @@ +-int f(int x) ++long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } +@@ -17,5 +17,5 @@ + int main () + { +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } + +commit f04fb20f2c77850996cba739709acc6faecc58f7 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:55 2013 +0100 + + change f() + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,8 +3,9 @@ + int f(int x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } ++ return s; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +3,8 @@ ++int f(int x) ++{ ++ int s = 0; ++ while (x) { ++ x >>= 1; ++ s++; ++ } ++} diff --git a/t/t4211/expect.vanishes-early b/t/t4211/expect.vanishes-early new file mode 100644 index 0000000000..1f7cd06941 --- /dev/null +++ b/t/t4211/expect.vanishes-early @@ -0,0 +1,39 @@ +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -22,1 +24,1 @@ +-} +\ No newline at end of file ++/* incomplete lines are bad! */ + +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -22,1 +22,1 @@ +-} ++} +\ No newline at end of file + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +20,1 @@ ++} diff --git a/t/t4211/history.export b/t/t4211/history.export new file mode 100644 index 0000000000..f9f41e211e --- /dev/null +++ b/t/t4211/history.export @@ -0,0 +1,406 @@ +blob +mark :1 +data 157 +#include <stdio.h> + +int f(int x) +{ + int s = 0; + while (x) { + x >>= 1; + s++; + } +} + +/* + * A comment. + */ + +int main () +{ + printf("%d\n", f(15)); + return 0; +} + +reset refs/tags/simple +commit refs/tags/simple +mark :2 +author Thomas Rast <trast@student.ethz.ch> 1362044688 +0100 +committer Thomas Rast <trast@student.ethz.ch> 1362044688 +0100 +data 8 +initial +M 100644 :1 a.c + +blob +mark :3 +data 168 +#include <stdio.h> + +int f(int x) +{ + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; +} + +/* + * A comment. + */ + +int main () +{ + printf("%d\n", f(15)); + return 0; +} + +commit refs/tags/simple +mark :4 +author Thomas Rast <trast@student.ethz.ch> 1362044695 +0100 +committer Thomas Rast <trast@student.ethz.ch> 1362044695 +0100 +data 11 +change f() +from :2 +M 100644 :3 a.c + +blob +mark :5 +data 171 +#include <stdio.h> + +long f(long x) +{ + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; +} + +/* + * A comment. + */ + +int main () +{ + printf("%ld\n", f(15)); + return 0; +} + +commit refs/tags/simple +mark :6 +author Thomas Rast <trast@student.ethz.ch> 1362044716 +0100 +committer Thomas Rast <trast@student.ethz.ch> 1362044716 +0100 +data 21 +touch both functions +from :4 +M 100644 :5 a.c + +blob +mark :7 +data 185 +#include <stdio.h> + +long f(long x) +{ + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; +} + +/* + * This is only an example! + */ + +int main () +{ + printf("%ld\n", f(15)); + return 0; +} + +commit refs/tags/simple +mark :8 +author Thomas Rast <trast@student.ethz.ch> 1362044741 +0100 +committer Thomas Rast <trast@student.ethz.ch> 1362044741 +0100 +data 14 +touch comment +from :6 +M 100644 :7 a.c + +blob +mark :9 +data 205 +#include <unistd.h> +#include <stdio.h> + +long f(long x) +{ + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; +} + +/* + * This is only an example! + */ + +int main () +{ + printf("%ld\n", f(15)); + return 0; +} + +commit refs/tags/simple +mark :10 +author Thomas Rast <trast@student.ethz.ch> 1362044860 +0100 +committer Thomas Rast <trast@student.ethz.ch> 1362044860 +0100 +data 25 +change at very beginning +from :8 +M 100644 :9 a.c + +blob +mark :11 +data 204 +#include <unistd.h> +#include <stdio.h> + +long f(long x) +{ + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; +} + +/* + * This is only an example! + */ + +int main () +{ + printf("%ld\n", f(15)); + return 0; +} +commit refs/tags/simple +mark :12 +author Thomas Rast <trast@student.ethz.ch> 1362044890 +0100 +committer Thomas Rast <trast@student.ethz.ch> 1362044890 +0100 +data 36 +change to an incomplete line at end +from :10 +M 100644 :11 a.c + +blob +mark :13 +data 238 +#include <unistd.h> +#include <stdio.h> + +long f(long x) +{ + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; +} + +/* + * This is only an example! + */ + +int main () +{ + printf("%ld\n", f(15)); + return 0; +} + +/* incomplete lines are bad! */ + +commit refs/tags/simple +mark :14 +author Thomas Rast <trast@student.ethz.ch> 1362044923 +0100 +committer Thomas Rast <trast@student.ethz.ch> 1362044923 +0100 +data 29 +change back to complete line +from :12 +M 100644 :13 a.c + +commit refs/tags/move-support +mark :15 +author Thomas Rast <trast@student.ethz.ch> 1362044968 +0100 +committer Thomas Rast <trast@student.ethz.ch> 1362044968 +0100 +data 10 +move file +from :14 +D a.c +M 100644 :13 b.c + +blob +mark :16 +data 237 +#include <unistd.h> +#include <stdio.h> + +long f(long x) +{ + int s = 0; + while (x) { + x /= 2; + s++; + } + return s; +} + +/* + * This is only an example! + */ + +int main () +{ + printf("%ld\n", f(15)); + return 0; +} + +/* incomplete lines are bad! */ + +commit refs/tags/move-support +mark :17 +author Thomas Rast <trast@student.ethz.ch> 1362044990 +0100 +committer Thomas Rast <trast@student.ethz.ch> 1362044990 +0100 +data 22 +another simple change +from :15 +M 100644 :16 b.c + +blob +mark :18 +data 254 +#include <unistd.h> +#include <stdio.h> + +long f(long x); + +/* + * This is only an example! + */ + +int main () +{ + printf("%ld\n", f(15)); + return 0; +} + +/* incomplete lines are bad! */ + +long f(long x) +{ + int s = 0; + while (x) { + x /= 2; + s++; + } + return s; +} + +commit refs/heads/master +mark :19 +author Thomas Rast <trast@student.ethz.ch> 1362045024 +0100 +committer Thomas Rast <trast@student.ethz.ch> 1362045024 +0100 +data 21 +move within the file +from :17 +M 100644 :18 b.c + +blob +mark :20 +data 243 +#include <unistd.h> +#include <stdio.h> + +long f(long x) +{ + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; +} + +/* + * This is only a short example! + */ + +int main () +{ + printf("%ld\n", f(15)); + return 0; +} + +/* incomplete lines are bad! */ + +commit refs/heads/parallel-change +mark :21 +author Thomas Rast <trast@inf.ethz.ch> 1365776157 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1365776157 +0200 +data 55 +change on another line of history while rename happens +from :14 +M 100644 :20 a.c + +blob +mark :22 +data 242 +#include <unistd.h> +#include <stdio.h> + +long f(long x) +{ + int s = 0; + while (x) { + x /= 2; + s++; + } + return s; +} + +/* + * This is only a short example! + */ + +int main () +{ + printf("%ld\n", f(15)); + return 0; +} + +/* incomplete lines are bad! */ + +commit refs/heads/parallel-change +mark :23 +author Thomas Rast <trast@inf.ethz.ch> 1365776184 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1365776191 +0200 +data 24 +Merge across the rename +from :21 +merge :17 +D a.c +M 100644 :22 b.c + +reset refs/heads/parallel-change +from :23 + diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 3fbd366ec3..c2023b1a3d 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -30,10 +30,76 @@ GUNZIP=${GUNZIP:-gzip -d} SUBSTFORMAT=%H%n +test_lazy_prereq TAR_NEEDS_PAX_FALLBACK ' + ( + mkdir pax && + cd pax && + "$TAR" xf "$TEST_DIRECTORY"/t5000/pax.tar && + test -f PaxHeaders.1791/file + ) +' + +get_pax_header() { + file=$1 + header=$2= + + while read len rest + do + if test "$len" = $(echo "$len $rest" | wc -c) + then + case "$rest" in + $header*) + echo "${rest#$header}" + ;; + esac + fi + done <"$file" +} + +check_tar() { + tarfile=$1.tar + listfile=$1.lst + dir=$1 + dir_with_prefix=$dir/$2 + + test_expect_success ' extract tar archive' ' + (mkdir $dir && cd $dir && "$TAR" xf -) <$tarfile + ' + + test_expect_success TAR_NEEDS_PAX_FALLBACK ' interpret pax headers' ' + ( + cd $dir && + for header in *.paxheader + do + data=${header%.paxheader}.data && + if test -h $data -o -e $data + then + path=$(get_pax_header $header path) && + if test -n "$path" + then + mv "$data" "$path" + fi + fi + done + ) + ' + + test_expect_success ' validate filenames' ' + (cd ${dir_with_prefix}a && find .) | sort >$listfile && + test_cmp a.lst $listfile + ' + + test_expect_success ' validate file contents' ' + diff -r a ${dir_with_prefix}a + ' +} + test_expect_success \ 'populate workdir' \ - 'mkdir a b c && + 'mkdir a && echo simple textfile >a/a && + ten=0123456789 && hundred=$ten$ten$ten$ten$ten$ten$ten$ten$ten$ten && + echo long filename >a/four$hundred && mkdir a/bin && cp /bin/sh a/bin && printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 && @@ -62,6 +128,12 @@ test_expect_success \ git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \ git commit-tree $treeid </dev/null)' +test_expect_success 'setup export-subst' ' + echo "substfile?" export-subst >>.git/info/attributes && + git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ + >a/substfile1 +' + test_expect_success \ 'create bare clone' \ 'git clone --bare . bare.git && @@ -75,13 +147,19 @@ test_expect_success \ 'git archive' \ 'git archive HEAD >b.tar' -test_expect_success \ - 'git tar-tree' \ - 'git tar-tree HEAD >b2.tar' +check_tar b -test_expect_success \ - 'git archive vs. git tar-tree' \ - 'test_cmp b.tar b2.tar' +test_expect_success 'git archive --prefix=prefix/' ' + git archive --prefix=prefix/ HEAD >with_prefix.tar +' + +check_tar with_prefix prefix/ + +test_expect_success 'git-archive --prefix=olde-' ' + git archive --prefix=olde- HEAD >with_olde-prefix.tar +' + +check_tar with_olde-prefix olde- test_expect_success 'git archive on large files' ' test_config core.bigfilethreshold 1 && @@ -118,66 +196,14 @@ test_expect_success \ 'git get-tar-commit-id <b.tar >b.commitid && test_cmp .git/$(git symbolic-ref HEAD) b.commitid' -test_expect_success \ - 'extract tar archive' \ - '(cd b && "$TAR" xf -) <b.tar' - -test_expect_success \ - 'validate filenames' \ - '(cd b/a && find .) | sort >b.lst && - test_cmp a.lst b.lst' - -test_expect_success \ - 'validate file contents' \ - 'diff -r a b/a' - -test_expect_success \ - 'git tar-tree with prefix' \ - 'git tar-tree HEAD prefix >c.tar' - -test_expect_success \ - 'extract tar archive with prefix' \ - '(cd c && "$TAR" xf -) <c.tar' - -test_expect_success \ - 'validate filenames with prefix' \ - '(cd c/prefix/a && find .) | sort >c.lst && - test_cmp a.lst c.lst' - -test_expect_success \ - 'validate file contents with prefix' \ - 'diff -r a c/prefix/a' - -test_expect_success \ - 'create archives with substfiles' \ - 'cp .git/info/attributes .git/info/attributes.before && - echo "substfile?" export-subst >>.git/info/attributes && - git archive HEAD >f.tar && - git archive --prefix=prefix/ HEAD >g.tar && - mv .git/info/attributes.before .git/info/attributes' - -test_expect_success \ - 'extract substfiles' \ - '(mkdir f && cd f && "$TAR" xf -) <f.tar' - -test_expect_success \ - 'validate substfile contents' \ - 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ - >f/a/substfile1.expected && - test_cmp f/a/substfile1.expected f/a/substfile1 && - test_cmp a/substfile2 f/a/substfile2 +test_expect_success 'git tar-tree' ' + git tar-tree HEAD >tar-tree.tar && + test_cmp b.tar tar-tree.tar ' -test_expect_success \ - 'extract substfiles from archive with prefix' \ - '(mkdir g && cd g && "$TAR" xf -) <g.tar' - -test_expect_success \ - 'validate substfile contents from archive with prefix' \ - 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ - >g/prefix/a/substfile1.expected && - test_cmp g/prefix/a/substfile1.expected g/prefix/a/substfile1 && - test_cmp a/substfile2 g/prefix/a/substfile2 +test_expect_success 'git tar-tree with prefix' ' + git tar-tree HEAD prefix >tar-tree_with_prefix.tar && + test_cmp with_prefix.tar tar-tree_with_prefix.tar ' test_expect_success 'git archive with --output, override inferred format' ' @@ -197,18 +223,6 @@ test_expect_success 'clients cannot access unreachable commits' ' test_must_fail git archive --remote=. $sha1 >remote.tar ' -test_expect_success 'git-archive --prefix=olde-' ' - git archive --prefix=olde- >h.tar HEAD && - ( - mkdir h && - cd h && - "$TAR" xf - <../h.tar - ) && - test -d h/olde-a && - test -d h/olde-a/bin && - test -f h/olde-a/bin/sh -' - test_expect_success 'setup tar filters' ' git config tar.tar.foo.command "tr ab ba" && git config tar.bar.command "tr ab ba" && diff --git a/t/t5000/pax.tar b/t/t5000/pax.tar Binary files differnew file mode 100644 index 0000000000..d911737149 --- /dev/null +++ b/t/t5000/pax.tar diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index 4e7b05dd23..c72f71eb18 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh @@ -37,7 +37,7 @@ check_zip() { test_expect_success \ 'populate workdir' \ - 'mkdir a b c && + 'mkdir a && echo simple textfile >a/a && mkdir a/bin && cp /bin/sh a/bin && diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index 8d1bbd356a..67f3b54bed 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -27,6 +27,21 @@ check_dir() { test_cmp expect actual } + +# bsdtar/libarchive versions before 3.1.3 consider a tar file with a +# global pax header that is not followed by a file record as corrupt. +if "$TAR" tf "$TEST_DIRECTORY"/t5004/empty-with-pax-header.tar >/dev/null 2>&1 +then + test_set_prereq HEADER_ONLY_TAR_OK +fi + +test_expect_success HEADER_ONLY_TAR_OK 'tar archive of commit with empty tree' ' + git archive --format=tar HEAD >empty-with-pax-header.tar && + make_dir extract && + "$TAR" xf empty-with-pax-header.tar -C extract && + check_dir extract +' + test_expect_success 'tar archive of empty tree is empty' ' git archive --format=tar HEAD: >empty.tar && perl -e "print \"\\0\" x 10240" >10knuls.tar && diff --git a/t/t5004/empty-with-pax-header.tar b/t/t5004/empty-with-pax-header.tar Binary files differnew file mode 100644 index 0000000000..da9e39e6cf --- /dev/null +++ b/t/t5004/empty-with-pax-header.tar diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh index baa670cea5..ea2e0d4b48 100755 --- a/t/t5407-post-rewrite-hook.sh +++ b/t/t5407-post-rewrite-hook.sh @@ -31,8 +31,8 @@ clear_hook_input () { } verify_hook_input () { - test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args && - test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data + test_cmp expected.args "$TRASH_DIRECTORY"/post-rewrite.args && + test_cmp expected.data "$TRASH_DIRECTORY"/post-rewrite.data } test_expect_success 'git commit --amend' ' diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index d574085696..6133d9ed13 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -373,6 +373,20 @@ test_expect_success 'clone shallow with packed refs' ' test_cmp count8.expected count8.actual ' +test_expect_success 'fetch in shallow repo unreachable shallow objects' ' + ( + git clone --bare --branch B --single-branch "file://$(pwd)/." no-reflog && + git clone --depth 1 "file://$(pwd)/no-reflog" shallow9 && + cd no-reflog && + git tag -d TAGB1 TAGB2 && + git update-ref refs/heads/B B~~ && + git gc --prune=now && + cd ../shallow9 && + git fetch origin && + git fsck --no-dangling + ) +' + test_expect_success 'setup tests for the --stdin parameter' ' for head in C D E F do diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index d7a19a1829..fde689166a 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -370,30 +370,39 @@ test_expect_success 'bundle should record HEAD correctly' ' ' -test_expect_success 'explicit fetch should not update tracking' ' +test_expect_success 'mark initial state of origin/master' ' + ( + cd three && + git tag base-origin-master refs/remotes/origin/master + ) +' + +test_expect_success 'explicit fetch should update tracking' ' cd "$D" && git branch -f side && ( cd three && + git update-ref refs/remotes/origin/master base-origin-master && o=$(git rev-parse --verify refs/remotes/origin/master) && git fetch origin master && n=$(git rev-parse --verify refs/remotes/origin/master) && - test "$o" = "$n" && + test "$o" != "$n" && test_must_fail git rev-parse --verify refs/remotes/origin/side ) ' -test_expect_success 'explicit pull should not update tracking' ' +test_expect_success 'explicit pull should update tracking' ' cd "$D" && git branch -f side && ( cd three && + git update-ref refs/remotes/origin/master base-origin-master && o=$(git rev-parse --verify refs/remotes/origin/master) && git pull origin master && n=$(git rev-parse --verify refs/remotes/origin/master) && - test "$o" = "$n" && + test "$o" != "$n" && test_must_fail git rev-parse --verify refs/remotes/origin/side ) ' @@ -404,6 +413,7 @@ test_expect_success 'configured fetch updates tracking' ' git branch -f side && ( cd three && + git update-ref refs/remotes/origin/master base-origin-master && o=$(git rev-parse --verify refs/remotes/origin/master) && git fetch origin && n=$(git rev-parse --verify refs/remotes/origin/master) && @@ -412,6 +422,22 @@ test_expect_success 'configured fetch updates tracking' ' ) ' +test_expect_success 'non-matching refspecs do not confuse tracking update' ' + cd "$D" && + git update-ref refs/odd/location HEAD && + ( + cd three && + git update-ref refs/remotes/origin/master base-origin-master && + git config --add remote.origin.fetch \ + refs/odd/location:refs/remotes/origin/odd && + o=$(git rev-parse --verify refs/remotes/origin/master) && + git fetch origin master && + n=$(git rev-parse --verify refs/remotes/origin/master) && + test "$o" != "$n" && + test_must_fail git rev-parse --verify refs/remotes/origin/odd + ) +' + test_expect_success 'pushing nonexistent branch by mistake should not segv' ' cd "$D" && diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh index b23efbbfd9..55a866af80 100755 --- a/t/t5551-http-fetch.sh +++ b/t/t5551-http-fetch.sh @@ -209,13 +209,17 @@ test_expect_success EXPENSIVE 'create 50,000 tags in the repo' ' # now assign tags to all the dangling commits we created above tag=$("$PERL_PATH" -e "print \"bla\" x 30") && - sed -e "s/^:\(.\+\) \(.\+\)$/\2 refs\/tags\/$tag-\1/" <marks >>packed-refs + sed -e "s|^:\([^ ]*\) \(.*\)$|\2 refs/tags/$tag-\1|" <marks >>packed-refs ) ' test_expect_success EXPENSIVE 'clone the 50,000 tag repo to check OS command line overflow' ' git clone $HTTPD_URL/smart/repo.git too-many-refs 2>err && - test_line_count = 0 err + test_line_count = 0 err && + ( + cd too-many-refs && + test $(git for-each-ref refs/tags | wc -l) = 50000 + ) ' stop_httpd diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 67869b4813..0629149edd 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -280,4 +280,9 @@ test_expect_success 'clone checking out a tag' ' test_cmp fetch.expected fetch.actual ' +test_expect_success NOT_MINGW,NOT_CYGWIN 'clone local path foo:bar' ' + cp -R src "foo:bar" && + git clone "./foo:bar" foobar +' + test_done diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh index dbb02e2afd..4899af3f7a 100755 --- a/t/t5801-remote-helpers.sh +++ b/t/t5801-remote-helpers.sh @@ -8,11 +8,6 @@ test_description='Test remote-helper import and export commands' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh -if ! type "${BASH-bash}" >/dev/null 2>&1; then - skip_all='skipping remote-testgit tests, bash not available' - test_done -fi - compare_refs() { git --git-dir="$1/.git" rev-parse --verify $2 >expect && git --git-dir="$3/.git" rev-parse --verify $4 >actual && @@ -101,39 +96,28 @@ test_expect_failure 'push new branch with old:new refspec' ' test_expect_success 'cloning without refspec' ' GIT_REMOTE_TESTGIT_REFSPEC="" \ - git clone "testgit::${PWD}/server" local2 && + git clone "testgit::${PWD}/server" local2 2>error && + grep "This remote helper should implement refspec capability" error && compare_refs local2 HEAD server HEAD ' test_expect_success 'pulling without refspecs' ' (cd local2 && git reset --hard && - GIT_REMOTE_TESTGIT_REFSPEC="" git pull) && + GIT_REMOTE_TESTGIT_REFSPEC="" git pull 2>../error) && + grep "This remote helper should implement refspec capability" error && compare_refs local2 HEAD server HEAD ' -test_expect_failure 'pushing without refspecs' ' +test_expect_success 'pushing without refspecs' ' test_when_finished "(cd local2 && git reset --hard origin)" && (cd local2 && echo content >>file && git commit -a -m ten && - GIT_REMOTE_TESTGIT_REFSPEC="" git push) && - compare_refs local2 HEAD server HEAD -' - -test_expect_success 'pulling with straight refspec' ' - (cd local2 && - GIT_REMOTE_TESTGIT_REFSPEC="*:*" git pull) && - compare_refs local2 HEAD server HEAD -' - -test_expect_failure 'pushing with straight refspec' ' - test_when_finished "(cd local2 && git reset --hard origin)" && - (cd local2 && - echo content >>file && - git commit -a -m eleven && - GIT_REMOTE_TESTGIT_REFSPEC="*:*" git push) && - compare_refs local2 HEAD server HEAD + GIT_REMOTE_TESTGIT_REFSPEC="" && + export GIT_REMOTE_TESTGIT_REFSPEC && + test_must_fail git push 2>../error) && + grep "remote-helper doesn.t support push; refspec needed" error ' test_expect_success 'pulling without marks' ' @@ -186,6 +170,52 @@ test_expect_success GPG 'push signed tag with signed-tags capability' ' compare_refs local signed-tag-2 server signed-tag-2 ' +test_expect_success 'push update refs' ' + (cd local && + git checkout -b update master && + echo update >>file && + git commit -a -m update && + git push origin update && + git rev-parse --verify remotes/origin/update >expect && + git rev-parse --verify testgit/origin/heads/update >actual && + test_cmp expect actual + ) +' + +test_expect_success 'push update refs failure' ' + (cd local && + git checkout update && + echo "update fail" >>file && + git commit -a -m "update fail" && + git rev-parse --verify testgit/origin/heads/update >expect && + GIT_REMOTE_TESTGIT_PUSH_ERROR="non-fast forward" && + export GIT_REMOTE_TESTGIT_PUSH_ERROR && + test_expect_code 1 git push origin update && + git rev-parse --verify testgit/origin/heads/update >actual && + test_cmp expect actual + ) +' + +test_expect_success 'proper failure checks for fetching' ' + (GIT_REMOTE_TESTGIT_FAILURE=1 && + export GIT_REMOTE_TESTGIT_FAILURE && + cd local && + test_must_fail git fetch 2> error && + cat error && + grep -q "Error while running fast-import" error + ) +' + +test_expect_success 'proper failure checks for pushing' ' + (GIT_REMOTE_TESTGIT_FAILURE=1 && + export GIT_REMOTE_TESTGIT_FAILURE && + cd local && + test_must_fail git push --all 2> error && + cat error && + grep -q "Reading from helper .git-remote-testgit. failed" error + ) +' + test_expect_success 'push messages' ' (cd local && git checkout -b new_branch master && diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh index 39b4cb0ecd..dd5b0e55d2 100755 --- a/t/t6019-rev-list-ancestry-path.sh +++ b/t/t6019-rev-list-ancestry-path.sh @@ -13,6 +13,9 @@ test_description='--ancestry-path' # # D..M -- M.t == M # --ancestry-path D..M -- M.t == M +# +# F...I == F G H I +# --ancestry-path F...I == F H I . ./test-lib.sh @@ -63,13 +66,29 @@ test_expect_success 'rev-list D..M -- M.t' ' test_cmp expect actual ' -test_expect_success 'rev-list --ancestry-patch D..M -- M.t' ' +test_expect_success 'rev-list --ancestry-path D..M -- M.t' ' echo M >expect && git rev-list --ancestry-path --format=%s D..M -- M.t | sed -e "/^commit /d" >actual && test_cmp expect actual ' +test_expect_success 'rev-list F...I' ' + for c in F G H I; do echo $c; done >expect && + git rev-list --format=%s F...I | + sed -e "/^commit /d" | + sort >actual && + test_cmp expect actual +' + +test_expect_success 'rev-list --ancestry-path F...I' ' + for c in F H I; do echo $c; done >expect && + git rev-list --ancestry-path --format=%s F...I | + sed -e "/^commit /d" | + sort >actual && + test_cmp expect actual +' + # b---bc # / \ / # a X diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index f67aa6ff6a..a25729f2a7 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -110,6 +110,9 @@ check_describe tags/e --all HEAD^^^ check_describe B-0-* --long HEAD^^2^ check_describe A-3-* --long HEAD^^2 +check_describe c-7-* --tags +check_describe e-3-* --first-parent --tags + : >err.expect check_describe A --all A^0 test_expect_success 'no warning was displayed for A' ' diff --git a/t/t7201-co.sh b/t/t7201-co.sh index be9672e5a0..0c9ec0ad44 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -431,6 +431,7 @@ test_expect_success 'detach a symbolic link HEAD' ' test_expect_success \ 'checkout with --track fakes a sensible -b <name>' ' + git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" && git update-ref refs/remotes/origin/koala/bear renamer && git checkout --track origin/koala/bear && diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index a6bd99eaf5..d46f0411bd 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -356,6 +356,13 @@ run_dir_diff_test 'difftool --dir-diff from subdirectory' ' ) ' +run_dir_diff_test 'difftool --dir-diff when worktree file is missing' ' + test_when_finished git reset --hard && + rm file2 && + git difftool --dir-diff $symlinks --extcmd ls branch master >output && + grep file2 output +' + write_script .git/CHECK_SYMLINKS <<\EOF for f in file file2 sub/sub do diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh index 230143cf31..e7cac1db55 100755 --- a/t/t8003-blame-corner-cases.sh +++ b/t/t8003-blame-corner-cases.sh @@ -175,6 +175,12 @@ test_expect_success 'blame -L with invalid end' ' grep "has only 2 lines" errors ' +test_expect_success 'blame parses <end> part of -L' ' + git blame -L1,1 tres >out && + cat out && + test $(wc -l < out) -eq 1 +' + test_expect_success 'indent of line numbers, nine lines' ' git blame nine_lines >actual && test $(grep -c " " actual) = 0 diff --git a/t/t9114-git-svn-dcommit-merge.sh b/t/t9114-git-svn-dcommit-merge.sh index 3077851015..f524d2f383 100755 --- a/t/t9114-git-svn-dcommit-merge.sh +++ b/t/t9114-git-svn-dcommit-merge.sh @@ -48,7 +48,7 @@ test_expect_success 'setup svn repository' ' test_expect_success 'setup git mirror and merge' ' git svn init "$svnrepo" -t tags -T trunk -b branches && git svn fetch && - git checkout --track -b svn remotes/trunk && + git checkout -b svn remotes/trunk && git checkout -b merge && echo new file > new_file && git add new_file && diff --git a/t/t9167-git-svn-cmd-branch-subproject.sh b/t/t9167-git-svn-cmd-branch-subproject.sh new file mode 100755 index 0000000000..53def876ed --- /dev/null +++ b/t/t9167-git-svn-cmd-branch-subproject.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# +# Copyright (c) 2013 Tobias Schulte +# + +test_description='git svn branch for subproject clones' +. ./lib-git-svn.sh + +test_expect_success 'initialize svnrepo' ' + mkdir import && + ( + cd import && + mkdir -p trunk/project branches tags && + ( + cd trunk/project && + echo foo > foo + ) && + svn_cmd import -m "import for git-svn" . "$svnrepo" >/dev/null + ) && + rm -rf import && + svn_cmd co "$svnrepo"/trunk/project trunk/project && + ( + cd trunk/project && + echo bar >> foo && + svn_cmd ci -m "updated trunk" + ) && + rm -rf trunk +' + +test_expect_success 'import into git' ' + git svn init --trunk=trunk/project --branches=branches/*/project \ + --tags=tags/*/project "$svnrepo" && + git svn fetch && + git checkout remotes/trunk +' + +test_expect_success 'git svn branch tests' ' + test_must_fail git svn branch a && + git svn branch --parents a && + test_must_fail git svn branch -t tag1 && + git svn branch --parents -t tag1 && + test_must_fail git svn branch --tag tag2 && + git svn branch --parents --tag tag2 && + test_must_fail git svn tag tag3 && + git svn tag --parents tag3 +' + +test_done diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 6d9d1418a0..81a1657efb 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -347,4 +347,81 @@ test_expect_success 'send-email' ' test_completion "git send-email ma" "master " ' +test_expect_success 'complete files' ' + git init tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + echo "expected" > .gitignore && + echo "out" >> .gitignore && + + git add .gitignore && + test_completion "git commit " ".gitignore" && + + git commit -m ignore && + + touch new && + test_completion "git add " "new" && + + git add new && + git commit -a -m new && + test_completion "git add " "" && + + git mv new modified && + echo modify > modified && + test_completion "git add " "modified" && + + touch untracked && + + : TODO .gitignore should not be here && + test_completion "git rm " <<-\EOF && + .gitignore + modified + EOF + + test_completion "git clean " "untracked" && + + : TODO .gitignore should not be here && + test_completion "git mv " <<-\EOF && + .gitignore + modified + EOF + + mkdir dir && + touch dir/file-in-dir && + git add dir/file-in-dir && + git commit -m dir && + + mkdir untracked-dir && + + : TODO .gitignore should not be here && + test_completion "git mv modified " <<-\EOF && + .gitignore + dir + modified + untracked + untracked-dir + EOF + + test_completion "git commit " "modified" && + + : TODO .gitignore should not be here && + test_completion "git ls-files " <<-\EOF + .gitignore + dir + modified + EOF + + touch momified && + test_completion "git add mom" "momified" +' + +test_expect_failure 'complete with tilde expansion' ' + git init tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + touch ~/tmp/file && + + test_completion "git add ~/tmp/" "~/tmp/file" +' + test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index ca6bdef63d..eff3a653d1 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -54,8 +54,8 @@ done,*) # do not redirect again ;; *' --tee '*|*' --va'*) - mkdir -p test-results - BASE=test-results/$(basename "$0" .sh) + mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" + BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" (GIT_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1; echo $? > $BASE.exit) | tee $BASE.out test "$(cat $BASE.exit)" = 0 diff --git a/t/valgrind/analyze.sh b/t/valgrind/analyze.sh index d8105d9fab..2ffc80f721 100755 --- a/t/valgrind/analyze.sh +++ b/t/valgrind/analyze.sh @@ -1,6 +1,10 @@ #!/bin/sh -out_prefix=$(dirname "$0")/../test-results/valgrind.out +# Get TEST_OUTPUT_DIRECTORY from GIT-BUILD-OPTIONS if it's there... +. "$(dirname "$0")/../../GIT-BUILD-OPTIONS" +# ... otherwise set it to the default value. +: ${TEST_OUTPUT_DIRECTORY=$(dirname "$0")/..} + output= count=0 total_count=0 @@ -115,7 +119,7 @@ handle_one () { finish_output } -for test_script in "$(dirname "$0")"/../test-results/*.out +for test_script in "$TEST_OUTPUT_DIRECTORY"/test-results/*.out do handle_one $test_script done diff --git a/test-chmtime.c b/test-chmtime.c index 02b42badd5..94903c4bff 100644 --- a/test-chmtime.c +++ b/test-chmtime.c @@ -56,7 +56,7 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq) return 1; } -int main(int argc, const char *argv[]) +int main(int argc, char *argv[]) { static int verbose; diff --git a/test-index-version.c b/test-index-version.c index bfaad9e09e..05d4699c4a 100644 --- a/test-index-version.c +++ b/test-index-version.c @@ -1,6 +1,6 @@ #include "cache.h" -int main(int argc, const char **argv) +int main(int argc, char **argv) { struct cache_header hdr; int version; diff --git a/test-mergesort.c b/test-mergesort.c index 3f388b4ce0..ea3b959e94 100644 --- a/test-mergesort.c +++ b/test-mergesort.c @@ -22,7 +22,7 @@ static int compare_strings(const void *a, const void *b) return strcmp(x->text, y->text); } -int main(int argc, const char **argv) +int main(int argc, char **argv) { struct line *line, *p = NULL, *lines = NULL; struct strbuf sb = STRBUF_INIT; diff --git a/test-parse-options.c b/test-parse-options.c index 3c9510a701..434e8b8929 100644 --- a/test-parse-options.c +++ b/test-parse-options.c @@ -29,7 +29,7 @@ static int number_callback(const struct option *opt, const char *arg, int unset) return 0; } -int main(int argc, const char **argv) +int main(int argc, char **argv) { const char *prefix = "prefix/"; const char *usage[] = { @@ -81,7 +81,7 @@ int main(int argc, const char **argv) }; int i; - argc = parse_options(argc, argv, prefix, options, usage, 0); + argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0); printf("boolean: %d\n", boolean); printf("integer: %u\n", integer); diff --git a/test-subprocess.c b/test-subprocess.c index f2d4c0d22b..93525eb7be 100644 --- a/test-subprocess.c +++ b/test-subprocess.c @@ -1,7 +1,7 @@ #include "cache.h" #include "run-command.h" -int main(int argc, const char **argv) +int main(int argc, char **argv) { struct child_process cp; int nogit = 0; @@ -15,6 +15,6 @@ int main(int argc, const char **argv) } memset(&cp, 0, sizeof(cp)); cp.git_cmd = 1; - cp.argv = argv + 1; + cp.argv = (const char **)argv + 1; return run_command(&cp); } diff --git a/transport-helper.c b/transport-helper.c index 522d79178e..2f5ac3fbee 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -11,6 +11,7 @@ #include "thread-utils.h" #include "sigchain.h" #include "argv-array.h" +#include "refs.h" static int debug; @@ -47,7 +48,7 @@ static void sendline(struct helper_data *helper, struct strbuf *buffer) die_errno("Full write to remote helper failed"); } -static int recvline_fh(FILE *helper, struct strbuf *buffer) +static int recvline_fh(FILE *helper, struct strbuf *buffer, const char *name) { strbuf_reset(buffer); if (debug) @@ -55,7 +56,7 @@ static int recvline_fh(FILE *helper, struct strbuf *buffer) if (strbuf_getline(buffer, helper, '\n') == EOF) { if (debug) fprintf(stderr, "Debug: Remote helper quit.\n"); - exit(128); + die("Reading from helper 'git-remote-%s' failed", name); } if (debug) @@ -65,7 +66,7 @@ static int recvline_fh(FILE *helper, struct strbuf *buffer) static int recvline(struct helper_data *helper, struct strbuf *buffer) { - return recvline_fh(helper->out, buffer); + return recvline_fh(helper->out, buffer, helper->name); } static void xchgline(struct helper_data *helper, struct strbuf *buffer) @@ -217,6 +218,8 @@ static struct child_process *get_helper(struct transport *transport) for (i = 0; i < refspec_nr; i++) free((char *)refspecs[i]); free(refspecs); + } else if (data->import || data->bidi_import || data->export) { + warning("This remote helper should implement refspec capability."); } strbuf_release(&buf); if (debug) @@ -473,7 +476,7 @@ static int fetch_with_import(struct transport *transport, * were fetching. * * (If no "refspec" capability was specified, for historical - * reasons we default to *:*.) + * reasons we default to the equivalent of *:*.) * * Store the result in to_fetch[i].old_sha1. Callers such * as "git fetch" can use the value to write feedback to the @@ -540,7 +543,7 @@ static int process_connect_service(struct transport *transport, goto exit; sendline(data, &cmdbuf); - recvline_fh(input, &cmdbuf); + recvline_fh(input, &cmdbuf, name); if (!strcmp(cmdbuf.buf, "")) { data->no_disconnect_req = 1; if (debug) @@ -622,7 +625,7 @@ static int fetch(struct transport *transport, return -1; } -static void push_update_ref_status(struct strbuf *buf, +static int push_update_ref_status(struct strbuf *buf, struct ref **ref, struct ref *remote_refs) { @@ -688,7 +691,7 @@ static void push_update_ref_status(struct strbuf *buf, *ref = find_ref_by_name(remote_refs, refname); if (!*ref) { warning("helper reported unexpected status of %s", refname); - return; + return 1; } if ((*ref)->status != REF_STATUS_NONE) { @@ -697,11 +700,12 @@ static void push_update_ref_status(struct strbuf *buf, * status reported by the remote helper if the latter is 'no match'. */ if (status == REF_STATUS_NONE) - return; + return 1; } (*ref)->status = status; (*ref)->remote_status = msg; + return !(status == REF_STATUS_OK); } static void push_update_refs_status(struct helper_data *data, @@ -710,11 +714,24 @@ static void push_update_refs_status(struct helper_data *data, struct strbuf buf = STRBUF_INIT; struct ref *ref = remote_refs; for (;;) { + char *private; + recvline(data, &buf); if (!buf.len) break; - push_update_ref_status(&buf, &ref, remote_refs); + if (push_update_ref_status(&buf, &ref, remote_refs)) + continue; + + if (!data->refspecs) + continue; + + /* propagate back the update to the remote namespace */ + private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name); + if (!private) + continue; + update_ref("update by helper", private, ref->new_sha1, NULL, 0, 0); + free(private); } strbuf_release(&buf); } @@ -789,6 +806,9 @@ static int push_refs_with_export(struct transport *transport, struct string_list revlist_args = STRING_LIST_INIT_NODUP; struct strbuf buf = STRBUF_INIT; + if (!data->refspecs) + die("remote-helper doesn't support push; refspec needed"); + helper = get_helper(transport); write_constant(helper->in, "export\n"); @@ -799,8 +819,9 @@ static int push_refs_with_export(struct transport *transport, char *private; unsigned char sha1[20]; - if (!data->refspecs) - continue; + if (ref->deletion) + die("remote-helpers do not support ref deletion"); + private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name); if (private && !get_sha1(private, sha1)) { strbuf_addf(&buf, "^%s", private); @@ -809,13 +830,8 @@ static int push_refs_with_export(struct transport *transport, } free(private); - if (ref->deletion) { - die("remote-helpers do not support ref deletion"); - } - if (ref->peer_ref) string_list_append(&revlist_args, ref->peer_ref->name); - } if (get_exporter(transport, &exporter, &revlist_args)) diff --git a/upload-pack.c b/upload-pack.c index bfa6279cc4..127e59a603 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -592,7 +592,7 @@ static void receive_needs(void) die("invalid shallow line: %s", line); object = parse_object(sha1); if (!object) - die("did not find object for %s", line); + continue; if (object->type != OBJ_COMMIT) die("invalid shallow object %s", sha1_to_hex(sha1)); if (!(object->flags & CLIENT_SHALLOW)) { @@ -408,18 +408,24 @@ void warn_on_inaccessible(const char *path) warning(_("unable to access '%s': %s"), path, strerror(errno)); } -int access_or_warn(const char *path, int mode) +static int access_error_is_ok(int err, unsigned flag) +{ + return err == ENOENT || err == ENOTDIR || + ((flag & ACCESS_EACCES_OK) && err == EACCES); +} + +int access_or_warn(const char *path, int mode, unsigned flag) { int ret = access(path, mode); - if (ret && errno != ENOENT && errno != ENOTDIR) + if (ret && !access_error_is_ok(errno, flag)) warn_on_inaccessible(path); return ret; } -int access_or_die(const char *path, int mode) +int access_or_die(const char *path, int mode, unsigned flag) { int ret = access(path, mode); - if (ret && errno != ENOENT && errno != ENOTDIR) + if (ret && !access_error_is_ok(errno, flag)) die_errno(_("unable to access '%s'"), path); return ret; } |