diff options
426 files changed, 15642 insertions, 3791 deletions
@@ -112,6 +112,8 @@ /git-remote-https /git-remote-ftp /git-remote-ftps +/git-remote-fd +/git-remote-ext /git-remote-testgit /git-repack /git-replace @@ -36,7 +36,7 @@ Lars Doelle <lars.doelle@on-line ! de> Lars Doelle <lars.doelle@on-line.de> Li Hong <leehong@pku.edu.cn> Lukas Sandström <lukass@etek.chalmers.se> -Martin Langhoff <martin@catalyst.net.nz> +Martin Langhoff <martin@laptop.org> Michael Coleman <tutufan@gmail.com> Michael J Gruber <git@drmicha.warpmail.net> <michaeljgruber+gmane@fastmail.fm> Michael W. Olson <mwolson@gnu.org> diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 46f8a3f..1b1c45d 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -143,3 +143,55 @@ For C programs: - When we pass <string, length> pair to functions, we should try to pass them in that order. + +Writing Documentation: + + Every user-visible change should be reflected in the documentation. + The same general rule as for code applies -- imitate the existing + conventions. A few commented examples follow to provide reference + when writing or modifying command usage strings and synopsis sections + in the manual pages: + + Placeholders are enclosed in angle brackets: + <file> + --sort=<key> + --abbrev[=<n>] + + Possibility of multiple occurences is indicated by three dots: + <file>... + (One or more of <file>.) + + Optional parts are enclosed in square brackets: + [<extra>] + (Zero or one <extra>.) + + --exec-path[=<path>] + (Option with an optional argument. Note that the "=" is inside the + brackets.) + + [<patch>...] + (Zero or more of <patch>. Note that the dots are inside, not + outside the brackets.) + + Multiple alternatives are indicated with vertical bar: + [-q | --quiet] + [--utf8 | --no-utf8] + + Parentheses are used for grouping: + [(<rev>|<range>)...] + (Any number of either <rev> or <range>. Parens are needed to make + it clear that "..." pertains to both <rev> and <range>.) + + [(-p <parent>)...] + (Any number of option -p, each with one <parent> argument.) + + git remote set-head <name> (-a | -d | <branch>) + (One and only one of "-a", "-d" or "<branch>" _must_ (no square + brackets) be provided.) + + And a somewhat more contrived example: + --diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]] + Here "=" is outside the brackets, because "--diff-filter=" is a + valid usage. "*" has its own pair of brackets, because it can + (optionally) be specified only when one or more of the letters is + also provided. diff --git a/Documentation/Makefile b/Documentation/Makefile index e117bc4..36989b7 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -63,35 +63,28 @@ endif # # For asciidoc ... -# -7.1.2, no extra settings are needed. -# 8.0-, set ASCIIDOC8. +# -7.1.2, set ASCIIDOC7 +# 8.0-, no extra settings are needed # # # For docbook-xsl ... -# -1.68.1, set ASCIIDOC_NO_ROFF? (based on changelog from 1.73.0) -# 1.69.0, no extra settings are needed? +# -1.68.1, no extra settings are needed? +# 1.69.0, set ASCIIDOC_ROFF? # 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP? -# 1.71.1, no extra settings are needed? +# 1.71.1, set ASCIIDOC_ROFF? # 1.72.0, set DOCBOOK_XSL_172. -# 1.73.0-, set ASCIIDOC_NO_ROFF +# 1.73.0-, no extra settings are needed # -# -# If you had been using DOCBOOK_XSL_172 in an attempt to get rid -# of 'the ".ft C" problem' in your generated manpages, and you -# instead ended up with weird characters around callouts, try -# using ASCIIDOC_NO_ROFF instead (it works fine with ASCIIDOC8). -# - -ifdef ASCIIDOC8 +ifndef ASCIIDOC7 ASCIIDOC_EXTRA += -a asciidoc7compatible -a no-inline-literal endif ifdef DOCBOOK_XSL_172 ASCIIDOC_EXTRA += -a git-asciidoc-no-roff MANPAGE_XSL = manpage-1.72.xsl else - ifdef ASCIIDOC_NO_ROFF + ifndef ASCIIDOC_ROFF # docbook-xsl after 1.72 needs the regular XSL, but will not # pass-thru raw roff codes from asciidoc.conf, so turn them off. ASCIIDOC_EXTRA += -a git-asciidoc-no-roff diff --git a/Documentation/RelNotes/1.6.4.5.txt b/Documentation/RelNotes/1.6.4.5.txt new file mode 100644 index 0000000..eb6307d --- /dev/null +++ b/Documentation/RelNotes/1.6.4.5.txt @@ -0,0 +1,20 @@ +Git v1.6.4.5 Release Notes +========================== + +Fixes since v1.6.4.4 +-------------------- + + * Simplified base85 implementation. + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git count-objects" did not handle packs larger than 4G. + + * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option + when --keep-dashdash was in effect. + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. + +Other minor fixes and documentation updates are included. diff --git a/Documentation/RelNotes/1.6.5.9.txt b/Documentation/RelNotes/1.6.5.9.txt new file mode 100644 index 0000000..bb469dd --- /dev/null +++ b/Documentation/RelNotes/1.6.5.9.txt @@ -0,0 +1,18 @@ +Git v1.6.5.9 Release Notes +========================== + +Fixes since v1.6.5.8 +-------------------- + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git blame -L $start,$end" segfaulted when too large $start was given. + + * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option + when --keep-dashdash was in effect. + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. + +Other minor fixes and documentation updates are included. diff --git a/Documentation/RelNotes/1.6.6.3.txt b/Documentation/RelNotes/1.6.6.3.txt new file mode 100644 index 0000000..11483ac --- /dev/null +++ b/Documentation/RelNotes/1.6.6.3.txt @@ -0,0 +1,23 @@ +Git v1.6.6.3 Release Notes +========================== + +Fixes since v1.6.6.2 +-------------------- + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git bisect $path" did not correctly diagnose an error when given a + non-existent path. + + * "git blame -L $start,$end" segfaulted when too large $start was given. + + * "git imap-send" did not write draft box with CRLF line endings per RFC. + + * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option + when --keep-dashdash was in effect. + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. + +Other minor fixes and documentation updates are included. diff --git a/Documentation/RelNotes/1.7.0.9.txt b/Documentation/RelNotes/1.7.0.9.txt new file mode 100644 index 0000000..bfb3166 --- /dev/null +++ b/Documentation/RelNotes/1.7.0.9.txt @@ -0,0 +1,8 @@ +Git v1.7.0.9 Release Notes +========================== + +Fixes since v1.7.0.8 +-------------------- + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. diff --git a/Documentation/RelNotes/1.7.1.4.txt b/Documentation/RelNotes/1.7.1.4.txt new file mode 100644 index 0000000..7c734b4 --- /dev/null +++ b/Documentation/RelNotes/1.7.1.4.txt @@ -0,0 +1,8 @@ +Git v1.7.1.4 Release Notes +========================== + +Fixes since v1.7.1.3 +-------------------- + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. diff --git a/Documentation/RelNotes/1.7.2.5.txt b/Documentation/RelNotes/1.7.2.5.txt new file mode 100644 index 0000000..bf976c4 --- /dev/null +++ b/Documentation/RelNotes/1.7.2.5.txt @@ -0,0 +1,8 @@ +Git v1.7.2.5 Release Notes +========================== + +Fixes since v1.7.2.4 +-------------------- + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. diff --git a/Documentation/RelNotes/1.7.3.4.txt b/Documentation/RelNotes/1.7.3.4.txt new file mode 100644 index 0000000..e57f7c1 --- /dev/null +++ b/Documentation/RelNotes/1.7.3.4.txt @@ -0,0 +1,45 @@ +Git v1.7.3.4 Release Notes +========================== + +Fixes since v1.7.3.3 +-------------------- + + * Smart HTTP transport used to incorrectly retry redirected POST + request with GET request. + + * "git apply" did not correctly handle patches that only change modes + if told to apply while stripping leading paths with -p option. + + * "git apply" can deal with patches with timezone formatted with a + colon between the hours and minutes part (e.g. "-08:00" instead of + "-0800"). + + * "git checkout" removed an untracked file "foo" from the working + tree when switching to a branch that contains a tracked path + "foo/bar". Prevent this, just like the case where the conflicting + path were "foo" (c752e7f..7980872d). + + * "git cherry-pick" or "git revert" refused to work when a path that + would be modified by the operation was stat-dirty without a real + difference in the contents of the file. + + * "git diff --check" reported an incorrect line number for added + blank lines at the end of file. + + * "git imap-send" failed to build under NO_OPENSSL. + + * Setting log.decorate configuration variable to "0" or "1" to mean + "false" or "true" did not work. + + * "git push" over dumb HTTP protocol did not work against WebDAV + servers that did not terminate a collection name with a slash. + + * "git tag -v" did not work with GPG signatures in rfc1991 mode. + + * The post-receive-email sample hook was accidentally broken in 1.7.3.3 + update. + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. + +Other minor fixes and documentation updates are also included. diff --git a/Documentation/RelNotes/1.7.4.txt b/Documentation/RelNotes/1.7.4.txt new file mode 100644 index 0000000..c1ed23a --- /dev/null +++ b/Documentation/RelNotes/1.7.4.txt @@ -0,0 +1,118 @@ +Git v1.7.4 Release Notes (draft) +================================ + +Updates since v1.7.3 +-------------------- + + * The documentation Makefile now assumes by default asciidoc 8 and + docbook-xsl >= 1.73. If you have older versions, you can set + ASCIIDOC7 and ASCIIDOC_ROFF, respectively. + + * The option parsers of various commands that create new branch (or + rename existing ones to a new name) were too loose and users were + allowed to call a branch with a name that begins with a dash by + creative abuse of their command line options, which only lead to + burn themselves. The name of a branch cannot begin with a dash + now. + + * System-wide fallback default attributes can be stored in + /etc/gitattributes; core.attributesfile configuration variable can + be used to customize the path to this file. + + * The thread structure generated by "git send-email" has changed + slightly. Setting the cover letter of the latest series as a reply + to the cover letter of the previous series with --in-reply-to used + to make the new cover letter and all the patches replies to the + cover letter of the previous series; this has been changed to make + the patches in the new series replies to the new cover letter. + + * Bash completion script in contrib/ has been adjusted to be also + usable by zsh. + + * "git blame" learned --show-email option to display the e-mail + addresses instead of the names of authors. + + * "git daemon" can be built in MinGW environment. + + * "git daemon" can take more than one --listen option to listen to + multiple addresses. + + * "git diff" and "git grep" learned how functions and subroutines + in Fortran look like. + + * "git mergetool" tells vim/gvim to show three-way diff by default + (use vimdiff2/gvimdiff2 as the tool name for old behaviour). + + * "git log -G<pattern>" limits the output to commits whose change has + added or deleted lines that match the given pattern. + + * "git read-tree" with no argument as a way to empty the index is + deprecated; we might want to remove it in the future. Users can + use the new --empty option to be more explicit instead. + + * "git repack -f" does not spend cycles to recompress objects in the + non-delta representation anymore (use -F if you really mean it when + e.g. you changed the compression level). + + * "git merge --log" used to limit the resulting merge log to 20 + entries; this is now customizable by giving e.g. "--log=47". + + * "git merge" may work better when all files were moved out of a + directory in one branch while a new file is created in place of that + directory in the other branch. + + * "git rebase --autosquash" can use SHA-1 object names to name which + commit to fix up (e.g. "fixup! e83c5163"). + + * The default "recursive" merge strategy learned --rename-threshold + option to influence the rename detection, similar to the -M option + of "git diff". E.g. "git merge -Xrename-threshold=50% ..." to use + this. + + * The "recursive" strategy also learned to ignore various whitespace + changes; the most notable is -Xignore-space-at-eol. + + * "git send-email" learned "--to-cmd", similar to "--cc-cmd", to read + recipient list from a command output. + + * "git send-email" learned to read and use "To:" from its input files. + + * you can extend "git shell", which is often used on boxes that allow + git-only login over ssh as login shell, with custom set of + commands. + + * "git submodule sync" updates metainformation for all submodules, + not just the ones that have been checked out. + + * gitweb can use custom 'highlight' command with its configuration file. + + +Also contains various documentation updates. + + +Fixes since v1.7.3 +------------------ + +All of the fixes in v1.7.3.X maintenance series are included in this +release, unless otherwise noted. + + * "git log --author=me --author=her" did not find commits written by + me or by her; instead it looked for commits written by me and by + her, which is impossible. + + * "git merge" into an unborn branch removed an untracked file "foo" + from the working tree when merged branch had "foo" (2caf20c..172b642). + + * "git push --progress" shows progress indicators now. + + * "git repack" places its temporary packs under $GIT_OBJECT_DIRECTORY/pack + instead of $GIT_OBJECT_DIRECTORY/ to avoid cross directory renames. + + * "git submodule update --recursive --other-flags" passes flags down + to its subinvocations. + +--- +exec >/var/tmp/1 +O=v1.7.3.2-450-g5b9c331 +echo O=$(git describe master) +git shortlog --no-merges ^maint ^$O master diff --git a/Documentation/config.txt b/Documentation/config.txt index 3fd4b62..3f01bd9 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -374,6 +374,15 @@ core.warnAmbiguousRefs:: If true, git will warn you if the ref name you passed it is ambiguous and might match multiple refs in the .git/refs/ tree. True by default. +core.abbrevguard:: + Even though git makes sure that it uses enough hexdigits to show + an abbreviated object name unambiguously, as more objects are + added to the repository over time, a short name that used to be + unique will stop being unique. Git uses this many extra hexdigits + that are more than necessary to make the object name currently + unique, in the hope that its output will stay unique a bit longer. + Defaults to 0. + core.compression:: An integer -1..9, indicating a default compression level. -1 is the zlib default. 0 means no compression, @@ -459,6 +468,12 @@ core.askpass:: prompt. The external program shall be given a suitable prompt as command line argument and write the password on its STDOUT. +core.attributesfile:: + In addition to '.gitattributes' (per-directory) and + '.git/info/attributes', git looks into this file for attributes + (see linkgit:gitattributes[5]). Path expansions are made the same + way as for `core.excludesfile`. + core.editor:: Commands such as `commit` and `tag` that lets you edit messages by launching an editor uses the value of this @@ -507,6 +522,9 @@ core.whitespace:: part of the line terminator, i.e. with it, `trailing-space` does not trigger if the character before such a carriage-return is not a whitespace (not enabled by default). +* `tabwidth=<n>` tells how many character positions a tab occupies; this + is relevant for `indent-with-non-tab` and when git fixes `tab-in-indent` + errors. The default tab width is 8. Allowed values are 1 to 63. core.fsyncobjectfiles:: This boolean will enable 'fsync()' when writing object files. @@ -599,8 +617,9 @@ branch.autosetupmerge:: this behavior can be chosen per-branch using the `--track` and `--no-track` options. The valid settings are: `false` -- no automatic setup is done; `true` -- automatic setup is done when the - starting point is a remote branch; `always` -- automatic setup is - done when the starting point is either a local branch or remote + starting point is a remote-tracking branch; `always` -- + automatic setup is done when the starting point is either a + local branch or remote-tracking branch. This option defaults to true. branch.autosetuprebase:: @@ -611,7 +630,7 @@ branch.autosetuprebase:: When `local`, rebase is set to true for tracked branches of other local branches. When `remote`, rebase is set to true for tracked branches of - remote branches. + remote-tracking branches. When `always`, rebase will be set to true for all tracking branches. See "branch.autosetupmerge" for details on how to set up a @@ -678,7 +697,7 @@ color.branch:: color.branch.<slot>:: Use customized color for branch coloration. `<slot>` is one of `current` (the current branch), `local` (a local branch), - `remote` (a tracking branch in refs/remotes/), `plain` (other + `remote` (a remote-tracking branch in refs/remotes/), `plain` (other refs). + The value for these configuration variables is a list of colors (at most @@ -706,7 +725,7 @@ color.diff.<slot>:: color.decorate.<slot>:: Use customized color for 'git log --decorate' output. `<slot>` is one of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local - branches, remote tracking branches, tags, stash and HEAD, respectively. + branches, remote-tracking branches, tags, stash and HEAD, respectively. color.grep:: When set to `always`, always highlight matches. When `false` (or @@ -1100,7 +1119,7 @@ gui.newbranchtemplate:: linkgit:git-gui[1]. gui.pruneduringfetch:: - "true" if linkgit:git-gui[1] should prune tracking branches when + "true" if linkgit:git-gui[1] should prune remote-tracking branches when performing a fetch. The default value is "false". gui.trustmtime:: @@ -1529,11 +1548,13 @@ pack.packSizeLimit:: supported. pager.<cmd>:: - Allows turning on or off pagination of the output of a - particular git subcommand when writing to a tty. If - `\--paginate` or `\--no-pager` is specified on the command line, - it takes precedence over this option. To disable pagination for - all commands, set `core.pager` or `GIT_PAGER` to `cat`. + If the value is boolean, turns on or off pagination of the + output of a particular git subcommand when writing to a tty. + Otherwise, turns on pagination for the subcommand using the + pager specified by the value of `pager.<cmd>`. If `\--paginate` + or `\--no-pager` is specified on the command line, it takes + precedence over this option. To disable pagination for all + commands, set `core.pager` or `GIT_PAGER` to `cat`. pretty.<name>:: Alias for a --pretty= format string, as specified in @@ -1735,6 +1756,7 @@ sendemail.to:: sendemail.smtpdomain:: sendemail.smtpserver:: sendemail.smtpserverport:: +sendemail.smtpserveroption:: sendemail.smtpuser:: sendemail.thread:: sendemail.validate:: diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 5495344..f3e9538 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -207,6 +207,7 @@ endif::git-format-patch[] digits can be specified with `--abbrev=<n>`. -B[<n>][/<m>]:: +--break-rewrites[=[<n>][/<m>]]:: Break complete rewrite changes into pairs of delete and create. This serves two purposes: + @@ -229,6 +230,7 @@ eligible for being picked up as a possible source of a rename to another file. -M[<n>]:: +--detect-renames[=<n>]:: ifndef::git-log[] Detect renames. endif::git-log[] @@ -244,6 +246,7 @@ endif::git-log[] hasn't changed. -C[<n>]:: +--detect-copies[=<n>]:: Detect copies as well as renames. See also `--find-copies-harder`. If `n` is specified, it has the same meaning as for `-M<n>`. @@ -282,8 +285,12 @@ ifndef::git-format-patch[] appearing in diff output; see the 'pickaxe' entry in linkgit:gitdiffcore[7] for more details. +-G<regex>:: + Look for differences whose added or removed line matches + the given <regex>. + --pickaxe-all:: - When `-S` finds a change, show all the changes in that + When `-S` or `-G` finds a change, show all the changes in that changeset, not just the files that contain the change in <string>. diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index e0ba8cc..ae413e5 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -180,12 +180,12 @@ directory; clone from it to start a repository on the satellite machine. <2> clone sets these configuration variables by default. It arranges `git pull` to fetch and store the branches of mothership -machine to local `remotes/origin/*` tracking branches. +machine to local `remotes/origin/*` remote-tracking branches. <3> arrange `git push` to push local `master` branch to `remotes/satellite/master` branch of the mothership machine. <4> push will stash our work away on `remotes/satellite/master` -tracking branch on the mothership machine. You could use this as -a back-up method. +remote-tracking branch on the mothership machine. You could use this +as a back-up method. <5> on mothership machine, merge the work done on the satellite machine into the master branch. diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 5ce1e72..678675c 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -36,7 +36,7 @@ ifndef::git-pull[] -p:: --prune:: - After fetching, remove any remote tracking branches which + After fetching, remove any remote-tracking branches which no longer exist on the remote. endif::git-pull[] diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt index 4f358c8..2411ce5 100644 --- a/Documentation/git-archimport.txt +++ b/Documentation/git-archimport.txt @@ -109,7 +109,7 @@ OPTIONS Author ------ -Written by Martin Langhoff <martin@catalyst.net.nz>. +Written by Martin Langhoff <martin@laptop.org>. Documentation -------------- diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index a27f439..c71671b 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -8,7 +8,7 @@ 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] [-p] [-w] [--incremental] [-L n,m] +'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>] [<rev> | --contents <file> | --reverse <rev>] [--] <file> @@ -65,6 +65,10 @@ include::blame-options.txt[] -s:: Suppress the author name and timestamp from the output. +-e:: +--show-email:: + Show the author email instead of author name (Default: off). + -w:: Ignore whitespace when comparing the parent's version and the child's to find where the lines came from. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 1940256..9106d38 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -37,11 +37,12 @@ Note that this will create the new branch, but it will not switch the working tree to it; use "git checkout <newbranch>" to switch to the new branch. -When a local branch is started off a remote branch, git sets up the +When a local branch is started off a remote-tracking branch, git sets up the branch so that 'git pull' will appropriately merge from -the remote branch. This behavior may be changed via the global +the remote-tracking branch. This behavior may be changed via the global `branch.autosetupmerge` configuration flag. That setting can be -overridden by using the `--track` and `--no-track` options. +overridden by using the `--track` and `--no-track` options, and +changed later using `git branch --set-upstream`. With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>. If <oldbranch> had a corresponding reflog, it is renamed to match @@ -89,7 +90,8 @@ OPTIONS Move/rename a branch even if the new branch name already exists. --color[=<when>]:: - Color branches to highlight current, local, and remote branches. + Color branches to highlight current, local, and + remote-tracking branches. The value must be always (the default), never, or auto. --no-color:: @@ -125,11 +127,11 @@ OPTIONS it directs `git pull` without arguments to pull from the upstream when the new branch is checked out. + -This behavior is the default when the start point is a remote branch. +This behavior is the default when the start point is a remote-tracking branch. Set the branch.autosetupmerge configuration variable to `false` if you want `git checkout` and `git branch` to always behave as if '--no-track' were given. Set it to `always` if you want this behavior when the -start-point is either a local or remote branch. +start-point is either a local or remote-tracking branch. --no-track:: Do not set up "upstream" configuration, even if the diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 22d3611..880763d 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -98,7 +98,7 @@ 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 branch. If "remotes/" or "refs/remotes/" +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. This would tell us to use "hack" as the local branch when branching diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index ab72933..42e7021 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -12,7 +12,8 @@ SYNOPSIS 'git clone' [--template=<template_directory>] [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror] [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>] - [--depth <depth>] [--recursive] [--] <repository> [<directory>] + [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository> + [<directory>] DESCRIPTION ----------- @@ -131,7 +132,7 @@ objects from the source repository into a pack in the cloned repository. Set up a mirror of the source repository. This implies `--bare`. Compared to `--bare`, `--mirror` not only maps local branches of the source to local branches of the target, it maps all refs (including - remote branches, notes etc.) and sets up a refspec configuration such + remote-tracking branches, notes etc.) and sets up a refspec configuration such that all these refs are overwritten by a `git remote update` in the target repository. @@ -167,6 +168,7 @@ objects from the source repository into a pack in the cloned repository. as patches. --recursive:: +--recurse-submodules:: After the clone is created, initialize all submodules within, using their default settings. This is equivalent to running `git submodule update --init --recursive` immediately after diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index ec7b577..b586c0f 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -9,10 +9,10 @@ SYNOPSIS -------- [verse] 'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run] - [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author] - [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>] - [--date=<date>] [--cleanup=<mode>] [--status | --no-status] - [-i | -o] [--] [<file>...] + [(-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] [-i | -o] [--] [<file>...] DESCRIPTION ----------- @@ -70,6 +70,19 @@ OPTIONS Like '-C', but with '-c' the editor is invoked, so that the user can further edit the commit message. +--fixup=<commit>:: + Construct a commit message for use with `rebase --autosquash`. + The commit message will be the subject line from the specified + commit with a prefix of "fixup! ". See linkgit:git-rebase[1] + for details. + +--squash=<commit>:: + Construct a commit message for use with `rebase --autosquash`. + The commit message subject line is taken from the specified + commit with a prefix of "squash! ". Can be used with additional + commit message options (`-m`/`-c`/`-C`/`-F`). See + linkgit:git-rebase[1] for details. + --reset-author:: When used with -C/-c/--amend options, declare that the authorship of the resulting commit now belongs of the committer. diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index b2696ef..d25661e 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -114,11 +114,11 @@ $ git cherry cvshead myhead | sed -n 's/^+ //p' | xargs -l1 git cvsexportcommit Author ------ -Written by Martin Langhoff <martin@catalyst.net.nz> and others. +Written by Martin Langhoff <martin@laptop.org> and others. Documentation -------------- -Documentation by Martin Langhoff <martin@catalyst.net.nz> and others. +Documentation by Martin Langhoff <martin@laptop.org> and others. GIT --- diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index f4472c6..70cbb2c 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -399,13 +399,13 @@ This program is copyright The Open University UK - 2006. Authors: - Martyn Smith <martyn@catalyst.net.nz> -- Martin Langhoff <martin@catalyst.net.nz> +- Martin Langhoff <martin@laptop.org> with ideas and patches from participants of the git-list <git@vger.kernel.org>. Documentation -------------- -Documentation by Martyn Smith <martyn@catalyst.net.nz>, Martin Langhoff <martin@catalyst.net.nz>, and Matthias Urlichs <smurf@smurf.noris.de>. +Documentation by Martyn Smith <martyn@catalyst.net.nz>, Martin Langhoff <martin@laptop.org>, and Matthias Urlichs <smurf@smurf.noris.de>. GIT --- diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 2f0ddf6..d15cb6a 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -78,13 +78,15 @@ OPTIONS --inetd:: Have the server run as an inetd service. Implies --syslog. - Incompatible with --port, --listen, --user and --group options. + Incompatible with --detach, --port, --listen, --user and --group + options. --listen=<host_or_ipaddr>:: Listen on a specific IP address or hostname. IP addresses can be either an IPv4 address or an IPv6 address if supported. If IPv6 is not supported, then --listen=hostname is also not supported and --listen must be given an IPv4 address. + Can be given more than once. Incompatible with '--inetd' option. --port=<n>:: diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 7ef9d51..02e015a 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -37,7 +37,7 @@ OPTIONS --all:: Instead of using only the annotated tags, use any ref found in `.git/refs/`. This option enables matching - any known branch, remote branch, or lightweight tag. + any known branch, remote-tracking branch, or lightweight tag. --tags:: Instead of using only the annotated tags, use any tag diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index dd1fb32..f6ac847 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -8,12 +8,17 @@ git-diff - Show changes between commits, commit and working tree, etc SYNOPSIS -------- -'git diff' [<common diff options>] <commit>{0,2} [--] [<path>...] +[verse] +'git diff' [options] [<commit>] [--] [<path>...] +'git diff' [options] --cached [<commit>] [--] [<path>...] +'git diff' [options] <commit> <commit> [--] [<path>...] +'git diff' [options] [--no-index] [--] <path> <path> DESCRIPTION ----------- -Show changes between two trees, a tree and the working tree, a -tree and the index file, or the index file and the working tree. +Show changes between the working tree and the index or a tree, changes +between the index and a tree, changes between two trees, or changes +between two files on disk. 'git diff' [--options] [--] [<path>...]:: diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 8250bad..6fffbc7 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -7,13 +7,14 @@ git-difftool - Show changes using common diff tools SYNOPSIS -------- -'git difftool' [<options>] <commit>{0,2} [--] [<path>...] +'git difftool' [<options>] [<commit> [<commit>]] [--] [<path>...] DESCRIPTION ----------- 'git difftool' is a git command that allows you to compare and edit files between revisions using common diff tools. 'git difftool' is a frontend -to 'git diff' and accepts the same options and arguments. +to 'git diff' and accepts the same options and arguments. See +linkgit:git-diff[1]. OPTIONS ------- diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 2c6ad5b..9667e9a 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -524,6 +524,9 @@ start with double quote (`"`). If an `LF` or double quote must be encoded into `<path>` shell-style quoting should be used, e.g. `"path/with\n and \" in it"`. +Additionally, in `040000` mode, `<path>` may also be an empty string +(`""`) to specify the root of the tree. + The value of `<path>` must be in canonical form. That is it must not: * contain an empty directory component (e.g. `foo//bar` is invalid), @@ -1230,6 +1233,13 @@ and lazy loading of subtrees, allows fast-import to efficiently import projects with 2,000+ branches and 45,114+ files in a very limited memory footprint (less than 2.7 MiB per active branch). +Signals +------- +Sending *SIGUSR1* to the 'git fast-import' process ends the current +packfile early, simulating a `checkpoint` command. The impatient +operator can use this facility to peek at the objects and refs from an +import in progress, at the cost of some added running time and worse +compression. Author ------ diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index d159e88..c76e313 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -26,7 +26,7 @@ The ref names and their object names of fetched refs are stored in `.git/FETCH_HEAD`. This information is left for a later merge operation done by 'git merge'. -When <refspec> stores the fetched result in tracking branches, +When <refspec> stores the fetched result in remote-tracking branches, the tags that point at these branches are automatically followed. This is done by first fetching from the remote using the given <refspec>s, and if the repository has objects that are diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt index 302f56b..40dba8c 100644 --- a/Documentation/git-fmt-merge-msg.txt +++ b/Documentation/git-fmt-merge-msg.txt @@ -9,8 +9,8 @@ git-fmt-merge-msg - Produce a merge commit message SYNOPSIS -------- [verse] -'git fmt-merge-msg' [-m <message>] [--log | --no-log] <$GIT_DIR/FETCH_HEAD -'git fmt-merge-msg' [-m <message>] [--log | --no-log] -F <file> +'git fmt-merge-msg' [-m <message>] [--log[=<n>] | --no-log] <$GIT_DIR/FETCH_HEAD +'git fmt-merge-msg' [-m <message>] [--log[=<n>] | --no-log] -F <file> DESCRIPTION ----------- @@ -24,10 +24,12 @@ automatically invoking 'git merge'. OPTIONS ------- ---log:: +--log[=<n>]:: In addition to branch names, populate the log message with one-line descriptions from the actual commits that are being - merged. + merged. At most <n> commits from each merge parent will be + used (20 if <n> is omitted). This overrides the `merge.log` + configuration variable. --no-log:: Do not list one-line descriptions from the actual commits being @@ -52,8 +54,10 @@ CONFIGURATION ------------- merge.log:: - Whether to include summaries of merged commits in newly - merge commit messages. False by default. + In addition to branch names, populate the log message with at + most the specified number of one-line descriptions from the + actual commits that are being merged. Defaults to false, and + true is a synoym for 20. merge.summary:: Synonym to `merge.log`; this is deprecated and will be removed in diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index a00b783..9dcafc6 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -74,7 +74,7 @@ OPTIONS include::diff-options.txt[] -<n>:: - Limits the number of patches to prepare. + Prepare patches from the topmost <n> commits. -o <dir>:: --output-directory <dir>:: diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 315f07e..801aede 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -89,7 +89,7 @@ are not part of the current project most users will want to expire them sooner. This option defaults to '30 days'. The above two configuration variables can be given to a pattern. For -example, this sets non-default expiry values only to remote tracking +example, this sets non-default expiry values only to remote-tracking branches: ------------ @@ -128,8 +128,8 @@ Notes 'git gc' tries very hard to be safe about the garbage it collects. In particular, it will keep not only objects referenced by your current set -of branches and tags, but also objects referenced by the index, remote -tracking branches, refs saved by 'git filter-branch' in +of branches and tags, but also objects referenced by the index, +remote-tracking branches, refs saved by 'git filter-branch' in refs/original/, or reflogs (which may reference commits in branches that were later amended or rewound). diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 6d40f00..ff41784 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -116,7 +116,7 @@ git log --follow builtin-rev-list.c:: git log --branches --not --remotes=origin:: Shows all commits that are in any of local branches but not in - any of remote tracking branches for 'origin' (what you have that + any of remote-tracking branches for 'origin' (what you have that origin doesn't). git log master --not --remotes=*/master:: diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index d43416d..c1efaaa 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -13,6 +13,7 @@ SYNOPSIS [-s <strategy>] [-X <strategy-option>] [--[no-]rerere-autoupdate] [-m <msg>] <commit>... 'git merge' <msg> HEAD <commit>... +'git merge' --abort DESCRIPTION ----------- @@ -47,6 +48,14 @@ The second syntax (<msg> `HEAD` <commit>...) is supported for historical reasons. Do not use it from the command line or in new scripts. It is the same as `git merge -m <msg> <commit>...`. +The third syntax ("`git merge --abort`") can only be run after the +merge has resulted in conflicts. 'git merge --abort' will abort the +merge process and try to reconstruct the pre-merge state. However, +if there were uncommitted changes when the merge started (and +especially if those changes were further modified after the merge +was started), 'git merge --abort' will in some cases be unable to +reconstruct the original (pre-merge) changes. Therefore: + *Warning*: Running 'git merge' with uncommitted changes is discouraged: while possible, it leaves you in a state that is hard to back out of in the case of a conflict. @@ -72,6 +81,18 @@ invocations. Allow the rerere mechanism to update the index with the result of auto-conflict resolution if possible. +--abort:: + Abort the current conflict resolution process, and + try to reconstruct the pre-merge state. ++ +If there were uncommitted worktree changes present when the merge +started, 'git merge --abort' will in some cases be unable to +reconstruct these changes. It is therefore recommended to always +commit or stash your changes before running 'git merge'. ++ +'git merge --abort' is equivalent to 'git reset --merge' when +`MERGE_HEAD` is present. + <commit>...:: Commits, usually other branch heads, to merge into our branch. You need at least one <commit>. Specifying more than one @@ -142,7 +163,7 @@ happens: i.e. matching `HEAD`. If you tried a merge which resulted in complex conflicts and -want to start over, you can recover with `git reset --merge`. +want to start over, you can recover with `git merge --abort`. HOW CONFLICTS ARE PRESENTED --------------------------- @@ -213,8 +234,8 @@ After seeing a conflict, you can do two things: * Decide not to merge. The only clean-ups you need are to reset the index file to the `HEAD` commit to reverse 2. and to clean - up working tree changes made by 2. and 3.; `git-reset --hard` can - be used for this. + up working tree changes made by 2. and 3.; `git merge --abort` + can be used for this. * Resolve the conflicts. Git will mark the conflicts in the working tree. Edit the files into shape and diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 2981d8c..296f314 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -14,8 +14,12 @@ SYNOPSIS 'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>] 'git notes' edit [<object>] 'git notes' show [<object>] +'git notes' merge [-v | -q] [-s <strategy> ] <notes_ref> +'git notes' merge --commit [-v | -q] +'git notes' merge --abort [-v | -q] 'git notes' remove [<object>] 'git notes' prune [-n | -v] +'git notes' get-ref DESCRIPTION @@ -83,6 +87,21 @@ edit:: show:: Show the notes for a given object (defaults to HEAD). +merge:: + Merge the given notes ref into the current notes ref. + This will try to merge the changes made by the given + notes ref (called "remote") since the merge-base (if + any) into the current notes ref (called "local"). ++ +If conflicts arise and a strategy for automatically resolving +conflicting notes (see the -s/--strategy option) is not given, +the "manual" resolver is used. This resolver checks out the +conflicting notes in a special worktree (`.git/NOTES_MERGE_WORKTREE`), +and instructs the user to manually resolve the conflicts there. +When done, the user can either finalize the merge with +'git notes merge --commit', or abort the merge with +'git notes merge --abort'. + remove:: Remove the notes for a given object (defaults to HEAD). This is equivalent to specifying an empty note message to @@ -91,6 +110,10 @@ remove:: prune:: Remove all notes for non-existing/unreachable objects. +get-ref:: + Print the current notes ref. This provides an easy way to + retrieve the current notes ref (e.g. from scripts). + OPTIONS ------- -f:: @@ -133,9 +156,37 @@ OPTIONS Do not remove anything; just report the object names whose notes would be removed. +-s <strategy>:: +--strategy=<strategy>:: + When merging notes, resolve notes conflicts using the given + strategy. The following strategies are recognized: "manual" + (default), "ours", "theirs", "union" and "cat_sort_uniq". + See the "NOTES MERGE STRATEGIES" section below for more + information on each notes merge strategy. + +--commit:: + Finalize an in-progress 'git notes merge'. Use this option + when you have resolved the conflicts that 'git notes merge' + stored in .git/NOTES_MERGE_WORKTREE. This amends the partial + merge commit created by 'git notes merge' (stored in + .git/NOTES_MERGE_PARTIAL) by adding the notes in + .git/NOTES_MERGE_WORKTREE. The notes ref stored in the + .git/NOTES_MERGE_REF symref is updated to the resulting commit. + +--abort:: + Abort/reset a in-progress 'git notes merge', i.e. a notes merge + with conflicts. This simply removes all files related to the + notes merge. + +-q:: +--quiet:: + When merging notes, operate quietly. + -v:: --verbose:: - Report all object names whose notes are removed. + When merging notes, be more verbose. + When pruning notes, report all object names whose notes are + removed. DISCUSSION @@ -163,6 +214,38 @@ object, in which case the history of the notes can be read with `git log -p -g <refname>`. +NOTES MERGE STRATEGIES +---------------------- + +The default notes merge strategy is "manual", which checks out +conflicting notes in a special work tree for resolving notes conflicts +(`.git/NOTES_MERGE_WORKTREE`), and instructs the user to resolve the +conflicts in that work tree. +When done, the user can either finalize the merge with +'git notes merge --commit', or abort the merge with +'git notes merge --abort'. + +"ours" automatically resolves conflicting notes in favor of the local +version (i.e. the current notes ref). + +"theirs" automatically resolves notes conflicts in favor of the remote +version (i.e. the given notes ref being merged into the current notes +ref). + +"union" automatically resolves notes conflicts by concatenating the +local and remote versions. + +"cat_sort_uniq" is similar to "union", but in addition to concatenating +the local and remote versions, this strategy also sorts the resulting +lines, and removes duplicate lines from the result. This is equivalent +to applying the "cat | sort | uniq" shell pipeline to the local and +remote versions. This strategy is useful if the notes follow a line-based +format where one wants to avoid duplicated lines in the merge result. +Note that if either the local or remote version contain duplicate lines +prior to the merge, these will also be removed by this notes merge +strategy. + + EXAMPLES -------- diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index abbc3eb..3046691 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -26,7 +26,7 @@ With `--rebase`, it runs 'git rebase' instead of 'git merge'. <repository> should be the name of a remote repository as passed to linkgit:git-fetch[1]. <refspec> can name an arbitrary remote ref (for example, the name of a tag) or even -a collection of refs with corresponding remote tracking branches +a collection of refs with corresponding remote-tracking branches (e.g., refs/heads/{asterisk}:refs/remotes/origin/{asterisk}), but usually it is the name of a branch in the remote repository. @@ -98,8 +98,9 @@ include::merge-options.txt[] fetched, the rebase uses that information to avoid rebasing non-local changes. + -See `branch.<name>.rebase` in linkgit:git-config[1] if you want to make -`git pull` always use `{litdd}rebase` instead of merging. +See `branch.<name>.rebase` and `branch.autosetuprebase` in +linkgit:git-config[1] if you want to make `git pull` always use +`{litdd}rebase` instead of merging. + [NOTE] This is a potentially _dangerous_ mode of operation. @@ -136,7 +137,7 @@ and if there is not any such variable, the value on `URL: ` line in `$GIT_DIR/remotes/<origin>` file is used. In order to determine what remote branches to fetch (and -optionally store in the tracking branches) when the command is +optionally store in the remote-tracking branches) when the command is run without any refspec parameters on the command line, values of the configuration variable `remote.<origin>.fetch` are consulted, and if there aren't any, `$GIT_DIR/remotes/<origin>` @@ -149,9 +150,9 @@ refs/heads/*:refs/remotes/origin/* ------------ A globbing refspec must have a non-empty RHS (i.e. must store -what were fetched in tracking branches), and its LHS and RHS +what were fetched in remote-tracking branches), and its LHS and RHS must end with `/*`. The above specifies that all remote -branches are tracked using tracking branches in +branches are tracked using remote-tracking branches in `refs/remotes/origin/` hierarchy under the same name. The rule to determine which remote branch to merge after diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 2e78da4..e88e9c2 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--index-output=<file>] [--no-sparse-checkout] - <tree-ish1> [<tree-ish2> [<tree-ish3>]] + (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]]) DESCRIPTION @@ -114,6 +114,10 @@ OPTIONS Disable sparse checkout support even if `core.sparseCheckout` is true. +--empty:: + Instead of reading tree object(s) into the index, just empty + it. + <tree-ish#>:: The id of the tree object(s) to be read/merged. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 30e5c0e..96680c8 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -279,6 +279,10 @@ which makes little sense. --no-verify:: This option bypasses the pre-rebase hook. See also linkgit:githooks[5]. +--verify:: + Allows the pre-rebase hook to run, which is the default. This option can + be used to override --no-verify. See also linkgit:githooks[5]. + -C<n>:: Ensure at least <n> lines of surrounding context match before and after each change. When fewer lines of surrounding diff --git a/Documentation/git-remote-ext.txt b/Documentation/git-remote-ext.txt new file mode 100644 index 0000000..f4fbf67 --- /dev/null +++ b/Documentation/git-remote-ext.txt @@ -0,0 +1,125 @@ +git-remote-ext(1) +================= + +NAME +---- +git-remote-ext - Bridge smart transport to external command. + +SYNOPSIS +-------- +git remote add nick "ext::<command>[ <arguments>...]" + +DESCRIPTION +----------- +This remote helper uses the specified 'program' to connect +to a remote git server. + +Data written to stdin of this specified 'program' is assumed +to be sent to git:// server, git-upload-pack, git-receive-pack +or git-upload-archive (depending on situation), and data read +from stdout of this program is assumed to be received from +the same service. + +Command and arguments are separated by unescaped space. + +The following sequences have a special meaning: + +'% ':: + Literal space in command or argument. + +'%%':: + Literal percent sign. + +'%s':: + Replaced with name (receive-pack, upload-pack, or + upload-archive) of the service git wants to invoke. + +'%S':: + Replaced with long name (git-receive-pack, + git-upload-pack, or git-upload-archive) of the service + git wants to invoke. + +'%G' (must be first characters in argument):: + This argument will not be passed to 'program'. Instead, it + will cause helper to start by sending git:// service request to + remote side with service field set to approiate value and + repository field set to rest of the argument. Default is not to send + such request. ++ +This is useful if remote side is git:// server accessed over +some tunnel. + +'%V' (must be first characters in argument):: + This argument will not be passed to 'program'. Instead it sets + the vhost field in git:// service request (to rest of the argument). + Default is not to send vhost in such request (if sent). + +ENVIRONMENT VARIABLES: +---------------------- + +GIT_TRANSLOOP_DEBUG:: + If set, prints debugging information about various reads/writes. + +ENVIRONMENT VARIABLES PASSED TO COMMAND: +---------------------------------------- + +GIT_EXT_SERVICE:: + Set to long name (git-upload-pack, etc...) of service helper needs + to invoke. + +GIT_EXT_SERVICE_NOPREFIX:: + Set to long name (upload-pack, etc...) of service helper needs + to invoke. + + +EXAMPLES: +--------- +This remote helper is transparently used by git when +you use commands such as "git fetch <URL>", "git clone <URL>", +, "git push <URL>" or "git remote add nick <URL>", where <URL> +begins with `ext::`. Examples: + +"ext::ssh -i /home/foo/.ssh/somekey user@host.example %S 'foo/repo'":: + Like host.example:foo/repo, but use /home/foo/.ssh/somekey as + keypair and user as user on remote side. This avoids needing to + edit .ssh/config. + +"ext::socat -t3600 - ABSTRACT-CONNECT:/git-server %G/somerepo":: + Represents repository with path /somerepo accessable over + git protocol at abstract namespace address /git-server. + +"ext::git-server-alias foo %G/repo":: + Represents a repository with path /repo accessed using the + helper program "git-server-alias foo". The path to the + repository and type of request are not passed on the command + line but as part of the protocol stream, as usual with git:// + protocol. + +"ext::git-server-alias foo %G/repo %Vfoo":: + Represents a repository with path /repo accessed using the + helper program "git-server-alias foo". The hostname for the + remote server passed in the protocol stream will be "foo" + (this allows multiple virtual git servers to share a + link-level address). + +"ext::git-server-alias foo %G/repo% with% spaces %Vfoo":: + Represents a repository with path '/repo with spaces' accessed + using the helper program "git-server-alias foo". The hostname for + the remote server passed in the protocol stream will be "foo" + (this allows multiple virtual git servers to share a + link-level address). + +"ext::git-ssl foo.example /bar":: + Represents a repository accessed using the helper program + "git-ssl foo.example /bar". The type of request can be + determined by the helper using environment variables (see + above). + +Documentation +-------------- +Documentation by Ilari Liusvaara, Jonathan Nieder and the git list +<git@vger.kernel.org> + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-remote-fd.txt b/Documentation/git-remote-fd.txt new file mode 100644 index 0000000..abc4944 --- /dev/null +++ b/Documentation/git-remote-fd.txt @@ -0,0 +1,59 @@ +git-remote-fd(1) +================ + +NAME +---- +git-remote-fd - Reflect smart transport stream back to caller + +SYNOPSIS +-------- +"fd::<infd>[,<outfd>][/<anything>]" (as URL) + +DESCRIPTION +----------- +This helper uses specified file descriptors to connect to remote git server. +This is not meant for end users but for programs and scripts calling git +fetch, push or archive. + +If only <infd> is given, it is assumed to be bidirectional socket connected +to remote git server (git-upload-pack, git-receive-pack or +git-upload-achive). If both <infd> and <outfd> are given, they are assumed +to be pipes connected to remote git server (<infd> being the inbound pipe +and <outfd> being the outbound pipe. + +It is assumed that any handshaking procedures have already been completed +(such as sending service request for git://) before this helper is started. + +<anything> can be any string. It is ignored. It is meant for provoding +information to user in the URL in case that URL is displayed in some +context. + +ENVIRONMENT VARIABLES +--------------------- +GIT_TRANSLOOP_DEBUG:: + If set, prints debugging information about various reads/writes. + +EXAMPLES +-------- +git fetch fd::17 master:: + Fetch master, using file descriptor #17 to communicate with + git-upload-pack. + +git fetch fd::17/foo master:: + Same as above. + +git push fd::7,8 master (as URL):: + Push master, using file descriptor #7 to read data from + git-receive-pack and file descriptor #8 to write data to + same service. + +git push fd::7,8/bar master:: + Same as above. + +Documentation +-------------- +Documentation by Ilari Liusvaara and the git list <git@vger.kernel.org> + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 0d28feb..c258ea4 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -75,7 +75,7 @@ was passed. 'rename':: -Rename the remote named <old> to <new>. All remote tracking branches and +Rename the remote named <old> to <new>. All remote-tracking branches and configuration settings for the remote are updated. + In case <old> and <new> are the same, and <old> is a file under @@ -84,7 +84,7 @@ the configuration file format. 'rm':: -Remove the remote named <name>. All remote tracking branches and +Remove the remote named <name>. All remote-tracking branches and configuration settings for the remote are removed. 'set-head':: @@ -146,7 +146,7 @@ With `-n` option, the remote heads are not queried first with 'prune':: -Deletes all stale tracking branches under <name>. +Deletes all stale remote-tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in "remotes/<name>". diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index 71e3d9f..0adbe8b 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -89,8 +89,8 @@ the paths that have disappeared from the filesystem. However, depending on the use case, there are several ways that can be done. -Using "git commit -a" -~~~~~~~~~~~~~~~~~~~~~ +Using ``git commit -a'' +~~~~~~~~~~~~~~~~~~~~~~~ If you intend that your next commit should record all modifications of tracked files in the working tree and record all removals of files that have been removed from the working tree with `rm` @@ -98,8 +98,8 @@ files that have been removed from the working tree with `rm` automatically notice and record all removals. You can also have a similar effect without committing by using `git add -u`. -Using "git add -A" -~~~~~~~~~~~~~~~~~~ +Using ``git add -A'' +~~~~~~~~~~~~~~~~~~~~ When accepting a new code drop for a vendor branch, you probably want to record both the removal of paths and additions of new paths as well as modifications of existing paths. @@ -111,8 +111,8 @@ tree using this command: git ls-files -z | xargs -0 rm -f ---------------- -and then "untar" the new code in the working tree. Alternately -you could "rsync" the changes into the working tree. +and then untar the new code in the working tree. Alternately +you could 'rsync' the changes into the working tree. After that, the easiest way to record all removals, additions, and modifications in the working tree is: diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index c283084..7ec9dab 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -82,11 +82,26 @@ See the CONFIGURATION section for 'sendemail.multiedit'. set, as returned by "git var -l". --in-reply-to=<identifier>:: - Specify the contents of the first In-Reply-To header. - Subsequent emails will refer to the previous email - instead of this if --chain-reply-to is set. - Only necessary if --compose is also set. If --compose - is not set, this will be prompted for. + Make the first mail (or all the mails with `--no-thread`) appear as a + reply to the given Message-Id, which avoids breaking threads to + provide a new patch series. + The second and subsequent emails will be sent as replies according to + the `--[no]-chain-reply-to` setting. ++ +So for example when `--thread` and `--no-chain-reply-to` are specified, the +second and subsequent patches will be replies to the first one like in the +illustration below where `[PATCH v2 0/3]` is in reply to `[PATCH 0/2]`: ++ + [PATCH 0/2] Here is what I did... + [PATCH 1/2] Clean up and tests + [PATCH 2/2] Implementation + [PATCH v2 0/3] Here is a reroll + [PATCH v2 1/3] Clean up + [PATCH v2 2/3] New tests + [PATCH v2 3/3] Implementation ++ +Only necessary if --compose is also set. If --compose +is not set, this will be prompted for. --subject=<string>:: Specify the initial subject of the email thread. @@ -97,7 +112,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'. Specify the primary recipient of the emails generated. Generally, this will be the upstream maintainer of the project involved. Default is the value of the 'sendemail.to' configuration value; if that is unspecified, - this will be prompted for. + and --to-cmd is not specified, this will be prompted for. + The --to option must be repeated for each user you want on the to list. @@ -165,6 +180,15 @@ user is prompted for a password while the input is masked for privacy. are also accepted. The port can also be set with the 'sendemail.smtpserverport' configuration variable. +--smtp-server-option=<option>:: + If set, specifies the outgoing SMTP server option to use. + Default value can be specified by the 'sendemail.smtpserveroption' + configuration option. ++ +The --smtp-server-option option must be repeated for each option you want +to pass to the server. Likewise, different lines in the configuration files +must be used for each option. + --smtp-ssl:: Legacy alias for '--smtp-encryption ssl'. @@ -177,6 +201,12 @@ user is prompted for a password while the input is masked for privacy. Automating ~~~~~~~~~~ +--to-cmd=<command>:: + Specify a command to execute once per patch file which + should generate patch file specific "To:" entries. + Output of this command must be single email address per line. + Default is the value of 'sendemail.tocmd' configuration value. + --cc-cmd=<command>:: Specify a command to execute once per patch file which should generate patch file specific "Cc:" entries. @@ -292,6 +322,9 @@ have been specified, in which case default to 'compose'. Default is the value of 'sendemail.validate'; if this is not set, default to '--validate'. +--force:: + Send emails even if safety checks would prevent it. + CONFIGURATION ------------- diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt index 0f3ad81..6403126 100644 --- a/Documentation/git-shell.txt +++ b/Documentation/git-shell.txt @@ -3,24 +3,30 @@ git-shell(1) NAME ---- -git-shell - Restricted login shell for GIT-only SSH access +git-shell - Restricted login shell for Git-only SSH access SYNOPSIS -------- -'$(git --exec-path)/git-shell' -c <command> <argument> +'git shell' [-c <command> <argument>] DESCRIPTION ----------- -This is meant to be used as a login shell for SSH accounts you want -to restrict to GIT pull/push access only. It permits execution only -of server-side GIT commands implementing the pull/push functionality. -The commands can be executed only by the '-c' option; the shell is not -interactive. - -Currently, only four commands are permitted to be called, 'git-receive-pack' -'git-upload-pack' and 'git-upload-archive' with a single required argument, or -'cvs server' (to invoke 'git-cvsserver'). + +A login shell for SSH accounts to provide restricted Git access. When +'-c' is given, the program executes <command> non-interactively; +<command> can be one of 'git receive-pack', 'git upload-pack', 'git +upload-archive', 'cvs server', or a command in COMMAND_DIR. The shell +is started in interactive mode when no arguments are given; in this +case, COMMAND_DIR must exist, and any of the executables in it can be +invoked. + +'cvs server' is a special command which executes git-cvsserver. + +COMMAND_DIR is the path "$HOME/git-shell-commands". The user must have +read and execute permissions to the directory in order to execute the +programs in it. The programs are executed with a cwd of $HOME, and +<argument> is parsed as a command-line string. Author ------ diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 31c78a8..8b169e3 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -177,7 +177,7 @@ On Automatic following ~~~~~~~~~~~~~~~~~~~~~~ If you are following somebody else's tree, you are most likely -using tracking branches (`refs/heads/origin` in traditional +using remote-tracking branches (`refs/heads/origin` in traditional layout, or `refs/remotes/origin/master` in the separate-remote layout). You usually want the tags from the other end. @@ -232,7 +232,7 @@ this case. It may well be that among networking people, they may want to exchange the tags internal to their group, but in that workflow they are most likely tracking with each other's progress by -having tracking branches. Again, the heuristic to automatically +having remote-tracking branches. Again, the heuristic to automatically follow such tags is a good thing. diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt index dada212..7112197 100644 --- a/Documentation/git-verify-tag.txt +++ b/Documentation/git-verify-tag.txt @@ -15,6 +15,10 @@ Validates the gpg signature created by 'git tag'. OPTIONS ------- +-v:: +--verbose:: + Print the contents of the tag object before validating it. + <tag>...:: SHA1 identifiers of git tag objects. diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt index 51e8e0a..c0416e5 100644 --- a/Documentation/git-web--browse.txt +++ b/Documentation/git-web--browse.txt @@ -20,8 +20,14 @@ The following browsers (or commands) are currently supported: * firefox (this is the default under X Window when not using KDE) * iceweasel +* seamonkey +* iceape +* chromium (also supported as chromium-browser) +* google-chrome (also supported as chrome) * konqueror (this is the default under KDE, see 'Note about konqueror' below) +* opera * w3m (this is the default outside graphical environments) +* elinks * links * lynx * dillo diff --git a/Documentation/git.txt b/Documentation/git.txt index 0c897df..80d795d 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -44,31 +44,36 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.7.3.2/git.html[documentation for release 1.7.3.2] +* link:v1.7.3.4/git.html[documentation for release 1.7.3.4] * release notes for + link:RelNotes/1.7.3.4.txt[1.7.3.4], + link:RelNotes/1.7.3.3.txt[1.7.3.3], link:RelNotes/1.7.3.2.txt[1.7.3.2], link:RelNotes/1.7.3.1.txt[1.7.3.1], link:RelNotes/1.7.3.txt[1.7.3]. -* link:v1.7.2.3/git.html[documentation for release 1.7.2.3] +* link:v1.7.2.4/git.html[documentation for release 1.7.2.4] * release notes for + link:RelNotes/1.7.2.4.txt[1.7.2.4], link:RelNotes/1.7.2.3.txt[1.7.2.3], link:RelNotes/1.7.2.2.txt[1.7.2.2], link:RelNotes/1.7.2.1.txt[1.7.2.1], link:RelNotes/1.7.2.txt[1.7.2]. -* link:v1.7.1.2/git.html[documentation for release 1.7.1.2] +* link:v1.7.1.3/git.html[documentation for release 1.7.1.3] * release notes for + link:RelNotes/1.7.1.3.txt[1.7.1.3], link:RelNotes/1.7.1.2.txt[1.7.1.2], link:RelNotes/1.7.1.1.txt[1.7.1.1], link:RelNotes/1.7.1.txt[1.7.1]. -* link:v1.7.0.7/git.html[documentation for release 1.7.0.7] +* link:v1.7.0.8/git.html[documentation for release 1.7.0.8] * release notes for + link:RelNotes/1.7.0.8.txt[1.7.0.8], link:RelNotes/1.7.0.7.txt[1.7.0.7], link:RelNotes/1.7.0.6.txt[1.7.0.6], link:RelNotes/1.7.0.5.txt[1.7.0.5], diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index fbf507a..5a7f936 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -62,14 +62,21 @@ consults `$GIT_DIR/info/attributes` file (which has the highest precedence), `.gitattributes` file in the same directory as the path in question, and its parent directories up to the toplevel of the work tree (the further the directory that contains `.gitattributes` -is from the path in question, the lower its precedence). +is from the path in question, the lower its precedence). Finally +global and system-wide files are considered (they have the lowest +precedence). If you wish to affect only a single repository (i.e., to assign -attributes to files that are particular to one user's workflow), then +attributes to files that are particular to +one user's workflow for that repository), then attributes should be placed in the `$GIT_DIR/info/attributes` file. Attributes which should be version-controlled and distributed to other repositories (i.e., attributes of interest to all users) should go into -`.gitattributes` files. +`.gitattributes` files. Attributes that should affect all repositories +for a single user should be placed in a file specified by the +`core.attributesfile` configuration option (see linkgit:git-config[1]). +Attributes for all users on a system should be placed in the +`$(prefix)/etc/gitattributes` file. Sometimes you would need to override an setting of an attribute for a path to `unspecified` state. This can be done by listing @@ -716,6 +723,8 @@ control per path. Set:: Notice all types of potential whitespace errors known to git. + The tab width is taken from the value of the `core.whitespace` + configuration variable. Unset:: @@ -723,13 +732,13 @@ Unset:: Unspecified:: - Use the value of `core.whitespace` configuration variable to + Use the value of the `core.whitespace` configuration variable to decide what to notice as error. String:: Specify a comma separate list of common whitespace problems to - notice in the same format as `core.whitespace` configuration + notice in the same format as the `core.whitespace` configuration variable. diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt index 5d91a7e..6af29a4 100644 --- a/Documentation/gitdiffcore.txt +++ b/Documentation/gitdiffcore.txt @@ -227,9 +227,9 @@ changes that touch a specified string, and is controlled by the commands. When diffcore-pickaxe is in use, it checks if there are -filepairs whose "result" side has the specified string and -whose "origin" side does not. Such a filepair represents "the -string appeared in this changeset". It also checks for the +filepairs whose "result" side and whose "origin" side have +different number of specified string. Such a filepair represents +"the string appeared in this changeset". It also checks for the opposite case that loses the specified string. When `\--pickaxe-all` is not in effect, diffcore-pickaxe leaves diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 7dc2e8b..8416f34 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -14,11 +14,8 @@ DESCRIPTION A `gitignore` file specifies intentionally untracked files that git should ignore. -Note that all the `gitignore` files really concern only files -that are not already tracked by git; -in order to ignore uncommitted changes in already tracked files, -please refer to the 'git update-index --assume-unchanged' -documentation. +Files already tracked by git are not affected; see the NOTES +below for details. Each line in a `gitignore` file specifies a pattern. When deciding whether to ignore a path, git normally checks @@ -62,7 +59,8 @@ files specified by command-line options. Higher-level git tools, such as 'git status' and 'git add', use patterns from the sources specified above. -Patterns have the following format: +PATTERN FORMAT +-------------- - A blank line matches no files, so it can serve as a separator for readability. @@ -98,7 +96,20 @@ Patterns have the following format: For example, "/{asterisk}.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". -An example: +NOTES +----- + +The purpose of gitignore files is to ensure that certain files +not tracked by git remain untracked. + +To ignore uncommitted changes in a file that is already tracked, +use 'git update-index {litdd}assume-unchanged'. + +To stop tracking a file that is currently tracked, use +'git rm --cached'. + +EXAMPLES +-------- -------------------------------------------------------------- $ git status @@ -140,6 +151,11 @@ Another example: The second .gitignore prevents git from ignoring `arch/foo/kernel/vmlinux.lds.S`. +SEE ALSO +-------- +linkgit:git-rm[1], linkgit:git-update-index[1], +linkgit:gitrepository-layout[5] + Documentation ------------- Documentation by David Greaves, Junio C Hamano, Josh Triplett, diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt index ecab0c0..7fe5848 100644 --- a/Documentation/gittutorial-2.txt +++ b/Documentation/gittutorial-2.txt @@ -373,7 +373,7 @@ $ git status # # new file: closing.txt # -# Changed but not updated: +# Changes not staged for commit: # (use "git add <file>..." to update what will be committed) # # modified: file.txt diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index 1c16066..0982f74 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -385,7 +385,7 @@ alice$ git fetch bob Unlike the longhand form, when Alice fetches from Bob using a remote repository shorthand set up with 'git remote', what was -fetched is stored in a remote tracking branch, in this case +fetched is stored in a remote-tracking branch, in this case `bob/master`. So after this: ------------------------------------- @@ -402,8 +402,8 @@ could merge the changes into her master branch: alice$ git merge bob/master ------------------------------------- -This `merge` can also be done by 'pulling from her own remote -tracking branch', like this: +This `merge` can also be done by 'pulling from her own remote-tracking +branch', like this: ------------------------------------- alice$ git pull . remotes/bob/master diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 1f029f8..f04b48e 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -131,7 +131,7 @@ to point at the new commit. you have. In such these cases, you do not make a new <<def_merge,merge>> <<def_commit,commit>> but instead just update to his revision. This will happen frequently on a - <<def_tracking_branch,tracking branch>> of a remote + <<def_remote_tracking_branch,remote-tracking branch>> of a remote <<def_repository,repository>>. [[def_fetch]]fetch:: @@ -260,7 +260,7 @@ This commit is referred to as a "merge commit", or sometimes just a The default upstream <<def_repository,repository>>. Most projects have at least one upstream project which they track. By default 'origin' is used for that purpose. New upstream updates - will be fetched into remote <<def_tracking_branch,tracking branches>> named + will be fetched into remote <<def_remote_tracking_branch,remote-tracking branches>> named origin/name-of-upstream-branch, which you can see using `git branch -r`. @@ -349,6 +349,14 @@ This commit is referred to as a "merge commit", or sometimes just a master branch head as to-upstream branch at $URL". See also linkgit:git-push[1]. +[[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>>. + [[def_repository]]repository:: A collection of <<def_ref,refs>> together with an <<def_object_database,object database>> containing all objects @@ -418,14 +426,6 @@ This commit is referred to as a "merge commit", or sometimes just a that each contain very well defined concepts or small incremental yet related changes. -[[def_tracking_branch]]tracking branch:: - A regular git <<def_branch,branch>> that is used to follow changes from - another <<def_repository,repository>>. A tracking - branch should not contain direct modifications or have local commits - made to it. A tracking branch can usually be - identified as the right-hand-side <<def_ref,ref>> in a Pull: - <<def_refspec,refspec>>. - [[def_tree]]tree:: Either a <<def_working_tree,working tree>>, or a <<def_tree_object,tree object>> together with the dependent <<def_blob_object,blob>> and tree objects diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index b72f533..92772e7 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -7,8 +7,10 @@ merge.conflictstyle:: marker and the original text before the `=======` marker. merge.log:: - Whether to include summaries of merged commits in newly created - merge commit messages. False by default. + In addition to branch names, populate the log message with at + most the specified number of one-line descriptions from the + actual commits that are being merged. Defaults to false, and + true is a synoym for 20. merge.renameLimit:: The number of files to consider when performing rename detection diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 722d704..e33e0f8 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -16,11 +16,11 @@ inspect and further tweak the merge result before committing. With --no-ff Generate a merge commit even if the merge resolved as a fast-forward. ---log:: +--log[=<n>]:: --no-log:: In addition to branch names, populate the log message with - one-line descriptions from the actual commits that are being - merged. + one-line descriptions from at most <n> actual commits that are being + merged. See also linkgit:git-fmt-merge-msg[1]. + With --no-log do not list one-line descriptions from the actual commits being merged. diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt index 8676e26..595a3cf 100644 --- a/Documentation/merge-strategies.txt +++ b/Documentation/merge-strategies.txt @@ -40,6 +40,28 @@ the other tree did, declaring 'our' history contains all that happened in it. theirs;; This is opposite of 'ours'. +patience;; + With this option, 'merge-recursive' spends a little extra time + to avoid mismerges that sometimes occur due to unimportant + matching lines (e.g., braces from distinct functions). Use + this when the branches to be merged have diverged wildly. + See also linkgit:git-diff[1] `--patience`. + +ignore-space-change;; +ignore-all-space;; +ignore-space-at-eol;; + Treats lines with the indicated type of whitespace change as + unchanged for the sake of a three-way merge. Whitespace + changes mixed with other changes to a line are not ignored. + See also linkgit:git-diff[1] `-b`, `-w`, and + `--ignore-space-at-eol`. ++ +* If 'their' version only introduces whitespace changes to a line, + 'our' version is used; +* If 'our' version introduces whitespace changes but 'their' + version includes a substantial change, 'their' version is used; +* Otherwise, the merge proceeds in the usual way. + renormalize;; This runs a virtual check-out and check-in of all three stages of a file when resolving a three-way merge. This option is @@ -52,6 +74,10 @@ no-renormalize;; Disables the `renormalize` option. This overrides the `merge.renormalize` configuration variable. +rename-threshold=<n>;; + Controls the similarity threshold used for rename detection. + See also linkgit:git-diff[1] `-M`. + subtree[=<path>];; This option is a more advanced form of 'subtree' strategy, where the strategy makes a guess on how two trees must be shifted to diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 42ca059..44a2ef1 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -269,7 +269,7 @@ endif::git-rev-list[] Pretend as if all the refs in `refs/remotes` are listed on the command line as '<commit>'. If '<pattern>' is given, limit - remote tracking branches to ones matching given shell glob. + remote-tracking branches to ones matching given shell glob. If pattern lacks '?', '*', or '[', '/*' at the end is implied. --glob=<glob-pattern>:: diff --git a/Documentation/technical/api-merge.txt b/Documentation/technical/api-merge.txt index a7e050b..9dc1bed 100644 --- a/Documentation/technical/api-merge.txt +++ b/Documentation/technical/api-merge.txt @@ -17,6 +17,40 @@ responsible for a few things. path-specific merge drivers (specified in `.gitattributes`) into account. +Data structures +--------------- + +* `mmbuffer_t`, `mmfile_t` + +These store data usable for use by the xdiff backend, for writing and +for reading, respectively. See `xdiff/xdiff.h` for the definitions +and `diff.c` for examples. + +* `struct ll_merge_options` + +This describes the set of options the calling program wants to affect +the operation of a low-level (single file) merge. Some options: + +`virtual_ancestor`:: + Behave as though this were part of a merge between common + ancestors in a recursive merge. + If a helper program is specified by the + `[merge "<driver>"] recursive` configuration, it will + be used (see linkgit:gitattributes[5]). + +`variant`:: + Resolve local conflicts automatically in favor + of one side or the other (as in 'git merge-file' + `--ours`/`--theirs`/`--union`). Can be `0`, + `XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, or + `XDL_MERGE_FAVOR_UNION`. + +`renormalize`:: + Resmudge and clean the "base", "theirs" and "ours" files + before merging. Use this when the merge is likely to have + overlapped with a change in smudge/clean or end-of-line + normalization rules. + Low-level (single file) merge ----------------------------- @@ -28,15 +62,24 @@ Low-level (single file) merge `.git/info/attributes` into account. Returns 0 for a clean merge. -The caller: +Calling sequence: -1. allocates an mmbuffer_t variable for the result; -2. allocates and fills variables with the file's original content - and two modified versions (using `read_mmfile`, for example); -3. calls ll_merge(); -4. reads the output from result_buf.ptr and result_buf.size; -5. releases buffers when finished (free(ancestor.ptr); free(ours.ptr); - free(theirs.ptr); free(result_buf.ptr);). +* Prepare a `struct ll_merge_options` to record options. + If you have no special requests, skip this and pass `NULL` + as the `opts` parameter to use the default options. + +* Allocate an mmbuffer_t variable for the result. + +* Allocate and fill variables with the file's original content + and two modified versions (using `read_mmfile`, for example). + +* Call `ll_merge()`. + +* Read the merged content from `result_buf.ptr` and `result_buf.size`. + +* Release buffers when finished. A simple + `free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); + free(result_buf.ptr);` will do. If the modifications do not merge cleanly, `ll_merge` will return a nonzero value and `result_buf` will generally include a description of @@ -47,18 +90,6 @@ The `ancestor_label`, `our_label`, and `their_label` parameters are used to label the different sides of a conflict if the merge driver supports this. -The `flag` parameter is a bitfield: - - - The `LL_OPT_VIRTUAL_ANCESTOR` bit indicates whether this is an - internal merge to consolidate ancestors for a recursive merge. - - - The `LL_OPT_FAVOR_MASK` bits allow local conflicts to be automatically - resolved in favor of one side or the other (as in 'git merge-file' - `--ours`/`--theirs`/`--union`). - They can be populated by `create_ll_flag`, whose argument can be - `XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, or - `XDL_MERGE_FAVOR_UNION`. - Everything else --------------- diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index c5d141c..f6a4a36 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -118,13 +118,16 @@ There are some macros to easily define options: `OPT__COLOR(&int_var, description)`:: Add `\--color[=<when>]` and `--no-color`. -`OPT__DRY_RUN(&int_var)`:: +`OPT__DRY_RUN(&int_var, description)`:: Add `-n, \--dry-run`. -`OPT__QUIET(&int_var)`:: +`OPT__FORCE(&int_var, description)`:: + Add `-f, \--force`. + +`OPT__QUIET(&int_var, description)`:: Add `-q, \--quiet`. -`OPT__VERBOSE(&int_var)`:: +`OPT__VERBOSE(&int_var, description)`:: Add `-v, \--verbose`. `OPT_GROUP(description)`:: diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index fc56da6..f13a846 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -344,7 +344,8 @@ Examining branches from a remote repository The "master" branch that was created at the time you cloned is a copy of the HEAD in the repository that you cloned from. That repository may also have had other branches, though, and your local repository -keeps branches which track each of those remote branches, which you +keeps branches which track each of those remote branches, called +remote-tracking branches, which you can view using the "-r" option to linkgit:git-branch[1]: ------------------------------------------------ @@ -359,13 +360,23 @@ $ git branch -r origin/todo ------------------------------------------------ -You cannot check out these remote-tracking branches, but you can -examine them on a branch of your own, just as you would a tag: +In this example, "origin" is called a remote repository, or "remote" +for short. The branches of this repository are called "remote +branches" from our point of view. The remote-tracking branches listed +above were created based on the remote branches at clone time and will +be updated by "git fetch" (hence "git pull") and "git push". See +<<Updating-a-repository-With-git-fetch>> for details. + +You might want to build on one of these remote-tracking branches +on a branch of your own, just as you would for a tag: ------------------------------------------------ $ git checkout -b my-todo-copy origin/todo ------------------------------------------------ +You can also check out "origin/todo" directly to examine it or +write a one-off patch. See <<detached-head,detached head>>. + Note that the name "origin" is just the name that git uses by default to refer to the repository that you cloned from. @@ -435,7 +446,7 @@ linux-nfs/master origin/master ------------------------------------------------- -If you run "git fetch <remote>" later, the tracking branches for the +If you run "git fetch <remote>" later, the remote-tracking branches for the named <remote> will be updated. If you examine the file .git/config, you will see that git has added @@ -1700,7 +1711,7 @@ may wish to check the original repository for updates and merge them into your own work. We have already seen <<Updating-a-repository-With-git-fetch,how to -keep remote tracking branches up to date>> with linkgit:git-fetch[1], +keep remote-tracking branches up to date>> with linkgit:git-fetch[1], and how to merge two branches. So you can merge in changes from the original repository's master branch with: @@ -1716,15 +1727,21 @@ one step: $ git pull origin master ------------------------------------------------- -In fact, if you have "master" checked out, then by default "git pull" -merges from the HEAD branch of the origin repository. So often you can +In fact, if you have "master" checked out, then this branch has been +configured by "git clone" to get changes from the HEAD branch of the +origin repository. So often you can accomplish the above with just a simple ------------------------------------------------- $ git pull ------------------------------------------------- -More generally, a branch that is created from a remote branch will pull +This command will fetch changes from the remote branches to your +remote-tracking branches `origin/*`, and merge the default branch into +the current branch. + +More generally, a branch that is created from a remote-tracking branch +will pull by default from that branch. See the descriptions of the branch.<name>.remote and branch.<name>.merge options in linkgit:git-config[1], and the discussion of the `--track` option in @@ -2106,7 +2123,7 @@ $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git $ cd work ------------------------------------------------- -Linus's tree will be stored in the remote branch named origin/master, +Linus's tree will be stored in the remote-tracking branch named origin/master, and can be updated using linkgit:git-fetch[1]; you can track other public trees using linkgit:git-remote[1] to set up a "remote" and linkgit:git-fetch[1] to keep them up-to-date; see @@ -2800,8 +2817,8 @@ Be aware that commits that the old version of example/master pointed at may be lost, as we saw in the previous section. [[remote-branch-configuration]] -Configuring remote branches ---------------------------- +Configuring remote-tracking branches +------------------------------------ We saw above that "origin" is just a shortcut to refer to the repository that you originally cloned from. This information is diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 702306a..d441d88 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.3.3 +DEF_VER=v1.7.3.GIT LF=' ' @@ -67,10 +67,10 @@ Issues of note: - A POSIX-compliant shell is required to run many scripts needed for everyday use (e.g. "bisect", "pull"). - - "Perl" is needed to use some of the features (e.g. preparing a - partial commit using "git add -i/-p", interacting with svn - repositories with "git svn"). If you can live without these, use - NO_PERL. + - "Perl" version 5.8 or later is needed to use some of the + features (e.g. preparing a partial commit using "git add -i/-p", + interacting with svn repositories with "git svn"). If you can + live without these, use NO_PERL. - "openssl" library is used by git-imap-send to use IMAP over SSL. If you don't need it, use NO_OPENSSL. @@ -122,8 +122,9 @@ Issues of note: Building and installing the pdf file additionally requires dblatex. Version 0.2.7 with asciidoc >= 8.2.7 is known to work. - The documentation is written for AsciiDoc 7, but "make - ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8. + The documentation is written for AsciiDoc 7, but by default + uses some compatibility wrappers to work on AsciiDoc 8. If you have + AsciiDoc 7, try "make ASCIIDOC7=YesPlease". Alternatively, pre-formatted documentation is available in "html" and "man" branches of the git repository itself. For @@ -70,6 +70,11 @@ all:: # # Define NO_STRTOK_R if you don't have strtok_r in the C library. # +# Define NO_FNMATCH if you don't have fnmatch in the C library. +# +# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the +# FNM_CASEFOLD GNU extension. +# # Define NO_LIBGEN_H if you don't have libgen.h. # # Define NEEDS_LIBGEN if your libgen needs -lgen when linking @@ -162,13 +167,13 @@ all:: # Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks # field that counts the on-disk footprint in 512-byte blocks. # -# Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8 +# Define ASCIIDOC7 if you want to format documentation with AsciiDoc 7 # # Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72 # (not v1.73 or v1.71). # -# Define ASCIIDOC_NO_ROFF if your DocBook XSL escapes raw roff directives -# (versions 1.72 and later and 1.68.1 and earlier). +# Define ASCIIDOC_ROFF if your DocBook XSL does not escape raw roff directives +# (versions 1.68.1 through v1.72). # # Define GNU_ROFF if your target system uses GNU groff. This forces # apostrophes to be ASCII so that cut&pasting examples to the shell @@ -270,6 +275,7 @@ STRIP ?= strip # infodir # htmldir # ETC_GITCONFIG (but not sysconfdir) +# ETC_GITATTRIBUTES # can be specified as a relative path some/where/else; # this is interpreted as relative to $(prefix) and "git" at # runtime figures out where they are based on the path to the executable. @@ -288,9 +294,11 @@ htmldir = share/doc/git-doc ifeq ($(prefix),/usr) sysconfdir = /etc ETC_GITCONFIG = $(sysconfdir)/gitconfig +ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes else sysconfdir = $(prefix)/etc ETC_GITCONFIG = etc/gitconfig +ETC_GITATTRIBUTES = etc/gitattributes endif lib = lib # DESTDIR= @@ -398,6 +406,7 @@ EXTRA_PROGRAMS = # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS += $(EXTRA_PROGRAMS) +PROGRAM_OBJS += daemon.o PROGRAM_OBJS += fast-import.o PROGRAM_OBJS += imap-send.o PROGRAM_OBJS += shell.o @@ -493,6 +502,9 @@ LIB_H += compat/bswap.h LIB_H += compat/cygwin.h LIB_H += compat/mingw.h LIB_H += compat/win32/pthread.h +LIB_H += compat/win32/syslog.h +LIB_H += compat/win32/sys/poll.h +LIB_H += compat/win32/dirent.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@ -514,6 +526,7 @@ LIB_H += mailmap.h LIB_H += merge-recursive.h LIB_H += notes.h LIB_H += notes-cache.h +LIB_H += notes-merge.h LIB_H += object.h LIB_H += pack.h LIB_H += pack-refs.h @@ -604,6 +617,7 @@ LIB_OBJS += merge-recursive.o LIB_OBJS += name-hash.o LIB_OBJS += notes.o LIB_OBJS += notes-cache.o +LIB_OBJS += notes-merge.o LIB_OBJS += object.o LIB_OBJS += pack-check.o LIB_OBJS += pack-refs.o @@ -659,6 +673,7 @@ LIB_OBJS += write_or_die.o LIB_OBJS += ws.o LIB_OBJS += wt-status.o LIB_OBJS += xdiff-interface.o +LIB_OBJS += zlib.o BUILTIN_OBJS += builtin/add.o BUILTIN_OBJS += builtin/annotate.o @@ -725,6 +740,8 @@ BUILTIN_OBJS += builtin/read-tree.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o BUILTIN_OBJS += builtin/remote.o +BUILTIN_OBJS += builtin/remote-ext.o +BUILTIN_OBJS += builtin/remote-fd.o BUILTIN_OBJS += builtin/replace.o BUILTIN_OBJS += builtin/rerere.o BUILTIN_OBJS += builtin/reset.o @@ -843,6 +860,7 @@ ifeq ($(uname_S),SunOS) NO_MKDTEMP = YesPlease NO_MKSTEMPS = YesPlease NO_REGEX = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease ifeq ($(uname_R),5.6) SOCKLEN_T = int NO_HSTRERROR = YesPlease @@ -1049,6 +1067,7 @@ ifeq ($(uname_S),Windows) NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease NO_STRTOK_R = YesPlease + NO_FNMATCH = YesPlease NO_MEMMEM = YesPlease # NEEDS_LIBICONV = YesPlease NO_ICONV = YesPlease @@ -1061,7 +1080,6 @@ ifeq ($(uname_S),Windows) NO_SVN_TESTS = YesPlease NO_PERL_MAKEMAKER = YesPlease RUNTIME_PREFIX = YesPlease - NO_POSIX_ONLY_PROGRAMS = YesPlease NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease NO_NSEC = YesPlease USE_WIN32_MMAP = YesPlease @@ -1072,14 +1090,17 @@ ifeq ($(uname_S),Windows) NO_CURL = YesPlease NO_PYTHON = YesPlease BLK_SHA1 = YesPlease + NO_POSIX_GOODIES = UnfortunatelyYes NATIVE_CRLF = YesPlease CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl CFLAGS = BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE - COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o - COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" + COMPAT_OBJS = compat/msvc.o compat/winansi.o \ + compat/win32/pthread.o compat/win32/syslog.o \ + compat/win32/sys/poll.o compat/win32/dirent.o + COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib PTHREAD_LIBS = @@ -1093,6 +1114,25 @@ else endif X = .exe endif +ifeq ($(uname_S),Interix) + NO_SYS_POLL_H = YesPlease + NO_INTTYPES_H = YesPlease + NO_INITGROUPS = YesPlease + NO_IPV6 = YesPlease + NO_MEMMEM = YesPlease + NO_MKDTEMP = YesPlease + NO_STRTOUMAX = YesPlease + NO_NSEC = YesPlease + NO_MKSTEMPS = YesPlease + ifeq ($(uname_R),3.5) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + endif + ifeq ($(uname_R),5.2) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + endif +endif ifneq (,$(findstring MINGW,$(uname_S))) pathsep = ; NO_PREAD = YesPlease @@ -1104,6 +1144,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease NO_STRTOK_R = YesPlease + NO_FNMATCH = YesPlease NO_MEMMEM = YesPlease NEEDS_LIBICONV = YesPlease OLD_ICONV = YesPlease @@ -1114,7 +1155,6 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_SVN_TESTS = YesPlease NO_PERL_MAKEMAKER = YesPlease RUNTIME_PREFIX = YesPlease - NO_POSIX_ONLY_PROGRAMS = YesPlease NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease NO_NSEC = YesPlease USE_WIN32_MMAP = YesPlease @@ -1125,10 +1165,14 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_PYTHON = YesPlease BLK_SHA1 = YesPlease ETAGS_TARGET = ETAGS - COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32 + NO_INET_PTON = YesPlease + NO_INET_NTOP = YesPlease + NO_POSIX_GOODIES = UnfortunatelyYes + COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" - COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \ - compat/win32/pthread.o + COMPAT_OBJS += compat/mingw.o compat/winansi.o \ + compat/win32/pthread.o compat/win32/syslog.o \ + compat/win32/sys/poll.o compat/win32/dirent.o EXTLIBS += -lws2_32 PTHREAD_LIBS = X = .exe @@ -1243,9 +1287,6 @@ ifdef ZLIB_PATH endif EXTLIBS += -lz -ifndef NO_POSIX_ONLY_PROGRAMS - PROGRAM_OBJS += daemon.o -endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl ifdef OPENSSLDIR @@ -1262,11 +1303,15 @@ else BLK_SHA1 = 1 OPENSSL_LIBSSL = endif +ifdef NO_OPENSSL + LIB_4_CRYPTO = +else ifdef NEEDS_SSL_WITH_CRYPTO LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl else LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto endif +endif ifdef NEEDS_LIBICONV ifdef ICONVDIR BASIC_CFLAGS += -I$(ICONVDIR)/include @@ -1339,6 +1384,17 @@ ifdef NO_STRTOK_R COMPAT_CFLAGS += -DNO_STRTOK_R COMPAT_OBJS += compat/strtok_r.o endif +ifdef NO_FNMATCH + COMPAT_CFLAGS += -Icompat/fnmatch + COMPAT_CFLAGS += -DNO_FNMATCH + COMPAT_OBJS += compat/fnmatch/fnmatch.o +else +ifdef NO_FNMATCH_CASEFOLD + COMPAT_CFLAGS += -Icompat/fnmatch + COMPAT_CFLAGS += -DNO_FNMATCH_CASEFOLD + COMPAT_OBJS += compat/fnmatch/fnmatch.o +endif +endif ifdef NO_SETENV COMPAT_CFLAGS += -DNO_SETENV COMPAT_OBJS += compat/setenv.o @@ -1357,6 +1413,15 @@ endif ifdef NO_SYS_SELECT_H BASIC_CFLAGS += -DNO_SYS_SELECT_H endif +ifdef NO_SYS_POLL_H + BASIC_CFLAGS += -DNO_SYS_POLL_H +endif +ifdef NO_INTTYPES_H + BASIC_CFLAGS += -DNO_INTTYPES_H +endif +ifdef NO_INITGROUPS + BASIC_CFLAGS += -DNO_INITGROUPS +endif ifdef NO_MMAP COMPAT_CFLAGS += -DNO_MMAP COMPAT_OBJS += compat/mmap.o @@ -1394,9 +1459,11 @@ endif endif ifdef NO_INET_NTOP LIB_OBJS += compat/inet_ntop.o + BASIC_CFLAGS += -DNO_INET_NTOP endif ifdef NO_INET_PTON LIB_OBJS += compat/inet_pton.o + BASIC_CFLAGS += -DNO_INET_PTON endif ifdef NO_ICONV @@ -1411,6 +1478,10 @@ ifdef NO_DEFLATE_BOUND BASIC_CFLAGS += -DNO_DEFLATE_BOUND endif +ifdef NO_POSIX_GOODIES + BASIC_CFLAGS += -DNO_POSIX_GOODIES +endif + ifdef BLK_SHA1 SHA1_HEADER = "block-sha1/sha1.h" LIB_OBJS += block-sha1/sha1.o @@ -1515,14 +1586,15 @@ ifndef V endif endif -ifdef ASCIIDOC8 - export ASCIIDOC8 +ifdef ASCIIDOC7 + export ASCIIDOC7 endif # Shell quote (do not use $(call) to accommodate ancient setups); SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER)) ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG)) +ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES)) DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) bindir_SQ = $(subst ','\'',$(bindir)) @@ -1607,6 +1679,8 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) +help.o: common-cmds.h + builtin/help.o: common-cmds.h builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ @@ -1762,6 +1836,8 @@ XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ xdiff/xmerge.o xdiff/xpatience.o VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \ vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o +VCSSVN_TEST_OBJS = test-obj-pool.o test-string-pool.o \ + test-line-buffer.o test-treap.o OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS) dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) @@ -1870,25 +1946,26 @@ builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o tra builtin/bundle.o bundle.o transport.o: bundle.h builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h -builtin/grep.o: thread-utils.h +builtin/grep.o builtin/pack-objects.o transport-helper.o: thread-utils.h builtin/send-pack.o transport.o: send-pack.h builtin/log.o builtin/shortlog.o: shortlog.h builtin/prune.o builtin/reflog.o reachable.o: reachable.h builtin/commit.o builtin/revert.o wt-status.o: wt-status.h builtin/tar-tree.o archive-tar.o: tar.h -builtin/pack-objects.o: thread-utils.h connect.o transport.o http-backend.o: url.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h -http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h +http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h xdiff-interface.o $(XDIFF_OBJS): \ xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h -$(VCSSVN_OBJS): \ +$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) \ vcs-svn/obj_pool.h vcs-svn/trp.h vcs-svn/string_pool.h \ vcs-svn/line_buffer.h vcs-svn/repo_tree.h vcs-svn/fast_export.h \ vcs-svn/svndump.h + +test-svn-fe.o: vcs-svn/svndump.h endif exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \ @@ -1901,6 +1978,8 @@ builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \ config.s config.o: EXTRA_CPPFLAGS = -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' +attr.s attr.o: EXTRA_CPPFLAGS = -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"' + http.s http.o: EXTRA_CPPFLAGS = -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"' ifdef NO_EXPAT @@ -1 +1 @@ -Documentation/RelNotes/1.7.3.3.txt
\ No newline at end of file +Documentation/RelNotes/1.7.4.txt
\ No newline at end of file @@ -108,10 +108,14 @@ const char *make_nonrelative_path(const char *path) if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX) die("Too long path: %.*s", 60, path); } else { + size_t len; + const char *fmt; const char *cwd = get_pwd_cwd(); if (!cwd) die_errno("Cannot determine the current working directory"); - if (snprintf(buf, PATH_MAX, "%s/%s", cwd, path) >= PATH_MAX) + len = strlen(cwd); + fmt = (len > 0 && is_dir_sep(cwd[len-1])) ? "%s%s" : "%s/%s"; + if (snprintf(buf, PATH_MAX, fmt, cwd, path) >= PATH_MAX) die("Too long path: %.*s", 60, path); } return buf; @@ -314,7 +314,7 @@ static int parse_archive_args(int argc, const char **argv, "write the archive to this file"), OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes, "read .gitattributes in working directory"), - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "report archived files on stderr"), OPT__COMPR('0', &compression_level, "store only", 0), OPT__COMPR('1', &compression_level, "compress faster", 1), OPT__COMPR_HIDDEN('2', &compression_level, 2), @@ -1,5 +1,6 @@ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "exec_cmd.h" #include "attr.h" const char git_attr__true[] = "(builtin)true"; @@ -10,6 +11,8 @@ static const char git_attr__unknown[] = "(builtin)unknown"; #define ATTR__UNSET NULL #define ATTR__UNKNOWN git_attr__unknown +static const char *attributes_file; + /* * The basic design decision here is that we are not going to have * insanely large number of attributes. @@ -462,6 +465,32 @@ static void drop_attr_stack(void) } } +const char *git_etc_gitattributes(void) +{ + static const char *system_wide; + if (!system_wide) + system_wide = system_path(ETC_GITATTRIBUTES); + return system_wide; +} + +int git_attr_system(void) +{ + return !git_env_bool("GIT_ATTR_NOSYSTEM", 0); +} + +int git_attr_global(void) +{ + return !git_env_bool("GIT_ATTR_NOGLOBAL", 0); +} + +static int git_attr_config(const char *var, const char *value, void *dummy) +{ + if (!strcmp(var, "core.attributesfile")) + return git_config_pathname(&attributes_file, var, value); + + return 0; +} + static void bootstrap_attr_stack(void) { if (!attr_stack) { @@ -472,6 +501,25 @@ static void bootstrap_attr_stack(void) elem->prev = attr_stack; attr_stack = elem; + if (git_attr_system()) { + elem = read_attr_from_file(git_etc_gitattributes(), 1); + if (elem) { + elem->origin = NULL; + elem->prev = attr_stack; + attr_stack = elem; + } + } + + git_config(git_attr_config, NULL); + if (git_attr_global() && attributes_file) { + elem = read_attr_from_file(attributes_file, 1); + if (elem) { + elem->origin = NULL; + elem->prev = attr_stack; + attr_stack = elem; + } + } + if (!is_bare_repository() || direction == GIT_ATTR_INDEX) { elem = read_attr(GITATTRIBUTES_FILE, 1); elem->origin = strdup(""); @@ -499,7 +547,9 @@ static void prepare_attr_stack(const char *path, int dirlen) /* * At the bottom of the attribute stack is the built-in - * set of attribute definitions. Then, contents from + * set of attribute definitions, followed by the contents + * of $(prefix)/etc/gitattributes and a file specified by + * core.attributesfile. Then, contents from * .gitattribute files from directories closer to the * root to the ones in deeper directories are pushed * to the stack. Finally, at the very top of the stack @@ -22,8 +22,8 @@ void create_branch(const char *head, const char *name, const char *start_name, void remove_branch_state(void); /* - * Configure local branch "local" to merge remote branch "remote" - * taken from origin "origin". + * Configure local branch "local" as downstream to branch "remote" + * from remote "origin". Used by git branch --set-upstream. */ #define BRANCH_CONFIG_VERBOSE 01 extern void install_branch_config(int flag, const char *local, const char *origin, const char *remote); @@ -7,15 +7,16 @@ #include "commit.h" #include "notes.h" +#define DEFAULT_MERGE_LOG_LEN 20 + extern const char git_version_string[]; extern const char git_usage_string[]; extern const char git_more_info_string[]; extern void prune_packed_objects(int); -extern int fmt_merge_msg(int merge_summary, struct strbuf *in, - struct strbuf *out); -extern int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out); -extern int commit_notes(struct notes_tree *t, const char *msg); +extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out, + int merge_title, int shortlog_len); +extern void commit_notes(struct notes_tree *t, const char *msg); struct notes_rewrite_cfg { struct notes_tree **trees; @@ -107,6 +108,8 @@ extern int cmd_read_tree(int argc, const char **argv, const char *prefix); extern int cmd_receive_pack(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); extern int cmd_remote(int argc, const char **argv, const char *prefix); +extern int cmd_remote_ext(int argc, const char **argv, const char *prefix); +extern int cmd_remote_fd(int argc, const char **argv, const char *prefix); extern int cmd_config(int argc, const char **argv, const char *prefix); extern int cmd_rerere(int argc, const char **argv, const char *prefix); extern int cmd_reset(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index 3a5fca5..12b964e 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -313,13 +313,13 @@ static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0; static struct option builtin_add_options[] = { - OPT__DRY_RUN(&show_only), - OPT__VERBOSE(&verbose), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__VERBOSE(&verbose, "be verbose"), OPT_GROUP(""), OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"), - OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"), + OPT__FORCE(&ignored_too, "allow adding otherwise ignored files"), OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"), OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"), OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"), @@ -447,7 +447,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (!seen[i] && pathspec[i][0] && !file_exists(pathspec[i])) { if (ignore_missing) { - if (excluded(&dir, pathspec[i], DT_UNKNOWN)) + int dtype = DT_UNKNOWN; + if (excluded(&dir, pathspec[i], &dtype)) dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); } else die("pathspec '%s' did not match any files", diff --git a/builtin/apply.c b/builtin/apply.c index f051e66..14951da 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -449,7 +449,7 @@ static char *find_name_gnu(const char *line, char *def, int p_value) return squash_slash(strbuf_detach(&name, NULL)); } -static size_t tz_len(const char *line, size_t len) +static size_t sane_tz_len(const char *line, size_t len) { const char *tz, *p; @@ -467,6 +467,24 @@ static size_t tz_len(const char *line, size_t len) return line + len - tz; } +static size_t tz_with_colon_len(const char *line, size_t len) +{ + const char *tz, *p; + + if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':') + return 0; + tz = line + len - strlen(" +08:00"); + + if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-')) + return 0; + p = tz + 2; + if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || + !isdigit(*p++) || !isdigit(*p++)) + return 0; + + return line + len - tz; +} + static size_t date_len(const char *line, size_t len) { const char *date, *p; @@ -561,7 +579,9 @@ static size_t diff_timestamp_len(const char *line, size_t len) if (!isdigit(end[-1])) return 0; - n = tz_len(line, end - line); + n = sane_tz_len(line, end - line); + if (!n) + n = tz_with_colon_len(line, end - line); end -= n; n = short_time_len(line, end - line); @@ -733,8 +753,8 @@ static int has_epoch_timestamp(const char *nameline) " " "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?" " " - "([-+][0-2][0-9][0-5][0-9])\n"; - const char *timestamp = NULL, *cp; + "([-+][0-2][0-9]:?[0-5][0-9])\n"; + const char *timestamp = NULL, *cp, *colon; static regex_t *stamp; regmatch_t m[10]; int zoneoffset; @@ -764,8 +784,11 @@ static int has_epoch_timestamp(const char *nameline) return 0; } - zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10); - zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); + zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10); + if (*colon == ':') + zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10); + else + zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); if (timestamp[m[3].rm_so] == '-') zoneoffset = -zoneoffset; @@ -919,28 +942,28 @@ static int gitdiff_newfile(const char *line, struct patch *patch) static int gitdiff_copysrc(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->old_name = find_name(line, NULL, 0, 0); + patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } static int gitdiff_copydst(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->new_name = find_name(line, NULL, 0, 0); + patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } static int gitdiff_renamesrc(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->old_name = find_name(line, NULL, 0, 0); + patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } static int gitdiff_renamedst(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->new_name = find_name(line, NULL, 0, 0); + patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } @@ -1025,7 +1048,7 @@ static char *git_header_name(char *line, int llen) { const char *name; const char *second = NULL; - size_t len; + size_t len, line_len; line += strlen("diff --git "); llen -= strlen("diff --git "); @@ -1125,6 +1148,10 @@ static char *git_header_name(char *line, int llen) * Accept a name only if it shows up twice, exactly the same * form. */ + second = strchr(name, '\n'); + if (!second) + return NULL; + line_len = second - name; for (len = 0 ; ; len++) { switch (name[len]) { default: @@ -1132,15 +1159,11 @@ static char *git_header_name(char *line, int llen) case '\n': return NULL; case '\t': case ' ': - second = name+len; - for (;;) { - char c = *second++; - if (c == '\n') - return NULL; - if (c == '/') - break; - } - if (second[len] == '\n' && !memcmp(name, second, len)) { + second = stop_at_slash(name + len, line_len - len); + if (!second) + return NULL; + second++; + if (second[len] == '\n' && !strncmp(name, second, len)) { return xmemdupz(name, len); } } @@ -3849,7 +3872,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) "don't expect at least one line of context"), OPT_BOOLEAN(0, "reject", &apply_with_reject, "leave the rejected hunks in corresponding *.rej files"), - OPT__VERBOSE(&apply_verbosely), + OPT__VERBOSE(&apply_verbosely, "be verbose"), OPT_BIT(0, "inaccurate-eof", &options, "tolerate incorrectly detected missing new-line at the end of file", INACCURATE_EOF), diff --git a/builtin/blame.c b/builtin/blame.c index f5fccc1..aa30ec5 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1617,6 +1617,7 @@ static const char *format_time(unsigned long time, const char *tz_str, #define OUTPUT_SHOW_NUMBER 040 #define OUTPUT_SHOW_SCORE 0100 #define OUTPUT_NO_AUTHOR 0200 +#define OUTPUT_SHOW_EMAIL 0400 static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) { @@ -1682,12 +1683,17 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) } printf("%.*s", length, hex); - if (opt & OUTPUT_ANNOTATE_COMPAT) - printf("\t(%10s\t%10s\t%d)", ci.author, + if (opt & OUTPUT_ANNOTATE_COMPAT) { + const char *name; + if (opt & OUTPUT_SHOW_EMAIL) + name = ci.author_mail; + else + name = ci.author; + printf("\t(%10s\t%10s\t%d)", name, format_time(ci.author_time, ci.author_tz, show_raw_time), ent->lno + 1 + cnt); - else { + } else { if (opt & OUTPUT_SHOW_SCORE) printf(" %*d %02d", max_score_digits, ent->score, @@ -1700,9 +1706,15 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) ent->s_lno + 1 + cnt); if (!(opt & OUTPUT_NO_AUTHOR)) { - int pad = longest_author - utf8_strwidth(ci.author); + const char *name; + int pad; + if (opt & OUTPUT_SHOW_EMAIL) + name = ci.author_mail; + else + name = ci.author; + pad = longest_author - utf8_strwidth(name); printf(" (%s%*s %10s", - ci.author, pad, "", + name, pad, "", format_time(ci.author_time, ci.author_tz, show_raw_time)); @@ -1840,7 +1852,10 @@ static void find_alignment(struct scoreboard *sb, int *option) if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); - num = utf8_strwidth(ci.author); + if (*option & OUTPUT_SHOW_EMAIL) + num = utf8_strwidth(ci.author_mail); + else + num = utf8_strwidth(ci.author); if (longest_author < num) longest_author = num; } @@ -2289,6 +2304,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP), OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME), OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR), + OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL), OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE), OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"), OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"), @@ -2309,8 +2325,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; dashdash_pos = 0; - parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_ARGV0); + parse_options_start(&ctx, argc, argv, prefix, options, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); for (;;) { switch (parse_options_step(&ctx, options, blame_opt_usage)) { case PARSE_OPT_HELP: diff --git a/builtin/branch.c b/builtin/branch.c index 87976f0..0cad20b 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -621,7 +621,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_GROUP("Generic options"), - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, + "show hash and subject, give twice for upstream branch"), OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))", BRANCH_TRACK_EXPLICIT), OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", @@ -651,7 +652,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1), OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"), - OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"), + OPT__FORCE(&force_create, "force creation (when already exists)"), { OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, "commit", "print only not merged branches", @@ -667,6 +668,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_END(), }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_branch_usage, options); + git_config(git_branch_config, NULL); if (branch_use_color == -1) diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 65cbee0..f1fec24 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -217,9 +217,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) struct option builtin_checkout_index_options[] = { OPT_BOOLEAN('a', "all", &all, "checks out all files in the index"), - OPT_BOOLEAN('f', "force", &force, - "forces overwrite of existing files"), - OPT__QUIET(&quiet), + OPT__FORCE(&force, "forces overwrite of existing files"), + OPT__QUIET(&quiet, + "no warning for existing files and files not in index"), OPT_BOOLEAN('n', "no-create", ¬_new, "don't checkout new files"), { OPTION_CALLBACK, 'u', "index", &newfd, NULL, @@ -241,6 +241,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) OPT_END() }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_checkout_index_usage, + builtin_checkout_index_options); git_config(git_default_config, NULL); state.base_dir = ""; prefix_length = prefix ? strlen(prefix) : 0; diff --git a/builtin/checkout.c b/builtin/checkout.c index a54583b..757f9a0 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -161,7 +161,7 @@ static int checkout_merged(int pos, struct checkout *state) * merge.renormalize set, too */ status = ll_merge(&result_buf, path, &ancestor, "base", - &ours, "ours", &theirs, "theirs", 0); + &ours, "ours", &theirs, "theirs", NULL); free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); @@ -686,7 +686,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) int patch_mode = 0; int dwim_new_local_branch = 1; struct option options[] = { - OPT__QUIET(&opts.quiet), + OPT__QUIET(&opts.quiet, "suppress progress reporting"), OPT_STRING('b', NULL, &opts.new_branch, "branch", "create and checkout a new branch"), OPT_STRING('B', NULL, &opts.new_branch_force, "branch", @@ -699,7 +699,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 2), OPT_SET_INT('3', "theirs", &opts.writeout_stage, "checkout their version for unmerged files", 3), - OPT_BOOLEAN('f', "force", &opts.force, "force checkout (throw away local modifications)"), + OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"), OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"), OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), @@ -784,9 +784,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * between A and B, A...B names that merge base. * * With no paths, if <something> is _not_ a commit, no -t nor -b - * was given, and there is a tracking branch whose name is + * was given, and there is a remote-tracking branch whose name is * <something> in one and only one remote, then this is a short-hand - * to fork local <something> from that remote tracking branch. + * to fork local <something> from that remote-tracking branch. * * Otherwise <something> shall not be ambiguous. * - If it's *only* a reference, treat it like case (1). diff --git a/builtin/clean.c b/builtin/clean.c index fb24030..4a312ab 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -48,9 +48,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) const char *qname; char *seen = NULL; struct option options[] = { - OPT__QUIET(&quiet), - OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', "force", &force, "force"), + OPT__QUIET(&quiet, "do not print names of files removed"), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__FORCE(&force, "force"), OPT_BOOLEAN('d', NULL, &remove_directories, "remove whole directories"), { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern", diff --git a/builtin/clone.c b/builtin/clone.c index 19ed640..61e0989 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -66,6 +66,8 @@ static struct option builtin_clone_options[] = { "setup as shared repository"), OPT_BOOLEAN(0, "recursive", &option_recursive, "initialize submodules in the clone"), + OPT_BOOLEAN(0, "recurse_submodules", &option_recursive, + "initialize submodules in the clone"), OPT_STRING(0, "template", &option_template, "path", "path the template repository"), OPT_STRING(0, "reference", &option_reference, "repo", diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index e065739..d083795 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -56,10 +56,12 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) if (strbuf_read(&buffer, 0, 0) < 0) die_errno("git commit-tree: failed to read"); - if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) { - printf("%s\n", sha1_to_hex(commit_sha1)); - return 0; - } - else + if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) { + strbuf_release(&buffer); return 1; + } + + printf("%s\n", sha1_to_hex(commit_sha1)); + strbuf_release(&buffer); + return 0; } diff --git a/builtin/commit.c b/builtin/commit.c index 66fdd22..c045c9e 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -69,6 +69,7 @@ static enum { static const char *logfile, *force_author; static const char *template_file; static char *edit_message, *use_message; +static char *fixup_message, *squash_message; static char *author_name, *author_email, *author_date; static int all, edit_flag, also, interactive, only, amend, signoff; static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; @@ -114,8 +115,8 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset) } static struct option builtin_commit_options[] = { - OPT__QUIET(&quiet), - OPT__VERBOSE(&verbose), + OPT__QUIET(&quiet, "suppress summary after successful commit"), + OPT__VERBOSE(&verbose, "show diff in commit message template"), OPT_GROUP("Commit message options"), OPT_FILENAME('F', "file", &logfile, "read log from file"), @@ -124,6 +125,8 @@ static struct option builtin_commit_options[] = { OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"), OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), + OPT_STRING(0, "fixup", &fixup_message, "COMMIT", "use autosquash formatted message to fixup specified commit"), + OPT_STRING(0, "squash", &squash_message, "COMMIT", "use autosquash formatted message to squash specified commit"), OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_FILENAME('t', "template", &template_file, "use specified template file"), @@ -565,6 +568,25 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (!no_verify && run_hook(index_file, "pre-commit", NULL)) return 0; + if (squash_message) { + /* + * Insert the proper subject line before other commit + * message options add their content. + */ + if (use_message && !strcmp(use_message, squash_message)) + strbuf_addstr(&sb, "squash! "); + else { + struct pretty_print_context ctx = {0}; + struct commit *c; + c = lookup_commit_reference_by_name(squash_message); + if (!c) + die("could not lookup commit %s", squash_message); + ctx.output_encoding = get_commit_output_encoding(); + format_commit_message(c, "squash! %s\n\n", &sb, + &ctx); + } + } + if (message.len) { strbuf_addbuf(&sb, &message); hook_arg1 = "message"; @@ -586,6 +608,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); hook_arg1 = "commit"; hook_arg2 = use_message; + } else if (fixup_message) { + struct pretty_print_context ctx = {0}; + struct commit *commit; + commit = lookup_commit_reference_by_name(fixup_message); + if (!commit) + die("could not lookup commit %s", fixup_message); + ctx.output_encoding = get_commit_output_encoding(); + format_commit_message(commit, "fixup! %s\n\n", + &sb, &ctx); + hook_arg1 = "message"; } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) die_errno("could not read MERGE_MSG"); @@ -607,6 +639,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix, else if (in_merge) hook_arg1 = "merge"; + if (squash_message) { + /* + * If squash_commit was used for the commit subject, + * then we're possibly hijacking other commit log options. + * Reset the hook args to tell the real story. + */ + hook_arg1 = "message"; + hook_arg2 = ""; + } + fp = fopen(git_path(commit_editmsg), "w"); if (fp == NULL) die_errno("could not open '%s'", git_path(commit_editmsg)); @@ -863,7 +905,7 @@ static int parse_and_validate_options(int argc, const char *argv[], if (force_author && renew_authorship) die("Using both --reset-author and --author does not make sense"); - if (logfile || message.len || use_message) + if (logfile || message.len || use_message || fixup_message) use_editor = 0; if (edit_flag) use_editor = 1; @@ -878,48 +920,35 @@ static int parse_and_validate_options(int argc, const char *argv[], die("You have nothing to amend."); if (amend && in_merge) die("You are in the middle of a merge -- cannot amend."); - + if (fixup_message && squash_message) + die("Options --squash and --fixup cannot be used together"); if (use_message) f++; if (edit_message) f++; + if (fixup_message) + f++; if (logfile) f++; if (f > 1) - die("Only one of -c/-C/-F can be used."); + die("Only one of -c/-C/-F/--fixup can be used."); if (message.len && f > 0) - die("Option -m cannot be combined with -c/-C/-F."); + die("Option -m cannot be combined with -c/-C/-F/--fixup."); if (edit_message) use_message = edit_message; - if (amend && !use_message) + if (amend && !use_message && !fixup_message) use_message = "HEAD"; if (!use_message && renew_authorship) die("--reset-author can be used only with -C, -c or --amend."); if (use_message) { - unsigned char sha1[20]; - static char utf8[] = "UTF-8"; const char *out_enc; - char *enc, *end; struct commit *commit; - if (get_sha1(use_message, sha1)) + commit = lookup_commit_reference_by_name(use_message); + if (!commit) die("could not lookup commit %s", use_message); - commit = lookup_commit_reference(sha1); - if (!commit || parse_commit(commit)) - die("could not parse commit %s", use_message); - - enc = strstr(commit->buffer, "\nencoding"); - if (enc) { - end = strchr(enc + 10, '\n'); - enc = xstrndup(enc + 10, end - (enc + 10)); - } else { - enc = utf8; - } - out_enc = git_commit_encoding ? git_commit_encoding : utf8; - - if (strcmp(out_enc, enc)) - use_message_buffer = - reencode_string(commit->buffer, out_enc, enc); + out_enc = get_commit_output_encoding(); + use_message_buffer = logmsg_reencode(commit, out_enc); /* * If we failed to reencode the buffer, just copy it @@ -929,8 +958,6 @@ static int parse_and_validate_options(int argc, const char *argv[], */ if (use_message_buffer == NULL) use_message_buffer = xstrdup(commit->buffer); - if (enc != utf8) - free(enc); } if (!!also + !!only + !!all + !!interactive > 1) @@ -1048,7 +1075,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) int fd; unsigned char sha1[20]; static struct option builtin_status_options[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_SET_INT('s', "short", &status_format, "show status concisely", STATUS_FORMAT_SHORT), OPT_BOOLEAN('b', "branch", &status_show_branch, @@ -1070,6 +1097,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) OPT_END(), }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_status_usage, builtin_status_options); + if (null_termination && status_format == STATUS_FORMAT_LONG) status_format = STATUS_FORMAT_PORCELAIN; @@ -1255,6 +1285,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) int allow_fast_forward = 1; struct wt_status s; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_commit_usage, builtin_commit_options); + wt_status_prepare(&s); git_config(git_commit_config, &s); in_merge = file_exists(git_path("MERGE_HEAD")); diff --git a/builtin/count-objects.c b/builtin/count-objects.c index 2bdd8eb..c37cb98 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -79,7 +79,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; off_t loose_size = 0; struct option opts[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_END(), }; diff --git a/builtin/diff.c b/builtin/diff.c index a43d326..945e758 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -22,7 +22,7 @@ struct blobinfo { }; static const char builtin_diff_usage[] = -"git diff <options> <rev>{0,2} -- <path>*"; +"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]"; static void stuff_change(struct diff_options *opt, unsigned old_mode, unsigned new_mode, diff --git a/builtin/fetch.c b/builtin/fetch.c index d35f000..6bcce55 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -43,8 +43,7 @@ static struct option builtin_fetch_options[] = { "append to .git/FETCH_HEAD instead of overwriting"), OPT_STRING(0, "upload-pack", &upload_pack, "PATH", "path to upload pack on remote end"), - OPT_BOOLEAN('f', "force", &force, - "force overwrite of local branch"), + OPT__FORCE(&force, "force overwrite of local branch"), OPT_BOOLEAN('m', "multiple", &multiple, "fetch from multiple remotes"), OPT_SET_INT('t', "tags", &tags, @@ -52,7 +51,7 @@ static struct option builtin_fetch_options[] = { OPT_SET_INT('n', NULL, &tags, "do not fetch all tags (--no-tags)", TAGS_UNSET), OPT_BOOLEAN('p', "prune", &prune, - "prune tracking branches no longer on remote"), + "prune remote-tracking branches no longer on remote"), OPT_BOOLEAN(0, "dry-run", &dry_run, "dry run"), OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), @@ -98,7 +97,7 @@ static void add_merge_config(struct ref **head, continue; /* - * Not fetched to a tracking branch? We need to fetch + * Not fetched to a remote-tracking branch? We need to fetch * it anyway to allow this branch's "branch.$name.merge" * to be honored by 'git pull', but we do not have to * fail if branch.$name.merge is misconfigured to point @@ -359,7 +358,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, what = rm->name + 10; } else if (!prefixcmp(rm->name, "refs/remotes/")) { - kind = "remote branch"; + kind = "remote-tracking branch"; what = rm->name + 13; } else { diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index e7e12ee..5189b16 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -7,21 +7,22 @@ #include "string-list.h" static const char * const fmt_merge_msg_usage[] = { - "git fmt-merge-msg [-m <message>] [--log|--no-log] [--file <file>]", + "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]", NULL }; -static int merge_summary; +static int shortlog_len; static int fmt_merge_msg_config(const char *key, const char *value, void *cb) { - static int found_merge_log = 0; - if (!strcmp("merge.log", key)) { - found_merge_log = 1; - merge_summary = git_config_bool(key, value); + if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) { + int is_bool; + shortlog_len = git_config_bool_or_int(key, value, &is_bool); + if (!is_bool && shortlog_len < 0) + return error("%s: negative length %s", key, value); + if (is_bool && shortlog_len) + shortlog_len = DEFAULT_MERGE_LOG_LEN; } - if (!found_merge_log && !strcmp("merge.summary", key)) - merge_summary = git_config_bool(key, value); return 0; } @@ -99,8 +100,8 @@ static int handle_line(char *line) origin = line; string_list_append(&src_data->tag, origin + 4); src_data->head_status |= 2; - } else if (!prefixcmp(line, "remote branch ")) { - origin = line + 14; + } else if (!prefixcmp(line, "remote-tracking branch ")) { + origin = line + strlen("remote-tracking branch "); string_list_append(&src_data->r_branch, origin); src_data->head_status |= 2; } else { @@ -232,7 +233,7 @@ static void do_fmt_merge_msg_title(struct strbuf *out, if (src_data->r_branch.nr) { strbuf_addstr(out, subsep); subsep = ", "; - print_joined("remote branch ", "remote branches ", + print_joined("remote-tracking branch ", "remote-tracking branches ", &src_data->r_branch, out); } if (src_data->tag.nr) { @@ -255,9 +256,9 @@ static void do_fmt_merge_msg_title(struct strbuf *out, strbuf_addf(out, " into %s\n", current_branch); } -static int do_fmt_merge_msg(int merge_title, int merge_summary, - struct strbuf *in, struct strbuf *out) { - int limit = 20, i = 0, pos = 0; +static int do_fmt_merge_msg(int merge_title, struct strbuf *in, + struct strbuf *out, int shortlog_len) { + int i = 0, pos = 0; unsigned char head_sha1[20]; const char *current_branch; @@ -288,7 +289,7 @@ static int do_fmt_merge_msg(int merge_title, int merge_summary, if (merge_title) do_fmt_merge_msg_title(out, current_branch); - if (merge_summary) { + if (shortlog_len) { struct commit *head; struct rev_info rev; @@ -303,17 +304,14 @@ static int do_fmt_merge_msg(int merge_title, int merge_summary, for (i = 0; i < origins.nr; i++) shortlog(origins.items[i].string, origins.items[i].util, - head, &rev, limit, out); + head, &rev, shortlog_len, out); } return 0; } -int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { - return do_fmt_merge_msg(1, merge_summary, in, out); -} - -int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out) { - return do_fmt_merge_msg(0, 1, in, out); +int fmt_merge_msg(struct strbuf *in, struct strbuf *out, + int merge_title, int shortlog_len) { + return do_fmt_merge_msg(merge_title, in, out, shortlog_len); } int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) @@ -321,10 +319,13 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) const char *inpath = NULL; const char *message = NULL; struct option options[] = { - OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"), - { OPTION_BOOLEAN, 0, "summary", &merge_summary, NULL, + { OPTION_INTEGER, 0, "log", &shortlog_len, "n", + "populate log with at most <n> entries from shortlog", + PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, + { OPTION_INTEGER, 0, "summary", &shortlog_len, "n", "alias for --log (deprecated)", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL, + DEFAULT_MERGE_LOG_LEN }, OPT_STRING('m', "message", &message, "text", "use <text> as start of message"), OPT_FILENAME('F', "file", &inpath, "file to read from"), @@ -340,12 +341,14 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) 0); if (argc > 0) usage_with_options(fmt_merge_msg_usage, options); - if (message && !merge_summary) { + if (message && !shortlog_len) { char nl = '\n'; write_in_full(STDOUT_FILENO, message, strlen(message)); write_in_full(STDOUT_FILENO, &nl, 1); return 0; } + if (shortlog_len < 0) + die("Negative --log=%d", shortlog_len); if (inpath && strcmp(inpath, "-")) { in = fopen(inpath, "r"); @@ -355,12 +358,13 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) if (strbuf_read(&input, fileno(in), 0) < 0) die_errno("could not read input file"); - if (message) { + + if (message) strbuf_addstr(&output, message); - ret = fmt_merge_msg_shortlog(&input, &output); - } else { - ret = fmt_merge_msg(merge_summary, &input, &output); - } + ret = fmt_merge_msg(&input, &output, + message ? 0 : 1, + shortlog_len); + if (ret) return ret; write_in_full(STDOUT_FILENO, output.buf, output.len); diff --git a/builtin/fsck.c b/builtin/fsck.c index 0929c7f..6d5ebca 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -572,7 +572,7 @@ static char const * const fsck_usage[] = { }; static struct option fsck_opts[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"), OPT_BOOLEAN(0, "tags", &show_tags, "report tags"), OPT_BOOLEAN(0, "root", &show_root, "report root nodes"), diff --git a/builtin/gc.c b/builtin/gc.c index c304638..1a80702 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -180,7 +180,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) char buf[80]; struct option builtin_gc_options[] = { - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, "suppress progress reporting"), { OPTION_STRING, 0, "prune", &prune_expire, "date", "prune unreferenced objects", PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, @@ -189,6 +189,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix) OPT_END() }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_gc_usage, builtin_gc_options); + git_config(gc_config, NULL); if (pack_refs < 0) diff --git a/builtin/grep.c b/builtin/grep.c index 3d5f6ac..fdf7131 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -17,11 +17,7 @@ #include "grep.h" #include "quote.h" #include "dir.h" - -#ifndef NO_PTHREADS -#include <pthread.h> #include "thread-utils.h" -#endif static char const * const grep_usage[] = { "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]", @@ -915,8 +911,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, ')', NULL, &opt, NULL, "", PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, close_callback }, - OPT_BOOLEAN('q', "quiet", &opt.status_only, - "indicate hit with exit status without output"), + OPT__QUIET(&opt.status_only, + "indicate hit with exit status without output"), OPT_BOOLEAN(0, "all-match", &opt.all_match, "show only matches from files that match all patterns"), OPT_GROUP(""), diff --git a/builtin/log.c b/builtin/log.c index eaa1ee0..d8c6c28 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -329,8 +329,7 @@ static void show_tagger(char *buf, int len, struct rev_info *rev) struct strbuf out = STRBUF_INIT; pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode, - git_log_output_encoding ? - git_log_output_encoding: git_commit_encoding); + get_log_output_encoding()); printf("%s", out.buf); strbuf_release(&out); } @@ -1056,8 +1055,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.commit_format = CMIT_FMT_EMAIL; rev.verbose_header = 1; rev.diff = 1; - rev.combine_merges = 0; - rev.ignore_merges = 1; + rev.no_merges = 1; DIFF_OPT_SET(&rev.diffopt, RECURSIVE); rev.subject_prefix = fmt_patch_subject_prefix; memset(&s_r_opt, 0, sizeof(s_r_opt)); @@ -1160,6 +1158,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!use_stdout) output_directory = set_outdir(prefix, output_directory); + else + setup_pager(); if (output_directory) { if (use_stdout) @@ -1228,10 +1228,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) continue; } - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids)) continue; @@ -1370,7 +1366,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) struct option options[] = { OPT__ABBREV(&abbrev), - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_END() }; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 6a307ab..fb2d5f4 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -530,6 +530,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) OPT_END() }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(ls_files_usage, builtin_ls_files_options); + memset(&dir, 0, sizeof(dir)); prefix = cmd_prefix; if (prefix) diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index 2320d98..71e6262 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -1032,7 +1032,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) */ git_config(git_mailinfo_config, NULL); - def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8"); + def_charset = get_commit_output_encoding(); metainfo_charset = def_charset; while (1 < argc && argv[1][0] == '-') { diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 6c4afb5..237abd3 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -40,7 +40,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) XDL_MERGE_FAVOR_UNION), OPT_INTEGER(0, "marker-size", &xmp.marker_size, "for conflicts, use this marker size"), - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, "do not warn about conflicts"), OPT_CALLBACK('L', NULL, names, "name", "set labels for file1/orig_file/file2", &label_cb), OPT_END(), diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index 78b9db7..c33091b 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -2,6 +2,7 @@ #include "commit.h" #include "tag.h" #include "merge-recursive.h" +#include "xdiff-interface.h" static const char builtin_merge_recursive_usage[] = "git %s <base>... -- <head> <remote> ..."; @@ -40,19 +41,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (!prefixcmp(arg, "--")) { if (!arg[2]) break; - if (!strcmp(arg+2, "ours")) - o.recursive_variant = MERGE_RECURSIVE_OURS; - else if (!strcmp(arg+2, "theirs")) - o.recursive_variant = MERGE_RECURSIVE_THEIRS; - else if (!strcmp(arg+2, "subtree")) - o.subtree_shift = ""; - else if (!prefixcmp(arg+2, "subtree=")) - o.subtree_shift = arg + 10; - else if (!strcmp(arg+2, "renormalize")) - o.renormalize = 1; - else if (!strcmp(arg+2, "no-renormalize")) - o.renormalize = 0; - else + if (parse_merge_opt(&o, arg + 2)) die("Unknown option %s", arg); continue; } diff --git a/builtin/merge.c b/builtin/merge.c index d0bd651..42fff38 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -42,7 +42,7 @@ static const char * const builtin_merge_usage[] = { NULL }; -static int show_diffstat = 1, option_log, squash; +static int show_diffstat = 1, shortlog_len, squash; static int option_commit = 1, allow_fast_forward = 1; static int fast_forward_only; static int allow_trivial = 1, have_message; @@ -57,6 +57,7 @@ static const char *branch; static int option_renormalize; static int verbosity; static int allow_rerere_auto; +static int abort_current_merge; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -177,8 +178,9 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "stat", &show_diffstat, "show a diffstat at the end of the merge"), OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"), - OPT_BOOLEAN(0, "log", &option_log, - "add list of one-line log to merge commit message"), + { OPTION_INTEGER, 0, "log", &shortlog_len, "n", + "add (at most <n>) entries from shortlog to merge commit message", + PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, OPT_BOOLEAN(0, "squash", &squash, "create a single commit instead of doing a merge"), OPT_BOOLEAN(0, "commit", &option_commit, @@ -196,6 +198,8 @@ static struct option builtin_merge_options[] = { "message to be used for the merge commit (if any)", option_parse_message), OPT__VERBOSITY(&verbosity), + OPT_BOOLEAN(0, "abort", &abort_current_merge, + "abort the current in-progress merge"), OPT_END() }; @@ -420,7 +424,7 @@ static void merge_name(const char *remote, struct strbuf *msg) goto cleanup; } if (!prefixcmp(found_ref, "refs/remotes/")) { - strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n", + strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n", sha1_to_hex(branch_head), remote); goto cleanup; } @@ -522,10 +526,17 @@ static int git_merge_config(const char *k, const char *v, void *cb) return git_config_string(&pull_twohead, k, v); else if (!strcmp(k, "pull.octopus")) return git_config_string(&pull_octopus, k, v); - else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) - option_log = git_config_bool(k, v); else if (!strcmp(k, "merge.renormalize")) option_renormalize = git_config_bool(k, v); + else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) { + int is_bool; + shortlog_len = git_config_bool_or_int(k, v, &is_bool); + if (!is_bool && shortlog_len < 0) + return error("%s: negative length %s", k, v); + if (is_bool && shortlog_len) + shortlog_len = DEFAULT_MERGE_LOG_LEN; + return 0; + } return git_diff_ui_config(k, v, cb); } @@ -649,25 +660,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, o.renormalize = option_renormalize; - /* - * NEEDSWORK: merge with table in builtin/merge-recursive - */ - for (x = 0; x < xopts_nr; x++) { - if (!strcmp(xopts[x], "ours")) - o.recursive_variant = MERGE_RECURSIVE_OURS; - else if (!strcmp(xopts[x], "theirs")) - o.recursive_variant = MERGE_RECURSIVE_THEIRS; - else if (!strcmp(xopts[x], "subtree")) - o.subtree_shift = ""; - else if (!prefixcmp(xopts[x], "subtree=")) - o.subtree_shift = xopts[x]+8; - else if (!strcmp(xopts[x], "renormalize")) - o.renormalize = 1; - else if (!strcmp(xopts[x], "no-renormalize")) - o.renormalize = 0; - else + for (x = 0; x < xopts_nr; x++) + if (parse_merge_opt(&o, xopts[x])) die("Unknown option for merge-recursive: -X%s", xopts[x]); - } o.branch1 = head_arg; o.branch2 = remoteheads->item->util; @@ -927,22 +922,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; - if (read_cache_unmerged()) { - die_resolve_conflict("merge"); - } - if (file_exists(git_path("MERGE_HEAD"))) { - /* - * There is no unmerged entry, don't advise 'git - * add/rm <file>', just 'git commit'. - */ - if (advice_resolve_conflict) - die("You have not concluded your merge (MERGE_HEAD exists).\n" - "Please, commit your changes before you can merge."); - else - die("You have not concluded your merge (MERGE_HEAD exists)."); - } + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_merge_usage, builtin_merge_options); - resolve_undo_clear(); /* * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. @@ -961,6 +943,34 @@ int cmd_merge(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_merge_options, builtin_merge_usage, 0); + + if (abort_current_merge) { + int nargc = 2; + const char *nargv[] = {"reset", "--merge", NULL}; + + if (!file_exists(git_path("MERGE_HEAD"))) + die("There is no merge to abort (MERGE_HEAD missing)."); + + /* Invoke 'git reset --merge' */ + return cmd_reset(nargc, nargv, prefix); + } + + if (read_cache_unmerged()) + die_resolve_conflict("merge"); + + if (file_exists(git_path("MERGE_HEAD"))) { + /* + * There is no unmerged entry, don't advise 'git + * add/rm <file>', just 'git commit'. + */ + if (advice_resolve_conflict) + die("You have not concluded your merge (MERGE_HEAD exists).\n" + "Please, commit your changes before you can merge."); + else + die("You have not concluded your merge (MERGE_HEAD exists)."); + } + resolve_undo_clear(); + if (verbosity < 0) show_diffstat = 0; @@ -1030,14 +1040,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (i = 0; i < argc; i++) merge_name(argv[i], &merge_names); - if (have_message && option_log) - fmt_merge_msg_shortlog(&merge_names, &merge_msg); - else if (!have_message) - fmt_merge_msg(option_log, &merge_names, &merge_msg); - - - if (!(have_message && !option_log) && merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len-1); + if (!have_message || shortlog_len) { + fmt_merge_msg(&merge_names, &merge_msg, !have_message, + shortlog_len); + if (merge_msg.len) + strbuf_setlen(&merge_msg, merge_msg.len - 1); + } } if (head_invalid || !argc) diff --git a/builtin/mv.c b/builtin/mv.c index cdbb094..93e8995 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -55,8 +55,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int i, newfd; int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; struct option builtin_mv_options[] = { - OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__FORCE(&force, "force move/rename even if target exists"), OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"), OPT_END(), }; diff --git a/builtin/notes.c b/builtin/notes.c index 6d07aac..4d5556e 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -17,6 +17,7 @@ #include "run-command.h" #include "parse-options.h" #include "string-list.h" +#include "notes-merge.h" static const char * const git_notes_usage[] = { "git notes [--ref <notes_ref>] [list [<object>]]", @@ -25,8 +26,12 @@ static const char * const git_notes_usage[] = { "git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]", "git notes [--ref <notes_ref>] edit [<object>]", "git notes [--ref <notes_ref>] show [<object>]", + "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>", + "git notes merge --commit [-v | -q]", + "git notes merge --abort [-v | -q]", "git notes [--ref <notes_ref>] remove [<object>]", "git notes [--ref <notes_ref>] prune [-n | -v]", + "git notes [--ref <notes_ref>] get-ref", NULL }; @@ -61,6 +66,13 @@ static const char * const git_notes_show_usage[] = { NULL }; +static const char * const git_notes_merge_usage[] = { + "git notes merge [<options>] <notes_ref>", + "git notes merge --commit [<options>]", + "git notes merge --abort [<options>]", + NULL +}; + static const char * const git_notes_remove_usage[] = { "git notes remove [<object>]", NULL @@ -71,6 +83,11 @@ static const char * const git_notes_prune_usage[] = { NULL }; +static const char * const git_notes_get_ref_usage[] = { + "git notes get-ref", + NULL +}; + static const char note_template[] = "\n" "#\n" @@ -83,6 +100,16 @@ struct msg_arg { struct strbuf buf; }; +static void expand_notes_ref(struct strbuf *sb) +{ + if (!prefixcmp(sb->buf, "refs/notes/")) + return; /* we're happy */ + else if (!prefixcmp(sb->buf, "notes/")) + strbuf_insert(sb, 0, "refs/", 5); + else + strbuf_insert(sb, 0, "refs/notes/", 11); +} + static int list_each_note(const unsigned char *object_sha1, const unsigned char *note_sha1, char *note_path, void *cb_data) @@ -271,18 +298,17 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset return parse_reuse_arg(opt, arg, unset); } -int commit_notes(struct notes_tree *t, const char *msg) +void commit_notes(struct notes_tree *t, const char *msg) { - struct commit_list *parent; - unsigned char tree_sha1[20], prev_commit[20], new_commit[20]; struct strbuf buf = STRBUF_INIT; + unsigned char commit_sha1[20]; if (!t) t = &default_notes_tree; if (!t->initialized || !t->ref || !*t->ref) die("Cannot commit uninitialized/unreferenced notes tree"); if (!t->dirty) - return 0; /* don't have to commit an unchanged tree */ + return; /* don't have to commit an unchanged tree */ /* Prepare commit message and reflog message */ strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */ @@ -290,27 +316,10 @@ int commit_notes(struct notes_tree *t, const char *msg) if (buf.buf[buf.len - 1] != '\n') strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */ - /* Convert notes tree to tree object */ - if (write_notes_tree(t, tree_sha1)) - die("Failed to write current notes tree to database"); - - /* Create new commit for the tree object */ - if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */ - parent = xmalloc(sizeof(*parent)); - parent->item = lookup_commit(prev_commit); - parent->next = NULL; - } else { - hashclr(prev_commit); - parent = NULL; - } - if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL)) - die("Failed to commit notes tree to database"); - - /* Update notes ref with new commit */ - update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR); + create_notes_commit(t, NULL, buf.buf + 7, commit_sha1); + update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR); strbuf_release(&buf); - return 0; } combine_notes_fn parse_combine_notes_fn(const char *v) @@ -321,6 +330,8 @@ combine_notes_fn parse_combine_notes_fn(const char *v) return combine_notes_ignore; else if (!strcasecmp(v, "concatenate")) return combine_notes_concatenate; + else if (!strcasecmp(v, "cat_sort_uniq")) + return combine_notes_cat_sort_uniq; else return NULL; } @@ -538,7 +549,7 @@ static int add(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT", "reuse specified note object", PARSE_OPT_NONEG, parse_reuse_arg}, - OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT__FORCE(&force, "replace existing notes"), OPT_END() }; @@ -573,8 +584,8 @@ static int add(int argc, const char **argv, const char *prefix) if (is_null_sha1(new_note)) remove_note(t, object); - else - add_note(t, object, new_note, combine_notes_overwrite); + else if (add_note(t, object, new_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", is_null_sha1(new_note) ? "removed" : "added", "add"); @@ -594,7 +605,7 @@ static int copy(int argc, const char **argv, const char *prefix) struct notes_tree *t; const char *rewrite_cmd = NULL; struct option options[] = { - OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT__FORCE(&force, "replace existing notes"), OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"), OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command", "load rewriting config for <command> (implies " @@ -653,7 +664,8 @@ static int copy(int argc, const char **argv, const char *prefix) goto out; } - add_note(t, object, from_note, combine_notes_overwrite); + if (add_note(t, object, from_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); commit_notes(t, "Notes added by 'git notes copy'"); out: free_notes(t); @@ -712,8 +724,8 @@ static int append_edit(int argc, const char **argv, const char *prefix) if (is_null_sha1(new_note)) remove_note(t, object); - else - add_note(t, object, new_note, combine_notes_overwrite); + else if (add_note(t, object, new_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", is_null_sha1(new_note) ? "removed" : "added", argv[0]); @@ -761,6 +773,180 @@ static int show(int argc, const char **argv, const char *prefix) return retval; } +static int merge_abort(struct notes_merge_options *o) +{ + int ret = 0; + + /* + * Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call + * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE. + */ + + if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0)) + ret += error("Failed to delete ref NOTES_MERGE_PARTIAL"); + if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF)) + ret += error("Failed to delete ref NOTES_MERGE_REF"); + if (notes_merge_abort(o)) + ret += error("Failed to remove 'git notes merge' worktree"); + return ret; +} + +static int merge_commit(struct notes_merge_options *o) +{ + struct strbuf msg = STRBUF_INIT; + unsigned char sha1[20], parent_sha1[20]; + struct notes_tree *t; + struct commit *partial; + struct pretty_print_context pretty_ctx; + + /* + * Read partial merge result from .git/NOTES_MERGE_PARTIAL, + * and target notes ref from .git/NOTES_MERGE_REF. + */ + + if (get_sha1("NOTES_MERGE_PARTIAL", sha1)) + die("Failed to read ref NOTES_MERGE_PARTIAL"); + else if (!(partial = lookup_commit_reference(sha1))) + die("Could not find commit from NOTES_MERGE_PARTIAL."); + else if (parse_commit(partial)) + die("Could not parse commit from NOTES_MERGE_PARTIAL."); + + if (partial->parents) + hashcpy(parent_sha1, partial->parents->item->object.sha1); + else + hashclr(parent_sha1); + + t = xcalloc(1, sizeof(struct notes_tree)); + init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); + + o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, 0); + if (!o->local_ref) + die("Failed to resolve NOTES_MERGE_REF"); + + if (notes_merge_commit(o, t, partial, sha1)) + die("Failed to finalize notes merge"); + + /* Reuse existing commit message in reflog message */ + memset(&pretty_ctx, 0, sizeof(pretty_ctx)); + format_commit_message(partial, "%s", &msg, &pretty_ctx); + strbuf_trim(&msg); + strbuf_insert(&msg, 0, "notes: ", 7); + update_ref(msg.buf, o->local_ref, sha1, + is_null_sha1(parent_sha1) ? NULL : parent_sha1, + 0, DIE_ON_ERR); + + free_notes(t); + strbuf_release(&msg); + return merge_abort(o); +} + +static int merge(int argc, const char **argv, const char *prefix) +{ + struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; + unsigned char result_sha1[20]; + struct notes_tree *t; + struct notes_merge_options o; + int do_merge = 0, do_commit = 0, do_abort = 0; + int verbosity = 0, result; + const char *strategy = NULL; + struct option options[] = { + OPT_GROUP("General options"), + OPT__VERBOSITY(&verbosity), + OPT_GROUP("Merge options"), + OPT_STRING('s', "strategy", &strategy, "strategy", + "resolve notes conflicts using the given strategy " + "(manual/ours/theirs/union/cat_sort_uniq)"), + OPT_GROUP("Committing unmerged notes"), + { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL, + "finalize notes merge by committing unmerged notes", + PARSE_OPT_NOARG | PARSE_OPT_NONEG }, + OPT_GROUP("Aborting notes merge resolution"), + { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL, + "abort notes merge", + PARSE_OPT_NOARG | PARSE_OPT_NONEG }, + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_notes_merge_usage, 0); + + if (strategy || do_commit + do_abort == 0) + do_merge = 1; + if (do_merge + do_commit + do_abort != 1) { + error("cannot mix --commit, --abort or -s/--strategy"); + usage_with_options(git_notes_merge_usage, options); + } + + if (do_merge && argc != 1) { + error("Must specify a notes ref to merge"); + usage_with_options(git_notes_merge_usage, options); + } else if (!do_merge && argc) { + error("too many parameters"); + usage_with_options(git_notes_merge_usage, options); + } + + init_notes_merge_options(&o); + o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT; + + if (do_abort) + return merge_abort(&o); + if (do_commit) + return merge_commit(&o); + + o.local_ref = default_notes_ref(); + strbuf_addstr(&remote_ref, argv[0]); + expand_notes_ref(&remote_ref); + o.remote_ref = remote_ref.buf; + + if (strategy) { + if (!strcmp(strategy, "manual")) + o.strategy = NOTES_MERGE_RESOLVE_MANUAL; + else if (!strcmp(strategy, "ours")) + o.strategy = NOTES_MERGE_RESOLVE_OURS; + else if (!strcmp(strategy, "theirs")) + o.strategy = NOTES_MERGE_RESOLVE_THEIRS; + else if (!strcmp(strategy, "union")) + o.strategy = NOTES_MERGE_RESOLVE_UNION; + else if (!strcmp(strategy, "cat_sort_uniq")) + o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ; + else { + error("Unknown -s/--strategy: %s", strategy); + usage_with_options(git_notes_merge_usage, options); + } + } + + t = init_notes_check("merge"); + + strbuf_addf(&msg, "notes: Merged notes from %s into %s", + remote_ref.buf, default_notes_ref()); + strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */ + + result = notes_merge(&o, t, result_sha1); + + if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ + /* Update default notes ref with new commit */ + update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, + 0, DIE_ON_ERR); + else { /* Merge has unresolved conflicts */ + /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ + update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, + 0, DIE_ON_ERR); + /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ + if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) + die("Failed to store link to current notes ref (%s)", + default_notes_ref()); + printf("Automatic notes merge failed. Fix conflicts in %s and " + "commit the result with 'git notes merge --commit', or " + "abort the merge with 'git notes merge --abort'.\n", + git_path(NOTES_MERGE_WORKTREE)); + } + + free_notes(t); + strbuf_release(&remote_ref); + strbuf_release(&msg); + return result < 0; /* return non-zero on conflicts */ +} + static int remove_cmd(int argc, const char **argv, const char *prefix) { struct option options[] = { @@ -804,9 +990,8 @@ static int prune(int argc, const char **argv, const char *prefix) struct notes_tree *t; int show_only = 0, verbose = 0; struct option options[] = { - OPT_BOOLEAN('n', "dry-run", &show_only, - "do not remove, show only"), - OPT_BOOLEAN('v', "verbose", &verbose, "report pruned notes"), + OPT__DRY_RUN(&show_only, "do not remove, show only"), + OPT__VERBOSE(&verbose, "report pruned notes"), OPT_END() }; @@ -828,6 +1013,21 @@ static int prune(int argc, const char **argv, const char *prefix) return 0; } +static int get_ref(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { OPT_END() }; + argc = parse_options(argc, argv, prefix, options, + git_notes_get_ref_usage, 0); + + if (argc) { + error("too many parameters"); + usage_with_options(git_notes_get_ref_usage, options); + } + + puts(default_notes_ref()); + return 0; +} + int cmd_notes(int argc, const char **argv, const char *prefix) { int result; @@ -844,13 +1044,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) if (override_notes_ref) { struct strbuf sb = STRBUF_INIT; - if (!prefixcmp(override_notes_ref, "refs/notes/")) - /* we're happy */; - else if (!prefixcmp(override_notes_ref, "notes/")) - strbuf_addstr(&sb, "refs/"); - else - strbuf_addstr(&sb, "refs/notes/"); strbuf_addstr(&sb, override_notes_ref); + expand_notes_ref(&sb); setenv("GIT_NOTES_REF", sb.buf, 1); strbuf_release(&sb); } @@ -865,10 +1060,14 @@ int cmd_notes(int argc, const char **argv, const char *prefix) result = append_edit(argc, argv, prefix); else if (!strcmp(argv[0], "show")) result = show(argc, argv, prefix); + else if (!strcmp(argv[0], "merge")) + result = merge(argc, argv, prefix); else if (!strcmp(argv[0], "remove")) result = remove_cmd(argc, argv, prefix); else if (!strcmp(argv[0], "prune")) result = prune(argc, argv, prefix); + else if (!strcmp(argv[0], "get-ref")) + result = get_ref(argc, argv, prefix); else { result = error("Unknown subcommand: %s", argv[0]); usage_with_options(git_notes_usage, options); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index f8eba53..b0503b2 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -16,11 +16,7 @@ #include "list-objects.h" #include "progress.h" #include "refs.h" - -#ifndef NO_PTHREADS -#include <pthread.h> #include "thread-utils.h" -#endif static const char pack_usage[] = "git pack-objects [ -q | --progress | --all-progress ]\n" @@ -1298,9 +1294,23 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, read_lock(); src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz); read_unlock(); - if (!src->data) + if (!src->data) { + if (src_entry->preferred_base) { + static int warned = 0; + if (!warned++) + warning("object %s cannot be read", + sha1_to_hex(src_entry->idx.sha1)); + /* + * Those objects are not included in the + * resulting pack. Be resilient and ignore + * them if they can't be read, in case the + * pack could be created nevertheless. + */ + return 0; + } die("object %s cannot be read", sha1_to_hex(src_entry->idx.sha1)); + } if (sz != src_size) die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(src_entry->idx.sha1), sz, src_size); @@ -1529,7 +1539,7 @@ static void try_to_free_from_threads(size_t size) read_unlock(); } -try_to_free_t old_try_to_free_routine; +static try_to_free_t old_try_to_free_routine; /* * The main thread waits on the condition that (at least) one of the workers diff --git a/builtin/prune.c b/builtin/prune.c index 99218ba..e65690b 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -125,9 +125,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; const struct option options[] = { - OPT_BOOLEAN('n', "dry-run", &show_only, - "do not remove, show only"), - OPT_BOOLEAN('v', "verbose", &verbose, "report pruned objects"), + OPT__DRY_RUN(&show_only, "do not remove, show only"), + OPT__VERBOSE(&verbose, "report pruned objects"), OPT_DATE(0, "expire", &expire, "expire objects older than <time>"), OPT_END() diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 9ad1e66..73c89ed 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -16,6 +16,7 @@ #include "resolve-undo.h" static int nr_trees; +static int read_empty; static struct tree *trees[MAX_UNPACK_TREES]; static int list_tree(unsigned char *sha1) @@ -32,7 +33,7 @@ static int list_tree(unsigned char *sha1) } static const char * const read_tree_usage[] = { - "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]", + "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])", NULL }; @@ -106,7 +107,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { OPTION_CALLBACK, 0, "index-output", NULL, "FILE", "write resulting index to <FILE>", PARSE_OPT_NONEG, index_output_cb }, - OPT__VERBOSE(&opts.verbose_update), + OPT_SET_INT(0, "empty", &read_empty, + "only empty the index", 1), + OPT__VERBOSE(&opts.verbose_update, "be verbose"), OPT_GROUP("Merging"), OPT_SET_INT('m', NULL, &opts.merge, "perform a merge in addition to a read", 1), @@ -166,6 +169,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("failed to unpack tree object %s", arg); stage++; } + if (nr_trees == 0 && !read_empty) + warning("read-tree: emptying the index with no arguments is deprecated; use --empty"); + else if (nr_trees > 0 && read_empty) + die("passing trees as arguments contradicts --empty"); + if (1 < opts.index_only + opts.update) die("-u and -i at the same time makes no sense"); if ((opts.update||opts.index_only) && !opts.merge) diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c new file mode 100644 index 0000000..1f77317 --- /dev/null +++ b/builtin/remote-ext.c @@ -0,0 +1,246 @@ +#include "git-compat-util.h" +#include "transport.h" +#include "run-command.h" + +/* + * URL syntax: + * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments. + * Special characters: + * '% ': Literal space in argument. + * '%%': Literal percent sign. + * '%S': Name of service (git-upload-pack/git-upload-archive/ + * git-receive-pack. + * '%s': Same as \s, but with possible git- prefix stripped. + * '%G': Only allowed as first 'character' of argument. Do not pass this + * Argument to command, instead send this as name of repository + * in in-line git://-style request (also activates sending this + * style of request). + * '%V': Only allowed as first 'character' of argument. Used in + * conjunction with '%G': Do not pass this argument to command, + * instead send this as vhost in git://-style request (note: does + * not activate sending git:// style request). + */ + +static char *git_req; +static char *git_req_vhost; + +static char *strip_escapes(const char *str, const char *service, + const char **next) +{ + size_t rpos = 0; + int escape = 0; + char special = 0; + size_t pslen = 0; + size_t pSlen = 0; + size_t psoff = 0; + struct strbuf ret = STRBUF_INIT; + + /* Calculate prefix length for \s and lengths for \s and \S */ + if (!strncmp(service, "git-", 4)) + psoff = 4; + pSlen = strlen(service); + pslen = pSlen - psoff; + + /* Pass the service to command. */ + setenv("GIT_EXT_SERVICE", service, 1); + setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1); + + /* Scan the length of argument. */ + while (str[rpos] && (escape || str[rpos] != ' ')) { + if (escape) { + switch (str[rpos]) { + case ' ': + case '%': + case 's': + case 'S': + break; + case 'G': + case 'V': + special = str[rpos]; + if (rpos == 1) + break; + /* Fall-through to error. */ + default: + die("Bad remote-ext placeholder '%%%c'.", + str[rpos]); + } + escape = 0; + } else + escape = (str[rpos] == '%'); + rpos++; + } + if (escape && !str[rpos]) + die("remote-ext command has incomplete placeholder"); + *next = str + rpos; + if (**next == ' ') + ++*next; /* Skip over space */ + + /* + * Do the actual placeholder substitution. The string will be short + * enough not to overflow integers. + */ + rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */ + escape = 0; + while (str[rpos] && (escape || str[rpos] != ' ')) { + if (escape) { + switch (str[rpos]) { + case ' ': + case '%': + strbuf_addch(&ret, str[rpos]); + break; + case 's': + strbuf_addstr(&ret, service + psoff); + break; + case 'S': + strbuf_addstr(&ret, service); + break; + } + escape = 0; + } else + switch (str[rpos]) { + case '%': + escape = 1; + break; + default: + strbuf_addch(&ret, str[rpos]); + break; + } + rpos++; + } + switch (special) { + case 'G': + git_req = strbuf_detach(&ret, NULL); + return NULL; + case 'V': + git_req_vhost = strbuf_detach(&ret, NULL); + return NULL; + default: + return strbuf_detach(&ret, NULL); + } +} + +/* Should be enough... */ +#define MAXARGUMENTS 256 + +static const char **parse_argv(const char *arg, const char *service) +{ + int arguments = 0; + int i; + const char **ret; + char *temparray[MAXARGUMENTS + 1]; + + while (*arg) { + char *expanded; + if (arguments == MAXARGUMENTS) + die("remote-ext command has too many arguments"); + expanded = strip_escapes(arg, service, &arg); + if (expanded) + temparray[arguments++] = expanded; + } + + ret = xmalloc((arguments + 1) * sizeof(char *)); + for (i = 0; i < arguments; i++) + ret[i] = temparray[i]; + ret[arguments] = NULL; + return ret; +} + +static void send_git_request(int stdin_fd, const char *serv, const char *repo, + const char *vhost) +{ + size_t bufferspace; + size_t wpos = 0; + char *buffer; + + /* + * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and + * 6 bytes extra (xxxx \0) if there is no vhost. + */ + if (vhost) + bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12; + else + bufferspace = strlen(serv) + strlen(repo) + 6; + + if (bufferspace > 0xFFFF) + die("Request too large to send"); + buffer = xmalloc(bufferspace); + + /* Make the packet. */ + wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace, + serv, repo, 0); + + /* Add vhost if any. */ + if (vhost) + sprintf(buffer + wpos, "host=%s%c", vhost, 0); + + /* Send the request */ + if (write_in_full(stdin_fd, buffer, bufferspace) < 0) + die_errno("Failed to send request"); + + free(buffer); +} + +static int run_child(const char *arg, const char *service) +{ + int r; + struct child_process child; + + memset(&child, 0, sizeof(child)); + child.in = -1; + child.out = -1; + child.err = 0; + child.argv = parse_argv(arg, service); + + if (start_command(&child) < 0) + die("Can't run specified command"); + + if (git_req) + send_git_request(child.in, service, git_req, git_req_vhost); + + r = bidirectional_transfer_loop(child.out, child.in); + if (!r) + r = finish_command(&child); + else + finish_command(&child); + return r; +} + +#define MAXCOMMAND 4096 + +static int command_loop(const char *child) +{ + char buffer[MAXCOMMAND]; + + while (1) { + size_t length; + if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { + if (ferror(stdin)) + die("Comammand input error"); + exit(0); + } + /* Strip end of line characters. */ + length = strlen(buffer); + while (isspace((unsigned char)buffer[length - 1])) + buffer[--length] = 0; + + if (!strcmp(buffer, "capabilities")) { + printf("*connect\n\n"); + fflush(stdout); + } else if (!strncmp(buffer, "connect ", 8)) { + printf("\n"); + fflush(stdout); + return run_child(child, buffer + 8); + } else { + fprintf(stderr, "Bad command"); + return 1; + } + } +} + +int cmd_remote_ext(int argc, const char **argv, const char *prefix) +{ + if (argc != 3) + die("Expected two arguments"); + + return command_loop(argv[2]); +} diff --git a/builtin/remote-fd.c b/builtin/remote-fd.c new file mode 100644 index 0000000..1f2467b --- /dev/null +++ b/builtin/remote-fd.c @@ -0,0 +1,79 @@ +#include "git-compat-util.h" +#include "transport.h" + +/* + * URL syntax: + * 'fd::<inoutfd>[/<anything>]' Read/write socket pair + * <inoutfd>. + * 'fd::<infd>,<outfd>[/<anything>]' Read pipe <infd> and write + * pipe <outfd>. + * [foo] indicates 'foo' is optional. <anything> is any string. + * + * The data output to <outfd>/<inoutfd> should be passed unmolested to + * git-receive-pack/git-upload-pack/git-upload-archive and output of + * git-receive-pack/git-upload-pack/git-upload-archive should be passed + * unmolested to <infd>/<inoutfd>. + * + */ + +#define MAXCOMMAND 4096 + +static void command_loop(int input_fd, int output_fd) +{ + char buffer[MAXCOMMAND]; + + while (1) { + size_t i; + if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { + if (ferror(stdin)) + die("Input error"); + return; + } + /* Strip end of line characters. */ + i = strlen(buffer); + while (i > 0 && isspace(buffer[i - 1])) + buffer[--i] = 0; + + if (!strcmp(buffer, "capabilities")) { + printf("*connect\n\n"); + fflush(stdout); + } else if (!strncmp(buffer, "connect ", 8)) { + printf("\n"); + fflush(stdout); + if (bidirectional_transfer_loop(input_fd, + output_fd)) + die("Copying data between file descriptors failed"); + return; + } else { + die("Bad command: %s", buffer); + } + } +} + +int cmd_remote_fd(int argc, const char **argv, const char *prefix) +{ + int input_fd = -1; + int output_fd = -1; + char *end; + + if (argc != 3) + die("Expected two arguments"); + + input_fd = (int)strtoul(argv[2], &end, 10); + + if ((end == argv[2]) || (*end != ',' && *end != '/' && *end)) + die("Bad URL syntax"); + + if (*end == '/' || !*end) { + output_fd = input_fd; + } else { + char *end2; + output_fd = (int)strtoul(end + 1, &end2, 10); + + if ((end2 == end + 1) || (*end2 != '/' && *end2)) + die("Bad URL syntax"); + } + + command_loop(input_fd, output_fd); + return 0; +} diff --git a/builtin/remote.c b/builtin/remote.c index e9a6e09..cb26080 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -507,7 +507,7 @@ static int add_branch_for_removal(const char *refname, return 0; } - /* don't delete non-remote refs */ + /* don't delete non-remote-tracking refs */ if (prefixcmp(refname, "refs/remotes")) { /* advise user how to delete local branches */ if (!prefixcmp(refname, "refs/heads/")) @@ -791,9 +791,9 @@ static int rm(int argc, const char **argv) if (skipped.nr) { fprintf(stderr, skipped.nr == 1 ? - "Note: A non-remote branch was not removed; " + "Note: A branch outside the refs/remotes/ hierarchy was not removed;\n" "to delete it, use:\n" : - "Note: Non-remote branches were not removed; " + "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n" "to delete them, use:\n"); for (i = 0; i < skipped.nr; i++) fprintf(stderr, " git branch -d %s\n", @@ -1200,7 +1200,7 @@ static int prune(int argc, const char **argv) { int dry_run = 0, result = 0; struct option options[] = { - OPT__DRY_RUN(&dry_run), + OPT__DRY_RUN(&dry_run, "dry run"), OPT_END() }; @@ -1512,7 +1512,7 @@ static int show_all(void) int cmd_remote(int argc, const char **argv, const char *prefix) { struct option options[] = { - OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"), + OPT__VERBOSE(&verbose, "be verbose; must be placed before a subcommand"), OPT_END() }; int result; diff --git a/builtin/reset.c b/builtin/reset.c index 0037be4..5de2bce 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -243,7 +243,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) struct commit *commit; char *reflog_action, msg[1024]; const struct option options[] = { - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, "be quiet, only report errors"), OPT_SET_INT(0, "mixed", &reset_type, "reset HEAD and index", MIXED), OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT), diff --git a/builtin/revert.c b/builtin/revert.c index 57b51e4..bb6e9e8 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -547,6 +547,21 @@ static void prepare_revs(struct rev_info *revs) die("empty commit set passed"); } +static void read_and_refresh_cache(const char *me) +{ + static struct lock_file index_lock; + int index_fd = hold_locked_index(&index_lock, 0); + if (read_index_preload(&the_index, NULL) < 0) + die("git %s: failed to read the index", me); + refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); + if (the_index.cache_changed) { + if (write_index(&the_index, index_fd) || + commit_locked_index(&index_lock)) + die("git %s: failed to refresh the index", me); + } + rollback_lock_file(&index_lock); +} + static int revert_or_cherry_pick(int argc, const char **argv) { struct rev_info revs; @@ -567,8 +582,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) die("cherry-pick --ff cannot be used with --edit"); } - if (read_cache() < 0) - die("git %s: failed to read the index", me); + read_and_refresh_cache(me); prepare_revs(&revs); diff --git a/builtin/rm.c b/builtin/rm.c index f3772c8..c7b7bb3 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -139,10 +139,10 @@ static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; static int ignore_unmatch = 0; static struct option builtin_rm_options[] = { - OPT__DRY_RUN(&show_only), - OPT__QUIET(&quiet), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__QUIET(&quiet, "do not list removed files"), OPT_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"), - OPT_BOOLEAN('f', "force", &force, "override the up-to-date check"), + OPT__FORCE(&force, "override the up-to-date check"), OPT_BOOLEAN('r', NULL, &recursive, "allow recursive removal"), OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch, "exit with a zero status even if nothing matched"), diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 8aa3031..2cd1c40 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -48,6 +48,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext NULL, NULL, NULL, + NULL, }; struct child_process po; int i; @@ -59,6 +60,8 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext argv[i++] = "--delta-base-offset"; if (args->quiet) argv[i++] = "-q"; + if (args->progress) + argv[i++] = "--progress"; memset(&po, 0, sizeof(po)); po.argv = argv; po.in = -1; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 2135b0d..1a21e4b 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -268,8 +268,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); shortlog_init(&log); init_revisions(&rev, prefix); - parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_ARGV0); + parse_options_start(&ctx, argc, argv, prefix, options, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); for (;;) { switch (parse_options_step(&ctx, options, shortlog_usage)) { diff --git a/builtin/show-ref.c b/builtin/show-ref.c index be9b512..45f0340 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -193,7 +193,8 @@ static const struct option show_ref_options[] = { "only show SHA1 hash using <n> digits", PARSE_OPT_OPTARG, &hash_callback }, OPT__ABBREV(&abbrev), - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, + "do not print results to stdout (useful with --verify)"), { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg, "pattern", "show refs from stdin that aren't in local repository", PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback }, diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index ca855a5..dea849c 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -30,7 +30,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) int quiet = 0; const char *msg = NULL; struct option options[] = { - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, + "suppress error message for non-symbolic (detached) refs"), OPT_STRING('m', NULL, &msg, "reason", "reason of the update"), OPT_END(), }; diff --git a/builtin/tag.c b/builtin/tag.c index d311491..aa1f87d 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -29,8 +29,6 @@ struct tag_filter { struct commit_list *with_commit; }; -#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" - static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { @@ -70,9 +68,9 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; } /* only take up to "lines" lines, and strip the signature */ + size = parse_signature(buf, size); for (i = 0, sp += 2; - i < filter->lines && sp < buf + size && - prefixcmp(sp, PGP_SIGNATURE "\n"); + i < filter->lines && sp < buf + size; i++) { if (i) printf("\n "); @@ -242,8 +240,7 @@ static void write_tag_body(int fd, const unsigned char *sha1) { unsigned long size; enum object_type type; - char *buf, *sp, *eob; - size_t len; + char *buf, *sp; buf = read_sha1_file(sha1, &type, &size); if (!buf) @@ -256,12 +253,7 @@ static void write_tag_body(int fd, const unsigned char *sha1) return; } sp += 2; /* skip the 2 LFs */ - eob = strstr(sp, "\n" PGP_SIGNATURE "\n"); - if (eob) - len = eob - sp; - else - len = buf + size - sp; - write_or_die(fd, sp, len); + write_or_die(fd, sp, parse_signature(sp, buf + size - sp)); free(buf); } @@ -390,7 +382,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"), OPT_STRING('u', NULL, &keyid, "key-id", "use another key to sign the tag"), - OPT_BOOLEAN('f', "force", &force, "replace the tag if exists"), + OPT__FORCE(&force, "replace the tag if exists"), OPT_GROUP("Tag listing options"), { diff --git a/builtin/update-index.c b/builtin/update-index.c index 62d9f3f..56baf27 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -10,6 +10,7 @@ #include "builtin.h" #include "refs.h" #include "resolve-undo.h" +#include "parse-options.h" /* * Default to not allowing changes to the list of files. The @@ -397,8 +398,10 @@ static void read_index_info(int line_termination) strbuf_release(&uq); } -static const char update_index_usage[] = -"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] [<file>...]"; +static const char * const update_index_usage[] = { + "git update-index [options] [--] [<file>...]", + NULL +}; static unsigned char head_sha1[20]; static unsigned char merge_head_sha1[20]; @@ -578,16 +581,214 @@ static int do_reupdate(int ac, const char **av, return 0; } +struct refresh_params { + unsigned int flags; + int *has_errors; +}; + +static int refresh(struct refresh_params *o, unsigned int flag) +{ + setup_work_tree(); + *o->has_errors |= refresh_cache(o->flags | flag); + return 0; +} + +static int refresh_callback(const struct option *opt, + const char *arg, int unset) +{ + return refresh(opt->value, 0); +} + +static int really_refresh_callback(const struct option *opt, + const char *arg, int unset) +{ + return refresh(opt->value, REFRESH_REALLY); +} + +static int chmod_callback(const struct option *opt, + const char *arg, int unset) +{ + char *flip = opt->value; + if ((arg[0] != '-' && arg[0] != '+') || arg[1] != 'x' || arg[2]) + return error("option 'chmod' expects \"+x\" or \"-x\""); + *flip = arg[0]; + return 0; +} + +static int resolve_undo_clear_callback(const struct option *opt, + const char *arg, int unset) +{ + resolve_undo_clear(); + return 0; +} + +static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) +{ + unsigned char sha1[20]; + unsigned int mode; + + if (ctx->argc <= 3) + return error("option 'cacheinfo' expects three arguments"); + if (strtoul_ui(*++ctx->argv, 8, &mode) || + get_sha1_hex(*++ctx->argv, sha1) || + add_cacheinfo(mode, sha1, *++ctx->argv, 0)) + die("git update-index: --cacheinfo cannot add %s", *ctx->argv); + ctx->argc -= 3; + return 0; +} + +static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) +{ + int *line_termination = opt->value; + + if (ctx->argc != 1) + return error("option '%s' must be the last argument", opt->long_name); + allow_add = allow_replace = allow_remove = 1; + read_index_info(*line_termination); + return 0; +} + +static int stdin_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) +{ + int *read_from_stdin = opt->value; + + if (ctx->argc != 1) + return error("option '%s' must be the last argument", opt->long_name); + *read_from_stdin = 1; + return 0; +} + +static int unresolve_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int flags) +{ + int *has_errors = opt->value; + const char *prefix = startup_info->prefix; + + /* consume remaining arguments. */ + *has_errors = do_unresolve(ctx->argc, ctx->argv, + prefix, prefix ? strlen(prefix) : 0); + if (*has_errors) + active_cache_changed = 0; + + ctx->argv += ctx->argc - 1; + ctx->argc = 1; + return 0; +} + +static int reupdate_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int flags) +{ + int *has_errors = opt->value; + const char *prefix = startup_info->prefix; + + /* consume remaining arguments. */ + setup_work_tree(); + *has_errors = do_reupdate(ctx->argc, ctx->argv, + prefix, prefix ? strlen(prefix) : 0); + if (*has_errors) + active_cache_changed = 0; + + ctx->argv += ctx->argc - 1; + ctx->argc = 1; + return 0; +} + int cmd_update_index(int argc, const char **argv, const char *prefix) { - int i, newfd, entries, has_errors = 0, line_termination = '\n'; - int allow_options = 1; + int newfd, entries, has_errors = 0, line_termination = '\n'; int read_from_stdin = 0; int prefix_length = prefix ? strlen(prefix) : 0; char set_executable_bit = 0; - unsigned int refresh_flags = 0; + struct refresh_params refresh_args = {0, &has_errors}; int lock_error = 0; struct lock_file *lock_file; + struct parse_opt_ctx_t ctx; + int parseopt_state = PARSE_OPT_UNKNOWN; + struct option options[] = { + OPT_BIT('q', NULL, &refresh_args.flags, + "continue refresh even when index needs update", + REFRESH_QUIET), + OPT_BIT(0, "ignore-submodules", &refresh_args.flags, + "refresh: ignore submodules", + REFRESH_IGNORE_SUBMODULES), + OPT_SET_INT(0, "add", &allow_add, + "do not ignore new files", 1), + OPT_SET_INT(0, "replace", &allow_replace, + "let files replace directories and vice-versa", 1), + OPT_SET_INT(0, "remove", &allow_remove, + "notice files missing from worktree", 1), + OPT_BIT(0, "unmerged", &refresh_args.flags, + "refresh even if index contains unmerged entries", + REFRESH_UNMERGED), + {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL, + "refresh stat information", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + refresh_callback}, + {OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL, + "like --refresh, but ignore assume-unchanged setting", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + really_refresh_callback}, + {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL, + "<mode> <object> <path>", + "add the specified entry to the index", + PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */ + PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + (parse_opt_cb *) cacheinfo_callback}, + {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+/-)x", + "override the executable bit of the listed files", + PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + chmod_callback}, + {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL, + "mark files as \"not changing\"", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, + {OPTION_SET_INT, 0, "no-assume-unchanged", &mark_valid_only, NULL, + "clear assumed-unchanged bit", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + {OPTION_SET_INT, 0, "skip-worktree", &mark_skip_worktree_only, NULL, + "mark files as \"index-only\"", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, + {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL, + "clear skip-worktree bit", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + OPT_SET_INT(0, "info-only", &info_only, + "add to index only; do not add content to object database", 1), + OPT_SET_INT(0, "force-remove", &force_remove, + "remove named paths even if present in worktree", 1), + OPT_SET_INT('z', NULL, &line_termination, + "with --stdin: input lines are terminated by null bytes", '\0'), + {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL, + "read list of paths to be updated from standard input", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) stdin_callback}, + {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &line_termination, NULL, + "add entries from standard input to the index", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) stdin_cacheinfo_callback}, + {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL, + "repopulate stages #2 and #3 for the listed paths", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) unresolve_callback}, + {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL, + "only update entries that differ from HEAD", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) reupdate_callback}, + OPT_BIT(0, "ignore-missing", &refresh_args.flags, + "ignore files missing from worktree", + REFRESH_IGNORE_MISSING), + OPT_SET_INT(0, "verbose", &verbose, + "report actions to standard output", 1), + {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL, + "(for porcelains) forget saved unresolved conflicts", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + resolve_undo_clear_callback}, + OPT_END() + }; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(update_index_usage[0]); git_config(git_default_config, NULL); @@ -602,151 +803,48 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) if (entries < 0) die("cache corrupted"); - for (i = 1 ; i < argc; i++) { - const char *path = argv[i]; - const char *p; + /* + * Custom copy of parse_options() because we want to handle + * filename arguments as they come. + */ + parse_options_start(&ctx, argc, argv, prefix, + options, PARSE_OPT_STOP_AT_NON_OPTION); + while (ctx.argc) { + if (parseopt_state != PARSE_OPT_DONE) + parseopt_state = parse_options_step(&ctx, options, + update_index_usage); + if (!ctx.argc) + break; + switch (parseopt_state) { + case PARSE_OPT_HELP: + exit(129); + case PARSE_OPT_NON_OPTION: + case PARSE_OPT_DONE: + { + const char *path = ctx.argv[0]; + const char *p; - if (allow_options && *path == '-') { - if (!strcmp(path, "--")) { - allow_options = 0; - continue; - } - if (!strcmp(path, "-q")) { - refresh_flags |= REFRESH_QUIET; - continue; - } - if (!strcmp(path, "--ignore-submodules")) { - refresh_flags |= REFRESH_IGNORE_SUBMODULES; - continue; - } - if (!strcmp(path, "--add")) { - allow_add = 1; - continue; - } - if (!strcmp(path, "--replace")) { - allow_replace = 1; - continue; - } - if (!strcmp(path, "--remove")) { - allow_remove = 1; - continue; - } - if (!strcmp(path, "--unmerged")) { - refresh_flags |= REFRESH_UNMERGED; - continue; - } - if (!strcmp(path, "--refresh")) { - setup_work_tree(); - has_errors |= refresh_cache(refresh_flags); - continue; - } - if (!strcmp(path, "--really-refresh")) { - setup_work_tree(); - has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags); - continue; - } - if (!strcmp(path, "--cacheinfo")) { - unsigned char sha1[20]; - unsigned int mode; - - if (i+3 >= argc) - die("git update-index: --cacheinfo <mode> <sha1> <path>"); - - if (strtoul_ui(argv[i+1], 8, &mode) || - get_sha1_hex(argv[i+2], sha1) || - add_cacheinfo(mode, sha1, argv[i+3], 0)) - die("git update-index: --cacheinfo" - " cannot add %s", argv[i+3]); - i += 3; - continue; - } - if (!strcmp(path, "--chmod=-x") || - !strcmp(path, "--chmod=+x")) { - if (argc <= i+1) - die("git update-index: %s <path>", path); - set_executable_bit = path[8]; - continue; - } - if (!strcmp(path, "--assume-unchanged")) { - mark_valid_only = MARK_FLAG; - continue; - } - if (!strcmp(path, "--no-assume-unchanged")) { - mark_valid_only = UNMARK_FLAG; - continue; - } - if (!strcmp(path, "--no-skip-worktree")) { - mark_skip_worktree_only = UNMARK_FLAG; - continue; - } - if (!strcmp(path, "--skip-worktree")) { - mark_skip_worktree_only = MARK_FLAG; - continue; - } - if (!strcmp(path, "--info-only")) { - info_only = 1; - continue; - } - if (!strcmp(path, "--force-remove")) { - force_remove = 1; - continue; - } - if (!strcmp(path, "-z")) { - line_termination = 0; - continue; - } - if (!strcmp(path, "--stdin")) { - if (i != argc - 1) - die("--stdin must be at the end"); - read_from_stdin = 1; - break; - } - if (!strcmp(path, "--index-info")) { - if (i != argc - 1) - die("--index-info must be at the end"); - allow_add = allow_replace = allow_remove = 1; - read_index_info(line_termination); - break; - } - if (!strcmp(path, "--unresolve")) { - has_errors = do_unresolve(argc - i, argv + i, - prefix, prefix_length); - if (has_errors) - active_cache_changed = 0; - goto finish; - } - if (!strcmp(path, "--again") || !strcmp(path, "-g")) { - setup_work_tree(); - has_errors = do_reupdate(argc - i, argv + i, - prefix, prefix_length); - if (has_errors) - active_cache_changed = 0; - goto finish; - } - if (!strcmp(path, "--ignore-missing")) { - refresh_flags |= REFRESH_IGNORE_MISSING; - continue; - } - if (!strcmp(path, "--verbose")) { - verbose = 1; - continue; - } - if (!strcmp(path, "--clear-resolve-undo")) { - resolve_undo_clear(); - continue; - } - if (!strcmp(path, "-h") || !strcmp(path, "--help")) - usage(update_index_usage); - die("unknown option %s", path); + setup_work_tree(); + p = prefix_path(prefix, prefix_length, path); + update_one(p, NULL, 0); + if (set_executable_bit) + chmod_path(set_executable_bit, p); + if (p < path || p > path + strlen(path)) + free((char *)p); + ctx.argc--; + ctx.argv++; + break; + } + case PARSE_OPT_UNKNOWN: + if (ctx.argv[0][1] == '-') + error("unknown option '%s'", ctx.argv[0] + 2); + else + error("unknown switch '%c'", *ctx.opt); + usage_with_options(update_index_usage, options); } - setup_work_tree(); - p = prefix_path(prefix, prefix_length, path); - update_one(p, NULL, 0); - if (set_executable_bit) - chmod_path(set_executable_bit, p); - if (p < path || p > path + strlen(path)) - free((char *)p); } + argc = parse_options_end(&ctx); + if (read_from_stdin) { struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; @@ -770,10 +868,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) strbuf_release(&buf); } - finish: if (active_cache_changed) { if (newfd < 0) { - if (refresh_flags & REFRESH_QUIET) + if (refresh_args.flags & REFRESH_QUIET) exit(128); unable_to_lock_index_die(get_index_file(), lock_error); } diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c index 2b3fddc..b90dce6 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -11,8 +11,7 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix) { int force = 0; struct option options[] = { - OPT_BOOLEAN('f', "force", &force, - "update the info files from scratch"), + OPT__FORCE(&force, "update the info files from scratch"), OPT_END() }; diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 9f482c2..3134766 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -17,13 +17,11 @@ static const char * const verify_tag_usage[] = { NULL }; -#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" - static int run_gpg_verify(const char *buf, unsigned long size, int verbose) { struct child_process gpg; const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL}; - char path[PATH_MAX], *eol; + char path[PATH_MAX]; size_t len; int fd, ret; @@ -37,11 +35,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose) close(fd); /* find the length without signature */ - len = 0; - while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) { - eol = memchr(buf + len, '\n', size - len); - len += eol ? eol - (buf + len) + 1 : size - len; - } + len = parse_signature(buf, size); if (verbose) write_in_full(1, buf, len); @@ -93,7 +87,7 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; const struct option verify_tag_options[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "print tag contents"), OPT_END() }; @@ -545,6 +545,7 @@ extern int assume_unchanged; extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; +extern int unique_abbrev_extra_length; extern int shared_repository; extern const char *apply_default_whitespace; extern const char *apply_default_ignorewhitespace; @@ -859,7 +860,7 @@ struct cache_def { extern int has_symlink_leading_path(const char *name, int len); extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int); -extern int has_symlink_or_noent_leading_path(const char *name, int len); +extern int check_leading_path(const char *name, int len); extern int has_dirs_only_path(const char *name, int len, int prefix_len); extern void schedule_dir_for_removal(const char *name, int len); extern void remove_scheduled_dirs(void); @@ -1003,6 +1004,9 @@ extern int git_env_bool(const char *, int); extern int git_config_system(void); extern int git_config_global(void); extern int config_error_nonbool(const char *); +extern const char *get_log_output_encoding(void); +extern const char *get_commit_output_encoding(void); + extern const char *config_exclusive_filename; #define MAX_GITNAME (1000) @@ -1087,15 +1091,17 @@ void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char * /* * whitespace rules. * used by both diff and apply + * last two digits are tab width */ -#define WS_BLANK_AT_EOL 01 -#define WS_SPACE_BEFORE_TAB 02 -#define WS_INDENT_WITH_NON_TAB 04 -#define WS_CR_AT_EOL 010 -#define WS_BLANK_AT_EOF 020 -#define WS_TAB_IN_INDENT 040 +#define WS_BLANK_AT_EOL 0100 +#define WS_SPACE_BEFORE_TAB 0200 +#define WS_INDENT_WITH_NON_TAB 0400 +#define WS_CR_AT_EOL 01000 +#define WS_BLANK_AT_EOF 02000 +#define WS_TAB_IN_INDENT 04000 #define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF) -#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) +#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8) +#define WS_TAB_WIDTH_MASK 077 extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); extern unsigned parse_whitespace_rule(const char *); @@ -1104,6 +1110,7 @@ extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *str extern char *whitespace_error_string(unsigned ws); extern void ws_fix_copy(struct strbuf *, const char *, int, unsigned, int *); extern int ws_blank_line(const char *line, int len, unsigned ws_rule); +#define ws_tab_width(rule) ((rule) & WS_TAB_WIDTH_MASK) /* ls-files */ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset); @@ -1117,6 +1124,7 @@ const char *split_cmdline_strerror(int cmdline_errno); /* git.c */ struct startup_info { int have_repository; + const char *prefix; }; extern struct startup_info *startup_info; @@ -49,6 +49,19 @@ struct commit *lookup_commit(const unsigned char *sha1) return check_commit(obj, sha1, 0); } +struct commit *lookup_commit_reference_by_name(const char *name) +{ + unsigned char sha1[20]; + struct commit *commit; + + if (get_sha1(name, sha1)) + return NULL; + commit = lookup_commit_reference(sha1); + if (!commit || parse_commit(commit)) + return NULL; + return commit; +} + static unsigned long parse_commit_date(const char *buf, const char *tail) { const char *dateptr; @@ -137,12 +150,8 @@ struct commit_graft *read_graft_line(char *buf, int len) buf[--len] = '\0'; if (buf[0] == '#' || buf[0] == '\0') return NULL; - if ((len + 1) % 41) { - bad_graft_data: - error("bad graft data: %s", buf); - free(graft); - return NULL; - } + if ((len + 1) % 41) + goto bad_graft_data; i = (len + 1) / 41 - 1; graft = xmalloc(sizeof(*graft) + 20 * i); graft->nr_parent = i; @@ -155,6 +164,11 @@ struct commit_graft *read_graft_line(char *buf, int len) goto bad_graft_data; } return graft; + +bad_graft_data: + error("bad graft data: %s", buf); + free(graft); + return NULL; } static int read_graft_file(const char *graft_file) @@ -36,6 +36,7 @@ struct commit *lookup_commit(const unsigned char *sha1); struct commit *lookup_commit_reference(const unsigned char *sha1); struct commit *lookup_commit_reference_gently(const unsigned char *sha1, int quiet); +struct commit *lookup_commit_reference_by_name(const char *name); int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size); @@ -76,6 +77,7 @@ struct pretty_print_context int need_8bit_cte; int show_notes; struct reflog_walk_info *reflog_info; + const char *output_encoding; }; struct userformat_want { @@ -84,6 +86,8 @@ struct userformat_want { extern int has_non_ascii(const char *text); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ +extern char *logmsg_reencode(const struct commit *commit, + const char *output_encoding); extern char *reencode_commit_message(const struct commit *commit, const char **encoding_p); extern void get_commit_format(const char *arg, struct rev_info *); diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c index f444982..ea249c6 100644 --- a/compat/inet_ntop.c +++ b/compat/inet_ntop.c @@ -17,9 +17,9 @@ #include <errno.h> #include <sys/types.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> + +#include "../git-compat-util.h" + #include <stdio.h> #include <string.h> @@ -50,10 +50,7 @@ * Paul Vixie, 1996. */ static const char * -inet_ntop4(src, dst, size) - const u_char *src; - char *dst; - size_t size; +inet_ntop4(const u_char *src, char *dst, size_t size) { static const char fmt[] = "%u.%u.%u.%u"; char tmp[sizeof "255.255.255.255"]; @@ -78,10 +75,7 @@ inet_ntop4(src, dst, size) * Paul Vixie, 1996. */ static const char * -inet_ntop6(src, dst, size) - const u_char *src; - char *dst; - size_t size; +inet_ntop6(const u_char *src, char *dst, size_t size) { /* * Note that int32_t and int16_t need only be "at least" large enough @@ -178,11 +172,7 @@ inet_ntop6(src, dst, size) * Paul Vixie, 1996. */ const char * -inet_ntop(af, src, dst, size) - int af; - const void *src; - char *dst; - size_t size; +inet_ntop(int af, const void *src, char *dst, size_t size) { switch (af) { case AF_INET: diff --git a/compat/inet_pton.c b/compat/inet_pton.c index 4078fc0..2ec995e 100644 --- a/compat/inet_pton.c +++ b/compat/inet_pton.c @@ -17,9 +17,9 @@ #include <errno.h> #include <sys/types.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> + +#include "../git-compat-util.h" + #include <stdio.h> #include <string.h> @@ -41,7 +41,9 @@ */ static int inet_pton4(const char *src, unsigned char *dst); +#ifndef NO_IPV6 static int inet_pton6(const char *src, unsigned char *dst); +#endif /* int * inet_pton4(src, dst) diff --git a/compat/mingw.c b/compat/mingw.c index b98e600..bee6054 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -127,7 +127,7 @@ int mingw_open (const char *filename, int oflags, ...) mode = va_arg(args, int); va_end(args); - if (!strcmp(filename, "/dev/null")) + if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; fd = open(filename, oflags, mode); @@ -160,7 +160,7 @@ ssize_t mingw_write(int fd, const void *buf, size_t count) #undef fopen FILE *mingw_fopen (const char *filename, const char *otype) { - if (!strcmp(filename, "/dev/null")) + if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; return fopen(filename, otype); } @@ -192,8 +192,11 @@ static inline time_t filetime_to_time_t(const FILETIME *ft) /* We keep the do_lstat code in a separate function to avoid recursion. * When a path ends with a slash, the stat will fail with ENOENT. In * this case, we strip the trailing slashes and stat again. + * + * If follow is true then act like stat() and report on the link + * target. Otherwise report on the link itself. */ -static int do_lstat(const char *file_name, struct stat *buf) +static int do_lstat(int follow, const char *file_name, struct stat *buf) { int err; WIN32_FILE_ATTRIBUTE_DATA fdata; @@ -210,6 +213,25 @@ static int do_lstat(const char *file_name, struct stat *buf) buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + WIN32_FIND_DATAA findbuf; + HANDLE handle = FindFirstFileA(file_name, &findbuf); + if (handle != INVALID_HANDLE_VALUE) { + if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && + (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) { + if (follow) { + char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE); + } else { + buf->st_mode = S_IFLNK; + } + buf->st_mode |= S_IREAD; + if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + buf->st_mode |= S_IWRITE; + } + FindClose(handle); + } + } return 0; } errno = err; @@ -222,12 +244,12 @@ static int do_lstat(const char *file_name, struct stat *buf) * complete. Note that Git stat()s are redirected to mingw_lstat() * too, since Windows doesn't really handle symlinks that well. */ -int mingw_lstat(const char *file_name, struct stat *buf) +static int do_stat_internal(int follow, const char *file_name, struct stat *buf) { int namelen; static char alt_name[PATH_MAX]; - if (!do_lstat(file_name, buf)) + if (!do_lstat(follow, file_name, buf)) return 0; /* if file_name ended in a '/', Windows returned ENOENT; @@ -246,7 +268,16 @@ int mingw_lstat(const char *file_name, struct stat *buf) memcpy(alt_name, file_name, namelen); alt_name[namelen] = 0; - return do_lstat(alt_name, buf); + return do_lstat(follow, alt_name, buf); +} + +int mingw_lstat(const char *file_name, struct stat *buf) +{ + return do_stat_internal(0, file_name, buf); +} +int mingw_stat(const char *file_name, struct stat *buf) +{ + return do_stat_internal(1, file_name, buf); } #undef fstat @@ -379,71 +410,6 @@ int pipe(int filedes[2]) return 0; } -int poll(struct pollfd *ufds, unsigned int nfds, int timeout) -{ - int i, pending; - - if (timeout >= 0) { - if (nfds == 0) { - Sleep(timeout); - return 0; - } - return errno = EINVAL, error("poll timeout not supported"); - } - - /* When there is only one fd to wait for, then we pretend that - * input is available and let the actual wait happen when the - * caller invokes read(). - */ - if (nfds == 1) { - if (!(ufds[0].events & POLLIN)) - return errno = EINVAL, error("POLLIN not set"); - ufds[0].revents = POLLIN; - return 0; - } - -repeat: - pending = 0; - for (i = 0; i < nfds; i++) { - DWORD avail = 0; - HANDLE h = (HANDLE) _get_osfhandle(ufds[i].fd); - if (h == INVALID_HANDLE_VALUE) - return -1; /* errno was set */ - - if (!(ufds[i].events & POLLIN)) - return errno = EINVAL, error("POLLIN not set"); - - /* this emulation works only for pipes */ - if (!PeekNamedPipe(h, NULL, 0, NULL, &avail, NULL)) { - int err = GetLastError(); - if (err == ERROR_BROKEN_PIPE) { - ufds[i].revents = POLLHUP; - pending++; - } else { - errno = EINVAL; - return error("PeekNamedPipe failed," - " GetLastError: %u", err); - } - } else if (avail) { - ufds[i].revents = POLLIN; - pending++; - } else - ufds[i].revents = 0; - } - if (!pending) { - /* The only times that we spin here is when the process - * that is connected through the pipes is waiting for - * its own input data to become available. But since - * the process (pack-objects) is itself CPU intensive, - * it will happily pick up the time slice that we are - * relinquishing here. - */ - Sleep(0); - goto repeat; - } - return 0; -} - struct tm *gmtime_r(const time_t *timep, struct tm *result) { /* gmtime() in MSVCRT.DLL is thread-safe, but not reentrant */ @@ -673,6 +639,14 @@ static int env_compare(const void *a, const void *b) return strcasecmp(*ea, *eb); } +struct pinfo_t { + struct pinfo_t *next; + pid_t pid; + HANDLE proc; +} pinfo_t; +struct pinfo_t *pinfo = NULL; +CRITICAL_SECTION pinfo_cs; + static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, const char *dir, int prepend_cmd, int fhin, int fhout, int fherr) @@ -765,7 +739,26 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, return -1; } CloseHandle(pi.hThread); - return (pid_t)pi.hProcess; + + /* + * The process ID is the human-readable identifier of the process + * that we want to present in log and error messages. The handle + * is not useful for this purpose. But we cannot close it, either, + * because it is not possible to turn a process ID into a process + * handle after the process terminated. + * Keep the handle in a list for waitpid. + */ + EnterCriticalSection(&pinfo_cs); + { + struct pinfo_t *info = xmalloc(sizeof(struct pinfo_t)); + info->pid = pi.dwProcessId; + info->proc = pi.hProcess; + info->next = pinfo; + pinfo = info; + } + LeaveCriticalSection(&pinfo_cs); + + return (pid_t)pi.dwProcessId; } static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, @@ -875,6 +868,30 @@ void mingw_execvp(const char *cmd, char *const *argv) free_path_split(path); } +void mingw_execv(const char *cmd, char *const *argv) +{ + mingw_execve(cmd, argv, environ); +} + +int mingw_kill(pid_t pid, int sig) +{ + if (pid > 0 && sig == SIGTERM) { + HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + + if (TerminateProcess(h, -1)) { + CloseHandle(h); + return 0; + } + + errno = err_win_to_posix(GetLastError()); + CloseHandle(h); + return -1; + } + + errno = EINVAL; + return -1; +} + static char **copy_environ(void) { char **env; @@ -959,19 +976,22 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { - struct hostent *h = gethostbyname(node); + struct hostent *h = NULL; struct addrinfo *ai; struct sockaddr_in *sin; - if (!h) - return WSAGetLastError(); + if (node) { + h = gethostbyname(node); + if (!h) + return WSAGetLastError(); + } ai = xmalloc(sizeof(struct addrinfo)); *res = ai; ai->ai_flags = 0; ai->ai_family = AF_INET; - ai->ai_socktype = hints->ai_socktype; - switch (hints->ai_socktype) { + ai->ai_socktype = hints ? hints->ai_socktype : 0; + switch (ai->ai_socktype) { case SOCK_STREAM: ai->ai_protocol = IPPROTO_TCP; break; @@ -983,14 +1003,25 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service, break; } ai->ai_addrlen = sizeof(struct sockaddr_in); - ai->ai_canonname = strdup(h->h_name); + if (hints && (hints->ai_flags & AI_CANONNAME)) + ai->ai_canonname = h ? strdup(h->h_name) : NULL; + else + ai->ai_canonname = NULL; sin = xmalloc(ai->ai_addrlen); memset(sin, 0, ai->ai_addrlen); sin->sin_family = AF_INET; + /* Note: getaddrinfo is supposed to allow service to be a string, + * which should be looked up using getservbyname. This is + * currently not implemented */ if (service) sin->sin_port = htons(atoi(service)); - sin->sin_addr = *(struct in_addr *)h->h_addr; + if (h) + sin->sin_addr = *(struct in_addr *)h->h_addr; + else if (hints && (hints->ai_flags & AI_PASSIVE)) + sin->sin_addr.s_addr = INADDR_ANY; + else + sin->sin_addr.s_addr = INADDR_LOOPBACK; ai->ai_addr = (struct sockaddr *)sin; ai->ai_next = 0; return 0; @@ -1141,7 +1172,10 @@ int mingw_getnameinfo(const struct sockaddr *sa, socklen_t salen, int mingw_socket(int domain, int type, int protocol) { int sockfd; - SOCKET s = WSASocket(domain, type, protocol, NULL, 0, 0); + SOCKET s; + + ensure_socket_initialization(); + s = WSASocket(domain, type, protocol, NULL, 0, 0); if (s == INVALID_SOCKET) { /* * WSAGetLastError() values are regular BSD error codes @@ -1171,6 +1205,45 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz) return connect(s, sa, sz); } +#undef bind +int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz) +{ + SOCKET s = (SOCKET)_get_osfhandle(sockfd); + return bind(s, sa, sz); +} + +#undef setsockopt +int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen) +{ + SOCKET s = (SOCKET)_get_osfhandle(sockfd); + return setsockopt(s, lvl, optname, (const char*)optval, optlen); +} + +#undef listen +int mingw_listen(int sockfd, int backlog) +{ + SOCKET s = (SOCKET)_get_osfhandle(sockfd); + return listen(s, backlog); +} + +#undef accept +int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz) +{ + int sockfd2; + + SOCKET s1 = (SOCKET)_get_osfhandle(sockfd1); + SOCKET s2 = accept(s1, sa, sz); + + /* convert into a file descriptor */ + if ((sockfd2 = _open_osfhandle(s2, O_RDWR|O_BINARY)) < 0) { + int err = errno; + closesocket(s2); + return error("unable to make a socket file descriptor: %s", + strerror(err)); + } + return sockfd2; +} + #undef rename int mingw_rename(const char *pold, const char *pnew) { @@ -1388,6 +1461,7 @@ void mingw_open_html(const char *unixpath) const char *, const char *, const char *, INT); T ShellExecute; HMODULE shell32; + int r; shell32 = LoadLibrary("shell32.dll"); if (!shell32) @@ -1397,9 +1471,12 @@ void mingw_open_html(const char *unixpath) die("cannot run browser"); printf("Launching default browser to display HTML ...\n"); - ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0); - + r = (int)ShellExecute(NULL, "open", htmlpath, NULL, "\\", SW_SHOWNORMAL); FreeLibrary(shell32); + /* see the MSDN documentation referring to the result codes here */ + if (r <= 32) { + die("failed to launch browser for %.*s", MAX_PATH, unixpath); + } } int link(const char *oldpath, const char *newpath) @@ -1438,62 +1515,54 @@ char *getpass(const char *prompt) return strbuf_detach(&buf, NULL); } -#ifndef NO_MINGW_REPLACE_READDIR -/* MinGW readdir implementation to avoid extra lstats for Git */ -struct mingw_DIR -{ - struct _finddata_t dd_dta; /* disk transfer area for this dir */ - struct mingw_dirent dd_dir; /* Our own implementation, including d_type */ - long dd_handle; /* _findnext handle */ - int dd_stat; /* 0 = next entry to read is first entry, -1 = off the end, positive = 0 based index of next entry */ - char dd_name[1]; /* given path for dir with search pattern (struct is extended) */ -}; - -struct dirent *mingw_readdir(DIR *dir) +pid_t waitpid(pid_t pid, int *status, unsigned options) { - WIN32_FIND_DATAA buf; - HANDLE handle; - struct mingw_DIR *mdir = (struct mingw_DIR*)dir; - - if (!dir->dd_handle) { - errno = EBADF; /* No set_errno for mingw */ - return NULL; + HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, + FALSE, pid); + if (!h) { + errno = ECHILD; + return -1; } - if (dir->dd_handle == (long)INVALID_HANDLE_VALUE && dir->dd_stat == 0) - { - DWORD lasterr; - handle = FindFirstFileA(dir->dd_name, &buf); - lasterr = GetLastError(); - dir->dd_handle = (long)handle; - if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) { - errno = err_win_to_posix(lasterr); - return NULL; + if (pid > 0 && options & WNOHANG) { + if (WAIT_OBJECT_0 != WaitForSingleObject(h, 0)) { + CloseHandle(h); + return 0; } - } else if (dir->dd_handle == (long)INVALID_HANDLE_VALUE) { - return NULL; - } else if (!FindNextFileA((HANDLE)dir->dd_handle, &buf)) { - DWORD lasterr = GetLastError(); - FindClose((HANDLE)dir->dd_handle); - dir->dd_handle = (long)INVALID_HANDLE_VALUE; - /* POSIX says you shouldn't set errno when readdir can't - find any more files; so, if another error we leave it set. */ - if (lasterr != ERROR_NO_MORE_FILES) - errno = err_win_to_posix(lasterr); - return NULL; + options &= ~WNOHANG; } - /* We get here if `buf' contains valid data. */ - strcpy(dir->dd_dir.d_name, buf.cFileName); - ++dir->dd_stat; + if (options == 0) { + struct pinfo_t **ppinfo; + if (WaitForSingleObject(h, INFINITE) != WAIT_OBJECT_0) { + CloseHandle(h); + return 0; + } + + if (status) + GetExitCodeProcess(h, (LPDWORD)status); - /* Set file type, based on WIN32_FIND_DATA */ - mdir->dd_dir.d_type = 0; - if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - mdir->dd_dir.d_type |= DT_DIR; - else - mdir->dd_dir.d_type |= DT_REG; + EnterCriticalSection(&pinfo_cs); + + ppinfo = &pinfo; + while (*ppinfo) { + struct pinfo_t *info = *ppinfo; + if (info->pid == pid) { + CloseHandle(info->proc); + *ppinfo = info->next; + free(info); + break; + } + ppinfo = &info->next; + } + + LeaveCriticalSection(&pinfo_cs); - return (struct dirent*)&dir->dd_dir; + CloseHandle(h); + return pid; + } + CloseHandle(h); + + errno = EINVAL; + return -1; } -#endif // !NO_MINGW_REPLACE_READDIR diff --git a/compat/mingw.h b/compat/mingw.h index 3b2477b..2283071 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -6,23 +6,40 @@ */ typedef int pid_t; +typedef int uid_t; +typedef int socklen_t; #define hstrerror strerror #define S_IFLNK 0120000 /* Symbolic link */ #define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK) #define S_ISSOCK(x) 0 + +#ifndef _STAT_H_ +#define S_IRUSR 0 +#define S_IWUSR 0 +#define S_IXUSR 0 +#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +#endif #define S_IRGRP 0 #define S_IWGRP 0 #define S_IXGRP 0 -#define S_ISGID 0 +#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) #define S_IROTH 0 +#define S_IWOTH 0 #define S_IXOTH 0 +#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) +#define S_ISUID 0 +#define S_ISGID 0 +#define S_ISVTX 0 #define WIFEXITED(x) 1 #define WIFSIGNALED(x) 0 #define WEXITSTATUS(x) ((x) & 0xff) #define WTERMSIG(x) SIGTERM +#define EWOULDBLOCK EAGAIN +#define SHUT_WR SD_SEND + #define SIGHUP 1 #define SIGQUIT 3 #define SIGKILL 9 @@ -34,6 +51,9 @@ typedef int pid_t; #define F_SETFD 2 #define FD_CLOEXEC 0x1 +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#define ECONNABORTED WSAECONNABORTED + struct passwd { char *pw_name; char *pw_gecos; @@ -42,16 +62,6 @@ struct passwd { extern char *getpass(const char *prompt); -#ifndef POLLIN -struct pollfd { - int fd; /* file descriptor */ - short events; /* requested events */ - short revents; /* returned events */ -}; -#define POLLIN 1 -#define POLLHUP 2 -#endif - typedef void (__cdecl *sig_handler_t)(int); struct sigaction { sig_handler_t sa_handler; @@ -66,6 +76,12 @@ struct itimerval { #define ITIMER_REAL 0 /* + * sanitize preprocessor namespace polluted by Windows headers defining + * macros which collide with git local versions + */ +#undef HELP_COMMAND /* from winuser.h */ + +/* * trivial stubs */ @@ -75,17 +91,17 @@ static inline int symlink(const char *oldpath, const char *newpath) { errno = ENOSYS; return -1; } static inline int fchmod(int fildes, mode_t mode) { errno = ENOSYS; return -1; } -static inline int fork(void) +static inline pid_t fork(void) { errno = ENOSYS; return -1; } static inline unsigned int alarm(unsigned int seconds) { return 0; } static inline int fsync(int fd) { return _commit(fd); } -static inline int getppid(void) +static inline pid_t getppid(void) { return 1; } static inline void sync(void) {} -static inline int getuid() +static inline uid_t getuid(void) { return 1; } static inline struct passwd *getpwnam(const char *name) { return NULL; } @@ -117,13 +133,11 @@ static inline int mingw_unlink(const char *pathname) } #define unlink mingw_unlink -static inline int waitpid(pid_t pid, int *status, unsigned options) -{ - if (options == 0) - return _cwait(status, pid, 0); - errno = EINVAL; - return -1; -} +#define WNOHANG 1 +pid_t waitpid(pid_t pid, int *status, unsigned options); + +#define kill mingw_kill +int mingw_kill(pid_t pid, int sig); #ifndef NO_OPENSSL #include <openssl/ssl.h> @@ -154,11 +168,10 @@ int pipe(int filedes[2]); unsigned int sleep (unsigned int seconds); int mkstemp(char *template); int gettimeofday(struct timeval *tv, void *tz); -int poll(struct pollfd *ufds, unsigned int nfds, int timeout); struct tm *gmtime_r(const time_t *timep, struct tm *result); struct tm *localtime_r(const time_t *timep, struct tm *result); int getpagesize(void); /* defined in MinGW's libgcc.a */ -struct passwd *getpwuid(int uid); +struct passwd *getpwuid(uid_t uid); int setitimer(int type, struct itimerval *in, struct itimerval *out); int sigaction(int sig, struct sigaction *in, struct sigaction *out); int link(const char *oldpath, const char *newpath); @@ -206,6 +219,18 @@ int mingw_socket(int domain, int type, int protocol); int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz); #define connect mingw_connect +int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz); +#define bind mingw_bind + +int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen); +#define setsockopt mingw_setsockopt + +int mingw_listen(int sockfd, int backlog); +#define listen mingw_listen + +int mingw_accept(int sockfd, struct sockaddr *sa, socklen_t *sz); +#define accept mingw_accept + int mingw_rename(const char*, const char*); #define rename mingw_rename @@ -222,10 +247,11 @@ int mingw_getpagesize(void); #ifndef ALREADY_DECLARED_STAT_FUNCS #define stat _stati64 int mingw_lstat(const char *file_name, struct stat *buf); +int mingw_stat(const char *file_name, struct stat *buf); int mingw_fstat(int fd, struct stat *buf); #define fstat mingw_fstat #define lstat mingw_lstat -#define _stati64(x,y) mingw_lstat(x,y) +#define _stati64(x,y) mingw_stat(x,y) #endif int mingw_utime(const char *file_name, const struct utimbuf *times); @@ -236,6 +262,8 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, int fhin, int fhout, int fherr); void mingw_execvp(const char *cmd, char *const *argv); #define execvp mingw_execvp +void mingw_execv(const char *cmd, char *const *argv); +#define execv mingw_execv static inline unsigned int git_ntohl(unsigned int x) { return (unsigned int)ntohl(x); } @@ -283,44 +311,17 @@ void free_environ(char **env); static int mingw_main(); \ int main(int argc, const char **argv) \ { \ + extern CRITICAL_SECTION pinfo_cs; \ _fmode = _O_BINARY; \ _setmode(_fileno(stdin), _O_BINARY); \ _setmode(_fileno(stdout), _O_BINARY); \ _setmode(_fileno(stderr), _O_BINARY); \ argv[0] = xstrdup(_pgmptr); \ + InitializeCriticalSection(&pinfo_cs); \ return mingw_main(argc, argv); \ } \ static int mingw_main(c,v) -#ifndef NO_MINGW_REPLACE_READDIR -/* - * A replacement of readdir, to ensure that it reads the file type at - * the same time. This avoid extra unneeded lstats in git on MinGW - */ -#undef DT_UNKNOWN -#undef DT_DIR -#undef DT_REG -#undef DT_LNK -#define DT_UNKNOWN 0 -#define DT_DIR 1 -#define DT_REG 2 -#define DT_LNK 3 - -struct mingw_dirent -{ - long d_ino; /* Always zero. */ - union { - unsigned short d_reclen; /* Always zero. */ - unsigned char d_type; /* Reimplementation adds this */ - }; - unsigned short d_namlen; /* Length of name in d_name. */ - char d_name[FILENAME_MAX]; /* File name. */ -}; -#define dirent mingw_dirent -#define readdir(x) mingw_readdir(x) -struct dirent *mingw_readdir(DIR *dir); -#endif // !NO_MINGW_REPLACE_READDIR - /* * Used by Pthread API implementation for Windows */ diff --git a/compat/msvc.c b/compat/msvc.c index ac04a4c..71843d7 100644 --- a/compat/msvc.c +++ b/compat/msvc.c @@ -3,33 +3,4 @@ #include <conio.h> #include "../strbuf.h" -DIR *opendir(const char *name) -{ - int len; - DIR *p; - p = (DIR*)malloc(sizeof(DIR)); - memset(p, 0, sizeof(DIR)); - strncpy(p->dd_name, name, PATH_MAX); - len = strlen(p->dd_name); - p->dd_name[len] = '/'; - p->dd_name[len+1] = '*'; - - if (p == NULL) - return NULL; - - p->dd_handle = _findfirst(p->dd_name, &p->dd_dta); - - if (p->dd_handle == -1) { - free(p); - return NULL; - } - return p; -} -int closedir(DIR *dir) -{ - _findclose(dir->dd_handle); - free(dir); - return 0; -} - #include "mingw.c" diff --git a/compat/vcbuild/include/dirent.h b/compat/vcbuild/include/dirent.h deleted file mode 100644 index 440618d..0000000 --- a/compat/vcbuild/include/dirent.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * DIRENT.H (formerly DIRLIB.H) - * This file has no copyright assigned and is placed in the Public Domain. - * This file is a part of the mingw-runtime package. - * - * The mingw-runtime package and its code is distributed in the hope that it - * will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR - * IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to - * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You are free to use this package and its code without limitation. - */ -#ifndef _DIRENT_H_ -#define _DIRENT_H_ -#include <io.h> - -#define PATH_MAX 512 - -#define __MINGW_NOTHROW - -#ifndef RC_INVOKED - -#ifdef __cplusplus -extern "C" { -#endif - -struct dirent -{ - long d_ino; /* Always zero. */ - unsigned short d_reclen; /* Always zero. */ - unsigned short d_namlen; /* Length of name in d_name. */ - char d_name[FILENAME_MAX]; /* File name. */ -}; - -/* - * This is an internal data structure. Good programmers will not use it - * except as an argument to one of the functions below. - * dd_stat field is now int (was short in older versions). - */ -typedef struct -{ - /* disk transfer area for this dir */ - struct _finddata_t dd_dta; - - /* dirent struct to return from dir (NOTE: this makes this thread - * safe as long as only one thread uses a particular DIR struct at - * a time) */ - struct dirent dd_dir; - - /* _findnext handle */ - long dd_handle; - - /* - * Status of search: - * 0 = not started yet (next entry to read is first entry) - * -1 = off the end - * positive = 0 based index of next entry - */ - int dd_stat; - - /* given path for dir with search pattern (struct is extended) */ - char dd_name[PATH_MAX+3]; -} DIR; - -DIR* __cdecl __MINGW_NOTHROW opendir (const char*); -struct dirent* __cdecl __MINGW_NOTHROW readdir (DIR*); -int __cdecl __MINGW_NOTHROW closedir (DIR*); -void __cdecl __MINGW_NOTHROW rewinddir (DIR*); -long __cdecl __MINGW_NOTHROW telldir (DIR*); -void __cdecl __MINGW_NOTHROW seekdir (DIR*, long); - - -/* wide char versions */ - -struct _wdirent -{ - long d_ino; /* Always zero. */ - unsigned short d_reclen; /* Always zero. */ - unsigned short d_namlen; /* Length of name in d_name. */ - wchar_t d_name[FILENAME_MAX]; /* File name. */ -}; - -/* - * This is an internal data structure. Good programmers will not use it - * except as an argument to one of the functions below. - */ -typedef struct -{ - /* disk transfer area for this dir */ - //struct _wfinddata_t dd_dta; - - /* dirent struct to return from dir (NOTE: this makes this thread - * safe as long as only one thread uses a particular DIR struct at - * a time) */ - struct _wdirent dd_dir; - - /* _findnext handle */ - long dd_handle; - - /* - * Status of search: - * 0 = not started yet (next entry to read is first entry) - * -1 = off the end - * positive = 0 based index of next entry - */ - int dd_stat; - - /* given path for dir with search pattern (struct is extended) */ - wchar_t dd_name[1]; -} _WDIR; - - - -_WDIR* __cdecl __MINGW_NOTHROW _wopendir (const wchar_t*); -struct _wdirent* __cdecl __MINGW_NOTHROW _wreaddir (_WDIR*); -int __cdecl __MINGW_NOTHROW _wclosedir (_WDIR*); -void __cdecl __MINGW_NOTHROW _wrewinddir (_WDIR*); -long __cdecl __MINGW_NOTHROW _wtelldir (_WDIR*); -void __cdecl __MINGW_NOTHROW _wseekdir (_WDIR*, long); - - -#ifdef __cplusplus -} -#endif - -#endif /* Not RC_INVOKED */ - -#endif /* Not _DIRENT_H_ */ diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c new file mode 100644 index 0000000..7a0debe --- /dev/null +++ b/compat/win32/dirent.c @@ -0,0 +1,108 @@ +#include "../git-compat-util.h" +#include "dirent.h" + +struct DIR { + struct dirent dd_dir; /* includes d_type */ + HANDLE dd_handle; /* FindFirstFile handle */ + int dd_stat; /* 0-based index */ + char dd_name[1]; /* extend struct */ +}; + +DIR *opendir(const char *name) +{ + DWORD attrs = GetFileAttributesA(name); + int len; + DIR *p; + + /* check for valid path */ + if (attrs == INVALID_FILE_ATTRIBUTES) { + errno = ENOENT; + return NULL; + } + + /* check if it's a directory */ + if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) { + errno = ENOTDIR; + return NULL; + } + + /* check that the pattern won't be too long for FindFirstFileA */ + len = strlen(name); + if (is_dir_sep(name[len - 1])) + len--; + if (len + 2 >= MAX_PATH) { + errno = ENAMETOOLONG; + return NULL; + } + + p = malloc(sizeof(DIR) + len + 2); + if (!p) + return NULL; + + memset(p, 0, sizeof(DIR) + len + 2); + strcpy(p->dd_name, name); + p->dd_name[len] = '/'; + p->dd_name[len+1] = '*'; + + p->dd_handle = INVALID_HANDLE_VALUE; + return p; +} + +struct dirent *readdir(DIR *dir) +{ + WIN32_FIND_DATAA buf; + HANDLE handle; + + if (!dir || !dir->dd_handle) { + errno = EBADF; /* No set_errno for mingw */ + return NULL; + } + + if (dir->dd_handle == INVALID_HANDLE_VALUE && dir->dd_stat == 0) { + DWORD lasterr; + handle = FindFirstFileA(dir->dd_name, &buf); + lasterr = GetLastError(); + dir->dd_handle = handle; + if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) { + errno = err_win_to_posix(lasterr); + return NULL; + } + } else if (dir->dd_handle == INVALID_HANDLE_VALUE) { + return NULL; + } else if (!FindNextFileA(dir->dd_handle, &buf)) { + DWORD lasterr = GetLastError(); + FindClose(dir->dd_handle); + dir->dd_handle = INVALID_HANDLE_VALUE; + /* POSIX says you shouldn't set errno when readdir can't + find any more files; so, if another error we leave it set. */ + if (lasterr != ERROR_NO_MORE_FILES) + errno = err_win_to_posix(lasterr); + return NULL; + } + + /* We get here if `buf' contains valid data. */ + strcpy(dir->dd_dir.d_name, buf.cFileName); + ++dir->dd_stat; + + /* Set file type, based on WIN32_FIND_DATA */ + dir->dd_dir.d_type = 0; + if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + dir->dd_dir.d_type |= DT_DIR; + else + dir->dd_dir.d_type |= DT_REG; + + return &dir->dd_dir; +} + +int closedir(DIR *dir) +{ + if (!dir) { + errno = EBADF; + return -1; + } + + if (dir->dd_handle != INVALID_HANDLE_VALUE) + FindClose(dir->dd_handle); + free(dir); + return 0; +} diff --git a/compat/win32/dirent.h b/compat/win32/dirent.h new file mode 100644 index 0000000..927a25c --- /dev/null +++ b/compat/win32/dirent.h @@ -0,0 +1,24 @@ +#ifndef DIRENT_H +#define DIRENT_H + +typedef struct DIR DIR; + +#define DT_UNKNOWN 0 +#define DT_DIR 1 +#define DT_REG 2 +#define DT_LNK 3 + +struct dirent { + long d_ino; /* Always zero. */ + char d_name[FILENAME_MAX]; /* File name. */ + union { + unsigned short d_reclen; /* Always zero. */ + unsigned char d_type; /* Reimplementation adds this */ + }; +}; + +DIR *opendir(const char *dirname); +struct dirent *readdir(DIR *dir); +int closedir(DIR *dir); + +#endif /* DIRENT_H */ diff --git a/compat/win32/sys/poll.c b/compat/win32/sys/poll.c new file mode 100644 index 0000000..7e74ebe --- /dev/null +++ b/compat/win32/sys/poll.c @@ -0,0 +1,596 @@ +/* Emulation for poll(2) + Contributed by Paolo Bonzini. + + Copyright 2001-2003, 2006-2010 Free Software Foundation, Inc. + + This file is part of gnulib. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Tell gcc not to warn about the (nfd < 0) tests, below. */ +#if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__ +# pragma GCC diagnostic ignored "-Wtype-limits" +#endif + +#include <malloc.h> + +#include <sys/types.h> +#include "poll.h" +#include <errno.h> +#include <limits.h> +#include <assert.h> + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +# define WIN32_NATIVE +# include <winsock2.h> +# include <windows.h> +# include <io.h> +# include <stdio.h> +# include <conio.h> +#else +# include <sys/time.h> +# include <sys/socket.h> +# include <sys/select.h> +# include <unistd.h> +#endif + +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif + +#include <time.h> + +#ifndef INFTIM +# define INFTIM (-1) +#endif + +/* BeOS does not have MSG_PEEK. */ +#ifndef MSG_PEEK +# define MSG_PEEK 0 +#endif + +#ifdef WIN32_NATIVE + +#define IsConsoleHandle(h) (((long) (h) & 3) == 3) + +static BOOL +IsSocketHandle (HANDLE h) +{ + WSANETWORKEVENTS ev; + + if (IsConsoleHandle (h)) + return FALSE; + + /* Under Wine, it seems that getsockopt returns 0 for pipes too. + WSAEnumNetworkEvents instead distinguishes the two correctly. */ + ev.lNetworkEvents = 0xDEADBEEF; + WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev); + return ev.lNetworkEvents != 0xDEADBEEF; +} + +/* Declare data structures for ntdll functions. */ +typedef struct _FILE_PIPE_LOCAL_INFORMATION { + ULONG NamedPipeType; + ULONG NamedPipeConfiguration; + ULONG MaximumInstances; + ULONG CurrentInstances; + ULONG InboundQuota; + ULONG ReadDataAvailable; + ULONG OutboundQuota; + ULONG WriteQuotaAvailable; + ULONG NamedPipeState; + ULONG NamedPipeEnd; +} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION; + +typedef struct _IO_STATUS_BLOCK +{ + union { + DWORD Status; + PVOID Pointer; + } u; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef enum _FILE_INFORMATION_CLASS { + FilePipeLocalInformation = 24 +} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; + +typedef DWORD (WINAPI *PNtQueryInformationFile) + (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS); + +# ifndef PIPE_BUF +# define PIPE_BUF 512 +# endif + +/* Compute revents values for file handle H. If some events cannot happen + for the handle, eliminate them from *P_SOUGHT. */ + +static int +win32_compute_revents (HANDLE h, int *p_sought) +{ + int i, ret, happened; + INPUT_RECORD *irbuffer; + DWORD avail, nbuffer; + BOOL bRet; + IO_STATUS_BLOCK iosb; + FILE_PIPE_LOCAL_INFORMATION fpli; + static PNtQueryInformationFile NtQueryInformationFile; + static BOOL once_only; + + switch (GetFileType (h)) + { + case FILE_TYPE_PIPE: + if (!once_only) + { + NtQueryInformationFile = (PNtQueryInformationFile) + GetProcAddress (GetModuleHandle ("ntdll.dll"), + "NtQueryInformationFile"); + once_only = TRUE; + } + + happened = 0; + if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0) + { + if (avail) + happened |= *p_sought & (POLLIN | POLLRDNORM); + } + else if (GetLastError () == ERROR_BROKEN_PIPE) + happened |= POLLHUP; + + else + { + /* It was the write-end of the pipe. Check if it is writable. + If NtQueryInformationFile fails, optimistically assume the pipe is + writable. This could happen on Win9x, where NtQueryInformationFile + is not available, or if we inherit a pipe that doesn't permit + FILE_READ_ATTRIBUTES access on the write end (I think this should + not happen since WinXP SP2; WINE seems fine too). Otherwise, + ensure that enough space is available for atomic writes. */ + memset (&iosb, 0, sizeof (iosb)); + memset (&fpli, 0, sizeof (fpli)); + + if (!NtQueryInformationFile + || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli), + FilePipeLocalInformation) + || fpli.WriteQuotaAvailable >= PIPE_BUF + || (fpli.OutboundQuota < PIPE_BUF && + fpli.WriteQuotaAvailable == fpli.OutboundQuota)) + happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND); + } + return happened; + + case FILE_TYPE_CHAR: + ret = WaitForSingleObject (h, 0); + if (!IsConsoleHandle (h)) + return ret == WAIT_OBJECT_0 ? *p_sought & ~(POLLPRI | POLLRDBAND) : 0; + + nbuffer = avail = 0; + bRet = GetNumberOfConsoleInputEvents (h, &nbuffer); + if (bRet) + { + /* Input buffer. */ + *p_sought &= POLLIN | POLLRDNORM; + if (nbuffer == 0) + return POLLHUP; + if (!*p_sought) + return 0; + + irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD)); + bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail); + if (!bRet || avail == 0) + return POLLHUP; + + for (i = 0; i < avail; i++) + if (irbuffer[i].EventType == KEY_EVENT) + return *p_sought; + return 0; + } + else + { + /* Screen buffer. */ + *p_sought &= POLLOUT | POLLWRNORM | POLLWRBAND; + return *p_sought; + } + + default: + ret = WaitForSingleObject (h, 0); + if (ret == WAIT_OBJECT_0) + return *p_sought & ~(POLLPRI | POLLRDBAND); + + return *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND); + } +} + +/* Convert fd_sets returned by select into revents values. */ + +static int +win32_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents) +{ + int happened = 0; + + if ((lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) == FD_ACCEPT) + happened |= (POLLIN | POLLRDNORM) & sought; + + else if (lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) + { + int r, error; + + char data[64]; + WSASetLastError (0); + r = recv (h, data, sizeof (data), MSG_PEEK); + error = WSAGetLastError (); + WSASetLastError (0); + + if (r > 0 || error == WSAENOTCONN) + happened |= (POLLIN | POLLRDNORM) & sought; + + /* Distinguish hung-up sockets from other errors. */ + else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET + || error == WSAECONNABORTED || error == WSAENETRESET) + happened |= POLLHUP; + + else + happened |= POLLERR; + } + + if (lNetworkEvents & (FD_WRITE | FD_CONNECT)) + happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought; + + if (lNetworkEvents & FD_OOB) + happened |= (POLLPRI | POLLRDBAND) & sought; + + return happened; +} + +#else /* !MinGW */ + +/* Convert select(2) returned fd_sets into poll(2) revents values. */ +static int +compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds) +{ + int happened = 0; + if (FD_ISSET (fd, rfds)) + { + int r; + int socket_errno; + +# if defined __MACH__ && defined __APPLE__ + /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK + for some kinds of descriptors. Detect if this descriptor is a + connected socket, a server socket, or something else using a + 0-byte recv, and use ioctl(2) to detect POLLHUP. */ + r = recv (fd, NULL, 0, MSG_PEEK); + socket_errno = (r < 0) ? errno : 0; + if (r == 0 || socket_errno == ENOTSOCK) + ioctl (fd, FIONREAD, &r); +# else + char data[64]; + r = recv (fd, data, sizeof (data), MSG_PEEK); + socket_errno = (r < 0) ? errno : 0; +# endif + if (r == 0) + happened |= POLLHUP; + + /* If the event happened on an unconnected server socket, + that's fine. */ + else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN)) + happened |= (POLLIN | POLLRDNORM) & sought; + + /* Distinguish hung-up sockets from other errors. */ + else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET + || socket_errno == ECONNABORTED || socket_errno == ENETRESET) + happened |= POLLHUP; + + else + happened |= POLLERR; + } + + if (FD_ISSET (fd, wfds)) + happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought; + + if (FD_ISSET (fd, efds)) + happened |= (POLLPRI | POLLRDBAND) & sought; + + return happened; +} +#endif /* !MinGW */ + +int +poll (pfd, nfd, timeout) + struct pollfd *pfd; + nfds_t nfd; + int timeout; +{ +#ifndef WIN32_NATIVE + fd_set rfds, wfds, efds; + struct timeval tv; + struct timeval *ptv; + int maxfd, rc; + nfds_t i; + +# ifdef _SC_OPEN_MAX + static int sc_open_max = -1; + + if (nfd < 0 + || (nfd > sc_open_max + && (sc_open_max != -1 + || nfd > (sc_open_max = sysconf (_SC_OPEN_MAX))))) + { + errno = EINVAL; + return -1; + } +# else /* !_SC_OPEN_MAX */ +# ifdef OPEN_MAX + if (nfd < 0 || nfd > OPEN_MAX) + { + errno = EINVAL; + return -1; + } +# endif /* OPEN_MAX -- else, no check is needed */ +# endif /* !_SC_OPEN_MAX */ + + /* EFAULT is not necessary to implement, but let's do it in the + simplest case. */ + if (!pfd) + { + errno = EFAULT; + return -1; + } + + /* convert timeout number into a timeval structure */ + if (timeout == 0) + { + ptv = &tv; + ptv->tv_sec = 0; + ptv->tv_usec = 0; + } + else if (timeout > 0) + { + ptv = &tv; + ptv->tv_sec = timeout / 1000; + ptv->tv_usec = (timeout % 1000) * 1000; + } + else if (timeout == INFTIM) + /* wait forever */ + ptv = NULL; + else + { + errno = EINVAL; + return -1; + } + + /* create fd sets and determine max fd */ + maxfd = -1; + FD_ZERO (&rfds); + FD_ZERO (&wfds); + FD_ZERO (&efds); + for (i = 0; i < nfd; i++) + { + if (pfd[i].fd < 0) + continue; + + if (pfd[i].events & (POLLIN | POLLRDNORM)) + FD_SET (pfd[i].fd, &rfds); + + /* see select(2): "the only exceptional condition detectable + is out-of-band data received on a socket", hence we push + POLLWRBAND events onto wfds instead of efds. */ + if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) + FD_SET (pfd[i].fd, &wfds); + if (pfd[i].events & (POLLPRI | POLLRDBAND)) + FD_SET (pfd[i].fd, &efds); + if (pfd[i].fd >= maxfd + && (pfd[i].events & (POLLIN | POLLOUT | POLLPRI + | POLLRDNORM | POLLRDBAND + | POLLWRNORM | POLLWRBAND))) + { + maxfd = pfd[i].fd; + if (maxfd > FD_SETSIZE) + { + errno = EOVERFLOW; + return -1; + } + } + } + + /* examine fd sets */ + rc = select (maxfd + 1, &rfds, &wfds, &efds, ptv); + if (rc < 0) + return rc; + + /* establish results */ + rc = 0; + for (i = 0; i < nfd; i++) + if (pfd[i].fd < 0) + pfd[i].revents = 0; + else + { + int happened = compute_revents (pfd[i].fd, pfd[i].events, + &rfds, &wfds, &efds); + if (happened) + { + pfd[i].revents = happened; + rc++; + } + } + + return rc; +#else + static struct timeval tv0; + static HANDLE hEvent; + WSANETWORKEVENTS ev; + HANDLE h, handle_array[FD_SETSIZE + 2]; + DWORD ret, wait_timeout, nhandles; + fd_set rfds, wfds, xfds; + BOOL poll_again; + MSG msg; + int rc = 0; + nfds_t i; + + if (nfd < 0 || timeout < -1) + { + errno = EINVAL; + return -1; + } + + if (!hEvent) + hEvent = CreateEvent (NULL, FALSE, FALSE, NULL); + + handle_array[0] = hEvent; + nhandles = 1; + FD_ZERO (&rfds); + FD_ZERO (&wfds); + FD_ZERO (&xfds); + + /* Classify socket handles and create fd sets. */ + for (i = 0; i < nfd; i++) + { + int sought = pfd[i].events; + pfd[i].revents = 0; + if (pfd[i].fd < 0) + continue; + if (!(sought & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM | POLLWRBAND + | POLLPRI | POLLRDBAND))) + continue; + + h = (HANDLE) _get_osfhandle (pfd[i].fd); + assert (h != NULL); + if (IsSocketHandle (h)) + { + int requested = FD_CLOSE; + + /* see above; socket handles are mapped onto select. */ + if (sought & (POLLIN | POLLRDNORM)) + { + requested |= FD_READ | FD_ACCEPT; + FD_SET ((SOCKET) h, &rfds); + } + if (sought & (POLLOUT | POLLWRNORM | POLLWRBAND)) + { + requested |= FD_WRITE | FD_CONNECT; + FD_SET ((SOCKET) h, &wfds); + } + if (sought & (POLLPRI | POLLRDBAND)) + { + requested |= FD_OOB; + FD_SET ((SOCKET) h, &xfds); + } + + if (requested) + WSAEventSelect ((SOCKET) h, hEvent, requested); + } + else + { + /* Poll now. If we get an event, do not poll again. Also, + screen buffer handles are waitable, and they'll block until + a character is available. win32_compute_revents eliminates + bits for the "wrong" direction. */ + pfd[i].revents = win32_compute_revents (h, &sought); + if (sought) + handle_array[nhandles++] = h; + if (pfd[i].revents) + timeout = 0; + } + } + + if (select (0, &rfds, &wfds, &xfds, &tv0) > 0) + { + /* Do MsgWaitForMultipleObjects anyway to dispatch messages, but + no need to call select again. */ + poll_again = FALSE; + wait_timeout = 0; + } + else + { + poll_again = TRUE; + if (timeout == INFTIM) + wait_timeout = INFINITE; + else + wait_timeout = timeout; + } + + for (;;) + { + ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE, + wait_timeout, QS_ALLINPUT); + + if (ret == WAIT_OBJECT_0 + nhandles) + { + /* new input of some other kind */ + BOOL bRet; + while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + } + else + break; + } + + if (poll_again) + select (0, &rfds, &wfds, &xfds, &tv0); + + /* Place a sentinel at the end of the array. */ + handle_array[nhandles] = NULL; + nhandles = 1; + for (i = 0; i < nfd; i++) + { + int happened; + + if (pfd[i].fd < 0) + continue; + if (!(pfd[i].events & (POLLIN | POLLRDNORM | + POLLOUT | POLLWRNORM | POLLWRBAND))) + continue; + + h = (HANDLE) _get_osfhandle (pfd[i].fd); + if (h != handle_array[nhandles]) + { + /* It's a socket. */ + WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev); + WSAEventSelect ((SOCKET) h, 0, 0); + + /* If we're lucky, WSAEnumNetworkEvents already provided a way + to distinguish FD_READ and FD_ACCEPT; this saves a recv later. */ + if (FD_ISSET ((SOCKET) h, &rfds) + && !(ev.lNetworkEvents & (FD_READ | FD_ACCEPT))) + ev.lNetworkEvents |= FD_READ | FD_ACCEPT; + if (FD_ISSET ((SOCKET) h, &wfds)) + ev.lNetworkEvents |= FD_WRITE | FD_CONNECT; + if (FD_ISSET ((SOCKET) h, &xfds)) + ev.lNetworkEvents |= FD_OOB; + + happened = win32_compute_revents_socket ((SOCKET) h, pfd[i].events, + ev.lNetworkEvents); + } + else + { + /* Not a socket. */ + int sought = pfd[i].events; + happened = win32_compute_revents (h, &sought); + nhandles++; + } + + if ((pfd[i].revents |= happened) != 0) + rc++; + } + + return rc; +#endif +} diff --git a/compat/win32/sys/poll.h b/compat/win32/sys/poll.h new file mode 100644 index 0000000..b7aa59d --- /dev/null +++ b/compat/win32/sys/poll.h @@ -0,0 +1,53 @@ +/* Header for poll(2) emulation + Contributed by Paolo Bonzini. + + Copyright 2001, 2002, 2003, 2007, 2009, 2010 Free Software Foundation, Inc. + + This file is part of gnulib. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _GL_POLL_H +#define _GL_POLL_H + +/* fake a poll(2) environment */ +#define POLLIN 0x0001 /* any readable data available */ +#define POLLPRI 0x0002 /* OOB/Urgent readable data */ +#define POLLOUT 0x0004 /* file descriptor is writeable */ +#define POLLERR 0x0008 /* some poll error occurred */ +#define POLLHUP 0x0010 /* file descriptor was "hung up" */ +#define POLLNVAL 0x0020 /* requested events "invalid" */ +#define POLLRDNORM 0x0040 +#define POLLRDBAND 0x0080 +#define POLLWRNORM 0x0100 +#define POLLWRBAND 0x0200 + +struct pollfd +{ + int fd; /* which file descriptor to poll */ + short events; /* events we are interested in */ + short revents; /* events found on return */ +}; + +typedef unsigned long nfds_t; + +extern int poll (struct pollfd *pfd, nfds_t nfd, int timeout); + +/* Define INFTIM only if doing so conforms to POSIX. */ +#if !defined (_POSIX_C_SOURCE) && !defined (_XOPEN_SOURCE) +#define INFTIM (-1) +#endif + +#endif /* _GL_POLL_H */ diff --git a/compat/win32/syslog.c b/compat/win32/syslog.c new file mode 100644 index 0000000..42b95a9 --- /dev/null +++ b/compat/win32/syslog.c @@ -0,0 +1,72 @@ +#include "../../git-compat-util.h" +#include "../../strbuf.h" + +static HANDLE ms_eventlog; + +void openlog(const char *ident, int logopt, int facility) +{ + if (ms_eventlog) + return; + + ms_eventlog = RegisterEventSourceA(NULL, ident); + + if (!ms_eventlog) + warning("RegisterEventSource() failed: %lu", GetLastError()); +} + +void syslog(int priority, const char *fmt, ...) +{ + struct strbuf sb = STRBUF_INIT; + struct strbuf_expand_dict_entry dict[] = { + {"1", "% 1"}, + {NULL, NULL} + }; + WORD logtype; + char *str; + int str_len; + va_list ap; + + if (!ms_eventlog) + return; + + va_start(ap, fmt); + str_len = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + + if (str_len < 0) { + warning("vsnprintf failed: '%s'", strerror(errno)); + return; + } + + str = malloc(str_len + 1); + va_start(ap, fmt); + vsnprintf(str, str_len + 1, fmt, ap); + va_end(ap); + strbuf_expand(&sb, str, strbuf_expand_dict_cb, &dict); + free(str); + + switch (priority) { + case LOG_EMERG: + case LOG_ALERT: + case LOG_CRIT: + case LOG_ERR: + logtype = EVENTLOG_ERROR_TYPE; + break; + + case LOG_WARNING: + logtype = EVENTLOG_WARNING_TYPE; + break; + + case LOG_NOTICE: + case LOG_INFO: + case LOG_DEBUG: + default: + logtype = EVENTLOG_INFORMATION_TYPE; + break; + } + + ReportEventA(ms_eventlog, logtype, 0, 0, NULL, 1, 0, + (const char **)&sb.buf, NULL); + + strbuf_release(&sb); +} diff --git a/compat/win32/syslog.h b/compat/win32/syslog.h new file mode 100644 index 0000000..70daa7c --- /dev/null +++ b/compat/win32/syslog.h @@ -0,0 +1,20 @@ +#ifndef SYSLOG_H +#define SYSLOG_H + +#define LOG_PID 0x01 + +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 + +#define LOG_DAEMON (3<<3) + +void openlog(const char *ident, int logopt, int facility); +void syslog(int priority, const char *fmt, ...); + +#endif /* SYSLOG_H */ @@ -410,7 +410,7 @@ unsigned long git_config_ulong(const char *name, const char *value) return ret; } -int git_config_maybe_bool(const char *name, const char *value) +static int git_config_maybe_bool_text(const char *name, const char *value) { if (!value) return 1; @@ -427,9 +427,21 @@ int git_config_maybe_bool(const char *name, const char *value) return -1; } +int git_config_maybe_bool(const char *name, const char *value) +{ + int v = git_config_maybe_bool_text(name, value); + if (0 <= v) + return v; + if (!strcmp(value, "0")) + return 0; + if (!strcmp(value, "1")) + return 1; + return -1; +} + int git_config_bool_or_int(const char *name, const char *value, int *is_bool) { - int v = git_config_maybe_bool(name, value); + int v = git_config_maybe_bool_text(name, value); if (0 <= v) { *is_bool = 1; return v; @@ -489,6 +501,13 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.abbrevguard")) { + unique_abbrev_extra_length = git_config_int(var, value); + if (unique_abbrev_extra_length < 0) + unique_abbrev_extra_length = 0; + return 0; + } + if (!strcmp(var, "core.bare")) { is_bare_repository_cfg = git_config_bool(var, value); return 0; @@ -871,9 +890,7 @@ int git_config(config_fn_t fn, void *data) if (config_parameters) found += 1; - if (found == 0) - return -1; - return ret; + return ret == 0 ? found : ret; } /* diff --git a/config.mak.in b/config.mak.in index a0c34ee..9614973 100644 --- a/config.mak.in +++ b/config.mak.in @@ -27,7 +27,7 @@ VPATH = @srcdir@ export exec_prefix mandir export srcdir VPATH -ASCIIDOC8=@ASCIIDOC8@ +ASCIIDOC7=@ASCIIDOC7@ NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@ NO_OPENSSL=@NO_OPENSSL@ NO_CURL=@NO_CURL@ @@ -47,6 +47,8 @@ NO_C99_FORMAT=@NO_C99_FORMAT@ NO_HSTRERROR=@NO_HSTRERROR@ NO_STRCASESTR=@NO_STRCASESTR@ NO_STRTOK_R=@NO_STRTOK_R@ +NO_FNMATCH=@NO_FNMATCH@ +NO_FNMATCH_CASEFOLD=@NO_FNMATCH_CASEFOLD@ NO_MEMMEM=@NO_MEMMEM@ NO_STRLCPY=@NO_STRLCPY@ NO_UINTMAX_T=@NO_UINTMAX_T@ diff --git a/configure.ac b/configure.ac index 56731c3..5792425 100644 --- a/configure.ac +++ b/configure.ac @@ -282,7 +282,15 @@ GIT_PARSE_WITH(iconv)) GIT_PARSE_WITH_SET_MAKE_VAR(gitconfig, ETC_GITCONFIG, Use VALUE instead of /etc/gitconfig as the global git configuration file. - If VALUE is not fully qualified it will be interpretted + If VALUE is not fully qualified it will be interpreted + as a path relative to the computed prefix at runtime.) + +# +# Allow user to set ETC_GITATTRIBUTES variable +GIT_PARSE_WITH_SET_MAKE_VAR(gitattributes, ETC_GITATTRIBUTES, + Use VALUE instead of /etc/gitattributes as the + global git attributes file. + If VALUE is not fully qualified it will be interpreted as a path relative to the computed prefix at runtime.) # @@ -390,21 +398,21 @@ if test -n "$ASCIIDOC"; then AC_MSG_CHECKING([for asciidoc version]) asciidoc_version=`$ASCIIDOC --version 2>/dev/null` case "${asciidoc_version}" in - asciidoc' '8*) - ASCIIDOC8=YesPlease + asciidoc' '7*) + ASCIIDOC7=YesPlease AC_MSG_RESULT([${asciidoc_version} > 7]) ;; - asciidoc' '7*) - ASCIIDOC8= + asciidoc' '8*) + ASCIIDOC7= AC_MSG_RESULT([${asciidoc_version}]) ;; *) - ASCIIDOC8= + ASCIIDOC7= AC_MSG_RESULT([${asciidoc_version} (unknown)]) ;; esac fi -AC_SUBST(ASCIIDOC8) +AC_SUBST(ASCIIDOC7) ## Checks for libraries. @@ -609,6 +617,18 @@ AC_CHECK_HEADER([sys/select.h], [NO_SYS_SELECT_H=UnfortunatelyYes]) AC_SUBST(NO_SYS_SELECT_H) # +# Define NO_SYS_POLL_H if you don't have sys/poll.h +AC_CHECK_HEADER([sys/poll.h], +[NO_SYS_POLL_H=], +[NO_SYS_POLL_H=UnfortunatelyYes]) +AC_SUBST(NO_SYS_POLL_H) +# +# Define NO_INTTYPES_H if you don't have inttypes.h +AC_CHECK_HEADER([inttypes.h], +[NO_INTTYPES_H=], +[NO_INTTYPES_H=UnfortunatelyYes]) +AC_SUBST(NO_INTTYPES_H) +# # Define OLD_ICONV if your library has an old iconv(), where the second # (input buffer pointer) parameter is declared with type (const char **). AC_DEFUN([OLDICONVTEST_SRC], [[ @@ -810,6 +830,34 @@ GIT_CHECK_FUNC(strtok_r, [NO_STRTOK_R=YesPlease]) AC_SUBST(NO_STRTOK_R) # +# Define NO_FNMATCH if you don't have fnmatch +GIT_CHECK_FUNC(fnmatch, +[NO_FNMATCH=], +[NO_FNMATCH=YesPlease]) +AC_SUBST(NO_FNMATCH) +# +# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the +# FNM_CASEFOLD GNU extension. +AC_CACHE_CHECK([whether the fnmatch function supports the FNMATCH_CASEFOLD GNU extension], + [ac_cv_c_excellent_fnmatch], [ +AC_EGREP_CPP(yippeeyeswehaveit, + AC_LANG_PROGRAM([ +#include <fnmatch.h> +], +[#ifdef FNM_CASEFOLD +yippeeyeswehaveit +#endif +]), + [ac_cv_c_excellent_fnmatch=yes], + [ac_cv_c_excellent_fnmatch=no]) +]) +if test $ac_cv_c_excellent_fnmatch = yes; then + NO_FNMATCH_CASEFOLD= +else + NO_FNMATCH_CASEFOLD=YesPlease +fi +AC_SUBST(NO_FNMATCH_CASEFOLD) +# # Define NO_MEMMEM if you don't have memmem. GIT_CHECK_FUNC(memmem, [NO_MEMMEM=], @@ -860,6 +908,12 @@ GIT_CHECK_FUNC(mkstemps, [NO_MKSTEMPS=YesPlease]) AC_SUBST(NO_MKSTEMPS) # +# Define NO_INITGROUPS if you don't have initgroups in the C library. +GIT_CHECK_FUNC(initgroups, +[NO_INITGROUPS=], +[NO_INITGROUPS=YesPlease]) +AC_SUBST(NO_INITGROUPS) +# # # Define NO_MMAP if you want to avoid mmap. # diff --git a/contrib/ciabot/ciabot.py b/contrib/ciabot/ciabot.py index d0627e0..9775dff 100755 --- a/contrib/ciabot/ciabot.py +++ b/contrib/ciabot/ciabot.py @@ -122,7 +122,7 @@ def report(refname, merged): branch = os.path.basename(refname) # Compute a shortnane for the revision - rev = do("git describe ${merged} 2>/dev/null") or merged[:12] + rev = do("git describe '"+ merged +"' 2>/dev/null") or merged[:12] # Extract the neta-information for the commit rawcommit = do("git cat-file commit " + merged) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index d3037fc..604fa79 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -21,6 +21,11 @@ # 2) Added the following line to your .bashrc: # source ~/.git-completion.sh # +# Or, add the following lines to your .zshrc: +# autoload bashcompinit +# bashcompinit +# source ~/.git-completion.sh +# # 3) Consider changing your PS1 to also show the current branch: # PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' # @@ -138,11 +143,12 @@ __git_ps1_show_upstream () # get the upstream from the "git-svn-id: ..." in a commit message # (git-svn uses essentially the same procedure internally) local svn_upstream=($(git log --first-parent -1 \ - --grep="^git-svn-id: \(${svn_url_pattern:2}\)" 2>/dev/null)) + --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null)) if [[ 0 -ne ${#svn_upstream[@]} ]]; then svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]} svn_upstream=${svn_upstream%@*} - for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do + local n_stop="${#svn_remote[@]}" + for ((n=1; n <= n_stop; ++n)); do svn_upstream=${svn_upstream#${svn_remote[$n]}} done @@ -380,16 +386,19 @@ __git_tags () done } -# __git_refs accepts 0 or 1 arguments (to pass to __gitdir) +# __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments +# presence of 2nd argument means use the guess heuristic employed +# by checkout for tracking branches __git_refs () { - local i is_hash=y dir="$(__gitdir "${1-}")" + local i is_hash=y dir="$(__gitdir "${1-}")" track="${2-}" local cur="${COMP_WORDS[COMP_CWORD]}" format refs if [ -d "$dir" ]; then case "$cur" in refs|refs/*) format="refname" refs="${cur%/*}" + track="" ;; *) for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do @@ -401,6 +410,21 @@ __git_refs () esac git --git-dir="$dir" for-each-ref --format="%($format)" \ $refs + if [ -n "$track" ]; then + # employ the heuristic used by git checkout + # Try to find a remote branch that matches the completion word + # but only output if the branch name is unique + local ref entry + git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \ + "refs/remotes/" | \ + while read entry; do + eval "$entry" + ref="${ref#*/}" + if [[ "$ref" == "$cur"* ]]; then + echo "$ref" + fi + done | uniq -u + fi return fi for i in $(git ls-remote "$dir" 2>/dev/null); do @@ -750,6 +774,19 @@ __git_compute_porcelain_commands () : ${__git_porcelain_commands:=$(__git_list_porcelain_commands)} } +__git_pretty_aliases () +{ + local i IFS=$'\n' + for i in $(git --git-dir="$(__gitdir)" config --get-regexp "pretty\..*" 2>/dev/null); do + case "$i" in + pretty.*) + i="${i#pretty.}" + echo "${i/ */}" + ;; + esac + done +} + __git_aliases () { local i IFS=$'\n' @@ -907,12 +944,16 @@ _git_bisect () local subcommands="start bad good skip reset visualize replay log run" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then - __gitcomp "$subcommands" + if [ -f "$(__gitdir)"/BISECT_START ]; then + __gitcomp "$subcommands" + else + __gitcomp "replay start" + fi return fi case "$subcommand" in - bad|good|reset|skip) + bad|good|reset|skip|start) __gitcomp "$(__git_refs)" ;; *) @@ -988,7 +1029,13 @@ _git_checkout () " ;; *) - __gitcomp "$(__git_refs)" + # check if --track, --no-track, or --no-guess was specified + # if so, disable DWIM mode + local flags="--track --no-track --no-guess" track=1 + if [ -n "$(__git_find_on_cmdline "$flags")" ]; then + track='' + fi + __gitcomp "$(__git_refs '' $track)" ;; esac } @@ -1368,12 +1415,12 @@ _git_log () fi case "$cur" in --pretty=*) - __gitcomp "$__git_log_pretty_formats + __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) " "" "${cur##--pretty=}" return ;; --format=*) - __gitcomp "$__git_log_pretty_formats + __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) " "" "${cur##--format=}" return ;; @@ -1468,18 +1515,50 @@ _git_name_rev () _git_notes () { - local subcommands="edit show" - if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then - __gitcomp "$subcommands" - return - fi + local subcommands='add append copy edit list prune remove show' + local subcommand="$(__git_find_on_cmdline "$subcommands")" + local cur="${COMP_WORDS[COMP_CWORD]}" - case "${COMP_WORDS[COMP_CWORD-1]}" in - -m|-F) - COMPREPLY=() + case "$subcommand,$cur" in + ,--*) + __gitcomp '--ref' + ;; + ,*) + case "${COMP_WORDS[COMP_CWORD-1]}" in + --ref) + __gitcomp "$(__git_refs)" + ;; + *) + __gitcomp "$subcommands --ref" + ;; + esac + ;; + add,--reuse-message=*|append,--reuse-message=*) + __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}" + ;; + add,--reedit-message=*|append,--reedit-message=*) + __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}" + ;; + add,--*|append,--*) + __gitcomp '--file= --message= --reedit-message= + --reuse-message=' + ;; + copy,--*) + __gitcomp '--stdin' + ;; + prune,--*) + __gitcomp '--dry-run --verbose' + ;; + prune,*) ;; *) - __gitcomp "$(__git_refs)" + case "${COMP_WORDS[COMP_CWORD-1]}" in + -m|-F) + ;; + *) + __gitcomp "$(__git_refs)" + ;; + esac ;; esac } @@ -2100,12 +2179,12 @@ _git_show () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --pretty=*) - __gitcomp "$__git_log_pretty_formats + __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) " "" "${cur##--pretty=}" return ;; --format=*) - __gitcomp "$__git_log_pretty_formats + __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) " "" "${cur##--format=}" return ;; @@ -2339,6 +2418,11 @@ _git () { local i c=1 command __git_dir + if [[ -n ${ZSH_VERSION-} ]]; then + emulate -L bash + setopt KSH_TYPESET + fi + while [ $c -lt $COMP_CWORD ]; do i="${COMP_WORDS[c]}" case "$i" in @@ -2372,17 +2456,22 @@ _git () fi local completion_func="_git_${command//-/_}" - declare -F $completion_func >/dev/null && $completion_func && return + declare -f $completion_func >/dev/null && $completion_func && return local expansion=$(__git_aliased_command "$command") if [ -n "$expansion" ]; then completion_func="_git_${expansion//-/_}" - declare -F $completion_func >/dev/null && $completion_func + declare -f $completion_func >/dev/null && $completion_func fi } _gitk () { + if [[ -n ${ZSH_VERSION-} ]]; then + emulate -L bash + setopt KSH_TYPESET + fi + __git_has_doubledash && return local cur="${COMP_WORDS[COMP_CWORD]}" @@ -2417,3 +2506,29 @@ if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \ || complete -o default -o nospace -F _git git.exe fi + +if [[ -n ${ZSH_VERSION-} ]]; then + shopt () { + local option + if [ $# -ne 2 ]; then + echo "USAGE: $0 (-q|-s|-u) <option>" >&2 + return 1 + fi + case "$2" in + nullglob) + option="$2" + ;; + *) + echo "$0: invalid option: $2" >&2 + return 1 + esac + case "$1" in + -q) setopt | grep -q "$option" ;; + -u) unsetopt "$option" ;; + -s) setopt "$option" ;; + *) + echo "$0: invalid flag: $1" >&2 + return 1 + esac + } +fi diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c index cd10dbc..3140e40 100644 --- a/contrib/examples/builtin-fetch--tool.c +++ b/contrib/examples/builtin-fetch--tool.c @@ -148,7 +148,7 @@ static int append_fetch_head(FILE *fp, what = remote_name + 10; } else if (!strncmp(remote_name, "refs/remotes/", 13)) { - kind = "remote branch"; + kind = "remote-tracking branch"; what = remote_name + 13; } else { diff --git a/contrib/examples/git-svnimport.perl b/contrib/examples/git-svnimport.perl index 4576c4a..b09ff8f 100755 --- a/contrib/examples/git-svnimport.perl +++ b/contrib/examples/git-svnimport.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # This tool is copyright (c) 2005, Matthias Urlichs. # It is released under the Gnu Public License, version 2. @@ -289,7 +289,7 @@ my $current_rev = $opt_s || 1; unless(-d $git_dir) { system("git init"); die "Cannot init the GIT db at $git_tree: $?\n" if $?; - system("git read-tree"); + system("git read-tree --empty"); die "Cannot init an empty tree: $?\n" if $?; $last_branch = $opt_o; diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl index 3a5da4a..7f3afa5 100755 --- a/contrib/fast-import/import-directories.perl +++ b/contrib/fast-import/import-directories.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # # Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se> # @@ -140,6 +140,7 @@ by whitespace or other characters. # Globals use strict; +use warnings; use integer; my $crlfmode = 0; my @revs; diff --git a/contrib/git-shell-commands/README b/contrib/git-shell-commands/README new file mode 100644 index 0000000..438463b --- /dev/null +++ b/contrib/git-shell-commands/README @@ -0,0 +1,18 @@ +Sample programs callable through git-shell. Place a directory named +'git-shell-commands' in the home directory of a user whose shell is +git-shell. Then anyone logging in as that user will be able to run +executables in the 'git-shell-commands' directory. + +Provided commands: + +help: Prints out the names of available commands. When run +interactively, git-shell will automatically run 'help' on startup, +provided it exists. + +list: Displays any bare repository whose name ends with ".git" under +user's home directory. No other git repositories are visible, +although they might be clonable through git-shell. 'list' is designed +to minimize the number of calls to git that must be made in finding +available repositories; if your setup has additional repositories that +should be user-discoverable, you may wish to modify 'list' +accordingly. diff --git a/contrib/git-shell-commands/help b/contrib/git-shell-commands/help new file mode 100755 index 0000000..535770c --- /dev/null +++ b/contrib/git-shell-commands/help @@ -0,0 +1,18 @@ +#!/bin/sh + +if tty -s +then + echo "Run 'help' for help, or 'exit' to leave. Available commands:" +else + echo "Run 'help' for help. Available commands:" +fi + +cd "$(dirname "$0")" + +for cmd in * +do + case "$cmd" in + help) ;; + *) [ -f "$cmd" ] && [ -x "$cmd" ] && echo "$cmd" ;; + esac +done diff --git a/contrib/git-shell-commands/list b/contrib/git-shell-commands/list new file mode 100755 index 0000000..6f89938 --- /dev/null +++ b/contrib/git-shell-commands/list @@ -0,0 +1,10 @@ +#!/bin/sh + +print_if_bare_repo=' + if "$(git --git-dir="$1" rev-parse --is-bare-repository)" = true + then + printf "%s\n" "${1#./}" + fi +' + +find -type d -name "*.git" -exec sh -c "$print_if_bare_repo" -- \{} \; -prune 2>/dev/null diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 85724bf..f99ea95 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -144,13 +144,13 @@ prep_for_email() short_refname=${refname##refs/remotes/} echo >&2 "*** Push-update of tracking branch, $refname" echo >&2 "*** - no email generated." - exit 0 + return 1 ;; *) # Anything else (is there anything else?) echo >&2 "*** Unknown type of update to $refname ($rev_type)" echo >&2 "*** - no email generated" - return 0 + return 1 ;; esac @@ -166,10 +166,10 @@ prep_for_email() esac echo >&2 "*** $config_name is not set so no email will be sent" echo >&2 "*** for $refname update $oldrev->$newrev" - return 0 + return 1 fi - return 1 + return 0 } # @@ -3,8 +3,7 @@ #include "exec_cmd.h" #include "run-command.h" #include "strbuf.h" - -#include <syslog.h> +#include "string-list.h" #ifndef HOST_NAME_MAX #define HOST_NAME_MAX 256 @@ -14,6 +13,10 @@ #define NI_MAXSERV 32 #endif +#ifdef NO_INITGROUPS +#define initgroups(x, y) (0) /* nothing */ +#endif + static int log_syslog; static int verbose; static int reuseaddr; @@ -24,10 +27,10 @@ static const char daemon_usage[] = " [--strict-paths] [--base-path=<path>] [--base-path-relaxed]\n" " [--user-path | --user-path=<path>]\n" " [--interpolated-path=<path>]\n" -" [--reuseaddr] [--detach] [--pid-file=<file>]\n" +" [--reuseaddr] [--pid-file=<file>]\n" " [--(enable|disable|allow-override|forbid-override)=<service>]\n" " [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n" -" [--user=<user> [--group=<group>]]\n" +" [--detach] [--user=<user> [--group=<group>]]\n" " [<directory>...]"; /* List of acceptable pathname prefixes */ @@ -68,12 +71,14 @@ static void logreport(int priority, const char *err, va_list params) syslog(priority, "%s", buf); } else { /* - * Since stderr is set to linebuffered mode, the + * Since stderr is set to buffered mode, the * logging of different processes will not overlap + * unless they overflow the (rather big) buffers. */ fprintf(stderr, "[%"PRIuMAX"] ", (uintmax_t)getpid()); vfprintf(stderr, err, params); fputc('\n', stderr); + fflush(stderr); } } @@ -515,37 +520,14 @@ static void parse_host_arg(char *extra_args, int buflen) } -static int execute(struct sockaddr *addr) +static int execute(void) { static char line[1000]; int pktlen, len, i; + char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT"); - if (addr) { - char addrbuf[256] = ""; - int port = -1; - - if (addr->sa_family == AF_INET) { - struct sockaddr_in *sin_addr = (void *) addr; - inet_ntop(addr->sa_family, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf)); - port = ntohs(sin_addr->sin_port); -#ifndef NO_IPV6 - } else if (addr && addr->sa_family == AF_INET6) { - struct sockaddr_in6 *sin6_addr = (void *) addr; - - char *buf = addrbuf; - *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */ - inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(addrbuf) - 1); - strcat(buf, "]"); - - port = ntohs(sin6_addr->sin6_port); -#endif - } - loginfo("Connection from %s:%d", addrbuf, port); - setenv("REMOTE_ADDR", addrbuf, 1); - } - else { - unsetenv("REMOTE_ADDR"); - } + if (addr) + loginfo("Connection from %s:%s", addr, port); alarm(init_timeout ? init_timeout : timeout); pktlen = packet_read_line(0, line, sizeof(line)); @@ -615,17 +597,17 @@ static unsigned int live_children; static struct child { struct child *next; - pid_t pid; + struct child_process cld; struct sockaddr_storage address; } *firstborn; -static void add_child(pid_t pid, struct sockaddr *addr, int addrlen) +static void add_child(struct child_process *cld, struct sockaddr *addr, socklen_t addrlen) { struct child *newborn, **cradle; newborn = xcalloc(1, sizeof(*newborn)); live_children++; - newborn->pid = pid; + memcpy(&newborn->cld, cld, sizeof(*cld)); memcpy(&newborn->address, addr, addrlen); for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next) if (!addrcmp(&(*cradle)->address, &newborn->address)) @@ -634,19 +616,6 @@ static void add_child(pid_t pid, struct sockaddr *addr, int addrlen) *cradle = newborn; } -static void remove_child(pid_t pid) -{ - struct child **cradle, *blanket; - - for (cradle = &firstborn; (blanket = *cradle); cradle = &blanket->next) - if (blanket->pid == pid) { - *cradle = blanket->next; - live_children--; - free(blanket); - break; - } -} - /* * This gets called if the number of connections grows * past "max_connections". @@ -662,7 +631,7 @@ static void kill_some_child(void) for (; (next = blanket->next); blanket = next) if (!addrcmp(&blanket->address, &next->address)) { - kill(blanket->pid, SIGTERM); + kill(blanket->cld.pid, SIGTERM); break; } } @@ -672,18 +641,28 @@ static void check_dead_children(void) int status; pid_t pid; - while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { - const char *dead = ""; - remove_child(pid); - if (!WIFEXITED(status) || (WEXITSTATUS(status) > 0)) - dead = " (with error)"; - loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead); - } + struct child **cradle, *blanket; + for (cradle = &firstborn; (blanket = *cradle);) + if ((pid = waitpid(blanket->cld.pid, &status, WNOHANG)) > 1) { + const char *dead = ""; + if (status) + dead = " (with error)"; + loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead); + + /* remove the child */ + *cradle = blanket->next; + live_children--; + free(blanket); + } else + cradle = &blanket->next; } -static void handle(int incoming, struct sockaddr *addr, int addrlen) +static char **cld_argv; +static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen) { - pid_t pid; + struct child_process cld = { 0 }; + char addrbuf[300] = "REMOTE_ADDR=", portbuf[300]; + char *env[] = { addrbuf, portbuf, NULL }; if (max_connections && live_children >= max_connections) { kill_some_child(); @@ -696,22 +675,37 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen) } } - if ((pid = fork())) { - close(incoming); - if (pid < 0) { - logerror("Couldn't fork %s", strerror(errno)); - return; - } + if (addr->sa_family == AF_INET) { + struct sockaddr_in *sin_addr = (void *) addr; + inet_ntop(addr->sa_family, &sin_addr->sin_addr, addrbuf + 12, + sizeof(addrbuf) - 12); + snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d", + ntohs(sin_addr->sin_port)); +#ifndef NO_IPV6 + } else if (addr && addr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6_addr = (void *) addr; - add_child(pid, addr, addrlen); - return; + char *buf = addrbuf + 12; + *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */ + inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, + sizeof(addrbuf) - 13); + strcat(buf, "]"); + + snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d", + ntohs(sin6_addr->sin6_port)); +#endif } - dup2(incoming, 0); - dup2(incoming, 1); - close(incoming); + cld.env = (const char **)env; + cld.argv = (const char **)cld_argv; + cld.in = incoming; + cld.out = dup(incoming); - exit(execute(addr)); + if (start_command(&cld)) + logerror("unable to fork"); + else + add_child(&cld, addr, addrlen); + close(incoming); } static void child_handler(int signo) @@ -734,11 +728,17 @@ static int set_reuse_addr(int sockfd) &on, sizeof(on)); } +struct socketlist { + int *list; + size_t nr; + size_t alloc; +}; + #ifndef NO_IPV6 -static int socksetup(char *listen_addr, int listen_port, int **socklist_p) +static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist) { - int socknum = 0, *socklist = NULL; + int socknum = 0; int maxfd = -1; char pbuf[NI_MAXSERV]; struct addrinfo hints, *ai0, *ai; @@ -753,8 +753,10 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) hints.ai_flags = AI_PASSIVE; gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0); - if (gai) - die("getaddrinfo() failed: %s", gai_strerror(gai)); + if (gai) { + logerror("getaddrinfo() for %s failed: %s", listen_addr, gai_strerror(gai)); + return 0; + } for (ai = ai0; ai; ai = ai->ai_next) { int sockfd; @@ -795,8 +797,9 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) if (flags >= 0) fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC); - socklist = xrealloc(socklist, sizeof(int) * (socknum + 1)); - socklist[socknum++] = sockfd; + ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc); + socklist->list[socklist->nr++] = sockfd; + socknum++; if (maxfd < sockfd) maxfd = sockfd; @@ -804,13 +807,12 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) freeaddrinfo(ai0); - *socklist_p = socklist; return socknum; } #else /* NO_IPV6 */ -static int socksetup(char *listen_addr, int listen_port, int **socklist_p) +static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist) { struct sockaddr_in sin; int sockfd; @@ -851,22 +853,39 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) if (flags >= 0) fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC); - *socklist_p = xmalloc(sizeof(int)); - **socklist_p = sockfd; + ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc); + socklist->list[socklist->nr++] = sockfd; return 1; } #endif -static int service_loop(int socknum, int *socklist) +static void socksetup(struct string_list *listen_addr, int listen_port, struct socketlist *socklist) +{ + if (!listen_addr->nr) + setup_named_sock(NULL, listen_port, socklist); + else { + int i, socknum; + for (i = 0; i < listen_addr->nr; i++) { + socknum = setup_named_sock(listen_addr->items[i].string, + listen_port, socklist); + + if (socknum == 0) + logerror("unable to allocate any listen sockets for host %s on port %u", + listen_addr->items[i].string, listen_port); + } + } +} + +static int service_loop(struct socketlist *socklist) { struct pollfd *pfd; int i; - pfd = xcalloc(socknum, sizeof(struct pollfd)); + pfd = xcalloc(socklist->nr, sizeof(struct pollfd)); - for (i = 0; i < socknum; i++) { - pfd[i].fd = socklist[i]; + for (i = 0; i < socklist->nr; i++) { + pfd[i].fd = socklist->list[i]; pfd[i].events = POLLIN; } @@ -877,7 +896,7 @@ static int service_loop(int socknum, int *socklist) check_dead_children(); - if (poll(pfd, socknum, -1) < 0) { + if (poll(pfd, socklist->nr, -1) < 0) { if (errno != EINTR) { logerror("Poll failed, resuming: %s", strerror(errno)); @@ -886,11 +905,17 @@ static int service_loop(int socknum, int *socklist) continue; } - for (i = 0; i < socknum; i++) { + for (i = 0; i < socklist->nr; i++) { if (pfd[i].revents & POLLIN) { - struct sockaddr_storage ss; - unsigned int sslen = sizeof(ss); - int incoming = accept(pfd[i].fd, (struct sockaddr *)&ss, &sslen); + union { + struct sockaddr sa; + struct sockaddr_in sai; +#ifndef NO_IPV6 + struct sockaddr_in6 sai6; +#endif + } ss; + socklen_t sslen = sizeof(ss); + int incoming = accept(pfd[i].fd, &ss.sa, &sslen); if (incoming < 0) { switch (errno) { case EAGAIN: @@ -901,7 +926,7 @@ static int service_loop(int socknum, int *socklist) die_errno("accept returned"); } } - handle(incoming, (struct sockaddr *)&ss, sslen); + handle(incoming, &ss.sa, sslen); } } } @@ -919,6 +944,62 @@ static void sanitize_stdfds(void) close(fd); } +#ifdef NO_POSIX_GOODIES + +struct credentials; + +static void drop_privileges(struct credentials *cred) +{ + /* nothing */ +} + +static void daemonize(void) +{ + die("--detach not supported on this platform"); +} + +static struct credentials *prepare_credentials(const char *user_name, + const char *group_name) +{ + die("--user not supported on this platform"); +} + +#else + +struct credentials { + struct passwd *pass; + gid_t gid; +}; + +static void drop_privileges(struct credentials *cred) +{ + if (cred && (initgroups(cred->pass->pw_name, cred->gid) || + setgid (cred->gid) || setuid(cred->pass->pw_uid))) + die("cannot drop privileges"); +} + +static struct credentials *prepare_credentials(const char *user_name, + const char *group_name) +{ + static struct credentials c; + + c.pass = getpwnam(user_name); + if (!c.pass) + die("user not found - %s", user_name); + + if (!group_name) + c.gid = c.pass->pw_gid; + else { + struct group *group = getgrnam(group_name); + if (!group) + die("group not found - %s", group_name); + + c.gid = group->gr_gid; + } + + return &c; +} + static void daemonize(void) { switch (fork()) { @@ -936,6 +1017,7 @@ static void daemonize(void) close(2); sanitize_stdfds(); } +#endif static void store_pid(const char *path) { @@ -946,33 +1028,29 @@ static void store_pid(const char *path) die_errno("failed to write pid file '%s'", path); } -static int serve(char *listen_addr, int listen_port, struct passwd *pass, gid_t gid) +static int serve(struct string_list *listen_addr, int listen_port, + struct credentials *cred) { - int socknum, *socklist; + struct socketlist socklist = { NULL, 0, 0 }; - socknum = socksetup(listen_addr, listen_port, &socklist); - if (socknum == 0) - die("unable to allocate any listen sockets on host %s port %u", - listen_addr, listen_port); + socksetup(listen_addr, listen_port, &socklist); + if (socklist.nr == 0) + die("unable to allocate any listen sockets on port %u", + listen_port); - if (pass && gid && - (initgroups(pass->pw_name, gid) || setgid (gid) || - setuid(pass->pw_uid))) - die("cannot drop privileges"); + drop_privileges(cred); - return service_loop(socknum, socklist); + return service_loop(&socklist); } int main(int argc, char **argv) { int listen_port = 0; - char *listen_addr = NULL; - int inetd_mode = 0; + struct string_list listen_addr = STRING_LIST_INIT_NODUP; + int serve_mode = 0, inetd_mode = 0; const char *pid_file = NULL, *user_name = NULL, *group_name = NULL; int detach = 0; - struct passwd *pass = NULL; - struct group *group; - gid_t gid = 0; + struct credentials *cred = NULL; int i; git_extract_argv0_path(argv[0]); @@ -981,7 +1059,7 @@ int main(int argc, char **argv) char *arg = argv[i]; if (!prefixcmp(arg, "--listen=")) { - listen_addr = xstrdup_tolower(arg + 9); + string_list_append(&listen_addr, xstrdup_tolower(arg + 9)); continue; } if (!prefixcmp(arg, "--port=")) { @@ -993,6 +1071,10 @@ int main(int argc, char **argv) continue; } } + if (!strcmp(arg, "--serve")) { + serve_mode = 1; + continue; + } if (!strcmp(arg, "--inetd")) { inetd_mode = 1; log_syslog = 1; @@ -1101,12 +1183,12 @@ int main(int argc, char **argv) set_die_routine(daemon_die); } else /* avoid splitting a message in the middle */ - setvbuf(stderr, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOFBF, 4096); - if (inetd_mode && (group_name || user_name)) - die("--user and --group are incompatible with --inetd"); + if (inetd_mode && (detach || group_name || user_name)) + die("--detach, --user and --group are incompatible with --inetd"); - if (inetd_mode && (listen_port || listen_addr)) + if (inetd_mode && (listen_port || (listen_addr.nr > 0))) die("--listen= and --port= are incompatible with --inetd"); else if (listen_port == 0) listen_port = DEFAULT_GIT_PORT; @@ -1114,21 +1196,8 @@ int main(int argc, char **argv) if (group_name && !user_name) die("--group supplied without --user"); - if (user_name) { - pass = getpwnam(user_name); - if (!pass) - die("user not found - %s", user_name); - - if (!group_name) - gid = pass->pw_gid; - else { - group = getgrnam(group_name); - if (!group) - die("group not found - %s", group_name); - - gid = group->gr_gid; - } - } + if (user_name) + cred = prepare_credentials(user_name, group_name); if (strict_paths && (!ok_paths || !*ok_paths)) die("option --strict-paths requires a whitelist"); @@ -1138,19 +1207,13 @@ int main(int argc, char **argv) base_path); if (inetd_mode) { - struct sockaddr_storage ss; - struct sockaddr *peer = (struct sockaddr *)&ss; - socklen_t slen = sizeof(ss); - if (!freopen("/dev/null", "w", stderr)) die_errno("failed to redirect stderr to /dev/null"); - - if (getpeername(0, peer, &slen)) - peer = NULL; - - return execute(peer); } + if (inetd_mode || serve_mode) + return execute(); + if (detach) { daemonize(); loginfo("Ready to rumble"); @@ -1161,5 +1224,12 @@ int main(int argc, char **argv) if (pid_file) store_pid(pid_file); - return serve(listen_addr, listen_port, pass, gid); + /* prepare argv for serving-processes */ + cld_argv = xmalloc(sizeof (char *) * (argc + 2)); + for (i = 0; i < argc; ++i) + cld_argv[i] = argv[i]; + cld_argv[argc] = "--serve"; + cld_argv[argc+1] = NULL; + + return serve(&listen_addr, listen_port, cred); } @@ -2158,7 +2158,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, ecbdata.ws_rule = data.ws_rule; check_blank_at_eof(&mf1, &mf2, &ecbdata); - blank_at_eof = ecbdata.blank_at_eof_in_preimage; + blank_at_eof = ecbdata.blank_at_eof_in_postimage; if (blank_at_eof) { static char *err; @@ -2391,10 +2391,14 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) } else { enum object_type type; - if (size_only) + if (size_only) { type = sha1_object_info(s->sha1, &s->size); - else { + if (type < 0) + die("unable to read %s", sha1_to_hex(s->sha1)); + } else { s->data = read_sha1_file(s->sha1, &type, &s->size); + if (!s->data) + die("unable to read %s", sha1_to_hex(s->sha1)); s->should_free = 1; } } @@ -3145,20 +3149,23 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) return stat_opt(options, av); /* renames options */ - else if (!prefixcmp(arg, "-B")) { + else if (!prefixcmp(arg, "-B") || !prefixcmp(arg, "--break-rewrites=") || + !strcmp(arg, "--break-rewrites")) { if ((options->break_opt = diff_scoreopt_parse(arg)) == -1) - return -1; + return error("invalid argument to -B: %s", arg+2); } - else if (!prefixcmp(arg, "-M")) { + else if (!prefixcmp(arg, "-M") || !prefixcmp(arg, "--detect-renames=") || + !strcmp(arg, "--detect-renames")) { if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) - return -1; + return error("invalid argument to -M: %s", arg+2); options->detect_rename = DIFF_DETECT_RENAME; } - else if (!prefixcmp(arg, "-C")) { + else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--detect-copies=") || + !strcmp(arg, "--detect-copies")) { if (options->detect_rename == DIFF_DETECT_COPY) DIFF_OPT_SET(options, FIND_COPIES_HARDER); if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) - return -1; + return error("invalid argument to -C: %s", arg+2); options->detect_rename = DIFF_DETECT_COPY; } else if (!strcmp(arg, "--no-renames")) @@ -3276,12 +3283,17 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } else if ((argcount = short_opt('S', av, &optarg))) { options->pickaxe = optarg; + options->pickaxe_opts |= DIFF_PICKAXE_KIND_S; + return argcount; + } else if ((argcount = short_opt('G', av, &optarg))) { + options->pickaxe = optarg; + options->pickaxe_opts |= DIFF_PICKAXE_KIND_G; return argcount; } else if (!strcmp(arg, "--pickaxe-all")) - options->pickaxe_opts = DIFF_PICKAXE_ALL; + options->pickaxe_opts |= DIFF_PICKAXE_ALL; else if (!strcmp(arg, "--pickaxe-regex")) - options->pickaxe_opts = DIFF_PICKAXE_REGEX; + options->pickaxe_opts |= DIFF_PICKAXE_REGEX; else if ((argcount = short_opt('O', av, &optarg))) { options->orderfile = optarg; return argcount; @@ -3323,7 +3335,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) return 1; } -static int parse_num(const char **cp_p) +int parse_rename_score(const char **cp_p) { unsigned long num, scale; int ch, dot; @@ -3366,10 +3378,26 @@ static int diff_scoreopt_parse(const char *opt) if (*opt++ != '-') return -1; cmd = *opt++; + if (cmd == '-') { + /* convert the long-form arguments into short-form versions */ + if (!prefixcmp(opt, "break-rewrites")) { + opt += strlen("break-rewrites"); + if (*opt == 0 || *opt++ == '=') + cmd = 'B'; + } else if (!prefixcmp(opt, "detect-copies")) { + opt += strlen("detect-copies"); + if (*opt == 0 || *opt++ == '=') + cmd = 'C'; + } else if (!prefixcmp(opt, "detect-renames")) { + opt += strlen("detect-renames"); + if (*opt == 0 || *opt++ == '=') + cmd = 'M'; + } + } if (cmd != 'M' && cmd != 'C' && cmd != 'B') return -1; /* that is not a -M, -C nor -B option */ - opt1 = parse_num(&opt); + opt1 = parse_rename_score(&opt); if (cmd != 'B') opt2 = 0; else { @@ -3379,7 +3407,7 @@ static int diff_scoreopt_parse(const char *opt) return -1; /* we expect -B80/99 or -B80 */ else { opt++; - opt2 = parse_num(&opt); + opt2 = parse_rename_score(&opt); } } if (*opt != 0) @@ -4181,7 +4209,7 @@ void diffcore_std(struct diff_options *options) diffcore_merge_broken(); } if (options->pickaxe) - diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); + diffcore_pickaxe(options); if (options->orderfile) diffcore_order(options->orderfile); if (!options->found_follow) @@ -238,6 +238,9 @@ extern int diff_setup_done(struct diff_options *); #define DIFF_PICKAXE_ALL 1 #define DIFF_PICKAXE_REGEX 2 +#define DIFF_PICKAXE_KIND_S 4 /* traditional plumbing counter */ +#define DIFF_PICKAXE_KIND_G 8 /* grep in the patch */ + extern void diffcore_std(struct diff_options *); extern void diffcore_fix_diff_index(struct diff_options *); @@ -312,4 +315,6 @@ extern size_t fill_textconv(struct userdiff_driver *driver, extern struct userdiff_driver *get_textconv(struct diff_filespec *one); +extern int parse_rename_score(const char **cp_p); + #endif /* DIFF_H */ diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index 9c6544d..ea03b91 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -1,9 +1,148 @@ /* * Copyright (C) 2005 Junio C Hamano + * Copyright (C) 2010 Google Inc. */ #include "cache.h" #include "diff.h" #include "diffcore.h" +#include "xdiff-interface.h" + +struct diffgrep_cb { + regex_t *regexp; + int hit; +}; + +static void diffgrep_consume(void *priv, char *line, unsigned long len) +{ + struct diffgrep_cb *data = priv; + regmatch_t regmatch; + int hold; + + if (line[0] != '+' && line[0] != '-') + return; + if (data->hit) + /* + * NEEDSWORK: we should have a way to terminate the + * caller early. + */ + return; + /* Yuck -- line ought to be "const char *"! */ + hold = line[len]; + line[len] = '\0'; + data->hit = !regexec(data->regexp, line + 1, 1, ®match, 0); + line[len] = hold; +} + +static void fill_one(struct diff_filespec *one, + mmfile_t *mf, struct userdiff_driver **textconv) +{ + if (DIFF_FILE_VALID(one)) { + *textconv = get_textconv(one); + mf->size = fill_textconv(*textconv, one, &mf->ptr); + } else { + memset(mf, 0, sizeof(*mf)); + } +} + +static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_options *o) +{ + regmatch_t regmatch; + struct userdiff_driver *textconv_one = NULL; + struct userdiff_driver *textconv_two = NULL; + mmfile_t mf1, mf2; + int hit; + + if (diff_unmodified_pair(p)) + return 0; + + fill_one(p->one, &mf1, &textconv_one); + fill_one(p->two, &mf2, &textconv_two); + + if (!mf1.ptr) { + if (!mf2.ptr) + return 0; /* ignore unmerged */ + /* created "two" -- does it have what we are looking for? */ + hit = !regexec(regexp, p->two->data, 1, ®match, 0); + } else if (!mf2.ptr) { + /* removed "one" -- did it have what we are looking for? */ + hit = !regexec(regexp, p->one->data, 1, ®match, 0); + } else { + /* + * We have both sides; need to run textual diff and see if + * the pattern appears on added/deleted lines. + */ + struct diffgrep_cb ecbdata; + xpparam_t xpp; + xdemitconf_t xecfg; + + memset(&xpp, 0, sizeof(xpp)); + memset(&xecfg, 0, sizeof(xecfg)); + ecbdata.regexp = regexp; + ecbdata.hit = 0; + xecfg.ctxlen = o->context; + xecfg.interhunkctxlen = o->interhunkcontext; + xdi_diff_outf(&mf1, &mf2, diffgrep_consume, &ecbdata, + &xpp, &xecfg); + hit = ecbdata.hit; + } + if (textconv_one) + free(mf1.ptr); + if (textconv_two) + free(mf2.ptr); + return hit; +} + +static void diffcore_pickaxe_grep(struct diff_options *o) +{ + struct diff_queue_struct *q = &diff_queued_diff; + int i, has_changes, err; + regex_t regex; + struct diff_queue_struct outq; + outq.queue = NULL; + outq.nr = outq.alloc = 0; + + err = regcomp(®ex, o->pickaxe, REG_EXTENDED | REG_NEWLINE); + if (err) { + char errbuf[1024]; + regerror(err, ®ex, errbuf, 1024); + regfree(®ex); + die("invalid log-grep regex: %s", errbuf); + } + + if (o->pickaxe_opts & DIFF_PICKAXE_ALL) { + /* Showing the whole changeset if needle exists */ + for (i = has_changes = 0; !has_changes && i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (diff_grep(p, ®ex, o)) + has_changes++; + } + if (has_changes) + return; /* do not munge the queue */ + + /* + * Otherwise we will clear the whole queue by copying + * the empty outq at the end of this function, but + * first clear the current entries in the queue. + */ + for (i = 0; i < q->nr; i++) + diff_free_filepair(q->queue[i]); + } else { + /* Showing only the filepairs that has the needle */ + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (diff_grep(p, ®ex, o)) + diff_q(&outq, p); + else + diff_free_filepair(p); + } + } + + regfree(®ex); + + free(q->queue); + *q = outq; + return; +} static unsigned int contains(struct diff_filespec *one, const char *needle, unsigned long len, @@ -48,8 +187,10 @@ static unsigned int contains(struct diff_filespec *one, return cnt; } -void diffcore_pickaxe(const char *needle, int opts) +static void diffcore_pickaxe_count(struct diff_options *o) { + const char *needle = o->pickaxe; + int opts = o->pickaxe_opts; struct diff_queue_struct *q = &diff_queued_diff; unsigned long len = strlen(needle); int i, has_changes; @@ -135,3 +276,12 @@ void diffcore_pickaxe(const char *needle, int opts) *q = outq; return; } + +void diffcore_pickaxe(struct diff_options *o) +{ + /* Might want to warn when both S and G are on; I don't care... */ + if (o->pickaxe_opts & DIFF_PICKAXE_KIND_G) + diffcore_pickaxe_grep(o); + else + diffcore_pickaxe_count(o); +} @@ -107,7 +107,7 @@ extern void diff_q(struct diff_queue_struct *, struct diff_filepair *); extern void diffcore_break(int); extern void diffcore_rename(struct diff_options *); extern void diffcore_merge_broken(void); -extern void diffcore_pickaxe(const char *needle, int opts); +extern void diffcore_pickaxe(struct diff_options *); extern void diffcore_order(const char *orderfile); #define DIFF_DEBUG 0 @@ -18,6 +18,22 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, in int check_only, const struct path_simplify *simplify); static int get_dtype(struct dirent *de, const char *path, int len); +/* helper string functions with support for the ignore_case flag */ +int strcmp_icase(const char *a, const char *b) +{ + return ignore_case ? strcasecmp(a, b) : strcmp(a, b); +} + +int strncmp_icase(const char *a, const char *b, size_t count) +{ + return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); +} + +int fnmatch_icase(const char *pattern, const char *string, int flags) +{ + return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0)); +} + static int common_prefix(const char **pathspec) { const char *path, *slash, *next; @@ -91,16 +107,30 @@ static int match_one(const char *match, const char *name, int namelen) if (!*match) return MATCHED_RECURSIVELY; - for (;;) { - unsigned char c1 = *match; - unsigned char c2 = *name; - if (c1 == '\0' || is_glob_special(c1)) - break; - if (c1 != c2) - return 0; - match++; - name++; - namelen--; + if (ignore_case) { + for (;;) { + unsigned char c1 = tolower(*match); + unsigned char c2 = tolower(*name); + if (c1 == '\0' || is_glob_special(c1)) + break; + if (c1 != c2) + return 0; + match++; + name++; + namelen--; + } + } else { + for (;;) { + unsigned char c1 = *match; + unsigned char c2 = *name; + if (c1 == '\0' || is_glob_special(c1)) + break; + if (c1 != c2) + return 0; + match++; + name++; + namelen--; + } } @@ -109,8 +139,8 @@ static int match_one(const char *match, const char *name, int namelen) * we need to match by fnmatch */ matchlen = strlen(match); - if (strncmp(match, name, matchlen)) - return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0; + if (strncmp_icase(match, name, matchlen)) + return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0; if (namelen == matchlen) return MATCHED_EXACTLY; @@ -375,14 +405,14 @@ int excluded_from_list(const char *pathname, if (x->flags & EXC_FLAG_NODIR) { /* match basename */ if (x->flags & EXC_FLAG_NOWILDCARD) { - if (!strcmp(exclude, basename)) + if (!strcmp_icase(exclude, basename)) return to_exclude; } else if (x->flags & EXC_FLAG_ENDSWITH) { if (x->patternlen - 1 <= pathlen && - !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1)) + !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1)) return to_exclude; } else { - if (fnmatch(exclude, basename, 0) == 0) + if (fnmatch_icase(exclude, basename, 0) == 0) return to_exclude; } } @@ -397,14 +427,14 @@ int excluded_from_list(const char *pathname, if (pathlen < baselen || (baselen && pathname[baselen-1] != '/') || - strncmp(pathname, x->base, baselen)) + strncmp_icase(pathname, x->base, baselen)) continue; if (x->flags & EXC_FLAG_NOWILDCARD) { - if (!strcmp(exclude, pathname + baselen)) + if (!strcmp_icase(exclude, pathname + baselen)) return to_exclude; } else { - if (fnmatch(exclude, pathname+baselen, + if (fnmatch_icase(exclude, pathname+baselen, FNM_PATHNAME) == 0) return to_exclude; } @@ -470,6 +500,39 @@ enum exist_status { }; /* + * Do not use the alphabetically stored index to look up + * the directory name; instead, use the case insensitive + * name hash. + */ +static enum exist_status directory_exists_in_index_icase(const char *dirname, int len) +{ + struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case); + unsigned char endchar; + + if (!ce) + return index_nonexistent; + endchar = ce->name[len]; + + /* + * The cache_entry structure returned will contain this dirname + * and possibly additional path components. + */ + if (endchar == '/') + return index_directory; + + /* + * If there are no additional path components, then this cache_entry + * represents a submodule. Submodules, despite being directories, + * are stored in the cache without a closing slash. + */ + if (!endchar && S_ISGITLINK(ce->ce_mode)) + return index_gitdir; + + /* This should never be hit, but it exists just in case. */ + return index_nonexistent; +} + +/* * The index sorts alphabetically by entry name, which * means that a gitlink sorts as '\0' at the end, while * a directory (which is defined not as an entry, but as @@ -478,7 +541,12 @@ enum exist_status { */ static enum exist_status directory_exists_in_index(const char *dirname, int len) { - int pos = cache_name_pos(dirname, len); + int pos; + + if (ignore_case) + return directory_exists_in_index_icase(dirname, len); + + pos = cache_name_pos(dirname, len); if (pos < 0) pos = -pos-1; while (pos < active_nr) { @@ -965,6 +1033,12 @@ char *get_relative_cwd(char *buffer, int size, const char *dir) case '/': return cwd + 1; default: + /* + * dir can end with a path separator when it's root + * directory. Return proper prefix in that case. + */ + if (dir[-1] == '/') + return cwd; return NULL; } } @@ -101,4 +101,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag); /* tries to remove the path with empty directories along it, ignores ENOENT */ extern int remove_path(const char *path); +extern int strcmp_icase(const char *a, const char *b); +extern int strncmp_icase(const char *a, const char *b, size_t count); +extern int fnmatch_icase(const char *pattern, const char *string, int flags); + #endif @@ -106,14 +106,14 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout case S_IFLNK: new = read_blob_entry(ce, &size); if (!new) - return error("git checkout-index: unable to read sha1 file of %s (%s)", + return error("unable to read sha1 file of %s (%s)", path, sha1_to_hex(ce->sha1)); if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) { ret = symlink(new, path); free(new); if (ret) - return error("git checkout-index: unable to create symlink %s (%s)", + return error("unable to create symlink %s (%s)", path, strerror(errno)); break; } @@ -141,7 +141,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout } if (fd < 0) { free(new); - return error("git checkout-index: unable to create file %s (%s)", + return error("unable to create file %s (%s)", path, strerror(errno)); } @@ -155,16 +155,16 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout close(fd); free(new); if (wrote != size) - return error("git checkout-index: unable to write file %s", path); + return error("unable to write file %s", path); break; case S_IFGITLINK: if (to_tempfile) - return error("git checkout-index: cannot create temporary subproject %s", path); + return error("cannot create temporary subproject %s", path); if (mkdir(path, 0777) < 0) - return error("git checkout-index: cannot create subproject directory %s", path); + return error("cannot create subproject directory %s", path); break; default: - return error("git checkout-index: unknown file mode for %s", path); + return error("unknown file mode for %s in index", path); } if (state->refresh_cache) { @@ -211,7 +211,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t return 0; if (!state->force) { if (!state->quiet) - fprintf(stderr, "git-checkout-index: %s already exists\n", path); + fprintf(stderr, "%s already exists, no checkout\n", path); return -1; } diff --git a/environment.c b/environment.c index de5581f..c79f2a9 100644 --- a/environment.c +++ b/environment.c @@ -21,6 +21,7 @@ int prefer_symlink_refs; int is_bare_repository_cfg = -1; /* unspecified */ int log_all_ref_updates = -1; /* unspecified */ int warn_ambiguous_refs = 1; +int unique_abbrev_extra_length; int repository_format_version; const char *git_commit_encoding; const char *git_log_output_encoding; @@ -87,6 +88,7 @@ const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = { static void setup_git_env(void) { git_dir = getenv(GIT_DIR_ENVIRONMENT); + git_dir = git_dir ? xstrdup(git_dir) : NULL; if (!git_dir) { git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); git_dir = git_dir ? xstrdup(git_dir) : NULL; @@ -171,6 +173,43 @@ char *get_object_directory(void) return git_object_dir; } +int odb_mkstemp(char *template, size_t limit, const char *pattern) +{ + int fd; + /* + * we let the umask do its job, don't try to be more + * restrictive except to remove write permission. + */ + int mode = 0444; + snprintf(template, limit, "%s/%s", + get_object_directory(), pattern); + fd = git_mkstemp_mode(template, mode); + if (0 <= fd) + return fd; + + /* slow path */ + /* some mkstemp implementations erase template on failure */ + snprintf(template, limit, "%s/%s", + get_object_directory(), pattern); + safe_create_leading_directories(template); + return xmkstemp_mode(template, mode); +} + +int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1) +{ + int fd; + + snprintf(name, namesz, "%s/pack/pack-%s.keep", + get_object_directory(), sha1_to_hex(sha1)); + fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600); + if (0 <= fd) + return fd; + + /* slow path */ + safe_create_leading_directories(name); + return open(name, O_RDWR|O_CREAT|O_EXCL, 0600); +} + char *get_index_file(void) { if (!git_index_file) @@ -192,3 +231,14 @@ int set_git_dir(const char *path) setup_git_env(); return 0; } + +const char *get_log_output_encoding(void) +{ + return git_log_output_encoding ? git_log_output_encoding + : get_commit_output_encoding(); +} + +const char *get_commit_output_encoding(void) +{ + return git_commit_encoding ? git_commit_encoding : "UTF-8"; +} diff --git a/fast-import.c b/fast-import.c index eab68d5..3c58e6f 100644 --- a/fast-import.c +++ b/fast-import.c @@ -156,6 +156,7 @@ Format of STDIN stream: #include "csum-file.h" #include "quote.h" #include "exec_cmd.h" +#include "dir.h" #define PACK_ID_BITS 16 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) @@ -361,6 +362,9 @@ static uintmax_t next_mark; static struct strbuf new_data = STRBUF_INIT; static int seen_data_command; +/* Signal handling */ +static volatile sig_atomic_t checkpoint_requested; + static void parse_argv(void); static void write_branch_report(FILE *rpt, struct branch *b) @@ -500,6 +504,32 @@ static NORETURN void die_nicely(const char *err, va_list params) exit(128); } +#ifndef SIGUSR1 /* Windows, for example */ + +static void set_checkpoint_signal(void) +{ +} + +#else + +static void checkpoint_signal(int signo) +{ + checkpoint_requested = 1; +} + +static void set_checkpoint_signal(void) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = checkpoint_signal; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGUSR1, &sa, NULL); +} + +#endif + static void alloc_objects(unsigned int cnt) { struct object_entry_pool *b; @@ -539,22 +569,17 @@ static struct object_entry *insert_object(unsigned char *sha1) { unsigned int h = sha1[0] << 8 | sha1[1]; struct object_entry *e = object_table[h]; - struct object_entry *p = NULL; while (e) { if (!hashcmp(sha1, e->idx.sha1)) return e; - p = e; e = e->next; } e = new_object(sha1); - e->next = NULL; + e->next = object_table[h]; e->idx.offset = 0; - if (p) - p->next = e; - else - object_table[h] = e; + object_table[h] = e; return e; } @@ -1437,6 +1462,20 @@ static void store_tree(struct tree_entry *root) t->entry_count -= del; } +static void tree_content_replace( + struct tree_entry *root, + const unsigned char *sha1, + const uint16_t mode, + struct tree_content *newtree) +{ + if (!S_ISDIR(mode)) + die("Root cannot be a non-directory"); + hashcpy(root->versions[1].sha1, sha1); + if (root->tree) + release_tree_content_recursive(root->tree); + root->tree = newtree; +} + static int tree_content_set( struct tree_entry *root, const char *p, @@ -1444,7 +1483,7 @@ static int tree_content_set( const uint16_t mode, struct tree_content *subtree) { - struct tree_content *t = root->tree; + struct tree_content *t; const char *slash1; unsigned int i, n; struct tree_entry *e; @@ -1459,9 +1498,12 @@ static int tree_content_set( if (!slash1 && !S_ISDIR(mode) && subtree) die("Non-directories cannot have subtrees"); + if (!root->tree) + load_tree(root); + t = root->tree; for (i = 0; i < t->entry_count; i++) { e = t->entries[i]; - if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { + if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) { if (!slash1) { if (!S_ISDIR(mode) && e->versions[1].mode == mode @@ -1514,7 +1556,7 @@ static int tree_content_remove( const char *p, struct tree_entry *backup_leaf) { - struct tree_content *t = root->tree; + struct tree_content *t; const char *slash1; unsigned int i, n; struct tree_entry *e; @@ -1525,9 +1567,12 @@ static int tree_content_remove( else n = strlen(p); + if (!root->tree) + load_tree(root); + t = root->tree; for (i = 0; i < t->entry_count; i++) { e = t->entries[i]; - if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { + if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) { if (slash1 && !S_ISDIR(e->versions[1].mode)) /* * If p names a file in some subdirectory, and a @@ -1572,7 +1617,7 @@ static int tree_content_get( const char *p, struct tree_entry *leaf) { - struct tree_content *t = root->tree; + struct tree_content *t; const char *slash1; unsigned int i, n; struct tree_entry *e; @@ -1583,9 +1628,12 @@ static int tree_content_get( else n = strlen(p); + if (!root->tree) + load_tree(root); + t = root->tree; for (i = 0; i < t->entry_count; i++) { e = t->entries[i]; - if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) { + if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) { if (!slash1) { memcpy(leaf, e, sizeof(*leaf)); if (e->tree && is_null_sha1(e->versions[1].sha1)) @@ -2209,6 +2257,10 @@ static void file_change_m(struct branch *b) command_buf.buf); } + if (!*p) { + tree_content_replace(&b->branch_tree, sha1, mode, NULL); + return; + } tree_content_set(&b->branch_tree, p, sha1, mode, NULL); } @@ -2267,6 +2319,13 @@ static void file_change_cr(struct branch *b, int rename) tree_content_get(&b->branch_tree, s, &leaf); if (!leaf.versions[1].mode) die("Path %s not in branch", s); + if (!*d) { /* C "path/to/subdir" "" */ + tree_content_replace(&b->branch_tree, + leaf.versions[1].sha1, + leaf.versions[1].mode, + leaf.tree); + return; + } tree_content_set(&b->branch_tree, d, leaf.versions[1].sha1, leaf.versions[1].mode, @@ -2680,14 +2739,20 @@ static void parse_reset_branch(void) unread_command_buf = 1; } -static void parse_checkpoint(void) +static void checkpoint(void) { + checkpoint_requested = 0; if (object_count) { cycle_packfile(); dump_branches(); dump_tags(); dump_marks(); } +} + +static void parse_checkpoint(void) +{ + checkpoint_requested = 1; skip_optional_lf(); } @@ -2944,6 +3009,7 @@ int main(int argc, const char **argv) prepare_packed_git(); start_packfile(); set_die_routine(die_nicely); + set_checkpoint_signal(); while (read_next_command() != EOF) { if (!strcmp("blob", command_buf.buf)) parse_new_blob(); @@ -2965,6 +3031,9 @@ int main(int argc, const char **argv) /* ignore non-git options*/; else die("Unsupported command: %s", command_buf.buf); + + if (checkpoint_requested) + checkpoint(); } /* argv hasn't been parsed yet, do so */ diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 27fc793..a329c5a 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1,6 +1,8 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl +use 5.008; use strict; +use warnings; use Git; binmode(STDOUT, ":raw"); @@ -87,6 +89,7 @@ my %patch_modes = ( TARGET => '', PARTICIPLE => 'staging', FILTER => 'file-only', + IS_REVERSE => 0, }, 'stash' => { DIFF => 'diff-index -p HEAD', @@ -96,6 +99,7 @@ my %patch_modes = ( TARGET => '', PARTICIPLE => 'stashing', FILTER => undef, + IS_REVERSE => 0, }, 'reset_head' => { DIFF => 'diff-index -p --cached', @@ -105,6 +109,7 @@ my %patch_modes = ( TARGET => '', PARTICIPLE => 'unstaging', FILTER => 'index-only', + IS_REVERSE => 1, }, 'reset_nothead' => { DIFF => 'diff-index -R -p --cached', @@ -114,6 +119,7 @@ my %patch_modes = ( TARGET => ' to index', PARTICIPLE => 'applying', FILTER => 'index-only', + IS_REVERSE => 0, }, 'checkout_index' => { DIFF => 'diff-files -p', @@ -123,6 +129,7 @@ my %patch_modes = ( TARGET => ' from worktree', PARTICIPLE => 'discarding', FILTER => 'file-only', + IS_REVERSE => 1, }, 'checkout_head' => { DIFF => 'diff-index -p', @@ -132,6 +139,7 @@ my %patch_modes = ( TARGET => ' from index and worktree', PARTICIPLE => 'discarding', FILTER => undef, + IS_REVERSE => 1, }, 'checkout_nothead' => { DIFF => 'diff-index -R -p', @@ -141,6 +149,7 @@ my %patch_modes = ( TARGET => ' to index and worktree', PARTICIPLE => 'applying', FILTER => undef, + IS_REVERSE => 0, }, ); @@ -999,10 +1008,12 @@ sub edit_hunk_manually { print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n"; print $fh @$oldtext; my $participle = $patch_mode_flavour{PARTICIPLE}; + my $is_reverse = $patch_mode_flavour{IS_REVERSE}; + my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-'); print $fh <<EOF; # --- -# To remove '-' lines, make them ' ' lines (context). -# To remove '+' lines, delete them. +# To remove '$remove_minus' lines, make them ' ' lines (context). +# To remove '$remove_plus' lines, delete them. # Lines starting with # will be removed. # # If the patch applies cleanly, the edited hunk will immediately be @@ -444,12 +444,12 @@ else set x first= } - case "$arg" in - /*) - set "$@" "$arg" ;; - *) - set "$@" "$prefix$arg" ;; - esac + if is_absolute_path "$arg" + then + set "$@" "$arg" + else + set "$@" "$prefix$arg" + fi done shift fi diff --git a/git-archimport.perl b/git-archimport.perl index 98f3ede..bc32f18 100755 --- a/git-archimport.perl +++ b/git-archimport.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # # This tool is copyright (c) 2005, Martin Langhoff. # It is released under the Gnu Public License, version 2. @@ -54,6 +54,7 @@ and can contain multiple, unrelated branches. =cut +use 5.008; use strict; use warnings; use Getopt::Std; diff --git a/git-bisect.sh b/git-bisect.sh index 6e2acb8..c21e33c 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -316,7 +316,12 @@ bisect_reset() { *) usage ;; esac - git checkout "$branch" -- && bisect_clean_state + if git checkout "$branch" -- ; then + bisect_clean_state + else + die "Could not check out original HEAD '$branch'." \ + "Try 'git bisect reset <commit>'." + fi } bisect_clean_state() { @@ -338,6 +343,7 @@ bisect_clean_state() { } bisect_replay () { + test "$#" -eq 1 || die "No logfile given" test -r "$1" || die "cannot read $1 for replaying" bisect_reset while read git bisect command rev @@ -412,6 +418,10 @@ bisect_run () { done } +bisect_log () { + test -s "$GIT_DIR/BISECT_LOG" || die "We are not bisecting." + cat "$GIT_DIR/BISECT_LOG" +} case "$#" in 0) @@ -438,7 +448,7 @@ case "$#" in replay) bisect_replay "$@" ;; log) - cat "$GIT_DIR/BISECT_LOG" ;; + bisect_log ;; run) bisect_run "$@" ;; *) diff --git a/git-compat-util.h b/git-compat-util.h index 2af8d3e..d6d269f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -104,9 +104,14 @@ #include <assert.h> #include <regex.h> #include <utime.h> +#include <syslog.h> +#ifndef NO_SYS_POLL_H +#include <sys/poll.h> +#else +#include <poll.h> +#endif #ifndef __MINGW32__ #include <sys/wait.h> -#include <sys/poll.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <termios.h> @@ -118,7 +123,11 @@ #include <arpa/inet.h> #include <netdb.h> #include <pwd.h> +#ifndef NO_INTTYPES_H #include <inttypes.h> +#else +#include <stdint.h> +#endif #if defined(__CYGWIN__) #undef _XOPEN_SOURCE #include <grp.h> @@ -386,6 +395,14 @@ static inline void *gitmempcpy(void *dest, const void *src, size_t n) } #endif +#ifdef NO_INET_PTON +int inet_pton(int af, const char *src, void *dst); +#endif + +#ifdef NO_INET_NTOP +const char *inet_ntop(int af, const void *src, char *dst, size_t size); +#endif + extern void release_pack_memory(size_t, int); typedef void (*try_to_free_t)(size_t); @@ -404,6 +421,7 @@ extern ssize_t xwrite(int fd, const void *buf, size_t len); extern int xdup(int fd); extern FILE *xfdopen(int fd, const char *mode); extern int xmkstemp(char *template); +extern int xmkstemp_mode(char *template, int mode); extern int odb_mkstemp(char *template, size_t limit, const char *pattern); extern int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1); diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 59b6722..39a426e 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -1,6 +1,8 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl +use 5.008; use strict; +use warnings; use Getopt::Std; use File::Temp qw(tempdir); use Data::Dumper; diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 9e03eee..d27abfe 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # This tool is copyright (c) 2005, Matthias Urlichs. # It is released under the Gnu Public License, version 2. @@ -13,6 +13,7 @@ # The head revision is on branch "origin" by default. # You can change that with the '-o' option. +use 5.008; use strict; use warnings; use Getopt::Long; @@ -611,7 +612,7 @@ my %index; # holds filenames of one index per branch unless (-d $git_dir) { system(qw(git init)); die "Cannot init the GIT db at $git_tree: $?\n" if $?; - system(qw(git read-tree)); + system(qw(git read-tree --empty)); die "Cannot init an empty tree: $?\n" if $?; $last_branch = $opt_o; diff --git a/git-cvsserver.perl b/git-cvsserver.perl index e9f3037..1b8bff2 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -8,13 +8,14 @@ #### Copyright The Open University UK - 2006. #### #### Authors: Martyn Smith <martyn@catalyst.net.nz> -#### Martin Langhoff <martin@catalyst.net.nz> +#### Martin Langhoff <martin@laptop.org> #### #### #### Released under the GNU Public License, version 2. #### #### +use 5.008; use strict; use warnings; use bytes; @@ -2680,7 +2681,7 @@ package GITCVS::log; #### Copyright The Open University UK - 2006. #### #### Authors: Martyn Smith <martyn@catalyst.net.nz> -#### Martin Langhoff <martin@catalyst.net.nz> +#### Martin Langhoff <martin@laptop.org> #### #### @@ -2847,7 +2848,7 @@ package GITCVS::updater; #### Copyright The Open University UK - 2006. #### #### Authors: Martyn Smith <martyn@catalyst.net.nz> -#### Martin Langhoff <martin@catalyst.net.nz> +#### Martin Langhoff <martin@laptop.org> #### #### diff --git a/git-difftool.perl b/git-difftool.perl index adc42de..e95e4ad 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -10,6 +10,7 @@ # # Any arguments that are unknown to this script are forwarded to 'git diff'. +use 5.008; use strict; use warnings; use Cwd qw(abs_path); diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 4617f29..d3acf0d 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -83,6 +83,7 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { puts stderr "source $name" uplevel 1 real__source $name } + if {[tk windowingsystem] eq "win32"} { console show } } ###################################################################### @@ -444,6 +445,8 @@ proc _lappend_nice {cmd_var} { set _nice [_which nice] if {[catch {exec $_nice git version}]} { set _nice {} + } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} { + set _nice {} } } if {$_nice ne {}} { @@ -673,6 +676,7 @@ bind . <Visibility> { if {[is_Windows]} { wm iconbitmap . -default $oguilib/git-gui.ico set ::tk::AlwaysShowSelection 1 + bind . <Control-F2> {console show} # Spoof an X11 display for SSH if {![info exists env(DISPLAY)]} { @@ -874,12 +878,19 @@ if {![regsub {^git version } $_git_version {} _git_version]} { exit 1 } +proc get_trimmed_version {s} { + set r {} + foreach x [split $s -._] { + if {[string is integer -strict $x]} { + lappend r $x + } else { + break + } + } + return [join $r .] +} set _real_git_version $_git_version -regsub -- {[\-\.]dirty$} $_git_version {} _git_version -regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version -regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version -regsub {\.GIT$} $_git_version {} _git_version -regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version +set _git_version [get_trimmed_version $_git_version] if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} { catch {wm withdraw .} @@ -1183,13 +1194,22 @@ if {![file isdirectory $_gitdir]} { # _gitdir exists, so try loading the config load_config 0 apply_config -# try to set work tree from environment, falling back to core.worktree -if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} { - set _gitworktree [get_config core.worktree] - if {$_gitworktree eq ""} { - set _gitworktree [file dirname [file normalize $_gitdir]] + +# v1.7.0 introduced --show-toplevel to return the canonical work-tree +if {[package vsatisfies $_git_version 1.7.0]} { + set _gitworktree [git rev-parse --show-toplevel] +} else { + # try to set work tree from environment, core.worktree or use + # cdup to obtain a relative path to the top of the worktree. If + # run from the top, the ./ prefix ensures normalize expands pwd. + if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} { + set _gitworktree [get_config core.worktree] + if {$_gitworktree eq ""} { + set _gitworktree [file normalize ./[git rev-parse --show-cdup]] + } } } + if {$_prefix ne {}} { if {$_gitworktree eq {}} { regsub -all {[^/]+/} $_prefix ../ cdup @@ -2861,7 +2881,8 @@ proc usage {} { set s "usage: $::argv0 $::subcommand $::subcommand_args" if {[tk windowingsystem] eq "win32"} { wm withdraw . - tk_messageBox -icon info -title "Usage" -message $s + tk_messageBox -icon info -message $s \ + -title [mc "Usage"] } else { puts stderr $s } @@ -2934,7 +2955,11 @@ blame { if {[catch { set head [git rev-parse --verify $head] } err]} { - puts stderr $err + if {[tk windowingsystem] eq "win32"} { + tk_messageBox -icon error -title [mc Error] -message $err + } else { + puts stderr $err + } exit 1 } } @@ -2973,18 +2998,19 @@ blame { citool - gui { if {[llength $argv] != 0} { - puts -nonewline stderr "usage: $argv0" - if {$subcommand ne {gui} - && [file tail $argv0] ne "git-$subcommand"} { - puts -nonewline stderr " $subcommand" - } - puts stderr {} - exit 1 + usage } # fall through to setup UI for commits } default { - puts stderr "usage: $argv0 \[{blame|browser|citool}\]" + set err "usage: $argv0 \[{blame|browser|citool}\]" + if {[tk windowingsystem] eq "win32"} { + wm withdraw . + tk_messageBox -icon error -message $err \ + -title [mc "Usage"] + } else { + puts stderr $err + } exit 1 } } @@ -3286,6 +3312,7 @@ text $ui_diff -background white -foreground black \ -xscrollcommand {.vpane.lower.diff.body.sbx set} \ -yscrollcommand {.vpane.lower.diff.body.sby set} \ -state disabled +catch {$ui_diff configure -tabstyle wordprocessor} ${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \ -command [list $ui_diff xview] ${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \ @@ -3296,8 +3323,16 @@ pack $ui_diff -side left -fill both -expand 1 pack .vpane.lower.diff.header -side top -fill x pack .vpane.lower.diff.body -side bottom -fill both -expand 1 +foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 grey60} { + $ui_diff tag configure clr4$n -background $c + $ui_diff tag configure clri4$n -foreground $c + $ui_diff tag configure clr3$n -foreground $c + $ui_diff tag configure clri3$n -background $c +} +$ui_diff tag configure clr1 -font font_diffbold + $ui_diff tag conf d_cr -elide true -$ui_diff tag conf d_@ -foreground blue -font font_diffbold +$ui_diff tag conf d_@ -font font_diffbold $ui_diff tag conf d_+ -foreground {#00a000} $ui_diff tag conf d_- -foreground red diff --git a/git-gui/lib/branch_rename.tcl b/git-gui/lib/branch_rename.tcl index 6398877..6e510ec 100644 --- a/git-gui/lib/branch_rename.tcl +++ b/git-gui/lib/branch_rename.tcl @@ -53,7 +53,7 @@ constructor dialog {} { return 1 } - grid $w.rename.oldname_l $w.rename.oldname_m -sticky w -padx {0 5} + grid $w.rename.oldname_l $w.rename.oldname_m -sticky we -padx {0 5} grid $w.rename.newname_l $w.rename.newname_t -sticky we -padx {0 5} grid columnconfigure $w.rename 1 -weight 1 pack $w.rename -anchor nw -fill x -pady 5 -padx 5 diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index c628750..dcf0711 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -294,7 +294,7 @@ proc start_show_diff {cont_info {add_opts {}}} { } lappend cmd -p - lappend cmd --no-color + lappend cmd --color if {$repo_config(gui.diffcontext) >= 1} { lappend cmd "-U$repo_config(gui.diffcontext)" } @@ -332,6 +332,23 @@ proc start_show_diff {cont_info {add_opts {}}} { fileevent $fd readable [list read_diff $fd $cont_info] } +proc parse_color_line {line} { + set start 0 + set result "" + set markup [list] + set regexp {\033\[((?:\d+;)*\d+)?m} + while {[regexp -indices -start $start $regexp $line match code]} { + foreach {begin end} $match break + append result [string range $line $start [expr {$begin - 1}]] + lappend markup [string length $result] \ + [eval [linsert $code 0 string range $line]] + set start [incr end] + } + append result [string range $line $start end] + if {[llength $markup] < 4} {set markup {}} + return [list $result $markup] +} + proc read_diff {fd cont_info} { global ui_diff diff_active is_submodule_diff global is_3way_diff is_conflict_diff current_diff_header @@ -340,6 +357,9 @@ proc read_diff {fd cont_info} { $ui_diff conf -state normal while {[gets $fd line] >= 0} { + foreach {line markup} [parse_color_line $line] break + set line [string map {\033 ^} $line] + # -- Cleanup uninteresting diff header lines. # if {$::current_diff_inheader} { @@ -434,11 +454,23 @@ proc read_diff {fd cont_info} { } } } + set mark [$ui_diff index "end - 1 line linestart"] $ui_diff insert end $line $tags if {[string index $line end] eq "\r"} { $ui_diff tag add d_cr {end - 2c} } $ui_diff insert end "\n" $tags + + foreach {posbegin colbegin posend colend} $markup { + set prefix clr + foreach style [split $colbegin ";"] { + if {$style eq "7"} {append prefix i; continue} + if {$style < 30 || $style > 47} {continue} + set a "$mark linestart + $posbegin chars" + set b "$mark linestart + $posend chars" + catch {$ui_diff tag add $prefix$style $a $b} + } + } } $ui_diff conf -state disabled diff --git a/git-instaweb.sh b/git-instaweb.sh index e6f6ecd..10fcebb 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -580,6 +580,8 @@ gitweb_conf() { our \$projectroot = "$(dirname "$fqgitdir")"; our \$git_temp = "$fqgitdir/gitweb/tmp"; our \$projects_list = \$projectroot; + +\$feature{'remote_heads'}{'default'} = [1]; EOF } diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh index 615753c..8643f74 100755 --- a/git-merge-octopus.sh +++ b/git-merge-octopus.sh @@ -61,6 +61,11 @@ do esac eval pretty_name=\${GITHEAD_$SHA1:-$SHA1} + if test "$SHA1" = "$pretty_name" + then + SHA1_UP="$(echo "$SHA1" | tr a-z A-Z)" + eval pretty_name=\${GITHEAD_$SHA1_UP:-$pretty_name} + fi common=$(git merge-base --all $SHA1 $MRC) || die "Unable to find common commit with $pretty_name" diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index b5e1943..77d4aee 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -10,10 +10,10 @@ merge_mode() { translate_merge_tool_path () { case "$1" in - vimdiff) + vimdiff|vimdiff2) echo vim ;; - gvimdiff) + gvimdiff|gvimdiff2) echo gvim ;; emerge) @@ -47,7 +47,8 @@ check_unchanged () { valid_tool () { case "$1" in kdiff3 | tkdiff | xxdiff | meld | opendiff | \ - emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis | p4merge) + vimdiff | gvimdiff | vimdiff2 | gvimdiff2 | \ + emerge | ecmerge | diffuse | araxis | p4merge) ;; # happy tortoisemerge) if ! merge_mode; then @@ -169,25 +170,30 @@ run_merge_tool () { "$merge_tool_path" "$LOCAL" "$REMOTE" | cat fi ;; - vimdiff) + vimdiff|gvimdiff) if merge_mode; then touch "$BACKUP" - "$merge_tool_path" -d -c "wincmd l" \ - "$LOCAL" "$MERGED" "$REMOTE" + if $base_present; then + "$merge_tool_path" -f -d -c "wincmd J" \ + "$MERGED" "$LOCAL" "$BASE" "$REMOTE" + else + "$merge_tool_path" -f -d -c "wincmd l" \ + "$LOCAL" "$MERGED" "$REMOTE" + fi check_unchanged else - "$merge_tool_path" -d -c "wincmd l" \ + "$merge_tool_path" -f -d -c "wincmd l" \ "$LOCAL" "$REMOTE" fi ;; - gvimdiff) + vimdiff2|gvimdiff2) if merge_mode; then touch "$BACKUP" - "$merge_tool_path" -d -c "wincmd l" -f \ + "$merge_tool_path" -f -d -c "wincmd l" \ "$LOCAL" "$MERGED" "$REMOTE" check_unchanged else - "$merge_tool_path" -d -c "wincmd l" -f \ + "$merge_tool_path" -f -d -c "wincmd l" \ "$LOCAL" "$REMOTE" fi ;; diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 5f47b18..1cc2ba6 100644 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -66,7 +66,7 @@ get_remote_merge_branch () { origin="$1" default=$(get_default_remote) test -z "$origin" && origin=$default - curr_branch=$(git symbolic-ref -q HEAD) + curr_branch=$(git symbolic-ref -q HEAD) && [ "$origin" = "$default" ] && echo $(git for-each-ref --format='%(upstream)' $curr_branch) ;; @@ -89,7 +89,13 @@ get_remote_merge_branch () { refs/heads/*) remote=${remote#refs/heads/} ;; refs/* | tags/* | remotes/* ) remote= esac - - [ -n "$remote" ] && echo "refs/remotes/$repo/$remote" + [ -n "$remote" ] && case "$repo" in + .) + echo "refs/heads/$remote" + ;; + *) + echo "refs/remotes/$repo/$remote" + ;; + esac esac } diff --git a/git-pull.sh b/git-pull.sh index 8eb74d4..20a3bbe 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -201,10 +201,7 @@ test true = "$rebase" && { die "updating an unborn branch with changes added to the index" fi else - git update-index --ignore-submodules --refresh && - git diff-files --ignore-submodules --quiet && - git diff-index --ignore-submodules --cached --quiet HEAD -- || - die "refusing to pull with rebase: your working tree is not up-to-date" + require_clean_work_tree "pull with rebase" "Please commit or stash them." fi oldremoteref= && . git-parse-remote && diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index a27952d..a5ffd9a 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -28,6 +28,7 @@ continue continue rebasing process abort abort rebasing process and restore original branch skip skip current patch and continue rebasing process no-verify override pre-rebase hook from stopping the operation +verify allow pre-rebase hook to run root rebase all reachable commmits up to the root(s) autosquash move commits that begin with squash!/fixup! under -i " @@ -153,14 +154,6 @@ run_pre_rebase_hook () { fi } -require_clean_work_tree () { - # test if working tree is dirty - git rev-parse --verify HEAD > /dev/null && - git update-index --ignore-submodules --refresh && - git diff-files --quiet --ignore-submodules && - git diff-index --cached --quiet HEAD --ignore-submodules -- || - die "Working tree is dirty" -} ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION" @@ -557,7 +550,7 @@ do_next () { exit "$status" fi # Run in subshell because require_clean_work_tree can die. - if ! (require_clean_work_tree) + if ! (require_clean_work_tree "rebase") then warn "Commit or stash your changes, and then run" warn @@ -675,9 +668,27 @@ get_saved_options () { # comes immediately after the former, and change "pick" to # "fixup"/"squash". rearrange_squash () { - sed -n -e 's/^pick \([0-9a-f]*\) \(squash\)! /\1 \2 /p' \ - -e 's/^pick \([0-9a-f]*\) \(fixup\)! /\1 \2 /p' \ - "$1" >"$1.sq" + # extract fixup!/squash! lines and resolve any referenced sha1's + while read -r pick sha1 message + do + case "$message" in + "squash! "*|"fixup! "*) + action="${message%%!*}" + rest="${message#*! }" + echo "$sha1 $action $rest" + # if it's a single word, try to resolve to a full sha1 and + # emit a second copy. This allows us to match on both message + # and on sha1 prefix + if test "${rest#* }" = "$rest"; then + fullsha="$(git rev-parse -q --verify "$rest" 2>/dev/null)" + if test -n "$fullsha"; then + # prefix the action to uniquely identify this line as + # intended for full sha1 match + echo "$sha1 +$action $fullsha" + fi + fi + esac + done >"$1.sq" <"$1" test -s "$1.sq" || return used= @@ -687,14 +698,26 @@ rearrange_squash () { *" $sha1 "*) continue ;; esac printf '%s\n' "$pick $sha1 $message" + used="$used$sha1 " while read -r squash action msg do - case "$message" in - "$msg"*) + case " $used" in + *" $squash "*) continue ;; + esac + emit=0 + case "$action" in + +*) + action="${action#+}" + # full sha1 prefix test + case "$msg" in "$sha1"*) emit=1;; esac ;; + *) + # message prefix test + case "$message" in "$msg"*) emit=1;; esac ;; + esac + if test $emit = 1; then printf '%s\n' "$action $squash $action! $msg" used="$used$squash " - ;; - esac + fi done <"$1.sq" done >"$1.rearranged" <"$1" cat "$1.rearranged" >"$1" @@ -727,6 +750,7 @@ do OK_TO_SKIP_PRE_REBASE=yes ;; --verify) + OK_TO_SKIP_PRE_REBASE= ;; --continue) is_standalone "$@" || usage @@ -768,7 +792,7 @@ first and then run 'git rebase --continue' again." record_in_rewritten "$(cat "$DOTEST"/stopped-sha)" - require_clean_work_tree + require_clean_work_tree "rebase" do_rest ;; --abort) @@ -866,7 +890,7 @@ first and then run 'git rebase --continue' again." comment_for_reflog start - require_clean_work_tree + require_clean_work_tree "rebase" "Please commit or stash them." if test ! -z "$1" then diff --git a/git-rebase.sh b/git-rebase.sh index 10a238a..d8e1903 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -49,7 +49,8 @@ do_merge= dotest="$GIT_DIR"/rebase-merge prec=4 verbose= -diffstat=$(git config --bool rebase.stat) +diffstat= +test "$(git config --bool rebase.stat)" = true && diffstat=t git_am_opt= rebase_root= force_rebase= @@ -205,6 +206,9 @@ do --no-verify) OK_TO_SKIP_PRE_REBASE=yes ;; + --verify) + OK_TO_SKIP_PRE_REBASE= + ;; --continue) test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || die "No rebase in progress?" @@ -274,15 +278,16 @@ do die "No rebase in progress?" git rerere clear - if test -d "$dotest" - then - GIT_QUIET=$(cat "$dotest/quiet") - move_to_original_branch - else - dotest="$GIT_DIR"/rebase-apply - GIT_QUIET=$(cat "$dotest/quiet") - move_to_original_branch - fi + + test -d "$dotest" || dotest="$GIT_DIR"/rebase-apply + + head_name="$(cat "$dotest"/head-name)" && + case "$head_name" in + refs/*) + git symbolic-ref HEAD $head_name || + die "Could not move back to $head_name" + ;; + esac git reset --hard $(cat "$dotest/orig-head") rm -r "$dotest" exit @@ -412,19 +417,7 @@ else fi fi -# The tree must be really really clean. -if ! git update-index --ignore-submodules --refresh > /dev/null; then - echo >&2 "cannot rebase: you have unstaged changes" - git diff-files --name-status -r --ignore-submodules -- >&2 - exit 1 -fi -diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --) -case "$diff" in -?*) echo >&2 "cannot rebase: your index contains uncommitted changes" - echo >&2 "$diff" - exit 1 - ;; -esac +require_clean_work_tree "rebase" "Please commit or stash them." if test -z "$rebase_root" then diff --git a/git-relink.perl b/git-relink.perl index c2a0ef8..e136732 100755 --- a/git-relink.perl +++ b/git-relink.perl @@ -6,7 +6,7 @@ # # Scan two git object-trees, and hardlink any common objects between them. -use 5.006; +use 5.008; use strict; use warnings; use Getopt::Long; diff --git a/git-send-email.perl b/git-send-email.perl index 81b2ea5..76565de 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # # Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com> # Copyright 2005 Ryan Anderson <ryan@michonline.com> @@ -16,6 +16,7 @@ # and second line is the subject of the message. # +use 5.008; use strict; use warnings; use Term::ReadLine; @@ -24,6 +25,7 @@ use Text::ParseWords; use Data::Dumper; use Term::ANSIColor; use File::Temp qw/ tempdir tempfile /; +use File::Spec::Functions qw(catfile); use Error qw(:try); use Git; @@ -60,6 +62,7 @@ git send-email [options] <file | directory | rev-list options > --envelope-sender <str> * Email envelope sender. --smtp-server <str:int> * Outgoing SMTP server to use. The port is optional. Default 'localhost'. + --smtp-server-option <str> * Outgoing SMTP server option to use. --smtp-server-port <int> * Outgoing SMTP server port. --smtp-user <str> * Username for SMTP-AUTH. --smtp-pass <str> * Password for SMTP-AUTH; not necessary. @@ -70,6 +73,7 @@ git send-email [options] <file | directory | rev-list options > Automating: --identity <str> * Use the sendemail.<id> options. + --to-cmd <str> * Email To: via `<str> \$patch_path` --cc-cmd <str> * Email Cc: via `<str> \$patch_path` --suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, all. --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on. @@ -135,11 +139,8 @@ my $have_mail_address = eval { require Mail::Address; 1 }; my $smtp; my $auth; -sub unique_email_list(@); -sub cleanup_compose_files(); - # Variables we fill in automatically, or via prompting: -my (@to,$no_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh, +my (@to,$no_to,@initial_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh, $initial_reply_to,$initial_subject,@files, $author,$sender,$smtp_authpass,$annotate,$compose,$time); @@ -189,9 +190,11 @@ sub do_edit { } # Variables with corresponding config settings -my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd); -my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption); -my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts, $smtp_domain); +my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc); +my ($to_cmd, $cc_cmd); +my ($smtp_server, $smtp_server_port, @smtp_server_options); +my ($smtp_authuser, $smtp_encryption); +my ($identity, $aliasfiletype, @alias_files, $smtp_domain); my ($validate, $confirm); my (@suppress_cc); my ($auto_8bit_encoding); @@ -212,10 +215,12 @@ my %config_bool_settings = ( my %config_settings = ( "smtpserver" => \$smtp_server, "smtpserverport" => \$smtp_server_port, + "smtpserveroption" => \@smtp_server_options, "smtpuser" => \$smtp_authuser, "smtppass" => \$smtp_authpass, - "smtpdomain" => \$smtp_domain, - "to" => \@to, + "smtpdomain" => \$smtp_domain, + "to" => \@initial_to, + "tocmd" => \$to_cmd, "cc" => \@initial_cc, "cccmd" => \$cc_cmd, "aliasfiletype" => \$aliasfiletype, @@ -273,7 +278,8 @@ $SIG{INT} = \&signal_handler; my $rc = GetOptions("sender|from=s" => \$sender, "in-reply-to=s" => \$initial_reply_to, "subject=s" => \$initial_subject, - "to=s" => \@to, + "to=s" => \@initial_to, + "to-cmd=s" => \$to_cmd, "no-to" => \$no_to, "cc=s" => \@initial_cc, "no-cc" => \$no_cc, @@ -281,6 +287,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "no-bcc" => \$no_bcc, "chain-reply-to!" => \$chain_reply_to, "smtp-server=s" => \$smtp_server, + "smtp-server-option=s" => \@smtp_server_options, "smtp-server-port=s" => \$smtp_server_port, "smtp-user=s" => \$smtp_authuser, "smtp-pass:s" => \$smtp_authpass, @@ -367,7 +374,7 @@ my(%suppress_cc); if (@suppress_cc) { foreach my $entry (@suppress_cc) { die "Unknown --suppress-cc field: '$entry'\n" - unless $entry =~ /^(all|cccmd|cc|author|self|sob|body|bodycc)$/; + unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/; $suppress_cc{$entry} = 1; } } @@ -412,7 +419,7 @@ my ($repoauthor, $repocommitter); # Verify the user input -foreach my $entry (@to) { +foreach my $entry (@initial_to) { die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/; } @@ -511,12 +518,12 @@ while (defined(my $f = shift @ARGV)) { push @rev_list_opts, "--", @ARGV; @ARGV = (); } elsif (-d $f and !check_file_rev_conflict($f)) { - opendir(DH,$f) + opendir my $dh, $f or die "Failed to opendir $f: $!"; - push @files, grep { -f $_ } map { +$f . "/" . $_ } - sort readdir(DH); - closedir(DH); + push @files, grep { -f $_ } map { catfile($f, $_) } + sort readdir $dh; + closedir $dh; } elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) { push @files, $f; } else { @@ -548,7 +555,7 @@ if (@files) { usage(); } -sub get_patch_subject($) { +sub get_patch_subject { my $fn = shift; open (my $fh, '<', $fn); while (my $line = <$fh>) { @@ -566,7 +573,7 @@ if ($compose) { $compose_filename = ($repo ? tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) : tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1]; - open(C,">",$compose_filename) + open my $c, ">", $compose_filename or die "Failed to open for writing $compose_filename: $!"; @@ -574,7 +581,7 @@ if ($compose) { my $tpl_subject = $initial_subject || ''; my $tpl_reply_to = $initial_reply_to || ''; - print C <<EOT; + print $c <<EOT; From $tpl_sender # This line is ignored. GIT: Lines beginning in "GIT:" will be removed. GIT: Consider including an overall diffstat or table of contents @@ -587,9 +594,9 @@ In-Reply-To: $tpl_reply_to EOT for my $f (@files) { - print C get_patch_subject($f); + print $c get_patch_subject($f); } - close(C); + close $c; if ($annotate) { do_edit($compose_filename, @files); @@ -597,23 +604,23 @@ EOT do_edit($compose_filename); } - open(C2,">",$compose_filename . ".final") + open my $c2, ">", $compose_filename . ".final" or die "Failed to open $compose_filename.final : " . $!; - open(C,"<",$compose_filename) + open $c, "<", $compose_filename or die "Failed to open $compose_filename : " . $!; my $need_8bit_cte = file_has_nonascii($compose_filename); my $in_body = 0; my $summary_empty = 1; - while(<C>) { + while(<$c>) { next if m/^GIT:/; if ($in_body) { $summary_empty = 0 unless (/^\n$/); } elsif (/^\n$/) { $in_body = 1; if ($need_8bit_cte) { - print C2 "MIME-Version: 1.0\n", + print $c2 "MIME-Version: 1.0\n", "Content-Type: text/plain; ", "charset=UTF-8\n", "Content-Transfer-Encoding: 8bit\n"; @@ -638,10 +645,10 @@ EOT print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n"; next; } - print C2 $_; + print $c2 $_; } - close(C); - close(C2); + close $c; + close $c2; if ($summary_empty) { print "Summary email is empty, skipping it\n"; @@ -678,7 +685,7 @@ sub ask { my %broken_encoding; -sub file_declares_8bit_cte($) { +sub file_declares_8bit_cte { my $fn = shift; open (my $fh, '<', $fn); while (my $line = <$fh>) { @@ -707,7 +714,7 @@ if (!defined $auto_8bit_encoding && scalar %broken_encoding) { if (!$force) { for my $f (@files) { - if (get_patch_subject($f) =~ /\*\*\* SUBJECT HERE \*\*\*/) { + if (get_patch_subject($f) =~ /\Q*** SUBJECT HERE ***\E/) { die "Refusing to send because the patch\n\t$f\n" . "has the template subject '*** SUBJECT HERE ***'. " . "Pass --force if you really want to send.\n"; @@ -724,9 +731,9 @@ if (!defined $sender) { $prompting++; } -if (!@to) { +if (!@initial_to && !defined $to_cmd) { my $to = ask("Who should the emails be sent to? "); - push @to, parse_address_line($to) if defined $to; # sanitized/validated later + push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later $prompting++; } @@ -744,8 +751,8 @@ sub expand_one_alias { return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias; } -@to = expand_aliases(@to); -@to = (map { sanitize_address($_) } @to); +@initial_to = expand_aliases(@initial_to); +@initial_to = (map { sanitize_address($_) } @initial_to); @initial_cc = expand_aliases(@initial_cc); @bcclist = expand_aliases(@bcclist); @@ -779,8 +786,8 @@ our ($message_id, %mail, $subject, $reply_to, $references, $message, sub extract_valid_address { my $address = shift; - my $local_part_regexp = '[^<>"\s@]+'; - my $domain_regexp = '[^.<>"\s@]+(?:\.[^.<>"\s@]+)+'; + my $local_part_regexp = qr/[^<>"\s@]+/; + my $domain_regexp = qr/[^.<>"\s@]+(?:\.[^.<>"\s@]+)+/; # check for a local address: return $address if ($address =~ /^($local_part_regexp)$/); @@ -821,7 +828,7 @@ sub make_message_id { last if (defined $du_part and $du_part ne ''); } if (not defined $du_part or $du_part eq '') { - use Sys::Hostname qw(); + require Sys::Hostname; $du_part = 'user@' . Sys::Hostname::hostname(); } my $message_id_template = "<%s-git-send-email-%s>"; @@ -854,8 +861,8 @@ sub quote_rfc2047 { sub is_rfc2047_quoted { my $s = shift; - my $token = '[^][()<>@,;:"\/?.= \000-\037\177-\377]+'; - my $encoded_text = '[!->@-~]+'; + my $token = qr/[^][()<>@,;:"\/?.= \000-\037\177-\377]+/; + my $encoded_text = qr/[!->@-~]+/; length($s) <= 75 && $s =~ m/^(?:"[[:ascii:]]*"|=\?$token\?$token\?$encoded_text\?=)$/o; } @@ -866,7 +873,7 @@ sub sanitize_address { my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/); if (not $recipient_name) { - return "$recipient"; + return $recipient; } # if recipient_name is already quoted, do nothing @@ -883,7 +890,7 @@ sub sanitize_address { # double quotes are needed if specials or CTLs are included elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) { $recipient_name =~ s/(["\\\r])/\\$1/g; - $recipient_name = "\"$recipient_name\""; + $recipient_name = qq["$recipient_name"]; } return "$recipient_name $recipient_addr"; @@ -1028,6 +1035,8 @@ X-Mailer: git-send-email $gitversion } } + unshift (@sendmail_parameters, @smtp_server_options); + if ($dry_run) { # We don't want to send the email. } elsif ($smtp_server =~ m#^/#) { @@ -1037,7 +1046,7 @@ X-Mailer: git-send-email $gitversion exec($smtp_server, @sendmail_parameters) or die $!; } print $sm "$header\n$message"; - close $sm or die $?; + close $sm or die $!; } else { if (!defined $smtp_server) { @@ -1143,12 +1152,13 @@ $subject = $initial_subject; $message_num = 0; foreach my $t (@files) { - open(F,"<",$t) or die "can't open file $t"; + open my $fh, "<", $t or die "can't open file $t"; my $author = undef; my $author_encoding; my $has_content_type; my $body_encoding; + @to = (); @cc = (); @xh = (); my $input_format = undef; @@ -1156,7 +1166,7 @@ foreach my $t (@files) { $message = ""; $message_num++; # First unfold multiline header fields - while(<F>) { + while(<$fh>) { last if /^\s*$/; if (/^\s+\S/ and @header) { chomp($header[$#header]); @@ -1189,6 +1199,13 @@ foreach my $t (@files) { $1, $_) unless $quiet; push @cc, $1; } + elsif (/^To:\s+(.*)$/) { + foreach my $addr (parse_address_line($1)) { + printf("(mbox) Adding to: %s from line '%s'\n", + $addr, $_) unless $quiet; + push @to, sanitize_address($addr); + } + } elsif (/^Cc:\s+(.*)$/) { foreach my $addr (parse_address_line($1)) { if (unquote_rfc2047($addr) eq $sender) { @@ -1232,7 +1249,7 @@ foreach my $t (@files) { } } # Now parse the message body - while(<F>) { + while(<$fh>) { $message .= $_; if (/^(Signed-off-by|Cc): (.*)$/i) { chomp; @@ -1249,23 +1266,12 @@ foreach my $t (@files) { $c, $_) unless $quiet; } } - close F; - - if (defined $cc_cmd && !$suppress_cc{'cccmd'}) { - open(F, "$cc_cmd \Q$t\E |") - or die "(cc-cmd) Could not execute '$cc_cmd'"; - while(<F>) { - my $c = $_; - $c =~ s/^\s*//g; - $c =~ s/\n$//g; - next if ($c eq $sender and $suppress_from); - push @cc, $c; - printf("(cc-cmd) Adding cc: %s from: '%s'\n", - $c, $cc_cmd) unless $quiet; - } - close F - or die "(cc-cmd) failed to close pipe to '$cc_cmd'"; - } + close $fh; + + push @to, recipients_cmd("to-cmd", "to", $to_cmd, $t) + if defined $to_cmd; + push @cc, recipients_cmd("cc-cmd", "cc", $cc_cmd, $t) + if defined $cc_cmd && !$suppress_cc{'cccmd'}; if ($broken_encoding{$t} && !$has_content_type) { $has_content_type = 1; @@ -1306,13 +1312,15 @@ foreach my $t (@files) { ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1)); $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc); + @to = (@initial_to, @to); @cc = (@initial_cc, @cc); my $message_was_sent = send_message(); # set up for the next message if ($thread && $message_was_sent && - (chain_reply_to() || !defined $reply_to || length($reply_to) == 0)) { + (chain_reply_to() || !defined $reply_to || length($reply_to) == 0 || + $message_num == 1)) { $reply_to = $message_id; if (length $references > 0) { $references .= "\n $message_id"; @@ -1323,15 +1331,38 @@ foreach my $t (@files) { $message_id = undef; } +# Execute a command (e.g. $to_cmd) to get a list of email addresses +# and return a results array +sub recipients_cmd { + my ($prefix, $what, $cmd, $file) = @_; + + my $sanitized_sender = sanitize_address($sender); + my @addresses = (); + open my $fh, "$cmd \Q$file\E |" + or die "($prefix) Could not execute '$cmd'"; + while (my $address = <$fh>) { + $address =~ s/^\s*//g; + $address =~ s/\s*$//g; + $address = sanitize_address($address); + next if ($address eq $sanitized_sender and $suppress_from); + push @addresses, $address; + printf("($prefix) Adding %s: %s from: '%s'\n", + $what, $address, $cmd) unless $quiet; + } + close $fh + or die "($prefix) failed to close pipe to '$cmd'"; + return @addresses; +} + cleanup_compose_files(); -sub cleanup_compose_files() { +sub cleanup_compose_files { unlink($compose_filename, $compose_filename . ".final") if $compose; } $smtp->quit if $smtp; -sub unique_email_list(@) { +sub unique_email_list { my %seen; my @emails; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 8d54b73..aa16b83 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -145,6 +145,35 @@ require_work_tree () { die "fatal: $0 cannot be used without a working tree." } +require_clean_work_tree () { + git rev-parse --verify HEAD >/dev/null || exit 1 + git update-index -q --ignore-submodules --refresh + err=0 + + if ! git diff-files --quiet --ignore-submodules + then + echo >&2 "Cannot $1: You have unstaged changes." + err=1 + fi + + if ! git diff-index --cached --quiet --ignore-submodules HEAD -- + then + if [ $err = 0 ] + then + echo >&2 "Cannot $1: Your index contains uncommitted changes." + else + echo >&2 "Additionally, your index contains uncommitted changes." + fi + err=1 + fi + + if [ $err = 1 ] + then + test -n "$2" && echo >&2 "$2" + exit 1 + fi +} + get_author_ident_from_commit () { pick_author_script=' /^author /{ @@ -206,5 +235,20 @@ case $(uname -s) in find () { /usr/bin/find "$@" } + is_absolute_path () { + case "$1" in + [/\\]* | [A-Za-z]:*) + return 0 ;; + esac + return 1 + } ;; +*) + is_absolute_path () { + case "$1" in + /*) + return 0 ;; + esac + return 1 + } esac diff --git a/git-submodule.sh b/git-submodule.sh index c291eed..c21b77a 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -93,20 +93,6 @@ module_clone() url=$2 reference="$3" - # If there already is a directory at the submodule path, - # expect it to be empty (since that is the default checkout - # action) and try to remove it. - # Note: if $path is a symlink to a directory the test will - # succeed but the rmdir will fail. We might want to fix this. - if test -d "$path" - then - rmdir "$path" 2>/dev/null || - die "Directory '$path' exists, but is neither empty nor a git repository" - fi - - test -e "$path" && - die "A file already exist at path '$path'" - if test -n "$reference" then git-clone "$reference" -n "$url" "$path" @@ -241,7 +227,7 @@ cmd_add() # ash fails to wordsplit ${branch:+-b "$branch"...} case "$branch" in '') git checkout -f -q ;; - ?*) git checkout -f -q -b "$branch" "origin/$branch" ;; + ?*) git checkout -f -q -B "$branch" "origin/$branch" ;; esac ) || die "Unable to checkout submodule '$path'" fi @@ -374,41 +360,35 @@ cmd_init() cmd_update() { # parse $args after "submodule ... update". - orig_args="$@" + orig_flags= while test $# -ne 0 do case "$1" in -q|--quiet) - shift GIT_QUIET=1 ;; -i|--init) init=1 - shift ;; -N|--no-fetch) - shift nofetch=1 ;; -r|--rebase) - shift update="rebase" ;; --reference) case "$2" in '') usage ;; esac reference="--reference=$2" - shift 2 + orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")" + shift ;; --reference=*) reference="$1" - shift ;; -m|--merge) - shift update="merge" ;; --recursive) - shift recursive=1 ;; --) @@ -422,6 +402,8 @@ cmd_update() break ;; esac + orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")" + shift done if test -n "$init" @@ -500,7 +482,7 @@ cmd_update() if test -n "$recursive" then - (clear_local_git_env; cd "$path" && cmd_update $orig_args) || + (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags") || die "Failed to recurse into submodule path '$path'" fi done @@ -733,7 +715,7 @@ cmd_summary() { cmd_status() { # parse $args after "submodule ... status". - orig_args="$@" + orig_flags= while test $# -ne 0 do case "$1" in @@ -757,6 +739,7 @@ cmd_status() break ;; esac + orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")" shift done @@ -790,7 +773,7 @@ cmd_status() prefix="$displaypath/" clear_local_git_env cd "$path" && - cmd_status $orig_args + eval cmd_status "$orig_args" ) || die "Failed to recurse into submodule path '$path'" fi diff --git a/git-svn.perl b/git-svn.perl index 18cfb24..177dd25 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1,6 +1,7 @@ #!/usr/bin/env perl # Copyright (C) 2006, Eric Wong <normalperson@yhbt.net> # License: GPL v2 or later +use 5.008; use warnings; use strict; use vars qw/ $AUTHOR $VERSION @@ -83,7 +84,7 @@ my ($_stdin, $_help, $_edit, $_version, $_fetch_all, $_no_rebase, $_fetch_parent, $_merge, $_strategy, $_dry_run, $_local, $_prefix, $_no_checkout, $_url, $_verbose, - $_git_format, $_commit_url, $_tag); + $_git_format, $_commit_url, $_tag, $_merge_info); $Git::SVN::_follow_parent = 1; $_q ||= 0; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, @@ -153,6 +154,7 @@ my %cmd = ( 'commit-url=s' => \$_commit_url, 'revision|r=i' => \$_revision, 'no-rebase' => \$_no_rebase, + 'mergeinfo=s' => \$_merge_info, %cmt_opts, %fc_opts } ], branch => [ \&cmd_branch, 'Create a branch in the SVN repository', @@ -568,6 +570,7 @@ sub cmd_dcommit { print "Committed r$_[0]\n"; $cmt_rev = $_[0]; }, + mergeinfo => $_merge_info, svn_path => ''); if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) { print "No changes\n$d~1 == $d\n"; @@ -4450,6 +4453,7 @@ sub new { $self->{path_prefix} = length $self->{svn_path} ? "$self->{svn_path}/" : ''; $self->{config} = $opts->{config}; + $self->{mergeinfo} = $opts->{mergeinfo}; return $self; } @@ -4759,6 +4763,11 @@ sub change_file_prop { $self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool}); } +sub change_dir_prop { + my ($self, $pbat, $pname, $pval) = @_; + $self->SUPER::change_dir_prop($pbat, $pname, $pval, $self->{pool}); +} + sub _chg_file_get_blob ($$$$) { my ($self, $fbat, $m, $which) = @_; my $fh = $::_repository->temp_acquire("git_blob_$which"); @@ -4852,6 +4861,11 @@ sub apply_diff { fatal("Invalid change type: $f"); } } + + if (defined($self->{mergeinfo})) { + $self->change_dir_prop($self->{bat}{''}, "svn:mergeinfo", + $self->{mergeinfo}); + } $self->rmdirs if $_rmdir; if (@$mods == 0) { $self->abort_edit; diff --git a/git-web--browse.sh b/git-web--browse.sh index 3fc4166..e9de241 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -31,154 +31,161 @@ valid_custom_tool() valid_tool() { case "$1" in - firefox | iceweasel | chrome | google-chrome | chromium | konqueror | w3m | links | lynx | dillo | open | start) - ;; # happy - *) - valid_custom_tool "$1" || return 1 - ;; + firefox | iceweasel | seamonkey | iceape | \ + chrome | google-chrome | chromium | chromium-browser |\ + konqueror | opera | w3m | elinks | links | lynx | dillo | open | start) + ;; # happy + *) + valid_custom_tool "$1" || return 1 + ;; esac } init_browser_path() { browser_path=$(git config "browser.$1.path") - test -z "$browser_path" && browser_path="$1" + if test -z "$browser_path" && + test "$1" = chromium && + type chromium-browser >/dev/null 2>&1 + then + browser_path=chromium-browser + fi + : ${browser_path:="$1"} } while test $# != 0 do - case "$1" in + case "$1" in -b|--browser*|-t|--tool*) - case "$#,$1" in + case "$#,$1" in *,*=*) - browser=`expr "z$1" : 'z-[^=]*=\(.*\)'` - ;; + browser=`expr "z$1" : 'z-[^=]*=\(.*\)'` + ;; 1,*) - usage ;; + usage ;; *) - browser="$2" - shift ;; - esac - ;; + browser="$2" + shift ;; + esac + ;; -c|--config*) - case "$#,$1" in + case "$#,$1" in *,*=*) - conf=`expr "z$1" : 'z-[^=]*=\(.*\)'` - ;; + conf=`expr "z$1" : 'z-[^=]*=\(.*\)'` + ;; 1,*) - usage ;; + usage ;; *) - conf="$2" - shift ;; - esac - ;; + conf="$2" + shift ;; + esac + ;; --) - break - ;; + break + ;; -*) - usage - ;; + usage + ;; *) - break - ;; - esac - shift + break + ;; + esac + shift done test $# = 0 && usage if test -z "$browser" then - for opt in "$conf" "web.browser" - do - test -z "$opt" && continue - browser="`git config $opt`" - test -z "$browser" || break - done - if test -n "$browser" && ! valid_tool "$browser"; then - echo >&2 "git config option $opt set to unknown browser: $browser" - echo >&2 "Resetting to default..." - unset browser - fi + for opt in "$conf" "web.browser" + do + test -z "$opt" && continue + browser="`git config $opt`" + test -z "$browser" || break + done + if test -n "$browser" && ! valid_tool "$browser"; then + echo >&2 "git config option $opt set to unknown browser: $browser" + echo >&2 "Resetting to default..." + unset browser + fi fi if test -z "$browser" ; then - if test -n "$DISPLAY"; then - browser_candidates="firefox iceweasel google-chrome chrome chromium konqueror w3m links lynx dillo" - if test "$KDE_FULL_SESSION" = "true"; then - browser_candidates="konqueror $browser_candidates" + if test -n "$DISPLAY"; then + browser_candidates="firefox iceweasel google-chrome chrome chromium chromium-browser konqueror opera seamonkey iceape w3m elinks links lynx dillo" + if test "$KDE_FULL_SESSION" = "true"; then + browser_candidates="konqueror $browser_candidates" + fi + else + browser_candidates="w3m elinks links lynx" fi - else - browser_candidates="w3m links lynx" - fi - # SECURITYSESSIONID indicates an OS X GUI login session - if test -n "$SECURITYSESSIONID" \ - -o "$TERM_PROGRAM" = "Apple_Terminal" ; then - browser_candidates="open $browser_candidates" - fi - # /bin/start indicates MinGW - if test -x /bin/start; then - browser_candidates="start $browser_candidates" - fi - - for i in $browser_candidates; do - init_browser_path $i - if type "$browser_path" > /dev/null 2>&1; then - browser=$i - break + # SECURITYSESSIONID indicates an OS X GUI login session + if test -n "$SECURITYSESSIONID" \ + -o "$TERM_PROGRAM" = "Apple_Terminal" ; then + browser_candidates="open $browser_candidates" fi - done - test -z "$browser" && die "No known browser available." + # /bin/start indicates MinGW + if test -x /bin/start; then + browser_candidates="start $browser_candidates" + fi + + for i in $browser_candidates; do + init_browser_path $i + if type "$browser_path" > /dev/null 2>&1; then + browser=$i + break + fi + done + test -z "$browser" && die "No known browser available." else - valid_tool "$browser" || die "Unknown browser '$browser'." + valid_tool "$browser" || die "Unknown browser '$browser'." - init_browser_path "$browser" + init_browser_path "$browser" - if test -z "$browser_cmd" && ! type "$browser_path" > /dev/null 2>&1; then - die "The browser $browser is not available as '$browser_path'." - fi + if test -z "$browser_cmd" && ! type "$browser_path" > /dev/null 2>&1; then + die "The browser $browser is not available as '$browser_path'." + fi fi case "$browser" in - firefox|iceweasel) +firefox|iceweasel|seamonkey|iceape) # Check version because firefox < 2.0 does not support "-new-tab". vers=$(expr "$($browser_path -version)" : '.* \([0-9][0-9]*\)\..*') NEWTAB='-new-tab' test "$vers" -lt 2 && NEWTAB='' "$browser_path" $NEWTAB "$@" & ;; - google-chrome|chrome|chromium) - # Actual command for chromium is chromium-browser. +google-chrome|chrome|chromium|chromium-browser) # No need to specify newTab. It's default in chromium eval "$browser_path" "$@" & ;; - konqueror) +konqueror) case "$(basename "$browser_path")" in - konqueror) + konqueror) # It's simpler to use kfmclient to open a new tab in konqueror. browser_path="$(echo "$browser_path" | sed -e 's/konqueror$/kfmclient/')" type "$browser_path" > /dev/null 2>&1 || die "No '$browser_path' found." eval "$browser_path" newTab "$@" ;; - kfmclient) + kfmclient) eval "$browser_path" newTab "$@" ;; - *) + *) "$browser_path" "$@" & ;; esac ;; - w3m|links|lynx|open) +w3m|elinks|links|lynx|open) eval "$browser_path" "$@" ;; - start) - exec "$browser_path" '"web-browse"' "$@" - ;; - dillo) +start) + exec "$browser_path" '"web-browse"' "$@" + ;; +opera|dillo) "$browser_path" "$@" & ;; - *) +*) if test -n "$browser_cmd"; then - ( eval $browser_cmd "$@" ) + ( eval $browser_cmd "$@" ) fi ;; esac @@ -19,14 +19,22 @@ static struct startup_info git_startup_info; static int use_pager = -1; struct pager_config { const char *cmd; - int val; + int want; + char *value; }; static int pager_command_config(const char *var, const char *value, void *data) { struct pager_config *c = data; - if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd)) - c->val = git_config_bool(var, value); + if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd)) { + int b = git_config_maybe_bool(var, value); + if (b >= 0) + c->want = b; + else { + c->want = 1; + c->value = xstrdup(value); + } + } return 0; } @@ -35,9 +43,12 @@ int check_pager_config(const char *cmd) { struct pager_config c; c.cmd = cmd; - c.val = -1; + c.want = -1; + c.value = NULL; git_config(pager_command_config, &c); - return c.val; + if (c.value) + pager_program = c.value; + return c.want; } static void commit_pager_choice(void) { @@ -374,6 +385,8 @@ static void handle_internal_command(int argc, const char **argv) { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, + { "remote-ext", cmd_remote_ext }, + { "remote-fd", cmd_remote_fd }, { "replace", cmd_replace, RUN_SETUP }, { "repo-config", cmd_config, RUN_SETUP_GENTLY }, { "rerere", cmd_rerere, RUN_SETUP }, diff --git a/gitk-git/gitk b/gitk-git/gitk index 1b0e09a..e82c6bf 100644 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -131,6 +131,7 @@ proc unmerged_files {files} { proc parseviewargs {n arglist} { global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env + global worddiff git_version set vdatemode($n) 0 set vmergeonly($n) 0 @@ -168,7 +169,7 @@ proc parseviewargs {n arglist} { lappend diffargs $arg } "--raw" - "--patch-with-raw" - "--patch-with-stat" - - "--name-only" - "--name-status" - "--color" - "--color-words" - + "--name-only" - "--name-status" - "--color" - "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" - "--cc" - "-z" - "--header" - "--parents" - "--boundary" - "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" - @@ -177,6 +178,18 @@ proc parseviewargs {n arglist} { # These cause our parsing of git log's output to fail, or else # they're options we want to set ourselves, so ignore them. } + "--color-words*" - "--word-diff=color" { + # These trigger a word diff in the console interface, + # so help the user by enabling our own support + if {[package vcompare $git_version "1.7.2"] >= 0} { + set worddiff [mc "Color words"] + } + } + "--word-diff*" { + if {[package vcompare $git_version "1.7.2"] >= 0} { + set worddiff [mc "Markup words"] + } + } "--stat=*" - "--numstat" - "--shortstat" - "--summary" - "--check" - "--exit-code" - "--quiet" - "--topo-order" - "--full-history" - "--dense" - "--sparse" - @@ -313,6 +326,7 @@ proc start_rev_list {view} { global viewactive viewinstances vmergeonly global mainheadid viewmainheadid viewmainheadid_orig global vcanopt vflags vrevs vorigargs + global show_notes set startmsecs [clock clicks -milliseconds] set commitidx($view) 0 @@ -361,8 +375,8 @@ proc start_rev_list {view} { } if {[catch { - set fd [open [concat | git log --no-color -z --pretty=raw --parents \ - --boundary $args "--" $files] r] + set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \ + --parents --boundary $args "--" $files] r] } err]} { error_popup "[mc "Error executing git log:"] $err" return 0 @@ -456,6 +470,7 @@ proc updatecommits {} { global mainheadid viewmainheadid viewmainheadid_orig pending_select global isworktree global varcid vposids vnegids vflags vrevs + global show_notes set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}] rereadrefs @@ -508,8 +523,8 @@ proc updatecommits {} { set args $vorigargs($view) } if {[catch { - set fd [open [concat | git log --no-color -z --pretty=raw --parents \ - --boundary $args "--" $vfilelimit($view)] r] + set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \ + --parents --boundary $args "--" $vfilelimit($view)] r] } err]} { error_popup "[mc "Error executing git log:"] $err" return @@ -1970,6 +1985,8 @@ proc makewindow {} { global fprogitem fprogcoord lastprogupdate progupdatepending global rprogitem rprogcoord rownumsel numcommits global have_tk85 use_ttk NS + global git_version + global worddiff # The "mc" arguments here are purely so that xgettext # sees the following string as needing to be translated @@ -2243,6 +2260,15 @@ proc makewindow {} { ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \ -command changeignorespace -variable ignorespace pack .bleft.mid.ignspace -side left -padx 5 + + set worddiff [mc "Line diff"] + if {[package vcompare $git_version "1.7.2"] >= 0} { + makedroplist .bleft.mid.worddiff worddiff [mc "Line diff"] \ + [mc "Markup words"] [mc "Color words"] + trace add variable worddiff write changeworddiff + pack .bleft.mid.worddiff -side left -padx 5 + } + set ctext .bleft.bottom.ctext text $ctext -background $bgcolor -foreground $fgcolor \ -state disabled -font textfont \ @@ -2451,6 +2477,7 @@ proc makewindow {} { global ctxbut bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y} bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y} + bind $ctext <Button-1> {focus %W} set maincursor [. cget -cursor] set textcursor [$ctext cget -cursor] @@ -7301,6 +7328,7 @@ proc getblobline {bf id} { [lindex [split $commentend .] 0]}] mark_ctext_line $lnum } + $ctext config -state disabled return 0 } $ctext config -state disabled @@ -7502,11 +7530,16 @@ proc changeignorespace {} { reselectline } +proc changeworddiff {name ix op} { + reselectline +} + proc getblobdiffs {ids} { global blobdifffd diffids env global diffinhdr treediffs global diffcontext global ignorespace + global worddiff global limitdiffs vfilelimit curview global diffencoding targetline diffnparents global git_version currdiffsubmod @@ -7523,6 +7556,9 @@ proc getblobdiffs {ids} { if {$ignorespace} { append cmd " -w" } + if {$worddiff ne [mc "Line diff"]} { + append cmd " --word-diff=porcelain" + } if {$limitdiffs && $vfilelimit($curview) ne {}} { set cmd [concat $cmd -- $vfilelimit($curview)] } @@ -7608,6 +7644,7 @@ proc getblobdiffline {bdf ids} { global ctext_file_names ctext_file_lines global diffinhdr treediffs mergemax diffnparents global diffencoding jump_to_here targetline diffline currdiffsubmod + global worddiff set nr 0 $ctext conf -state normal @@ -7747,15 +7784,28 @@ proc getblobdiffline {bdf ids} { # parse the prefix - one ' ', '-' or '+' for each parent set prefix [string range $line 0 [expr {$diffnparents - 1}]] set tag [expr {$diffnparents > 1? "m": "d"}] + set dowords [expr {$worddiff ne [mc "Line diff"] && $diffnparents == 1}] + set words_pre_markup "" + set words_post_markup "" if {[string trim $prefix " -+"] eq {}} { # prefix only has " ", "-" and "+" in it: normal diff line set num [string first "-" $prefix] + if {$dowords} { + set line [string range $line 1 end] + } if {$num >= 0} { # removed line, first parent with line is $num if {$num >= $mergemax} { set num "max" } - $ctext insert end "$line\n" $tag$num + if {$dowords && $worddiff eq [mc "Markup words"]} { + $ctext insert end "\[-$line-\]" $tag$num + } else { + $ctext insert end "$line" $tag$num + } + if {!$dowords} { + $ctext insert end "\n" $tag$num + } } else { set tags {} if {[string first "+" $prefix] >= 0} { @@ -7770,6 +7820,8 @@ proc getblobdiffline {bdf ids} { lappend tags m$num } } + set words_pre_markup "{+" + set words_post_markup "+}" } if {$targetline ne {}} { if {$diffline == $targetline} { @@ -7779,8 +7831,17 @@ proc getblobdiffline {bdf ids} { incr diffline } } - $ctext insert end "$line\n" $tags + if {$dowords && $worddiff eq [mc "Markup words"]} { + $ctext insert end "$words_pre_markup$line$words_post_markup" $tags + } else { + $ctext insert end "$line" $tags + } + if {!$dowords} { + $ctext insert end "\n" $tags + } } + } elseif {$dowords && $prefix eq "~"} { + $ctext insert end "\n" {} } else { # "\ No newline at end of file", # or something else we don't recognize @@ -11391,6 +11452,7 @@ if {[tk windowingsystem] eq "win32"} { set diffcolors {red "#00a000" blue} set diffcontext 3 set ignorespace 0 +set worddiff "" set markbgcolor "#e0e0ff" set circlecolors {white blue gray blue blue} @@ -11521,6 +11583,11 @@ set NS [expr {$use_ttk ? "ttk" : ""}] set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .] +set show_notes {} +if {[package vcompare $git_version "1.6.6.2"] >= 0} { + set show_notes "--show-notes" +} + set runq {} set history {} set historyindex 0 diff --git a/gitk-git/po/pt_br.po b/gitk-git/po/pt_br.po new file mode 100644 index 0000000..1486e32 --- /dev/null +++ b/gitk-git/po/pt_br.po @@ -0,0 +1,1277 @@ +# Translation of gitk to Brazilian Portuguese. +# Copyright (C) 2007 Paul Mackerras, et al. +# This file is distributed under the same license as the gitk package. +# +# Alexandre Erwin Ittner <alexandre@ittner.com.br>, 2010. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: gitk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-01-26 15:47-0800\n" +"PO-Revision-Date: 2010-12-06 23:39-0200\n" +"Last-Translator: Alexandre Erwin Ittner <alexandre@ittner.com.br>\n" +"Language-Team: Brazilian Portuguese <>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: gitk:115 +msgid "Couldn't get list of unmerged files:" +msgstr "Não foi possível obter a lista dos arquivos não mesclados:" + +#: gitk:274 +msgid "Error parsing revisions:" +msgstr "Erro ao interpretar revisões:" + +#: gitk:330 +msgid "Error executing --argscmd command:" +msgstr "Erro ao executar o comando--argscmd:" + +#: gitk:343 +msgid "No files selected: --merge specified but no files are unmerged." +msgstr "" +"Nenhum arquivo foi selecionado: --merge especificado mas não há arquivos não-" +"mesclados." + +#: gitk:346 +msgid "" +"No files selected: --merge specified but no unmerged files are within file " +"limit." +msgstr "" +"Nenhum arquivo foi selecionado: --merge especificado mas não há arquivos não-" +"mesclados dentro dos limites." + +#: gitk:368 gitk:516 +msgid "Error executing git log:" +msgstr "Erro ao executar git log:" + +#: gitk:386 gitk:532 +msgid "Reading" +msgstr "Lendo" + +#: gitk:446 gitk:4271 +msgid "Reading commits..." +msgstr "Lendo revisões..." + +#: gitk:449 gitk:1580 gitk:4274 +msgid "No commits selected" +msgstr "Nenhuma revisão foi selecionada" + +#: gitk:1456 +msgid "Can't parse git log output:" +msgstr "Não foi possível interpretar a saída do \"git log\":" + +#: gitk:1676 +msgid "No commit information available" +msgstr "Não há informações disponíveis sobre a revisão" + +#: gitk:1818 +msgid "mc" +msgstr "mc" + +#: gitk:1853 gitk:4064 gitk:9067 gitk:10607 gitk:10817 +msgid "OK" +msgstr "Ok" + +#: gitk:1855 gitk:4066 gitk:8657 gitk:8736 gitk:8851 gitk:8900 gitk:9069 +#: gitk:10608 gitk:10818 +msgid "Cancel" +msgstr "Cancelar" + +#: gitk:1980 +msgid "Update" +msgstr "Atualizar" + +#: gitk:1981 +msgid "Reload" +msgstr "Recarregar" + +#: gitk:1982 +msgid "Reread references" +msgstr "Ler as referências novamente" + +#: gitk:1983 +msgid "List references" +msgstr "Listar referências" + +#: gitk:1985 +msgid "Start git gui" +msgstr "Iniciar Git GUI" + +#: gitk:1987 +msgid "Quit" +msgstr "Sair" + +#: gitk:1979 +msgid "File" +msgstr "Arquivo" + +#: gitk:1991 +msgid "Preferences" +msgstr "Preferências" + +#: gitk:1990 +msgid "Edit" +msgstr "Editar" + +#: gitk:1995 +msgid "New view..." +msgstr "Nova vista..." + +#: gitk:1996 +msgid "Edit view..." +msgstr "Editar vista..." + +#: gitk:1997 +msgid "Delete view" +msgstr "Apagar vista" + +#: gitk:1999 +msgid "All files" +msgstr "Todos os arquivos" + +#: gitk:1994 gitk:3817 +msgid "View" +msgstr "Exibir" + +#: gitk:2004 gitk:2014 gitk:2787 +msgid "About gitk" +msgstr "Sobre o gitk" + +#: gitk:2005 gitk:2019 +msgid "Key bindings" +msgstr "Atalhos de teclado" + +#: gitk:2003 gitk:2018 +msgid "Help" +msgstr "Ajuda" + +#: gitk:2096 gitk:8132 +msgid "SHA1 ID:" +msgstr "SHA1 ID:" + +#: gitk:2127 +msgid "Row" +msgstr "Linha" + +#: gitk:2165 +msgid "Find" +msgstr "Encontrar" + +#: gitk:2166 +msgid "next" +msgstr "Próximo" + +#: gitk:2167 +msgid "prev" +msgstr "Anterior" + +#: gitk:2168 +msgid "commit" +msgstr "Revisão" + +#: gitk:2171 gitk:2173 gitk:4432 gitk:4455 gitk:4479 gitk:6420 gitk:6492 +#: gitk:6576 +msgid "containing:" +msgstr "contendo:" + +#: gitk:2174 gitk:3298 gitk:3303 gitk:4507 +msgid "touching paths:" +msgstr "envolvendo os caminhos:" + +#: gitk:2175 gitk:4512 +msgid "adding/removing string:" +msgstr "Adicionando/removendo texto:" + +#: gitk:2184 gitk:2186 +msgid "Exact" +msgstr "Exatamente" + +#: gitk:2186 gitk:4587 gitk:6388 +msgid "IgnCase" +msgstr "Ignorar maiúsculas/minúsculas" + +#: gitk:2186 gitk:4481 gitk:4585 gitk:6384 +msgid "Regexp" +msgstr "Expressão regular" + +#: gitk:2188 gitk:2189 gitk:4606 gitk:4636 gitk:4643 gitk:6512 gitk:6580 +msgid "All fields" +msgstr "Todos os campos" + +#: gitk:2189 gitk:4604 gitk:4636 gitk:6451 +msgid "Headline" +msgstr "Assunto" + +#: gitk:2190 gitk:4604 gitk:6451 gitk:6580 gitk:7013 +msgid "Comments" +msgstr "Descrição da revisão" + +#: gitk:2190 gitk:4604 gitk:4608 gitk:4643 gitk:6451 gitk:6948 gitk:8307 +#: gitk:8322 +msgid "Author" +msgstr "Autor" + +#: gitk:2190 gitk:4604 gitk:6451 gitk:6950 +msgid "Committer" +msgstr "Revisor" + +#: gitk:2221 +msgid "Search" +msgstr "Buscar" + +#: gitk:2229 +msgid "Diff" +msgstr "Diferenças" + +#: gitk:2231 +msgid "Old version" +msgstr "Versão antiga" + +#: gitk:2233 +msgid "New version" +msgstr "Versão nova" + +#: gitk:2235 +msgid "Lines of context" +msgstr "Número de linhas de contexto" + +#: gitk:2245 +msgid "Ignore space change" +msgstr "Ignorar mudanças de caixa" + +#: gitk:2304 +msgid "Patch" +msgstr "Diferenças" + +#: gitk:2306 +msgid "Tree" +msgstr "Árvore" + +#: gitk:2463 gitk:2480 +msgid "Diff this -> selected" +msgstr "Comparar esta revisão com a selecionada" + +#: gitk:2464 gitk:2481 +msgid "Diff selected -> this" +msgstr "Comparar a revisão selecionada com esta" + +#: gitk:2465 gitk:2482 +msgid "Make patch" +msgstr "Criar patch" + +#: gitk:2466 gitk:8715 +msgid "Create tag" +msgstr "Criar etiqueta" + +#: gitk:2467 gitk:8831 +msgid "Write commit to file" +msgstr "Salvar revisão para um arquivo" + +#: gitk:2468 gitk:8888 +msgid "Create new branch" +msgstr "Criar novo ramo" + +#: gitk:2469 +msgid "Cherry-pick this commit" +msgstr "Fazer cherry-pick desta revisão" + +#: gitk:2470 +msgid "Reset HEAD branch to here" +msgstr "Redefinir HEAD para cá" + +#: gitk:2471 +msgid "Mark this commit" +msgstr "Marcar esta revisão" + +#: gitk:2472 +msgid "Return to mark" +msgstr "Voltar à marca" + +#: gitk:2473 +msgid "Find descendant of this and mark" +msgstr "Encontrar descendente e marcar" + +#: gitk:2474 +msgid "Compare with marked commit" +msgstr "Comparar com a revisão marcada" + +#: gitk:2488 +msgid "Check out this branch" +msgstr "Efetuar checkout deste ramo" + +#: gitk:2489 +msgid "Remove this branch" +msgstr "Excluir este ramo" + +#: gitk:2496 +msgid "Highlight this too" +msgstr "Marcar este também" + +#: gitk:2497 +msgid "Highlight this only" +msgstr "Marcar apenas este" + +#: gitk:2498 +msgid "External diff" +msgstr "Diff externo" + +#: gitk:2499 +msgid "Blame parent commit" +msgstr "Anotar revisão anterior" + +#: gitk:2506 +msgid "Show origin of this line" +msgstr "Exibir origem desta linha" + +#: gitk:2507 +msgid "Run git gui blame on this line" +msgstr "Executar 'git blame' nesta linha" + +#: gitk:2789 +msgid "\n" +"Gitk - a commit viewer for git\n" +"\n" +"Copyright ©9 2005-2010 Paul Mackerras\n" +"\n" +"Use and redistribute under the terms of the GNU General Public License" +msgstr "\n" +"Gitk - um visualizador de revisões para o git \n" +"\n" +"Copyright ©9 2005-2010 Paul Mackerras\n" +"\n" +"Uso e distribuição segundo os termos da Licença Pública Geral GNU" + +#: gitk:2797 gitk:2862 gitk:9253 +msgid "Close" +msgstr "Fechar" + +#: gitk:2818 +msgid "Gitk key bindings" +msgstr "Atalhos de teclado" + +#: gitk:2821 +msgid "Gitk key bindings:" +msgstr "Atalhos de teclado:" + +#: gitk:2823 +#, tcl-format +msgid "<%s-Q>\t\tQuit" +msgstr "<%s-Q>\t\tSair" + +#: gitk:2824 +#, tcl-format +msgid "<%s-W>\t\tClose window" +msgstr "<%s-W>\t\tFechar janela" + +#: gitk:2825 +msgid "<Home>\t\tMove to first commit" +msgstr "<Home>\t\tIr para a primeira revisão" + +#: gitk:2826 +msgid "<End>\t\tMove to last commit" +msgstr "<End>\t\tIr para a última revisão" + +#: gitk:2827 +msgid "<Up>, p, i\tMove up one commit" +msgstr "<Up>, p, i\tIr para uma revisão acima" + +#: gitk:2828 +msgid "<Down>, n, k\tMove down one commit" +msgstr "<Down>, n, k\tIr para uma revisão abaixo" + +#: gitk:2829 +msgid "<Left>, z, j\tGo back in history list" +msgstr "<Left>, z, j\tVoltar no histórico" + +#: gitk:2830 +msgid "<Right>, x, l\tGo forward in history list" +msgstr "<Right>, x, l\tAvançar no histórico" + +#: gitk:2831 +msgid "<PageUp>\tMove up one page in commit list" +msgstr "<PageUp>\tSubir uma página na lista de revisões" + +#: gitk:2832 +msgid "<PageDown>\tMove down one page in commit list" +msgstr "<PageDown>\tDescer uma página na lista de revisões" + +#: gitk:2833 +#, tcl-format +msgid "<%s-Home>\tScroll to top of commit list" +msgstr "<%s-Home>\tRolar para o início da lista de revisões" + +#: gitk:2834 +#, tcl-format +msgid "<%s-End>\tScroll to bottom of commit list" +msgstr "<%s-End>\tRolar para o final da lista de revisões" + +#: gitk:2835 +#, tcl-format +msgid "<%s-Up>\tScroll commit list up one line" +msgstr "<%s-Up>\tRolar uma linha acima na lista de revisões" + +#: gitk:2836 +#, tcl-format +msgid "<%s-Down>\tScroll commit list down one line" +msgstr "<%s-Down>\tRolar uma linha abaixo na lista de revisões" + +#: gitk:2837 +#, tcl-format +msgid "<%s-PageUp>\tScroll commit list up one page" +msgstr "<%s-PageUp>\tRolar uma página acima na lista de revisões" + +#: gitk:2838 +#, tcl-format +msgid "<%s-PageDown>\tScroll commit list down one page" +msgstr "<%s-PageDown>\tRolar uma página abaixo na lista de revisões" + +#: gitk:2839 +msgid "<Shift-Up>\tFind backwards (upwards, later commits)" +msgstr "<Shift-Up>\tProcurar próxima (revisões mas recentes)" + +#: gitk:2840 +msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" +msgstr "<Shift-Down>\tProcurar anterior (revisões mais antigas)" + +#: gitk:2841 +msgid "<Delete>, b\tScroll diff view up one page" +msgstr "<Delete>, b\tRola alterações uma página acima" + +#: gitk:2842 +msgid "<Backspace>\tScroll diff view up one page" +msgstr "<Backspace>\tRolar alterações uma página abaixo" + +#: gitk:2843 +msgid "<Space>\t\tScroll diff view down one page" +msgstr "<Space>\t\tRolar alterações uma página abaixo" + +#: gitk:2844 +msgid "u\t\tScroll diff view up 18 lines" +msgstr "u\t\tRolar alterações 18 linhas acima" + +#: gitk:2845 +msgid "d\t\tScroll diff view down 18 lines" +msgstr "d\t\tRolar alterações 18 linhas abaixo" + +#: gitk:2846 +#, tcl-format +msgid "<%s-F>\t\tFind" +msgstr "<%s-F>\t\tProcurar" + +#: gitk:2847 +#, tcl-format +msgid "<%s-G>\t\tMove to next find hit" +msgstr "<%s-G>\t\tIr para a próxima ocorrência" + +#: gitk:2848 +msgid "<Return>\tMove to next find hit" +msgstr "<Return>\tIr para a próxima ocorrência" + +#: gitk:2849 +msgid "/\t\tFocus the search box" +msgstr "/\t\tPor foco na caixa de busca" + +#: gitk:2850 +msgid "?\t\tMove to previous find hit" +msgstr "?\t\tIr para a ocorrência anterior" + +#: gitk:2851 +msgid "f\t\tScroll diff view to next file" +msgstr "f\t\tRolar alterações para o próximo arquivo" + +#: gitk:2852 +#, tcl-format +msgid "<%s-S>\t\tSearch for next hit in diff view" +msgstr "<%s-S>\t\tProcurar a próxima ocorrência na lista de alterações" + +#: gitk:2853 +#, tcl-format +msgid "<%s-R>\t\tSearch for previous hit in diff view" +msgstr "<%s-R>\t\tProcurar ocorrência anterior na lista de alterações" + +#: gitk:2854 +#, tcl-format +msgid "<%s-KP+>\tIncrease font size" +msgstr "<%s-KP+>\tAumentar tamanho da fonte" + +#: gitk:2855 +#, tcl-format +msgid "<%s-plus>\tIncrease font size" +msgstr "<%s-plus>\tAumentar tamanho da fonte" + +#: gitk:2856 +#, tcl-format +msgid "<%s-KP->\tDecrease font size" +msgstr "<%s-KP->\tReduzir tamanho da fonte" + +#: gitk:2857 +#, tcl-format +msgid "<%s-minus>\tDecrease font size" +msgstr "<%s-minus>\tReduzir tamanho da fonte" + +#: gitk:2858 +msgid "<F5>\t\tUpdate" +msgstr "<F5>\t\tAtualizar" + +#: gitk:3313 gitk:3322 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "Erro ao criar o diretório temporário %s:" + +#: gitk:3335 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Erro ao ler \"%s\" de %s:" + +#: gitk:3398 +msgid "command failed:" +msgstr "O comando falhou:" + +#: gitk:3547 +msgid "No such commit" +msgstr "Revisão não encontrada" + +#: gitk:3561 +msgid "git gui blame: command failed:" +msgstr "Comando 'git gui blame' falhou:" + +#: gitk:3592 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "Impossível ler merge head: %s" + +#: gitk:3600 +#, tcl-format +msgid "Error reading index: %s" +msgstr "Erro ao ler o índice: %s" + +#: gitk:3625 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "Não foi possível inciar o 'git blame': %s" + +#: gitk:3628 gitk:6419 +msgid "Searching" +msgstr "Procurando" + +#: gitk:3660 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "Erro ao executar 'git blame': %s" + +#: gitk:3688 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "Esta linha vem da revisão %s, que não está nesta vista" + +#: gitk:3702 +msgid "External diff viewer failed:" +msgstr "Erro do visualizador de alterações externo:" + +#: gitk:3820 +msgid "Gitk view definition" +msgstr "Definir vista" + +#: gitk:3824 +msgid "Remember this view" +msgstr "Lembrar esta vista" + +#: gitk:3825 +msgid "References (space separated list):" +msgstr "Referências (separar a lista com um espaço):" + +#: gitk:3826 +msgid "Branches & tags:" +msgstr "Ramos & etiquetas:" + +#: gitk:3827 +msgid "All refs" +msgstr "Todas as referências" + +#: gitk:3828 +msgid "All (local) branches" +msgstr "Todos os ramos locais" + +#: gitk:3829 +msgid "All tags" +msgstr "Todas as etiquetas" + +#: gitk:3830 +msgid "All remote-tracking branches" +msgstr "Todos os ramos de rastreio" + +#: gitk:3831 +msgid "Commit Info (regular expressions):" +msgstr "Informações da revisão (expressões regulares):" + +#: gitk:3832 +msgid "Author:" +msgstr "Autor:" + +#: gitk:3833 +msgid "Committer:" +msgstr "Revisor:" + +#: gitk:3834 +msgid "Commit Message:" +msgstr "Descrição da revisão:" + +#: gitk:3835 +msgid "Matches all Commit Info criteria" +msgstr "Coincidir todos os critérios de informações da revisão" + +#: gitk:3836 +msgid "Changes to Files:" +msgstr "Mudanças para os arquivos:" + +#: gitk:3837 +msgid "Fixed String" +msgstr "Texto fixo" + +#: gitk:3838 +msgid "Regular Expression" +msgstr "Expressão regular" + +#: gitk:3839 +msgid "Search string:" +msgstr "Texto de busca" + +#: gitk:3840 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "" +"Datas de revisão (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" + +#: gitk:3841 +msgid "Since:" +msgstr "Desde:" + +#: gitk:3842 +msgid "Until:" +msgstr "Até:" + +#: gitk:3843 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "Limitar e/ou ignorar um número de revisões (inteiro positivo):" + +#: gitk:3844 +msgid "Number to show:" +msgstr "Número para mostrar:" + +#: gitk:3845 +msgid "Number to skip:" +msgstr "Número para ignorar:" + +#: gitk:3846 +msgid "Miscellaneous options:" +msgstr "Opções diversas:" + +#: gitk:3847 +msgid "Strictly sort by date" +msgstr "Ordenar estritamente pela data" + +#: gitk:3848 +msgid "Mark branch sides" +msgstr "Marcar os dois lados do ramo" + +#: gitk:3849 +msgid "Limit to first parent" +msgstr "Limitar ao primeiro antecessor" + +#: gitk:3850 +msgid "Simple history" +msgstr "Histórico simplificado" + +#: gitk:3851 +msgid "Additional arguments to git log:" +msgstr "Argumentos adicionais para o 'git log':" + +#: gitk:3852 +msgid "Enter files and directories to include, one per line:" +msgstr "Arquivos e diretórios para incluir, um por linha" + +#: gitk:3853 +msgid "Command to generate more commits to include:" +msgstr "Comando para gerar mais revisões para incluir:" + +#: gitk:3977 +msgid "Gitk: edit view" +msgstr "Gitk: editar vista" + +#: gitk:3985 +msgid "-- criteria for selecting revisions" +msgstr "-- critérios para selecionar revisões" + +#: gitk:3990 +msgid "View Name" +msgstr "Nome da vista" + +#: gitk:4065 +msgid "Apply (F5)" +msgstr "Aplicar (F5)" + +#: gitk:4103 +msgid "Error in commit selection arguments:" +msgstr "Erro nos argumentos de seleção de revisões:" + +#: gitk:4156 gitk:4208 gitk:4656 gitk:4670 gitk:5931 gitk:11551 gitk:11552 +msgid "None" +msgstr "Nenhum" + +#: gitk:4604 gitk:6451 gitk:8309 gitk:8324 +msgid "Date" +msgstr "Data" + +#: gitk:4604 gitk:6451 +msgid "CDate" +msgstr "DataR" + +#: gitk:4753 gitk:4758 +msgid "Descendant" +msgstr "Descendente de" + +#: gitk:4754 +msgid "Not descendant" +msgstr "Não descendente de" + +#: gitk:4761 gitk:4766 +msgid "Ancestor" +msgstr "Antecessor de" + +#: gitk:4762 +msgid "Not ancestor" +msgstr "Não antecessor de" + +#: gitk:5052 +msgid "Local changes checked in to index but not committed" +msgstr "Mudanças locais marcadas, porém não salvas" + +#: gitk:5088 +msgid "Local uncommitted changes, not checked in to index" +msgstr "Mudanças locais não marcadas" + +#: gitk:6769 +msgid "many" +msgstr "muitas" + +#: gitk:6952 +msgid "Tags:" +msgstr "Etiquetas:" + +#: gitk:6969 gitk:6975 gitk:8302 +msgid "Parent" +msgstr "Antecessor" + +#: gitk:6980 +msgid "Child" +msgstr "Descendente" + +#: gitk:6989 +msgid "Branch" +msgstr "Ramo" + +#: gitk:6992 +msgid "Follows" +msgstr "Segue" + +#: gitk:6995 +msgid "Precedes" +msgstr "Precede" + +#: gitk:7532 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "Erro ao obter diferenças: %s" + +#: gitk:8130 +msgid "Goto:" +msgstr "Ir para:" + +#: gitk:8151 +#, tcl-format +msgid "Short SHA1 id %s is ambiguous" +msgstr "O id SHA1 %s é ambíguo" + +#: gitk:8158 +#, tcl-format +msgid "Revision %s is not known" +msgstr "Revisão %s desconhecida" + +#: gitk:8168 +#, tcl-format +msgid "SHA1 id %s is not known" +msgstr "Id SHA1 %s desconhecido" + +#: gitk:8170 +#, tcl-format +msgid "Revision %s is not in the current view" +msgstr "A revisão %s não está na vista atual" + +#: gitk:8312 +msgid "Children" +msgstr "Descendentes" + +#: gitk:8370 +#, tcl-format +msgid "Reset %s branch to here" +msgstr "Redefinir ramo %s para este ponto" + +#: gitk:8372 +msgid "Detached head: can't reset" +msgstr "Detached head: impossível redefinir" + +#: gitk:8481 gitk:8487 +msgid "Skipping merge commit " +msgstr "Saltando revisão de mesclagem" + +#: gitk:8496 gitk:8501 +msgid "Error getting patch ID for " +msgstr "Erro ao obter patch ID para" + +#: gitk:8497 gitk:8502 +msgid " - stopping\n" +msgstr "- parando\n" + +#: gitk:8507 gitk:8510 gitk:8518 gitk:8532 gitk:8541 +msgid "Commit " +msgstr "Revisão" + +#: gitk:8511 +msgid "" +" is the same patch as\n" +" " +msgstr "" +"é o mesmo patch que\n" +" " + +#: gitk:8519 +msgid "" +" differs from\n" +" " +msgstr "difere de" + +#: gitk:8521 +msgid "" +"Diff of commits:\n" +"\n" +msgstr "" +"Diferença de revisões:\n" +"\n" + +#: gitk:8533 gitk:8542 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr "possui %s descendentes - parando\n" + +#: gitk:8561 +#, tcl-format +msgid "Error writing commit to file: %s" +msgstr "Erro ao salvar revisão para o arquivo: %s" + +#: gitk:8567 +#, tcl-format +msgid "Error diffing commits: %s" +msgstr "Erro ao comparar revisões: %s" + +#: gitk:8598 +msgid "Top" +msgstr "Início" + +#: gitk:8599 +msgid "From" +msgstr "De" + +#: gitk:8604 +msgid "To" +msgstr "Para" + +#: gitk:8628 +msgid "Generate patch" +msgstr "Gerar patch" + +#: gitk:8630 +msgid "From:" +msgstr "De:" + +#: gitk:8639 +msgid "To:" +msgstr "Para:" + +#: gitk:8648 +msgid "Reverse" +msgstr "Inverter" + +#: gitk:8650 gitk:8845 +msgid "Output file:" +msgstr "Arquivo de saída:" + +#: gitk:8656 +msgid "Generate" +msgstr "Gerar" + +#: gitk:8694 +msgid "Error creating patch:" +msgstr "Erro ao criar patch:" + +#: gitk:8717 gitk:8833 gitk:8890 +msgid "ID:" +msgstr "ID:" + +#: gitk:8726 +msgid "Tag name:" +msgstr "Nome da etiqueta:" + +#: gitk:8729 +msgid "Tag message is optional" +msgstr "A descrição da etiqueta é opcional" + +#: gitk:8731 +msgid "Tag message:" +msgstr "Descrição da etiqueta" + +#: gitk:8735 gitk:8899 +msgid "Create" +msgstr "Criar" + +#: gitk:8753 +msgid "No tag name specified" +msgstr "Nome da etiqueta não indicado" + +#: gitk:8757 +#, tcl-format +msgid "Tag \"%s\" already exists" +msgstr "Etiqueta \"%s\" já existe" + +#: gitk:8767 +msgid "Error creating tag:" +msgstr "Erro ao criar etiqueta:" + +#: gitk:8842 +msgid "Command:" +msgstr "Comando:" + +#: gitk:8850 +msgid "Write" +msgstr "Exportar" + +#: gitk:8868 +msgid "Error writing commit:" +msgstr "Erro ao exportar revisão" + +#: gitk:8895 +msgid "Name:" +msgstr "Nome:" + +#: gitk:8918 +msgid "Please specify a name for the new branch" +msgstr "Indique um nome para o novo ramo" + +#: gitk:8923 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "O ramo \"%s\" já existe. Sobrescrever?" + +#: gitk:8989 +#, tcl-format +msgid "Commit %s is already included in branch %s -- really re-apply it?" +msgstr "Revisão %s já inclusa no ramo %s -- você realmente deseja reaplicá-la?" + +#: gitk:8994 +msgid "Cherry-picking" +msgstr "Cherry-picking" + +#: gitk:9003 +#, tcl-format +msgid "" +"Cherry-pick failed because of local changes to file '%s'.\n" +"Please commit, reset or stash your changes and try again." +msgstr "" +"O cherry-pick falhou porque o arquivo \"%s\" possui mudanças locais.\n" +"Salve a uma revisão, redefina ou armazene (stash) suas mudanças e tente " +"novamente." + +#: gitk:9009 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"O cherry-pick falhou porque houve um conflito na mesclagem.\n" +"Executar o 'git citool' para resolvê-lo?" + +#: gitk:9025 +msgid "No changes committed" +msgstr "Nenhuma revisão foi salva" + +#: gitk:9051 +msgid "Confirm reset" +msgstr "Confirmar redefinição" + +#: gitk:9053 +#, tcl-format +msgid "Reset branch %s to %s?" +msgstr "Você realmente deseja redefinir o ramo %s para %s?" + +#: gitk:9055 +msgid "Reset type:" +msgstr "Tipo de redefinição" + +#: gitk:9058 +msgid "Soft: Leave working tree and index untouched" +msgstr "Soft: deixa a árvore de trabalho e o índice intocados" + +#: gitk:9061 +msgid "Mixed: Leave working tree untouched, reset index" +msgstr "Misto: Deixa a árvore de trabalho intocada, redefine o índice" + +#: gitk:9064 +msgid "" +"Hard: Reset working tree and index\n" +"(discard ALL local changes)" +msgstr "" +"Hard: Redefine a árvore de trabalho e o índice\n" +"(descarta TODAS as mudanças locais)" + +#: gitk:9081 +msgid "Resetting" +msgstr "Redefinindo" + +#: gitk:9141 +msgid "Checking out" +msgstr "Abrindo" + +#: gitk:9194 +msgid "Cannot delete the currently checked-out branch" +msgstr "Impossível excluir o ramo atualmente aberto" + +#: gitk:9200 +#, tcl-format +msgid "" +"The commits on branch %s aren't on any other branch.\n" +"Really delete branch %s?" +msgstr "" +"As revisões do ramo \"%s\" não existem em nenhum outro ramo.\n" +"Você realmente deseja excluir ramo \"%s\"?" + +#: gitk:9231 +#, tcl-format +msgid "Tags and heads: %s" +msgstr "Referências: %s" + +#: gitk:9246 +msgid "Filter" +msgstr "Filtro" + +#: gitk:9541 +msgid "" +"Error reading commit topology information; branch and preceding/following " +"tag information will be incomplete." +msgstr "" +"Erro ao ler a topologia das revisões; as informações dos ramos e etiquetas " +"antecessoras/sucessoras estarão incompletas" + +#: gitk:10527 +msgid "Tag" +msgstr "Etiqueta" + +#: gitk:10527 +msgid "Id" +msgstr "Id" + +#: gitk:10576 +msgid "Gitk font chooser" +msgstr "Selecionar fontes do Gitk" + +#: gitk:10593 +msgid "B" +msgstr "B" + +#: gitk:10596 +msgid "I" +msgstr "I" + +#: gitk:10714 +msgid "Gitk preferences" +msgstr "Preferências do Gitk" + +#: gitk:10716 +msgid "Commit list display options" +msgstr "Opções da lista de revisões" + +#: gitk:10719 +msgid "Maximum graph width (lines)" +msgstr "Largura máxima do grafo (linhas)" + +#: gitk:10722 +#, tcl-format +msgid "Maximum graph width (% of pane)" +msgstr "Largura máxima do grafo (% do painel)" + +#: gitk:10725 +msgid "Show local changes" +msgstr "Exibir mudanças locais" + +#: gitk:10728 +msgid "Auto-select SHA1" +msgstr "Selecionar o SHA1 automaticamente" + +#: gitk:10731 +msgid "Hide remote refs" +msgstr "Ocultar referências remotas" + +#: gitk:10735 +msgid "Diff display options" +msgstr "Opções de exibição das alterações" + +#: gitk:10737 +msgid "Tab spacing" +msgstr "Espaços por tabulação" + +#: gitk:10740 +msgid "Display nearby tags" +msgstr "Exibir etiquetas próximas" + +#: gitk:10743 +msgid "Limit diffs to listed paths" +msgstr "Limitar diferenças aos caminhos listados" + +#: gitk:10746 +msgid "Support per-file encodings" +msgstr "Usar codificações distintas por arquivo" + +#: gitk:10752 gitk:10832 +msgid "External diff tool" +msgstr "Ferramenta 'diff' externa" + +#: gitk:10753 +msgid "Choose..." +msgstr "Selecionar..." + +#: gitk:10758 +msgid "General options" +msgstr "Opções gerais" + +#: gitk:10761 +msgid "Use themed widgets" +msgstr "Usar temas para as janelas" + +#: gitk:10763 +msgid "(change requires restart)" +msgstr "(exige reinicialização)" + +#: gitk:10765 +msgid "(currently unavailable)" +msgstr "(atualmente indisponível)" + +#: gitk:10769 +msgid "Colors: press to choose" +msgstr "Cores: clique para escolher" + +#: gitk:10772 +msgid "Interface" +msgstr "Interface" + +#: gitk:10773 +msgid "interface" +msgstr "interface" + +#: gitk:10776 +msgid "Background" +msgstr "Segundo plano" + +#: gitk:10777 gitk:10807 +msgid "background" +msgstr "segundo plano" + +#: gitk:10780 +msgid "Foreground" +msgstr "Primeiro plano" + +#: gitk:10781 +msgid "foreground" +msgstr "primeiro plano" + +#: gitk:10784 +msgid "Diff: old lines" +msgstr "Diff: linhas excluídas" + +#: gitk:10785 +msgid "diff old lines" +msgstr "linhas excluídas" + +#: gitk:10789 +msgid "Diff: new lines" +msgstr "Diff: linhas adicionadas" + +#: gitk:10790 +msgid "diff new lines" +msgstr "linhas adicionadas" + +#: gitk:10794 +msgid "Diff: hunk header" +msgstr "Diff: cabeçalho do bloco" + +#: gitk:10796 +msgid "diff hunk header" +msgstr "cabeçalho do bloco" + +#: gitk:10800 +msgid "Marked line bg" +msgstr "2º plano da linha marcada" + +#: gitk:10802 +msgid "marked line background" +msgstr "segundo plano da linha marcada" + +#: gitk:10806 +msgid "Select bg" +msgstr "2º plano da seleção" + +#: gitk:10810 +msgid "Fonts: press to choose" +msgstr "Fontes: clique para escolher" + +#: gitk:10812 +msgid "Main font" +msgstr "Fonte principal" + +#: gitk:10813 +msgid "Diff display font" +msgstr "Fonte da lista de mudanças" + +#: gitk:10814 +msgid "User interface font" +msgstr "Fonte da interface" + +#: gitk:10842 +#, tcl-format +msgid "Gitk: choose color for %s" +msgstr "Gitk: selecionar cor para %s" + +#: gitk:11445 +msgid "Cannot find a git repository here." +msgstr "Não há nenhum repositório git aqui." + +#: gitk:11449 +#, tcl-format +msgid "Cannot find the git directory \"%s\"." +msgstr "Impossível encontrar o diretório git \"%s\"." + +#: gitk:11496 +#, tcl-format +msgid "Ambiguous argument '%s': both revision and filename" +msgstr "" +"O argumento \"%s\" é ambíguo (especifica tanto uma revisão e um nome de " +"arquivo)" + +#: gitk:11508 +msgid "Bad arguments to gitk:" +msgstr "Argumentos incorretos para o gitk:" + +#: gitk:11604 +msgid "Command line" +msgstr "Linha de comando" diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po index 386763a..2f07a2e 100644 --- a/gitk-git/po/sv.po +++ b/gitk-git/po/sv.po @@ -1,5 +1,5 @@ # Swedish translation for gitk -# Copyright (C) 2005-2009 Paul Mackerras +# Copyright (C) 2005-2010 Paul Mackerras # This file is distributed under the same license as the gitk package. # # Peter Krefting <peter@softwolves.pp.se>, 2008-2010. @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: sv\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-01-28 13:16+0100\n" -"PO-Revision-Date: 2010-01-28 13:48+0100\n" +"POT-Creation-Date: 2010-09-12 21:14+0100\n" +"PO-Revision-Date: 2010-09-12 21:16+0100\n" "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n" "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" "MIME-Version: 1.0\n" @@ -24,17 +24,17 @@ msgstr "Kunde inte hämta lista över ej sammanslagna filer:" msgid "Error parsing revisions:" msgstr "Fel vid tolkning av revisioner:" -#: gitk:329 +#: gitk:330 msgid "Error executing --argscmd command:" msgstr "Fel vid körning av --argscmd-kommando:" -#: gitk:342 +#: gitk:343 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Inga filer valdes: --merge angavs men det finns inga filer som inte har " "slagits samman." -#: gitk:345 +#: gitk:346 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -42,600 +42,605 @@ msgstr "" "Inga filer valdes: --merge angavs men det finns inga filer inom " "filbegränsningen." -#: gitk:367 gitk:514 +#: gitk:368 gitk:516 msgid "Error executing git log:" msgstr "Fel vid körning av git log:" -#: gitk:385 gitk:530 +#: gitk:386 gitk:532 msgid "Reading" msgstr "Läser" -#: gitk:445 gitk:4261 +#: gitk:446 gitk:4271 msgid "Reading commits..." msgstr "Läser incheckningar..." -#: gitk:448 gitk:1578 gitk:4264 +#: gitk:449 gitk:1580 gitk:4274 msgid "No commits selected" msgstr "Inga incheckningar markerade" -#: gitk:1454 +#: gitk:1456 msgid "Can't parse git log output:" msgstr "Kan inte tolka utdata från git log:" -#: gitk:1674 +#: gitk:1676 msgid "No commit information available" msgstr "Ingen incheckningsinformation är tillgänglig" -#: gitk:1816 +#: gitk:1818 msgid "mc" msgstr "mc" -#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804 +#: gitk:1853 gitk:4064 gitk:9067 gitk:10607 gitk:10817 msgid "OK" msgstr "OK" -#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046 -#: gitk:10586 gitk:10805 +#: gitk:1855 gitk:4066 gitk:8657 gitk:8736 gitk:8851 gitk:8900 gitk:9069 +#: gitk:10608 gitk:10818 msgid "Cancel" msgstr "Avbryt" -#: gitk:1975 +#: gitk:1980 msgid "Update" msgstr "Uppdatera" -#: gitk:1976 +#: gitk:1981 msgid "Reload" msgstr "Ladda om" -#: gitk:1977 +#: gitk:1982 msgid "Reread references" msgstr "Läs om referenser" -#: gitk:1978 +#: gitk:1983 msgid "List references" msgstr "Visa referenser" -#: gitk:1980 +#: gitk:1985 msgid "Start git gui" msgstr "Starta git gui" -#: gitk:1982 +#: gitk:1987 msgid "Quit" msgstr "Avsluta" -#: gitk:1974 +#: gitk:1979 msgid "File" msgstr "Arkiv" -#: gitk:1986 +#: gitk:1991 msgid "Preferences" msgstr "Inställningar" -#: gitk:1985 +#: gitk:1990 msgid "Edit" msgstr "Redigera" -#: gitk:1990 +#: gitk:1995 msgid "New view..." msgstr "Ny vy..." -#: gitk:1991 +#: gitk:1996 msgid "Edit view..." msgstr "Ändra vy..." -#: gitk:1992 +#: gitk:1997 msgid "Delete view" msgstr "Ta bort vy" -#: gitk:1994 +#: gitk:1999 msgid "All files" msgstr "Alla filer" -#: gitk:1989 gitk:3808 +#: gitk:1994 gitk:3817 msgid "View" msgstr "Visa" -#: gitk:1999 gitk:2009 gitk:2780 +#: gitk:2004 gitk:2014 gitk:2787 msgid "About gitk" msgstr "Om gitk" -#: gitk:2000 gitk:2014 +#: gitk:2005 gitk:2019 msgid "Key bindings" msgstr "Tangentbordsbindningar" -#: gitk:1998 gitk:2013 +#: gitk:2003 gitk:2018 msgid "Help" msgstr "Hjälp" -#: gitk:2091 gitk:8110 +#: gitk:2096 gitk:8132 msgid "SHA1 ID:" msgstr "SHA1-id:" -#: gitk:2122 +#: gitk:2127 msgid "Row" msgstr "Rad" -#: gitk:2160 +#: gitk:2165 msgid "Find" msgstr "Sök" -#: gitk:2161 +#: gitk:2166 msgid "next" msgstr "nästa" -#: gitk:2162 +#: gitk:2167 msgid "prev" msgstr "föreg" -#: gitk:2163 +#: gitk:2168 msgid "commit" msgstr "incheckning" -#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482 -#: gitk:6566 +#: gitk:2171 gitk:2173 gitk:4432 gitk:4455 gitk:4479 gitk:6420 gitk:6492 +#: gitk:6576 msgid "containing:" msgstr "som innehåller:" -#: gitk:2169 gitk:3290 gitk:3295 gitk:4497 +#: gitk:2174 gitk:3298 gitk:3303 gitk:4507 msgid "touching paths:" msgstr "som rör sökväg:" -#: gitk:2170 gitk:4502 +#: gitk:2175 gitk:4512 msgid "adding/removing string:" msgstr "som lägger/till tar bort sträng:" -#: gitk:2179 gitk:2181 +#: gitk:2184 gitk:2186 msgid "Exact" msgstr "Exakt" -#: gitk:2181 gitk:4577 gitk:6378 +#: gitk:2186 gitk:4587 gitk:6388 msgid "IgnCase" msgstr "IgnVersaler" -#: gitk:2181 gitk:4471 gitk:4575 gitk:6374 +#: gitk:2186 gitk:4481 gitk:4585 gitk:6384 msgid "Regexp" msgstr "Reg.uttr." -#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570 +#: gitk:2188 gitk:2189 gitk:4606 gitk:4636 gitk:4643 gitk:6512 gitk:6580 msgid "All fields" msgstr "Alla fält" -#: gitk:2184 gitk:4594 gitk:4626 gitk:6441 +#: gitk:2189 gitk:4604 gitk:4636 gitk:6451 msgid "Headline" msgstr "Rubrik" -#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003 +#: gitk:2190 gitk:4604 gitk:6451 gitk:6580 gitk:7013 msgid "Comments" msgstr "Kommentarer" -#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285 -#: gitk:8300 +#: gitk:2190 gitk:4604 gitk:4608 gitk:4643 gitk:6451 gitk:6948 gitk:8307 +#: gitk:8322 msgid "Author" msgstr "Författare" -#: gitk:2185 gitk:4594 gitk:6441 gitk:6940 +#: gitk:2190 gitk:4604 gitk:6451 gitk:6950 msgid "Committer" msgstr "Incheckare" -#: gitk:2216 +#: gitk:2221 msgid "Search" msgstr "Sök" -#: gitk:2224 +#: gitk:2229 msgid "Diff" msgstr "Diff" -#: gitk:2226 +#: gitk:2231 msgid "Old version" msgstr "Gammal version" -#: gitk:2228 +#: gitk:2233 msgid "New version" msgstr "Ny version" -#: gitk:2230 +#: gitk:2235 msgid "Lines of context" msgstr "Rader sammanhang" -#: gitk:2240 +#: gitk:2245 msgid "Ignore space change" msgstr "Ignorera ändringar i blanksteg" -#: gitk:2299 +#: gitk:2304 msgid "Patch" msgstr "Patch" -#: gitk:2301 +#: gitk:2306 msgid "Tree" msgstr "Träd" -#: gitk:2456 gitk:2473 +#: gitk:2463 gitk:2480 msgid "Diff this -> selected" msgstr "Diff denna -> markerad" -#: gitk:2457 gitk:2474 +#: gitk:2464 gitk:2481 msgid "Diff selected -> this" msgstr "Diff markerad -> denna" -#: gitk:2458 gitk:2475 +#: gitk:2465 gitk:2482 msgid "Make patch" msgstr "Skapa patch" -#: gitk:2459 gitk:8692 +#: gitk:2466 gitk:8715 msgid "Create tag" msgstr "Skapa tagg" -#: gitk:2460 gitk:8808 +#: gitk:2467 gitk:8831 msgid "Write commit to file" msgstr "Skriv incheckning till fil" -#: gitk:2461 gitk:8865 +#: gitk:2468 gitk:8888 msgid "Create new branch" msgstr "Skapa ny gren" -#: gitk:2462 +#: gitk:2469 msgid "Cherry-pick this commit" msgstr "Plocka denna incheckning" -#: gitk:2463 +#: gitk:2470 msgid "Reset HEAD branch to here" msgstr "Återställ HEAD-grenen hit" -#: gitk:2464 +#: gitk:2471 msgid "Mark this commit" msgstr "Markera denna incheckning" -#: gitk:2465 +#: gitk:2472 msgid "Return to mark" msgstr "Återgå till markering" -#: gitk:2466 +#: gitk:2473 msgid "Find descendant of this and mark" msgstr "Hitta efterföljare till denna och markera" -#: gitk:2467 +#: gitk:2474 msgid "Compare with marked commit" msgstr "Jämför med markerad incheckning" -#: gitk:2481 +#: gitk:2488 msgid "Check out this branch" msgstr "Checka ut denna gren" -#: gitk:2482 +#: gitk:2489 msgid "Remove this branch" msgstr "Ta bort denna gren" -#: gitk:2489 +#: gitk:2496 msgid "Highlight this too" msgstr "Markera även detta" -#: gitk:2490 +#: gitk:2497 msgid "Highlight this only" msgstr "Markera bara detta" -#: gitk:2491 +#: gitk:2498 msgid "External diff" msgstr "Extern diff" -#: gitk:2492 +#: gitk:2499 msgid "Blame parent commit" msgstr "Klandra föräldraincheckning" -#: gitk:2499 +#: gitk:2506 msgid "Show origin of this line" msgstr "Visa ursprunget för den här raden" -#: gitk:2500 +#: gitk:2507 msgid "Run git gui blame on this line" msgstr "Kör git gui blame på den här raden" -#: gitk:2782 +#: gitk:2789 msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright \\u00a9 2005-2010 Paul Mackerras\n" +"Copyright ©9 2005-2010 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - en incheckningsvisare för git\n" "\n" -"Copyright \\u00a9 2005-2010 Paul Mackerras\n" +"Copyright ©9 2005-2010 Paul Mackerras\n" "\n" "Använd och vidareförmedla enligt villkoren i GNU General Public License" -#: gitk:2790 gitk:2854 gitk:9230 +#: gitk:2797 gitk:2862 gitk:9253 msgid "Close" msgstr "Stäng" -#: gitk:2811 +#: gitk:2818 msgid "Gitk key bindings" msgstr "Tangentbordsbindningar för Gitk" -#: gitk:2814 +#: gitk:2821 msgid "Gitk key bindings:" msgstr "Tangentbordsbindningar för Gitk:" -#: gitk:2816 +#: gitk:2823 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tAvsluta" -#: gitk:2817 +#: gitk:2824 +#, tcl-format +msgid "<%s-W>\t\tClose window" +msgstr "<%s-W>\t\tStäng fönster" + +#: gitk:2825 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tGå till första incheckning" -#: gitk:2818 +#: gitk:2826 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tGå till sista incheckning" -#: gitk:2819 +#: gitk:2827 msgid "<Up>, p, i\tMove up one commit" msgstr "<Upp>, p, i\tGå en incheckning upp" -#: gitk:2820 +#: gitk:2828 msgid "<Down>, n, k\tMove down one commit" msgstr "<Ned>, n, k\tGå en incheckning ned" -#: gitk:2821 +#: gitk:2829 msgid "<Left>, z, j\tGo back in history list" msgstr "<Vänster>, z, j\tGå bakåt i historiken" -#: gitk:2822 +#: gitk:2830 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Höger>, x, l\tGå framåt i historiken" -#: gitk:2823 +#: gitk:2831 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tGå upp en sida i incheckningslistan" -#: gitk:2824 +#: gitk:2832 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tGå ned en sida i incheckningslistan" -#: gitk:2825 +#: gitk:2833 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tRulla till början av incheckningslistan" -#: gitk:2826 +#: gitk:2834 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tRulla till slutet av incheckningslistan" -#: gitk:2827 +#: gitk:2835 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg" -#: gitk:2828 +#: gitk:2836 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg" -#: gitk:2829 +#: gitk:2837 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida" -#: gitk:2830 +#: gitk:2838 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida" -#: gitk:2831 +#: gitk:2839 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)" -#: gitk:2832 +#: gitk:2840 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)" -#: gitk:2833 +#: gitk:2841 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tRulla diffvisningen upp en sida" -#: gitk:2834 +#: gitk:2842 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Baksteg>\tRulla diffvisningen upp en sida" -#: gitk:2835 +#: gitk:2843 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Blanksteg>\tRulla diffvisningen ned en sida" -#: gitk:2836 +#: gitk:2844 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tRulla diffvisningen upp 18 rader" -#: gitk:2837 +#: gitk:2845 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tRulla diffvisningen ned 18 rader" -#: gitk:2838 +#: gitk:2846 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tSök" -#: gitk:2839 +#: gitk:2847 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tGå till nästa sökträff" -#: gitk:2840 +#: gitk:2848 msgid "<Return>\tMove to next find hit" msgstr "<Return>\t\tGå till nästa sökträff" -#: gitk:2841 +#: gitk:2849 msgid "/\t\tFocus the search box" msgstr "/\t\tFokusera sökrutan" -#: gitk:2842 +#: gitk:2850 msgid "?\t\tMove to previous find hit" msgstr "?\t\tGå till föregående sökträff" -#: gitk:2843 +#: gitk:2851 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tRulla diffvisningen till nästa fil" -#: gitk:2844 +#: gitk:2852 #, 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:2845 +#: gitk:2853 #, 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:2846 +#: gitk:2854 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-Num+>\tÖka teckenstorlek" -#: gitk:2847 +#: gitk:2855 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tÖka teckenstorlek" -#: gitk:2848 +#: gitk:2856 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-Num->\tMinska teckenstorlek" -#: gitk:2849 +#: gitk:2857 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tMinska teckenstorlek" -#: gitk:2850 +#: gitk:2858 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tUppdatera" -#: gitk:3305 gitk:3314 +#: gitk:3313 gitk:3322 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Fel vid skapande av temporär katalog %s:" -#: gitk:3327 +#: gitk:3335 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Fel vid hämtning av \"%s\" från %s:" -#: gitk:3390 +#: gitk:3398 msgid "command failed:" msgstr "kommando misslyckades:" -#: gitk:3539 +#: gitk:3547 msgid "No such commit" msgstr "Incheckning saknas" -#: gitk:3553 +#: gitk:3561 msgid "git gui blame: command failed:" msgstr "git gui blame: kommando misslyckades:" -#: gitk:3584 +#: gitk:3592 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Kunde inte läsa sammanslagningshuvud: %s" -#: gitk:3592 +#: gitk:3600 #, tcl-format msgid "Error reading index: %s" msgstr "Fel vid läsning av index: %s" -#: gitk:3617 +#: gitk:3625 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Kunde inte starta git blame: %s" -#: gitk:3620 gitk:6409 +#: gitk:3628 gitk:6419 msgid "Searching" msgstr "Söker" -#: gitk:3652 +#: gitk:3660 #, tcl-format msgid "Error running git blame: %s" msgstr "Fel vid körning av git blame: %s" -#: gitk:3680 +#: gitk:3688 #, 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:3694 +#: gitk:3702 msgid "External diff viewer failed:" msgstr "Externt diff-verktyg misslyckades:" -#: gitk:3812 +#: gitk:3820 msgid "Gitk view definition" msgstr "Definition av Gitk-vy" -#: gitk:3816 +#: gitk:3824 msgid "Remember this view" msgstr "Spara denna vy" -#: gitk:3817 +#: gitk:3825 msgid "References (space separated list):" msgstr "Referenser (blankstegsavdelad lista):" -#: gitk:3818 +#: gitk:3826 msgid "Branches & tags:" msgstr "Grenar & taggar:" -#: gitk:3819 +#: gitk:3827 msgid "All refs" msgstr "Alla referenser" -#: gitk:3820 +#: gitk:3828 msgid "All (local) branches" msgstr "Alla (lokala) grenar" -#: gitk:3821 +#: gitk:3829 msgid "All tags" msgstr "Alla taggar" -#: gitk:3822 +#: gitk:3830 msgid "All remote-tracking branches" msgstr "Alla fjärrspårande grenar" -#: gitk:3823 +#: gitk:3831 msgid "Commit Info (regular expressions):" msgstr "Incheckningsinfo (reguljära uttryck):" -#: gitk:3824 +#: gitk:3832 msgid "Author:" msgstr "Författare:" -#: gitk:3825 +#: gitk:3833 msgid "Committer:" msgstr "Incheckare:" -#: gitk:3826 +#: gitk:3834 msgid "Commit Message:" msgstr "Incheckningsmeddelande:" -#: gitk:3827 +#: gitk:3835 msgid "Matches all Commit Info criteria" msgstr "Motsvarar alla kriterier för incheckningsinfo" -#: gitk:3828 +#: gitk:3836 msgid "Changes to Files:" msgstr "Ändringar av filer:" -#: gitk:3829 +#: gitk:3837 msgid "Fixed String" msgstr "Fast sträng" -#: gitk:3830 +#: gitk:3838 msgid "Regular Expression" msgstr "Reguljärt uttryck" -#: gitk:3831 +#: gitk:3839 msgid "Search string:" msgstr "Söksträng:" -#: gitk:3832 +#: gitk:3840 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -643,201 +648,201 @@ msgstr "" "Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -#: gitk:3833 +#: gitk:3841 msgid "Since:" msgstr "Från:" -#: gitk:3834 +#: gitk:3842 msgid "Until:" msgstr "Till:" -#: gitk:3835 +#: gitk:3843 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):" -#: gitk:3836 +#: gitk:3844 msgid "Number to show:" msgstr "Antal att visa:" -#: gitk:3837 +#: gitk:3845 msgid "Number to skip:" msgstr "Antal att hoppa över:" -#: gitk:3838 +#: gitk:3846 msgid "Miscellaneous options:" msgstr "Diverse alternativ:" -#: gitk:3839 +#: gitk:3847 msgid "Strictly sort by date" msgstr "Strikt datumsortering" -#: gitk:3840 +#: gitk:3848 msgid "Mark branch sides" msgstr "Markera sidogrenar" -#: gitk:3841 +#: gitk:3849 msgid "Limit to first parent" msgstr "Begränsa till första förälder" -#: gitk:3842 +#: gitk:3850 msgid "Simple history" msgstr "Enkel historik" -#: gitk:3843 +#: gitk:3851 msgid "Additional arguments to git log:" msgstr "Ytterligare argument till git log:" -#: gitk:3844 +#: gitk:3852 msgid "Enter files and directories to include, one per line:" msgstr "Ange filer och kataloger att ta med, en per rad:" -#: gitk:3845 +#: gitk:3853 msgid "Command to generate more commits to include:" msgstr "Kommando för att generera fler incheckningar att ta med:" -#: gitk:3967 +#: gitk:3977 msgid "Gitk: edit view" msgstr "Gitk: redigera vy" -#: gitk:3975 +#: gitk:3985 msgid "-- criteria for selecting revisions" msgstr " - kriterier för val av revisioner" -#: gitk:3980 +#: gitk:3990 msgid "View Name" msgstr "Namn på vy" -#: gitk:4055 +#: gitk:4065 msgid "Apply (F5)" msgstr "Använd (F5)" -#: gitk:4093 +#: gitk:4103 msgid "Error in commit selection arguments:" msgstr "Fel i argument för val av incheckningar:" -#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535 +#: gitk:4156 gitk:4208 gitk:4656 gitk:4670 gitk:5931 gitk:11551 gitk:11552 msgid "None" msgstr "Inget" -#: gitk:4594 gitk:6441 gitk:8287 gitk:8302 +#: gitk:4604 gitk:6451 gitk:8309 gitk:8324 msgid "Date" msgstr "Datum" -#: gitk:4594 gitk:6441 +#: gitk:4604 gitk:6451 msgid "CDate" msgstr "Skapat datum" -#: gitk:4743 gitk:4748 +#: gitk:4753 gitk:4758 msgid "Descendant" msgstr "Avkomling" -#: gitk:4744 +#: gitk:4754 msgid "Not descendant" msgstr "Inte avkomling" -#: gitk:4751 gitk:4756 +#: gitk:4761 gitk:4766 msgid "Ancestor" msgstr "Förfader" -#: gitk:4752 +#: gitk:4762 msgid "Not ancestor" msgstr "Inte förfader" -#: gitk:5042 +#: gitk:5052 msgid "Local changes checked in to index but not committed" msgstr "Lokala ändringar sparade i indexet men inte incheckade" -#: gitk:5078 +#: gitk:5088 msgid "Local uncommitted changes, not checked in to index" msgstr "Lokala ändringar, ej sparade i indexet" -#: gitk:6759 +#: gitk:6769 msgid "many" msgstr "många" -#: gitk:6942 +#: gitk:6952 msgid "Tags:" msgstr "Taggar:" -#: gitk:6959 gitk:6965 gitk:8280 +#: gitk:6969 gitk:6975 gitk:8302 msgid "Parent" msgstr "Förälder" -#: gitk:6970 +#: gitk:6980 msgid "Child" msgstr "Barn" -#: gitk:6979 +#: gitk:6989 msgid "Branch" msgstr "Gren" -#: gitk:6982 +#: gitk:6992 msgid "Follows" msgstr "Följer" -#: gitk:6985 +#: gitk:6995 msgid "Precedes" msgstr "Föregår" -#: gitk:7522 +#: gitk:7532 #, tcl-format msgid "Error getting diffs: %s" msgstr "Fel vid hämtning av diff: %s" -#: gitk:8108 +#: gitk:8130 msgid "Goto:" msgstr "Gå till:" -#: gitk:8129 +#: gitk:8151 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Förkortat SHA1-id %s är tvetydigt" -#: gitk:8136 +#: gitk:8158 #, tcl-format msgid "Revision %s is not known" msgstr "Revisionen %s är inte känd" -#: gitk:8146 +#: gitk:8168 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA-id:t %s är inte känt" -#: gitk:8148 +#: gitk:8170 #, tcl-format msgid "Revision %s is not in the current view" msgstr "Revisionen %s finns inte i den nuvarande vyn" -#: gitk:8290 +#: gitk:8312 msgid "Children" msgstr "Barn" -#: gitk:8348 +#: gitk:8370 #, tcl-format msgid "Reset %s branch to here" msgstr "Återställ grenen %s hit" -#: gitk:8350 +#: gitk:8372 msgid "Detached head: can't reset" msgstr "Frånkopplad head: kan inte återställa" -#: gitk:8459 gitk:8465 +#: gitk:8481 gitk:8487 msgid "Skipping merge commit " msgstr "Hoppar över sammanslagningsincheckning " -#: gitk:8474 gitk:8479 +#: gitk:8496 gitk:8501 msgid "Error getting patch ID for " msgstr "Fel vid hämtning av patch-id för " -#: gitk:8475 gitk:8480 +#: gitk:8497 gitk:8502 msgid " - stopping\n" msgstr " - stannar\n" -#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519 +#: gitk:8507 gitk:8510 gitk:8518 gitk:8532 gitk:8541 msgid "Commit " msgstr "Incheckning " -#: gitk:8489 +#: gitk:8511 msgid "" " is the same patch as\n" " " @@ -845,7 +850,7 @@ msgstr "" " är samma patch som\n" " " -#: gitk:8497 +#: gitk:8519 msgid "" " differs from\n" " " @@ -853,139 +858,139 @@ msgstr "" " skiljer sig från\n" " " -#: gitk:8499 +#: gitk:8521 msgid "" "Diff of commits:\n" "\n" -msgstr "Skillnad mellan incheckningar:\n" +msgstr "" +"Skillnad mellan incheckningar:\n" "\n" -"" -#: gitk:8511 gitk:8520 +#: gitk:8533 gitk:8542 #, tcl-format msgid " has %s children - stopping\n" msgstr " har %s barn - stannar\n" -#: gitk:8539 +#: gitk:8561 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Fel vid skrivning av incheckning till fil: %s" -#: gitk:8545 +#: gitk:8567 #, tcl-format msgid "Error diffing commits: %s" msgstr "Fel vid jämförelse av incheckningar: %s" -#: gitk:8575 +#: gitk:8598 msgid "Top" msgstr "Topp" -#: gitk:8576 +#: gitk:8599 msgid "From" msgstr "Från" -#: gitk:8581 +#: gitk:8604 msgid "To" msgstr "Till" -#: gitk:8605 +#: gitk:8628 msgid "Generate patch" msgstr "Generera patch" -#: gitk:8607 +#: gitk:8630 msgid "From:" msgstr "Från:" -#: gitk:8616 +#: gitk:8639 msgid "To:" msgstr "Till:" -#: gitk:8625 +#: gitk:8648 msgid "Reverse" msgstr "Vänd" -#: gitk:8627 gitk:8822 +#: gitk:8650 gitk:8845 msgid "Output file:" msgstr "Utdatafil:" -#: gitk:8633 +#: gitk:8656 msgid "Generate" msgstr "Generera" -#: gitk:8671 +#: gitk:8694 msgid "Error creating patch:" msgstr "Fel vid generering av patch:" -#: gitk:8694 gitk:8810 gitk:8867 +#: gitk:8717 gitk:8833 gitk:8890 msgid "ID:" msgstr "Id:" -#: gitk:8703 +#: gitk:8726 msgid "Tag name:" msgstr "Taggnamn:" -#: gitk:8706 +#: gitk:8729 msgid "Tag message is optional" msgstr "Taggmeddelandet är valfritt" -#: gitk:8708 +#: gitk:8731 msgid "Tag message:" msgstr "Taggmeddelande:" -#: gitk:8712 gitk:8876 +#: gitk:8735 gitk:8899 msgid "Create" msgstr "Skapa" -#: gitk:8730 +#: gitk:8753 msgid "No tag name specified" msgstr "Inget taggnamn angavs" -#: gitk:8734 +#: gitk:8757 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Taggen \"%s\" finns redan" -#: gitk:8744 +#: gitk:8767 msgid "Error creating tag:" msgstr "Fel vid skapande av tagg:" -#: gitk:8819 +#: gitk:8842 msgid "Command:" msgstr "Kommando:" -#: gitk:8827 +#: gitk:8850 msgid "Write" msgstr "Skriv" -#: gitk:8845 +#: gitk:8868 msgid "Error writing commit:" msgstr "Fel vid skrivning av incheckning:" -#: gitk:8872 +#: gitk:8895 msgid "Name:" msgstr "Namn:" -#: gitk:8895 +#: gitk:8918 msgid "Please specify a name for the new branch" msgstr "Ange ett namn för den nya grenen" -#: gitk:8900 +#: gitk:8923 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Grenen \"%s\" finns redan. Skriva över?" -#: gitk:8966 +#: gitk:8989 #, 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:8971 +#: gitk:8994 msgid "Cherry-picking" msgstr "Plockar" -#: gitk:8980 +#: gitk:9003 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -995,7 +1000,7 @@ msgstr "" "Checka in, återställ eller spara undan (stash) dina ändringar och försök " "igen." -#: gitk:8986 +#: gitk:9009 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1003,32 +1008,32 @@ msgstr "" "Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n" "Vill du köra git citool för att lösa den?" -#: gitk:9002 +#: gitk:9025 msgid "No changes committed" msgstr "Inga ändringar incheckade" -#: gitk:9028 +#: gitk:9051 msgid "Confirm reset" msgstr "Bekräfta återställning" -#: gitk:9030 +#: gitk:9053 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Återställa grenen %s till %s?" -#: gitk:9032 +#: gitk:9055 msgid "Reset type:" msgstr "Typ av återställning:" -#: gitk:9035 +#: gitk:9058 msgid "Soft: Leave working tree and index untouched" msgstr "Mjuk: Rör inte utcheckning och index" -#: gitk:9038 +#: gitk:9061 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Blandad: Rör inte utcheckning, återställ index" -#: gitk:9041 +#: gitk:9064 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1036,19 +1041,19 @@ msgstr "" "Hård: Återställ utcheckning och index\n" "(förkastar ALLA lokala ändringar)" -#: gitk:9058 +#: gitk:9081 msgid "Resetting" msgstr "Återställer" -#: gitk:9118 +#: gitk:9141 msgid "Checking out" msgstr "Checkar ut" -#: gitk:9171 +#: gitk:9194 msgid "Cannot delete the currently checked-out branch" msgstr "Kan inte ta bort den just nu utcheckade grenen" -#: gitk:9177 +#: gitk:9200 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1057,16 +1062,16 @@ msgstr "" "Incheckningarna på grenen %s existerar inte på någon annan gren.\n" "Vill du verkligen ta bort grenen %s?" -#: gitk:9208 +#: gitk:9231 #, tcl-format msgid "Tags and heads: %s" msgstr "Taggar och huvuden: %s" -#: gitk:9223 +#: gitk:9246 msgid "Filter" msgstr "Filter" -#: gitk:9518 +#: gitk:9541 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1074,203 +1079,203 @@ msgstr "" "Fel vid läsning av information om incheckningstopologi; information om " "grenar och föregående/senare taggar kommer inte vara komplett." -#: gitk:10504 +#: gitk:10527 msgid "Tag" msgstr "Tagg" -#: gitk:10504 +#: gitk:10527 msgid "Id" msgstr "Id" -#: gitk:10554 +#: gitk:10576 msgid "Gitk font chooser" msgstr "Teckensnittsväljare för Gitk" -#: gitk:10571 +#: gitk:10593 msgid "B" msgstr "F" -#: gitk:10574 +#: gitk:10596 msgid "I" msgstr "K" -#: gitk:10692 +#: gitk:10714 msgid "Gitk preferences" msgstr "Inställningar för Gitk" -#: gitk:10694 +#: gitk:10716 msgid "Commit list display options" msgstr "Alternativ för incheckningslistvy" -#: gitk:10697 +#: gitk:10719 msgid "Maximum graph width (lines)" msgstr "Maximal grafbredd (rader)" -#: gitk:10700 +#: gitk:10722 #, tcl-format msgid "Maximum graph width (% of pane)" msgstr "Maximal grafbredd (% av ruta)" -#: gitk:10703 +#: gitk:10725 msgid "Show local changes" msgstr "Visa lokala ändringar" -#: gitk:10706 +#: gitk:10728 msgid "Auto-select SHA1" msgstr "Välj SHA1 automatiskt" -#: gitk:10709 +#: gitk:10731 msgid "Hide remote refs" msgstr "Dölj fjärr-referenser" -#: gitk:10713 +#: gitk:10735 msgid "Diff display options" msgstr "Alternativ för diffvy" -#: gitk:10715 +#: gitk:10737 msgid "Tab spacing" msgstr "Blanksteg för tabulatortecken" -#: gitk:10718 +#: gitk:10740 msgid "Display nearby tags" msgstr "Visa närliggande taggar" -#: gitk:10721 +#: gitk:10743 msgid "Limit diffs to listed paths" msgstr "Begränsa diff till listade sökvägar" -#: gitk:10724 +#: gitk:10746 msgid "Support per-file encodings" msgstr "Stöd för filspecifika teckenkodningar" -#: gitk:10730 gitk:10819 +#: gitk:10752 gitk:10832 msgid "External diff tool" msgstr "Externt diff-verktyg" -#: gitk:10731 +#: gitk:10753 msgid "Choose..." msgstr "Välj..." -#: gitk:10736 +#: gitk:10758 msgid "General options" msgstr "Allmänna inställningar" -#: gitk:10739 +#: gitk:10761 msgid "Use themed widgets" msgstr "Använd tema på fönsterelement" -#: gitk:10741 +#: gitk:10763 msgid "(change requires restart)" msgstr "(ändringen kräver omstart)" -#: gitk:10743 +#: gitk:10765 msgid "(currently unavailable)" msgstr "(för närvarande inte tillgängligt)" -#: gitk:10747 +#: gitk:10769 msgid "Colors: press to choose" msgstr "Färger: tryck för att välja" -#: gitk:10750 +#: gitk:10772 msgid "Interface" msgstr "Gränssnitt" -#: gitk:10751 +#: gitk:10773 msgid "interface" msgstr "gränssnitt" -#: gitk:10754 +#: gitk:10776 msgid "Background" msgstr "Bakgrund" -#: gitk:10755 gitk:10785 +#: gitk:10777 gitk:10807 msgid "background" msgstr "bakgrund" -#: gitk:10758 +#: gitk:10780 msgid "Foreground" msgstr "Förgrund" -#: gitk:10759 +#: gitk:10781 msgid "foreground" msgstr "förgrund" -#: gitk:10762 +#: gitk:10784 msgid "Diff: old lines" msgstr "Diff: gamla rader" -#: gitk:10763 +#: gitk:10785 msgid "diff old lines" msgstr "diff gamla rader" -#: gitk:10767 +#: gitk:10789 msgid "Diff: new lines" msgstr "Diff: nya rader" -#: gitk:10768 +#: gitk:10790 msgid "diff new lines" msgstr "diff nya rader" -#: gitk:10772 +#: gitk:10794 msgid "Diff: hunk header" msgstr "Diff: delhuvud" -#: gitk:10774 +#: gitk:10796 msgid "diff hunk header" msgstr "diff delhuvud" -#: gitk:10778 +#: gitk:10800 msgid "Marked line bg" msgstr "Markerad rad bakgrund" -#: gitk:10780 +#: gitk:10802 msgid "marked line background" msgstr "markerad rad bakgrund" -#: gitk:10784 +#: gitk:10806 msgid "Select bg" msgstr "Markerad bakgrund" -#: gitk:10788 +#: gitk:10810 msgid "Fonts: press to choose" msgstr "Teckensnitt: tryck för att välja" -#: gitk:10790 +#: gitk:10812 msgid "Main font" msgstr "Huvudteckensnitt" -#: gitk:10791 +#: gitk:10813 msgid "Diff display font" msgstr "Teckensnitt för diffvisning" -#: gitk:10792 +#: gitk:10814 msgid "User interface font" msgstr "Teckensnitt för användargränssnitt" -#: gitk:10829 +#: gitk:10842 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: välj färg för %s" -#: gitk:11433 +#: gitk:11445 msgid "Cannot find a git repository here." -msgstr "Hittar inget gitk-arkiv här." +msgstr "Hittar inget git-arkiv här." -#: gitk:11437 +#: gitk:11449 #, tcl-format msgid "Cannot find the git directory \"%s\"." msgstr "Hittar inte git-katalogen \"%s\"." -#: gitk:11484 +#: gitk:11496 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Tvetydigt argument \"%s\": både revision och filnamn" -#: gitk:11496 +#: gitk:11508 msgid "Bad arguments to gitk:" msgstr "Felaktiga argument till gitk:" -#: gitk:11587 +#: gitk:11604 msgid "Command line" msgstr "Kommandorad" diff --git a/gitweb/Makefile b/gitweb/Makefile index 2fb7c2d..0a6ac00 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -35,10 +35,12 @@ GITWEB_FAVICON = static/git-favicon.png GITWEB_JS = static/gitweb.js GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = +HIGHLIGHT_BIN = highlight # include user config -include ../config.mak.autogen -include ../config.mak +-include config.mak # determine version ../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @@ -129,7 +131,8 @@ GITWEB_REPLACE = \ -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \ -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ - -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' + -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ + -e 's|++HIGHLIGHT_BIN++|$(HIGHLIGHT_BIN)|g' GITWEB-BUILD-OPTIONS: FORCE @rm -f $@+ @@ -143,6 +146,15 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS chmod +x $@+ && \ mv $@+ $@ +### Testing rules + +test: + $(MAKE) -C ../t gitweb-test + +test-installed: + GITWEB_TEST_INSTALLED='$(DESTDIR_SQ)$(gitwebdir_SQ)' \ + $(MAKE) -C ../t gitweb-test + ### Installation rules install: all @@ -156,5 +168,5 @@ install: all clean: $(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS -.PHONY: all clean install .FORCE-GIT-VERSION-FILE FORCE +.PHONY: all clean install test test-installed .FORCE-GIT-VERSION-FILE FORCE diff --git a/gitweb/README b/gitweb/README index d481198..4a67393 100644 --- a/gitweb/README +++ b/gitweb/README @@ -114,6 +114,11 @@ You can specify the following configuration variables when building GIT: when gitweb.cgi is executed, then the file specified in the environment variable will be loaded instead of the file specified when gitweb.cgi was created. [Default: /etc/gitweb.conf] + * HIGHLIGHT_BIN + Path to the highlight executable to use (must be the one from + http://www.andre-simon.de due to assumptions about parameters and output). + Useful if highlight is not installed on your webserver's PATH. + [Default: highlight] Runtime gitweb configuration @@ -172,13 +177,15 @@ not include variables usually directly set during build): * $my_url, $my_uri Full URL and absolute URL of gitweb script; in earlier versions of gitweb you might have need to set those - variables, now there should be no need to do it. + variables, now there should be no need to do it. See + $per_request_config if you need to set them still. * $base_url Base URL for relative URLs in pages generated by gitweb, (e.g. $logo, $favicon, @stylesheets if they are relative URLs), needed and used only for URLs with nonempty PATH_INFO via <base href="$base_url">. Usually gitweb sets its value correctly, and there is no need to set this variable, e.g. to $my_uri or "/". + See $per_request_config if you need to set it anyway. * $home_link Target of the home link on top of all pages (the first part of view "breadcrumbs"). By default set to absolute URI of a page ($my_uri). @@ -236,7 +243,21 @@ not include variables usually directly set during build): If server load exceed this value then return "503 Service Unavailable" error. Server load is taken to be 0 if gitweb cannot determine its value. Set it to undefined value to turn it off. The default is 300. - + * $highlight_bin + Path to the highlight executable to use (must be the one from + http://www.andre-simon.de due to assumptions about parameters and output). + Useful if highlight is not installed on your webserver's PATH. + [Default: highlight] + * $per_request_config + If set to code reference, it would be run once per each request. You can + set parts of configuration that change per session, e.g. by setting it to + sub { $ENV{GL_USER} = $cgi->remote_user || "gitweb"; } + Otherwise it is treated as boolean value: if true gitweb would process + config file once per request, if false it would process config file only + once. Note: $my_url, $my_uri, and $base_url are overwritten with + their default values before every request, so if you want to change + them, be sure to set this variable to true or a code reference effecting + the desired changes. The default is true. Projects list file format ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a85e2f6..d521c93 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -7,6 +7,7 @@ # # This program is licensed under the GPLv2 +use 5.008; use strict; use warnings; use CGI qw(:standard :escapeHTML -nosticky); @@ -16,12 +17,10 @@ use Encode; use Fcntl ':mode'; use File::Find qw(); use File::Basename qw(basename); +use Time::HiRes qw(gettimeofday tv_interval); binmode STDOUT, ':utf8'; -our $t0; -if (eval { require Time::HiRes; 1; }) { - $t0 = [Time::HiRes::gettimeofday()]; -} +our $t0 = [ gettimeofday() ]; our $number_of_git_cmds = 0; BEGIN { @@ -165,6 +164,12 @@ our @diff_opts = ('-M'); # taken from git_commit # the gitweb domain. our $prevent_xss = 0; +# Path to the highlight executable to use (must be the one from +# http://www.andre-simon.de due to assumptions about parameters and output). +# Useful if highlight is not installed on your webserver's PATH. +# [Default: highlight] +our $highlight_bin = "++HIGHLIGHT_BIN++"; + # information about snapshot formats that gitweb is capable of serving our %known_snapshot_formats = ( # name => { @@ -486,6 +491,18 @@ our %feature = ( 'sub' => sub { feature_bool('highlight', @_) }, 'override' => 0, 'default' => [0]}, + + # Enable displaying of remote heads in the heads list + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'remote_heads'}{'default'} = [1]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'remote_heads'}{'override'} = 1; + # and in project config gitweb.remote_heads = 0|1; + 'remote_heads' => { + 'sub' => sub { feature_bool('remote_heads', @_) }, + 'override' => 0, + 'default' => [0]}, ); sub gitweb_get_feature { @@ -594,6 +611,14 @@ sub filter_snapshot_fmts { !$known_snapshot_formats{$_}{'disabled'}} @fmts; } +# If it is set to code reference, it is code that it is to be run once per +# request, allowing updating configurations that change with each request, +# while running other code in config file only once. +# +# Otherwise, if it is false then gitweb would process config file only once; +# if it is true then gitweb config would be run for each request. +our $per_request_config = 1; + our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM); sub evaluate_gitweb_config { our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; @@ -700,6 +725,7 @@ our %actions = ( "log" => \&git_log, "patch" => \&git_patch, "patches" => \&git_patches, + "remotes" => \&git_remotes, "rss" => \&git_rss, "atom" => \&git_atom, "search" => \&git_search, @@ -774,10 +800,10 @@ sub evaluate_path_info { 'history', ); - # we want to catch + # we want to catch, among others # [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name] my ($parentrefname, $parentpathname, $refname, $pathname) = - ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?(.+?)(?::(.+))?$/); + ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?([^:]+?)?(?::(.+))?$/); # first, analyze the 'current' part if (defined $pathname) { @@ -813,8 +839,15 @@ sub evaluate_path_info { # hash_base instead. It should also be noted that hand-crafted # links having 'history' as an action and no pathname or hash # set will fail, but that happens regardless of PATH_INFO. - $input_params{'action'} ||= "shortlog"; - if (grep { $_ eq $input_params{'action'} } @wants_base) { + if (defined $parentrefname) { + # if there is parent let the default be 'shortlog' action + # (for http://git.example.com/repo.git/A..B links); if there + # is no parent, dispatch will detect type of object and set + # action appropriately if required (if action is not set) + $input_params{'action'} ||= "shortlog"; + } + if ($input_params{'action'} && + grep { $_ eq $input_params{'action'} } @wants_base) { $input_params{'hash_base'} ||= $refname; } else { $input_params{'hash'} ||= $refname; @@ -1051,16 +1084,27 @@ sub dispatch { } sub reset_timer { - our $t0 = [Time::HiRes::gettimeofday()] + our $t0 = [ gettimeofday() ] if defined $t0; our $number_of_git_cmds = 0; } +our $first_request = 1; sub run_request { reset_timer(); evaluate_uri(); - evaluate_gitweb_config(); + if ($first_request) { + evaluate_gitweb_config(); + evaluate_git_version(); + } + if ($per_request_config) { + if (ref($per_request_config) eq 'CODE') { + $per_request_config->(); + } elsif (!$first_request) { + evaluate_gitweb_config(); + } + } check_loadavg(); # $projectroot and $projects_list might be set in gitweb config file @@ -1113,8 +1157,8 @@ sub evaluate_argv { sub run { evaluate_argv(); - evaluate_git_version(); + $first_request = 1; $pre_listen_hook->() if $pre_listen_hook; @@ -1127,6 +1171,7 @@ sub run { $post_dispatch_hook->() if $post_dispatch_hook; + $first_request = 0; last REQUEST if ($is_last_request->()); } @@ -1355,6 +1400,13 @@ sub esc_url { return $str; } +# quote unsafe characters in HTML attributes +sub esc_attr { + + # for XHTML conformance escaping '"' to '"' is not enough + return esc_html(@_); +} + # replace invalid utf8 character with SUBSTITUTION sequence sub esc_html { my $str = shift; @@ -1760,7 +1812,7 @@ sub format_ref_marker { hash=>$dest )}, $name); - $markers .= " <span class=\"$class\" title=\"$ref\">" . + $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" . $link . "</span>"; } } @@ -1844,7 +1896,7 @@ sub git_get_avatar { return $pre_white . "<img width=\"$size\" " . "class=\"avatar\" " . - "src=\"$url\" " . + "src=\"".esc_url($url)."\" " . "alt=\"\" " . "/>" . $post_white; } else { @@ -2555,7 +2607,7 @@ sub git_show_project_tagcloud { } else { my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud; return '<p align="center">' . join (', ', map { - "<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>" + $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname}) } splice(@tags, 0, $count)) . '</p>'; } } @@ -2745,6 +2797,44 @@ sub git_get_last_activity { return (undef, undef); } +# Implementation note: when a single remote is wanted, we cannot use 'git +# remote show -n' because that command always work (assuming it's a remote URL +# if it's not defined), and we cannot use 'git remote show' because that would +# try to make a network roundtrip. So the only way to find if that particular +# remote is defined is to walk the list provided by 'git remote -v' and stop if +# and when we find what we want. +sub git_get_remotes_list { + my $wanted = shift; + my %remotes = (); + + open my $fd, '-|' , git_cmd(), 'remote', '-v'; + return unless $fd; + while (my $remote = <$fd>) { + chomp $remote; + $remote =~ s!\t(.*?)\s+\((\w+)\)$!!; + next if $wanted and not $remote eq $wanted; + my ($url, $key) = ($1, $2); + + $remotes{$remote} ||= { 'heads' => () }; + $remotes{$remote}{$key} = $url; + } + close $fd or return; + return wantarray ? %remotes : \%remotes; +} + +# Takes a hash of remotes as first parameter and fills it by adding the +# available remote heads for each of the indicated remotes. +sub fill_remote_heads { + my $remotes = shift; + my @heads = map { "remotes/$_" } keys %$remotes; + my @remoteheads = git_get_heads_list(undef, @heads); + foreach my $remote (keys %$remotes) { + $remotes->{$remote}{'heads'} = [ grep { + $_->{'name'} =~ s!^$remote/!! + } @remoteheads ]; + } +} + sub git_get_references { my $type = shift || ""; my %refs; @@ -3143,13 +3233,15 @@ sub parse_from_to_diffinfo { ## parse to array of hashes functions sub git_get_heads_list { - my $limit = shift; + my ($limit, @classes) = @_; + @classes = ('heads') unless @classes; + my @patterns = map { "refs/$_" } @classes; my @headslist; open my $fd, '-|', git_cmd(), 'for-each-ref', ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate', '--format=%(objectname) %(refname) %(subject)%00%(committer)', - 'refs/heads' + @patterns or return; while (my $line = <$fd>) { my %ref_item; @@ -3160,7 +3252,7 @@ sub git_get_heads_list { my ($committer, $epoch, $tz) = ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/); $ref_item{'fullname'} = $name; - $name =~ s!^refs/heads/!!; + $name =~ s!^refs/(?:head|remote)s/!!; $ref_item{'name'} = $name; $ref_item{'id'} = $hash; @@ -3360,7 +3452,8 @@ sub run_highlighter { close $fd or die_error(404, "Reading blob failed"); open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ". - "highlight --xhtml --fragment --syntax $syntax |" + quote_command($highlight_bin). + " --xhtml --fragment --syntax $syntax |" or die_error(500, "Couldn't open file or run syntax highlighter"); return $fd; } @@ -3428,11 +3521,11 @@ EOF # print out each stylesheet that exist, providing backwards capability # for those people who defined $stylesheet in a config file if (defined $stylesheet) { - print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n"; + print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n"; } else { foreach my $stylesheet (@stylesheets) { next unless $stylesheet; - print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n"; + print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n"; } } if (defined $project) { @@ -3445,7 +3538,7 @@ EOF my $type = lc($format); my %link_attr = ( '-rel' => 'alternate', - '-title' => "$project - $href_params{'-title'} - $format feed", + '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"), '-type' => "application/$type+xml" ); @@ -3472,13 +3565,13 @@ EOF } else { printf('<link rel="alternate" title="%s projects list" '. 'href="%s" type="text/plain; charset=utf-8" />'."\n", - $site_name, href(project=>undef, action=>"project_index")); + esc_attr($site_name), href(project=>undef, action=>"project_index")); printf('<link rel="alternate" title="%s projects feeds" '. 'href="%s" type="text/x-opml" />'."\n", - $site_name, href(project=>undef, action=>"opml")); + esc_attr($site_name), href(project=>undef, action=>"opml")); } if (defined $favicon) { - print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n); + print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n); } print "</head>\n" . @@ -3491,12 +3584,20 @@ EOF print "<div class=\"page_header\">\n" . $cgi->a({-href => esc_url($logo_url), -title => $logo_label}, - qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>)); + qq(<img src=").esc_url($logo).qq(" width="72" height="27" alt="git" class="logo"/>)); print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; if (defined $project) { print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); if (defined $action) { - print " / $action"; + my $action_print = $action ; + if (defined $opts{-action_extra}) { + $action_print = $cgi->a({-href => href(action=>$action)}, + $action); + } + print " / $action_print"; + } + if (defined $opts{-action_extra}) { + print " / $opts{-action_extra}"; } print "\n"; } @@ -3575,7 +3676,7 @@ sub git_footer_html { print "<div id=\"generating_info\">\n"; print 'This page took '. '<span id="generating_time" class="time_span">'. - Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]). + tv_interval($t0, [ gettimeofday() ]). ' seconds </span>'. ' and '. '<span id="generating_cmd">'. @@ -3589,7 +3690,7 @@ sub git_footer_html { insert_file($site_footer); } - print qq!<script type="text/javascript" src="$javascript"></script>\n!; + print qq!<script type="text/javascript" src="!.esc_url($javascript).qq!"></script>\n!; if (defined $action && $action eq 'blame_incremental') { print qq!<script type="text/javascript">\n!. @@ -3703,6 +3804,19 @@ sub git_print_page_nav { "</div>\n"; } +# returns a submenu for the nagivation of the refs views (tags, heads, +# remotes) with the current view disabled and the remotes view only +# available if the feature is enabled +sub format_ref_views { + my ($current) = @_; + my @ref_views = qw{tags heads}; + push @ref_views, 'remotes' if gitweb_check_feature('remote_heads'); + return join " | ", map { + $_ eq $current ? $_ : + $cgi->a({-href => href(action=>$_)}, $_) + } @ref_views +} + sub format_paging_nav { my ($action, $page, $has_next_link) = @_; my $paging_nav; @@ -3746,6 +3860,49 @@ sub git_print_header_div { "\n</div>\n"; } +sub format_repo_url { + my ($name, $url) = @_; + return "<tr class=\"metadata_url\"><td>$name</td><td>$url</td></tr>\n"; +} + +# Group output by placing it in a DIV element and adding a header. +# Options for start_div() can be provided by passing a hash reference as the +# first parameter to the function. +# Options to git_print_header_div() can be provided by passing an array +# reference. This must follow the options to start_div if they are present. +# The content can be a scalar, which is output as-is, a scalar reference, which +# is output after html escaping, an IO handle passed either as *handle or +# *handle{IO}, or a function reference. In the latter case all following +# parameters will be taken as argument to the content function call. +sub git_print_section { + my ($div_args, $header_args, $content); + my $arg = shift; + if (ref($arg) eq 'HASH') { + $div_args = $arg; + $arg = shift; + } + if (ref($arg) eq 'ARRAY') { + $header_args = $arg; + $arg = shift; + } + $content = $arg; + + print $cgi->start_div($div_args); + git_print_header_div(@$header_args); + + if (ref($content) eq 'CODE') { + $content->(@_); + } elsif (ref($content) eq 'SCALAR') { + print esc_html($$content); + } elsif (ref($content) eq 'GLOB' or ref($content) eq 'IO::Handle') { + print <$content>; + } elsif (!ref($content) && defined($content)) { + print $content; + } + + print $cgi->end_div; +} + sub print_local_time { print format_local_time(@_); } @@ -4945,7 +5102,7 @@ sub git_heads_body { "<td class=\"link\">" . $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " . $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") . + $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'fullname'})}, "tree") . "</td>\n" . "</tr>"; } @@ -4957,6 +5114,101 @@ sub git_heads_body { print "</table>\n"; } +# Display a single remote block +sub git_remote_block { + my ($remote, $rdata, $limit, $head) = @_; + + my $heads = $rdata->{'heads'}; + my $fetch = $rdata->{'fetch'}; + my $push = $rdata->{'push'}; + + my $urls_table = "<table class=\"projects_list\">\n" ; + + if (defined $fetch) { + if ($fetch eq $push) { + $urls_table .= format_repo_url("URL", $fetch); + } else { + $urls_table .= format_repo_url("Fetch URL", $fetch); + $urls_table .= format_repo_url("Push URL", $push) if defined $push; + } + } elsif (defined $push) { + $urls_table .= format_repo_url("Push URL", $push); + } else { + $urls_table .= format_repo_url("", "No remote URL"); + } + + $urls_table .= "</table>\n"; + + my $dots; + if (defined $limit && $limit < @$heads) { + $dots = $cgi->a({-href => href(action=>"remotes", hash=>$remote)}, "..."); + } + + print $urls_table; + git_heads_body($heads, $head, 0, $limit, $dots); +} + +# Display a list of remote names with the respective fetch and push URLs +sub git_remotes_list { + my ($remotedata, $limit) = @_; + print "<table class=\"heads\">\n"; + my $alternate = 1; + my @remotes = sort keys %$remotedata; + + my $limited = $limit && $limit < @remotes; + + $#remotes = $limit - 1 if $limited; + + while (my $remote = shift @remotes) { + my $rdata = $remotedata->{$remote}; + my $fetch = $rdata->{'fetch'}; + my $push = $rdata->{'push'}; + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td>" . + $cgi->a({-href=> href(action=>'remotes', hash=>$remote), + -class=> "list name"},esc_html($remote)) . + "</td>"; + print "<td class=\"link\">" . + (defined $fetch ? $cgi->a({-href=> $fetch}, "fetch") : "fetch") . + " | " . + (defined $push ? $cgi->a({-href=> $push}, "push") : "push") . + "</td>"; + + print "</tr>\n"; + } + + if ($limited) { + print "<tr>\n" . + "<td colspan=\"3\">" . + $cgi->a({-href => href(action=>"remotes")}, "...") . + "</td>\n" . "</tr>\n"; + } + + print "</table>"; +} + +# Display remote heads grouped by remote, unless there are too many +# remotes, in which case we only display the remote names +sub git_remotes_body { + my ($remotedata, $limit, $head) = @_; + if ($limit and $limit < keys %$remotedata) { + git_remotes_list($remotedata, $limit); + } else { + fill_remote_heads($remotedata); + while (my ($remote, $rdata) = each %$remotedata) { + git_print_section({-class=>"remote", -id=>$remote}, + ["remotes", $remote, $remote], sub { + git_remote_block($remote, $rdata, $limit, $head); + }); + } + } +} + sub git_search_grep_body { my ($commitlist, $from, $to, $extra) = @_; $from = 0 unless defined $from; @@ -5094,6 +5346,7 @@ sub git_summary { my %co = parse_commit("HEAD"); my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : (); my $head = $co{'id'}; + my $remote_heads = gitweb_check_feature('remote_heads'); my $owner = git_get_project_owner($project); @@ -5102,6 +5355,7 @@ sub git_summary { # there are more ... my @taglist = git_get_tags_list(16); my @headlist = git_get_heads_list(16); + my %remotedata = $remote_heads ? git_get_remotes_list() : (); my @forklist; my $check_forks = gitweb_check_feature('forks'); @@ -5127,7 +5381,7 @@ sub git_summary { @url_list = map { "$_/$project" } @git_base_url_list unless @url_list; foreach my $git_url (@url_list) { next unless $git_url; - print "<tr class=\"metadata_url\"><td>$url_tag</td><td>$git_url</td></tr>\n"; + print format_repo_url($url_tag, $git_url); $url_tag = ""; } @@ -5179,6 +5433,11 @@ sub git_summary { $cgi->a({-href => href(action=>"heads")}, "...")); } + if (%remotedata) { + git_print_header_div('remotes'); + git_remotes_body(\%remotedata, 15, $head); + } + if (@forklist) { git_print_header_div('forks'); git_project_list_body(\@forklist, 'age', 0, 15, @@ -5283,7 +5542,7 @@ sub git_blame_common { print 'END'; if (defined $t0 && gitweb_check_feature('timed')) { print ' '. - Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]). |