diff options
148 files changed, 4391 insertions, 1111 deletions
diff --git a/.gitignore b/.gitignore index 561401b2b..14e2b6bde 100644 --- a/.gitignore +++ b/.gitignore @@ -112,6 +112,7 @@ /git-remote-https /git-remote-ftp /git-remote-ftps +/git-remote-testgit /git-repack /git-replace /git-repo-config @@ -155,6 +156,7 @@ /git-write-tree /git-core-*/?* /gitk-git/gitk-wish +/gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi /gitweb/gitweb.min.* /test-chmtime diff --git a/Documentation/Makefile b/Documentation/Makefile index 8a8a3954d..04f69cf64 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -264,7 +264,9 @@ manpage-base-url.xsl: manpage-base-url.xsl.in mv $@+ $@ user-manual.xml: user-manual.txt user-manual.conf - $(QUIET_ASCIIDOC)$(ASCIIDOC) $(ASCIIDOC_EXTRA) -b docbook -d book $< + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + $(ASCIIDOC) $(ASCIIDOC_EXTRA) -b docbook -d book -o $@+ $< && \ + mv $@+ $@ technical/api-index.txt: technical/api-index-skel.txt \ technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS)) @@ -278,7 +280,9 @@ XSLT = docbook.xsl XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css user-manual.html: user-manual.xml - $(QUIET_XSLTPROC)xsltproc $(XSLTOPTS) -o $@ $(XSLT) $< + $(QUIET_XSLTPROC)$(RM) $@+ $@ && \ + xsltproc $(XSLTOPTS) -o $@+ $(XSLT) $< && \ + mv $@+ $@ git.info: user-manual.texi $(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi diff --git a/Documentation/RelNotes-1.7.0.6.txt b/Documentation/RelNotes-1.7.0.6.txt new file mode 100644 index 000000000..b2852b67d --- /dev/null +++ b/Documentation/RelNotes-1.7.0.6.txt @@ -0,0 +1,13 @@ +Git v1.7.0.6 Release Notes +========================== + +Fixes since v1.7.0.5 +-------------------- + + * "git diff --stat" used "int" to count the size of differences, + which could result in overflowing. + + * "git rev-list --abbrev-commit" defaulted to 40-byte abbreviations, unlike + newer tools in the git toolset. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes-1.7.1.txt b/Documentation/RelNotes-1.7.1.txt index 10389adb4..9d89fedb3 100644 --- a/Documentation/RelNotes-1.7.1.txt +++ b/Documentation/RelNotes-1.7.1.txt @@ -1,5 +1,5 @@ -Git v1.7.1 Release Notes (draft) -================================ +Git v1.7.1 Release Notes +======================== Updates since v1.7.0 -------------------- @@ -87,9 +87,3 @@ release, unless otherwise noted. * "git rev-list --abbrev-commit" defaulted to 40-byte abbreviations, unlike newer tools in the git toolset. - ---- -exec >/var/tmp/1 -echo O=$(git describe) -O=v1.7.1-rc1-49-g407a963 -git shortlog --no-merges ^maint $O.. diff --git a/Documentation/RelNotes-1.7.2.txt b/Documentation/RelNotes-1.7.2.txt new file mode 100644 index 000000000..37781b4f1 --- /dev/null +++ b/Documentation/RelNotes-1.7.2.txt @@ -0,0 +1,40 @@ +Git v1.7.2 Release Notes (draft) +================================ + +Updates since v1.7.1 +-------------------- + + * After "git apply --whitespace=fix" removed trailing blank lines in an + patch in a patch series, it failed to apply later patches that depend + on the presense of such blank lines. + + * The output from the textconv filter used by "git diff" can be cached to + speed up their reuse. + + * "git send-email" learned --smtp-domain option to specify the domainname + used in the EHLO/HELO exchange. + + * "git revert" learned --strategy option to specify the merge strategy. + + * The whitespace rules used in "git apply --whitespace" and "git diff" + gained a new member in the family (tab-in-indent) to help projects with + policy to indent only with spaces. + + * Authentication over http transport can now be made lazily, in that the + request can first go to a URL without username, get a 401 response and + then the client will ask for the username to use. + + +Fixes since v1.7.1 +------------------ + + * In 1.7.1, "git status" stopped refreshing the index by mistake. + +All of the fixes in v1.7.1.X maintenance series are included in this +release, unless otherwise noted. + +-- +exec >/var/tmp/1 +O=v1.7.1-77-gb751157 +echo O=$(git describe master) +git shortlog --no-merges master ^maint ^$O diff --git a/Documentation/config.txt b/Documentation/config.txt index 630e7ca0c..87f397ed9 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -481,6 +481,8 @@ core.whitespace:: error (enabled by default). * `indent-with-non-tab` treats a line that is indented with 8 or more space characters as an error (not enabled by default). +* `tab-in-indent` treats a tab character in the initial indent part of + the line as an error (not enabled by default). * `blank-at-eof` treats blank lines added at the end of file as an error (enabled by default). * `trailing-space` is a short-hand to cover both `blank-at-eol` and @@ -944,13 +946,19 @@ gc.pruneexpire:: unreachable objects immediately. gc.reflogexpire:: +gc.<pattern>.reflogexpire:: 'git reflog expire' removes reflog entries older than - this time; defaults to 90 days. + this time; defaults to 90 days. With "<pattern>" (e.g. + "refs/stash") in the middle the setting applies only to + the refs that match the <pattern>. gc.reflogexpireunreachable:: +gc.<ref>.reflogexpireunreachable:: 'git reflog expire' removes reflog entries older than this time and are not reachable from the current tip; - defaults to 30 days. + defaults to 30 days. With "<pattern>" (e.g. "refs/stash") + in the middle, the setting applies only to the refs that + match the <pattern>. gc.rerereresolved:: Records of conflicted merge you resolved earlier are @@ -1268,6 +1276,13 @@ log.date:: following alternatives: {relative,local,default,iso,rfc,short}. See linkgit:git-log[1]. +log.decorate:: + Print out the ref names of any commits that are shown by the log + command. If 'short' is specified, the ref name prefixes 'refs/heads/', + 'refs/tags/' and 'refs/remotes/' will not be printed. If 'full' is + specified, the full ref name (including prefix) will be printed. + This is the same as the log commands '--decorate' option. + log.showroot:: If true, the initial commit will be shown as a big creation event. This is equivalent to a diff against an empty tree. @@ -1359,10 +1374,6 @@ notes.rewrite.<command>:: automatically copies your notes from the original to the rewritten commit. Defaults to `true`, but see "notes.rewriteRef" below. -+ -This setting can be overridden with the `GIT_NOTES_REWRITE_REF` -environment variable, which must be a colon separated list of refs or -globs. notes.rewriteMode:: When copying notes during a rewrite (see the @@ -1382,6 +1393,10 @@ notes.rewriteRef:: + Does not have a default value; you must configure this variable to enable note rewriting. ++ +This setting can be overridden with the `GIT_NOTES_REWRITE_REF` +environment variable, which must be a colon separated list of refs or +globs. pack.window:: The size of the window used by linkgit:git-pack-objects[1] when no @@ -1516,7 +1531,7 @@ receive.denyDeletes:: the ref. Use this to prevent such a ref deletion via a push. receive.denyCurrentBranch:: - If set to true or "refuse", receive-pack will deny a ref update + If set to true or "refuse", git-receive-pack will deny a ref update to the currently checked out branch of a non-bare repository. Such a push is potentially dangerous because it brings the HEAD out of sync with the index and working tree. If set to "warn", @@ -1644,6 +1659,7 @@ sendemail.smtppass:: sendemail.suppresscc:: sendemail.suppressfrom:: sendemail.to:: +sendemail.smtpdomain:: sendemail.smtpserver:: sendemail.smtpserverport:: sendemail.smtpuser:: diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index c9c6c2b1c..0d89aaaf2 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -21,6 +21,7 @@ endif::git-format-patch[] ifndef::git-format-patch[] -p:: -u:: +--patch:: Generate patch (see section on generating patches). {git-diff? This is the default.} endif::git-format-patch[] @@ -126,11 +127,39 @@ any of those replacements occurred. gives the default to color output. Same as `--color=never`. ---color-words[=<regex>]:: - Show colored word diff, i.e., color words which have changed. - By default, words are separated by whitespace. +--word-diff[=<mode>]:: + Show a word diff, using the <mode> to delimit changed words. + By default, words are delimited by whitespace; see + `--word-diff-regex` below. The <mode> defaults to 'plain', and + must be one of: ++ +-- +color:: + Highlight changed words using only colors. Implies `--color`. +plain:: + Show words as `[-removed-]` and `{+added+}`. Makes no + attempts to escape the delimiters if they appear in the input, + so the output may be ambiguous. +porcelain:: + Use a special line-based format intended for script + consumption. Added/removed/unchanged runs are printed in the + usual unified diff format, starting with a `+`/`-`/` ` + character at the beginning of the line and extending to the + end of the line. Newlines in the input are represented by a + tilde `~` on a line of its own. +none:: + Disable word diff again. +-- ++ +Note that despite the name of the first mode, color is used to +highlight the changed parts in all modes if enabled. + +--word-diff-regex=<regex>:: + Use <regex> to decide what a word is, instead of considering + runs of non-whitespace to be a word. Also implies + `--word-diff` unless it was already enabled. + -When a <regex> is specified, every non-overlapping match of the +Every non-overlapping match of the <regex> is considered a word. Anything between these matches is considered whitespace and ignored(!) for the purposes of finding differences. You may want to append `|[^[:space:]]` to your regular @@ -142,6 +171,10 @@ The regex can also be set via a diff driver or configuration option, see linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly overrides any diff driver or configuration setting. Diff drivers override configuration settings. + +--color-words[=<regex>]:: + Equivalent to `--word-diff=color` plus (if a regex was + specified) `--word-diff-regex=<regex>`. endif::git-format-patch[] --no-renames:: diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 37c1810e3..4505eb6d8 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git checkout' [-q] [-f] [-m] [<branch>] -'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>] +'git checkout' [-q] [-f] [-m] [[-b|--orphan] <new_branch>] [<start_point>] 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>... 'git checkout' --patch [<tree-ish>] [--] [<paths>...] @@ -90,6 +90,24 @@ explicitly give a name with '-b' in such a case. Create the new branch's reflog; see linkgit:git-branch[1] for details. +--orphan:: + Create a new branch named <new_branch>, unparented to any other + branch. The new branch you switch to does not have any commit + and after the first one it will become the root of a new history + completely unconnected from all the other branches. ++ +When you use "--orphan", the index and the working tree are kept intact. +This allows you to start a new history that records set of paths similar +to that of the start-point commit, which is useful when you want to keep +different branches for different audiences you are working to like when +you have an open source and commercial versions of a software, for example. ++ +If you want to start a disconnected history that records set of paths +totally different from the original branch, you may want to first clear +the index and the working tree, by running "git rm -rf ." from the +top-level of the working tree, before preparing your files (by copying +from elsewhere, extracting a tarball, etc.) in the working tree. + -m:: --merge:: When switching branches, diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 64fb458b4..32c482f33 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run] [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author] - [--allow-empty] [--no-verify] [-e] [--author=<author>] + [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>] [--date=<date>] [--cleanup=<mode>] [--status | --no-status] [--] [[-i | -o ]<file>...] @@ -131,6 +131,12 @@ OPTIONS from making such a commit. This option bypasses the safety, and is primarily for use by foreign scm interface scripts. +--allow-empty-message:: + Like --allow-empty this command is primarily for use by foreign + scm interface scripts. It allows you to create a commit with an + empty commit message without using plumbing commands like + linkgit:git-commit-tree[1]. + --cleanup=<mode>:: This option sets how the commit message is cleaned up. The '<mode>' can be one of 'verbatim', 'whitespace', 'strip', diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 7e83288d1..390d85cca 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -86,6 +86,7 @@ objectsize:: objectname:: The object name (aka SHA-1). + For a non-ambiguous abbreviation of the object name append `:short`. upstream:: The name of a local ref which can be considered ``upstream'' diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 189573a3b..a9e0882e9 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -88,6 +88,16 @@ commits prior to the amend or rebase occurring. Since these changes 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 +branches: + +------------ +[gc "refs/remotes/*"] + reflogExpire = never + reflogexpireUnreachable = 3 days +------------ + The optional configuration variable 'gc.rerereresolved' indicates how long records of conflicted merge you resolved earlier are kept. This defaults to 60 days. diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index fb184ba18..57aa57467 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -37,7 +37,8 @@ include::diff-options.txt[] and <until>, see "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. ---decorate[=short|full]:: +--no-decorate:: +--decorate[=short|full|no]:: Print out the ref names of any commits that are shown. If 'short' is specified, the ref name prefixes 'refs/heads/', 'refs/tags/' and 'refs/remotes/' will not be printed. If 'full' is specified, the diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0d07b1b20..5863decdc 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -295,6 +295,7 @@ link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details). --ignore-date:: These flags are passed to 'git am' to easily change the dates of the rebased commits (see linkgit:git-am[1]). + Incompatible with the --interactive option. -i:: --interactive:: diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index ced35b2f5..12622fc49 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -119,6 +119,13 @@ Sending value reverts to plain SMTP. Default is the value of 'sendemail.smtpencryption'. +--smtp-domain=<FQDN>:: + Specifies the Fully Qualified Domain Name (FQDN) used in the + HELO/EHLO command to the SMTP server. Some servers require the + FQDN to match your IP address. If not set, git send-email attempts + to determine your FQDN automatically. Default is the value of + 'sendemail.smtpdomain'. + --smtp-pass[=<password>]:: Password for SMTP-AUTH. The argument is optional: If no argument is specified, then the empty string is used as @@ -300,6 +307,21 @@ sendemail.confirm:: in the previous section for the meaning of these values. +Use gmail as the smtp server +---------------------------- + +Add the following section to the config file: + + [sendemail] + smtpencryption = tls + smtpserver = smtp.gmail.com + smtpuser = yourname@gmail.com + smtpserverport = 587 + +Note: the following perl modules are required + Net::SMTP::SSL, MIME::Base64 and Authen::SASL + + Author ------ Written by Ryan Anderson <ryan@michonline.com> diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index dfd4d0c22..bc1ac7749 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] git log --pretty=short | 'git shortlog' [-h] [-n] [-s] [-e] [-w] -'git shortlog' [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...] +'git shortlog' [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] <commit>... DESCRIPTION ----------- @@ -19,6 +19,11 @@ the first line of the commit message will be shown. Additionally, "[PATCH]" will be stripped from the commit description. +If no revisions are passed on the command line and either standard input +is not a terminal or there is no current branch, 'git shortlog' will +output a summary of the log read from standard input, without +reference to the current repository. + OPTIONS ------- @@ -39,6 +44,14 @@ OPTIONS --email:: Show the email address of each author. +--format[='<format>']:: + Instead of the commit subject, use some other information to + describe each commit. '<format>' can be any string accepted + by the `--format` option of 'git log', such as '{asterisk} [%h] %s'. + (See the "PRETTY FORMATS" section of linkgit:git-log[1].) + + Each pretty-printed commit will be rewrapped before it is shown. + -w[<width>[,<indent1>[,<indent2>]]]:: Linewrap the output by wrapping each line at `width`. The first line of each entry is indented by `indent1` spaces, and the second diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 1cab91b53..2d4bbfcaf 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -72,21 +72,37 @@ In short-format, the status of each path is shown as where `PATH1` is the path in the `HEAD`, and ` -> PATH2` part is shown only when `PATH1` corresponds to a different path in the -index/worktree (i.e. renamed). - -For unmerged entries, `X` shows the status of stage #2 (i.e. ours) and `Y` -shows the status of stage #3 (i.e. theirs). - -For entries that do not have conflicts, `X` shows the status of the index, -and `Y` shows the status of the work tree. For untracked paths, `XY` are -`??`. +index/worktree (i.e. the file is renamed). The 'XY' is a two-letter +status code. + +The fields (including the `->`) are separated from each other by a +single space. If a filename contains whitespace or other nonprintable +characters, that field will be quoted in the manner of a C string +literal: surrounded by ASCII double quote (34) characters, and with +interior special characters backslash-escaped. + +For paths with merge conflicts, `X` and 'Y' show the modification +states of each side of the merge. For paths that do not have merge +conflicts, `X` shows the status of the index, and `Y` shows the status +of the work tree. For untracked paths, `XY` are `??`. Other status +codes can be interpreted as follows: + +* ' ' = unmodified +* 'M' = modified +* 'A' = added +* 'D' = deleted +* 'R' = renamed +* 'C' = copied +* 'U' = updated but unmerged + +Ignored files are not listed. X Y Meaning ------------------------------------------------- [MD] not updated M [ MD] updated in index A [ MD] added to index - D [ MD] deleted from index + D [ M] deleted from index R [ MD] renamed in index C [ MD] copied in index [MARC] index and work tree matches @@ -104,6 +120,15 @@ and `Y` shows the status of the work tree. For untracked paths, `XY` are ? ? untracked ------------------------------------------------- +There is an alternate -z format recommended for machine parsing. In +that format, the status field is the same, but some other things +change. First, the '->' is omitted from rename entries and the field +order is reversed (e.g 'from -> to' becomes 'to from'). Second, a NUL +(ASCII 0) follows each filename, replacing space as a field separator +and the terminating newline (but a space still separates the status +field from the first filename). Third, filenames containing special +characters are not specially formatted; no quoting or +backslash-escaping is performed. CONFIGURATION ------------- diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 99f3c1ea6..b09bd9761 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -243,7 +243,7 @@ where <name> is the name of the SVN repository as specified by the -R option to --username;; Specify the SVN username to perform the commit as. This option overrides - configuration property 'username'. + the 'username' configuration property. --commit-url;; Use the specified URL to connect to the destination Subversion diff --git a/Documentation/git.txt b/Documentation/git.txt index fbae9956b..bec6348da 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -12,6 +12,7 @@ SYNOPSIS 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] + [-c name=value] [--help] COMMAND [ARGS] DESCRIPTION @@ -43,9 +44,15 @@ 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.0.5/git.html[documentation for release 1.7.0.5] +* link:v1.7.1/git.html[documentation for release 1.7.1] * release notes for + link:RelNotes-1.7.1.txt[1.7.1]. + +* link:v1.7.0.6/git.html[documentation for release 1.7.0.6] + +* release notes for + link:RelNotes-1.7.0.6.txt[1.7.0.6], link:RelNotes-1.7.0.5.txt[1.7.0.5], link:RelNotes-1.7.0.4.txt[1.7.0.4], link:RelNotes-1.7.0.3.txt[1.7.0.3], @@ -222,6 +229,12 @@ displayed. See linkgit:git-help[1] for more information, because `git --help ...` is converted internally into `git help ...`. +-c <name>=<value>:: + Pass a configuration parameter to the command. The value + given will override values from configuration files. + The <name> is expected in the same format as listed by + 'git config' (subkeys separated by dots). + --exec-path:: Path to wherever your core git programs are installed. This can also be controlled by setting the GIT_EXEC_PATH @@ -532,6 +545,16 @@ git so take care if using Cogito etc. a GIT_DIR set on the command line or in the environment. (Useful for excluding slow-loading network directories.) +'GIT_DISCOVERY_ACROSS_FILESYSTEM':: + When run in a directory that does not have ".git" repository + directory, git tries to find such a directory in the parent + directories to find the top of the working tree, but by default it + does not cross filesystem boundaries. This environment variable + can be set to true to tell git not to stop at filesystem + boundaries. Like 'GIT_CEILING_DIRECTORIES', this will not affect + an explicit repository directory set via 'GIT_DIR' or on the + command line. + git Commits ~~~~~~~~~~~ 'GIT_AUTHOR_NAME':: diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index d892e642e..0523a5769 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -360,7 +360,7 @@ patterns are available: Customizing word diff ^^^^^^^^^^^^^^^^^^^^^ -You can customize the rules that `git diff --color-words` uses to +You can customize the rules that `git diff --word-diff` uses to split words in a line, by specifying an appropriate regular expression in the "diff.*.wordRegex" configuration variable. For example, in TeX a backslash followed by a sequence of letters forms a command, but @@ -414,6 +414,26 @@ because it quickly conveys the changes you have made), you should generate it separately and send it as a comment _in addition to_ the usual binary diff that you might send. +Because text conversion can be slow, especially when doing a +large number of them with `git log -p`, git provides a mechanism +to cache the output and use it in future diffs. To enable +caching, set the "cachetextconv" variable in your diff driver's +config. For example: + +------------------------ +[diff "jpg"] + textconv = exif + cachetextconv = true +------------------------ + +This will cache the result of running "exif" on each blob +indefinitely. If you change the textconv config variable for a +diff driver, git will automatically invalidate the cache entries +and re-run the textconv filter. If you want to invalidate the +cache manually (e.g., because your version of "exif" was updated +and now produces better output), you can remove the cache +manually with `git update-ref -d refs/notes/textconv/jpg` (where +"jpg" is the name of the diff driver, as in the example above). Performing a three-way merge ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt index 9de8caf5d..5d91a7e5b 100644 --- a/Documentation/gitdiffcore.txt +++ b/Documentation/gitdiffcore.txt @@ -227,8 +227,8 @@ 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 "original" side has the specified string and -whose "result" side does not. Such a filepair represents "the +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 opposite case that loses the specified string. diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 1686a54d2..bd760d3bd 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -123,6 +123,7 @@ The placeholders are: - '%s': subject - '%f': sanitized subject line, suitable for a filename - '%b': body +- '%B': raw body (unwrapped subject and body) - '%N': commit notes - '%gD': reflog selector, e.g., `refs/stash@\{1\}` - '%gd': shortened reflog selector, e.g., `stash@\{1\}` diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 1063945c3..e45513dee 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.1.rc2 +DEF_VER=v1.7.1.GIT LF=' ' @@ -12,7 +12,7 @@ if test -f version then VN=$(cat version) || VN="$DEF_VER" elif test -d .git -o -f .git && - VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && + VN=$(git describe --match "v[0-9]*" --abbrev=4 HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; v[0-9]*) @@ -31,6 +31,9 @@ all:: # Define EXPATDIR=/foo/bar if your expat header and library files are in # /foo/bar/include and /foo/bar/lib directories. # +# Define HAVE_PATHS_H if you have paths.h and want to use the default PATH +# it specifies. +# # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. # # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks @@ -366,6 +369,8 @@ SCRIPT_PERL += git-relink.perl SCRIPT_PERL += git-send-email.perl SCRIPT_PERL += git-svn.perl +SCRIPT_PYTHON += git-remote-testgit.py + SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ @@ -486,6 +491,7 @@ LIB_H += log-tree.h LIB_H += mailmap.h LIB_H += merge-recursive.h LIB_H += notes.h +LIB_H += notes-cache.h LIB_H += object.h LIB_H += pack.h LIB_H += pack-refs.h @@ -575,6 +581,7 @@ LIB_OBJS += merge-file.o LIB_OBJS += merge-recursive.o LIB_OBJS += name-hash.o LIB_OBJS += notes.o +LIB_OBJS += notes-cache.o LIB_OBJS += object.o LIB_OBJS += pack-check.o LIB_OBJS += pack-refs.o @@ -735,10 +742,12 @@ EXTLIBS = ifeq ($(uname_S),Linux) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),UnixWare) CC = cc @@ -867,6 +876,7 @@ ifeq ($(uname_S),FreeBSD) NO_STRTOUMAX = YesPlease endif PYTHON_PATH = /usr/local/bin/python + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease @@ -875,6 +885,7 @@ ifeq ($(uname_S),OpenBSD) NEEDS_LIBICONV = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),NetBSD) ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) @@ -884,6 +895,7 @@ ifeq ($(uname_S),NetBSD) BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib USE_ST_TIMESPEC = YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),AIX) NO_STRCASESTR=YesPlease @@ -904,6 +916,7 @@ ifeq ($(uname_S),GNU) # GNU/Hurd NO_STRLCPY=YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),IRIX) NO_SETENV = YesPlease @@ -1353,6 +1366,10 @@ else LIB_OBJS += thread-utils.o endif +ifdef HAVE_PATHS_H + BASIC_CFLAGS += -DHAVE_PATHS_H +endif + ifdef DIR_HAS_BSD_GROUP_SEMANTICS COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS endif @@ -1620,13 +1637,8 @@ $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \ --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \ instlibdir` && \ - sed -e '1{' \ - -e ' s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ - -e '}' \ - -e 's|^import sys.*|&; \\\ - import os; \\\ - sys.path.insert(0, os.getenv("GITPYTHONLIB",\ - "@@INSTLIBDIR@@"));|' \ + sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ + -e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \ -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ $@.py >$@+ && \ chmod +x $@+ && \ @@ -2004,6 +2016,9 @@ endif done; } && \ ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X" +install-gitweb: + $(MAKE) -C gitweb install + install-doc: $(MAKE) -C Documentation install @@ -2098,7 +2113,7 @@ clean: $(RM) $(htmldocs).tar.gz $(manpages).tar.gz $(MAKE) -C Documentation/ clean ifndef NO_PERL - $(RM) gitweb/gitweb.cgi gitweb/gitweb.min.* + $(MAKE) -C gitweb clean $(MAKE) -C perl clean endif ifndef NO_PYTHON @@ -1 +1 @@ -Documentation/RelNotes-1.7.1.txt
\ No newline at end of file +Documentation/RelNotes-1.7.2.txt
\ No newline at end of file @@ -594,20 +594,25 @@ static int path_matches(const char *pathname, int pathlen, return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0; } +static int macroexpand_one(int attr_nr, int rem); + static int fill_one(const char *what, struct match_attr *a, int rem) { struct git_attr_check *check = check_all_attr; int i; - for (i = 0; 0 < rem && i < a->num_attr; i++) { + for (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) { struct git_attr *attr = a->state[i].attr; const char **n = &(check[attr->attr_nr].value); const char *v = a->state[i].setto; if (*n == ATTR__UNKNOWN) { - debug_set(what, a->u.pattern, attr, v); + debug_set(what, + a->is_macro ? a->u.attr->name : a->u.pattern, + attr, v); *n = v; rem--; + rem = macroexpand_one(attr->attr_nr, rem); } } return rem; @@ -629,19 +634,27 @@ static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem) return rem; } -static int macroexpand(struct attr_stack *stk, int rem) +static int macroexpand_one(int attr_nr, int rem) { + struct attr_stack *stk; + struct match_attr *a = NULL; int i; - struct git_attr_check *check = check_all_attr; - for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) { - struct match_attr *a = stk->attrs[i]; - if (!a->is_macro) - continue; - if (check[a->u.attr->attr_nr].value != ATTR__TRUE) - continue; + if (check_all_attr[attr_nr].value != ATTR__TRUE) + return rem; + + for (stk = attr_stack; !a && stk; stk = stk->prev) + for (i = stk->num_matches - 1; !a && 0 <= i; i--) { + struct match_attr *ma = stk->attrs[i]; + if (!ma->is_macro) + continue; + if (ma->u.attr->attr_nr == attr_nr) + a = ma; + } + + if (a) rem = fill_one("expand", a, rem); - } + return rem; } @@ -666,9 +679,6 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check) for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) rem = fill(path, pathlen, stk, rem); - for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) - rem = macroexpand(stk, rem); - for (i = 0; i < num; i++) { const char *value = check_all_attr[check[i].attr->attr_nr].value; if (value == ATTR__UNKNOWN) @@ -16,9 +16,6 @@ extern const char *help_unknown_cmd(const char *cmd); extern void prune_packed_objects(int); extern int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out); -extern int commit_tree(const char *msg, unsigned char *tree, - struct commit_list *parents, unsigned char *ret, - const char *author); extern int commit_notes(struct notes_tree *t, const char *msg); struct notes_rewrite_cfg { diff --git a/builtin/apply.c b/builtin/apply.c index 771c972c5..8fc5ec31d 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1854,6 +1854,8 @@ static int match_fragment(struct image *img, { int i; char *fixed_buf, *buf, *orig, *target; + struct strbuf fixed; + size_t fixed_len; int preimage_limit; if (preimage->nr + try_lno <= img->nr) { @@ -1864,13 +1866,13 @@ static int match_fragment(struct image *img, if (match_end && (preimage->nr + try_lno != img->nr)) return 0; } else if (ws_error_action == correct_ws_error && - (ws_rule & WS_BLANK_AT_EOF) && match_end) { + (ws_rule & WS_BLANK_AT_EOF)) { /* - * This hunk that matches at the end extends beyond - * the end of img, and we are removing blank lines - * at the end of the file. This many lines from the - * beginning of the preimage must match with img, and - * the remainder of the preimage must be blank. + * This hunk extends beyond the end of img, and we are + * removing blank lines at the end of the file. This + * many lines from the beginning of the preimage must + * match with img, and the remainder of the preimage + * must be blank. */ preimage_limit = img->nr - try_lno; } else { @@ -1977,12 +1979,12 @@ static int match_fragment(struct image *img, * use the whitespace from the preimage. */ extra_chars = preimage_end - preimage_eof; - fixed_buf = xmalloc(imgoff + extra_chars); - memcpy(fixed_buf, img->buf + try, imgoff); - memcpy(fixed_buf + imgoff, preimage_eof, extra_chars); - imgoff += extra_chars; + strbuf_init(&fixed, imgoff + extra_chars); + strbuf_add(&fixed, img->buf + try, imgoff); + strbuf_add(&fixed, preimage_eof, extra_chars); + fixed_buf = strbuf_detach(&fixed, &fixed_len); update_pre_post_images(preimage, postimage, - fixed_buf, imgoff, postlen); + fixed_buf, fixed_len, postlen); return 1; } @@ -1999,27 +2001,22 @@ static int match_fragment(struct image *img, * but in this loop we will only handle the part of the * preimage that falls within the file. */ - fixed_buf = xmalloc(preimage->len + 1); - buf = fixed_buf; + strbuf_init(&fixed, preimage->len + 1); orig = preimage->buf; target = img->buf + try; for (i = 0; i < preimage_limit; i++) { - size_t fixlen; /* length after fixing the preimage */ size_t oldlen = preimage->line[i].len; size_t tgtlen = img->line[try_lno + i].len; - size_t tgtfixlen; /* length after fixing the target line */ - char tgtfixbuf[1024], *tgtfix; + size_t fixstart = fixed.len; + struct strbuf tgtfix; int match; /* Try fixing the line in the preimage */ - fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL); + ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL); /* Try fixing the line in the target */ - if (sizeof(tgtfixbuf) > tgtlen) - tgtfix = tgtfixbuf; - else - tgtfix = xmalloc(tgtlen); - tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL); + strbuf_init(&tgtfix, tgtlen); + ws_fix_copy(&tgtfix, target, tgtlen, ws_rule, NULL); /* * If they match, either the preimage was based on @@ -2031,15 +2028,15 @@ static int match_fragment(struct image *img, * so we might as well take the fix together with their * real change. */ - match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen)); + match = (tgtfix.len == fixed.len - fixstart && + !memcmp(tgtfix.buf, fixed.buf + fixstart, + fixed.len - fixstart)); - if (tgtfix != tgtfixbuf) - free(tgtfix); + strbuf_release(&tgtfix); if (!match) goto unmatch_exit; orig += oldlen; - buf += fixlen; target += tgtlen; } @@ -2051,19 +2048,18 @@ static int match_fragment(struct image *img, * false). */ for ( ; i < preimage->nr; i++) { - size_t fixlen; /* length after fixing the preimage */ + size_t fixstart = fixed.len; /* start of the fixed preimage */ size_t oldlen = preimage->line[i].len; int j; /* Try fixing the line in the preimage */ - fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL); + ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL); - for (j = 0; j < fixlen; j++) - if (!isspace(buf[j])) + for (j = fixstart; j < fixed.len; j++) + if (!isspace(fixed.buf[j])) goto unmatch_exit; orig += oldlen; - buf += fixlen; } /* @@ -2071,12 +2067,13 @@ static int match_fragment(struct image *img, * has whitespace breakages unfixed, and fixing them makes the * hunk match. Update the context lines in the postimage. */ + fixed_buf = strbuf_detach(&fixed, &fixed_len); update_pre_post_images(preimage, postimage, - fixed_buf, buf - fixed_buf, 0); + fixed_buf, fixed_len, 0); return 1; unmatch_exit: - free(fixed_buf); + strbuf_release(&fixed); return 0; } @@ -2244,7 +2241,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, int match_beginning, match_end; const char *patch = frag->patch; int size = frag->size; - char *old, *new, *oldlines, *newlines; + char *old, *oldlines; + struct strbuf newlines; int new_blank_lines_at_end = 0; unsigned long leading, trailing; int pos, applied_pos; @@ -2254,16 +2252,16 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, memset(&preimage, 0, sizeof(preimage)); memset(&postimage, 0, sizeof(postimage)); oldlines = xmalloc(size); - newlines = xmalloc(size); + strbuf_init(&newlines, size); old = oldlines; - new = newlines; while (size > 0) { char first; int len = linelen(patch, size); - int plen, added; + int plen; int added_blank_line = 0; int is_blank_context = 0; + size_t start; if (!len) break; @@ -2293,7 +2291,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, /* ... followed by '\No newline'; nothing */ break; *old++ = '\n'; - *new++ = '\n'; + strbuf_addch(&newlines, '\n'); add_line_info(&preimage, "\n", 1, LINE_COMMON); add_line_info(&postimage, "\n", 1, LINE_COMMON); is_blank_context = 1; @@ -2315,18 +2313,17 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, if (first == '+' && no_add) break; + start = newlines.len; if (first != '+' || !whitespace_error || ws_error_action != correct_ws_error) { - memcpy(new, patch + 1, plen); - added = plen; + strbuf_add(&newlines, patch + 1, plen); } else { - added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws); + ws_fix_copy(&newlines, patch + 1, plen, ws_rule, &applied_after_fixing_ws); } - add_line_info(&postimage, new, added, + add_line_info(&postimage, newlines.buf + start, newlines.len - start, (first == '+' ? 0 : LINE_COMMON)); - new += added; if (first == '+' && (ws_rule & WS_BLANK_AT_EOF) && ws_blank_line(patch + 1, plen, ws_rule)) @@ -2351,9 +2348,9 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } if (inaccurate_eof && old > oldlines && old[-1] == '\n' && - new > newlines && new[-1] == '\n') { + newlines.len > 0 && newlines.buf[newlines.len - 1] == '\n') { old--; - new--; + strbuf_setlen(&newlines, newlines.len - 1); } leading = frag->leading; @@ -2385,8 +2382,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, pos = frag->newpos ? (frag->newpos - 1) : 0; preimage.buf = oldlines; preimage.len = old - oldlines; - postimage.buf = newlines; - postimage.len = new - newlines; + postimage.buf = newlines.buf; + postimage.len = newlines.len; preimage.line = preimage.line_allocated; postimage.line = postimage.line_allocated; @@ -2462,7 +2459,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } free(oldlines); - free(newlines); + strbuf_release(&newlines); free(preimage.line_allocated); free(postimage.line_allocated); @@ -3141,11 +3138,7 @@ static void remove_file(struct patch *patch, int rmdir_empty) die("unable to remove %s from index", patch->old_name); } if (!cached) { - if (S_ISGITLINK(patch->old_mode)) { - if (rmdir(patch->old_name)) - warning("unable to remove submodule %s", - patch->old_name); - } else if (!unlink_or_warn(patch->old_name) && rmdir_empty) { + if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) { remove_path(patch->old_name); } } diff --git a/builtin/checkout.c b/builtin/checkout.c index 88b1f43e0..c3825219c 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -33,6 +33,7 @@ struct checkout_opts { int writeout_error; const char *new_branch; + const char *new_orphan_branch; int new_branch_log; enum branch_track track; }; @@ -492,8 +493,9 @@ static void update_refs_for_switch(struct checkout_opts *opts, struct strbuf msg = STRBUF_INIT; const char *old_desc; if (opts->new_branch) { - create_branch(old->name, opts->new_branch, new->name, 0, - opts->new_branch_log, opts->track); + if (!opts->new_orphan_branch) + create_branch(old->name, opts->new_branch, new->name, 0, + opts->new_branch_log, opts->track); new->name = opts->new_branch; setup_branch_path(new); } @@ -633,6 +635,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), OPT_SET_INT('t', "track", &opts.track, "track", BRANCH_TRACK_EXPLICIT), + OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"), OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage", 2), OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage", @@ -678,6 +681,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.new_branch = argv0 + 1; } + if (opts.new_orphan_branch) { + if (opts.new_branch) + die("--orphan and -b are mutually exclusive"); + if (opts.track > 0 || opts.new_branch_log) + die("--orphan cannot be used with -t or -l"); + opts.new_branch = opts.new_orphan_branch; + } + if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); diff --git a/builtin/clone.c b/builtin/clone.c index 05f8fb477..445792242 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -302,6 +302,8 @@ static const struct ref *clone_local(const char *src_repo, transport = transport_get(remote, src_repo); ret = transport_get_remote_refs(transport); transport_disconnect(transport); + if (0 <= option_verbosity) + printf("done.\n"); return ret; } @@ -461,7 +463,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die("could not create leading directories of '%s'", git_dir); set_git_dir(make_absolute_path(git_dir)); - init_db(option_template, (option_verbosity < 0) ? INIT_DB_QUIET : 0); + if (0 <= option_verbosity) + printf("Cloning into %s...\n", get_git_dir()); + init_db(option_template, INIT_DB_QUIET); /* * At this point, the config exists, so we do not need the @@ -470,9 +474,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) */ unsetenv(CONFIG_ENVIRONMENT); - if (option_reference) - setup_reference(git_dir); - git_config(git_default_config, NULL); if (option_bare) { @@ -498,12 +499,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set(key.buf, "true"); strbuf_reset(&key); } - - strbuf_addf(&key, "remote.%s.url", option_origin); - git_config_set(key.buf, repo); - strbuf_reset(&key); } + strbuf_addf(&key, "remote.%s.url", option_origin); + git_config_set(key.buf, repo); + strbuf_reset(&key); + + if (option_reference) + setup_reference(git_dir); + fetch_pattern = value.buf; refspec = parse_fetch_refspec(1, &fetch_pattern); @@ -513,7 +517,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) refs = clone_local(path, git_dir); mapped_refs = wanted_peer_refs(refs, refspec); } else { - struct remote *remote = remote_get(argv[0]); + struct remote *remote = remote_get(option_origin); transport = transport_get(remote, remote->url[0]); if (!transport->get_refs_list || !transport->fetch) diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 90dac349a..87f0591c2 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -9,19 +9,6 @@ #include "builtin.h" #include "utf8.h" -/* - * FIXME! Share the code with "write-tree.c" - */ -static void check_valid(unsigned char *sha1, enum object_type expect) -{ - enum object_type type = sha1_object_info(sha1, NULL); - if (type < 0) - die("%s is not a valid object", sha1_to_hex(sha1)); - if (type != expect) - die("%s is not a valid '%s' object", sha1_to_hex(sha1), - typename(expect)); -} - static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog"; static void new_parent(struct commit *parent, struct commit_list **parents_p) @@ -38,61 +25,6 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p) commit_list_insert(parent, parents_p); } -static const char commit_utf8_warn[] = -"Warning: commit message does not conform to UTF-8.\n" -"You may want to amend it after fixing the message, or set the config\n" -"variable i18n.commitencoding to the encoding your project uses.\n"; - -int commit_tree(const char *msg, unsigned char *tree, - struct commit_list *parents, unsigned char *ret, - const char *author) -{ - int result; - int encoding_is_utf8; - struct strbuf buffer; - - check_valid(tree, OBJ_TREE); - - /* Not having i18n.commitencoding is the same as having utf-8 */ - encoding_is_utf8 = is_encoding_utf8(git_commit_encoding); - - strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */ - strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree)); - - /* - * NOTE! This ordering means that the same exact tree merged with a - * different order of parents will be a _different_ changeset even - * if everything else stays the same. - */ - while (parents) { - struct commit_list *next = parents->next; - strbuf_addf(&buffer, "parent %s\n", - sha1_to_hex(parents->item->object.sha1)); - free(parents); - parents = next; - } - - /* Person/date information */ - if (!author) - author = git_author_info(IDENT_ERROR_ON_NO_NAME); - strbuf_addf(&buffer, "author %s\n", author); - strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME)); - if (!encoding_is_utf8) - strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding); - strbuf_addch(&buffer, '\n'); - - /* And add the comment */ - strbuf_addstr(&buffer, msg); - - /* And check the encoding */ - if (encoding_is_utf8 && !is_utf8(buffer.buf)) - fprintf(stderr, commit_utf8_warn); - - result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret); - strbuf_release(&buffer); - return result; -} - int cmd_commit_tree(int argc, const char **argv, const char *prefix) { int i; @@ -117,7 +49,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) if (get_sha1(b, sha1)) die("Not a valid object name %s", b); - check_valid(sha1, OBJ_COMMIT); + assert_sha1_type(sha1, OBJ_COMMIT); new_parent(lookup_commit(sha1), &parents); } diff --git a/builtin/commit.c b/builtin/commit.c index c5ab683d5..ddf77e48e 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -66,7 +66,7 @@ static char *edit_message, *use_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; -static int no_post_rewrite; +static int no_post_rewrite, allow_empty_message; static char *untracked_files_arg, *force_date; /* * The default commit message cleanup mode will remove the lines @@ -83,6 +83,7 @@ static enum { static char *cleanup_arg; static int use_editor = 1, initial_commit, in_merge, include_status = 1; +static int show_ignored_in_status; static const char *only_include_assumed; static struct strbuf message; @@ -140,9 +141,15 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), /* end commit contents options */ + { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL, + "ok to record an empty change", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL, + "ok to record a change with an empty message", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + OPT_END() }; @@ -1017,6 +1024,7 @@ static int git_status_config(const char *k, const char *v, void *cb) int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; + int fd; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), @@ -1031,6 +1039,8 @@ int cmd_status(int argc, const char **argv, const char *prefix) "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_BOOLEAN(0, "ignored", &show_ignored_in_status, + "show ignored files"), OPT_END(), }; @@ -1044,12 +1054,21 @@ int cmd_status(int argc, const char **argv, const char *prefix) builtin_status_options, builtin_status_usage, 0); handle_untracked_files_arg(&s); - + if (show_ignored_in_status) + s.show_ignored_files = 1; if (*argv) s.pathspec = get_pathspec(prefix, argv); read_cache_preload(s.pathspec); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); + + fd = hold_locked_index(&index_lock, 0); + if (0 <= fd) { + if (!write_cache(fd, active_cache, active_nr)) + commit_locked_index(&index_lock); + rollback_lock_file(&index_lock); + } + s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; s.in_merge = in_merge; wt_status_collect(&s); @@ -1293,7 +1312,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (cleanup_mode != CLEANUP_NONE) stripspace(&sb, cleanup_mode == CLEANUP_ALL); - if (message_is_empty(&sb)) { + if (message_is_empty(&sb) && !allow_empty_message) { rollback_index_files(); fprintf(stderr, "Aborting commit due to empty commit message.\n"); exit(1); diff --git a/builtin/config.c b/builtin/config.c index 4bc46b15f..f3d1660d0 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -197,7 +197,11 @@ static int get_value(const char *key_, const char *regex_) git_config_from_file(show_config, system_wide, NULL); if (do_all && global) git_config_from_file(show_config, global, NULL); - git_config_from_file(show_config, local, NULL); + if (do_all) + git_config_from_file(show_config, local, NULL); + git_config_from_parameters(show_config, NULL); + if (!do_all && !seen) + git_config_from_file(show_config, local, NULL); if (!do_all && !seen && global) git_config_from_file(show_config, global, NULL); if (!do_all && !seen && system_wide) diff --git a/builtin/describe.c b/builtin/describe.c index 71be2a936..43caff2ff 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -35,7 +35,8 @@ static const char *diff_index_args[] = { struct commit_name { struct tag *tag; - int prio; /* annotated tag = 2, tag = 1, head = 0 */ + unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ + unsigned name_checked:1; unsigned char sha1[20]; char path[FLEX_ARRAY]; /* more */ }; @@ -43,18 +44,53 @@ static const char *prio_names[] = { "head", "lightweight", "annotated", }; +static int replace_name(struct commit_name *e, + int prio, + const unsigned char *sha1, + struct tag **tag) +{ + if (!e || e->prio < prio) + return 1; + + if (e->prio == 2 && prio == 2) { + /* Multiple annotated tags point to the same commit. + * Select one to keep based upon their tagger date. + */ + struct tag *t; + + if (!e->tag) { + t = lookup_tag(e->sha1); + if (!t || parse_tag(t)) + return 1; + e->tag = t; + } + + t = lookup_tag(sha1); + if (!t || parse_tag(t)) + return 0; + *tag = t; + + if (e->tag->date < t->date) + return 1; + } + + return 0; +} + static void add_to_known_names(const char *path, struct commit *commit, int prio, const unsigned char *sha1) { struct commit_name *e = commit->util; - if (!e || e->prio < prio) { + struct tag *tag = NULL; + if (replace_name(e, prio, sha1, &tag)) { size_t len = strlen(path)+1; free(e); e = xmalloc(sizeof(struct commit_name) + len); - e->tag = NULL; + e->tag = tag; e->prio = prio; + e->name_checked = 0; hashcpy(e->sha1, sha1); memcpy(e->path, path, len); commit->util = e; @@ -165,10 +201,15 @@ static void display_name(struct commit_name *n) { if (n->prio == 2 && !n->tag) { n->tag = lookup_tag(n->sha1); - if (!n->tag || parse_tag(n->tag) || !n->tag->tag) + if (!n->tag || parse_tag(n->tag)) die("annotated tag %s not available", n->path); + } + if (n->tag && !n->name_checked) { + if (!n->tag->tag) + die("annotated tag %s has no embedded name", n->path); if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) warning("tag '%s' is really '%s' here", n->tag->tag, n->path); + n->name_checked = 1; } if (n->tag) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 62be1bbfd..3a9795317 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -227,6 +227,9 @@ static void grab_common_values(struct atom_value *val, int deref, struct object strcpy(s, sha1_to_hex(obj->sha1)); v->s = s; } + else if (!strcmp(name, "objectname:short")) { + v->s = find_unique_abbrev(obj->sha1, DEFAULT_ABBREV); + } } } diff --git a/builtin/grep.c b/builtin/grep.c index 8e928e217..b194ea3ce 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -17,8 +17,8 @@ #include "dir.h" #ifndef NO_PTHREADS -#include "thread-utils.h" #include <pthread.h> +#include "thread-utils.h" #endif static char const * const grep_usage[] = { diff --git a/builtin/index-pack.c b/builtin/index-pack.c index b4cf8c53e..a89ae831d 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -11,7 +11,7 @@ #include "exec_cmd.h" static const char index_pack_usage[] = -"git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }"; +"git index-pack [-v] [-o <index-file>] [{ --keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }"; struct object_entry { @@ -266,26 +266,23 @@ static void unlink_base_data(struct base_data *c) static void *unpack_entry_data(unsigned long offset, unsigned long size) { + int status; z_stream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); + git_inflate_init(&stream); stream.next_out = buf; stream.avail_out = size; - stream.next_in = fill(1); - stream.avail_in = input_len; - git_inflate_init(&stream); - for (;;) { - int ret = git_inflate(&stream, 0); - use(input_len - stream.avail_in); - if (stream.total_out == size && ret == Z_STREAM_END) - break; - if (ret != Z_OK) - bad_object(offset, "inflate returned %d", ret); + do { stream.next_in = fill(1); stream.avail_in = input_len; - } + status = git_inflate(&stream, 0); + use(input_len - stream.avail_in); + } while (status == Z_OK); + if (stream.total_out != size || status != Z_STREAM_END) + bad_object(offset, "inflate returned %d", status); git_inflate_end(&stream); return buf; } @@ -359,34 +356,38 @@ static void *get_data_from_pack(struct object_entry *obj) { off_t from = obj[0].idx.offset + obj[0].hdr_size; unsigned long len = obj[1].idx.offset - from; - unsigned long rdy = 0; - unsigned char *src, *data; + unsigned char *data, *inbuf; z_stream stream; - int st; + int status; - src = xmalloc(len); - data = src; - do { - ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy); - if (n < 0) - die_errno("cannot pread pack file"); - if (!n) - die("premature end of pack file, %lu bytes missing", - len - rdy); - rdy += n; - } while (rdy < len); data = xmalloc(obj->size); + inbuf = xmalloc((len < 64*1024) ? len : 64*1024); + memset(&stream, 0, sizeof(stream)); + git_inflate_init(&stream); stream.next_out = data; stream.avail_out = obj->size; - stream.next_in = src; - stream.avail_in = len; - git_inflate_init(&stream); - while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK); - git_inflate_end(&stream); - if (st != Z_STREAM_END || stream.total_out != obj->size) + + do { + ssize_t n = (len < 64*1024) ? len : 64*1024; + n = pread(pack_fd, inbuf, n, from); + if (n < 0) + die_errno("cannot pread pack file"); + if (!n) + die("premature end of pack file, %lu bytes missing", len); + from += n; + len -= n; + stream.next_in = inbuf; + stream.avail_in = n; + status = git_inflate(&stream, 0); + } while (len && status == Z_OK && !stream.avail_in); + + /* This has been inflated OK when first encountered, so... */ + if (status != Z_STREAM_END || stream.total_out != obj->size) die("serious inflate inconsistency"); - free(src); + + git_inflate_end(&stream); + free(inbuf); return data; } @@ -668,25 +669,25 @@ static void parse_pack_objects(unsigned char *sha1) static int write_compressed(struct sha1file *f, void *in, unsigned int size) { z_stream stream; - unsigned long maxsize; - void *out; + int status; + unsigned char outbuf[4096]; memset(&stream, 0, sizeof(stream)); deflateInit(&stream, zlib_compression_level); - maxsize = deflateBound(&stream, size); - out = xmalloc(maxsize); - - /* Compress it */ stream.next_in = in; stream.avail_in = size; - stream.next_out = out; - stream.avail_out = maxsize; - while (deflate(&stream, Z_FINISH) == Z_OK); - deflateEnd(&stream); + do { + stream.next_out = outbuf; + stream.avail_out = sizeof(outbuf); + status = deflate(&stream, Z_FINISH); + sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out); + } while (status == Z_OK); + + if (status != Z_STREAM_END) + die("unable to deflate appended object (%d)", status); size = stream.total_out; - sha1write(f, out, size); - free(out); + deflateEnd(&stream); return size; } diff --git a/builtin/init-db.c b/builtin/init-db.c index edc40ff57..0271285fa 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -463,7 +463,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) static char git_dir[PATH_MAX+1]; setenv(GIT_DIR_ENVIRONMENT, - getcwd(git_dir, sizeof(git_dir)), 0); + getcwd(git_dir, sizeof(git_dir)), argc > 0); } if (init_shared_repository != -1) diff --git a/builtin/log.c b/builtin/log.c index 6208703c0..976e16f9f 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -24,6 +24,7 @@ static const char *default_date_mode = NULL; static int default_show_root = 1; +static int decoration_style; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; @@ -31,11 +32,28 @@ static const char * const builtin_log_usage = "git log [<options>] [<since>..<until>] [[--] <path>...]\n" " or: git show [options] <object>..."; +static int parse_decoration_style(const char *var, const char *value) +{ + switch (git_config_maybe_bool(var, value)) { + case 1: + return DECORATE_SHORT_REFS; + case 0: + return 0; + default: + break; + } + if (!strcmp(value, "full")) + return DECORATE_FULL_REFS; + else if (!strcmp(value, "short")) + return DECORATE_SHORT_REFS; + return -1; +} + static void cmd_log_init(int argc, const char **argv, const char *prefix, struct rev_info *rev, struct setup_revision_opt *opt) { int i; - int decoration_style = 0; + int decoration_given = 0; struct userformat_want w; rev->abbrev = DEFAULT_ABBREV; @@ -78,14 +96,15 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, const char *arg = argv[i]; if (!strcmp(arg, "--decorate")) { decoration_style = DECORATE_SHORT_REFS; + decoration_given = 1; } else if (!prefixcmp(arg, "--decorate=")) { const char *v = skip_prefix(arg, "--decorate="); - if (!strcmp(v, "full")) - decoration_style = DECORATE_FULL_REFS; - else if (!strcmp(v, "short")) - decoration_style = DECORATE_SHORT_REFS; - else + decoration_style = parse_decoration_style(arg, v); + if (decoration_style < 0) die("invalid --decorate option: %s", arg); + decoration_given = 1; + } else if (!strcmp(arg, "--no-decorate")) { + decoration_style = 0; } else if (!strcmp(arg, "--source")) { rev->show_source = 1; } else if (!strcmp(arg, "-h")) { @@ -93,6 +112,15 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, } else die("unrecognized argument: %s", arg); } + + /* + * defeat log.decorate configuration interacting with --pretty=raw + * from the command line. + */ + if (!decoration_given && rev->pretty_given + && rev->commit_format == CMIT_FMT_RAW) + decoration_style = 0; + if (decoration_style) { rev->show_decorations = 1; load_ref_decorations(decoration_style); @@ -258,6 +286,12 @@ static int git_log_config(const char *var, const char *value, void *cb) return git_config_string(&fmt_patch_subject_prefix, var, value); if (!strcmp(var, "log.date")) return git_config_string(&default_date_mode, var, value); + if (!strcmp(var, "log.decorate")) { + decoration_style = parse_decoration_style(var, value); + if (decoration_style < 0) + decoration_style = 0; /* maybe warn? */ + return 0; + } if (!strcmp(var, "log.showroot")) { default_show_root = git_config_bool(var, value); return 0; diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 70f5622d9..8ee91eb54 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -4,7 +4,8 @@ #include "remote.h" static const char ls_remote_usage[] = -"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>] <repository> <refs>..."; +"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n" +" [<repository> [<refs>...]]"; /* * Is there one among the list of patterns that match the tail part @@ -73,9 +74,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) break; } - if (!dest) - usage(ls_remote_usage); - if (argv[i]) { int j; pattern = xcalloc(sizeof(const char *), argc - i + 1); @@ -87,6 +85,11 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } } remote = remote_get(dest); + if (!remote) { + if (dest) + die("bad repository '%s'", dest); + die("No remote configured to list refs from."); + } if (!remote->url_nr) die("remote %s has no configured URL", dest); transport = transport_get(remote, NULL); diff --git a/builtin/merge.c b/builtin/merge.c index c04306684..37d414b3b 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -548,13 +548,53 @@ static void write_tree_trivial(unsigned char *sha1) die("git write-tree failed to write a tree"); } -static int try_merge_strategy(const char *strategy, struct commit_list *common, - const char *head_arg) +int try_merge_command(const char *strategy, struct commit_list *common, + const char *head_arg, struct commit_list *remotes) { const char **args; int i = 0, x = 0, ret; struct commit_list *j; struct strbuf buf = STRBUF_INIT; + + args = xmalloc((4 + xopts_nr + commit_list_count(common) + + commit_list_count(remotes)) * sizeof(char *)); + strbuf_addf(&buf, "merge-%s", strategy); + args[i++] = buf.buf; + for (x = 0; x < xopts_nr; x++) { + char *s = xmalloc(strlen(xopts[x])+2+1); + strcpy(s, "--"); + strcpy(s+2, xopts[x]); + args[i++] = s; + } + for (j = common; j; j = j->next) + args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i++] = "--"; + args[i++] = head_arg; + for (j = remotes; j; j = j->next) + args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i] = NULL; + ret = run_command_v_opt(args, RUN_GIT_CMD); + strbuf_release(&buf); + i = 1; + for (x = 0; x < xopts_nr; x++) + free((void *)args[i++]); + for (j = common; j; j = j->next) + free((void *)args[i++]); + i += 2; + for (j = remotes; j; j = j->next) + free((void *)args[i++]); + free(args); + discard_cache(); + if (read_cache() < 0) + die("failed to read the cache"); + resolve_undo_clear(); + + return ret; +} + +static int try_merge_strategy(const char *strategy, struct commit_list *common, + const char *head_arg) +{ int index_fd; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); @@ -567,12 +607,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, rollback_lock_file(lock); if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { - int clean; + int clean, x; struct commit *result; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int index_fd; struct commit_list *reversed = NULL; struct merge_options o; + struct commit_list *j; if (remoteheads->next) { error("Not handling anything other than two heads merge."); @@ -612,39 +653,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, rollback_lock_file(lock); return clean ? 0 : 1; } else { - args = xmalloc((4 + xopts_nr + commit_list_count(common) + - commit_list_count(remoteheads)) * sizeof(char *)); - strbuf_addf(&buf, "merge-%s", strategy); - args[i++] = buf.buf; - for (x = 0; x < xopts_nr; x++) { - char *s = xmalloc(strlen(xopts[x])+2+1); - strcpy(s, "--"); - strcpy(s+2, xopts[x]); - args[i++] = s; - } - for (j = common; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i++] = "--"; - args[i++] = head_arg; - for (j = remoteheads; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i] = NULL; - ret = run_command_v_opt(args, RUN_GIT_CMD); - strbuf_release(&buf); - i = 1; - for (x = 0; x < xopts_nr; x++) - free((void *)args[i++]); - for (j = common; j; j = j->next) - free((void *)args[i++]); - i += 2; - for (j = remoteheads; j; j = j->next) - free((void *)args[i++]); - free(args); - discard_cache(); - if (read_cache() < 0) - die("failed to read the cache"); - resolve_undo_clear(); - return ret; + return try_merge_command(strategy, common, head_arg, remoteheads); } } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 97802585e..214d7ef2b 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -18,8 +18,8 @@ #include "refs.h" #ifndef NO_PTHREADS -#include "thread-utils.h" #include <pthread.h> +#include "thread-utils.h" #endif static const char pack_usage[] = @@ -1522,6 +1522,13 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, #ifndef NO_PTHREADS +static void try_to_free_from_threads(size_t size) +{ + read_lock(); + release_pack_memory(size, -1); + read_unlock(); +} + /* * The main thread waits on the condition that (at least) one of the workers * has stopped working (which is indicated in the .working member of @@ -1552,14 +1559,16 @@ static pthread_cond_t progress_cond; */ static void init_threaded_search(void) { - pthread_mutex_init(&read_mutex, NULL); + init_recursive_mutex(&read_mutex); pthread_mutex_init(&cache_mutex, NULL); pthread_mutex_init(&progress_mutex, NULL); pthread_cond_init(&progress_cond, NULL); + set_try_to_free_routine(try_to_free_from_threads); } static void cleanup_threaded_search(void) { + set_try_to_free_routine(NULL); pthread_cond_destroy(&progress_cond); pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&cache_mutex); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 0559fcc87..bb34757d2 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -9,6 +9,7 @@ #include "object.h" #include "remote.h" #include "transport.h" +#include "string-list.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -129,13 +130,12 @@ static void write_head_info(void) struct command { struct command *next; const char *error_string; + unsigned int skip_update; unsigned char old_sha1[20]; unsigned char new_sha1[20]; char ref_name[FLEX_ARRAY]; /* more */ }; -static struct command *commands; - static const char pre_receive_hook[] = "hooks/pre-receive"; static const char post_receive_hook[] = "hooks/post-receive"; @@ -188,7 +188,7 @@ static int copy_to_sideband(int in, int out, void *arg) return 0; } -static int run_receive_hook(const char *hook_name) +static int run_receive_hook(struct command *commands, const char *hook_name) { static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; struct command *cmd; @@ -447,15 +447,15 @@ static const char *update(struct command *cmd) static char update_post_hook[] = "hooks/post-update"; -static void run_update_post_hook(struct command *cmd) +static void run_update_post_hook(struct command *commands) { - struct command *cmd_p; + struct command *cmd; int argc; const char **argv; struct child_process proc; - for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) { - if (cmd_p->error_string) + for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { + if (cmd->error_string) continue; argc++; } @@ -464,12 +464,12 @@ static void run_update_post_hook(struct command *cmd) argv = xmalloc(sizeof(*argv) * (2 + argc)); argv[0] = update_post_hook; - for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) { + for (argc = 1, cmd = commands; cmd; cmd = cmd->next) { char *p; - if (cmd_p->error_string) + if (cmd->error_string) continue; - p = xmalloc(strlen(cmd_p->ref_name) + 1); - strcpy(p, cmd_p->ref_name); + p = xmalloc(strlen(cmd->ref_name) + 1); + strcpy(p, cmd->ref_name); argv[argc] = p; argc++; } @@ -488,37 +488,92 @@ static void run_update_post_hook(struct command *cmd) } } -static void execute_commands(const char *unpacker_error) +static void check_aliased_update(struct command *cmd, struct string_list *list) +{ + struct string_list_item *item; + struct command *dst_cmd; + unsigned char sha1[20]; + char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41]; + int flag; + + const char *dst_name = resolve_ref(cmd->ref_name, sha1, 0, &flag); + + if (!(flag & REF_ISSYMREF)) + return; + + if ((item = string_list_lookup(dst_name, list)) == NULL) + return; + + cmd->skip_update = 1; + + dst_cmd = (struct command *) item->util; + + if (!hashcmp(cmd->old_sha1, dst_cmd->old_sha1) && + !hashcmp(cmd->new_sha1, dst_cmd->new_sha1)) + return; + + dst_cmd->skip_update = 1; + + strcpy(cmd_oldh, find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV)); + strcat(cmd_newh, find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV)); + strcpy(dst_oldh, find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV)); + strcat(dst_newh, find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV)); + rp_error("refusing inconsistent update between symref '%s' (%s..%s) and" + " its target '%s' (%s..%s)", + cmd->ref_name, cmd_oldh, cmd_newh, + dst_cmd->ref_name, dst_oldh, dst_newh); + + cmd->error_string = dst_cmd->error_string = + "inconsistent aliased update"; +} + +static void check_aliased_updates(struct command *commands) +{ + struct command *cmd; + struct string_list ref_list = { NULL, 0, 0, 0 }; + + for (cmd = commands; cmd; cmd = cmd->next) { + struct string_list_item *item = + string_list_append(cmd->ref_name, &ref_list); + item->util = (void *)cmd; + } + sort_string_list(&ref_list); + + for (cmd = commands; cmd; cmd = cmd->next) + check_aliased_update(cmd, &ref_list); + + string_list_clear(&ref_list, 0); +} + +static void execute_commands(struct command *commands, const char *unpacker_error) { - struct command *cmd = commands; + struct command *cmd; unsigned char sha1[20]; if (unpacker_error) { - while (cmd) { + for (cmd = commands; cmd; cmd = cmd->next) cmd->error_string = "n/a (unpacker error)"; - cmd = cmd->next; - } return; } - if (run_receive_hook(pre_receive_hook)) { - while (cmd) { + if (run_receive_hook(commands, pre_receive_hook)) { + for (cmd = commands; cmd; cmd = cmd->next) cmd->error_string = "pre-receive hook declined"; - cmd = cmd->next; - } return; } + check_aliased_updates(commands); + head_name = resolve_ref("HEAD", sha1, 0, NULL); - while (cmd) { - cmd->error_string = update(cmd); - cmd = cmd->next; - } + for (cmd = commands; cmd; cmd = cmd->next) + if (!cmd->skip_update) + cmd->error_string = update(cmd); } -static void read_head_info(void) +static struct command *read_head_info(void) { + struct command *commands = NULL; struct command **p = &commands; for (;;) { static char line[1000]; @@ -548,15 +603,14 @@ static void read_head_info(void) if (strstr(refname + reflen + 1, "side-band-64k")) use_sideband = LARGE_PACKET_MAX; } - cmd = xmalloc(sizeof(struct command) + len - 80); + cmd = xcalloc(1, sizeof(struct command) + len - 80); hashcpy(cmd->old_sha1, old_sha1); hashcpy(cmd->new_sha1, new_sha1); memcpy(cmd->ref_name, line + 82, len - 81); - cmd->error_string = NULL; - cmd->next = NULL; *p = cmd; p = &cmd->next; } + return commands; } static const char *parse_pack_header(struct pack_header *hdr) @@ -643,7 +697,7 @@ static const char *unpack(void) } } -static void report(const char *unpack_status) +static void report(struct command *commands, const char *unpack_status) { struct command *cmd; struct strbuf buf = STRBUF_INIT; @@ -667,12 +721,12 @@ static void report(const char *unpack_status) strbuf_release(&buf); } -static int delete_only(struct command *cmd) +static int delete_only(struct command *commands) { - while (cmd) { + struct command *cmd; + for (cmd = commands; cmd; cmd = cmd->next) { if (!is_null_sha1(cmd->new_sha1)) return 0; - cmd = cmd->next; } return 1; } @@ -722,6 +776,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) int stateless_rpc = 0; int i; char *dir = NULL; + struct command *commands; argv++; for (i = 1; i < argc; i++) { @@ -772,18 +827,17 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) if (advertise_refs) return 0; - read_head_info(); - if (commands) { + if ((commands = read_head_info()) != NULL) { const char *unpack_status = NULL; if (!delete_only(commands)) unpack_status = unpack(); - execute_commands(unpack_status); + execute_commands(commands, unpack_status); if (pack_lockfile) unlink_or_warn(pack_lockfile); if (report_status) - report(unpack_status); - run_receive_hook(post_receive_hook); + report(commands, unpack_status); + run_receive_hook(commands, post_receive_hook); run_update_post_hook(commands); if (auto_gc) { const char *argv_gc_auto[] = { diff --git a/builtin/reflog.c b/builtin/reflog.c index 64e45bd81..ebf610e64 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -13,7 +13,7 @@ */ static const char reflog_expire_usage[] = -"git reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; +"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; static const char reflog_delete_usage[] = "git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>..."; @@ -34,8 +34,13 @@ struct cmd_reflog_expire_cb { struct expire_reflog_cb { FILE *newlog; - const char *ref; - struct commit *ref_commit; + enum { + UE_NORMAL, + UE_ALWAYS, + UE_HEAD + } unreachable_expire_kind; + struct commit_list *mark_list; + unsigned long mark_limit; struct cmd_reflog_expire_cb *cmd; unsigned char last_kept_sha1[20]; }; @@ -210,46 +215,23 @@ static int keep_entry(struct commit **it, unsigned char *sha1) return 1; } -static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1) +/* + * Starting from commits in the cb->mark_list, mark commits that are + * reachable from them. Stop the traversal at commits older than + * the expire_limit and queue them back, so that the caller can call + * us again to restart the traversal with longer expire_limit. + */ +static void mark_reachable(struct expire_reflog_cb *cb) { - /* - * We may or may not have the commit yet - if not, look it - * up using the supplied sha1. - */ - if (!commit) { - if (is_null_sha1(sha1)) - return 0; - - commit = lookup_commit_reference_gently(sha1, 1); - - /* Not a commit -- keep it */ - if (!commit) - return 0; - } - - /* Reachable from the current ref? Don't prune. */ - if (commit->object.flags & REACHABLE) - return 0; - if (in_merge_bases(commit, &cb->ref_commit, 1)) - return 0; - - /* We can't reach it - prune it. */ - return 1; -} + struct commit *commit; + struct commit_list *pending; + unsigned long expire_limit = cb->mark_limit; + struct commit_list *leftover = NULL; -static void mark_reachable(struct commit *commit, unsigned long expire_limit) -{ - /* - * We need to compute whether the commit on either side of a reflog - * entry is reachable from the tip of the ref for all entries. - * Mark commits that are reachable from the tip down to the - * time threshold first; we know a commit marked thusly is - * reachable from the tip without running in_merge_bases() - * at all. - */ - struct commit_list *pending = NULL; + for (pending = cb->mark_list; pending; pending = pending->next) + pending->item->object.flags &= ~REACHABLE; - commit_list_insert(commit, &pending); + pending = cb->mark_list; while (pending) { struct commit_list *entry = pending; struct commit_list *parent; @@ -261,8 +243,11 @@ static void mark_reachable(struct commit *commit, unsigned long expire_limit) if (parse_commit(commit)) continue; commit->object.flags |= REACHABLE; - if (commit->date < expire_limit) + if (commit->date < expire_limit) { + commit_list_insert(commit, &leftover); continue; + } + commit->object.flags |= REACHABLE; parent = commit->parents; while (parent) { commit = parent->item; @@ -272,6 +257,36 @@ static void mark_reachable(struct commit *commit, unsigned long expire_limit) commit_list_insert(commit, &pending); } } + cb->mark_list = leftover; +} + +static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1) +{ + /* + * We may or may not have the commit yet - if not, look it + * up using the supplied sha1. + */ + if (!commit) { + if (is_null_sha1(sha1)) + return 0; + + commit = lookup_commit_reference_gently(sha1, 1); + + /* Not a commit -- keep it */ + if (!commit) + return 0; + } + + /* Reachable from the current ref? Don't prune. */ + if (commit->object.flags & REACHABLE) + return 0; + + if (cb->mark_list && cb->mark_limit) { + cb->mark_limit = 0; /* dig down to the root */ + mark_reachable(cb); + } + + return !(commit->object.flags & REACHABLE); } static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, @@ -293,7 +308,7 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, goto prune; if (timestamp < cb->cmd->expire_unreachable) { - if (!cb->ref_commit) + if (cb->unreachable_expire_kind == UE_ALWAYS) goto prune; if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1)) goto prune; @@ -320,12 +335,27 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, return 0; } +static int push_tip_to_list(const char *refname, const unsigned char *sha1, int flags, void *cb_data) +{ + struct commit_list **list = cb_data; + struct commit *tip_commit; + if (flags & REF_ISSYMREF) + return 0; + tip_commit = lookup_commit_reference_gently(sha1, 1); + if (!tip_commit) + return 0; + commit_list_insert(tip_commit, list); + return 0; +} + static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) { struct cmd_reflog_expire_cb *cmd = cb_data; struct expire_reflog_cb cb; struct ref_lock *lock; char *log_file, *newlog_path = NULL; + struct commit *tip_commit; + struct commit_list *tips; int status = 0; memset(&cb, 0, sizeof(cb)); @@ -345,14 +375,49 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, cb.newlog = fopen(newlog_path, "w"); } - cb.ref_commit = lookup_commit_reference_gently(sha1, 1); - cb.ref = ref; cb.cmd = cmd; - if (cb.ref_commit) - mark_reachable(cb.ref_commit, cmd->expire_total); + + if (!cmd->expire_unreachable || !strcmp(ref, "HEAD")) { + tip_commit = NULL; + cb.unreachable_expire_kind = UE_HEAD; + } else { + tip_commit = lookup_commit_reference_gently(sha1, 1); + if (!tip_commit) + cb.unreachable_expire_kind = UE_ALWAYS; + else + cb.unreachable_expire_kind = UE_NORMAL; + } + + if (cmd->expire_unreachable <= cmd->expire_total) + cb.unreachable_expire_kind = UE_ALWAYS; + + cb.mark_list = NULL; + tips = NULL; + if (cb.unreachable_expire_kind != UE_ALWAYS) { + if (cb.unreachable_expire_kind == UE_HEAD) { + struct commit_list *elem; + for_each_ref(push_tip_to_list, &tips); + for (elem = tips; elem; elem = elem->next) + commit_list_insert(elem->item, &cb.mark_list); + } else { + commit_list_insert(tip_commit, &cb.mark_list); + } + cb.mark_limit = cmd->expire_total; + mark_reachable(&cb); + } + for_each_reflog_ent(ref, expire_reflog_ent, &cb); - if (cb.ref_commit) - clear_commit_marks(cb.ref_commit, REACHABLE); + + if (cb.unreachable_expire_kind != UE_ALWAYS) { + if (cb.unreachable_expire_kind == UE_HEAD) { + struct commit_list *elem; + for (elem = tips; elem; elem = elem->next) + clear_commit_marks(tip_commit, REACHABLE); + free_commit_list(tips); + } else { + clear_commit_marks(tip_commit, REACHABLE); + } + } finish: if (cb.newlog) { if (fclose(cb.newlog)) { diff --git a/builtin/revert.c b/builtin/revert.c index 778a56eb5..7976b5a32 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -43,6 +43,7 @@ static const char *commit_name; static int allow_rerere_auto; static const char *me; +static const char *strategy; #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -62,6 +63,7 @@ static void parse_args(int argc, const char **argv) OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_INTEGER('m', "mainline", &mainline, "parent number"), OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), + OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"), OPT_END(), OPT_END(), OPT_END(), @@ -109,8 +111,13 @@ static int get_message(const char *raw_message, struct commit_message *out) encoding = "UTF-8"; if (!git_commit_encoding) git_commit_encoding = "UTF-8"; - if ((out->reencoded_message = reencode_string(raw_message, - git_commit_encoding, encoding))) + + out->reencoded_message = NULL; + out->message = raw_message; + if (strcmp(encoding, git_commit_encoding)) + out->reencoded_message = reencode_string(raw_message, + git_commit_encoding, encoding); + if (out->reencoded_message) out->message = out->reencoded_message; abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); @@ -169,28 +176,17 @@ static char *get_encoding(const char *message) return NULL; } -static struct lock_file msg_file; -static int msg_fd; - -static void add_to_msg(const char *string) -{ - int len = strlen(string); - if (write_in_full(msg_fd, string, len) < 0) - die_errno ("Could not write to MERGE_MSG"); -} - -static void add_message_to_msg(const char *message) +static void add_message_to_msg(struct strbuf *msgbuf, const char *message) { const char *p = message; while (*p && (*p != '\n' || p[1] != '\n')) p++; if (!*p) - add_to_msg(sha1_to_hex(commit->object.sha1)); + strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1)); p += 2; - add_to_msg(p); - return; + strbuf_addstr(msgbuf, p); } static void set_author_ident_env(const char *message) @@ -266,6 +262,19 @@ static char *help_msg(const char *name) return strbuf_detach(&helpbuf, NULL); } +static void write_message(struct strbuf *msgbuf, const char *filename) +{ + static struct lock_file msg_file; + + int msg_fd = hold_lock_file_for_update(&msg_file, filename, + LOCK_DIE_ON_ERROR); + if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) + die_errno("Could not write to %s.", filename); + strbuf_release(msgbuf); + if (commit_lock_file(&msg_file) < 0) + die("Error wrapping up %s", filename); +} + static struct tree *empty_tree(void) { struct tree *tree = xcalloc(1, sizeof(struct tree)); @@ -300,17 +309,70 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from) return write_ref_sha1(ref_lock, to, "cherry-pick"); } +static void do_recursive_merge(struct commit *base, struct commit *next, + const char *base_label, const char *next_label, + unsigned char *head, struct strbuf *msgbuf, + char *defmsg) +{ + struct merge_options o; + struct tree *result, *next_tree, *base_tree, *head_tree; + int clean, index_fd; + static struct lock_file index_lock; + + index_fd = hold_locked_index(&index_lock, 1); + + read_cache(); + init_merge_options(&o); + o.ancestor = base ? base_label : "(empty tree)"; + o.branch1 = "HEAD"; + o.branch2 = next ? next_label : "(empty tree)"; + + head_tree = parse_tree_indirect(head); + next_tree = next ? next->tree : empty_tree(); + base_tree = base ? base->tree : empty_tree(); + + clean = merge_trees(&o, + head_tree, + next_tree, base_tree, &result); + + if (active_cache_changed && + (write_cache(index_fd, active_cache, active_nr) || + commit_locked_index(&index_lock))) + die("%s: Unable to write new index file", me); + rollback_lock_file(&index_lock); + + if (!clean) { + int i; + strbuf_addstr(msgbuf, "\nConflicts:\n\n"); + for (i = 0; i < active_nr;) { + struct cache_entry *ce = active_cache[i++]; + if (ce_stage(ce)) { + strbuf_addch(msgbuf, '\t'); + strbuf_addstr(msgbuf, ce->name); + strbuf_addch(msgbuf, '\n'); + while (i < active_nr && !strcmp(ce->name, + active_cache[i]->name)) + i++; + } + } + write_message(msgbuf, defmsg); + fprintf(stderr, "Automatic %s failed.%s\n", + me, help_msg(commit_name)); + rerere(allow_rerere_auto); + exit(1); + } + write_message(msgbuf, defmsg); + fprintf(stderr, "Finished one %s.\n", me); +} + static int revert_or_cherry_pick(int argc, const char **argv) { unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; - int i, index_fd, clean; struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; - struct merge_options o; - struct tree *result, *next_tree, *base_tree, *head_tree; - static struct lock_file index_lock; + struct strbuf msgbuf = STRBUF_INIT; git_config(git_default_config, NULL); me = action == REVERT ? "revert" : "cherry-pick"; @@ -398,83 +460,57 @@ static int revert_or_cherry_pick(int argc, const char **argv) */ defmsg = git_pathdup("MERGE_MSG"); - msg_fd = hold_lock_file_for_update(&msg_file, defmsg, - LOCK_DIE_ON_ERROR); - - index_fd = hold_locked_index(&index_lock, 1); if (action == REVERT) { base = commit; base_label = msg.label; next = parent; next_label = msg.parent_label; - add_to_msg("Revert \""); - add_to_msg(msg.subject); - add_to_msg("\"\n\nThis reverts commit "); - add_to_msg(sha1_to_hex(commit->object.sha1)); + strbuf_addstr(&msgbuf, "Revert \""); + strbuf_addstr(&msgbuf, msg.subject); + strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit "); + strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); if (commit->parents->next) { - add_to_msg(", reversing\nchanges made to "); - add_to_msg(sha1_to_hex(parent->object.sha1)); + strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); + strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); } - add_to_msg(".\n"); + strbuf_addstr(&msgbuf, ".\n"); } else { base = parent; base_label = msg.parent_label; next = commit; next_label = msg.label; set_author_ident_env(msg.message); - add_message_to_msg(msg.message); + add_message_to_msg(&msgbuf, msg.message); if (no_replay) { - add_to_msg("(cherry picked from commit "); - add_to_msg(sha1_to_hex(commit->object.sha1)); - add_to_msg(")\n"); + strbuf_addstr(&msgbuf, "(cherry picked from commit "); + strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); + strbuf_addstr(&msgbuf, ")\n"); } } - read_cache(); - init_merge_options(&o); - o.ancestor = base ? base_label : "(empty tree)"; - o.branch1 = "HEAD"; - o.branch2 = next ? next_label : "(empty tree)"; - - head_tree = parse_tree_indirect(head); - next_tree = next ? next->tree : empty_tree(); - base_tree = base ? base->tree : empty_tree(); - - clean = merge_trees(&o, - head_tree, - next_tree, base_tree, &result); - - if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(&index_lock))) - die("%s: Unable to write new index file", me); - rollback_lock_file(&index_lock); - - if (!clean) { - add_to_msg("\nConflicts:\n\n"); - for (i = 0; i < active_nr;) { - struct cache_entry *ce = active_cache[i++]; - if (ce_stage(ce)) { - add_to_msg("\t"); - add_to_msg(ce->name); - add_to_msg("\n"); - while (i < active_nr && !strcmp(ce->name, - active_cache[i]->name)) - i++; - } + if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) + do_recursive_merge(base, next, base_label, next_label, + head, &msgbuf, defmsg); + else { + int res; + struct commit_list *common = NULL; + struct commit_list *remotes = NULL; + write_message(&msgbuf, defmsg); + commit_list_insert(base, &common); + commit_list_insert(next, &remotes); + res = try_merge_command(strategy, common, + sha1_to_hex(head), remotes); + free_commit_list(common); + free_commit_list(remotes); + if (res) { + fprintf(stderr, "Automatic %s with strategy %s failed.%s\n", + me, strategy, help_msg(commit_name)); + rerere(allow_rerere_auto); + exit(1); } - if (commit_lock_file(&msg_file) < 0) - die ("Error wrapping up %s", defmsg); - fprintf(stderr, "Automatic %s failed.%s\n", - me, help_msg(commit_name)); - rerere(allow_rerere_auto); - exit(1); } - if (commit_lock_file(&msg_file) < 0) - die ("Error wrapping up %s", defmsg); - fprintf(stderr, "Finished one %s.\n", me); /* * diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 06320f528..508950280 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -162,7 +162,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) sha1_to_hex(commit->object.sha1)); if (log->user_format) { struct pretty_print_context ctx = {0}; - ctx.abbrev = DEFAULT_ABBREV; + ctx.abbrev = log->abbrev; ctx.subject = ""; ctx.after_subject = ""; ctx.date_mode = DATE_NORMAL; @@ -290,6 +290,7 @@ parse_done: } log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT; + log.abbrev = rev.abbrev; /* assume HEAD if from a tty */ if (!nongit && !rev.pending.nr && isatty(0)) @@ -718,6 +718,8 @@ extern int has_loose_object_nonlocal(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); +extern void assert_sha1_type(const unsigned char *sha1, enum object_type expect); + extern const signed char hexval_table[256]; static inline unsigned int hexval(unsigned char c) { @@ -905,7 +907,7 @@ struct extra_have_objects { extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags, struct extra_have_objects *); extern int server_supports(const char *feature); -extern struct packed_git *parse_pack_index(unsigned char *sha1); +extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path); extern void prepare_packed_git(void); extern void reprepare_packed_git(void); @@ -916,6 +918,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1, extern void pack_report(void); extern int open_pack_index(struct packed_git *); +extern void close_pack_index(struct packed_git *); extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *); extern void close_pack_windows(struct packed_git *); extern void unuse_pack(struct pack_window **); @@ -936,12 +939,15 @@ extern int update_server_info(int); typedef int (*config_fn_t)(const char *, const char *, void *); extern int git_default_config(const char *, const char *, void *); extern int git_config_from_file(config_fn_t fn, const char *, void *); +extern int git_config_parse_parameter(const char *text); +extern int git_config_from_parameters(config_fn_t fn, void *data); extern int git_config(config_fn_t fn, void *); extern int git_parse_ulong(const char *, unsigned long *); extern int git_config_int(const char *, const char *); extern unsigned long git_config_ulong(const char *, const char *); extern int git_config_bool_or_int(const char *, const char *, int *); extern int git_config_bool(const char *, const char *); +extern int git_config_maybe_bool(const char *, const char *); extern int git_config_string(const char **, const char *, const char *); extern int git_config_pathname(const char **, const char *, const char *); extern int git_config_set(const char *, const char *); @@ -949,6 +955,7 @@ extern int git_config_set_multivar(const char *, const char *, const char *, int extern int git_config_rename_section(const char *, const char *); extern const char *git_etc_gitconfig(void); extern int check_repository_format_version(const char *var, const char *value, void *cb); +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 *); @@ -1040,6 +1047,7 @@ void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char * #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_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF) #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) extern unsigned whitespace_rule_cfg; @@ -1048,7 +1056,7 @@ extern unsigned parse_whitespace_rule(const char *); extern unsigned ws_check(const char *line, int len, unsigned ws_rule); extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws); extern char *whitespace_error_string(unsigned ws); -extern int ws_fix_copy(char *, const char *, int, unsigned, int *); +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); /* ls-files */ @@ -211,31 +211,3 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...) va_end(args); return r; } - -/* - * This function splits the buffer by newlines and colors the lines individually. - * - * Returns 0 on success. - */ -int color_fwrite_lines(FILE *fp, const char *color, - size_t count, const char *buf) -{ - if (!*color) - return fwrite(buf, count, 1, fp) != 1; - while (count) { - char *p = memchr(buf, '\n', count); - if (p != buf && (fputs(color, fp) < 0 || - fwrite(buf, p ? p - buf : count, 1, fp) != 1 || - fputs(GIT_COLOR_RESET, fp) < 0)) - return -1; - if (!p) - return 0; - if (fputc('\n', fp) < 0) - return -1; - count -= p + 1 - buf; - buf = p + 1; - } - return 0; -} - - @@ -61,6 +61,5 @@ __attribute__((format (printf, 3, 4))) int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); __attribute__((format (printf, 3, 4))) int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...); -int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf); #endif /* COLOR_H */ diff --git a/combine-diff.c b/combine-diff.c index 3480dae82..7557136c8 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -211,7 +211,6 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, xpparam_t xpp; xdemitconf_t xecfg; mmfile_t parent_file; - xdemitcb_t ecb; struct combine_diff_state state; unsigned long sz; @@ -231,7 +230,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, state.n = n; xdi_diff_outf(&parent_file, result_file, consume_line, &state, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); free(parent_file.ptr); /* Assign line numbers for this parent. @@ -790,3 +790,58 @@ struct commit_list *reduce_heads(struct commit_list *heads) free(other); return result; } + +static const char commit_utf8_warn[] = +"Warning: commit message does not conform to UTF-8.\n" +"You may want to amend it after fixing the message, or set the config\n" +"variable i18n.commitencoding to the encoding your project uses.\n"; + +int commit_tree(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret, + const char *author) +{ + int result; + int encoding_is_utf8; + struct strbuf buffer; + + assert_sha1_type(tree, OBJ_TREE); + + /* Not having i18n.commitencoding is the same as having utf-8 */ + encoding_is_utf8 = is_encoding_utf8(git_commit_encoding); + + strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */ + strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree)); + + /* + * NOTE! This ordering means that the same exact tree merged with a + * different order of parents will be a _different_ changeset even + * if everything else stays the same. + */ + while (parents) { + struct commit_list *next = parents->next; + strbuf_addf(&buffer, "parent %s\n", + sha1_to_hex(parents->item->object.sha1)); + free(parents); + parents = next; + } + + /* Person/date information */ + if (!author) + author = git_author_info(IDENT_ERROR_ON_NO_NAME); + strbuf_addf(&buffer, "author %s\n", author); + strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME)); + if (!encoding_is_utf8) + strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding); + strbuf_addch(&buffer, '\n'); + + /* And add the comment */ + strbuf_addstr(&buffer, msg); + + /* And check the encoding */ + if (encoding_is_utf8 && !is_utf8(buffer.buf)) + fprintf(stderr, commit_utf8_warn); + + result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret); + strbuf_release(&buffer); + return result; +} @@ -163,4 +163,8 @@ static inline int single_parent(struct commit *commit) struct commit_list *reduce_heads(struct commit_list *heads); +extern int commit_tree(const char *msg, unsigned char *tree, + struct commit_list *parents, unsigned char *ret, + const char *author); + #endif /* COMMIT_H */ diff --git a/compat/mingw.c b/compat/mingw.c index f90a114b0..9a8e33658 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -140,6 +140,23 @@ int mingw_open (const char *filename, int oflags, ...) return fd; } +#undef write +ssize_t mingw_write(int fd, const void *buf, size_t count) +{ + /* + * While write() calls to a file on a local disk are translated + * into WriteFile() calls with a maximum size of 64KB on Windows + * XP and 256KB on Vista, no such cap is placed on writes to + * files over the network on Windows XP. Unfortunately, there + * seems to be a limit of 32MB-28KB on X64 and 64MB-32KB on x86; + * bigger writes fail on Windows XP. + * So we cap to a nice 31MB here to avoid write failures over + * the net without changing the number of WriteFile() calls in + * the local case. + */ + return write(fd, buf, min(count, 31 * 1024 * 1024)); +} + #undef fopen FILE *mingw_fopen (const char *filename, const char *otype) { diff --git a/compat/mingw.h b/compat/mingw.h index 7c2ab64cb..0e3e74304 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -170,6 +170,9 @@ int link(const char *oldpath, const char *newpath); int mingw_open (const char *filename, int oflags, ...); #define open mingw_open +ssize_t mingw_write(int fd, const void *buf, size_t count); +#define write mingw_write + FILE *mingw_fopen (const char *filename, const char *otype); #define fopen mingw_fopen diff --git a/compat/vcbuild/include/termios.h b/compat/vcbuild/include/termios.h new file mode 100644 index 000000000..0d8552a2c --- /dev/null +++ b/compat/vcbuild/include/termios.h @@ -0,0 +1 @@ +/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h index c72f100f4..a45f8d66d 100644 --- a/compat/win32/pthread.h +++ b/compat/win32/pthread.h @@ -18,11 +18,17 @@ */ #define pthread_mutex_t CRITICAL_SECTION -#define pthread_mutex_init(a,b) InitializeCriticalSection((a)) +#define pthread_mutex_init(a,b) (InitializeCriticalSection((a)), 0) #define pthread_mutex_destroy(a) DeleteCriticalSection((a)) #define pthread_mutex_lock EnterCriticalSection #define pthread_mutex_unlock LeaveCriticalSection +typedef int pthread_mutexattr_t; +#define pthread_mutexattr_init(a) (*(a) = 0) +#define pthread_mutexattr_destroy(a) do {} while (0) +#define pthread_mutexattr_settype(a, t) 0 +#define PTHREAD_MUTEX_RECURSIVE 0 + /* * Implement simple condition variable for Windows threads, based on ACE * implementation. diff --git a/compat/win32mmap.c b/compat/win32mmap.c index 1c5a14922..b58aa69fa 100644 --- a/compat/win32mmap.c +++ b/compat/win32mmap.c @@ -4,19 +4,19 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of { HANDLE hmap; void *temp; - size_t len; + off_t len; struct stat st; uint64_t o = offset; uint32_t l = o & 0xFFFFFFFF; uint32_t h = (o >> 32) & 0xFFFFFFFF; if (!fstat(fd, &st)) - len = xsize_t(st.st_size); + len = st.st_size; else die("mmap: could not determine filesize"); if ((length + offset) > len) - length = len - offset; + length = xsize_t(len - offset); if (!(flags & MAP_PRIVATE)) die("Invalid usage of mmap when built with USE_WIN32_MMAP"); @@ -7,6 +7,7 @@ */ #include "cache.h" #include "exec_cmd.h" +#include "strbuf.h" #define MAXNAME (256) @@ -18,6 +19,48 @@ static int zlib_compression_seen; const char *config_exclusive_filename = NULL; +struct config_item +{ + struct config_item *next; + char *name; + char *value; +}; +static struct config_item *config_parameters; +static struct config_item **config_parameters_tail = &config_parameters; + +static void lowercase(char *p) +{ + for (; *p; p++) + *p = tolower(*p); +} + +int git_config_parse_parameter(const char *text) +{ + struct config_item *ct; + struct strbuf tmp = STRBUF_INIT; + struct strbuf **pair; + strbuf_addstr(&tmp, text); + pair = strbuf_split(&tmp, '='); + if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') + strbuf_setlen(pair[0], pair[0]->len - 1); + strbuf_trim(pair[0]); + if (!pair[0]->len) { + strbuf_list_free(pair); + return -1; + } + ct = xcalloc(1, sizeof(struct config_item)); + ct->name = strbuf_detach(pair[0], NULL); + if (pair[1]) { + strbuf_trim(pair[1]); + ct->value = strbuf_detach(pair[1], NULL); + } + strbuf_list_free(pair); + lowercase(ct->name); + *config_parameters_tail = ct; + config_parameters_tail = &ct->next; + return 0; +} + static int get_next_char(void) { int c; @@ -322,17 +365,30 @@ unsigned long git_config_ulong(const char *name, const char *value) return ret; } -int git_config_bool_or_int(const char *name, const char *value, int *is_bool) +int git_config_maybe_bool(const char *name, const char *value) { - *is_bool = 1; if (!value) return 1; if (!*value) return 0; - if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on")) + if (!strcasecmp(value, "true") + || !strcasecmp(value, "yes") + || !strcasecmp(value, "on")) return 1; - if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off")) + if (!strcasecmp(value, "false") + || !strcasecmp(value, "no") + || !strcasecmp(value, "off")) return 0; + 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); + if (0 <= v) { + *is_bool = 1; + return v; + } *is_bool = 0; return git_config_int(name, value); } @@ -683,7 +739,7 @@ const char *git_etc_gitconfig(void) return system_wide; } -static int git_env_bool(const char *k, int def) +int git_env_bool(const char *k, int def) { const char *v = getenv(k); return v ? git_config_bool(k, v) : def; @@ -699,6 +755,15 @@ int git_config_global(void) return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0); } +int git_config_from_parameters(config_fn_t fn, void *data) +{ + const struct config_item *ct; + for (ct = config_parameters; ct; ct = ct->next) + if (fn(ct->name, ct->value, data) < 0) + return -1; + return 0; +} + int git_config(config_fn_t fn, void *data) { int ret = 0, found = 0; @@ -730,6 +795,12 @@ int git_config(config_fn_t fn, void *data) found += 1; } free(repo_config); + + if (config_parameters) { + ret += git_config_from_parameters(fn, data); + found += 1; + } + if (found == 0) return -1; return ret; diff --git a/config.mak.in b/config.mak.in index 6008ac9f1..0d4b64d07 100644 --- a/config.mak.in +++ b/config.mak.in @@ -31,6 +31,7 @@ NO_OPENSSL=@NO_OPENSSL@ NO_CURL=@NO_CURL@ NO_EXPAT=@NO_EXPAT@ NO_LIBGEN_H=@NO_LIBGEN_H@ +HAVE_PATHS_H=@HAVE_PATHS_H@ NEEDS_LIBICONV=@NEEDS_LIBICONV@ NEEDS_SOCKET=@NEEDS_SOCKET@ NEEDS_RESOLV=@NEEDS_RESOLV@ diff --git a/configure.ac b/configure.ac index f4d7372ef..71038fcf1 100644 --- a/configure.ac +++ b/configure.ac @@ -724,6 +724,12 @@ AC_CHECK_HEADER([libgen.h], [NO_LIBGEN_H=YesPlease]) AC_SUBST(NO_LIBGEN_H) # +# Define HAVE_PATHS_H if you have paths.h. +AC_CHECK_HEADER([paths.h], +[HAVE_PATHS_H=YesPlease], +[HAVE_PATHS_H=]) +AC_SUBST(HAVE_PATHS_H) +# # Define NO_STRCASESTR if you don't have strcasestr. GIT_CHECK_FUNC(strcasestr, [NO_STRCASESTR=], diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh index c364dda69..a4ed4c3c6 100755 --- a/contrib/git-resurrect.sh +++ b/contrib/git-resurrect.sh @@ -9,6 +9,7 @@ other/Merge <other> into <name> (respectively) commit subjects, which is rather slow but allows you to resurrect other people's topic branches." +OPTIONS_KEEPDASHDASH= OPTIONS_SPEC="\ git resurrect $USAGE -- diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 58a35c828..30ae63d74 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -23,6 +23,13 @@ # possible for the email to be from someone other than the person doing the # push. # +# To help with debugging and use on pre-v1.5.1 git servers, this script will +# also obey the interface of hooks/update, taking its arguments on the +# command line. Unfortunately, hooks/update is called once for each ref. +# To avoid firing one email per ref, this script just prints its output to +# the screen when used in this mode. The output can then be redirected if +# wanted. +# # Config # ------ # hooks.mailinglist @@ -44,7 +44,8 @@ static char diff_colors[][COLOR_MAXLEN] = { }; static void diff_filespec_load_driver(struct diff_filespec *one); -static char *run_textconv(const char *, struct diff_filespec *, size_t *); +static size_t fill_textconv(struct userdiff_driver *driver, + struct diff_filespec *df, char **outbuf); static int parse_diff_color_slot(const char *var, int ofs) { @@ -466,8 +467,8 @@ static void emit_rewrite_diff(const char *name_a, const char *name_b, struct diff_filespec *one, struct diff_filespec *two, - const char *textconv_one, - const char *textconv_two, + struct userdiff_driver *textconv_one, + struct userdiff_driver *textconv_two, struct diff_options *o) { int lc_a, lc_b; @@ -478,7 +479,7 @@ static void emit_rewrite_diff(const char *name_a, const char *reset = diff_get_color(color_diff, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; const char *a_prefix, *b_prefix; - const char *data_one, *data_two; + char *data_one, *data_two; size_t size_one, size_two; struct emit_callback ecbdata; @@ -500,26 +501,8 @@ static void emit_rewrite_diff(const char *name_a, quote_two_c_style(&a_name, a_prefix, name_a, 0); quote_two_c_style(&b_name, b_prefix, name_b, 0); - diff_populate_filespec(one, 0); - diff_populate_filespec(two, 0); - if (textconv_one) { - data_one = run_textconv(textconv_one, one, &size_one); - if (!data_one) - die("unable to read files to diff"); - } - else { - data_one = one->data; - size_one = one->size; - } - if (textconv_two) { - data_two = run_textconv(textconv_two, two, &size_two); - if (!data_two) - die("unable to read files to diff"); - } - else { - data_two = two->data; - size_two = two->size; - } + size_one = fill_textconv(textconv_one, one, &data_one); + size_two = fill_textconv(textconv_two, two, &data_two); memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.color_diff = color_diff; @@ -577,16 +560,68 @@ static void diff_words_append(char *line, unsigned long len, buffer->text.ptr[buffer->text.size] = '\0'; } +struct diff_words_style_elem +{ + const char *prefix; + const char *suffix; + const char *color; /* NULL; filled in by the setup code if + * color is enabled */ +}; + +struct diff_words_style +{ + enum diff_words_type type; + struct diff_words_style_elem new, old, ctx; + const char *newline; +}; + +struct diff_words_style diff_words_styles[] = { + { DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" }, + { DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" }, + { DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" } +}; + struct diff_words_data { struct diff_words_buffer minus, plus; const char *current_plus; FILE *file; regex_t *word_regex; + enum diff_words_type type; + struct diff_words_style *style; }; +static int fn_out_diff_words_write_helper(FILE *fp, + struct diff_words_style_elem *st_el, + const char *newline, + size_t count, const char *buf) +{ + while (count) { + char *p = memchr(buf, '\n', count); + if (p != buf) { + if (st_el->color && fputs(st_el->color, fp) < 0) + return -1; + if (fputs(st_el->prefix, fp) < 0 || + fwrite(buf, p ? p - buf : count, 1, fp) != 1 || + fputs(st_el->suffix, fp) < 0) + return -1; + if (st_el->color && *st_el->color + && fputs(GIT_COLOR_RESET, fp) < 0) + return -1; + } + if (!p) + return 0; + if (fputs(newline, fp) < 0) + return -1; + count -= p + 1 - buf; + buf = p + 1; + } + return 0; +} + static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) { struct diff_words_data *diff_words = priv; + struct diff_words_style *style = diff_words->style; int minus_first, minus_len, plus_first, plus_len; const char *minus_begin, *minus_end, *plus_begin, *plus_end; @@ -610,16 +645,17 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) plus_begin = plus_end = diff_words->plus.orig[plus_first].end; if (diff_words->current_plus != plus_begin) - fwrite(diff_words->current_plus, - plus_begin - diff_words->current_plus, 1, - diff_words->file); + fn_out_diff_words_write_helper(diff_words->file, + &style->ctx, style->newline, + plus_begin - diff_words->current_plus, + diff_words->current_plus); if (minus_begin != minus_end) - color_fwrite_lines(diff_words->file, - diff_get_color(1, DIFF_FILE_OLD), + fn_out_diff_words_write_helper(diff_words->file, + &style->old, style->newline, minus_end - minus_begin, minus_begin); if (plus_begin != plus_end) - color_fwrite_lines(diff_words->file, - diff_get_color(1, DIFF_FILE_NEW), + fn_out_diff_words_write_helper(diff_words->file, + &style->new, style->newline, plus_end - plus_begin, plus_begin); diff_words->current_plus = plus_end; @@ -700,13 +736,13 @@ static void diff_words_show(struct diff_words_data *diff_words) { xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; mmfile_t minus, plus; + struct diff_words_style *style = diff_words->style; /* special case: only removal */ if (!diff_words->plus.text.size) { - color_fwrite_lines(diff_words->file, - diff_get_color(1, DIFF_FILE_OLD), + fn_out_diff_words_write_helper(diff_words->file, + &style->old, style->newline, diff_words->minus.text.size, diff_words->minus.text.ptr); diff_words->minus.text.size = 0; return; @@ -722,15 +758,15 @@ static void diff_words_show(struct diff_words_data *diff_words) /* as only the hunk header will be parsed, we need a 0-context */ xecfg.ctxlen = 0; xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); free(minus.ptr); free(plus.ptr); if (diff_words->current_plus != diff_words->plus.text.ptr + diff_words->plus.text.size) - fwrite(diff_words->current_plus, + fn_out_diff_words_write_helper(diff_words->file, + &style->ctx, style->newline, diff_words->plus.text.ptr + diff_words->plus.text.size - - diff_words->current_plus, 1, - diff_words->file); + - diff_words->current_plus, diff_words->current_plus); diff_words->minus.text.size = diff_words->plus.text.size = 0; } @@ -842,6 +878,9 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (len < 1) { emit_line(ecbdata->file, reset, reset, line, len); + if (ecbdata->diff_words + && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) + fputs("~\n", ecbdata->file); return; } @@ -856,9 +895,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } diff_words_flush(ecbdata); - line++; - len--; - emit_line(ecbdata->file, plain, reset, line, len); + if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) { + emit_line(ecbdata->file, plain, reset, line, len); + fputs("~\n", ecbdata->file); + } else { + /* don't print the prefix character */ + emit_line(ecbdata->file, plain, reset, line+1, len-1); + } return; } @@ -1586,14 +1629,26 @@ void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const options->b_prefix = b; } -static const char *get_textconv(struct diff_filespec *one) +static struct userdiff_driver *get_textconv(struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) return NULL; if (!S_ISREG(one->mode)) return NULL; diff_filespec_load_driver(one); - return one->driver->textconv; + if (!one->driver->textconv) + return NULL; + + if (one->driver->textconv_want_cache && !one->driver->textconv_cache) { + struct notes_cache *c = xmalloc(sizeof(*c)); + struct strbuf name = STRBUF_INIT; + + strbuf_addf(&name, "textconv/%s", one->driver->name); + notes_cache_init(c, name.buf, one->driver->textconv); + one->driver->textconv_cache = c; + } + + return one->driver; } static void builtin_diff(const char *name_a, @@ -1610,7 +1665,8 @@ static void builtin_diff(const char *name_a, const char *set = diff_get_color_opt(o, DIFF_METAINFO); const char *reset = diff_get_color_opt(o, DIFF_RESET); const char *a_prefix, *b_prefix; - const char *textconv_one = NULL, *textconv_two = NULL; + struct userdiff_driver *textconv_one = NULL; + struct userdiff_driver *textconv_two = NULL; struct strbuf header = STRBUF_INIT; if (DIFF_OPT_TST(o, SUBMODULE_LOG) && @@ -1684,12 +1740,11 @@ static void builtin_diff(const char *name_a, } } - if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) - die("unable to read files to diff"); - if (!DIFF_OPT_TST(o, TEXT) && - ( (diff_filespec_is_binary(one) && !textconv_one) || - (diff_filespec_is_binary(two) && !textconv_two) )) { + ( (!textconv_one && diff_filespec_is_binary(one)) || + (!textconv_two && diff_filespec_is_binary(two)) )) { + if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) + die("unable to read files to diff"); /* Quite common confusing case */ if (mf1.size == mf2.size && !memcmp(mf1.ptr, mf2.ptr, mf1.size)) @@ -1708,7 +1763,6 @@ static void builtin_diff(const char *name_a, const char *diffopts = getenv("GIT_DIFF_OPTS"); xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; struct emit_callback ecbdata; const struct userdiff_funcname *pe; @@ -1717,20 +1771,8 @@ static void builtin_diff(const char *name_a, strbuf_reset(&header); } - if (textconv_one) { - size_t size; - mf1.ptr = run_textconv(textconv_one, one, &size); - if (!mf1.ptr) - die("unable to read files to diff"); - mf1.size = size; - } - if (textconv_two) { - size_t size; - mf2.ptr = run_textconv(textconv_two, two, &size); - if (!mf2.ptr) - die("unable to read files to diff"); - mf2.size = size; - } + mf1.size = fill_textconv(textconv_one, one, &mf1.ptr); + mf2.size = fill_textconv(textconv_two, two, &mf2.ptr); pe = diff_funcname_pattern(one); if (!pe) @@ -1759,10 +1801,13 @@ static void builtin_diff(const char *name_a, xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); else if (!prefixcmp(diffopts, "-u")) xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); - if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) { + if (o->word_diff) { + int i; + ecbdata.diff_words = xcalloc(1, sizeof(struct diff_words_data)); ecbdata.diff_words->file = o->file; + ecbdata.diff_words->type = o->word_diff; if (!o->word_regex) o->word_regex = userdiff_word_regex(one); if (!o->word_regex) @@ -1778,10 +1823,23 @@ static void builtin_diff(const char *name_a, die ("Invalid regular expression: %s", o->word_regex); } + for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) { + if (o->word_diff == diff_words_styles[i].type) { + ecbdata.diff_words->style = + &diff_words_styles[i]; + break; + } + } + if (DIFF_OPT_TST(o, COLOR_DIFF)) { + struct diff_words_style *st = ecbdata.diff_words->style; + st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD); + st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW); + st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN); + } } xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, - &xpp, &xecfg, &ecb); - if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) + &xpp, &xecfg); + if (o->word_diff) free_diff_words_data(&ecbdata); if (textconv_one) free(mf1.ptr); @@ -1833,13 +1891,12 @@ static void builtin_diffstat(const char *name_a, const char *name_b, /* Crazy xdl interfaces.. */ xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); } free_and_return: @@ -1881,14 +1938,13 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, /* Crazy xdl interfaces.. */ xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 1; /* at least one context line */ xpp.flags = XDF_NEED_MINIMAL; xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); if (data.ws_rule & WS_BLANK_AT_EOF) { struct emit_callback ecbdata; @@ -2722,7 +2778,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) const char *arg = av[0]; /* Output format options */ - if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) + if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch")) options->output_format |= DIFF_FORMAT_PATCH; else if (opt_arg(arg, 'U', "unified", &options->context)) options->output_format |= DIFF_FORMAT_PATCH; @@ -2850,13 +2906,37 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, COLOR_DIFF); else if (!strcmp(arg, "--color-words")) { DIFF_OPT_SET(options, COLOR_DIFF); - DIFF_OPT_SET(options, COLOR_DIFF_WORDS); + options->word_diff = DIFF_WORDS_COLOR; } else if (!prefixcmp(arg, "--color-words=")) { DIFF_OPT_SET(options, COLOR_DIFF); - DIFF_OPT_SET(options, COLOR_DIFF_WORDS); + options->word_diff = DIFF_WORDS_COLOR; options->word_regex = arg + 14; } + else if (!strcmp(arg, "--word-diff")) { + if (options->word_diff == DIFF_WORDS_NONE) + options->word_diff = DIFF_WORDS_PLAIN; + } + else if (!prefixcmp(arg, "--word-diff=")) { + const char *type = arg + 12; + if (!strcmp(type, "plain")) + options->word_diff = DIFF_WORDS_PLAIN; + else if (!strcmp(type, "color")) { + DIFF_OPT_SET(options, COLOR_DIFF); + options->word_diff = DIFF_WORDS_COLOR; + } + else if (!strcmp(type, "porcelain")) + options->word_diff = DIFF_WORDS_PORCELAIN; + else if (!strcmp(type, "none")) + options->word_diff = DIFF_WORDS_NONE; + else + die("bad --word-diff argument: %s", type); + } + else if (!prefixcmp(arg, "--word-diff-regex=")) { + if (options->word_diff == DIFF_WORDS_NONE) + options->word_diff = DIFF_WORDS_PLAIN; + options->word_regex = arg + 18; + } else if (!strcmp(arg, "--exit-code")) DIFF_OPT_SET(options, EXIT_WITH_STATUS); else if (!strcmp(arg, "--quiet")) @@ -3383,7 +3463,6 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) for (i = 0; i < q->nr; i++) { xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; mmfile_t mf1, mf2; struct diff_filepair *p = q->queue[i]; int len1, len2; @@ -3445,7 +3524,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_FUNCNAMES; xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); } git_SHA1_Final(sha1, &ctx); @@ -3917,3 +3996,47 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, return strbuf_detach(&buf, outsize); } + +static size_t fill_textconv(struct userdiff_driver *driver, + struct diff_filespec *df, + char **outbuf) +{ + size_t size; + + if (!driver || !driver->textconv) { + if (!DIFF_FILE_VALID(df)) { + *outbuf = ""; + return 0; + } + if (diff_populate_filespec(df, 0)) + die("unable to read files to diff"); + *outbuf = df->data; + return df->size; + } + + if (driver->textconv_cache) { + *outbuf = notes_cache_get(driver->textconv_cache, df->sha1, + &size); + if (*outbuf) + return size; + } + + *outbuf = run_textconv(driver->textconv, df, &size); + if (!*outbuf) + die("unable to read files to diff"); + + if (driver->textconv_cache) { + /* ignore errors, as we might be in a readonly repository */ + notes_cache_put(driver->textconv_cache, df->sha1, *outbuf, + size); + /* + * we could save up changes and flush them all at the end, + * but we would need an extra call after all diffing is done. + * Since generating a cache entry is the slow path anyway, + * this extra overhead probably isn't a big deal. + */ + notes_cache_write(driver->textconv_cache); + } + + return size; +} @@ -54,7 +54,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_FIND_COPIES_HARDER (1 << 6) #define DIFF_OPT_FOLLOW_RENAMES (1 << 7) #define DIFF_OPT_COLOR_DIFF (1 << 8) -#define DIFF_OPT_COLOR_DIFF_WORDS (1 << 9) +/* (1 << 9) unused */ #define DIFF_OPT_HAS_CHANGES (1 << 10) #define DIFF_OPT_QUICK (1 << 11) #define DIFF_OPT_NO_INDEX (1 << 12) @@ -79,6 +79,13 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_XDL_SET(opts, flag) ((opts)->xdl_opts |= XDF_##flag) #define DIFF_XDL_CLR(opts, flag) ((opts)->xdl_opts &= ~XDF_##flag) +enum diff_words_type { + DIFF_WORDS_NONE = 0, + DIFF_WORDS_PORCELAIN, + DIFF_WORDS_PLAIN, + DIFF_WORDS_COLOR +}; + struct diff_options { const char *filter; const char *orderfile; @@ -108,6 +115,7 @@ struct diff_options { int stat_width; int stat_name_width; const char *word_regex; + enum diff_words_type word_diff; /* this is set by diffcore for DIFF_FORMAT_PATCH */ int found_changes; diff --git a/exec_cmd.c b/exec_cmd.c index b2c07c70c..bf225706e 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -107,7 +107,7 @@ void setup_path(void) if (old_path) strbuf_addstr(&new_path, old_path); else - strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin"); + strbuf_addstr(&new_path, _PATH_DEFPATH); setenv("PATH", new_path.buf, 1); diff --git a/fast-import.c b/fast-import.c index 309f2c58a..129a78683 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2707,6 +2707,7 @@ static void option_import_marks(const char *marks, int from_stream) } import_marks_file = make_fast_import_path(marks); + safe_create_leading_directories_const(import_marks_file); import_marks_file_from_stream = from_stream; } @@ -2737,6 +2738,7 @@ static void option_active_branches(const char *branches) static void option_export_marks(const char *marks) { export_marks_file = make_fast_import_path(marks); + safe_create_leading_directories_const(export_marks_file); } static void option_export_pack_edges(const char *edges) @@ -222,12 +222,47 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) return retval; } +static int fsck_ident(char **ident, struct object *obj, fsck_error error_func) +{ + if (**ident == '<' || **ident == '\n') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email"); + *ident += strcspn(*ident, "<\n"); + if ((*ident)[-1] != ' ') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email"); + if (**ident != '<') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing email"); + (*ident)++; + *ident += strcspn(*ident, "<>\n"); + if (**ident != '>') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad email"); + (*ident)++; + if (**ident != ' ') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before date"); + (*ident)++; + if (**ident == '0' && (*ident)[1] != ' ') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - zero-padded date"); + *ident += strspn(*ident, "0123456789"); + if (**ident != ' ') + return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad date"); + (*ident)++; + if ((**ident != '+' && **ident != '-') || + !isdigit((*ident)[1]) || + !isdigit((*ident)[2]) || + !isdigit((*ident)[3]) || + !isdigit((*ident)[4]) || + ((*ident)[5] != '\n')) + return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad time zone"); + (*ident) += 6; + return 0; +} + static int fsck_commit(struct commit *commit, fsck_error error_func) { char *buffer = commit->buffer; unsigned char tree_sha1[20], sha1[20]; struct commit_graft *graft; int parents = 0; + int err; if (commit->date == ULONG_MAX) return error_func(&commit->object, FSCK_ERROR, "invalid author/committer line"); @@ -266,6 +301,18 @@ static int fsck_commit(struct commit *commit, fsck_error error_func) } if (memcmp(buffer, "author ", 7)) return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'author' line"); + buffer += 7; + err = fsck_ident(&buffer, &commit->object, error_func); + if (err) + return err; + if (memcmp(buffer, "committer ", strlen("committer "))) + return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'committer' line"); + buffer += strlen("committer "); + err = fsck_ident(&buffer, &commit->object, error_func); + if (err) + return err; + if (*buffer != '\n') + return error_func(&commit->object, FSCK_ERROR, "invalid format - expected blank line"); if (!commit->tree) return error_func(&commit->object, FSCK_ERROR, "could not load commit's tree %s", sha1_to_hex(tree_sha1)); @@ -740,7 +740,7 @@ do ;; esac - if test $apply_status = 1 && test "$threeway" = t + if test $apply_status != 0 && test "$threeway" = t then if (fall_back_3way) then diff --git a/git-compat-util.h b/git-compat-util.h index 7e62b5527..edf352dee 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -164,6 +164,13 @@ extern char *gitbasename(char *); #define PATH_SEP ':' #endif +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif +#ifndef _PATH_DEFPATH +#define _PATH_DEFPATH "/usr/local/bin:/usr/bin:/bin" +#endif + #ifndef STRIP_EXTENSION #define STRIP_EXTENSION "" #endif @@ -356,6 +363,8 @@ static inline void *gitmempcpy(void *dest, const void *src, size_t n) extern void release_pack_memory(size_t, int); +extern void set_try_to_free_routine(void (*routine)(size_t)); + extern char *xstrdup(const char *str); extern void *xmalloc(size_t size); extern void *xmallocz(size_t size); @@ -479,5 +488,14 @@ void git_qsort(void *base, size_t nmemb, size_t size, * Always returns the return value of unlink(2). */ int unlink_or_warn(const char *path); +/* + * Likewise for rmdir(2). + */ +int rmdir_or_warn(const char *path); +/* + * Calls the correct function out of {unlink,rmdir}_or_warn based on + * the supplied file mode. + */ +int remove_or_warn(unsigned int mode, const char *path); #endif diff --git a/git-remote-testgit.py b/git-remote-testgit.py new file mode 100644 index 000000000..92539222c --- /dev/null +++ b/git-remote-testgit.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python + +import hashlib +import sys +import os +sys.path.insert(0, os.getenv("GITPYTHONLIB",".")) + +from git_remote_helpers.util import die, debug, warn +from git_remote_helpers.git.repo import GitRepo +from git_remote_helpers.git.exporter import GitExporter +from git_remote_helpers.git.importer import GitImporter +from git_remote_helpers.git.non_local import NonLocalGit + +def get_repo(alias, url): + """Returns a git repository object initialized for usage. + """ + + repo = GitRepo(url) + repo.get_revs() + repo.get_head() + + hasher = hashlib.sha1() + hasher.update(repo.path) + repo.hash = hasher.hexdigest() + + repo.get_base_path = lambda base: os.path.join( + base, 'info', 'fast-import', repo.hash) + + prefix = 'refs/testgit/%s/' % alias + debug("prefix: '%s'", prefix) + + repo.gitdir = "" + repo.alias = alias + repo.prefix = prefix + + repo.exporter = GitExporter(repo) + repo.importer = GitImporter(repo) + repo.non_local = NonLocalGit(repo) + + return repo + + +def local_repo(repo, path): + """Returns a git repository object initalized for usage. + """ + + local = GitRepo(path) + + local.non_local = None + local.gitdir = repo.gitdir + local.alias = repo.alias + local.prefix = repo.prefix + local.hash = repo.hash + local.get_base_path = repo.get_base_path + local.exporter = GitExporter(local) + local.importer = GitImporter(local) + + return local + + +def do_capabilities(repo, args): + """Prints the supported capabilities. + """ + + print "import" + print "export" + print "gitdir" + print "refspec refs/heads/*:%s*" % repo.prefix + + print # end capabilities + + +def do_list(repo, args): + """Lists all known references. + + Bug: This will always set the remote head to master for non-local + repositories, since we have no way of determining what the remote + head is at clone time. + """ + + for ref in repo.revs: + debug("? refs/heads/%s", ref) + print "? refs/heads/%s" % ref + + if repo.head: + debug("@refs/heads/%s HEAD" % repo.head) + print "@refs/heads/%s HEAD" % repo.head + else: + debug("@refs/heads/master HEAD") + print "@refs/heads/master HEAD" + + print # end list + + +def update_local_repo(repo): + """Updates (or clones) a local repo. + """ + + if repo.local: + return repo + + path = repo.non_local.clone(repo.gitdir) + repo.non_local.update(repo.gitdir) + repo = local_repo(repo, path) + return repo + + +def do_import(repo, args): + """Exports a fast-import stream from testgit for git to import. + """ + + if len(args) != 1: + die("Import needs exactly one ref") + + if not repo.gitdir: + die("Need gitdir to import") + + repo = update_local_repo(repo) + repo.exporter.export_repo(repo.gitdir) + + +def do_export(repo, args): + """Imports a fast-import stream from git to testgit. + """ + + if not repo.gitdir: + die("Need gitdir to export") + + dirname = repo.get_base_path(repo.gitdir) + + if not os.path.exists(dirname): + os.makedirs(dirname) + + path = os.path.join(dirname, 'testgit.marks') + print path + print path if os.path.exists(path) else "" + sys.stdout.flush() + + update_local_repo(repo) + repo.importer.do_import(repo.gitdir) + repo.non_local.push(repo.gitdir) + + +def do_gitdir(repo, args): + """Stores the location of the gitdir. + """ + + if not args: + die("gitdir needs an argument") + + repo.gitdir = ' '.join(args) + + +COMMANDS = { + 'capabilities': do_capabilities, + 'list': do_list, + 'import': do_import, + 'export': do_export, + 'gitdir': do_gitdir, +} + + +def sanitize(value): + """Cleans up the url. + """ + + if value.startswith('testgit::'): + value = value[9:] + + return value + + +def read_one_line(repo): + """Reads and processes one command. + """ + + line = sys.stdin.readline() + + cmdline = line + + if not cmdline: + warn("Unexpected EOF") + return False + + cmdline = cmdline.strip().split() + if not cmdline: + # Blank line means we're about to quit + return False + + cmd = cmdline.pop(0) + debug("Got command '%s' with args '%s'", cmd, ' '.join(cmdline)) + + if cmd not in COMMANDS: + die("Unknown command, %s", cmd) + + func = COMMANDS[cmd] + func(repo, cmdline) + sys.stdout.flush() + + return True + + +def main(args): + """Starts a new remote helper for the specified repository. + """ + + if len(args) != 3: + die("Expecting exactly three arguments.") + sys.exit(1) + + if os.getenv("GIT_DEBUG_TESTGIT"): + import git_remote_helpers.util + git_remote_helpers.util.DEBUG = True + + alias = sanitize(args[1]) + url = sanitize(args[2]) + + if not alias.isalnum(): + warn("non-alnum alias '%s'", alias) + alias = "tmp" + + args[1] = alias + args[2] = url + + repo = get_repo(alias, url) + + debug("Got arguments %s", args[1:]) + + more = True + + while (more): + more = read_one_line(repo) + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/git-request-pull.sh b/git-request-pull.sh index 8fd15f6df..74238b031 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -8,6 +8,7 @@ USAGE='<start> <url> [<end>]' LONG_USAGE='Summarizes the changes between two commits to the standard output, and includes the given URL in the generated summary.' SUBDIRECTORY_OK='Yes' +OPTIONS_KEEPDASHDASH= OPTIONS_SPEC='git request-pull [options] start url [end] -- p show patch text as well diff --git a/git-send-email.perl b/git-send-email.perl index ce569a9c8..111c98122 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -132,8 +132,6 @@ my $have_email_valid = eval { require Email::Valid; 1 }; my $have_mail_address = eval { require Mail::Address; 1 }; my $smtp; my $auth; -my $mail_domain_default = "localhost.localdomain"; -my $mail_domain; sub unique_email_list(@); sub cleanup_compose_files(); @@ -190,7 +188,7 @@ 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); +my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts, $smtp_domain); my ($validate, $confirm); my (@suppress_cc); @@ -212,6 +210,7 @@ my %config_settings = ( "smtpserverport" => \$smtp_server_port, "smtpuser" => \$smtp_authuser, "smtppass" => \$smtp_authpass, + "smtpdomain" => \$smtp_domain, "to" => \@to, "cc" => \@initial_cc, "cccmd" => \$cc_cmd, @@ -283,7 +282,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "smtp-ssl" => sub { $smtp_encryption = 'ssl' }, "smtp-encryption=s" => \$smtp_encryption, "smtp-debug:i" => \$debug_net_smtp, - "smtp-domain:s" => \$mail_domain, + "smtp-domain:s" => \$smtp_domain, "identity=s" => \$identity, "annotate" => \$annotate, "compose" => \$compose, @@ -761,8 +760,7 @@ sub extract_valid_address { # We'll setup a template for the message id, using the "from" address: my ($message_id_stamp, $message_id_serial); -sub make_message_id -{ +sub make_message_id { my $uniq; if (!defined $message_id_stamp) { $message_id_stamp = sprintf("%s-%s", time, $$); @@ -817,8 +815,7 @@ sub is_rfc2047_quoted { } # use the simplest quoting being able to handle the recipient -sub sanitize_address -{ +sub sanitize_address { my ($recipient) = @_; my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/); @@ -863,21 +860,23 @@ sub sanitize_address # This maildomain*() code is based on ideas in Perl library Test::Reporter # /usr/share/perl5/Test/Reporter/Mail/Util.pm ==> sub _maildomain () -sub maildomain_net -{ +sub valid_fqdn { + my $domain = shift; + return !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./; +} + +sub maildomain_net { my $maildomain; if (eval { require Net::Domain; 1 }) { my $domain = Net::Domain::domainname(); - $maildomain = $domain - unless $^O eq 'darwin' && $domain =~ /\.local$/; + $maildomain = $domain if valid_fqdn($domain); } return $maildomain; } -sub maildomain_mta -{ +sub maildomain_mta { my $maildomain; if (eval { require Net::SMTP; 1 }) { @@ -887,8 +886,7 @@ sub maildomain_mta my $domain = $smtp->domain; $smtp->quit; - $maildomain = $domain - unless $^O eq 'darwin' && $domain =~ /\.local$/; + $maildomain = $domain if valid_fqdn($domain); last if $maildomain; } @@ -898,17 +896,15 @@ sub maildomain_mta return $maildomain; } -sub maildomain -{ - return maildomain_net() || maildomain_mta() || $mail_domain_default; +sub maildomain { + return maildomain_net() || maildomain_mta() || 'localhost.localdomain'; } # Returns 1 if the message was sent, and 0 otherwise. # In actuality, the whole program dies when there # is an error sending a message. -sub send_message -{ +sub send_message { my @recipients = unique_email_list(@to); @cc = (grep { my $cc = extract_valid_address($_); not grep { $cc eq $_ } @recipients @@ -1005,18 +1001,18 @@ X-Mailer: git-send-email $gitversion if ($smtp_encryption eq 'ssl') { $smtp_server_port ||= 465; # ssmtp require Net::SMTP::SSL; - $mail_domain ||= maildomain(); + $smtp_domain ||= maildomain(); $smtp ||= Net::SMTP::SSL->new($smtp_server, - Hello => $mail_domain, + Hello => $smtp_domain, Port => $smtp_server_port); } else { require Net::SMTP; - $mail_domain ||= maildomain(); + $smtp_domain ||= maildomain(); $smtp ||= Net::SMTP->new((defined $smtp_server_port) ? "$smtp_server:$smtp_server_port" : $smtp_server, - Hello => $mail_domain, + Hello => $smtp_domain, Debug => $debug_net_smtp); if ($smtp_encryption eq 'tls' && $smtp) { require Net::SMTP::SSL; @@ -1039,7 +1035,7 @@ X-Mailer: git-send-email $gitversion die "Unable to initialize SMTP properly. Check config and use --smtp-debug. ", "VALUES: server=$smtp_server ", "encryption=$smtp_encryption ", - "maildomain=$mail_domain", + "hello=$smtp_domain", defined $smtp_server_port ? "port=$smtp_server_port" : ""; } diff --git a/git-stash.sh b/git-stash.sh index 59db3dc38..908aab265 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -57,7 +57,7 @@ create_stash () { # state of the base commit if b_commit=$(git rev-parse --verify HEAD) then - head=$(git log --no-color --abbrev-commit --pretty=oneline -n 1 HEAD --) + head=$(git rev-list --oneline -n 1 HEAD --) else die "You do not have the initial commit yet" fi diff --git a/git-submodule.sh b/git-submodule.sh index 2dd372a21..8175cb279 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -21,6 +21,8 @@ command= branch= reference= cached= +recursive= +init= files= nofetch= update= @@ -648,7 +650,7 @@ cmd_summary() { range=$sha1_dst fi GIT_DIR="$name/.git" \ - git log --pretty=oneline --first-parent $range | wc -l + git rev-list --first-parent $range -- | wc -l ) total_commits=" ($(($total_commits + 0)))" ;; diff --git a/git-svn.perl b/git-svn.perl index 2c86ea2e3..b3b6964f9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1185,6 +1185,7 @@ sub cmd_reset { "history\n"; } my ($r, $c) = $gs->find_rev_before($target, not $_fetch_parent); + die "Cannot find SVN revision $target\n" unless defined($c); $gs->rev_map_set($r, $c, 'reset', $uuid); print "r$r = $c ($gs->{ref_id})\n"; } @@ -2086,6 +2087,14 @@ sub refname { # .. becomes %2E%2E $refname =~ s{\.\.}{%2E%2E}g; + # trailing dots and .lock are not allowed + # .$ becomes %2E and .lock becomes %2Elock + $refname =~ s{\.(?=$|lock$)}{%2E}; + + # the sequence @{ is used to access the reflog + # @{ becomes %40{ + $refname =~ s{\@\{}{%40\{}g; + return $refname; } @@ -3605,6 +3614,7 @@ sub mkfile { sub rev_map_set { my ($self, $rev, $commit, $update_ref, $uuid) = @_; + defined $commit or die "missing arg3\n"; length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n"; my $db = $self->map_path($uuid); my $db_lock = "$db.lock"; @@ -3998,7 +4008,6 @@ use vars qw/@ISA/; use strict; use warnings; use Carp qw/croak/; -use File::Temp qw/tempfile/; use IO::File qw//; use vars qw/$_ignore_regex/; @@ -8,6 +8,7 @@ const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n" " [-p|--paginate|--no-pager] [--no-replace-objects]\n" " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n" + " [-c name=value\n" " [--help] COMMAND [ARGS]"; const char git_more_info_string[] = @@ -130,6 +131,14 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 0); if (envchanged) *envchanged = 1; + } else if (!strcmp(cmd, "-c")) { + if (*argc < 2) { + fprintf(stderr, "-c expects a configuration string\n" ); + usage(git_usage_string); + } + git_config_parse_parameter((*argv)[1]); + (*argv)++; + (*argc)--; } else { fprintf(stderr, "Unknown option: %s\n", cmd); usage(git_usage_string); diff --git a/git_remote_helpers/git/exporter.py b/git_remote_helpers/git/exporter.py new file mode 100644 index 000000000..dfaab00b5 --- /dev/null +++ b/git_remote_helpers/git/exporter.py @@ -0,0 +1,51 @@ +import os +import subprocess +import sys + + +class GitExporter(object): + """An exporter for testgit repositories. + + The exporter simply delegates to git fast-export. + """ + + def __init__(self, repo): + """Creates a new exporter for the specified repo. + """ + + self.repo = repo + + def export_repo(self, base): + """Exports a fast-export stream for the given directory. + + Simply delegates to git fast-epxort and pipes it through sed + to make the refs show up under the prefix rather than the + default refs/heads. This is to demonstrate how the export + data can be stored under it's own ref (using the refspec + capability). + """ + + dirname = self.repo.get_base_path(base) + path = os.path.abspath(os.path.join(dirname, 'testgit.marks')) + + if not os.path.exists(dirname): + os.makedirs(dirname) + + print "feature relative-marks" + if os.path.exists(os.path.join(dirname, 'git.marks')): + print "feature import-marks=%s/git.marks" % self.repo.hash + print "feature export-marks=%s/git.marks" % self.repo.hash + sys.stdout.flush() + + args = ["git", "--git-dir=" + self.repo.gitpath, "fast-export", "--export-marks=" + path] + + if os.path.exists(path): + args.append("--import-marks=" + path) + + args.append("HEAD") + + p1 = subprocess.Popen(args, stdout=subprocess.PIPE) + + args = ["sed", "s_refs/heads/_" + self.repo.prefix + "_g"] + + subprocess.check_call(args, stdin=p1.stdout) diff --git a/git_remote_helpers/git/importer.py b/git_remote_helpers/git/importer.py new file mode 100644 index 000000000..af2919d92 --- /dev/null +++ b/git_remote_helpers/git/importer.py @@ -0,0 +1,38 @@ +import os +import subprocess + + +class GitImporter(object): + """An importer for testgit repositories. + + This importer simply delegates to git fast-import. + """ + + def __init__(self, repo): + """Creates a new importer for the specified repo. + """ + + self.repo = repo + + def do_import(self, base): + """Imports a fast-import stream to the given directory. + + Simply delegates to git fast-import. + """ + + dirname = self.repo.get_base_path(base) + if self.repo.local: + gitdir = self.repo.gitpath + else: + gitdir = os.path.abspath(os.path.join(dirname, '.git')) + path = os.path.abspath(os.path.join(dirname, 'git.marks')) + + if not os.path.exists(dirname): + os.makedirs(dirname) + + args = ["git", "--git-dir=" + gitdir, "fast-import", "--quiet", "--export-marks=" + path] + + if os.path.exists(path): + args.append("--import-marks=" + path) + + subprocess.check_call(args) diff --git a/git_remote_helpers/git/non_local.py b/git_remote_helpers/git/non_local.py new file mode 100644 index 000000000..d75ef8f21 --- /dev/null +++ b/git_remote_helpers/git/non_local.py @@ -0,0 +1,61 @@ +import os +import subprocess + +from git_remote_helpers.util import die, warn + + +class NonLocalGit(object): + """Handler to interact with non-local repos. + """ + + def __init__(self, repo): + """Creates a new non-local handler for the specified repo. + """ + + self.repo = repo + + def clone(self, base): + """Clones the non-local repo to base. + + Does nothing if a clone already exists. + """ + + path = os.path.join(self.repo.get_base_path(base), '.git') + + # already cloned + if os.path.exists(path): + return path + + os.makedirs(path) + args = ["git", "clone", "--bare", "--quiet", self.repo.gitpath, path] + + subprocess.check_call(args) + + return path + + def update(self, base): + """Updates checkout of the non-local repo in base. + """ + + path = os.path.join(self.repo.get_base_path(base), '.git') + + if not os.path.exists(path): + die("could not find repo at %s", path) + + args = ["git", "--git-dir=" + path, "fetch", "--quiet", self.repo.gitpath] + subprocess.check_call(args) + + args = ["git", "--git-dir=" + path, "update-ref", "refs/heads/master", "FETCH_HEAD"] + subprocess.check_call(args) + + def push(self, base): + """Pushes from the non-local repo to base. + """ + + path = os.path.join(self.repo.get_base_path(base), '.git') + + if not os.path.exists(path): + die("could not find repo at %s", path) + + args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath] + subprocess.check_call(args) diff --git a/git_remote_helpers/git/repo.py b/git_remote_helpers/git/repo.py new file mode 100644 index 000000000..82d5f78c7 --- /dev/null +++ b/git_remote_helpers/git/repo.py @@ -0,0 +1,70 @@ +import os +import subprocess + +def sanitize(rev, sep='\t'): + """Converts a for-each-ref line to a name/value pair. + """ + + splitrev = rev.split(sep) + branchval = splitrev[0] + branchname = splitrev[1].strip() + if branchname.startswith("refs/heads/"): + branchname = branchname[11:] + + return branchname, branchval + +def is_remote(url): + """Checks whether the specified value is a remote url. + """ + + prefixes = ["http", "file", "git"] + + return any(url.startswith(i) for i in prefixes) + +class GitRepo(object): + """Repo object representing a repo. + """ + + def __init__(self, path): + """Initializes a new repo at the given path. + """ + + self.path = path + self.head = None + self.revmap = {} + self.local = not is_remote(self.path) + + if(self.path.endswith('.git')): + self.gitpath = self.path + else: + self.gitpath = os.path.join(self.path, '.git') + + if self.local and not os.path.exists(self.gitpath): + os.makedirs(self.gitpath) + + def get_revs(self): + """Fetches all revs from the remote. + """ + + args = ["git", "ls-remote", self.gitpath] + path = ".cached_revs" + ofile = open(path, "w") + + subprocess.check_call(args, stdout=ofile) + output = open(path).readlines() + self.revmap = dict(sanitize(i) for i in output) + if "HEAD" in self.revmap: + del self.revmap["HEAD"] + self.revs = self.revmap.keys() + ofile.close() + + def get_head(self): + """Determines the head of a local repo. + """ + + if not self.local: + return + + path = os.path.join(self.gitpath, "HEAD") + head = open(path).readline() + self.head, _ = sanitize(head, ' ') diff --git a/gitweb/INSTALL b/gitweb/INSTALL index cbdc13647..d484d76b7 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -6,8 +6,8 @@ First you have to generate gitweb.cgi from gitweb.perl using gitweb.css, git-logo.png and git-favicon.png) to their destination. For example if git was (or is) installed with /usr prefix, you can do - $ make prefix=/usr gitweb ;# as yourself - # cp gitweb/git* /var/www/cgi-bin/ ;# as root + $ make prefix=/usr gitweb ;# as yourself + # make gitwebdir=/var/www/cgi-bin install-gitweb ;# as root Alternatively you can use autoconf generated ./configure script to set up path to git binaries (via config.mak.autogen), so you can write @@ -16,7 +16,8 @@ instead $ make configure ;# as yourself $ ./configure --prefix=/usr ;# as yourself $ make gitweb ;# as yourself - # cp gitweb/git* /var/www/cgi-bin/ ;# as root + # make gitwebdir=/var/www/cgi-bin \ + install-gitweb ;# as root The above example assumes that your web server is configured to run [executable] files in /var/www/cgi-bin/ as server scripts (as CGI @@ -74,9 +75,10 @@ file for gitweb (in gitweb/README). Build example ~~~~~~~~~~~~~ -- To install gitweb to /var/www/cgi-bin/gitweb/ when git wrapper - is installed at /usr/local/bin/git and the repositories (projects) - we want to display are under /home/local/scm, you can do +- To install gitweb to /var/www/cgi-bin/gitweb/, when git wrapper + is installed at /usr/local/bin/git, the repositories (projects) + we want to display are under /home/local/scm, and you do not use + minifiers, you can do make GITWEB_PROJECTROOT="/home/local/scm" \ GITWEB_JS="/gitweb/gitweb.js" \ @@ -86,8 +88,8 @@ Build example bindir=/usr/local/bin \ gitweb - cp -fv ~/git/gitweb/gitweb.{cgi,js,css} \ - ~/git/gitweb/git-{favicon,logo}.png \ + cp -fv gitweb/gitweb.{cgi,js,css} \ + gitweb/git-{favicon,logo}.png \ /var/www/cgi-bin/gitweb/ diff --git a/gitweb/Makefile b/gitweb/Makefile index f2e1d92fb..935d2d2e0 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -12,7 +12,10 @@ all:: prefix ?= $(HOME) bindir ?= $(prefix)/bin +gitwebdir ?= /var/www/cgi-bin + RM ?= rm -f +INSTALL ?= install # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl @@ -49,9 +52,11 @@ SHELL_PATH ?= $(SHELL) PERL_PATH ?= /usr/bin/perl # Shell quote; -bindir_SQ = $(subst ','\'',$(bindir)) #' -SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) #' -PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) #' +bindir_SQ = $(subst ','\'',$(bindir))#' +gitwebdir_SQ = $(subst ','\'',$(gitwebdir))#' +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))#' +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))#' +DESTDIR_SQ = $(subst ','\'',$(DESTDIR))#' # Quiet generation (unless V=1) QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir @@ -80,20 +85,30 @@ endif all:: gitweb.cgi +GITWEB_PROGRAMS = gitweb.cgi + ifdef JSMIN +GITWEB_FILES += gitweb.min.js GITWEB_JS = gitweb.min.js all:: gitweb.min.js gitweb.min.js: gitweb.js GITWEB-BUILD-OPTIONS $(QUIET_GEN)$(JSMIN) <$< >$@ +else +GITWEB_FILES += gitweb.js endif ifdef CSSMIN +GITWEB_FILES += gitweb.min.css GITWEB_CSS = gitweb.min.css all:: gitweb.min.css gitweb.min.css: gitweb.css GITWEB-BUILD-OPTIONS $(QUIET_GEN)$(CSSMIN) <$ >$@ +else +GITWEB_FILES += gitweb.css endif +GITWEB_FILES += git-logo.png git-favicon.png + GITWEB_REPLACE = \ -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ -e 's|++GIT_BINDIR++|$(bindir)|g' \ @@ -127,8 +142,17 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS chmod +x $@+ && \ mv $@+ $@ +### Installation rules + +install: all + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebdir_SQ)' + $(INSTALL) -m 755 $(GITWEB_PROGRAMS) '$(DESTDIR_SQ)$(gitwebdir_SQ)' + $(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebdir_SQ)' + +### Cleaning rules + clean: $(RM) gitweb.cgi gitweb.min.js gitweb.min.css GITWEB-BUILD-OPTIONS -.PHONY: all clean .FORCE-GIT-VERSION-FILE FORCE +.PHONY: all clean install .FORCE-GIT-VERSION-FILE FORCE diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c356e95f1..934aacb61 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -11,7 +11,7 @@ use strict; use warnings; use CGI qw(:standard :escapeHTML -nosticky); use CGI::Util qw(unescape); -use CGI::Carp qw(fatalsToBrowser); +use CGI::Carp qw(fatalsToBrowser set_message); use Encode; use Fcntl ':mode'; use File::Find qw(); @@ -952,6 +952,21 @@ if ($git_avatar eq 'gravatar') { $git_avatar = ''; } +# custom error handler: 'die <message>' is Internal Server Error +sub handle_errors_html { + my $msg = shift; # it is already HTML escaped + + # to avoid infinite loop where error occurs in die_error, + # change handler to default handler, disabling handle_errors_html + set_message("Error occured when inside die_error:\n$msg"); + + # you cannot jump out of die_error when called as error handler; + # the subroutine set via CGI::Carp::set_message is called _after_ + # HTTP headers are already written, so it cannot write them itself + die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1); +} +set_message(\&handle_errors_html); + # dispatch if (!defined $action) { if (defined $hash) { @@ -972,11 +987,16 @@ if ($action !~ m/^(?:opml|project_list|project_index)$/ && die_error(400, "Project needed"); } $actions{$action}->(); -exit; +DONE_GITWEB: +1; ## ====================================================================== ## action links +# possible values of extra options +# -full => 0|1 - use absolute/full URL ($my_uri/$my_url as base) +# -replay => 1 - start from a current view (replay with modifications) +# -path_info => 0|1 - don't use/use path_info URL (if possible) sub href { my %params = @_; # default is to use -absolute url() i.e. $my_uri @@ -993,7 +1013,8 @@ sub href { } my $use_pathinfo = gitweb_check_feature('pathinfo'); - if ($use_pathinfo and defined $params{'project'}) { + if (defined $params{'project'} && + (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) { # try to put as many parameters as possible in PATH_INFO: # - project name # - action @@ -2420,6 +2441,9 @@ sub git_get_projects_list { follow_skip => 2, # ignore duplicates dangling_symlinks => 0, # ignore dangling symlinks, silently wanted => sub { + # global variables + our $project_maxdepth; + our $projectroot; # skip project-list toplevel, if we get it. return if (m!^[/.]$!); # only directories can be git repositories @@ -3158,23 +3182,30 @@ sub blob_contenttype { ## ====================================================================== ## functions printing HTML: header, footer, error page +sub get_page_title { + my $title = to_utf8($site_name); + + return $title unless (defined $project); + $title .= " - " . to_utf8($project); + + return $title unless (defined $action); + $title .= "/$action"; # $action is US-ASCII (7bit ASCII) + + return $title unless (defined $file_name); + $title .= " - " . esc_path($file_name); + if ($action eq "tree" && $file_name !~ m|/$|) { + $title .= "/"; + } + + return $title; +} + sub git_header_html { my $status = shift || "200 OK"; my $expires = shift; + my %opts = @_; - my $title = "$site_name"; - if (defined $project) { - $title .= " - " . to_utf8($project); - if (defined $action) { - $title .= "/$action"; - if (defined $file_name) { - $title .= " - " . esc_path($file_name); - if ($action eq "tree" && $file_name !~ m|/$|) { - $title .= "/"; - } - } - } - } + my $title = get_page_title(); my $content_type; # require explicit support from the UA if we are to send the page as # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. @@ -3188,7 +3219,8 @@ sub git_header_html { $content_type = 'text/html'; } print $cgi->header(-type=>$content_type, -charset => 'utf-8', - -status=> $status, -expires => $expires); + -status=> $status, -expires => $expires) + unless ($opts{'-no_http_headers'}); my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : ''; print <<EOF; <?xml version="1.0" encoding="utf-8"?> @@ -3405,6 +3437,7 @@ sub die_error { my $status = shift || 500; my $error = esc_html(shift) || "Internal Server Error"; my $extra = shift; + my %opts = @_; my %http_responses = ( 400 => '400 Bad Request', @@ -3413,7 +3446,7 @@ sub die_error { 500 => '500 Internal Server Error', 503 => '503 Service Unavailable', ); - git_header_html($http_responses{$status}); + git_header_html($http_responses{$status}, undef, %opts); print <<EOF; <div class="page_body"> <br /><br /> @@ -3427,7 +3460,8 @@ EOF print "</div>\n"; git_footer_html(); - exit; + goto DONE_GITWEB + unless ($opts{'-error_handler'}); } ## ---------------------------------------------------------------------- @@ -6117,8 +6151,8 @@ sub git_commitdiff { } push @commit_spec, '--root', $hash; } - open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8', - '--stdout', @commit_spec + open $fd, "-|", git_cmd(), "format-patch", @diff_opts, + '--encoding=utf8', '--stdout', @commit_spec or die_error(500, "Open git-format-patch failed"); } else { die_error(400, "Unknown commitdiff format"); diff --git a/http-walker.c b/http-walker.c index ef99ae647..8ca76d050 100644 --- a/http-walker.c +++ b/http-walker.c @@ -510,7 +510,7 @@ static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned c ret = error("File %s has bad hash", hex); } else if (req->rename < 0) { ret = error("unable to write sha1 filename %s", - req->filename); + sha1_file_name(req->sha1)); } release_http_object_request(req); @@ -1,6 +1,7 @@ #include "http.h" #include "pack.h" #include "sideband.h" +#include "run-command.h" int data_received; int active_requests; @@ -720,7 +721,7 @@ static inline int hex(int v) return 'A' + v - 10; } -static void end_url_with_slash(struct strbuf *buf, const char *url) +void end_url_with_slash(struct strbuf *buf, const char *url) { strbuf_addstr(buf, url); if (buf->len && buf->buf[buf->len - 1] != '/') @@ -815,7 +816,21 @@ static int http_request(const char *url, void *result, int target, int options) ret = HTTP_OK; else if (missing_target(&results)) ret = HTTP_MISSING_TARGET; - else + else if (results.http_code == 401) { + if (user_name) { + ret = HTTP_NOAUTH; + } else { + /* + * git_getpass is needed here because its very likely stdin/stdout are + * pipes to our parent process. So we instead need to use /dev/tty, + * but that is non-portable. Using git_getpass() can at least be stubbed + * on other platforms with a different implementation if/when necessary. + */ + user_name = xstrdup(git_getpass("Username: ")); + init_curl_http_auth(slot->curl); + ret = HTTP_REAUTH; + } + } else ret = HTTP_ERROR; } else { error("Unable to start HTTP request for %s", url); @@ -831,7 +846,11 @@ static int http_request(const char *url, void *result, int target, int options) int http_get_strbuf(const char *url, struct strbuf *result, int options) { - return http_request(url, result, HTTP_REQUEST_STRBUF, options); + int http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options); + if (http_ret == HTTP_REAUTH) { + http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options); + } + return http_ret; } /* @@ -896,47 +915,67 @@ int http_fetch_ref(const char *base, struct ref *ref) } /* Helpers for fetching packs */ -static int fetch_pack_index(unsigned char *sha1, const char *base_url) +static char *fetch_pack_index(unsigned char *sha1, const char *base_url) { - int ret = 0; - char *hex = xstrdup(sha1_to_hex(sha1)); - char *filename; - char *url = NULL; + char *url, *tmp; struct strbuf buf = STRBUF_INIT; - if (has_pack_index(sha1)) { - ret = 0; - goto cleanup; - } - if (http_is_verbose) - fprintf(stderr, "Getting index for pack %s\n", hex); + fprintf(stderr, "Getting index for pack %s\n", sha1_to_hex(sha1)); end_url_with_slash(&buf, base_url); - strbuf_addf(&buf, "objects/pack/pack-%s.idx", hex); + strbuf_addf(&buf, "objects/pack/pack-%s.idx", sha1_to_hex(sha1)); url = strbuf_detach(&buf, NULL); - filename = sha1_pack_index_name(sha1); - if (http_get_file(url, filename, 0) != HTTP_OK) - ret = error("Unable to get pack index %s\n", url); + strbuf_addf(&buf, "%s.temp", sha1_pack_index_name(sha1)); + tmp = strbuf_detach(&buf, NULL); + + if (http_get_file(url, tmp, 0) != HTTP_OK) { + error("Unable to get pack index %s\n", url); + free(tmp); + tmp = NULL; + } -cleanup: - free(hex); free(url); - return ret; + return tmp; } static int fetch_and_setup_pack_index(struct packed_git **packs_head, unsigned char *sha1, const char *base_url) { struct packed_git *new_pack; + char *tmp_idx = NULL; + int ret; - if (fetch_pack_index(sha1, base_url)) + if (has_pack_index(sha1)) { + new_pack = parse_pack_index(sha1, NULL); + if (!new_pack) + return -1; /* parse_pack_index() already issued error message */ + goto add_pack; + } + + tmp_idx = fetch_pack_index(sha1, base_url); + if (!tmp_idx) return -1; - new_pack = parse_pack_index(sha1); - if (!new_pack) + new_pack = parse_pack_index(sha1, tmp_idx); + if (!new_pack) { + unlink(tmp_idx); + free(tmp_idx); + return -1; /* parse_pack_index() already issued error message */ + } + + ret = verify_pack_index(new_pack); + if (!ret) { + close_pack_index(new_pack); + ret = move_temp_to_file(tmp_idx, sha1_pack_index_name(sha1)); + } + free(tmp_idx); + if (ret) + return -1; + +add_pack: new_pack->next = *packs_head; *packs_head = new_pack; return 0; @@ -1000,37 +1039,62 @@ void release_http_pack_request(struct http_pack_request *preq) int finish_http_pack_request(struct http_pack_request *preq) { - int ret; struct packed_git **lst; + struct packed_git *p = preq->target; + char *tmp_idx; + struct child_process ip; + const char *ip_argv[8]; - preq->target->pack_size = ftell(preq->packfile); - - if (preq->packfile != NULL) { - fclose(preq->packfile); - preq->packfile = NULL; - preq->slot->local = NULL; - } + close_pack_index(p); - ret = move_temp_to_file(preq->tmpfile, preq->filename); - if (ret) - return ret; + fclose(preq->packfile); + preq->packfile = NULL; + preq->slot->local = NULL; lst = preq->lst; - while (*lst != preq->target) + while (*lst != p) lst = &((*lst)->next); *lst = (*lst)->next; - if (verify_pack(preq->target)) + tmp_idx = xstrdup(preq->tmpfile); + strcpy(tmp_idx + strlen(tmp_idx) - strlen(".pack.temp"), + ".idx.temp"); + + ip_argv[0] = "index-pack"; + ip_argv[1] = "-o"; + ip_argv[2] = tmp_idx; + ip_argv[3] = preq->tmpfile; + ip_argv[4] = NULL; + + memset(&ip, 0, sizeof(ip)); + ip.argv = ip_argv; + ip.git_cmd = 1; + ip.no_stdin = 1; + ip.no_stdout = 1; + + if (run_command(&ip)) { + unlink(preq->tmpfile); + unlink(tmp_idx); + free(tmp_idx); + return -1; + } + + unlink(sha1_pack_index_name(p->sha1)); + + if (move_temp_to_file(preq->tmpfile, sha1_pack_name(p->sha1)) + || move_temp_to_file(tmp_idx, sha1_pack_index_name(p->sha1))) { + free(tmp_idx); return -1; - install_packed_git(preq->target); + } + install_packed_git(p); + free(tmp_idx); return 0; } struct http_pack_request *new_http_pack_request( struct packed_git *target, const char *base_url) { - char *filename; long prev_posn = 0; char range[RANGE_HEADER_SIZE]; struct strbuf buf = STRBUF_INIT; @@ -1045,9 +1109,8 @@ struct http_pack_request *new_http_pack_request( sha1_to_hex(target->sha1)); preq->url = strbuf_detach(&buf, NULL); - filename = sha1_pack_name(target->sha1); - snprintf(preq->filename, sizeof(preq->filename), "%s", filename); - snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp", filename); + snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp", + sha1_pack_name(target->sha1)); preq->packfile = fopen(preq->tmpfile, "a"); if (!preq->packfile) { error("Unable to open local file %s for pack", @@ -1082,7 +1145,6 @@ struct http_pack_request *new_http_pack_request( return preq; abort: - free(filename); free(preq->url); free(preq); return NULL; @@ -1137,7 +1199,6 @@ struct http_object_request *new_http_object_request(const char *base_url, freq->localfile = -1; filename = sha1_file_name(sha1); - snprintf(freq->filename, sizeof(freq->filename), "%s", filename); snprintf(freq->tmpfile, sizeof(freq->tmpfile), "%s.temp", filename); @@ -1166,8 +1227,8 @@ struct http_object_request *new_http_object_request(const char *base_url, } if (freq->localfile < 0) { - error("Couldn't create temporary file %s for %s: %s", - freq->tmpfile, freq->filename, strerror(errno)); + error("Couldn't create temporary file %s: %s", + freq->tmpfile, strerror(errno)); goto abort; } @@ -1214,8 +1275,8 @@ struct http_object_request *new_http_object_request(const char *base_url, prev_posn = 0; lseek(freq->localfile, 0, SEEK_SET); if (ftruncate(freq->localfile, 0) < 0) { - error("Couldn't truncate temporary file %s for %s: %s", - freq->tmpfile, freq->filename, strerror(errno)); + error("Couldn't truncate temporary file %s: %s", + freq->tmpfile, strerror(errno)); goto abort; } } @@ -1291,7 +1352,7 @@ int finish_http_object_request(struct http_object_request *freq) return -1; } freq->rename = - move_temp_to_file(freq->tmpfile, freq->filename); + move_temp_to_file(freq->tmpfile, sha1_file_name(freq->sha1)); return freq->rename; } @@ -117,6 +117,7 @@ extern void append_remote_object_url(struct strbuf *buf, const char *url, int only_two_digit_prefix); extern char *get_remote_object_url(const char *url, const char *hex, int only_two_digit_prefix); +extern void end_url_with_slash(struct strbuf *buf, const char *url); /* Options for http_request_*() */ #define HTTP_NO_CACHE 1 @@ -126,6 +127,8 @@ extern char *get_remote_object_url(const char *url, const char *hex, #define HTTP_MISSING_TARGET 1 #define HTTP_ERROR 2 #define HTTP_START_FAILED 3 +#define HTTP_REAUTH 4 +#define HTTP_NOAUTH 5 /* * Requests an url and stores the result in a strbuf. @@ -152,7 +155,6 @@ struct http_pack_request struct packed_git *target; struct packed_git **lst; FILE *packfile; - char filename[PATH_MAX]; char tmpfile[PATH_MAX]; struct curl_slist *range_header; struct active_request_slot *slot; @@ -167,7 +169,6 @@ extern void release_http_pack_request(struct http_pack_request *preq); struct http_object_request { char *url; - char filename[PATH_MAX]; char tmpfile[PATH_MAX]; int localfile; CURLcode curl_result; diff --git a/merge-recursive.c b/merge-recursive.c index 917397ca7..206c10363 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -409,7 +409,7 @@ static int remove_file(struct merge_options *o, int clean, return -1; } if (update_working_directory) { - if (remove_path(path) && errno != ENOENT) + if (remove_path(path)) return -1; } return 0; diff --git a/merge-recursive.h b/merge-recursive.h index d1192f56d..0cc465ec5 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -54,4 +54,7 @@ int merge_recursive_generic(struct merge_options *o, void init_merge_options(struct merge_options *o); struct tree *write_tree_from_memory(struct merge_options *o); +/* builtin/merge.c */ +int try_merge_command(const char *strategy, struct commit_list *common, const char *head_arg, struct commit_list *remotes); + #endif diff --git a/notes-cache.c b/notes-cache.c new file mode 100644 index 000000000..dee6d62e7 --- /dev/null +++ b/notes-cache.c @@ -0,0 +1,94 @@ +#include "cache.h" +#include "notes-cache.h" +#include "commit.h" +#include "refs.h" + +static int notes_cache_match_validity(const char *ref, const char *validity) +{ + unsigned char sha1[20]; + struct commit *commit; + struct pretty_print_context pretty_ctx; + struct strbuf msg = STRBUF_INIT; + int ret; + + if (read_ref(ref, sha1) < 0) + return 0; + + commit = lookup_commit_reference_gently(sha1, 1); + if (!commit) + return 0; + + memset(&pretty_ctx, 0, sizeof(pretty_ctx)); + format_commit_message(commit, "%s", &msg, &pretty_ctx); + strbuf_trim(&msg); + + ret = !strcmp(msg.buf, validity); + strbuf_release(&msg); + + return ret; +} + +void notes_cache_init(struct notes_cache *c, const char *name, + const char *validity) +{ + struct strbuf ref = STRBUF_INIT; + int flags = 0; + + memset(c, 0, sizeof(*c)); + c->validity = xstrdup(validity); + + strbuf_addf(&ref, "refs/notes/%s", name); + if (!notes_cache_match_validity(ref.buf, validity)) + flags = NOTES_INIT_EMPTY; + init_notes(&c->tree, ref.buf, combine_notes_overwrite, flags); + strbuf_release(&ref); +} + +int notes_cache_write(struct notes_cache *c) +{ + unsigned char tree_sha1[20]; + unsigned char commit_sha1[20]; + + if (!c || !c->tree.initialized || !c->tree.ref || !*c->tree.ref) + return -1; + if (!c->tree.dirty) + return 0; + + if (write_notes_tree(&c->tree, tree_sha1)) + return -1; + if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0) + return -1; + if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL, + 0, QUIET_ON_ERR) < 0) + return -1; + + return 0; +} + +char *notes_cache_get(struct notes_cache *c, unsigned char key_sha1[20], + size_t *outsize) +{ + const unsigned char *value_sha1; + enum object_type type; + char *value; + unsigned long size; + + value_sha1 = get_note(&c->tree, key_sha1); + if (!value_sha1) + return NULL; + value = read_sha1_file(value_sha1, &type, &size); + + *outsize = size; + return value; +} + +int notes_cache_put(struct notes_cache *c, unsigned char key_sha1[20], + const char *data, size_t size) +{ + unsigned char value_sha1[20]; + + if (write_sha1_file(data, size, "blob", value_sha1) < 0) + return -1; + add_note(&c->tree, key_sha1, value_sha1, NULL); + return 0; +} diff --git a/notes-cache.h b/notes-cache.h new file mode 100644 index 000000000..356f88fb3 --- /dev/null +++ b/notes-cache.h @@ -0,0 +1,20 @@ +#ifndef NOTES_CACHE_H +#define NOTES_CACHE_H + +#include "notes.h" + +struct notes_cache { + struct notes_tree tree; + char *validity; +}; + +void notes_cache_init(struct notes_cache *c, const char *name, + const char *validity); +int notes_cache_write(struct notes_cache *c); + +char *notes_cache_get(struct notes_cache *c, unsigned char sha1[20], size_t + *outsize); +int notes_cache_put(struct notes_cache *c, unsigned char sha1[20], + const char *data, size_t size); + +#endif /* NOTES_CACHE_H */ diff --git a/pack-check.c b/pack-check.c index 166ca703c..395fb9527 100644 --- a/pack-check.c +++ b/pack-check.c @@ -133,14 +133,13 @@ static int verify_packfile(struct packed_git *p, return err; } -int verify_pack(struct packed_git *p) +int verify_pack_index(struct packed_git *p) { off_t index_size; const unsigned char *index_base; git_SHA_CTX ctx; unsigned char sha1[20]; int err = 0; - struct pack_window *w_curs = NULL; if (open_pack_index(p)) return error("packfile %s index not opened", p->pack_name); @@ -154,8 +153,18 @@ int verify_pack(struct packed_git *p) if (hashcmp(sha1, index_base + index_size - 20)) err = error("Packfile index for %s SHA1 mismatch", p->pack_name); + return err; +} + +int verify_pack(struct packed_git *p) +{ + int err = 0; + struct pack_window *w_curs = NULL; + + err |= verify_pack_index(p); + if (!p->index_data) + return -1; - /* Verify pack file */ err |= verify_packfile(p, &w_curs); unuse_pack(&w_curs); @@ -57,6 +57,7 @@ struct pack_idx_entry { extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1); extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr); +extern int verify_pack_index(struct packed_git *); extern int verify_pack(struct packed_git *); extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t); extern char *index_pack_lockfile(int fd); @@ -716,7 +716,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (add_again(sb, &c->abbrev_commit_hash)) return 1; strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1, - DEFAULT_ABBREV)); + c->pretty_ctx->abbrev)); c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off; return 1; case 'T': /* tree hash */ @@ -726,7 +726,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (add_again(sb, &c->abbrev_tree_hash)) return 1; strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1, - DEFAULT_ABBREV)); + c->pretty_ctx->abbrev)); c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off; return 1; case 'P': /* parent hashes */ @@ -743,7 +743,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (p != commit->parents) strbuf_addch(sb, ' '); strbuf_addstr(sb, find_unique_abbrev( - p->item->object.sha1, DEFAULT_ABBREV)); + p->item->object.sha1, + c->pretty_ctx->abbrev)); } c->abbrev_parent_hashes.len = sb->len - c->abbrev_parent_hashes.off; @@ -800,6 +801,10 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, case 'e': /* encoding */ strbuf_add(sb, msg + c->encoding.off, c->encoding.len); return 1; + case 'B': /* raw body */ + /* message_off is always left at the initial newline */ + strbuf_addstr(sb, msg + c->message_off + 1); + return 1; } /* Now we need to parse the commit message. */ diff --git a/remote-curl.c b/remote-curl.c index b76bfcb3d..24fbb9a9b 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -9,7 +9,7 @@ #include "sideband.h" static struct remote *remote; -static const char *url; +static const char *url; /* always ends with a trailing slash */ struct options { int verbosity; @@ -101,7 +101,7 @@ static struct discovery* discover_refs(const char *service) return last; free_discovery(last); - strbuf_addf(&buffer, "%s/info/refs", url); + strbuf_addf(&buffer, "%sinfo/refs", url); if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) { is_http = 1; if (!strchr(url, '?')) @@ -120,7 +120,7 @@ static struct discovery* discover_refs(const char *service) strbuf_reset(&buffer); proto_git_candidate = 0; - strbuf_addf(&buffer, "%s/info/refs", url); + strbuf_addf(&buffer, "%sinfo/refs", url); refs_url = strbuf_detach(&buffer, NULL); http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); @@ -132,6 +132,8 @@ static struct discovery* discover_refs(const char *service) case HTTP_MISSING_TARGET: die("%s not found: did you run git update-server-info on the" " server?", refs_url); + case HTTP_NOAUTH: + die("Authentication failed"); default: http_error(refs_url, http_ret); die("HTTP request failed"); @@ -509,7 +511,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) rpc->out = client.out; strbuf_init(&rpc->result, 0); - strbuf_addf(&buf, "%s/%s", url, svc); + strbuf_addf(&buf, "%s%s", url, svc); rpc->service_url = strbuf_detach(&buf, NULL); strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc); @@ -798,11 +800,13 @@ int main(int argc, const char **argv) remote = remote_get(argv[1]); if (argc > 2) { - url = argv[2]; + end_url_with_slash(&buf, argv[2]); } else { - url = remote->url[0]; + end_url_with_slash(&buf, remote->url[0]); } + url = strbuf_detach(&buf, NULL); + http_init(remote); do { diff --git a/run-command.c b/run-command.c index eb5c57562..c7793f50f 100644 --- a/run-command.c +++ b/run-command.c @@ -383,6 +383,8 @@ fail_pipe: close(cmd->out); if (need_err) close_pair(fderr); + else if (cmd->err) + close(cmd->err); errno = failed_errno; return -1; } @@ -323,6 +323,8 @@ const char *setup_git_directory_gently(int *nongit_ok) const char *gitdirenv; const char *gitfile_dir; int len, offset, ceil_offset, root_len; + int current_device = 0, one_filesystem = 1; + struct stat buf; /* * Let's assume that we are in a git repository. @@ -390,6 +392,12 @@ const char *setup_git_directory_gently(int *nongit_ok) * etc. */ offset = len = strlen(cwd); + one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0); + if (one_filesystem) { + if (stat(".", &buf)) + die_errno("failed to stat '.'"); + current_device = buf.st_dev; + } for (;;) { gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); if (gitfile_dir) { @@ -422,8 +430,27 @@ const char *setup_git_directory_gently(int *nongit_ok) } die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT); } - if (chdir("..")) + if (one_filesystem) { + if (stat("..", &buf)) { + cwd[offset] = '\0'; + die_errno("failed to stat '%s/..'", cwd); + } + if (buf.st_dev != current_device) { + if (nongit_ok) { + if (chdir(cwd)) + die_errno("Cannot come back to cwd"); + *nongit_ok = 1; + return NULL; + } + cwd[offset] = '\0'; + die("Not a git repository (or any parent up to mount parent %s)\n" + "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", cwd); + } + } + if (chdir("..")) { + cwd[offset] = '\0'; die_errno("Cannot change to '%s/..'", cwd); + } } inside_git_dir = 0; diff --git a/sha1_file.c b/sha1_file.c index ff6532800..d8e61a65d 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -599,6 +599,14 @@ void unuse_pack(struct pack_window **w_cursor) } } +void close_pack_index(struct packed_git *p) +{ + if (p->index_data) { + munmap((void *)p->index_data, p->index_size); + p->index_data = NULL; + } +} + /* * This is used by git-repack in case a newly created pack happens to * contain the same set of objects as an existing one. In that case @@ -620,8 +628,7 @@ void free_pack_by_name(const char *pack_name) close_pack_windows(p); if (p->pack_fd != -1) close(p->pack_fd); - if (p->index_data) - munmap((void *)p->index_data, p->index_size); + close_pack_index(p); free(p->bad_object_sha1); *pp = p->next; free(p); @@ -831,9 +838,8 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local) return p; } -struct packed_git *parse_pack_index(unsigned char *sha1) +struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path) { - const char *idx_path = sha1_pack_index_name(sha1); const char *path = sha1_pack_name(sha1); struct packed_git *p = alloc_packed_git(strlen(path) + 1); @@ -2448,6 +2454,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, else ret = -1; strbuf_release(&sbuf); + } else if (!size) { + ret = index_mem(sha1, NULL, size, write_object, type, path); } else if (size <= SMALL_FILE_SIZE) { char *buf = xmalloc(size); if (size == read_in_full(fd, buf, size)) @@ -2456,12 +2464,11 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, else ret = error("short read %s", strerror(errno)); free(buf); - } else if (size) { + } else { void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); ret = index_mem(sha1, buf, size, write_object, type, path); munmap(buf, size); - } else - ret = index_mem(sha1, NULL, size, write_object, type, path); + } close(fd); return ret; } @@ -2516,3 +2523,13 @@ int read_pack_header(int fd, struct pack_header *header) return PH_ERROR_PROTOCOL; return 0; } + +void assert_sha1_type(const unsigned char *sha1, enum object_type expect) +{ + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("%s is not a valid object", sha1_to_hex(sha1)); + if (type != expect) + die("%s is not a valid '%s' object", sha1_to_hex(sha1), + typename(expect)); +} diff --git a/shortlog.h b/shortlog.h index bc02cc29e..de4f86fb9 100644 --- a/shortlog.h +++ b/shortlog.h @@ -12,6 +12,7 @@ struct shortlog { int in1; int in2; int user_format; + int abbrev; char *common_repo_prefix; int email; diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index f4ca4fc85..3ec9cbef2 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -73,6 +73,27 @@ then exit 1 fi +clean=no +test_expect_success 'tests clean up after themselves' ' + test_when_finished clean=yes +' + +cleaner=no +test_expect_code 1 'tests clean up even after a failure' ' + test_when_finished cleaner=yes && + (exit 1) +' + +if test $clean$cleaner != yesyes +then + say "bug in test framework: cleanup commands do not work reliably" + exit 1 +fi + +test_expect_code 2 'failure to clean up causes the test to fail' ' + test_when_finished "(exit 2)" +' + ################################################################ # Basics of the basics diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 675773479..7c0a698b9 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -310,4 +310,18 @@ test_expect_success POSIXPERM 'init notices EPERM' ' ) ' +test_expect_success 'init creates a new bare directory with global --bare' ' + rm -rf newdir && + git --bare init newdir && + test -d newdir/refs +' + +test_expect_success 'init prefers command line to GIT_DIR' ' + rm -rf newdir && + mkdir otherdir && + GIT_DIR=otherdir git --bare init newdir && + test -d newdir/refs && + ! test -d otherdir/refs +' + test_done diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index 1c77192eb..53bd7fcc4 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -20,8 +20,12 @@ test_expect_success 'setup' ' mkdir -p a/b/d a/c && ( + echo "[attr]notest !test" echo "f test=f" echo "a/i test=a/i" + echo "onoff test -test" + echo "offon -test test" + echo "no notest" ) >.gitattributes && ( echo "g test=a/g" && @@ -30,6 +34,7 @@ test_expect_success 'setup' ' ( echo "h test=a/b/h" && echo "d/* test=a/b/d/*" + echo "d/yes notest" ) >a/b/.gitattributes ' @@ -44,6 +49,11 @@ test_expect_success 'attribute test' ' attr_check b/g unspecified && attr_check a/b/h a/b/h && attr_check a/b/d/g "a/b/d/*" + attr_check onoff unset + attr_check offon set + attr_check no unspecified + attr_check a/b/d/no "a/b/d/*" + attr_check a/b/d/yes unspecified ' @@ -58,6 +68,11 @@ a/b/g: test: a/b/g b/g: test: unspecified a/b/h: test: a/b/h a/b/d/g: test: a/b/d/* +onoff: test: unset +offon: test: set +no: test: unspecified +a/b/d/no: test: a/b/d/* +a/b/d/yes: test: unspecified EOF sed -e "s/:.*//" < expect | git check-attr --stdin test > actual && diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index f11f98c3c..64f05080b 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -824,4 +824,12 @@ test_expect_success 'check split_cmdline return' " test_must_fail git merge master " +test_expect_success 'git -c "key=value" support' ' + test "z$(git -c name=value config name)" = zvalue && + test "z$(git -c core.name=value config core.name)" = zvalue && + test "z$(git -c CamelCase=value config camelcase)" = zvalue && + test "z$(git -c flag config --bool flag)" = ztrue && + test_must_fail git -c core.name=value config name +' + test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 49cae3ed5..22a80c826 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -57,6 +57,34 @@ test_expect_success 'branch pointing to non-commit' ' git update-ref -d refs/heads/invalid ' +new=nothing +test_expect_success 'email without @ is okay' ' + git cat-file commit HEAD >basis && + sed "s/@/AT/" basis >okay && + new=$(git hash-object -t commit -w --stdin <okay) && + echo "$new" && + git update-ref refs/heads/bogus "$new" && + git fsck 2>out && + cat out && + ! grep "error in commit $new" out +' +git update-ref -d refs/heads/bogus +rm -f ".git/objects/$new" + +new=nothing +test_expect_success 'email with embedded > is not okay' ' + git cat-file commit HEAD >basis && + sed "s/@[a-z]/&>/" basis >bad-email && + new=$(git hash-object -t commit -w --stdin <bad-email) && + echo "$new" && + git update-ref refs/heads/bogus "$new" && + git fsck 2>out && + cat out && + grep "error in commit $new" out +' +git update-ref -d refs/heads/bogus +rm -f ".git/objects/$new" + cat > invalid-tag <<EOF object ffffffffffffffffffffffffffffffffffffffff type commit diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh new file mode 100755 index 000000000..a8297c61b --- /dev/null +++ b/t/t2017-checkout-orphan.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# +# Copyright (c) 2010 Erick Mattos +# + +test_description='git checkout --orphan + +Main Tests for --orphan functionality.' + +. ./test-lib.sh + +TEST_FILE=foo + +test_expect_success 'Setup' ' + echo "Initial" >"$TEST_FILE" && + git add "$TEST_FILE" && + git commit -m "First Commit" + test_tick && + echo "State 1" >>"$TEST_FILE" && + git add "$TEST_FILE" && + test_tick && + git commit -m "Second Commit" +' + +test_expect_success '--orphan creates a new orphan branch from HEAD' ' + git checkout --orphan alpha && + test_must_fail git rev-parse --verify HEAD && + test "refs/heads/alpha" = "$(git symbolic-ref HEAD)" && + test_tick && + git commit -m "Third Commit" && + test_must_fail git rev-parse --verify HEAD^ && + git diff-tree --quiet master alpha +' + +test_expect_success '--orphan creates a new orphan branch from <start_point>' ' + git checkout master && + git checkout --orphan beta master^ && + test_must_fail git rev-parse --verify HEAD && + test "refs/heads/beta" = "$(git symbolic-ref HEAD)" && + test_tick && + git commit -m "Fourth Commit" && + test_must_fail git rev-parse --verify HEAD^ && + git diff-tree --quiet master^ beta +' + +test_expect_success '--orphan must be rejected with -b' ' + git checkout master && + test_must_fail git checkout --orphan new -b newer && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + +test_expect_success '--orphan is rejected with an existing name' ' + git checkout master && + test_must_fail git checkout --orphan master && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + +test_expect_success '--orphan refuses to switch if a merge is needed' ' + git checkout master && + git reset --hard && + echo local >>"$TEST_FILE" && + cat "$TEST_FILE" >"$TEST_FILE.saved" && + test_must_fail git checkout --orphan gamma master^ && + test refs/heads/master = "$(git symbolic-ref HEAD)" && + test_cmp "$TEST_FILE" "$TEST_FILE.saved" && + git diff-index --quiet --cached HEAD && + git reset --hard +' + +test_expect_success '--orphan does not mix well with -t' ' + git checkout master && + test_must_fail git checkout -t master --orphan gamma && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + +test_expect_success '--orphan ignores branch.autosetupmerge' ' + git checkout -f master && + git config branch.autosetupmerge always && + git checkout --orphan delta && + test -z "$(git config branch.delta.merge)" && + test refs/heads/delta = "$(git symbolic-ref HEAD)" && + test_must_fail git rev-parse --verify HEAD^ +' + +test_expect_success '--orphan does not mix well with -l' ' + git checkout -f master && + test_must_fail git checkout -l --orphan gamma +' + +test_done diff --git a/t/t3500-cherry.sh b/t/t3500-cherry.sh index dadbbc2a9..f038f34b7 100755 --- a/t/t3500-cherry.sh +++ b/t/t3500-cherry.sh @@ -17,17 +17,19 @@ test_expect_success \ 'prepare repository with topic branch, and check cherry finds the 2 patches from there' \ 'echo First > A && git update-index --add A && + test_tick && git commit -m "Add A." && git checkout -b my-topic-branch && echo Second > B && git update-index --add B && + test_tick && git commit -m "Add B." && - sleep 2 && echo AnotherSecond > C && git update-index --add C && + test_tick && git commit -m "Add C." && git checkout -f master && @@ -35,6 +37,7 @@ test_expect_success \ echo Third >> A && git update-index A && + test_tick && git commit -m "Modify A." && expr "$(echo $(git cherry master my-topic-branch) )" : "+ [^ ]* + .*" diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index d7e327cc5..e12fbea1b 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -54,7 +54,7 @@ EOF test_expect_success \ 'diff removed symlink' \ - 'rm frotz && + 'mv frotz frotz2 && git diff-index -M -p $tree > current && compare_diff_patch current expected' @@ -64,8 +64,7 @@ EOF test_expect_success \ 'diff identical, but newly created symlink' \ - 'sleep 3 && - ln -s xyzzy frotz && + 'ln -s xyzzy frotz && git diff-index -M -p $tree > current && compare_diff_patch current expected' diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 90f334237..e92eab09c 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -352,6 +352,48 @@ test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab: ' +test_expect_success 'check tabs as indentation (tab-in-indent: off)' ' + + git config core.whitespace "-tab-in-indent" && + echo " foo ();" > x && + git diff --check + +' + +test_expect_success 'check tabs as indentation (tab-in-indent: on)' ' + + git config core.whitespace "tab-in-indent" && + echo " foo ();" > x && + test_must_fail git diff --check + +' + +test_expect_success 'check tabs and spaces as indentation (tab-in-indent: on)' ' + + git config core.whitespace "tab-in-indent" && + echo " foo ();" > x && + test_must_fail git diff --check + +' + +test_expect_success 'check tab-in-indent and indent-with-non-tab conflict' ' + + git config core.whitespace "tab-in-indent,indent-with-non-tab" && + echo "foo ();" > x && + test_must_fail git diff --check + +' + +test_expect_success 'check tab-in-indent excluded from wildcard whitespace attribute' ' + + git config --unset core.whitespace && + echo "x whitespace" > .gitattributes && + echo " foo ();" > x && + git diff --check && + rm -f .gitattributes + +' + test_expect_success 'line numbers in --check output are correct' ' echo "" > x && diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 2e2e103b3..6f7548c3a 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -55,6 +55,93 @@ test_expect_success 'word diff with runs of whitespace' ' ' +test_expect_success '--word-diff=color' ' + + word_diff --word-diff=color + +' + +test_expect_success '--color --word-diff=color' ' + + word_diff --color --word-diff=color + +' + +sed 's/#.*$//' > expect <<EOF +diff --git a/pre b/post +index 330b04f..5ed8eff 100644 +--- a/pre ++++ b/post +@@ -1,3 +1,7 @@ +-h(4) ++h(4),hh[44] +~ + # significant space +~ + a = b + c +~ +~ ++aa = a +~ +~ ++aeff = aeff * ( aaa ) +~ +EOF + +test_expect_success '--word-diff=porcelain' ' + + word_diff --word-diff=porcelain + +' + +cat > expect <<EOF +diff --git a/pre b/post +index 330b04f..5ed8eff 100644 +--- a/pre ++++ b/post +@@ -1,3 +1,7 @@ +[-h(4)-]{+h(4),hh[44]+} + +a = b + c + +{+aa = a+} + +{+aeff = aeff * ( aaa )+} +EOF + +test_expect_success '--word-diff=plain' ' + + word_diff --word-diff=plain + +' + +test_expect_success '--word-diff=plain --no-color' ' + + word_diff --word-diff=plain --no-color + +' + +cat > expect <<EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index 330b04f..5ed8eff 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> +<CYAN>@@ -1,3 +1,7 @@<RESET> +<RED>[-h(4)-]<RESET><GREEN>{+h(4),hh[44]+}<RESET> + +a = b + c<RESET> + +<GREEN>{+aa = a+}<RESET> + +<GREEN>{+aeff = aeff * ( aaa )+}<RESET> +EOF + +test_expect_success '--word-diff=plain --color' ' + + word_diff --word-diff=plain --color + +' + cat > expect <<\EOF <WHITE>diff --git a/pre b/post<RESET> <WHITE>index 330b04f..5ed8eff 100644<RESET> @@ -143,6 +230,25 @@ test_expect_success 'command-line overrides config' ' word_diff --color-words="[a-z]+" ' +cat > expect <<\EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index 330b04f..5ed8eff 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> +<CYAN>@@ -1,3 +1,7 @@<RESET> +h(4),<GREEN>{+hh+}<RESET>[44] + +a = b + c<RESET> + +<GREEN>{+aa = a+}<RESET> + +<GREEN>{+aeff = aeff * ( aaa+}<RESET> ) +EOF + +test_expect_success 'command-line overrides config: --word-diff-regex' ' + word_diff --color --word-diff-regex="[a-z]+" +' + cp expect.non-whitespace-is-word expect test_expect_success '.gitattributes override config' ' @@ -209,4 +315,20 @@ test_expect_success 'test when words are only removed at the end' ' ' +cat > expect <<\EOF +diff --git a/pre b/post +index 289cb9d..2d06f37 100644 +--- a/pre ++++ b/post +@@ -1 +1 @@ +-(: ++( +EOF + +test_expect_success '--word-diff=none' ' + + word_diff --word-diff=plain --word-diff=none + +' + test_done diff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh new file mode 100755 index 000000000..91f8198f0 --- /dev/null +++ b/t/t4042-diff-textconv-caching.sh @@ -0,0 +1,109 @@ +#!/bin/sh + +test_description='test textconv caching' +. ./test-lib.sh + +cat >helper <<'EOF' +#!/bin/sh +sed 's/^/converted: /' "$@" >helper.out +cat helper.out +EOF +chmod +x helper + +test_expect_success 'setup' ' + echo foo content 1 >foo.bin && + echo bar content 1 >bar.bin && + git add . && + git commit -m one && + echo foo content 2 >foo.bin && + echo bar content 2 >bar.bin && + git commit -a -m two && + echo "*.bin diff=magic" >.gitattributes && + git config diff.magic.textconv ./helper && + git config diff.magic.cachetextconv true +' + +cat >expect <<EOF +diff --git a/bar.bin b/bar.bin +index fcf9166..28283d5 100644 +--- a/bar.bin ++++ b/bar.bin +@@ -1 +1 @@ +-converted: bar content 1 ++converted: bar content 2 +diff --git a/foo.bin b/foo.bin +index d5b9fe3..1345db2 100644 +--- a/foo.bin ++++ b/foo.bin +@@ -1 +1 @@ +-converted: foo content 1 ++converted: foo content 2 +EOF + +test_expect_success 'first textconv works' ' + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'cached textconv produces same output' ' + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'cached textconv does not run helper' ' + rm -f helper.out && + git diff HEAD^ HEAD >actual && + test_cmp expect actual && + ! test -r helper.out +' + +cat >expect <<EOF +diff --git a/bar.bin b/bar.bin +index fcf9166..28283d5 100644 +--- a/bar.bin ++++ b/bar.bin +@@ -1,2 +1,2 @@ + converted: other +-converted: bar content 1 ++converted: bar content 2 +diff --git a/foo.bin b/foo.bin +index d5b9fe3..1345db2 100644 +--- a/foo.bin ++++ b/foo.bin +@@ -1,2 +1,2 @@ + converted: other +-converted: foo content 1 ++converted: foo content 2 +EOF +test_expect_success 'changing textconv invalidates cache' ' + echo other >other && + git config diff.magic.textconv "./helper other" && + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +cat >expect <<EOF +diff --git a/bar.bin b/bar.bin +index fcf9166..28283d5 100644 +--- a/bar.bin ++++ b/bar.bin +@@ -1,2 +1,2 @@ + converted: other +-converted: bar content 1 ++converted: bar content 2 +diff --git a/foo.bin b/foo.bin +index d5b9fe3..1345db2 100644 +--- a/foo.bin ++++ b/foo.bin +@@ -1 +1 @@ +-converted: foo content 1 ++converted: foo content 2 +EOF +test_expect_success 'switching diff driver produces correct results' ' + git config diff.moremagic.textconv ./helper && + echo foo.bin diff=moremagic >>.gitattributes && + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index fb9ad247b..d0af697aa 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -11,21 +11,22 @@ prepare_test_file () { # ! trailing-space # @ space-before-tab # # indent-with-non-tab + # % tab-in-indent sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF An_SP in an ordinary line>and a HT. - >A HT. - _>A SP and a HT (@). - _>_A SP, a HT and a SP (@). + >A HT (%). + _>A SP and a HT (@%). + _>_A SP, a HT and a SP (@%). _______Seven SP. ________Eight SP (#). - _______>Seven SP and a HT (@). - ________>Eight SP and a HT (@#). - _______>_Seven SP, a HT and a SP (@). - ________>_Eight SP, a HT and a SP (@#). + _______>Seven SP and a HT (@%). + ________>Eight SP and a HT (@#%). + _______>_Seven SP, a HT and a SP (@%). + ________>_Eight SP, a HT and a SP (@#%). _______________Fifteen SP (#). - _______________>Fifteen SP and a HT (@#). + _______________>Fifteen SP and a HT (@#%). ________________Sixteen SP (#). - ________________>Sixteen SP and a HT (@#). + ________________>Sixteen SP and a HT (@#%). _____a__Five SP, a non WS, two SP. A line with a (!) trailing SP_ A line with a (!) trailing HT> @@ -39,7 +40,6 @@ apply_patch () { } test_fix () { - # fix should not barf apply_patch --whitespace=fix || return 1 @@ -130,20 +130,25 @@ do for i in - '' do case "$i" in '') ti='#' ;; *) ti= ;; esac - rule=${t}trailing,${s}space,${i}indent - - rm -f .gitattributes - test_expect_success "rule=$rule" ' - git config core.whitespace "$rule" && - test_fix "$tt$ts$ti" - ' - - test_expect_success "rule=$rule (attributes)" ' - git config --unset core.whitespace && - echo "target whitespace=$rule" >.gitattributes && - test_fix "$tt$ts$ti" - ' - + for h in - '' + do + [ -z "$h$i" ] && continue + case "$h" in '') th='%' ;; *) th= ;; esac + rule=${t}trailing,${s}space,${i}indent,${h}tab + + rm -f .gitattributes + test_expect_success "rule=$rule" ' + git config core.whitespace "$rule" && + test_fix "$tt$ts$ti$th" + ' + + test_expect_success "rule=$rule (attributes)" ' + git config --unset core.whitespace && + echo "target whitespace=$rule" >.gitattributes && + test_fix "$tt$ts$ti$th" + ' + + done done done done @@ -325,6 +330,18 @@ test_expect_success 'two missing blank lines at end with --whitespace=fix' ' test_cmp one expect ' +test_expect_success 'missing blank line at end, insert before end, --whitespace=fix' ' + { echo a; echo; } >one && + git add one && + { echo b; echo a; echo; } >one && + cp one expect && + git diff -- one >patch && + echo a >one && + test_must_fail git apply patch && + git apply --whitespace=fix patch && + test_cmp one expect +' + test_expect_success 'shrink file with tons of missing blanks at end of file' ' { echo a; echo b; echo c; } >one && cp one no-blank-lines && diff --git a/t/t4134-apply-submodule.sh b/t/t4134-apply-submodule.sh new file mode 100755 index 000000000..1b82f93cf --- /dev/null +++ b/t/t4134-apply-submodule.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Copyright (c) 2010 Peter Collingbourne +# + +test_description='git apply submodule tests' + +. ./test-lib.sh + +test_expect_success setup ' + cat > create-sm.patch <<EOF +diff --git a/dir/sm b/dir/sm +new file mode 160000 +index 0000000..0123456 +--- /dev/null ++++ b/dir/sm +@@ -0,0 +1 @@ ++Subproject commit 0123456789abcdef0123456789abcdef01234567 +EOF + cat > remove-sm.patch <<EOF +diff --git a/dir/sm b/dir/sm +deleted file mode 160000 +index 0123456..0000000 +--- a/dir/sm ++++ /dev/null +@@ -1 +0,0 @@ +-Subproject commit 0123456789abcdef0123456789abcdef01234567 +EOF +' + +test_expect_success 'removing a submodule also removes all leading subdirectories' ' + git apply --index create-sm.patch && + test -d dir/sm && + git apply --index remove-sm.patch && + test \! -d dir +' + +test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index a01e55bf6..cdb70b4b3 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -8,30 +8,93 @@ test_description='git shortlog . ./test-lib.sh -echo 1 > a1 -git add a1 -tree=$(git write-tree) -commit=$( (echo "Test"; echo) | git commit-tree $tree ) -git update-ref HEAD $commit +test_expect_success 'setup' ' + echo 1 >a1 && + git add a1 && + tree=$(git write-tree) && + commit=$(printf "%s\n" "Test" "" | git commit-tree "$tree") && + git update-ref HEAD "$commit" && + + echo 2 >a1 && + git commit --quiet -m "This is a very, very long first line for the commit message to see if it is wrapped correctly" a1 && + + # test if the wrapping is still valid + # when replacing all is by treble clefs. + echo 3 >a1 && + git commit --quiet -m "$( + echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | + sed "s/i/1234/g" | + tr 1234 "\360\235\204\236")" a1 && + + # now fsck up the utf8 + git config i18n.commitencoding non-utf-8 && + echo 4 >a1 && + git commit --quiet -m "$( + echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | + sed "s/i/1234/g" | + tr 1234 "\370\235\204\236")" a1 && + + echo 5 >a1 && + git commit --quiet -m "a 12 34 56 78" a1 + + echo 6 >a1 && + git commit --quiet -m "Commit by someone else" \ + --author="Someone else <not!me>" a1 && + + cat >expect.template <<-\EOF + A U Thor (5): + SUBJECT + SUBJECT + SUBJECT + SUBJECT + SUBJECT + + Someone else (1): + SUBJECT + + EOF +' -echo 2 > a1 -git commit --quiet -m "This is a very, very long first line for the commit message to see if it is wrapped correctly" a1 +fuzz() { + file=$1 && + sed " + s/$_x40/OBJECT_NAME/g + s/$_x05/OBJID/g + s/^ \{6\}[CTa].*/ SUBJECT/g + s/^ \{8\}[^ ].*/ CONTINUATION/g + " <"$file" >"$file.fuzzy" && + sed "/CONTINUATION/ d" <"$file.fuzzy" +} -# test if the wrapping is still valid when replacing all i's by treble clefs. -echo 3 > a1 -git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\360\235\204\236')" a1 +test_expect_success 'default output format' ' + git shortlog HEAD >log && + fuzz log >log.predictable && + test_cmp expect.template log.predictable +' -# now fsck up the utf8 -git config i18n.commitencoding non-utf-8 -echo 4 > a1 -git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\370\235\204\236')" a1 +test_expect_success 'pretty format' ' + sed s/SUBJECT/OBJECT_NAME/ expect.template >expect && + git shortlog --format="%H" HEAD >log && + fuzz log >log.predictable && + test_cmp expect log.predictable +' -echo 5 > a1 -git commit --quiet -m "a 12 34 56 78" a1 +test_expect_success '--abbrev' ' + sed s/SUBJECT/OBJID/ expect.template >expect && + git shortlog --format="%h" --abbrev=5 HEAD >log && + fuzz log >log.predictable && + test_cmp expect log.predictable +' -git shortlog -w HEAD > out +test_expect_success 'output from user-defined format is re-wrapped' ' + sed "s/SUBJECT/two lines/" expect.template >expect && + git shortlog --format="two%nlines" HEAD >log && + fuzz log >log.predictable && + test_cmp expect log.predictable +' -cat > expect << EOF +test_expect_success 'shortlog wrapping' ' + cat >expect <<\EOF && A U Thor (5): Test This is a very, very long first line for the commit message to see if @@ -43,14 +106,19 @@ A U Thor (5): a 12 34 56 78 -EOF - -test_expect_success 'shortlog wrapping' 'test_cmp expect out' +Someone else (1): + Commit by someone else -git log HEAD > log -GIT_DIR=non-existing git shortlog -w < log > out +EOF + git shortlog -w HEAD >out && + test_cmp expect out +' -test_expect_success 'shortlog from non-git directory' 'test_cmp expect out' +test_expect_success 'shortlog from non-git directory' ' + git log HEAD >log && + GIT_DIR=non-existing git shortlog -w <log >out && + test_cmp expect out +' iconvfromutf8toiso88591() { printf "%s" "$*" | iconv -f UTF-8 -t ISO8859-1 diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 1dc224f6f..2230e606e 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -387,5 +387,54 @@ test_expect_success 'log --graph with merge' ' test_cmp expect actual ' +test_expect_success 'log.decorate configuration' ' + git config --unset-all log.decorate || : + + git log --oneline >expect.none && + git log --oneline --decorate >expect.short && + git log --oneline --decorate=full >expect.full && + + echo "[log] decorate" >>.git/config && + git log --oneline >actual && + test_cmp expect.short actual && + + git config --unset-all log.decorate && + git config log.decorate true && + git log --oneline >actual && + test_cmp expect.short actual && + git log --oneline --decorate=full >actual && + test_cmp expect.full actual && + git log --oneline --decorate=no >actual && + test_cmp expect.none actual && + + git config --unset-all log.decorate && + git config log.decorate no && + git log --oneline >actual && + test_cmp expect.none actual && + git log --oneline --decorate >actual && + test_cmp expect.short actual && + git log --oneline --decorate=full >actual && + test_cmp expect.full actual && + + git config --unset-all log.decorate && + git config log.decorate short && + git log --oneline >actual && + test_cmp expect.short actual && + git log --oneline --no-decorate >actual && + test_cmp expect.none actual && + git log --oneline --decorate=full >actual && + test_cmp expect.full actual && + + git config --unset-all log.decorate && + git config log.decorate full && + git log --oneline >actual && + test_cmp expect.full actual && + git log --oneline --no-decorate >actual && + test_cmp expect.none actual && + git log --oneline --decorate >actual && + test_cmp expect.short actual + +' + test_done diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh new file mode 100755 index 000000000..169d3ea37 --- /dev/null +++ b/t/t5150-request-pull.sh @@ -0,0 +1,228 @@ +#!/bin/sh + +test_description='Test workflows involving pull request.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + + git init --bare upstream.git && + git init --bare downstream.git && + git clone upstream.git upstream-private && + git clone downstream.git local && + + trash_url="file://$TRASH_DIRECTORY" && + downstream_url="$trash_url/downstream.git/" && + upstream_url="$trash_url/upstream.git/" && + + ( + cd upstream-private && + cat <<-\EOT >mnemonic.txt && + Thirtey days hath November, + Aprile, June, and September: + EOT + git add mnemonic.txt && + test_tick && + git commit -m "\"Thirty days\", a reminder of month lengths" && + git tag -m "version 1" -a initial && + git push --tags origin master + ) && + ( + cd local && + git remote add upstream "$trash_url/upstream.git" && + git fetch upstream && + git pull upstream master && + cat <<-\EOT >>mnemonic.txt && + Of twyecescore-eightt is but eine, + And all the remnante be thrycescore-eine. + O’course Leap yare comes an’pynes, + Ev’rie foure yares, gote it ryghth. + An’twyecescore-eight is but twyecescore-nyne. + EOT + git add mnemonic.txt && + test_tick && + git commit -m "More detail" && + git tag -m "version 2" -a full && + git checkout -b simplify HEAD^ && + mv mnemonic.txt mnemonic.standard && + cat <<-\EOT >mnemonic.clarified && + Thirty days has September, + All the rest I can’t remember. + EOT + git add -N mnemonic.standard mnemonic.clarified && + git commit -a -m "Adapt to use modern, simpler English + +But keep the old version, too, in case some people prefer it." && + git checkout master + ) + +' + +test_expect_success 'setup: two scripts for reading pull requests' ' + + downstream_url_for_sed=$( + printf "%s\n" "$downstream_url" | + sed -e '\''s/\\/\\\\/g'\'' -e '\''s/[[/.*^$]/\\&/g'\'' + ) && + + cat <<-\EOT >read-request.sed && + #!/bin/sed -nf + / in the git repository at:$/! d + n + /^$/ n + s/^[ ]*\(.*\) \([^ ]*\)/please pull\ + \1\ + \2/p + q + EOT + + cat <<-EOT >fuzz.sed + #!/bin/sed -nf + s/$_x40/OBJECT_NAME/g + s/A U Thor/AUTHOR/g + s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g + s/ [^ ].*/ SUBJECT/g + s/ [^ ].* (DATE)/ SUBJECT (DATE)/g + s/$downstream_url_for_sed/URL/g + s/for-upstream/BRANCH/g + s/mnemonic.txt/FILENAME/g + /^ FILENAME | *[0-9]* [-+]*\$/ b diffstat + /^AUTHOR ([0-9]*):\$/ b shortlog + p + b + : diffstat + n + / [0-9]* files changed/ { + a\\ + DIFFSTAT + b + } + b diffstat + : shortlog + /^ [a-zA-Z]/ n + /^[a-zA-Z]* ([0-9]*):\$/ n + /^\$/ N + /^\n[a-zA-Z]* ([0-9]*):\$/! { + a\\ + SHORTLOG + D + } + n + b shortlog + EOT + +' + +test_expect_success 'pull request when forgot to push' ' + + rm -fr downstream.git && + git init --bare downstream.git && + ( + cd local && + git checkout initial && + git merge --ff-only master && + test_must_fail git request-pull initial "$downstream_url" \ + 2>../err + ) && + grep "No branch of.*is at:\$" err && + grep "Are you sure you pushed" err + +' + +test_expect_success 'pull request after push' ' + + rm -fr downstream.git && + git init --bare downstream.git && + ( + cd local && + git checkout initial && + git merge --ff-only master && + git push origin master:for-upstream && + git request-pull initial origin >../request + ) && + sed -nf read-request.sed <request >digest && + cat digest && + { + read task && + read repository && + read branch + } <digest && + ( + cd upstream-private && + git checkout initial && + git pull --ff-only "$repository" "$branch" + ) && + test "$branch" = for-upstream && + test_cmp local/mnemonic.txt upstream-private/mnemonic.txt + +' + +test_expect_success 'request names an appropriate branch' ' + + rm -fr downstream.git && + git init --bare downstream.git && + ( + cd local && + git checkout initial && + git merge --ff-only master && + git push --tags origin master simplify && + git push origin master:for-upstream && + git request-pull initial "$downstream_url" >../request + ) && + sed -nf read-request.sed <request >digest && + cat digest && + { + read task && + read repository && + read branch + } <digest && + { + test "$branch" = master || + test "$branch" = for-upstream + } + +' + +test_expect_success 'pull request format' ' + + rm -fr downstream.git && + git init --bare downstream.git && + cat <<-\EOT >expect && + The following changes since commit OBJECT_NAME: + + SUBJECT (DATE) + + are available in the git repository at: + URL BRANCH + + SHORTLOG + + DIFFSTAT + EOT + ( + cd local && + git checkout initial && + git merge --ff-only master && + git push origin master:for-upstream && + git request-pull initial "$downstream_url" >../request + ) && + <request sed -nf fuzz.sed >request.fuzzy && + test_cmp expect request.fuzzy + +' + +test_expect_success 'request-pull ignores OPTIONS_KEEPDASHDASH poison' ' + + ( + cd local && + OPTIONS_KEEPDASHDASH=Yes && + export OPTIONS_KEEPDASHDASH && + git checkout initial && + git merge --ff-only master && + git push origin master:for-upstream && + git request-pull -- initial "$downstream_url" >../request + ) + +' + +test_done diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 1dd8eed5b..3cf1b3da4 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -49,4 +49,62 @@ test_expect_success 'ls-remote self' ' ' +test_expect_success 'dies when no remote specified and no default remotes found' ' + + test_must_fail git ls-remote + +' + +test_expect_success 'use "origin" when no remote specified' ' + + git remote add origin "$(pwd)/.git" && + git ls-remote >actual && + test_cmp expected.all actual + +' + +test_expect_success 'use branch.<name>.remote if possible' ' + + # + # Test that we are indeed using branch.<name>.remote, not "origin", even + # though the "origin" remote has been set. + # + + # setup a new remote to differentiate from "origin" + git clone . other.git && + ( + cd other.git && + echo "$(git rev-parse HEAD) HEAD" + git show-ref | sed -e "s/ / /" + ) >exp && + + git remote add other other.git && + git config branch.master.remote other && + + git ls-remote >actual && + test_cmp exp actual + +' + +cat >exp <<EOF +fatal: 'refs*master' does not appear to be a git repository +fatal: The remote end hung up unexpectedly +EOF +test_expect_success 'confuses pattern as remote when no remote specified' ' + # + # Do not expect "git ls-remote <pattern>" to work; ls-remote, correctly, + # confuses <pattern> for <remote>. Although ugly, this behaviour is akin + # to the confusion of refspecs for remotes by git-fetch and git-push, + # eg: + # + # $ git fetch branch + # + + # We could just as easily have used "master"; the "*" emphasizes its + # role as a pattern. + test_must_fail git ls-remote refs*master >actual 2>&1 && + test_cmp exp actual + +' + test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2de98e656..b11da79c9 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -64,13 +64,13 @@ check_push_result () { test_expect_success setup ' - : >path1 && + >path1 && git add path1 && test_tick && git commit -a -m repo && the_first_commit=$(git show-ref -s --verify refs/heads/master) && - : >path2 && + >path2 && git add path2 && test_tick && git commit -a -m second && @@ -483,8 +483,10 @@ git config --remove-section remote.there test_expect_success 'push with dry-run' ' mk_test heads/master && - (cd testrepo && - old_commit=$(git show-ref -s --verify refs/heads/master)) && + ( + cd testrepo && + old_commit=$(git show-ref -s --verify refs/heads/master) + ) && git push --dry-run testrepo && check_push_result $old_commit heads/master ' @@ -493,10 +495,13 @@ test_expect_success 'push updates local refs' ' mk_test heads/master && mk_child child && - (cd child && + ( + cd child && git pull .. master && git push && - test $(git rev-parse master) = $(git rev-parse remotes/origin/master)) + test $(git rev-parse master) = \ + $(git rev-parse remotes/origin/master) + ) ' @@ -506,10 +511,13 @@ test_expect_success 'push updates up-to-date local refs' ' mk_child child1 && mk_child child2 && (cd child1 && git pull .. master && git push) && - (cd child2 && + ( + cd child2 && git pull ../child1 master && git push && - test $(git rev-parse master) = $(git rev-parse remotes/origin/master)) + test $(git rev-parse master) = \ + $(git rev-parse remotes/origin/master) + ) ' @@ -517,9 +525,11 @@ test_expect_success 'push preserves up-to-date packed refs' ' mk_test heads/master && mk_child child && - (cd child && + ( + cd child && git push && - ! test -f .git/refs/remotes/origin/master) + ! test -f .git/refs/remotes/origin/master + ) ' @@ -528,13 +538,15 @@ test_expect_success 'push does not update local refs on failure' ' mk_test heads/master && mk_child child && mkdir testrepo/.git/hooks && - echo exit 1 >testrepo/.git/hooks/pre-receive && + echo "#!/no/frobnication/today" >testrepo/.git/hooks/pre-receive && chmod +x testrepo/.git/hooks/pre-receive && - (cd child && + ( + cd child && git pull .. master test_must_fail git push && test $(git rev-parse master) != \ - $(git rev-parse remotes/origin/master)) + $(git rev-parse remotes/origin/master) + ) ' @@ -575,34 +587,41 @@ test_expect_success 'push --delete refuses src:dest refspecs' ' test_expect_success 'warn on push to HEAD of non-bare repository' ' mk_test heads/master - (cd testrepo && + ( + cd testrepo && git checkout master && - git config receive.denyCurrentBranch warn) && + git config receive.denyCurrentBranch warn + ) && git push testrepo master 2>stderr && grep "warning: updating the current branch" stderr ' test_expect_success 'deny push to HEAD of non-bare repository' ' mk_test heads/master - (cd testrepo && + ( + cd testrepo && git checkout master && - git config receive.denyCurrentBranch true) && + git config receive.denyCurrentBranch true + ) && test_must_fail git push testrepo master ' test_expect_success 'allow push to HEAD of bare repository (bare)' ' mk_test heads/master - (cd testrepo && + ( + cd testrepo && git checkout master && git config receive.denyCurrentBranch true && - git config core.bare true) && + git config core.bare true + ) && git push testrepo master 2>stderr && ! grep "warning: updating the current branch" stderr ' test_expect_success 'allow push to HEAD of non-bare repository (config)' ' mk_test heads/master - (cd testrepo && + ( + cd testrepo && git checkout master && git config receive.denyCurrentBranch false ) && @@ -615,7 +634,8 @@ test_expect_success 'fetch with branches' ' git branch second $the_first_commit && git checkout second && echo ".." > testrepo/.git/branches/branch1 && - (cd testrepo && + ( + cd testrepo && git fetch branch1 && r=$(git show-ref -s --verify refs/heads/branch1) && test "z$r" = "z$the_commit" && @@ -627,7 +647,8 @@ test_expect_success 'fetch with branches' ' test_expect_success 'fetch with branches containing #' ' mk_empty && echo "..#second" > testrepo/.git/branches/branch2 && - (cd testrepo && + ( + cd testrepo && git fetch branch2 && r=$(git show-ref -s --verify refs/heads/branch2) && test "z$r" = "z$the_first_commit" && @@ -641,7 +662,8 @@ test_expect_success 'push with branches' ' git checkout second && echo "testrepo" > .git/branches/branch1 && git push branch1 && - (cd testrepo && + ( + cd testrepo && r=$(git show-ref -s --verify refs/heads/master) && test "z$r" = "z$the_first_commit" && test 1 = $(git for-each-ref refs/heads | wc -l) @@ -652,7 +674,8 @@ test_expect_success 'push with branches containing #' ' mk_empty && echo "testrepo#branch3" > .git/branches/branch2 && git push branch2 && - (cd testrepo && + ( + cd testrepo && r=$(git show-ref -s --verify refs/heads/branch3) && test "z$r" = "z$the_first_commit" && test 1 = $(git for-each-ref refs/heads | wc -l) @@ -660,6 +683,55 @@ test_expect_success 'push with branches containing #' ' git checkout master ' +test_expect_success 'push into aliased refs (consistent)' ' + mk_test heads/master && + mk_child child1 && + mk_child child2 && + ( + cd child1 && + git branch foo && + git symbolic-ref refs/heads/bar refs/heads/foo + git config receive.denyCurrentBranch false + ) && + ( + cd child2 && + >path2 && + git add path2 && + test_tick && + git commit -a -m child2 && + git branch foo && + git branch bar && + git push ../child1 foo bar + ) +' + +test_expect_success 'push into aliased refs (inconsistent)' ' + mk_test heads/master && + mk_child child1 && + mk_child child2 && + ( + cd child1 && + git branch foo && + git symbolic-ref refs/heads/bar refs/heads/foo + git config receive.denyCurrentBranch false + ) && + ( + cd child2 && + >path2 && + git add path2 && + test_tick && + git commit -a -m child2 && + git branch foo && + >path3 && + git add path3 && + test_tick && + git commit -a -m child2 && + git branch bar && + test_must_fail git push ../child1 foo bar 2>stderr && + grep "refusing inconsistent update" stderr + ) +' + test_expect_success 'push --porcelain' ' mk_empty && echo >.git/foo "To testrepo" && diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index 795dc2bcd..17e1bdc5a 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -34,8 +34,34 @@ test_expect_success 'setup remote repository' ' mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH" ' -test_expect_success 'clone remote repository' ' +cat >exp <<EOF +GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 +POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200 +EOF +test_expect_success 'no empty path components' ' + # In the URL, add a trailing slash, and see if git appends yet another + # slash. cd "$ROOT_PATH" && + git clone $HTTPD_URL/smart/test_repo.git/ test_repo_clone && + + sed -e " + s/^.* \"// + s/\"// + s/ [1-9][0-9]*\$// + s/^GET /GET / + " >act <"$HTTPD_ROOT_PATH"/access.log && + + # Clear the log, so that it does not affect the "used receive-pack + # service" test which reads the log too. + # + # We do this before the actual comparison to ensure the log is cleared. + echo > "$HTTPD_ROOT_PATH"/access.log && + + test_cmp exp act +' + +test_expect_success 'clone remote repository' ' + rm -rf test_repo_clone && git clone $HTTPD_URL/smart/test_repo.git test_repo_clone ' @@ -68,6 +94,7 @@ test_expect_success 'create and delete remote branch' ' ' cat >exp <<EOF + GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200 GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200 diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 8cfce969b..fc675b50a 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -55,12 +55,43 @@ test_expect_success 'http remote detects correct HEAD' ' test_expect_success 'fetch packed objects' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && - cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && - git --bare repack && - git --bare prune-packed && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && + git --bare repack && + git --bare prune-packed + ) && git clone $HTTPD_URL/dumb/repo_pack.git ' +test_expect_success 'fetch notices corrupt pack' ' + cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && + p=`ls objects/pack/pack-*.pack` && + chmod u+w $p && + printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc + ) && + mkdir repo_bad1.git && + (cd repo_bad1.git && + git --bare init && + test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad1.git && + test 0 = `ls objects/pack/pack-*.pack | wc -l` + ) +' + +test_expect_success 'fetch notices corrupt idx' ' + cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git && + p=`ls objects/pack/pack-*.idx` && + chmod u+w $p && + printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc + ) && + mkdir repo_bad2.git && + (cd repo_bad2.git && + git --bare init && + test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad2.git && + test 0 = `ls objects/pack | wc -l` + ) +' + test_expect_success 'did not use upload-pack service' ' grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act : >exp diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 214756731..678cee502 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -34,7 +34,7 @@ test_expect_success 'clone with excess parameters (2)' ' test_expect_success 'output from clone' ' rm -fr dst && git clone -n "file://$(pwd)/src" dst >output && - test $(grep Initialized output | wc -l) = 1 + test $(grep Clon output | wc -l) = 1 ' test_expect_success 'clone does not keep pack' ' diff --git a/t/t5705-clone-2gb.sh b/t/t5705-clone-2gb.sh index adfaae8c5..8afbdd4de 100755 --- a/t/t5705-clone-2gb.sh +++ b/t/t5705-clone-2gb.sh @@ -12,7 +12,7 @@ test_expect_success 'setup' ' git config pack.compression 0 && git config pack.depth 0 && - blobsize=$((20*1024*1024)) && + blobsize=$((100*1024*1024)) && blobcount=$((2*1024*1024*1024/$blobsize+1)) && i=1 && (while test $i -le $blobcount @@ -36,9 +36,15 @@ test_expect_success 'setup' ' ' -test_expect_success 'clone' ' +test_expect_success 'clone - bare' ' - git clone --bare --no-hardlinks . clone + git clone --bare --no-hardlinks . clone-bare + +' + +test_expect_success 'clone - with worktree, file:// protocol' ' + + git clone file://. clone-wt ' diff --git a/t/t5800-remote-helpers.sh b/t/t5800-remote-helpers.sh new file mode 100755 index 000000000..75a0163c0 --- /dev/null +++ b/t/t5800-remote-helpers.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# +# Copyright (c) 2010 Sverre Rabbelier +# + +test_description='Test remote-helper import and export commands' + +. ./test-lib.sh + +if ! test_have_prereq PYTHON +then + say 'skipping git remote-testgit tests: requires Python support' + test_done +fi + +test_expect_success 'setup repository' ' + git init --bare server/.git && + git clone server public && + (cd public && + echo content >file && + git add file && + git commit -m one && + git push origin master) +' + +test_expect_success 'cloning from local repo' ' + git clone "testgit::${PWD}/server" localclone && + test_cmp public/file localclone/file +' + +test_expect_success 'cloning from remote repo' ' + git clone "testgit::file://${PWD}/server" clone && + test_cmp public/file clone/file +' + +test_expect_success 'create new commit on remote' ' + (cd public && + echo content >>file && + git commit -a -m two && + git push) +' + +test_expect_success 'pulling from local repo' ' + (cd localclone && git pull) && + test_cmp public/file localclone/file +' + +test_expect_success 'pulling from remote remote' ' + (cd clone && git pull) && + test_cmp public/file clone/file +' + +test_expect_success 'pushing to local repo' ' + (cd localclone && + echo content >>file && + git commit -a -m three && + git push) && + HEAD=$(git --git-dir=localclone/.git rev-parse --verify HEAD) && + test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD) +' + +test_expect_success 'synch with changes from localclone' ' + (cd clone && + git pull) +' + +test_expect_success 'pushing remote local repo' ' + (cd clone && + echo content >>file && + git commit -a -m four && + git push) && + HEAD=$(git --git-dir=clone/.git rev-parse --verify HEAD) && + test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD) +' + +test_done diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index a49b7c572..9b77073df 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -101,6 +101,15 @@ commit 131a310eb913d107dd3c09a65d1651175898735d commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 EOF +test_format raw-body %B <<'EOF' +commit 131a310eb913d107dd3c09a65d1651175898735d +changed foo + +commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +added foo + +EOF + test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<'EOF' commit 131a310eb913d107dd3c09a65d1651175898735d [31mfoo[32mbar[34mbaz[mxyzzy @@ -191,6 +200,31 @@ test_expect_success 'add LF before non-empty (2)' ' grep "^$" actual ' +test_expect_success '--abbrev' ' + echo SHORT SHORT SHORT >expect2 && + echo LONG LONG LONG >expect3 && + git log -1 --format="%h %h %h" HEAD >actual1 && + git log -1 --abbrev=5 --format="%h %h %h" HEAD >actual2 && + git log -1 --abbrev=5 --format="%H %H %H" HEAD >actual3 && + sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual2 >fuzzy2 && + sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual3 >fuzzy3 && + test_cmp expect2 fuzzy2 && + test_cmp expect3 fuzzy3 && + ! test_cmp actual1 actual2 +' + +test_expect_success '%H is not affected by --abbrev-commit' ' + git log -1 --format=%H --abbrev-commit --abbrev=20 HEAD >actual && + len=$(wc -c <actual) && + test $len = 41 +' + +test_expect_success '%h is not affected by --abbrev-commit' ' + git log -1 --format=%h --abbrev-commit --abbrev=20 HEAD >actual && + len=$(wc -c <actual) && + test $len = 21 +' + test_expect_success '"%h %gD: %gs" is same as git-reflog' ' git reflog >expect && git log -g --format="%h %gD: %gs" >actual && @@ -203,6 +237,12 @@ test_expect_success '"%h %gD: %gs" is same as git-reflog (with date)' ' test_cmp expect actual ' +test_expect_success '"%h %gD: %gs" is same as git-reflog (with --abbrev)' ' + git reflog --abbrev=13 --date=raw >expect && + git log -g --abbrev=13 --format="%h %gD: %gs" --date=raw >actual && + test_cmp expect actual +' + test_expect_success '%gd shortens ref name' ' echo "master@{0}" >expect.gd-short && git log -g -1 --format=%gd refs/heads/master >actual.gd-short && diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 065deadc2..876d1ab74 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -8,7 +8,7 @@ test_description='test describe o----o----o----o----o----. / \ A c / .------------o---o---o - D e + D,R e ' . ./test-lib.sh @@ -68,6 +68,8 @@ test_expect_success setup ' echo D >another && git add another && git commit -m D && test_tick && git tag -a -m D D && + test_tick && + git tag -a -m R R && test_tick && echo DD >another && git commit -a -m another && @@ -89,10 +91,10 @@ test_expect_success setup ' check_describe A-* HEAD check_describe A-* HEAD^ -check_describe D-* HEAD^^ +check_describe R-* HEAD^^ check_describe A-* HEAD^^2 check_describe B HEAD^^2^ -check_describe D-* HEAD^^^ +check_describe R-* HEAD^^^ check_describe c-* --tags HEAD check_describe c-* --tags HEAD^ diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 8052c86ad..7dc8a510c 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -295,6 +295,15 @@ test_expect_success 'Check short upstream format' ' test_cmp expected actual ' +cat >expected <<EOF +67a36f1 +EOF + +test_expect_success 'Check short objectname format' ' + git for-each-ref --format="%(objectname:short)" refs/heads >actual && + test_cmp expected actual +' + test_expect_success 'Check for invalid refname format' ' test_must_fail git for-each-ref --format="%(refname:INVALID)" ' diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index d9202d5af..3bc7a2a79 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -4,17 +4,24 @@ test_description='Test automatic use of a pager.' . ./test-lib.sh -rm -f stdout_is_tty +cleanup_fail() { + echo >&2 cleanup failed + (exit 1) +} + test_expect_success 'set up terminal for tests' ' + rm -f stdout_is_tty || + cleanup_fail && + if test -t 1 then - : > stdout_is_tty + >stdout_is_tty elif test_have_prereq PERL && "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl \ sh -c "test -t 1" then - : > test_terminal_works + >test_terminal_works fi ' @@ -32,53 +39,68 @@ else say no usable terminal, so skipping some tests fi -unset GIT_PAGER GIT_PAGER_IN_USE -git config --unset core.pager -PAGER='cat > paginated.out' -export PAGER - test_expect_success 'setup' ' + unset GIT_PAGER GIT_PAGER_IN_USE && + test_might_fail git config --unset core.pager && + + PAGER="cat >paginated.out" && + export PAGER && + test_commit initial ' -rm -f paginated.out test_expect_success TTY 'some commands use a pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git log && test -e paginated.out ' -rm -f paginated.out test_expect_success TTY 'some commands do not use a pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git rev-list HEAD && ! test -e paginated.out ' -rm -f paginated.out test_expect_success 'no pager when stdout is a pipe' ' + rm -f paginated.out || + cleanup_fail && + git log | cat && ! test -e paginated.out ' -rm -f paginated.out test_expect_success 'no pager when stdout is a regular file' ' - git log > file && + rm -f paginated.out || + cleanup_fail && + + git log >file && ! test -e paginated.out ' -rm -f paginated.out test_expect_success TTY 'git --paginate rev-list uses a pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git --paginate rev-list HEAD && test -e paginated.out ' -rm -f file paginated.out test_expect_success 'no pager even with --paginate when stdout is a pipe' ' + rm -f file paginated.out || + cleanup_fail && + git --paginate log | cat && ! test -e paginated.out ' -rm -f paginated.out test_expect_success TTY 'no pager with --no-pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git --no-pager log && ! test -e paginated.out ' @@ -86,88 +108,119 @@ test_expect_success TTY 'no pager with --no-pager' ' # A colored commit log will begin with an appropriate ANSI escape # for the first color; the text "commit" comes later. colorful() { - read firstline < $1 + read firstline <$1 ! expr "$firstline" : "^[a-zA-Z]" >/dev/null } -rm -f colorful.log colorless.log test_expect_success 'tests can detect color' ' - git log --no-color > colorless.log && - git log --color > colorful.log && + rm -f colorful.log colorless.log || + cleanup_fail && + + git log --no-color >colorless.log && + git log --color >colorful.log && ! colorful colorless.log && colorful colorful.log ' -rm -f colorless.log -git config color.ui auto test_expect_success 'no color when stdout is a regular file' ' - git log > colorless.log && + rm -f colorless.log && + git config color.ui auto || + cleanup_fail && + + git log >colorless.log && ! colorful colorless.log ' -rm -f paginated.out -git config color.ui auto test_expect_success TTY 'color when writing to a pager' ' - TERM=vt100 test_terminal git log && + rm -f paginated.out && + git config color.ui auto || + cleanup_fail && + + ( + TERM=vt100 && + export TERM && + test_terminal git log + ) && colorful paginated.out ' -rm -f colorful.log -git config color.ui auto test_expect_success 'color when writing to a file intended for a pager' ' - TERM=vt100 GIT_PAGER_IN_USE=true git log > colorful.log && + rm -f colorful.log && + git config color.ui auto || + cleanup_fail && + + ( + TERM=vt100 && + GIT_PAGER_IN_USE=true && + export TERM GIT_PAGER_IN_USE && + git log >colorful.log + ) && colorful colorful.log ' -unset PAGER GIT_PAGER -git config --unset core.pager test_expect_success 'determine default pager' ' + unset PAGER GIT_PAGER && + test_might_fail git config --unset core.pager || + cleanup_fail && + less=$(git var GIT_PAGER) && test -n "$less" ' -if expr "$less" : '^[a-z]*$' > /dev/null && test_have_prereq TTY +if expr "$less" : '^[a-z][a-z]*$' >/dev/null && test_have_prereq TTY then test_set_prereq SIMPLEPAGER fi -unset PAGER GIT_PAGER -git config --unset core.pager -rm -f default_pager_used test_expect_success SIMPLEPAGER 'default pager is used by default' ' - cat > $less <<-EOF && - #!$SHELL_PATH - wc > default_pager_used + unset PAGER GIT_PAGER && + test_might_fail git config --unset core.pager && + rm -f default_pager_used || + cleanup_fail && + + cat >$less <<-\EOF && + #!/bin/sh + wc >default_pager_used EOF chmod +x $less && - PATH=.:$PATH test_terminal git log && + ( + PATH=.:$PATH && + export PATH && + test_terminal git log + ) && test -e default_pager_used ' -unset GIT_PAGER -git config --unset core.pager -rm -f PAGER_used test_expect_success TTY 'PAGER overrides default pager' ' - PAGER="wc > PAGER_used" && + unset GIT_PAGER && + test_might_fail git config --unset core.pager && + rm -f PAGER_used || + cleanup_fail && + + PAGER="wc >PAGER_used" && export PAGER && test_terminal git log && test -e PAGER_used ' -unset GIT_PAGER -rm -f core.pager_used test_expect_success TTY 'core.pager overrides PAGER' ' + unset GIT_PAGER && + rm -f core.pager_used || + cleanup_fail && + PAGER=wc && export PAGER && - git config core.pager "wc > core.pager_used" && + git config core.pager "wc >core.pager_used" && test_terminal git log && test -e core.pager_used ' -rm -f GIT_PAGER_used test_expect_success TTY 'GIT_PAGER overrides core.pager' ' + rm -f GIT_PAGER_used || + cleanup_fail && + git config core.pager wc && - GIT_PAGER="wc > GIT_PAGER_used" && + GIT_PAGER="wc >GIT_PAGER_used" && export GIT_PAGER && test_terminal git log && test -e GIT_PAGER_used diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh index 8d8b1c0e2..582d0b54f 100755 --- a/t/t7012-skip-worktree-writing.sh +++ b/t/t7012-skip-worktree-writing.sh @@ -136,11 +136,11 @@ test_expect_success 'git-clean, dirty case' ' test_cmp expected result ' -test_expect_failure 'git-apply adds file' false -test_expect_failure 'git-apply updates file' false -test_expect_failure 'git-apply removes file' false -test_expect_failure 'git-mv to skip-worktree' false -test_expect_failure 'git-mv from skip-worktree' false -test_expect_failure 'git-checkout' false +#TODO test_expect_failure 'git-apply adds file' false +#TODO test_expect_failure 'git-apply updates file' false +#TODO test_expect_failure 'git-apply removes file' false +#TODO test_expect_failure 'git-mv to skip-worktree' false +#TODO test_expect_failure 'git-mv from skip-worktree' false +#TODO test_expect_failure 'git-checkout' false test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 1a4dc5f89..97ff074da 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -11,226 +11,292 @@ subcommands of git submodule. . ./test-lib.sh -# -# Test setup: -# -create a repository in directory init -# -add a couple of files -# -add directory init to 'superproject', this creates a DIRLINK entry -# -add a couple of regular files to enable testing of submodule filtering -# -mv init subrepo -# -add an entry to .gitmodules for submodule 'example' -# -test_expect_success 'Prepare submodule testing' ' - : > t && +test_expect_success 'setup - initial commit' ' + >t && git add t && git commit -m "initial commit" && - git branch initial HEAD && + git branch initial +' + +test_expect_success 'setup - repository in init subdirectory' ' mkdir init && - cd init && - git init && - echo a >a && - git add a && - git commit -m "submodule commit 1" && - git tag -a -m "rev-1" rev-1 && - rev1=$(git rev-parse HEAD) && - if test -z "$rev1" - then - echo "[OOPS] submodule git rev-parse returned nothing" - false - fi && - cd .. && + ( + cd init && + git init && + echo a >a && + git add a && + git commit -m "submodule commit 1" && + git tag -a -m "rev-1" rev-1 + ) +' + +test_expect_success 'setup - commit with gitlink' ' echo a >a && echo z >z && git add a init z && - git commit -m "super commit 1" && - mv init .subrepo && - GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/init.git + git commit -m "super commit 1" +' + +test_expect_success 'setup - hide init subdirectory' ' + mv init .subrepo +' + +test_expect_success 'setup - repository to add submodules to' ' + git init addtest ' -test_expect_success 'Prepare submodule add testing' ' - submodurl=$(pwd) +# The 'submodule add' tests need some repository to add as a submodule. +# The trash directory is a good one as any. +submodurl=$TRASH_DIRECTORY + +listbranches() { + git for-each-ref --format='%(refname)' 'refs/heads/*' +} + +inspect() { + dir=$1 && + dotdot="${2:-..}" && + ( - mkdir addtest && - cd addtest && - git init + cd "$dir" && + listbranches >"$dotdot/heads" && + { git symbolic-ref HEAD || :; } >"$dotdot/head" && + git rev-parse HEAD >"$dotdot/head-sha1" && + git update-index --refresh && + git diff-files --exit-code && + git clean -n -d -x >"$dotdot/untracked" ) -' +} test_expect_success 'submodule add' ' + echo "refs/heads/master" >expect && + >empty && + ( cd addtest && git submodule add "$submodurl" submod && git submodule init - ) + ) && + + rm -f heads head untracked && + inspect addtest/submod ../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked ' test_expect_success 'submodule add --branch' ' + echo "refs/heads/initial" >expect-head && + cat <<-\EOF >expect-heads && + refs/heads/initial + refs/heads/master + EOF + >empty && + ( cd addtest && git submodule add -b initial "$submodurl" submod-branch && - git submodule init && - cd submod-branch && - git branch | grep initial - ) + git submodule init + ) && + + rm -f heads head untracked && + inspect addtest/submod-branch ../.. && + test_cmp expect-heads heads && + test_cmp expect-head head && + test_cmp empty untracked ' test_expect_success 'submodule add with ./ in path' ' + echo "refs/heads/master" >expect && + >empty && + ( cd addtest && git submodule add "$submodurl" ././dotsubmod/./frotz/./ && git submodule init - ) + ) && + + rm -f heads head untracked && + inspect addtest/dotsubmod/frotz ../../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked ' test_expect_success 'submodule add with // in path' ' + echo "refs/heads/master" >expect && + >empty && + ( cd addtest && git submodule add "$submodurl" slashslashsubmod///frotz// && git submodule init - ) + ) && + + rm -f heads head untracked && + inspect addtest/slashslashsubmod/frotz ../../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked ' test_expect_success 'submodule add with /.. in path' ' + echo "refs/heads/master" >expect && + >empty && + ( cd addtest && git submodule add "$submodurl" dotdotsubmod/../realsubmod/frotz/.. && git submodule init - ) + ) && + + rm -f heads head untracked && + inspect addtest/realsubmod ../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked ' test_expect_success 'submodule add with ./, /.. and // in path' ' + echo "refs/heads/master" >expect && + >empty && + ( cd addtest && git submodule add "$submodurl" dot/dotslashsubmod/./../..////realsubmod2/a/b/c/d/../../../../frotz//.. && git submodule init - ) + ) && + + rm -f heads head untracked && + inspect addtest/realsubmod2 ../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked +' + +test_expect_success 'setup - add an example entry to .gitmodules' ' + GIT_CONFIG=.gitmodules \ + git config submodule.example.url git://example.com/init.git ' test_expect_success 'status should fail for unmapped paths' ' - if git submodule status - then - echo "[OOPS] submodule status succeeded" - false - elif ! GIT_CONFIG=.gitmodules git config submodule.example.path init - then - echo "[OOPS] git config failed to update .gitmodules" - false - fi + test_must_fail git submodule status +' + +test_expect_success 'setup - map path in .gitmodules' ' + cat <<\EOF >expect && +[submodule "example"] + url = git://example.com/init.git + path = init +EOF + + GIT_CONFIG=.gitmodules git config submodule.example.path init && + + test_cmp expect .gitmodules ' test_expect_success 'status should only print one line' ' - lines=$(git submodule status | wc -l) && - test $lines = 1 + git submodule status >lines && + test $(wc -l <lines) = 1 +' + +test_expect_success 'setup - fetch commit name from submodule' ' + rev1=$(cd .subrepo && git rev-parse HEAD) && + printf "rev1: %s\n" "$rev1" && + test -n "$rev1" ' test_expect_success 'status should initially be "missing"' ' - git submodule status | grep "^-$rev1" + git submodule status >lines && + grep "^-$rev1" lines ' test_expect_success 'init should register submodule url in .git/config' ' + echo git://example.com/init.git >expect && + git submodule init && - url=$(git config submodule.example.url) && - if test "$url" != "git://example.com/init.git" - then - echo "[OOPS] init succeeded but submodule url is wrong" - false - elif test_must_fail git config submodule.example.url ./.subrepo - then - echo "[OOPS] init succeeded but update of url failed" - false - fi + git config submodule.example.url >url && + git config submodule.example.url ./.subrepo && + + test_cmp expect url ' test_expect_success 'update should fail when path is used by a file' ' + echo hello >expect && + echo "hello" >init && - if git submodule update - then - echo "[OOPS] update should have failed" - false - elif test "$(cat init)" != "hello" - then - echo "[OOPS] update failed but init file was molested" - false - else - rm init - fi + test_must_fail git submodule update && + + test_cmp expect init ' test_expect_success 'update should fail when path is used by a nonempty directory' ' + echo hello >expect && + + rm -fr init && mkdir init && echo "hello" >init/a && - if git submodule update - then - echo "[OOPS] update should have failed" - false - elif test "$(cat init/a)" != "hello" - then - echo "[OOPS] update failed but init/a was molested" - false - else - rm init/a - fi + + test_must_fail git submodule update && + + test_cmp expect init/a ' test_expect_success 'update should work when path is an empty dir' ' - rm -rf init && + rm -fr init && + rm -f head-sha1 && + echo "$rev1" >expect && + mkdir init && git submodule update && - head=$(cd init && git rev-parse HEAD) && - if test -z "$head" - then - echo "[OOPS] Failed to obtain submodule head" - false - elif test "$head" != "$rev1" - then - echo "[OOPS] Submodule head is $head but should have been $rev1" - false - fi + + inspect init && + test_cmp expect head-sha1 ' test_expect_success 'status should be "up-to-date" after update' ' - git submodule status | grep "^ $rev1" + git submodule status >list && + grep "^ $rev1" list ' test_expect_success 'status should be "modified" after submodule commit' ' - cd init && - echo b >b && - git add b && - git commit -m "submodule commit 2" && - rev2=$(git rev-parse HEAD) && - cd .. && - if test -z "$rev2" - then - echo "[OOPS] submodule git rev-parse returned nothing" - false - fi && - git submodule status | grep "^+$rev2" + ( + cd init && + echo b >b && + git add b && + git commit -m "submodule commit 2" + ) && + + rev2=$(cd init && git rev-parse HEAD) && + test -n "$rev2" && + git submodule status >list && + + grep "^+$rev2" list ' test_expect_success 'the --cached sha1 should be rev1' ' - git submodule --cached status | grep "^+$rev1" + git submodule --cached status >list && + grep "^+$rev1" list ' test_expect_success 'git diff should report the SHA1 of the new submodule commit' ' - git diff | grep "^+Subproject commit $rev2" + git diff >diff && + grep "^+Subproject commit $rev2" diff ' test_expect_success 'update should checkout rev1' ' + rm -f head-sha1 && + echo "$rev1" >expect && + git submodule update init && - head=$(cd init && git rev-parse HEAD) && - if test -z "$head" - then - echo "[OOPS] submodule git rev-parse returned nothing" - false - elif test "$head" != "$rev1" - then - echo "[OOPS] init did not checkout correct head" - false - fi + inspect init && + + test_cmp expect head-sha1 ' test_expect_success 'status should be "up-to-date" after update' ' - git submodule status | grep "^ $rev1" + git submodule status >list && + grep "^ $rev1" list ' test_expect_success 'checkout superproject with subproject already present' ' @@ -239,6 +305,8 @@ test_expect_success 'checkout superproject with subproject already present' ' ' test_expect_success 'apply submodule diff' ' + >empty && + git branch second && ( cd init && @@ -251,21 +319,24 @@ test_expect_success 'apply submodule diff' ' git format-patch -1 --stdout >P.diff && git checkout second && git apply --index P.diff && - D=$(git diff --cached master) && - test -z "$D" + + git diff --cached master >staged && + test_cmp empty staged ' test_expect_success 'update --init' ' - mv init init2 && git config -f .gitmodules submodule.example.url "$(pwd)/init2" && - git config --remove-section submodule.example + git config --remove-section submodule.example && + test_must_fail git config submodule.example.url && + git submodule update init > update.out && + cat update.out && grep "not initialized" update.out && - test ! -d init/.git && + ! test -d init/.git && + git submodule update --init init && test -d init/.git - ' test_expect_success 'do not add files from a submodule' ' diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 9f5c3edb0..aa9c577e9 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -193,4 +193,26 @@ test_expect_success 'commit -F overrides -t' ' commit_msg_is "-F log" ' +test_expect_success 'Commit without message is allowed with --allow-empty-message' ' + echo "more content" >>foo && + git add foo && + >empty && + git commit --allow-empty-message <empty && + commit_msg_is "" +' + +test_expect_success 'Commit without message is no-no without --allow-empty-message' ' + echo "more content" >>foo && + git add foo && + >empty && + test_must_fail git commit <empty +' + +test_expect_success 'Commit a message with --allow-empty-message' ' + echo "even more content" >>foo && + git add foo && + git commit --allow-empty-message -m"hello there" && + commit_msg_is "hello there" +' + test_done diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 556d0faa7..a9df7ff7b 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -496,6 +496,16 @@ test_expect_success 'dry-run of partial commit excluding new file in index' ' test_cmp expect output ' +cat >expect <<EOF +:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M dir1/modified +EOF +test_expect_success 'status refreshes the index' ' + touch dir2/added && + git status && + git diff-files >output && + test_cmp expect output +' + test_expect_success 'setup status submodule summary' ' test_create_repo sm && ( cd sm && @@ -693,4 +703,19 @@ test_expect_success 'commit --dry-run submodule summary (--amend)' ' test_cmp expect output ' +test_expect_success POSIXPERM 'status succeeds in a read-only repository' ' + ( + chmod a-w .git && + # make dir1/tracked stat-dirty + >dir1/tracked1 && mv -f dir1/tracked1 dir1/tracked && + git status -s >output && + ! grep dir1/tracked output && + # make sure "status" succeeded without writing index out + git diff-files | grep dir1/tracked + ) + status=$? + chmod 775 .git + (exit $status) +' + test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 57f6d2bae..cde8390c1 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -554,8 +554,7 @@ test_debug 'gitk --all' test_expect_success 'refresh the index before merging' ' git reset --hard c1 && - sleep 1 && - touch file && + cp file file.n && mv -f file.n file && git merge c3 ' diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index f4aa05475..c2f66ff17 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -8,6 +8,7 @@ test_expect_success 'objects in packs marked .keep are not repacked' ' echo content1 > file1 && echo content2 > file2 && git add . && + test_tick && git commit -m initial_commit && # Create two packs # The first pack will contain all of the objects except one @@ -40,6 +41,7 @@ test_expect_success 'loose objects in alternate ODB are not repacked' ' echo content3 > file3 && objsha1=$(GIT_OBJECT_DIRECTORY=alt_objects git hash-object -w file3) && git add file3 && + test_tick && git commit -m commit_file3 && git repack -a -d -l && git prune-packed && @@ -73,6 +75,7 @@ test_expect_success 'packed obs in alt ODB are repacked when local repo has pack rm -f .git/objects/pack/* && echo new_content >> file1 && git add file1 && + test_tick && git commit -m more_content && git repack && git repack -a -d && @@ -118,8 +121,8 @@ test_expect_success 'packed unreachable obs in alternate ODB are not loosened' ' mv .git/objects/pack/* alt_objects/pack/ && csha1=$(git rev-parse HEAD^{commit}) && git reset --hard HEAD^ && - sleep 1 && - git reflog expire --expire=now --expire-unreachable=now --all && + test_tick && + git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all && # The pack-objects call on the next line is equivalent to # git repack -A -d without the call to prune-packed git pack-objects --honor-pack-keep --non-empty --all --reflog \ @@ -156,7 +159,7 @@ test_expect_success 'objects made unreachable by grafts only are kept' ' H1=$(git rev-parse HEAD^) && H2=$(git rev-parse HEAD^^) && echo "$H0 $H2" > .git/info/grafts && - git reflog expire --expire=now --expire-unreachable=now --all && + git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all && git repack -a -d && git cat-file -t $H1 ' diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh index 5babdf26e..200ab6127 100755 --- a/t/t7701-repack-unpack-unreachable.sh +++ b/t/t7701-repack-unpack-unreachable.sh @@ -11,17 +11,20 @@ tsha1= test_expect_success '-A with -d option leaves unreachable objects unpacked' ' echo content > file1 && git add . && + test_tick && git commit -m initial_commit && # create a transient branch with unique content git checkout -b transient_branch && echo more content >> file1 && # record the objects created in the database for file, commit, tree fsha1=$(git hash-object file1) && + test_tick && git commit -a -m more_content && csha1=$(git rev-parse HEAD^{commit}) && tsha1=$(git rev-parse HEAD^{tree}) && git checkout master && echo even more content >> file1 && + test_tick && git commit -a -m even_more_content && # delete the transient branch git branch -D transient_branch && @@ -34,9 +37,11 @@ test_expect_success '-A with -d option leaves unreachable objects unpacked' ' git show $fsha1 && git show $csha1 && git show $tsha1 && - # now expire the reflog - sleep 1 && - git reflog expire --expire-unreachable=now --all && + # now expire the reflog, while keeping reachable ones but expiring + # unreachables immediately + test_tick && + sometimeago=$(( $test_tick - 10000 )) && + git reflog expire --expire=$sometimeago --expire-unreachable=$test_tick --all && # and repack git repack -A -d -l && # verify objects are retained unpacked @@ -71,7 +76,7 @@ test_expect_success '-A without -d option leaves unreachable objects packed' ' test 1 = $(ls -1 .git/objects/pack/pack-*.pack | wc -l) && packfile=$(ls .git/objects/pack/pack-*.pack) && git branch -D transient_branch && - sleep 1 && + test_tick && git repack -A -l && test ! -f "$fsha1path" && test ! -f "$csha1path" && diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh index ac52bff0e..7d7acc30b 100755 --- a/t/t9118-git-svn-funky-branch-names.sh +++ b/t/t9118-git-svn-funky-branch-names.sh @@ -21,6 +21,14 @@ test_expect_success 'setup svnrepo' ' "$svnrepo/pr ject/branches/more fun plugin!" && svn_cmd cp -m "scary" "$svnrepo/pr ject/branches/fun plugin" \ "$svnrepo/pr ject/branches/$scary_uri" && + svn_cmd cp -m "leading dot" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/branches/.leading_dot" && + svn_cmd cp -m "trailing dot" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/branches/trailing_dot." && + svn_cmd cp -m "trailing .lock" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/branches/trailing_dotlock.lock" && + svn_cmd cp -m "reflog" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/branches/not-a@{0}reflog" && start_httpd ' @@ -30,6 +38,10 @@ test_expect_success 'test clone with funky branch names' ' git rev-parse "refs/remotes/fun%20plugin" && git rev-parse "refs/remotes/more%20fun%20plugin!" && git rev-parse "refs/remotes/$scary_ref" && + git rev-parse "refs/remotes/%2Eleading_dot" && + git rev-parse "refs/remotes/trailing_dot%2E" && + git rev-parse "refs/remotes/trailing_dotlock%2Elock" && + git rev-parse "refs/remotes/not-a%40{0}reflog" && cd .. ' @@ -51,6 +63,15 @@ test_expect_success 'test dcommit to scary branch' ' cd .. ' +test_expect_success 'test dcommit to trailing_dotlock branch' ' + cd project && + git reset --hard "refs/remotes/trailing_dotlock%2Elock" && + echo who names branches like this anyway? >> foo && + git commit -m "bar" -- foo && + git svn dcommit && + cd .. + ' + stop_httpd test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index c582964b0..454880ac7 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -2,6 +2,18 @@ # # Copyright (c) 2005 Junio C Hamano # +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ . # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too. @@ -354,8 +366,10 @@ test_debug () { } test_run_ () { + test_cleanup=: eval >&3 2>&4 "$1" - eval_ret="$?" + eval_ret=$? + eval >&3 2>&4 "$test_cleanup" return 0 } @@ -459,6 +473,9 @@ test_external () { # Announce the script to reduce confusion about the # test output that follows. say_color "" " run $test_count: $descr ($*)" + # Export TEST_DIRECTORY, TRASH_DIRECTORY and GIT_TEST_LONG + # to be able to use them in script + export TEST_DIRECTORY TRASH_DIRECTORY GIT_TEST_LONG # Run command; redirect its stderr to &4 as in # test_run_, but keep its stdout on our stdout even in # non-verbose mode. @@ -516,6 +533,22 @@ test_must_fail () { test $? -gt 0 -a $? -le 129 -o $? -gt 192 } +# Similar to test_must_fail, but tolerates success, too. This is +# meant to be used in contexts like: +# +# test_expect_success 'some command works without configuration' ' +# test_might_fail git config --unset all.configuration && +# do something +# ' +# +# Writing "git config --unset all.configuration || :" would be wrong, +# because we want to notice if it fails due to segv. + +test_might_fail () { + "$@" + test $? -ge 0 -a $? -le 129 -o $? -gt 192 +} + # test_cmp is a helper function to compare actual and expected output. # You can use it like: # @@ -533,6 +566,31 @@ test_cmp() { $GIT_TEST_CMP "$@" } +# This function can be used to schedule some commands to be run +# unconditionally at the end of the test to restore sanity: +# +# test_expect_success 'test core.capslock' ' +# git config core.capslock true && +# test_when_finished "git config --unset core.capslock" && +# hello world +# ' +# +# That would be roughly equivalent to +# +# test_expect_success 'test core.capslock' ' +# git config core.capslock true && +# hello world +# git config --unset core.capslock +# ' +# +# except that the greeting and config --unset must both succeed for +# the test to pass. + +test_when_finished () { + test_cleanup="{ $* + } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" +} + # Most tests can use the created repository, but some may need to create more. # Usage: test_create_repo <directory> test_create_repo () { @@ -36,43 +36,50 @@ struct tag *lookup_tag(const unsigned char *sha1) return (struct tag *) obj; } +static unsigned long parse_tag_date(const char *buf, const char *tail) +{ + const char *dateptr; + + while (buf < tail && *buf++ != '>') + /* nada */; + if (buf >= tail) + return 0; + dateptr = buf; + while (buf < tail && *buf++ != '\n') + /* nada */; + if (buf >= tail) + return 0; + /* dateptr < buf && buf[-1] == '\n', so strtoul will stop at buf-1 */ + return strtoul(dateptr, NULL, 10); +} + int parse_tag_buffer(struct tag *item, void *data, unsigned long size) { - int typelen, taglen; unsigned char sha1[20]; - const char *type_line, *tag_line, *sig_line; char type[20]; - const char *start = data; + const char *bufptr = data; + const char *tail = bufptr + size; + const char *nl; - if (item->object.parsed) - return 0; - item->object.parsed = 1; + if (item->object.parsed) + return 0; + item->object.parsed = 1; if (size < 64) return -1; - if (memcmp("object ", data, 7) || get_sha1_hex((char *) data + 7, sha1)) + if (memcmp("object ", bufptr, 7) || get_sha1_hex(bufptr + 7, sha1) || bufptr[47] != '\n') return -1; + bufptr += 48; /* "object " + sha1 + "\n" */ - type_line = (char *) data + 48; - if (memcmp("\ntype ", type_line-1, 6)) + if (prefixcmp(bufptr, "type ")) return -1; - - tag_line = memchr(type_line, '\n', size - (type_line - start)); - if (!tag_line || memcmp("tag ", ++tag_line, 4)) + bufptr += 5; + nl = memchr(bufptr, '\n', tail - bufptr); + if (!nl || sizeof(type) <= (nl - bufptr)) return -1; - - sig_line = memchr(tag_line, '\n', size - (tag_line - start)); - if (!sig_line) - return -1; - sig_line++; - - typelen = tag_line - type_line - strlen("type \n"); - if (typelen >= 20) - return -1; - memcpy(type, type_line + 5, typelen); - type[typelen] = '\0'; - taglen = sig_line - tag_line - strlen("tag \n"); - item->tag = xmemdupz(tag_line + 4, taglen); + strncpy(type, bufptr, nl - bufptr); + type[nl - bufptr] = '\0'; + bufptr = nl + 1; if (!strcmp(type, blob_type)) { item->tagged = &lookup_blob(sha1)->object; @@ -87,6 +94,20 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size) item->tagged = NULL; } + if (prefixcmp(bufptr, "tag ")) + return -1; + bufptr += 4; + nl = memchr(bufptr, '\n', tail - bufptr); + if (!nl) + return -1; + item->tag = xmemdupz(bufptr, nl - bufptr); + bufptr = nl + 1; + + if (!prefixcmp(bufptr, "tagger ")) + item->date = parse_tag_date(bufptr, tail); + else + item->date = 0; + return 0; } @@ -9,7 +9,7 @@ struct tag { struct object object; struct object *tagged; char *tag; - char *signature; /* not actually implemented */ + unsigned long date; }; extern struct tag *lookup_tag(const unsigned char *sha1); diff --git a/thread-utils.c b/thread-utils.c index 4f9c829c2..589f838f8 100644 --- a/thread-utils.c +++ b/thread-utils.c @@ -1,4 +1,5 @@ #include "cache.h" +#include <pthread.h> #if defined(hpux) || defined(__hpux) || defined(_hpux) # include <sys/pstat.h> @@ -43,3 +44,18 @@ int online_cpus(void) return 1; } + +int init_recursive_mutex(pthread_mutex_t *m) +{ + pthread_mutexattr_t a; + int ret; + + ret = pthread_mutexattr_init(&a); + if (!ret) { + ret = pthread_mutexattr_settype(&a, PTHREAD_MUTEX_RECURSIVE); + if (!ret) + ret = pthread_mutex_init(m, &a); + pthread_mutexattr_destroy(&a); + } + return ret; +} diff --git a/thread-utils.h b/thread-utils.h index cce4b77bd..1727a0333 100644 --- a/thread-utils.h +++ b/thread-utils.h @@ -2,5 +2,6 @@ #define THREAD_COMPAT_H extern int online_cpus(void); +extern int init_recursive_mutex(pthread_mutex_t*); #endif /* THREAD_COMPAT_H */ diff --git a/transport-helper.c b/transport-helper.c index 2638781c5..0381de536 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -7,6 +7,7 @@ #include "revision.h" #include "quote.h" #include "remote.h" +#include "string-list.h" static int debug; @@ -17,6 +18,7 @@ struct helper_data FILE *out; unsigned fetch : 1, import : 1, + export : 1, option : 1, push : 1, connect : 1, @@ -163,6 +165,8 @@ static struct child_process *get_helper(struct transport *transport) data->push = 1; else if (!strcmp(capname, "import")) data->import = 1; + else if (!strcmp(capname, "export")) + data->export = 1; else if (!data->refspecs && !prefixcmp(capname, "refspec ")) { ALLOC_GROW(refspecs, refspec_nr + 1, @@ -170,6 +174,11 @@ static struct child_process *get_helper(struct transport *transport) refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec ")); } else if (!strcmp(capname, "connect")) { data->connect = 1; + } else if (!strcmp(buf.buf, "gitdir")) { + struct strbuf gitdir = STRBUF_INIT; + strbuf_addf(&gitdir, "gitdir %s\n", get_git_dir()); + sendline(data, &gitdir); + strbuf_release(&gitdir); } else if (mandatory) { die("Unknown mandatory capability %s. This remote " "helper probably needs newer version of Git.\n", @@ -351,6 +360,33 @@ static int get_importer(struct transport *transport, struct child_process *fasti return start_command(fastimport); } +static int get_exporter(struct transport *transport, + struct child_process *fastexport, + const char *export_marks, + const char *import_marks, + struct string_list *revlist_args) +{ + struct child_process *helper = get_helper(transport); + int argc = 0, i; + memset(fastexport, 0, sizeof(*fastexport)); + + /* we need to duplicate helper->in because we want to use it after + * fastexport is done with it. */ + fastexport->out = dup(helper->in); + fastexport->argv = xcalloc(4 + revlist_args->nr, sizeof(*fastexport->argv)); + fastexport->argv[argc++] = "fast-export"; + if (export_marks) + fastexport->argv[argc++] = export_marks; + if (import_marks) + fastexport->argv[argc++] = import_marks; + + for (i = 0; i < revlist_args->nr; i++) + fastexport->argv[argc++] = revlist_args->items[i].string; + + fastexport->git_cmd = 1; + return start_command(fastexport); +} + static int fetch_with_import(struct transport *transport, int nr_heads, struct ref **to_fetch) { @@ -518,7 +554,7 @@ static int fetch(struct transport *transport, return -1; } -static int push_refs(struct transport *transport, +static int push_refs_with_push(struct transport *transport, struct ref *remote_refs, int flags) { int force_all = flags & TRANSPORT_PUSH_FORCE; @@ -528,17 +564,6 @@ static int push_refs(struct transport *transport, struct child_process *helper; struct ref *ref; - if (process_connect(transport, 1)) { - do_take_over(transport); - return transport->push_refs(transport, remote_refs, flags); - } - - if (!remote_refs) { - fprintf(stderr, "No refs in common and none specified; doing nothing.\n" - "Perhaps you should specify a branch such as 'master'.\n"); - return 0; - } - helper = get_helper(transport); if (!data->push) return 1; @@ -657,6 +682,94 @@ static int push_refs(struct transport *transport, return 0; } +static int push_refs_with_export(struct transport *transport, + struct ref *remote_refs, int flags) +{ + struct ref *ref; + struct child_process *helper, exporter; + struct helper_data *data = transport->data; + char *export_marks = NULL, *import_marks = NULL; + struct string_list revlist_args = { NULL, 0, 0 }; + struct strbuf buf = STRBUF_INIT; + + helper = get_helper(transport); + + write_constant(helper->in, "export\n"); + + recvline(data, &buf); + if (debug) + fprintf(stderr, "Debug: Got export_marks '%s'\n", buf.buf); + if (buf.len) { + struct strbuf arg = STRBUF_INIT; + strbuf_addstr(&arg, "--export-marks="); + strbuf_addbuf(&arg, &buf); + export_marks = strbuf_detach(&arg, NULL); + } + + recvline(data, &buf); + if (debug) + fprintf(stderr, "Debug: Got import_marks '%s'\n", buf.buf); + if (buf.len) { + struct strbuf arg = STRBUF_INIT; + strbuf_addstr(&arg, "--import-marks="); + strbuf_addbuf(&arg, &buf); + import_marks = strbuf_detach(&arg, NULL); + } + + strbuf_reset(&buf); + + for (ref = remote_refs; ref; ref = ref->next) { + char *private; + unsigned char sha1[20]; + + if (!data->refspecs) + continue; + private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name); + if (private && !get_sha1(private, sha1)) { + strbuf_addf(&buf, "^%s", private); + string_list_append(strbuf_detach(&buf, NULL), &revlist_args); + } + + string_list_append(ref->name, &revlist_args); + + } + + if (get_exporter(transport, &exporter, + export_marks, import_marks, &revlist_args)) + die("Couldn't run fast-export"); + + data->no_disconnect_req = 1; + finish_command(&exporter); + disconnect_helper(transport); + return 0; +} + +static int push_refs(struct transport *transport, + struct ref *remote_refs, int flags) +{ + struct helper_data *data = transport->data; + + if (process_connect(transport, 1)) { + do_take_over(transport); + return transport->push_refs(transport, remote_refs, flags); + } + + if (!remote_refs) { + fprintf(stderr, "No refs in common and none specified; doing nothing.\n" + "Perhaps you should specify a branch such as 'master'.\n"); + return 0; + } + + if (data->push) + return push_refs_with_push(transport, remote_refs, flags); + + if (data->export) + return push_refs_with_export(transport, remote_refs, flags); + + return -1; +} + + static int has_attribute(const char *attrs, const char *attr) { int len; if (!attrs) diff --git a/unpack-trees.c b/unpack-trees.c index 75f54cac9..c29a9e067 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -67,16 +67,8 @@ static void unlink_entry(struct cache_entry *ce) { if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) return; - if (S_ISGITLINK(ce->ce_mode)) { - if (rmdir(ce->name)) { - warning("unable to rmdir %s: %s", - ce->name, strerror(errno)); - return; - } - } - else - if (unlink_or_warn(ce->name)) - return; + if (remove_or_warn(ce->ce_mode, ce->name)) + return; schedule_dir_for_removal(ce->name, ce_namelen(ce)); } diff --git a/userdiff.c b/userdiff.c index df992490d..67003fbb2 100644 --- a/userdiff.c +++ b/userdiff.c @@ -1,3 +1,4 @@ +#include "cache.h" #include "userdiff.h" #include "cache.h" #include "attr.h" @@ -167,6 +168,12 @@ static int parse_tristate(int *b, const char *k, const char *v) return 1; } +static int parse_bool(int *b, const char *k, const char *v) +{ + *b = git_config_bool(k, v); + return 1; +} + int userdiff_config(const char *k, const char *v) { struct userdiff_driver *drv; @@ -181,6 +188,8 @@ int userdiff_config(const char *k, const char *v) return parse_string(&drv->external, k, v); if ((drv = parse_driver(k, v, "textconv"))) return parse_string(&drv->textconv, k, v); + if ((drv = parse_driver(k, v, "cachetextconv"))) + return parse_bool(&drv->textconv_want_cache, k, v); if ((drv = parse_driver(k, v, "wordregex"))) return parse_string(&drv->word_regex, k, v); diff --git a/userdiff.h b/userdiff.h index c3151594f..942d59495 100644 --- a/userdiff.h +++ b/userdiff.h @@ -1,6 +1,8 @@ #ifndef USERDIFF_H #define USERDIFF_H +#include "notes-cache.h" + struct userdiff_funcname { const char *pattern; int cflags; @@ -13,6 +15,8 @@ struct userdiff_driver { struct userdiff_funcname funcname; const char *word_regex; const char *textconv; + struct notes_cache *textconv_cache; + int textconv_want_cache; }; int userdiff_config(const char *k, const char *v); @@ -3,11 +3,23 @@ */ #include "cache.h" +static void try_to_free_builtin(size_t size) +{ + release_pack_memory(size, -1); +} + +static void (*try_to_free_routine)(size_t size) = try_to_free_builtin; + +void set_try_to_free_routine(void (*routine)(size_t)) +{ + try_to_free_routine = (routine) ? routine : try_to_free_builtin; +} + char *xstrdup(const char *str) { char *ret = strdup(str); if (!ret) { - release_pack_memory(strlen(str) + 1, -1); + try_to_free_routine(strlen(str) + 1); ret = strdup(str); if (!ret) die("Out of memory, strdup failed"); @@ -21,7 +33,7 @@ void *xmalloc(size_t size) if (!ret && !size) ret = malloc(1); if (!ret) { - release_pack_memory(size, -1); + try_to_free_routine(size); ret = malloc(size); if (!ret && !size) ret = malloc(1); @@ -67,7 +79,7 @@ void *xrealloc(void *ptr, size_t size) if (!ret && !size) ret = realloc(ptr, 1); if (!ret) { - release_pack_memory(size, -1); + try_to_free_routine(size); ret = realloc(ptr, size); if (!ret && !size) ret = realloc(ptr, 1); @@ -83,7 +95,7 @@ void *xcalloc(size_t nmemb, size_t size) if (!ret && (!nmemb || !size)) ret = calloc(1, 1); if (!ret) { - release_pack_memory(nmemb * size, -1); + try_to_free_routine(nmemb * size); ret = calloc(nmemb, size); if (!ret && (!nmemb || !size)) ret = calloc(1, 1); @@ -311,18 +323,30 @@ int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1) return open(name, O_RDWR|O_CREAT|O_EXCL, 0600); } -int unlink_or_warn(const char *file) +static int warn_if_unremovable(const char *op, const char *file, int rc) { - int rc = unlink(file); - if (rc < 0) { int err = errno; if (ENOENT != err) { - warning("unable to unlink %s: %s", - file, strerror(errno)); + warning("unable to %s %s: %s", + op, file, strerror(errno)); errno = err; } } return rc; } +int unlink_or_warn(const char *file) +{ + return warn_if_unremovable("unlink", file, unlink(file)); +} + +int rmdir_or_warn(const char *file) +{ + return warn_if_unremovable("rmdir", file, rmdir(file)); +} + +int remove_or_warn(unsigned int mode, const char *file) +{ + return S_ISGITLINK(mode) ? rmdir_or_warn(file) : unlink_or_warn(file); +} @@ -10,7 +10,8 @@ static struct whitespace_rule { const char *rule_name; unsigned rule_bits; - unsigned loosens_error; + unsigned loosens_error:1, + exclude_default:1; } whitespace_rule_names[] = { { "trailing-space", WS_TRAILING_SPACE, 0 }, { "space-before-tab", WS_SPACE_BEFORE_TAB, 0 }, @@ -18,6 +19,7 @@ static struct whitespace_rule { { "cr-at-eol", WS_CR_AT_EOL, 1 }, { "blank-at-eol", WS_BLANK_AT_EOL, 0 }, { "blank-at-eof", WS_BLANK_AT_EOF, 0 }, + { "tab-in-indent", WS_TAB_IN_INDENT, 0, 1 }, }; unsigned parse_whitespace_rule(const char *string) @@ -56,6 +58,9 @@ unsigned parse_whitespace_rule(const char *string) } string = ep; } + + if (rule & WS_TAB_IN_INDENT && rule & WS_INDENT_WITH_NON_TAB) + die("cannot enforce both tab-in-indent and indent-with-non-tab"); return rule; } @@ -82,7 +87,8 @@ unsigned whitespace_rule(const char *pathname) unsigned all_rule = 0; int i; for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) - if (!whitespace_rule_names[i].loosens_error) + if (!whitespace_rule_names[i].loosens_error && + !whitespace_rule_names[i].exclude_default) all_rule |= whitespace_rule_names[i].rule_bits; return all_rule; } else if (ATTR_FALSE(value)) { @@ -125,6 +131,11 @@ char *whitespace_error_string(unsigned ws) strbuf_addstr(&err, ", "); strbuf_addstr(&err, "indent with spaces"); } + if (ws & WS_TAB_IN_INDENT) { + if (err.len) + strbuf_addstr(&err, ", "); + strbuf_addstr(&err, "tab in indent"); + } return strbuf_detach(&err, NULL); } @@ -163,7 +174,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, } } - /* Check for space before tab in initial indent. */ + /* Check indentation */ for (i = 0; i < len; i++) { if (line[i] == ' ') continue; @@ -175,11 +186,19 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, fputs(ws, stream); fwrite(line + written, i - written, 1, stream); fputs(reset, stream); + fwrite(line + i, 1, 1, stream); } - } else if (stream) - fwrite(line + written, i - written, 1, stream); - if (stream) - fwrite(line + i, 1, 1, stream); + } else if (ws_rule & WS_TAB_IN_INDENT) { + result |= WS_TAB_IN_INDENT; + if (stream) { + fwrite(line + written, i - written, 1, stream); + fputs(ws, stream); + fwrite(line + i, 1, 1, stream); + fputs(reset, stream); + } + } else if (stream) { + fwrite(line + written, i - written + 1, 1, stream); + } written = i + 1; } @@ -252,8 +271,8 @@ int ws_blank_line(const char *line, int len, unsigned ws_rule) return 1; } -/* Copy the line to the buffer while fixing whitespaces */ -int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *error_count) +/* Copy the line onto the end of the strbuf while fixing whitespaces */ +void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule, int *error_count) { /* * len is number of bytes to be copied from src, starting @@ -267,7 +286,6 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro int last_tab_in_indent = -1; int last_space_in_indent = -1; int need_fix_leading_space = 0; - char *buf; /* * Strip trailing whitespace @@ -307,7 +325,6 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro break; } - buf = dst; if (need_fix_leading_space) { /* Process indent ourselves */ int consecutive_spaces = 0; @@ -329,28 +346,41 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro char ch = src[i]; if (ch != ' ') { consecutive_spaces = 0; - *dst++ = ch; + strbuf_addch(dst, ch); } else { consecutive_spaces++; if (consecutive_spaces == 8) { - *dst++ = '\t'; + strbuf_addch(dst, '\t'); consecutive_spaces = 0; } } } while (0 < consecutive_spaces--) - *dst++ = ' '; + strbuf_addch(dst, ' '); + len -= last; + src += last; + fixed = 1; + } else if ((ws_rule & WS_TAB_IN_INDENT) && last_tab_in_indent >= 0) { + /* Expand tabs into spaces */ + int last = last_tab_in_indent + 1; + for (i = 0; i < last; i++) { + if (src[i] == '\t') + do { + strbuf_addch(dst, ' '); + } while (dst->len % 8); + else + strbuf_addch(dst, src[i]); + } len -= last; src += last; fixed = 1; } - memcpy(dst, src, len); + strbuf_add(dst, src, len); if (add_cr_to_tail) - dst[len++] = '\r'; + strbuf_addch(dst, '\r'); if (add_nl_to_tail) - dst[len++] = '\n'; + strbuf_addch(dst, '\n'); if (fixed && error_count) (*error_count)++; - return dst + len - buf; } diff --git a/wt-status.c b/wt-status.c index 8ca59a2d2..84a9002b8 100644 --- a/wt-status.c +++ b/wt-status.c @@ -42,6 +42,7 @@ void wt_status_prepare(struct wt_status *s) s->index_file = get_index_file(); s->change.strdup_strings = 1; s->untracked.strdup_strings = 1; + s->ignored.strdup_strings = 1; } static void wt_status_print_unmerged_header(struct wt_status *s) @@ -96,13 +97,15 @@ static void wt_status_print_dirty_header(struct wt_status *s, color_fprintf_ln(s->fp, c, "#"); } -static void wt_status_print_untracked_header(struct wt_status *s) +static void wt_status_print_other_header(struct wt_status *s, + const char *what, + const char *how) { const char *c = color(WT_STATUS_HEADER, s); - color_fprintf_ln(s->fp, c, "# Untracked files:"); + color_fprintf_ln(s->fp, c, "# %s files:", what); if (!advice_status_hints) return; - color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to include in what will be committed)"); + color_fprintf_ln(s->fp, c, "# (use \"git %s <file>...\" to include in what will be committed)", how); color_fprintf_ln(s->fp, c, "#"); } @@ -378,9 +381,26 @@ static void wt_status_collect_untracked(struct wt_status *s) continue; if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) continue; - s->workdir_untracked = 1; string_list_insert(ent->name, &s->untracked); + free(ent); } + + if (s->show_ignored_files) { + dir.nr = 0; + dir.flags = DIR_SHOW_IGNORED | DIR_SHOW_OTHER_DIRECTORIES; + fill_directory(&dir, s->pathspec); + for (i = 0; i < dir.nr; i++) { + struct dir_entry *ent = dir.entries[i]; + if (!cache_name_is_other(ent->name, ent->len)) + continue; + if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) + continue; + string_list_insert(ent->name, &s->ignored); + free(ent); + } + } + + free(dir.entries); } void wt_status_collect(struct wt_status *s) @@ -523,7 +543,10 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt run_command(&sm_summary); } -static void wt_status_print_untracked(struct wt_status *s) +static void wt_status_print_other(struct wt_status *s, + struct string_list *l, + const char *what, + const char *how) { int i; struct strbuf buf = STRBUF_INIT; @@ -531,10 +554,11 @@ static void wt_status_print_untracked(struct wt_status *s) if (!s->untracked.nr) return; - wt_status_print_untracked_header(s); - for (i = 0; i < s->untracked.nr; i++) { + wt_status_print_other_header(s, what, how); + + for (i = 0; i < l->nr; i++) { struct string_list_item *it; - it = &(s->untracked.items[i]); + it = &(l->items[i]); color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", quote_path(it->string, strlen(it->string), @@ -622,9 +646,11 @@ void wt_status_print(struct wt_status *s) wt_status_print_submodule_summary(s, 0); /* staged */ wt_status_print_submodule_summary(s, 1); /* unstaged */ } - if (s->show_untracked_files) - wt_status_print_untracked(s); - else if (s->commitable) + if (s->show_untracked_files) { + wt_status_print_other(s, &s->untracked, "Untracked", "add"); + if (s->show_ignored_files) + wt_status_print_other(s, &s->ignored, "Ignored", "add -f"); + } else if (s->commitable) fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n"); if (s->verbose) @@ -706,16 +732,16 @@ static void wt_shortstatus_status(int null_termination, struct string_list_item } } -static void wt_shortstatus_untracked(int null_termination, struct string_list_item *it, - struct wt_status *s) +static void wt_shortstatus_other(int null_termination, struct string_list_item *it, + struct wt_status *s, const char *sign) { if (null_termination) { - fprintf(stdout, "?? %s%c", it->string, 0); + fprintf(stdout, "%s %s%c", sign, it->string, 0); } else { struct strbuf onebuf = STRBUF_INIT; const char *one; one = quote_path(it->string, -1, &onebuf, s->prefix); - color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "??"); + color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign); printf(" %s\n", one); strbuf_release(&onebuf); } @@ -739,7 +765,13 @@ void wt_shortstatus_print(struct wt_status *s, int null_termination) struct string_list_item *it; it = &(s->untracked.items[i]); - wt_shortstatus_untracked(null_termination, it, s); + wt_shortstatus_other(null_termination, it, s, "??"); + } + for (i = 0; i < s->ignored.nr; i++) { + struct string_list_item *it; + + it = &(s->ignored.items[i]); + wt_shortstatus_other(null_termination, it, s, "!!"); } } diff --git a/wt-status.h b/wt-status.h index 91206739f..1093e65ae 100644 --- a/wt-status.h +++ b/wt-status.h @@ -41,18 +41,19 @@ struct wt_status { int use_color; int relative_paths; int submodule_summary; + int show_ignored_files; enum untracked_status_type show_untracked_files; char color_palette[WT_STATUS_UNMERGED+1][COLOR_MAXLEN]; /* These are computed during processing of the individual sections */ int commitable; int workdir_dirty; - int workdir_untracked; const char *index_file; FILE *fp; const char *prefix; struct string_list change; struct string_list untracked; + struct string_list ignored; }; void wt_status_prepare(struct wt_status *s); diff --git a/xdiff-interface.c b/xdiff-interface.c index ca5e3fbae..cd2285de1 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -138,19 +138,20 @@ int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t co int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, xdiff_emit_consume_fn fn, void *consume_callback_data, - xpparam_t const *xpp, - xdemitconf_t const *xecfg, xdemitcb_t *xecb) + xpparam_t const *xpp, xdemitconf_t const *xecfg) { int ret; struct xdiff_emit_state state; + xdemitcb_t ecb; memset(&state, 0, sizeof(state)); state.consume = fn; state.consume_callback_data = consume_callback_data; - xecb->outf = xdiff_outf; - xecb->priv = &state; + memset(&ecb, 0, sizeof(ecb)); + ecb.outf = xdiff_outf; + ecb.priv = &state; strbuf_init(&state.remainder, 0); - ret = xdi_diff(mf1, mf2, xpp, xecfg, xecb); + ret = xdi_diff(mf1, mf2, xpp, xecfg, &ecb); strbuf_release(&state.remainder); return ret; } diff --git a/xdiff-interface.h b/xdiff-interface.h index abba70c16..49d1116fc 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -9,8 +9,7 @@ typedef void (*xdiff_emit_hunk_consume_fn)(void *, long, long, long); int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, xdiff_emit_consume_fn fn, void *consume_callback_data, - xpparam_t const *xpp, - xdemitconf_t const *xecfg, xdemitcb_t *xecb); + xpparam_t const *xpp, xdemitconf_t const *xecfg); int xdi_diff_hunks(mmfile_t *mf1, mmfile_t *mf2, xdiff_emit_hunk_consume_fn fn, void *consume_callback_data, xpparam_t const *xpp, xdemitconf_t *xecfg); diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 16dd9acd3..6d6fc1bc5 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -152,7 +152,6 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, int marker1_size = (name1 ? strlen(name1) + 1 : 0); int marker2_size = (name2 ? strlen(name2) + 1 : 0); int marker3_size = (name3 ? strlen(name3) + 1 : 0); - int j; if (marker_size <= 0) marker_size = DEFAULT_CONFLICT_MARKER_SIZE; @@ -164,8 +163,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, if (!dest) { size += marker_size + 1 + marker1_size; } else { - for (j = 0; j < marker_size; j++) - dest[size++] = '<'; + memset(dest + size, '<', marker_size); + size += marker_size; if (marker1_size) { dest[size] = ' '; memcpy(dest + size + 1, name1, marker1_size - 1); @@ -183,8 +182,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, if (!dest) { size += marker_size + 1 + marker3_size; } else { - for (j = 0; j < marker_size; j++) - dest[size++] = '|'; + memset(dest + size, '|', marker_size); + size += marker_size; if (marker3_size) { dest[size] = ' '; memcpy(dest + size + 1, name3, marker3_size - 1); @@ -199,8 +198,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, if (!dest) { size += marker_size + 1; } else { - for (j = 0; j < marker_size; j++) - dest[size++] = '='; + memset(dest + size, '=', marker_size); + size += marker_size; dest[size++] = '\n'; } @@ -210,8 +209,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, if (!dest) { size += marker_size + 1 + marker2_size; } else { - for (j = 0; j < marker_size; j++) - dest[size++] = '>'; + memset(dest + size, '>', marker_size); + size += marker_size; if (marker2_size) { dest[size] = ' '; memcpy(dest + size + 1, name2, marker2_size - 1); |