]> asedeno.scripts.mit.edu Git - git.git/commitdiff
Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Tue, 22 Jun 2010 16:35:36 +0000 (09:35 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 22 Jun 2010 16:35:36 +0000 (09:35 -0700)
* maint:
  Update draft release notes to 1.7.1.1
  tests: remove unnecessary '^' from 'expr' regular expression

Conflicts:
diff.c

213 files changed:
.gitignore
Documentation/RelNotes-1.7.2.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-checkout.txt
Documentation/git-commit.txt
Documentation/git-cvsserver.txt
Documentation/git-for-each-ref.txt
Documentation/git-gc.txt
Documentation/git-instaweb.txt
Documentation/git-log.txt
Documentation/git-notes.txt
Documentation/git-remote.txt
Documentation/git-status.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/install-webdoc.sh
Documentation/pretty-formats.txt
Documentation/technical/api-run-command.txt
GIT-VERSION-GEN
Makefile
RelNotes
aclocal.m4 [new file with mode: 0644]
attr.c
attr.h
builtin.h
builtin/add.c
builtin/apply.c
builtin/blame.c
builtin/branch.c
builtin/cat-file.c
builtin/checkout.c
builtin/clone.c
builtin/commit-tree.c
builtin/commit.c
builtin/config.c
builtin/fetch.c
builtin/for-each-ref.c
builtin/grep.c
builtin/help.c
builtin/log.c
builtin/ls-remote.c
builtin/mailinfo.c
builtin/merge.c
builtin/notes.c
builtin/pack-objects.c
builtin/patch-id.c
builtin/receive-pack.c
builtin/reflog.c
builtin/remote.c
builtin/rev-parse.c
builtin/revert.c
cache.h
color.c
color.h
commit.c
commit.h
compat/mingw.h
compat/win32/pthread.c
compat/win32/pthread.h
config.c
config.mak.in
configure.ac
connect.c
contrib/completion/git-completion.bash
contrib/examples/git-fetch.sh
contrib/git-resurrect.sh
convert.c
ctype.c
daemon.c
date.c
diff.c
diff.h
diffcore-break.c
diffcore-pickaxe.c
diffcore-rename.c
diffcore.h
dir.c
environment.c
fast-import.c
fsck.c
git-am.sh
git-compat-util.h
git-cvsserver.perl
git-instaweb.sh
git-merge-one-file.sh
git-rebase.sh
git-remote-testgit.py [new file with mode: 0644]
git-request-pull.sh
git-stash.sh
git-submodule.sh
git-svn.perl
git-web--browse.sh
git.c
git_remote_helpers/git/exporter.py [new file with mode: 0644]
git_remote_helpers/git/importer.py [new file with mode: 0644]
git_remote_helpers/git/non_local.py [new file with mode: 0644]
git_remote_helpers/git/repo.py [new file with mode: 0644]
gitweb/INSTALL
gitweb/Makefile
gitweb/README
gitweb/gitweb.perl
gitweb/static/git-favicon.png [moved from gitweb/git-favicon.png with 100% similarity]
gitweb/static/git-logo.png [moved from gitweb/git-logo.png with 100% similarity]
gitweb/static/gitweb.css [moved from gitweb/gitweb.css with 93% similarity]
gitweb/static/gitweb.js [moved from gitweb/gitweb.js with 100% similarity]
graph.c
grep.c
grep.h
http-backend.c
http-push.c
http-walker.c
imap-send.c
ll-merge.c
log-tree.c
merge-recursive.h
notes-cache.c [new file with mode: 0644]
notes-cache.h [new file with mode: 0644]
notes.c
notes.h
parse-options.c
parse-options.h
pretty.c
refs.c
refs.h
remote.c
remote.h
rerere.c
revision.c
run-command.c
run-command.h
setup.c
sha1_file.c
t/Makefile
t/aggregate-results.sh
t/gitweb-lib.sh
t/lib-t6000.sh
t/t0000-basic.sh
t/t0020-crlf.sh
t/t0021-conversion.sh
t/t0025-crlf-auto.sh [new file with mode: 0755]
t/t0026-eol-config.sh [new file with mode: 0755]
t/t0040-parse-options.sh
t/t1300-repo-config.sh
t/t1450-fsck.sh
t/t1502-rev-parse-parseopt.sh
t/t2017-checkout-orphan.sh [new file with mode: 0755]
t/t3030-merge-recursive.sh
t/t3200-branch.sh
t/t3210-pack-refs.sh
t/t3306-notes-prune.sh
t/t3400-rebase.sh
t/t3500-cherry.sh
t/t3903-stash.sh
t/t4002-diff-basic.sh
t/t4011-diff-symlink.sh
t/t4015-diff-whitespace.sh
t/t4034-diff-words.sh
t/t4042-diff-textconv-caching.sh [new file with mode: 0755]
t/t4044-diff-index-unique-abbrev.sh [new file with mode: 0755]
t/t4124-apply-ws-rule.sh
t/t4127-apply-same-fn.sh
t/t4202-log.sh
t/t4204-patch-id.sh
t/t4205-log-pretty-formats.sh [new file with mode: 0755]
t/t4206-log-follow-harder-copies.sh [new file with mode: 0755]
t/t5150-request-pull.sh [new file with mode: 0755]
t/t5300-pack-object.sh
t/t5505-remote.sh
t/t5510-fetch.sh
t/t5512-ls-remote.sh
t/t5516-fetch-push.sh
t/t5520-pull.sh
t/t5530-upload-pack-error.sh
t/t5601-clone.sh
t/t5700-clone-reference.sh
t/t5800-remote-helpers.sh [new file with mode: 0755]
t/t6001-rev-list-graft.sh
t/t6006-rev-list-format.sh
t/t6022-merge-rename.sh
t/t6300-for-each-ref.sh
t/t7002-grep.sh
t/t7005-editor.sh
t/t7006-pager.sh
t/t7008-grep-binary.sh [new file with mode: 0755]
t/t7400-submodule-basic.sh
t/t7407-submodule-foreach.sh
t/t7500-commit.sh
t/t7508-status.sh
t/t7600-merge.sh
t/t7700-repack.sh
t/t7701-repack-unpack-unreachable.sh
t/t9118-git-svn-funky-branch-names.sh
t/t9200-git-cvsexportcommit.sh
t/t9400-git-cvsserver-server.sh
t/t9500-gitweb-standalone-no-errors.sh
t/test-lib.sh
trace.c
transport-helper.c
transport.c
tree-diff.c
unpack-trees.c
url.c [new file with mode: 0644]
url.h [new file with mode: 0644]
usage.c
userdiff.c
userdiff.h
wrapper.c
ws.c
wt-status.c
wt-status.h

index dbf1b90c63573b08efc894ed8c1e02de58fd53e4..14e2b6bde9bef55d678da8ba44dc180b039cd3ac 100644 (file)
 /git-remote-https
 /git-remote-ftp
 /git-remote-ftps
+/git-remote-testgit
 /git-repack
 /git-replace
 /git-repo-config
diff --git a/Documentation/RelNotes-1.7.2.txt b/Documentation/RelNotes-1.7.2.txt
new file mode 100644 (file)
index 0000000..a1b5428
--- /dev/null
@@ -0,0 +1,115 @@
+Git v1.7.2 Release Notes (draft)
+================================
+
+Updates since v1.7.1
+--------------------
+
+ * 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.
+
+ * When working from a subdirectory, by default, git does not look for its
+   metadirectory ".git" across filesystems, primarily to help people who
+   have invocations of git in their custom PS1 prompts, as being outside
+   of a git repository would look for ".git" all the way up to the root
+   directory, and NFS mounts are often slow.  DISCOVERY_ACROSS_FILESYSTEM
+   environment variable can be used to tell git not to stop at a
+   filesystem boundary.
+
+ * "git" wrapper learned "-c name=value" option to override configuration
+   variable from the command line.
+
+ * The message from "git am -3" has been improved when conflict
+   resolution ended up making the patch a no-op.
+
+ * "git checkout --orphan newbranch" is similar to "-b newbranch" but
+   prepares to create a root commit that is not connected to any existing
+   commit.
+
+ * "git commit --amend" on a commit with an invalid author-name line that
+   lacks the display name didn't work (fb7749e4).
+
+ * "git cvsserver" can be told to use pserver; its password file can be
+   stored outside the repository.
+
+ * The output from the textconv filter used by "git diff" can be cached to
+   speed up their reuse.
+
+ * "git diff --word-diff=<mode>" extends the existing "--color-words"
+   option, making it more useful in color-challenged environments.
+
+ * The regexp to detect function headers used by "git diff" for PHP has
+   been enhanced for visibility modifiers (public, protected, etc.) to
+   better support PHP5.
+
+ * "diff.noprefix" configuration variable can be used to implicitly
+   ask for "diff --no-prefix" behaviour.
+
+ * "git for-each-ref" learned "%(objectname:short)" that gives the object
+   name abbreviated.
+
+ * Various options to "git grep" (e.g. --count, --name-only) work better
+   with binary files.
+
+ * "git help -w" learned "chrome" and "chromium" browsers.
+
+ * "git log --follow <path>" follows across copies (it used to only follow
+   renames).  This may make the processing more expensive.
+
+ * "git ls-files ../out/side/cwd" works now.
+
+ * "git notes prune" learned "-n" (dry-run) and "-v" options, similar to
+   what "git prune" has.
+
+ * "git patch-id" can be fed a mbox without getting confused by the
+   signature line in the format-patch output.
+
+ * "git remote" learned "set-branches" subcommand.
+
+ * "git revert" learned --strategy option to specify the merge strategy.
+
+ * "git status [-s] --ignored" can be used to list ignored paths.
+
+ * "git status -s -b" shows the current branch in the output.
+
+ * Various "gitweb" enhancements and clean-ups, including syntax
+   highlighting, "plackup" support for instaweb, etc.
+
+
+Fixes since v1.7.1
+------------------
+
+All of the fixes in v1.7.1.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * We didn't URL decode "file:///path/to/repo" correctly when path/to/repo
+   had percent-encoded characters (638794c, 9d2e942).
+
+ * "git clone/fetch/pull" issued an incorrect error message when a ref and
+   a symref that points to the ref were updated at the same time.  This
+   obviously would update them to the same value, and should not result in
+   an error condition (7223dcaf).
+
+ * "git clone" did not configure remote.origin.url correctly for bare
+   clones (df61c889).
+
+ * "git diff --graph" works better with "--color-words" and other options
+   (81fa024..4297c0a).
+
+ * "git diff" could show ambiguous abbreviation of blob object names on
+   its "index" line (3e5a188).
+
+ * "git merge --log" used to replace the custom message given by "-m" with
+   the shortlog, instead of appending to it (tc/merge-m-log).
+
+ * "git reset --hard" started from a wrong directory and a working tree in
+   a nonstandard location is in use got confused (560fb6a1).
+
+ * "git show -C -C" and other corner cases lost diff metainfo output
+   in 1.7.0 (296c6bb).
+
+--
+exec >/var/tmp/1
+O=v1.7.1-423-gae391ec
+echo O=$(git describe HEAD)
+git shortlog --no-merges HEAD ^maint ^$O
index ae174c99dba1d398eef66f13ec15b257eabb7034..9f1c785b7e5e5fdebeaf6fa0425659697c51c19b 100644 (file)
@@ -196,20 +196,17 @@ core.quotepath::
        quoted without `-z` regardless of the setting of this
        variable.
 
-core.autocrlf::
-       If true, makes git convert `CRLF` at the end of lines in text files to
-       `LF` when reading from the work tree, and convert in reverse when
-       writing to the work tree.  The variable can be set to
-       'input', in which case the conversion happens only while
-       reading from the work tree but files are written out to the work
-       tree with `LF` at the end of lines.  A file is considered
-       "text" (i.e. be subjected to the autocrlf mechanism) based on
-       the file's `crlf` attribute, or if `crlf` is unspecified,
-       based on the file's contents.  See linkgit:gitattributes[5].
+core.eol::
+       Sets the line ending type to use in the working directory for
+       files that have the `text` property set.  Alternatives are
+       'lf', 'crlf' and 'native', which uses the platform's native
+       line ending.  The default value is `native`.  See
+       linkgit:gitattributes[5] for more information on end-of-line
+       conversion.
 
 core.safecrlf::
-       If true, makes git check if converting `CRLF` as controlled by
-       `core.autocrlf` is reversible.  Git will verify if a command
+       If true, makes git check if converting `CRLF` is reversible when
+       end-of-line conversion is active.  Git will verify if a command
        modifies a file in the work tree either directly or indirectly.
        For example, committing a file followed by checking out the
        same file should yield the original file in the work tree.  If
@@ -219,7 +216,7 @@ core.safecrlf::
        irreversible conversion but continue the operation.
 +
 CRLF conversion bears a slight chance of corrupting data.
-autocrlf=true will convert CRLF to LF during commit and LF to
+When it is enabled, git will convert CRLF to LF during commit and LF to
 CRLF during checkout.  A file that contains a mixture of LF and
 CRLF before the commit cannot be recreated by git.  For text
 files this is the right thing to do: it corrects line endings
@@ -243,15 +240,25 @@ converting CRLFs corrupts data.
 +
 Note, this safety check does not mean that a checkout will generate a
 file identical to the original file for a different setting of
-`core.autocrlf`, but only for the current one.  For example, a text
-file with `LF` would be accepted with `core.autocrlf=input` and could
-later be checked out with `core.autocrlf=true`, in which case the
+`core.eol` and `core.autocrlf`, but only for the current one.  For
+example, a text file with `LF` would be accepted with `core.eol=lf`
+and could later be checked out with `core.eol=crlf`, in which case the
 resulting file would contain `CRLF`, although the original file
 contained `LF`.  However, in both work trees the line endings would be
 consistent, that is either all `LF` or all `CRLF`, but never mixed.  A
 file with mixed line endings would be reported by the `core.safecrlf`
 mechanism.
 
+core.autocrlf::
+       Setting this variable to "true" is almost the same as setting
+       the `text` attribute to "auto" on all files except that text
+       files are not guaranteed to be normalized: files that contain
+       `CRLF` in the repository will not be touched.  Use this
+       setting if you want to have `CRLF` line endings in your
+       working directory even though the repository does not have
+       normalized line endings.  This variable can be set to 'input',
+       in which case no output conversion is performed.
+
 core.symlinks::
        If false, symbolic links are checked out as small plain files that
        contain the link text. linkgit:git-update-index[1] and
@@ -481,6 +488,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
@@ -790,6 +799,8 @@ diff.mnemonicprefix::
        standard "a/" and "b/" depending on what is being compared.  When
        this configuration is in effect, reverse diff output also swaps
        the order of the prefixes:
+diff.noprefix::
+       If set, 'git diff' does not show any source or destination prefix.
 `git diff`;;
        compares the (i)ndex and the (w)ork tree;
 `git diff HEAD`;;
@@ -938,13 +949,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
@@ -969,13 +986,15 @@ gitcvs.logfile::
        various stuff. See linkgit:git-cvsserver[1].
 
 gitcvs.usecrlfattr::
-       If true, the server will look up the `crlf` attribute for
-       files to determine the '-k' modes to use. If `crlf` is set,
-       the '-k' mode will be left blank, so cvs clients will
-       treat it as text. If `crlf` is explicitly unset, the file
+       If true, the server will look up the end-of-line conversion
+       attributes for files to determine the '-k' modes to use. If
+       the attributes force git to treat a file as text,
+       the '-k' mode will be left blank so cvs clients will
+       treat it as text. If they suppress text conversion, the file
        will be set with '-kb' mode, which suppresses any newline munging
-       the client might otherwise do. If `crlf` is not specified,
-       then 'gitcvs.allbinary' is used. See linkgit:gitattributes[5].
+       the client might otherwise do. If the attributes do not allow
+       the file type to be determined, then 'gitcvs.allbinary' is
+       used. See linkgit:gitattributes[5].
 
 gitcvs.allbinary::
        This is used if 'gitcvs.usecrlfattr' does not resolve
@@ -1262,6 +1281,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.
@@ -1460,6 +1486,16 @@ pager.<cmd>::
        it takes precedence over this option.  To disable pagination for
        all commands, set `core.pager` or `GIT_PAGER` to `cat`.
 
+pretty.<name>::
+       Alias for a --pretty= format string, as specified in
+       linkgit:git-log[1]. Any aliases defined here can be used just
+       as the built-in pretty formats could. For example,
+       running `git config pretty.changelog "format:{asterisk} %H %s"`
+       would cause the invocation `git log --pretty=changelog`
+       to be equivalent to running `git log "--pretty=format:{asterisk} %H %s"`.
+       Note that an alias with the same name as a built-in format
+       will be silently ignored.
+
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
@@ -1572,7 +1608,9 @@ remote.<name>.uploadpack::
 
 remote.<name>.tagopt::
        Setting this value to \--no-tags disables automatic tag following when
-       fetching from remote <name>
+       fetching from remote <name>. Setting it to \--tags will fetch every
+       tag from remote <name>, even if they are not reachable from remote
+       branch heads.
 
 remote.<name>.vcs::
        Setting this to a value <vcs> will cause git to interact with
index 3070dddfe2870b235dced1cf53da22d33ca8fb40..e745a3ccdc923b068e791a778a7c73b664b230d2 100644 (file)
@@ -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::
index 7146288ad6b8a2bc8b44513e76d87cdb628827e9..261dd90c381eccbd87d73325478e1d5abcf05e15 100644 (file)
@@ -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>...]
 
@@ -98,6 +98,31 @@ 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 'orphan' branch, named <new_branch>, started from
+       <start_point> and switch to it.  The first commit made on this
+       new branch will have no parents and it will be the root of a new
+       history totally disconnected from all the other branches and
+       commits.
++
+The index and the working tree are adjusted as if you had previously run
+"git checkout <start_point>".  This allows you to start a new history
+that records a set of paths similar to <start_point> by easily running
+"git commit -a" to make the root commit.
++
+This can be useful when you want to publish the tree from a commit
+without exposing its full history. You might want to do this to publish
+an open source branch of a project whose current tree is "clean", but
+whose full history contains proprietary or otherwise encumbered bits of
+code.
++
+If you want to start a disconnected history that records a set of paths
+that is totally different from the one of <start_point>, then you should
+clear the index and the working tree right after creating the orphan
+branch by running "git rm -rf ." from the top level of the working tree.
+Afterwards you will be ready to prepare your new files, repopulating the
+working tree, by copying them from elsewhere, extracting a tarball, etc.
+
 -m::
 --merge::
        When switching branches,
index 69eb86e4506411b0d8029f849fd4ebff47166d05..c28603ecf58bd065cc8f7c3c4436f1c0f82a7d2e 100644 (file)
@@ -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>...]
 
@@ -132,6 +132,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',
index dbb053ee17fbc9e254aac42bf87d204655e00614..7004dd2decfb2a9cf82b248aae1f848beaf885cb 100644 (file)
@@ -72,9 +72,6 @@ plugin. Most functionality works fine with both of these clients.
 LIMITATIONS
 -----------
 
-Currently cvsserver works over SSH connections for read/write clients, and
-over pserver for anonymous CVS access.
-
 CVS clients cannot tag, branch or perform GIT merges.
 
 'git-cvsserver' maps GIT branches to CVS modules. This is very different
@@ -84,7 +81,7 @@ one or more directories.
 INSTALLATION
 ------------
 
-1. If you are going to offer anonymous CVS access via pserver, add a line in
+1. If you are going to offer CVS access via pserver, add a line in
    /etc/inetd.conf like
 +
 --
@@ -101,6 +98,38 @@ looks like
    cvspserver stream tcp nowait nobody /usr/bin/git-cvsserver git-cvsserver pserver
 
 ------
+
+Only anonymous access is provided by pserve by default. To commit you
+will have to create pserver accounts, simply add a gitcvs.authdb
+setting in the config file of the repositories you want the cvsserver
+to allow writes to, for example:
+
+------
+
+   [gitcvs]
+       authdb = /etc/cvsserver/passwd
+
+------
+The format of these files is username followed by the crypted password,
+for example:
+
+------
+   myuser:$1Oyx5r9mdGZ2
+   myuser:$1$BA)@$vbnMJMDym7tA32AamXrm./
+------
+You can use the 'htpasswd' facility that comes with Apache to make these
+files, but Apache's MD5 crypt method differs from the one used by most C
+library's crypt() function, so don't use the -m option.
+
+Alternatively you can produce the password with perl's crypt() operator:
+-----
+   perl -e 'my ($user, $pass) = @ARGV; printf "%s:%s\n", $user, crypt($user, $pass)' $USER password
+-----
+
+Then provide your password via the pserver method, for example:
+------
+   cvs -d:pserver:someuser:somepassword <at> server/path/repo.git co <HEAD_name>
+------
 No special setup is needed for SSH access, other than having GIT tools
 in the PATH. If you have clients that do not accept the CVS_SERVER
 environment variable, you can rename 'git-cvsserver' to `cvs`.
@@ -340,16 +369,13 @@ By default the server leaves the '-k' mode blank for all files,
 which causes the cvs client to treat them as a text files, subject
 to crlf conversion on some platforms.
 
-You can make the server use `crlf` attributes to set the '-k' modes
-for files by setting the `gitcvs.usecrlfattr` config variable.
-In this case, if `crlf` is explicitly unset ('-crlf'), then the
-server will set '-kb' mode for binary files. If `crlf` is set,
-then the '-k' mode will explicitly be left blank.  See
-also linkgit:gitattributes[5] for more information about the `crlf`
-attribute.
+You can make the server use the end-of-line conversion attributes to
+set the '-k' modes for files by setting the `gitcvs.usecrlfattr`
+config variable.  See linkgit:gitattributes[5] for more information
+about end-of-line conversion.
 
 Alternatively, if `gitcvs.usecrlfattr` config is not enabled
-or if the `crlf` attribute is unspecified for a filename, then
+or the attributes do not allow automatic detection for a filename, then
 the server uses the `gitcvs.allbinary` config for the default setting.
 If `gitcvs.allbinary` is set, then file not otherwise
 specified will default to '-kb' mode. Otherwise the '-k' mode
index 7e83288d1846a7fcd53ec46776160a2f5ffbad84..390d85ccaea6ada3f3bce6148bcdd84d4f0bc2ac 100644 (file)
@@ -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''
index 189573a3b3d459822b465d39db2f90001ffc98d3..a9e0882e9b81fe15780785dc85e3fb2782480ae1 100644 (file)
@@ -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.
index a1f17df0744361ab999314b1834c2e2384af90bd..2c3c4d299472a265bfc4486816933ca3dc69b44f 100644 (file)
@@ -29,7 +29,7 @@ OPTIONS
        The HTTP daemon command-line that will be executed.
        Command-line options may be specified here, and the
        configuration file will be added at the end of the command-line.
-       Currently apache2, lighttpd, mongoose and webrick are supported.
+       Currently apache2, lighttpd, mongoose, plackup and webrick are supported.
        (Default: lighttpd)
 
 -m::
index d7f6a9cc3ee335c5437e27dd362ef47c8753ff43..0e6ff3182347c03a9ae9c3992fd1e8a6db2867f9 100644 (file)
@@ -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
@@ -56,7 +57,7 @@ include::diff-options.txt[]
        commits, and doesn't limit diff for those commits.
 
 --follow::
-       Continue listing the history of a file beyond renames.
+       Continue listing the history of a file beyond renames/copies.
 
 --log-size::
        Before the log message print out its size in bytes. Intended
index de63ef0745a82136eeee6a2f0993984f2ed2f4ec..5540af5d1667aad16ac11c53593921733c4af56f 100644 (file)
@@ -15,7 +15,7 @@ SYNOPSIS
 'git notes' edit [<object>]
 'git notes' show [<object>]
 'git notes' remove [<object>]
-'git notes' prune
+'git notes' prune [-n | -v]
 
 
 DESCRIPTION
@@ -128,6 +128,13 @@ OPTIONS
        'GIT_NOTES_REF' and the "core.notesRef" configuration.  The ref
        is taken to be in `refs/notes/` if it is not qualified.
 
+-n::
+       Do not remove anything; just report the object names whose notes
+       would be removed.
+
+-v::
+       Report all object names whose notes are removed.
+
 
 DISCUSSION
 ----------
index 3fc599c0c7d5495ada81b20e4d37fa4936708270..aa021b0cb803a9cd9f08566f6a6fe407eb025e4b 100644 (file)
@@ -10,10 +10,11 @@ SYNOPSIS
 --------
 [verse]
 'git remote' [-v | --verbose]
-'git remote add' [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
+'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror] <name> <url>
 'git remote rename' <old> <new>
 'git remote rm' <name>
 'git remote set-head' <name> (-a | -d | <branch>)
+'git remote set-branches' <name> [--add] <branch>...
 'git remote set-url' [--push] <name> <newurl> [<oldurl>]
 'git remote set-url --add' [--push] <name> <newurl>
 'git remote set-url --delete' [--push] <name> <url>
@@ -51,6 +52,12 @@ update remote-tracking branches <name>/<branch>.
 With `-f` option, `git fetch <name>` is run immediately after
 the remote information is set up.
 +
+With `--tags` option, `git fetch <name>` imports every tag from the
+remote repository.
++
+With `--no-tags` option, `git fetch <name>` does not import tags from
+the remote repository.
++
 With `-t <branch>` option, instead of the default glob
 refspec for the remote to track all branches under
 `$GIT_DIR/remotes/<name>/`, a refspec to track only `<branch>`
@@ -104,6 +111,18 @@ remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to
 `refs/remotes/origin/master` already exists; if not it must be fetched first.
 +
 
+'set-branches'::
+
+Changes the list of branches tracked by the named remote.
+This can be used to track a subset of the available remote branches
+after the initial setup for a remote.
++
+The named branches will be interpreted as if specified with the
+`-t` option on the 'git remote add' command line.
++
+With `--add`, instead of replacing the list of currently tracked
+branches, adds to that list.
+
 'set-url'::
 
 Changes URL remote points to. Sets first URL remote points to matching
index 2d4bbfcaf4cc2d2b92ad827662dc3b4b4ef355c0..fd0fe7cb56cf3ba163cef467f5fc9c35b0ea963b 100644 (file)
@@ -27,6 +27,10 @@ OPTIONS
 --short::
        Give the output in the short-format.
 
+-b::
+--branch::
+       Show the branch and tracking info even in short-format.
+
 --porcelain::
        Give the output in a stable, easy-to-parse format for scripts.
        Currently this is identical to --short output, but is guaranteed
@@ -120,6 +124,10 @@ Ignored files are not listed.
     ?           ?    untracked
     -------------------------------------------------
 
+If -b is used the short-format status is preceded by a line
+
+## branchname tracking info
+
 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
@@ -128,7 +136,7 @@ order is reversed (e.g 'from -> to' becomes 'to from'). Second, a NUL
 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.
+backslash-escaping is performed. Fourth, there is no branch line.
 
 CONFIGURATION
 -------------
index 2502531a3dfd0cee67f6d1d9fc1db1d69cf20e30..cdabfd29add5752caab24a1715427c0e4eba9a11 100644 (file)
@@ -145,10 +145,12 @@ summary::
 
 foreach::
        Evaluates an arbitrary shell command in each checked out submodule.
-       The command has access to the variables $name, $path and $sha1:
+       The command has access to the variables $name, $path, $sha1 and
+       $toplevel:
        $name is the name of the relevant submodule section in .gitmodules,
        $path is the name of the submodule directory relative to the
-       superproject, and $sha1 is the commit as recorded in the superproject.
+       superproject, $sha1 is the commit as recorded in the superproject,
+       and $toplevel is the absolute path to the top-level of the superproject.
        Any submodules defined in the superproject but not checked out are
        ignored by this command. Unless given --quiet, foreach prints the name
        of each submodule before evaluating the command.
index 99f3c1ea6c41b1cda7069f82bd8e38bbbe27a711..b09bd9761faa42f2bc87306ee5c1890647dce769 100644 (file)
@@ -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
index c4024d0edd9f77c49e6211c6950c6ccb775a70ff..bec6348dab45580d90b38dac5667434ba661bf94 100644 (file)
@@ -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
@@ -228,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
@@ -538,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'::
index d892e642eded92fd0409e39fc34c005f88bc4a9b..564586b943f439cb5ae04c4e76bc19d093c625da 100644 (file)
@@ -92,53 +92,154 @@ such as 'git checkout' and 'git merge' run.  They also affect how
 git stores the contents you prepare in the working tree in the
 repository upon 'git add' and 'git commit'.
 
-`crlf`
+`text`
 ^^^^^^
 
-This attribute controls the line-ending convention.
+This attribute enables and controls end-of-line normalization.  When a
+text file is normalized, its line endings are converted to LF in the
+repository.  To control what line ending style is used in the working
+directory, use the `eol` attribute for a single file and the
+`core.eol` configuration variable for all text files.
 
 Set::
 
-       Setting the `crlf` attribute on a path is meant to mark
-       the path as a "text" file.  'core.autocrlf' conversion
-       takes place without guessing the content type by
-       inspection.
+       Setting the `text` attribute on a path enables end-of-line
+       normalization and marks the path as a text file.  End-of-line
+       conversion takes place without guessing the content type.
 
 Unset::
 
-       Unsetting the `crlf` attribute on a path tells git not to
+       Unsetting the `text` attribute on a path tells git not to
        attempt any end-of-line conversion upon checkin or checkout.
 
+Set to string value "auto"::
+
+       When `text` is set to "auto", the path is marked for automatic
+       end-of-line normalization.  If git decides that the content is
+       text, its line endings are normalized to LF on checkin.
+
 Unspecified::
 
-       Unspecified `crlf` attribute tells git to apply the
-       `core.autocrlf` conversion when the file content looks
-       like text.
+       If the `text` attribute is unspecified, git uses the
+       `core.autocrlf` configuration variable to determine if the
+       file should be converted.
 
-Set to string value "input"::
+Any other value causes git to act as if `text` has been left
+unspecified.
 
-       This is similar to setting the attribute to `true`, but
-       also forces git to act as if `core.autocrlf` is set to
-       `input` for the path.
+`eol`
+^^^^^
 
-Any other value set to `crlf` attribute is ignored and git acts
-as if the attribute is left unspecified.
+This attribute sets a specific line-ending style to be used in the
+working directory.  It enables end-of-line normalization without any
+content checks, effectively setting the `text` attribute.
 
+Set to string value "crlf"::
 
-The `core.autocrlf` conversion
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+       This setting forces git to normalize line endings for this
+       file on checkin and convert them to CRLF when the file is
+       checked out.
+
+Set to string value "lf"::
+
+       This setting forces git to normalize line endings to LF on
+       checkin and prevents conversion to CRLF when the file is
+       checked out.
+
+Backwards compatibility with `crlf` attribute
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For backwards compatibility, the `crlf` attribute is interpreted as
+follows:
+
+------------------------
+crlf           text
+-crlf          -text
+crlf=input     eol=lf
+------------------------
+
+End-of-line conversion
+^^^^^^^^^^^^^^^^^^^^^^
+
+While git normally leaves file contents alone, it can be configured to
+normalize line endings to LF in the repository and, optionally, to
+convert them to CRLF when files are checked out.
+
+Here is an example that will make git normalize .txt, .vcproj and .sh
+files, ensure that .vcproj files have CRLF and .sh files have LF in
+the working directory, and prevent .jpg files from being normalized
+regardless of their content.
+
+------------------------
+*.txt          text
+*.vcproj       eol=crlf
+*.sh           eol=lf
+*.jpg          -text
+------------------------
+
+Other source code management systems normalize all text files in their
+repositories, and there are two ways to enable similar automatic
+normalization in git.
+
+If you simply want to have CRLF line endings in your working directory
+regardless of the repository you are working with, you can set the
+config variable "core.autocrlf" without changing any attributes.
 
-If the configuration variable `core.autocrlf` is false, no
-conversion is done.
+------------------------
+[core]
+       autocrlf = true
+------------------------
+
+This does not force normalization of all text files, but does ensure
+that text files that you introduce to the repository have their line
+endings normalized to LF when they are added, and that files that are
+already normalized in the repository stay normalized.
+
+If you want to interoperate with a source code management system that
+enforces end-of-line normalization, or you simply want all text files
+in your repository to be normalized, you should instead set the `text`
+attribute to "auto" for _all_ files.
+
+------------------------
+*      text=auto
+------------------------
+
+This ensures that all files that git considers to be text will have
+normalized (LF) line endings in the repository.  The `core.eol`
+configuration variable controls which line endings git will use for
+normalized files in your working directory; the default is to use the
+native line ending for your platform, or CRLF if `core.autocrlf` is
+set.
+
+NOTE: When `text=auto` normalization is enabled in an existing
+repository, any text files containing CRLFs should be normalized.  If
+they are not they will be normalized the next time someone tries to
+change them, causing unfortunate misattribution.  From a clean working
+directory:
+
+-------------------------------------------------
+$ echo "* text=auto" >>.gitattributes
+$ rm .git/index     # Remove the index to force git to
+$ git reset         # re-scan the working directory
+$ git status        # Show files that will be normalized
+$ git add -u
+$ git add .gitattributes
+$ git commit -m "Introduce end-of-line normalization"
+-------------------------------------------------
+
+If any files that should not be normalized show up in 'git status',
+unset their `text` attribute before running 'git add -u'.
 
-When `core.autocrlf` is true, it means that the platform wants
-CRLF line endings for files in the working tree, and you want to
-convert them back to the normal LF line endings when checking
-in to the repository.
+------------------------
+manual.pdf     -text
+------------------------
 
-When `core.autocrlf` is set to "input", line endings are
-converted to LF upon checkin, but there is no conversion done
-upon checkout.
+Conversely, text files that git does not detect can have normalization
+enabled manually.
+
+------------------------
+weirdchars.txt text
+------------------------
 
 If `core.safecrlf` is set to "true" or "warn", git verifies if
 the conversion is reversible for the current setting of
@@ -223,11 +324,11 @@ Interaction between checkin/checkout attributes
 In the check-in codepath, the worktree file is first converted
 with `filter` driver (if specified and corresponding driver
 defined), then the result is processed with `ident` (if
-specified), and then finally with `crlf` (again, if specified
+specified), and then finally with `text` (again, if specified
 and applicable).
 
 In the check-out codepath, the blob content is first converted
-with `crlf`, and then `ident` and fed to `filter`.
+with `text`, and then `ident` and fed to `filter`.
 
 
 Generating diff text
@@ -360,7 +461,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 +515,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
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -631,7 +752,7 @@ You do not want any end-of-line conversions applied to, nor textual diffs
 produced for, any binary file you track.  You would need to specify e.g.
 
 ------------
-*.jpg -crlf -diff
+*.jpg -text -diff
 ------------
 
 but that may become cumbersome, when you have many attributes.  Using
@@ -644,7 +765,7 @@ the same time.  The system knows a built-in attribute macro, `binary`:
 
 which is equivalent to the above.  Note that the attribute macros can only
 be "Set" (see the above example that sets "binary" macro as if it were an
-ordinary attribute --- setting it in turn unsets "crlf" and "diff").
+ordinary attribute --- setting it in turn unsets "text" and "diff").
 
 
 DEFINING ATTRIBUTE MACROS
@@ -655,7 +776,7 @@ at the toplevel (i.e. not in any subdirectory).  The built-in attribute
 macro "binary" is equivalent to:
 
 ------------
-[attr]binary -diff -crlf
+[attr]binary -diff -text
 ------------
 
 
index 2135a8ee1f4f56a8c799437949ba76d7526164c0..34d02a24188921ebac1b70f7cf0c6517a45e8fb1 100755 (executable)
@@ -12,7 +12,7 @@ do
        then
                : did not match
        elif test -f "$T/$h" &&
-          diff -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h"
+          $DIFF -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h"
        then
                :; # up to date
        else
index c85a52c0cc27a187abbdea17cd25cec51b808064..8c68ce94f990b0c7f3bc909cc3dc0c4e2a37cf3b 100644 (file)
@@ -11,7 +11,12 @@ have limited your view of history: for example, if you are
 only interested in changes related to a certain directory or
 file.
 
-Here are some additional details for each format:
+There are several built-in formats, and you can define
+additional formats by setting a pretty.<name>
+config option to either another format name, or a
+'format:' string, as described below (see
+linkgit:git-config[1]). Here are the details of the
+built-in formats:
 
 * 'oneline'
 
@@ -123,6 +128,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\}`
index 44876fa703578f4952d6e928993e15ddec70439c..f18b4f4817448530a5adbe2c8835bb7791add42a 100644 (file)
@@ -231,8 +231,9 @@ The function pointer in .proc has the following signature:
 
 
 There are serious restrictions on what the asynchronous function can do
-because this facility is implemented by a pipe to a forked process on
-UNIX, but by a thread in the same address space on Windows:
+because this facility is implemented by a thread in the same address
+space on most platforms (when pthreads is available), but by a pipe to
+a forked process otherwise:
 
 . It cannot change the program's state (global variables, environment,
   etc.) in a way that the caller notices; in other words, .in and .out
index 82fd72652b6d35837b8786005024263ac61630dd..e45513dee938dde3a8428a833fb43023b04ca95b 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.1
+DEF_VER=v1.7.1.GIT
 
 LF='
 '
index b18768e0aedfbf246173293f74049456070deec3..9aca8a16d955e904b4a8f07e70fba754ac65fcc3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,12 @@ all::
 # Define SANE_TOOL_PATH to a colon-separated list of paths to prepend
 # to PATH if your tools in /usr/bin are broken.
 #
+# Define SOCKLEN_T to a suitable type (such as 'size_t') if your
+# system headers do not define a socklen_t type.
+#
+# Define INLINE to a suitable substitute (such as '__inline' or '') if git
+# fails to compile with errors about undefined inline functions or similar.
+#
 # Define SNPRINTF_RETURNS_BOGUS if your are on a system which snprintf()
 # or vsnprintf() return -1 instead of number of characters which would
 # have been written to the final string if enough space had been available.
@@ -227,6 +233,8 @@ all::
 #
 # Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded
 # dependency rules.
+#
+# Define NATIVE_CRLF if your platform uses CRLF for line endings.
 
 GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -249,7 +257,7 @@ endif
 
 CFLAGS = -g -O2 -Wall
 LDFLAGS =
-ALL_CFLAGS = $(CFLAGS)
+ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS)
 ALL_LDFLAGS = $(LDFLAGS)
 STRIP ?= strip
 
@@ -272,6 +280,7 @@ mandir = share/man
 infodir = share/info
 gitexecdir = libexec/git-core
 sharedir = $(prefix)/share
+gitwebdir = $(sharedir)/gitweb
 template_dir = share/git-core/templates
 htmldir = share/doc/git-doc
 ifeq ($(prefix),/usr)
@@ -285,11 +294,12 @@ lib = lib
 # DESTDIR=
 pathsep = :
 
-export prefix bindir sharedir sysconfdir
+export prefix bindir sharedir sysconfdir gitwebdir
 
 CC = gcc
 AR = ar
 RM = rm -f
+DIFF = diff
 TAR = tar
 FIND = find
 INSTALL = install
@@ -297,6 +307,7 @@ RPMBUILD = rpmbuild
 TCL_PATH = tclsh
 TCLTK_PATH = wish
 PTHREAD_LIBS = -lpthread
+PTHREAD_CFLAGS =
 
 export TCL_PATH TCLTK_PATH
 
@@ -369,6 +380,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)) \
@@ -489,6 +502,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
@@ -578,6 +592,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
@@ -623,6 +638,7 @@ LIB_OBJS += tree-diff.o
 LIB_OBJS += tree.o
 LIB_OBJS += tree-walk.o
 LIB_OBJS += unpack-trees.o
+LIB_OBJS += url.o
 LIB_OBJS += usage.o
 LIB_OBJS += userdiff.o
 LIB_OBJS += utf8.o
@@ -735,6 +751,13 @@ EXTLIBS =
 # because maintaining the nesting to match is a pain.  If
 # we had "elif" things would have been much nicer...
 
+ifeq ($(uname_S),OSF1)
+       # Need this for u_short definitions et al
+       BASIC_CFLAGS += -D_OSF_SOURCE
+       SOCKLEN_T = int
+       NO_STRTOULL = YesPlease
+       NO_NSEC = YesPlease
+endif
 ifeq ($(uname_S),Linux)
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
@@ -809,6 +832,18 @@ ifeq ($(uname_S),SunOS)
        NO_MKDTEMP = YesPlease
        NO_MKSTEMPS = YesPlease
        NO_REGEX = YesPlease
+       ifeq ($(uname_R),5.6)
+               SOCKLEN_T = int
+               NO_HSTRERROR = YesPlease
+               NO_IPV6 = YesPlease
+               NO_SOCKADDR_STORAGE = YesPlease
+               NO_UNSETENV = YesPlease
+               NO_SETENV = YesPlease
+               NO_STRLCPY = YesPlease
+               NO_C99_FORMAT = YesPlease
+               NO_STRTOUMAX = YesPlease
+               GIT_TEST_CMP = cmp
+       endif
        ifeq ($(uname_R),5.7)
                NEEDS_RESOLV = YesPlease
                NO_IPV6 = YesPlease
@@ -818,18 +853,21 @@ ifeq ($(uname_S),SunOS)
                NO_STRLCPY = YesPlease
                NO_C99_FORMAT = YesPlease
                NO_STRTOUMAX = YesPlease
+               GIT_TEST_CMP = cmp
        endif
        ifeq ($(uname_R),5.8)
                NO_UNSETENV = YesPlease
                NO_SETENV = YesPlease
                NO_C99_FORMAT = YesPlease
                NO_STRTOUMAX = YesPlease
+               GIT_TEST_CMP = cmp
        endif
        ifeq ($(uname_R),5.9)
                NO_UNSETENV = YesPlease
                NO_SETENV = YesPlease
                NO_C99_FORMAT = YesPlease
                NO_STRTOUMAX = YesPlease
+               GIT_TEST_CMP = cmp
        endif
        INSTALL = /usr/ucb/install
        TAR = gtar
@@ -907,7 +945,13 @@ ifeq ($(uname_S),AIX)
        BASIC_CFLAGS += -D_LARGE_FILES
        ifeq ($(shell expr "$(uname_V)" : '[1234]'),1)
                NO_PTHREADS = YesPlease
+       else
+               PTHREAD_LIBS = -lpthread
+       endif
+       ifeq ($(shell expr "$(uname_V).$(uname_R)" : '5\.1'),3)
+               INLINE=''
        endif
+       GIT_TEST_CMP = cmp
 endif
 ifeq ($(uname_S),GNU)
        # GNU/Hurd
@@ -952,6 +996,7 @@ ifeq ($(uname_S),IRIX64)
        NEEDS_LIBGEN = YesPlease
 endif
 ifeq ($(uname_S),HP-UX)
+       INLINE = __inline
        NO_IPV6=YesPlease
        NO_SETENV=YesPlease
        NO_STRCASESTR=YesPlease
@@ -963,6 +1008,20 @@ ifeq ($(uname_S),HP-UX)
        NO_HSTRERROR = YesPlease
        NO_SYS_SELECT_H = YesPlease
        SNPRINTF_RETURNS_BOGUS = YesPlease
+       NO_NSEC = YesPlease
+       ifeq ($(uname_R),B.11.00)
+               NO_INET_NTOP = YesPlease
+               NO_INET_PTON = YesPlease
+       endif
+       ifeq ($(uname_R),B.10.20)
+               # Override HP-UX 11.x setting:
+               INLINE =
+               SOCKLEN_T = size_t
+               NO_PREAD = YesPlease
+               NO_INET_NTOP = YesPlease
+               NO_INET_PTON = YesPlease
+       endif
+       GIT_TEST_CMP = cmp
 endif
 ifeq ($(uname_S),Windows)
        GIT_VERSION := $(GIT_VERSION).MSVC
@@ -999,6 +1058,7 @@ ifeq ($(uname_S),Windows)
        NO_CURL = YesPlease
        NO_PYTHON = YesPlease
        BLK_SHA1 = YesPlease
+       NATIVE_CRLF = YesPlease
 
        CC = compat/vcbuild/scripts/clink.pl
        AR = compat/vcbuild/scripts/lib.pl
@@ -1036,7 +1096,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_STRTOUMAX = YesPlease
        NO_MKDTEMP = YesPlease
        NO_MKSTEMPS = YesPlease
-       SNPRINTF_RETURNS_BOGUS = YesPlease
        NO_SVN_TESTS = YesPlease
        NO_PERL_MAKEMAKER = YesPlease
        RUNTIME_PREFIX = YesPlease
@@ -1073,6 +1132,7 @@ endif
 -include config.mak
 
 ifdef CHECK_HEADER_DEPENDENCIES
+COMPUTE_HEADER_DEPENDENCIES =
 USE_COMPUTED_HEADER_DEPENDENCIES =
 endif
 
@@ -1088,6 +1148,14 @@ else
 BROKEN_PATH_FIX = '/^\# @@BROKEN_PATH_FIX@@$$/d'
 endif
 
+ifneq (,$(INLINE))
+       BASIC_CFLAGS += -Dinline=$(INLINE)
+endif
+
+ifneq (,$(SOCKLEN_T))
+       BASIC_CFLAGS += -Dsocklen_t=$(SOCKLEN_T)
+endif
+
 ifeq ($(uname_S),Darwin)
        ifndef NO_FINK
                ifeq ($(shell test -d /sw/lib && echo y),y)
@@ -1359,6 +1427,7 @@ endif
 ifdef NO_PTHREADS
        BASIC_CFLAGS += -DNO_PTHREADS
 else
+       BASIC_CFLAGS += $(PTHREAD_CFLAGS)
        EXTLIBS += $(PTHREAD_LIBS)
        LIB_OBJS += thread-utils.o
 endif
@@ -1383,6 +1452,10 @@ ifdef USE_NED_ALLOCATOR
        COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
 endif
 
+ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
+       export GIT_TEST_CMP_USE_COPIED_CONTEXT
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
 endif
@@ -1439,11 +1512,13 @@ gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 htmldir_SQ = $(subst ','\'',$(htmldir))
 prefix_SQ = $(subst ','\'',$(prefix))
+gitwebdir_SQ = $(subst ','\'',$(gitwebdir))
 
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
+DIFF_SQ = $(subst ','\'',$(DIFF))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
 
@@ -1470,7 +1545,7 @@ endif
 ALL_CFLAGS += $(BASIC_CFLAGS)
 ALL_LDFLAGS += $(BASIC_LDFLAGS)
 
-export TAR INSTALL DESTDIR SHELL_PATH
+export DIFF TAR INSTALL DESTDIR SHELL_PATH
 
 
 ### Build rules
@@ -1532,6 +1607,7 @@ define cmd_munge_script
 $(RM) $@ $@+ && \
 sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
     -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
+    -e 's|@@DIFF@@|$(DIFF_SQ)|' \
     -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
     -e $(BROKEN_PATH_FIX) \
@@ -1559,11 +1635,10 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        sed -e '1{' \
            -e '        s|#!.*perl|#!$(PERL_PATH_SQ)|' \
            -e '        h' \
-           -e '        s=.*=use lib (split(/$(pathsep)/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \
+           -e '        s=.*=use lib (split(/$(pathsep)/, $$ENV{GITPERLLIB} || "'"$$INSTLIBDIR"'"));=' \
            -e '        H' \
            -e '        x' \
            -e '}' \
-           -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $@.perl >$@+ && \
        chmod +x $@+ && \
@@ -1575,45 +1650,38 @@ gitweb:
        $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all
 
 ifdef JSMIN
-GITWEB_PROGRAMS += gitweb/gitweb.min.js
-GITWEB_JS = gitweb/gitweb.min.js
+GITWEB_PROGRAMS += gitweb/static/gitweb.min.js
+GITWEB_JS = gitweb/static/gitweb.min.js
 else
-GITWEB_JS = gitweb/gitweb.js
+GITWEB_JS = gitweb/static/gitweb.js
 endif
 ifdef CSSMIN
-GITWEB_PROGRAMS += gitweb/gitweb.min.css
-GITWEB_CSS = gitweb/gitweb.min.css
+GITWEB_PROGRAMS += gitweb/static/gitweb.min.css
+GITWEB_CSS = gitweb/static/gitweb.min.css
 else
-GITWEB_CSS = gitweb/gitweb.css
+GITWEB_CSS = gitweb/static/gitweb.css
 endif
 OTHER_PROGRAMS +=  gitweb/gitweb.cgi  $(GITWEB_PROGRAMS)
 gitweb/gitweb.cgi: gitweb/gitweb.perl $(GITWEB_PROGRAMS)
        $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
 
 ifdef JSMIN
-gitweb/gitweb.min.js: gitweb/gitweb.js
+gitweb/static/gitweb.min.js: gitweb/static/gitweb.js
        $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
 endif # JSMIN
 ifdef CSSMIN
-gitweb/gitweb.min.css: gitweb/gitweb.css
+gitweb/static/gitweb.min.css: gitweb/static/gitweb.css
        $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
 endif # CSSMIN
 
 
-git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js
+git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/static/gitweb.css gitweb/static/gitweb.js
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-           -e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \
-           -e '/@@GITWEB_CGI@@/d' \
-           -e '/@@GITWEB_CSS@@/r $(GITWEB_CSS)' \
-           -e '/@@GITWEB_CSS@@/d' \
-           -e '/@@GITWEB_JS@@/r $(GITWEB_JS)' \
-           -e '/@@GITWEB_JS@@/d' \
+           -e 's|@@GITWEBDIR@@|$(gitwebdir_SQ)|g' \
            -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
-            -e 's|@@GITWEB_CSS_NAME@@|$(GITWEB_CSS)|' \
-            -e 's|@@GITWEB_JS_NAME@@|$(GITWEB_JS)|' \
            $@.sh > $@+ && \
        chmod +x $@+ && \
        mv $@+ $@
@@ -1634,13 +1702,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 $@+ && \
@@ -1670,7 +1733,10 @@ git.o git.spec \
 
 TEST_OBJS := $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
 GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
-       git.o http.o http-walker.o remote-curl.o
+       git.o
+ifndef NO_CURL
+       GIT_OBJS += http.o http-walker.o remote-curl.o
+endif
 XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
        xdiff/xmerge.o xdiff/xpatience.o
 OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS)
@@ -1887,10 +1953,18 @@ GIT-CFLAGS: FORCE
 GIT-BUILD-OPTIONS: FORCE
        @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
        @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
+       @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@
+       @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@
        @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
        @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
        @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
+ifdef GIT_TEST_CMP
+       @echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@
+endif
+ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
+       @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
+endif
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -1985,6 +2059,7 @@ install: all
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
 ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
+       $(MAKE) -C gitweb install
 endif
 ifndef NO_PYTHON
        $(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
@@ -2019,6 +2094,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
 
index 136c1b6afa6bc9cee85b018cd5df66c639c3fc91..3da01a5622c0b6c581a701dbb5421fb84e87df80 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.7.1.1.txt
\ No newline at end of file
+Documentation/RelNotes-1.7.2.txt
\ No newline at end of file
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644 (file)
index 0000000..d399de2
--- /dev/null
@@ -0,0 +1,40 @@
+dnl Check for socklen_t: historically on BSD it is an int, and in
+dnl POSIX 1g it is a type of its own, but some platforms use different
+dnl types for the argument to getsockopt, getpeername, etc.  So we
+dnl have to test to find something that will work.
+AC_DEFUN([TYPE_SOCKLEN_T],
+[
+   AC_CHECK_TYPE([socklen_t], ,[
+      AC_MSG_CHECKING([for socklen_t equivalent])
+      AC_CACHE_VAL([git_cv_socklen_t_equiv],
+      [
+         # Systems have either "struct sockaddr *" or
+         # "void *" as the second argument to getpeername
+         git_cv_socklen_t_equiv=
+         for arg2 in "struct sockaddr" void; do
+            for t in int size_t unsigned long "unsigned long"; do
+               AC_TRY_COMPILE([
+                  #include <sys/types.h>
+                  #include <sys/socket.h>
+
+                  int getpeername (int, $arg2 *, $t *);
+               ],[
+                  $t len;
+                  getpeername(0,0,&len);
+               ],[
+                  git_cv_socklen_t_equiv="$t"
+                  break 2
+               ])
+            done
+         done
+
+         if test "x$git_cv_socklen_t_equiv" = x; then
+            AC_MSG_ERROR([Cannot find a type to use in place of socklen_t])
+         fi
+      ])
+      AC_MSG_RESULT($git_cv_socklen_t_equiv)
+      AC_DEFINE_UNQUOTED(socklen_t, $git_cv_socklen_t_equiv,
+                       [type to use in place of socklen_t if not defined])],
+      [#include <sys/types.h>
+#include <sys/socket.h>])
+])
diff --git a/attr.c b/attr.c
index 7467baf2d6c81f94a7d043dcde13d463b3b46272..8ba606c933088e27ac08aabb546b764745f8187e 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -287,7 +287,7 @@ static void free_attr_elem(struct attr_stack *e)
 }
 
 static const char *builtin_attr[] = {
-       "[attr]binary -diff -crlf",
+       "[attr]binary -diff -text",
        NULL,
 };
 
diff --git a/attr.h b/attr.h
index 450f49d648a013ffddc6321b7fd79b3fc1b66f7a..8b3f19be67f17c1fbf6edb37ac3ea27002ca6415 100644 (file)
--- a/attr.h
+++ b/attr.h
@@ -34,7 +34,7 @@ int git_checkattr(const char *path, int, struct git_attr_check *);
 enum git_attr_direction {
        GIT_ATTR_CHECKIN,
        GIT_ATTR_CHECKOUT,
-       GIT_ATTR_INDEX,
+       GIT_ATTR_INDEX
 };
 void git_attr_set_direction(enum git_attr_direction, struct index_state *);
 
index 322901f339a47486854d90048cbec198db8fce56..b614d12b9f3464eff9ecfa90c2eb5ecf43a82b7e 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -17,9 +17,6 @@ extern void prune_packed_objects(int);
 extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
        struct strbuf *out);
 extern int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out);
-extern int commit_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 {
index 87d2980313e71afc08df3eacde4a99a8468ff874..17149cfeedcc39956dc55f8b72acc1227fed5213 100644 (file)
@@ -261,12 +261,14 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
 {
        char *file = xstrdup(git_path("ADD_EDIT.patch"));
        const char *apply_argv[] = { "apply", "--recount", "--cached",
-               file, NULL };
+               NULL, NULL };
        struct child_process child;
        struct rev_info rev;
        int out;
        struct stat st;
 
+       apply_argv[3] = file;
+
        git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
 
        if (read_cache() < 0)
index 59bbcdb1323cb70432e90ccc2d6a1c60a562c706..562e5345fc969a5ad03b3ade4952668bcec09136 100644 (file)
@@ -56,7 +56,7 @@ static enum ws_error_action {
        nowarn_ws_error,
        warn_on_ws_error,
        die_on_ws_error,
-       correct_ws_error,
+       correct_ws_error
 } ws_error_action = warn_on_ws_error;
 static int whitespace_error;
 static int squelch_whitespace_errors = 5;
@@ -64,7 +64,7 @@ static int applied_after_fixing_ws;
 
 static enum ws_ignore {
        ignore_ws_none,
-       ignore_ws_change,
+       ignore_ws_change
 } ws_ignore_action = ignore_ws_none;
 
 
@@ -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) {
@@ -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);
 
index 8506286dd271d4e92369d81ec2cce9240a559d64..729b43058a8afe01b03a463d9c11f5d74d9c8d43 100644 (file)
@@ -733,10 +733,11 @@ static int pass_blame_to_parent(struct scoreboard *sb,
 {
        int last_in_target;
        mmfile_t file_p, file_o;
-       struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 };
+       struct blame_chunk_cb_data d;
        xpparam_t xpp;
        xdemitconf_t xecfg;
-
+       memset(&d, 0, sizeof(d));
+       d.sb = sb; d.target = target; d.parent = parent;
        last_in_target = find_last_in_target(sb, target);
        if (last_in_target < 0)
                return 1; /* nothing remains for this target */
@@ -875,10 +876,11 @@ static void find_copy_in_blob(struct scoreboard *sb,
        const char *cp;
        int cnt;
        mmfile_t file_o;
-       struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 };
+       struct handle_split_cb_data d;
        xpparam_t xpp;
        xdemitconf_t xecfg;
-
+       memset(&d, 0, sizeof(d));
+       d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
        /*
         * Prepare mmfile that contains only the lines in ent.
         */
index 6cf7e721e6b59f50c7a8018296aa49848deecda3..87976f0921d87f2a8564eb54aa3b81d30ee7f214 100644 (file)
@@ -43,13 +43,13 @@ enum color_branch {
        BRANCH_COLOR_PLAIN = 1,
        BRANCH_COLOR_REMOTE = 2,
        BRANCH_COLOR_LOCAL = 3,
-       BRANCH_COLOR_CURRENT = 4,
+       BRANCH_COLOR_CURRENT = 4
 };
 
 static enum merge_filter {
        NO_FILTER = 0,
        SHOW_NOT_MERGED,
-       SHOW_MERGED,
+       SHOW_MERGED
 } merge_filter;
 static unsigned char merge_filter_ref[20];
 
@@ -257,9 +257,15 @@ static char *resolve_symref(const char *src, const char *prefix)
        return xstrdup(dst);
 }
 
+struct append_ref_cb {
+       struct ref_list *ref_list;
+       int ret;
+};
+
 static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
 {
-       struct ref_list *ref_list = (struct ref_list*)(cb_data);
+       struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
+       struct ref_list *ref_list = cb->ref_list;
        struct ref_item *newitem;
        struct commit *commit;
        int kind, i;
@@ -293,8 +299,10 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        commit = NULL;
        if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
                commit = lookup_commit_reference_gently(sha1, 1);
-               if (!commit)
-                       return error("branch '%s' does not point at a commit", refname);
+               if (!commit) {
+                       cb->ret = error("branch '%s' does not point at a commit", refname);
+                       return 0;
+               }
 
                /* Filter with with_commit if specified */
                if (!is_descendant_of(commit, ref_list->with_commit))
@@ -484,9 +492,10 @@ static void show_detached(struct ref_list *ref_list)
        }
 }
 
-static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
+static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
 {
        int i;
+       struct append_ref_cb cb;
        struct ref_list ref_list;
 
        memset(&ref_list, 0, sizeof(ref_list));
@@ -496,7 +505,9 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
        ref_list.with_commit = with_commit;
        if (merge_filter != NO_FILTER)
                init_revisions(&ref_list.revs, NULL);
-       for_each_rawref(append_ref, &ref_list);
+       cb.ref_list = &ref_list;
+       cb.ret = 0;
+       for_each_rawref(append_ref, &cb);
        if (merge_filter != NO_FILTER) {
                struct commit *filter;
                filter = lookup_commit_reference_gently(merge_filter_ref, 0);
@@ -527,6 +538,11 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
        }
 
        free_ref_list(&ref_list);
+
+       if (cb.ret)
+               error("some refs could not be read");
+
+       return cb.ret;
 }
 
 static void rename_branch(const char *oldname, const char *newname, int force)
@@ -679,7 +695,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds);
        else if (argc == 0)
-               print_ref_list(kinds, detached, verbose, abbrev, with_commit);
+               return print_ref_list(kinds, detached, verbose, abbrev, with_commit);
        else if (rename && (argc == 1))
                rename_branch(head, argv[0], rename > 1);
        else if (rename && (argc == 2))
index a933eaa043257c84f35e1d86728ca91be035caff..e5118c57da2b69963894ff2994ac361ab7f6b837 100644 (file)
@@ -118,7 +118,9 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
 
                /* custom pretty-print here */
                if (type == OBJ_TREE) {
-                       const char *ls_args[3] = {"ls-tree", obj_name, NULL};
+                       const char *ls_args[3] = { NULL };
+                       ls_args[0] =  "ls-tree";
+                       ls_args[1] =  obj_name;
                        return cmd_ls_tree(2, ls_args, NULL);
                }
 
index 88b1f43e05e64f0e8dcd6dd0461bbebd6fab1e25..1994be92c66257da18c31502bda605be170fd092 100644 (file)
@@ -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,26 @@ 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) {
+                       if (opts->new_branch_log && !log_all_ref_updates) {
+                               int temp;
+                               char log_file[PATH_MAX];
+                               char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
+
+                               temp = log_all_ref_updates;
+                               log_all_ref_updates = 1;
+                               if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
+                                       fprintf(stderr, "Can not do reflog for '%s'\n",
+                                           opts->new_orphan_branch);
+                                       log_all_ref_updates = temp;
+                                       return;
+                               }
+                               log_all_ref_updates = temp;
+                       }
+               }
+               else
+                       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);
        }
@@ -515,6 +534,14 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                                        opts->new_branch ? " a new" : "",
                                        new->name);
                }
+               if (old->path && old->name) {
+                       char log_file[PATH_MAX], ref_file[PATH_MAX];
+
+                       git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
+                       git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
+                       if (!file_exists(ref_file) && file_exists(log_file))
+                               remove_path(log_file);
+               }
        } else if (strcmp(new->name, "HEAD")) {
                update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
                           REF_NODEREF, DIE_ON_ERR);
@@ -609,7 +636,8 @@ static int check_tracking_name(const char *refname, const unsigned char *sha1,
 
 static const char *unique_tracking_name(const char *name)
 {
-       struct tracking_name_data cb_data = { name, NULL, 1 };
+       struct tracking_name_data cb_data = { NULL, NULL, 1 };
+       cb_data.name = name;
        for_each_ref(check_tracking_name, &cb_data);
        if (cb_data.unique)
                return cb_data.remote;
@@ -633,6 +661,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 +707,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)
+                       die("--orphan cannot be used with -t");
+               opts.new_branch = opts.new_orphan_branch;
+       }
+
        if (conflict_style) {
                opts.merge = 1; /* implied */
                git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
index 3a3625b2ad97472c8486c9fbdd4ac6333699a37d..efb1e6faa539935de0e9dc590237185a1d222322 100644 (file)
@@ -475,9 +475,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) {
@@ -503,12 +500,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);
 
@@ -518,7 +518,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)
index 90dac349a3137405baaea63bfb6c798d2f8c92a3..87f0591c2f68a03e06c73b352282426b803450ba 100644 (file)
@@ -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);
        }
 
index 80c621dc061f9bb67a521dfb857ec52b536eacfd..3d99cf91587593ecce56a603ca064888cdd60988 100644 (file)
@@ -62,7 +62,7 @@ static struct lock_file false_lock; /* used only for partial commits */
 static enum {
        COMMIT_AS_IS = 1,
        COMMIT_NORMAL,
-       COMMIT_PARTIAL,
+       COMMIT_PARTIAL
 } commit_style;
 
 static const char *logfile, *force_author;
@@ -71,7 +71,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,11 +83,12 @@ static char *untracked_files_arg, *force_date;
 static enum {
        CLEANUP_SPACE,
        CLEANUP_NONE,
-       CLEANUP_ALL,
+       CLEANUP_ALL
 } cleanup_mode;
 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;
 
@@ -95,8 +96,9 @@ static int null_termination;
 static enum {
        STATUS_FORMAT_LONG,
        STATUS_FORMAT_SHORT,
-       STATUS_FORMAT_PORCELAIN,
+       STATUS_FORMAT_PORCELAIN
 } status_format = STATUS_FORMAT_LONG;
+static int status_show_branch;
 
 static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 {
@@ -138,6 +140,7 @@ static struct option builtin_commit_options[] = {
        OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
        OPT_SET_INT(0, "short", &status_format, "show status concisely",
                    STATUS_FORMAT_SHORT),
+       OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"),
        OPT_SET_INT(0, "porcelain", &status_format,
                    "show porcelain output format", STATUS_FORMAT_PORCELAIN),
        OPT_BOOLEAN('z', "null", &null_termination,
@@ -145,9 +148,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()
 };
 
@@ -422,7 +431,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
 
        switch (status_format) {
        case STATUS_FORMAT_SHORT:
-               wt_shortstatus_print(s, null_termination);
+               wt_shortstatus_print(s, null_termination, status_show_branch);
                break;
        case STATUS_FORMAT_PORCELAIN:
                wt_porcelain_print(s, null_termination);
@@ -730,7 +739,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
        if (use_editor) {
                char index[PATH_MAX];
-               const char *env[2] = { index, NULL };
+               const char *env[2] = { NULL };
+               env[0] =  index;
                snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
                if (launch_editor(git_path(commit_editmsg), NULL, env)) {
                        fprintf(stderr,
@@ -1036,6 +1046,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                OPT__VERBOSE(&verbose),
                OPT_SET_INT('s', "short", &status_format,
                            "show status concisely", STATUS_FORMAT_SHORT),
+               OPT_BOOLEAN('b', "branch", &status_show_branch,
+                           "show branch information"),
                OPT_SET_INT(0, "porcelain", &status_format,
                            "show porcelain output format",
                            STATUS_FORMAT_PORCELAIN),
@@ -1045,6 +1057,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(),
        };
 
@@ -1058,7 +1072,8 @@ 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);
 
@@ -1085,7 +1100,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 
        switch (status_format) {
        case STATUS_FORMAT_SHORT:
-               wt_shortstatus_print(&s, null_termination);
+               wt_shortstatus_print(&s, null_termination, status_show_branch);
                break;
        case STATUS_FORMAT_PORCELAIN:
                wt_porcelain_print(&s, null_termination);
@@ -1318,7 +1333,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);
index 4bc46b15fde00d913577a2980778746f8315bb70..f3d1660d023f7893373436617a9f729ce1d60f4d 100644 (file)
@@ -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)
index 8470850415c14cad8ceeca9f6baef46ab6feabc3..5cb369cfd1fbca1ceeee06b751c2b047f8a0e5b5 100644 (file)
@@ -574,9 +574,10 @@ static void find_non_local_tags(struct transport *transport,
 {
        struct string_list existing_refs = { NULL, 0, 0, 0 };
        struct string_list remote_refs = { NULL, 0, 0, 0 };
-       struct tag_data data = {head, tail};
+       struct tag_data data;
        const struct ref *ref;
        struct string_list_item *item = NULL;
+       data.head = head; data.tail = tail;
 
        for_each_ref(add_existing, &existing_refs);
        for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
@@ -778,7 +779,8 @@ static int get_remote_group(const char *key, const char *value, void *priv)
 static int add_remote_or_group(const char *name, struct string_list *list)
 {
        int prev_nr = list->nr;
-       struct remote_group_data g = { name, list };
+       struct remote_group_data g;
+       g.name = name; g.list = list;
 
        git_config(get_remote_group, &g);
        if (list->nr == prev_nr) {
index 7f5011f75ef9eb549b33861d9546f3d18a94c256..a2b28c6962be8ea6f8882bae3d4fde9d972e78d2 100644 (file)
@@ -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);
+               }
        }
 }
 
index b194ea3cea531f3a6c81d6d27b5b3028641ba9ce..d0a73da07a0d7d8d4b01adcfcda88a98130910a0 100644 (file)
@@ -724,11 +724,15 @@ static int file_callback(const struct option *opt, const char *arg, int unset)
        if (!patterns)
                die_errno("cannot open '%s'", arg);
        while (strbuf_getline(&sb, patterns, '\n') == 0) {
+               char *s;
+               size_t len;
+
                /* ignore empty line like grep does */
                if (sb.len == 0)
                        continue;
-               append_grep_pattern(grep_opt, strbuf_detach(&sb, NULL), arg,
-                                   ++lno, GREP_PATTERN);
+
+               s = strbuf_detach(&sb, &len);
+               append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN);
        }
        fclose(patterns);
        strbuf_release(&sb);
index 3182a2bec466c50a9a1db1e91888bad3335414b1..a9836b00aec8df68e7a615dce83d3c60f73e242f 100644 (file)
@@ -26,7 +26,7 @@ enum help_format {
        HELP_FORMAT_NONE,
        HELP_FORMAT_MAN,
        HELP_FORMAT_INFO,
-       HELP_FORMAT_WEB,
+       HELP_FORMAT_WEB
 };
 
 static int show_all = 0;
index 6208703c061abb868201073795cf516bf81b2602..976e16f9f2e6e5f8e2229d90caae9f7df4d75309 100644 (file)
@@ -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;
index 70f5622d9d49aae4080b38ee4487cc6e403a9d2c..34480cfad6a8842d65ae61f6257c597a2a987b33 100644 (file)
@@ -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"
+"                     [-q|--quiet] [<repository> [<refs>...]]";
 
 /*
  * Is there one among the list of patterns that match the tail part
@@ -33,6 +34,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        const char *dest = NULL;
        int nongit;
        unsigned flags = 0;
+       int quiet = 0;
        const char *uploadpack = NULL;
        const char **pattern = NULL;
 
@@ -66,6 +68,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
                                flags |= REF_NORMAL;
                                continue;
                        }
+                       if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
+                               quiet = 1;
+                               continue;
+                       }
                        usage(ls_remote_usage);
                }
                dest = arg;
@@ -73,9 +79,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 +90,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);
@@ -96,6 +104,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        ref = transport_get_remote_refs(transport);
        if (transport_disconnect(transport))
                return 1;
+
+       if (!dest && !quiet)
+               fprintf(stderr, "From %s\n", *remote->url);
        for ( ; ref; ref = ref->next) {
                if (!check_ref_type(ref, flags))
                        continue;
index 4a9729b9b388a1e589e9250d8157e723c68140de..2320d981ceab220e287e42c21f3c9f3be5b3722f 100644 (file)
@@ -17,10 +17,10 @@ static struct strbuf name = STRBUF_INIT;
 static struct strbuf email = STRBUF_INIT;
 
 static enum  {
-       TE_DONTCARE, TE_QP, TE_BASE64,
+       TE_DONTCARE, TE_QP, TE_BASE64
 } transfer_encoding;
 static enum  {
-       TYPE_TEXT, TYPE_OTHER,
+       TYPE_TEXT, TYPE_OTHER
 } message_type;
 
 static struct strbuf charset = STRBUF_INIT;
index cae1cbee26535635cfe0d7e3a6f98e3a36e94cf7..37ce4f589f6582abf0d1ef5bd263f590a16853d5 100644 (file)
@@ -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);
        }
 }
 
index 26617546c8cadac493f06473d02f27f320713b28..648033c27e75e9a19e9fd3a2fa21b2e26b19d5ac 100644 (file)
@@ -26,7 +26,7 @@ static const char * const git_notes_usage[] = {
        "git notes [--ref <notes_ref>] edit [<object>]",
        "git notes [--ref <notes_ref>] show [<object>]",
        "git notes [--ref <notes_ref>] remove [<object>]",
-       "git notes [--ref <notes_ref>] prune",
+       "git notes [--ref <notes_ref>] prune [-n | -v]",
        NULL
 };
 
@@ -67,7 +67,7 @@ static const char * const git_notes_remove_usage[] = {
 };
 
 static const char * const git_notes_prune_usage[] = {
-       "git notes prune",
+       "git notes prune [<options>]",
        NULL
 };
 
@@ -792,7 +792,10 @@ static int remove_cmd(int argc, const char **argv, const char *prefix)
 static int prune(int argc, const char **argv, const char *prefix)
 {
        struct notes_tree *t;
+       int show_only = 0, verbose = 0;
        struct option options[] = {
+               OPT_BOOLEAN('n', NULL, &show_only, "do not remove, show only"),
+               OPT_BOOLEAN('v', NULL, &verbose, "report pruned notes"),
                OPT_END()
        };
 
@@ -806,8 +809,10 @@ static int prune(int argc, const char **argv, const char *prefix)
 
        t = init_notes_check("prune");
 
-       prune_notes(t);
-       commit_notes(t, "Notes removed by 'git notes prune'");
+       prune_notes(t, (verbose ? NOTES_PRUNE_VERBOSE : 0) |
+               (show_only ? NOTES_PRUNE_VERBOSE|NOTES_PRUNE_DRYRUN : 0) );
+       if (!show_only)
+               commit_notes(t, "Notes removed by 'git notes prune'");
        free_notes(t);
        return 0;
 }
index 214d7ef2b12d41e8d70539fe4a4b16fde71a780a..0e8167311884d38c48a9421bf8aede89e7f2c22d 100644 (file)
@@ -1529,6 +1529,8 @@ static void try_to_free_from_threads(size_t size)
        read_unlock();
 }
 
+try_to_free_t old_try_to_free_routine;
+
 /*
  * 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
@@ -1563,12 +1565,12 @@ static void init_threaded_search(void)
        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);
+       old_try_to_free_routine = set_try_to_free_routine(try_to_free_from_threads);
 }
 
 static void cleanup_threaded_search(void)
 {
-       set_try_to_free_routine(NULL);
+       set_try_to_free_routine(old_try_to_free_routine);
        pthread_cond_destroy(&progress_cond);
        pthread_mutex_destroy(&read_mutex);
        pthread_mutex_destroy(&cache_mutex);
index af0911e4bd54848eadc7485c985e2a010e720e7f..512530022edac398f8541ee6c400c7312659e730 100644 (file)
@@ -28,16 +28,42 @@ static int remove_space(char *line)
        return dst - line;
 }
 
-static void generate_id_list(void)
+static int scan_hunk_header(const char *p, int *p_before, int *p_after)
+{
+       static const char digits[] = "0123456789";
+       const char *q, *r;
+       int n;
+
+       q = p + 4;
+       n = strspn(q, digits);
+       if (q[n] == ',') {
+               q += n + 1;
+               n = strspn(q, digits);
+       }
+       if (n == 0 || q[n] != ' ' || q[n+1] != '+')
+               return 0;
+
+       r = q + n + 2;
+       n = strspn(r, digits);
+       if (r[n] == ',') {
+               r += n + 1;
+               n = strspn(r, digits);
+       }
+       if (n == 0)
+               return 0;
+
+       *p_before = atoi(q);
+       *p_after = atoi(r);
+       return 1;
+}
+
+int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
 {
-       static unsigned char sha1[20];
        static char line[1000];
-       git_SHA_CTX ctx;
-       int patchlen = 0;
+       int patchlen = 0, found_next = 0;
+       int before = -1, after = -1;
 
-       git_SHA1_Init(&ctx);
        while (fgets(line, sizeof(line), stdin) != NULL) {
-               unsigned char n[20];
                char *p = line;
                int len;
 
@@ -45,32 +71,75 @@ static void generate_id_list(void)
                        p += 10;
                else if (!memcmp(line, "commit ", 7))
                        p += 7;
+               else if (!memcmp(line, "From ", 5))
+                       p += 5;
 
-               if (!get_sha1_hex(p, n)) {
-                       flush_current_id(patchlen, sha1, &ctx);
-                       hashcpy(sha1, n);
-                       patchlen = 0;
-                       continue;
+               if (!get_sha1_hex(p, next_sha1)) {
+                       found_next = 1;
+                       break;
                }
 
                /* Ignore commit comments */
                if (!patchlen && memcmp(line, "diff ", 5))
                        continue;
 
-               /* Ignore git-diff index header */
-               if (!memcmp(line, "index ", 6))
-                       continue;
+               /* Parsing diff header?  */
+               if (before == -1) {
+                       if (!memcmp(line, "index ", 6))
+                               continue;
+                       else if (!memcmp(line, "--- ", 4))
+                               before = after = 1;
+                       else if (!isalpha(line[0]))
+                               break;
+               }
 
-               /* Ignore line numbers when computing the SHA1 of the patch */
-               if (!memcmp(line, "@@ -", 4))
-                       continue;
+               /* Looking for a valid hunk header?  */
+               if (before == 0 && after == 0) {
+                       if (!memcmp(line, "@@ -", 4)) {
+                               /* Parse next hunk, but ignore line numbers.  */
+                               scan_hunk_header(line, &before, &after);
+                               continue;
+                       }
+
+                       /* Split at the end of the patch.  */
+                       if (memcmp(line, "diff ", 5))
+                               break;
+
+                       /* Else we're parsing another header.  */
+                       before = after = -1;
+               }
+
+               /* If we get here, we're inside a hunk.  */
+               if (line[0] == '-' || line[0] == ' ')
+                       before--;
+               if (line[0] == '+' || line[0] == ' ')
+                       after--;
 
                /* Compute the sha without whitespace */
                len = remove_space(line);
                patchlen += len;
-               git_SHA1_Update(&ctx, line, len);
+               git_SHA1_Update(ctx, line, len);
+       }
+
+       if (!found_next)
+               hashclr(next_sha1);
+
+       return patchlen;
+}
+
+static void generate_id_list(void)
+{
+       unsigned char sha1[20], n[20];
+       git_SHA_CTX ctx;
+       int patchlen;
+
+       git_SHA1_Init(&ctx);
+       hashclr(sha1);
+       while (!feof(stdin)) {
+               patchlen = get_one_patchid(n, &ctx);
+               flush_current_id(patchlen, sha1, &ctx);
+               hashcpy(sha1, n);
        }
-       flush_current_id(patchlen, sha1, &ctx);
 }
 
 static const char patch_id_usage[] = "git patch-id < patch";
index 0559fcc871894548050e99c8c258561a0dcad5c9..29bc8d50bbe187654e8b0903627655fbb05a6aea 100644 (file)
@@ -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>";
 
@@ -16,7 +17,7 @@ enum deny_action {
        DENY_UNCONFIGURED,
        DENY_IGNORE,
        DENY_WARN,
-       DENY_REFUSE,
+       DENY_REFUSE
 };
 
 static int deny_deletes;
@@ -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));
+       strcpy(cmd_newh, find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV));
+       strcpy(dst_oldh, find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV));
+       strcpy(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[] = {
index bd7880dc04830253daae932ba534f02db85f6d2a..ebf610e64a267c5ca769d70203b282fb8f96a434 100644 (file)
@@ -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)) {
index 277765b864202b2b6f20069e0cb3f95eaef4fcaa..0a52667e0f7cc14d3a760bf0ec5f6857a0ce9051 100644 (file)
@@ -16,6 +16,7 @@ static const char * const builtin_remote_usage[] = {
        "git remote [-v | --verbose] show [-n] <name>",
        "git remote prune [-n | --dry-run] <name>",
        "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+       "git remote set-branches <name> [--add] <branch>...",
        "git remote set-url <name> <newurl> [<oldurl>]",
        "git remote set-url --add <name> <newurl>",
        "git remote set-url --delete <name> <url>",
@@ -42,6 +43,12 @@ static const char * const builtin_remote_sethead_usage[] = {
        NULL
 };
 
+static const char * const builtin_remote_setbranches_usage[] = {
+       "git remote set-branches <name> <branch>...",
+       "git remote set-branches --add <name> <branch>...",
+       NULL
+};
+
 static const char * const builtin_remote_show_usage[] = {
        "git remote show [<options>] <name>",
        NULL
@@ -104,9 +111,29 @@ static int fetch_remote(const char *name)
        return 0;
 }
 
+enum {
+       TAGS_UNSET = 0,
+       TAGS_DEFAULT = 1,
+       TAGS_SET = 2
+};
+
+static int add_branch(const char *key, const char *branchname,
+               const char *remotename, int mirror, struct strbuf *tmp)
+{
+       strbuf_reset(tmp);
+       strbuf_addch(tmp, '+');
+       if (mirror)
+               strbuf_addf(tmp, "refs/%s:refs/%s",
+                               branchname, branchname);
+       else
+               strbuf_addf(tmp, "refs/heads/%s:refs/remotes/%s/%s",
+                               branchname, remotename, branchname);
+       return git_config_set_multivar(key, tmp->buf, "^$", 0);
+}
+
 static int add(int argc, const char **argv)
 {
-       int fetch = 0, mirror = 0;
+       int fetch = 0, mirror = 0, fetch_tags = TAGS_DEFAULT;
        struct string_list track = { NULL, 0, 0 };
        const char *master = NULL;
        struct remote *remote;
@@ -116,6 +143,11 @@ static int add(int argc, const char **argv)
 
        struct option options[] = {
                OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
+               OPT_SET_INT(0, "tags", &fetch_tags,
+                           "import all tags and associated objects when fetching",
+                           TAGS_SET),
+               OPT_SET_INT(0, NULL, &fetch_tags,
+                           "or do not fetch any tag at all (--no-tags)", TAGS_UNSET),
                OPT_CALLBACK('t', "track", &track, "branch",
                        "branch(es) to track", opt_parse_track),
                OPT_STRING('m', "master", &master, "branch", "master branch"),
@@ -151,17 +183,8 @@ static int add(int argc, const char **argv)
        if (track.nr == 0)
                string_list_append("*", &track);
        for (i = 0; i < track.nr; i++) {
-               struct string_list_item *item = track.items + i;
-
-               strbuf_reset(&buf2);
-               strbuf_addch(&buf2, '+');
-               if (mirror)
-                       strbuf_addf(&buf2, "refs/%s:refs/%s",
-                                       item->string, item->string);
-               else
-                       strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s",
-                                       item->string, name, item->string);
-               if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
+               if (add_branch(buf.buf, track.items[i].string,
+                               name, mirror, &buf2))
                        return 1;
        }
 
@@ -172,6 +195,14 @@ static int add(int argc, const char **argv)
                        return 1;
        }
 
+       if (fetch_tags != TAGS_DEFAULT) {
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "remote.%s.tagopt", name);
+               if (git_config_set(buf.buf,
+                       fetch_tags == TAGS_SET ? "--tags" : "--no-tags"))
+                       return 1;
+       }
+
        if (fetch && fetch_remote(name))
                return 1;
 
@@ -317,7 +348,7 @@ struct push_info {
                PUSH_STATUS_UPTODATE,
                PUSH_STATUS_FASTFORWARD,
                PUSH_STATUS_OUTOFDATE,
-               PUSH_STATUS_NOTQUERIED,
+               PUSH_STATUS_NOTQUERIED
        } status;
 };
 
@@ -705,11 +736,14 @@ static int rm(int argc, const char **argv)
        struct known_remotes known_remotes = { NULL, NULL };
        struct string_list branches = { NULL, 0, 0, 1 };
        struct string_list skipped = { NULL, 0, 0, 1 };
-       struct branches_for_remote cb_data = {
-               NULL, &branches, &skipped, &known_remotes
-       };
+       struct branches_for_remote cb_data;
        int i, result;
 
+       memset(&cb_data, 0, sizeof(cb_data));
+       cb_data.branches = &branches;
+       cb_data.skipped = &skipped;
+       cb_data.keep = &known_remotes;
+
        if (argc != 2)
                usage_with_options(builtin_remote_rm_usage, options);
 
@@ -1265,6 +1299,72 @@ static int update(int argc, const char **argv)
        return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
 }
 
+static int remove_all_fetch_refspecs(const char *remote, const char *key)
+{
+       return git_config_set_multivar(key, NULL, NULL, 1);
+}
+
+static int add_branches(struct remote *remote, const char **branches,
+                       const char *key)
+{
+       const char *remotename = remote->name;
+       int mirror = remote->mirror;
+       struct strbuf refspec = STRBUF_INIT;
+
+       for (; *branches; branches++)
+               if (add_branch(key, *branches, remotename, mirror, &refspec)) {
+                       strbuf_release(&refspec);
+                       return 1;
+               }
+
+       strbuf_release(&refspec);
+       return 0;
+}
+
+static int set_remote_branches(const char *remotename, const char **branches,
+                               int add_mode)
+{
+       struct strbuf key = STRBUF_INIT;
+       struct remote *remote;
+
+       strbuf_addf(&key, "remote.%s.fetch", remotename);
+
+       if (!remote_is_configured(remotename))
+               die("No such remote '%s'", remotename);
+       remote = remote_get(remotename);
+
+       if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) {
+               strbuf_release(&key);
+               return 1;
+       }
+       if (add_branches(remote, branches, key.buf)) {
+               strbuf_release(&key);
+               return 1;
+       }
+
+       strbuf_release(&key);
+       return 0;
+}
+
+static int set_branches(int argc, const char **argv)
+{
+       int add_mode = 0;
+       struct option options[] = {
+               OPT_BOOLEAN('\0', "add", &add_mode, "add branch"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, NULL, options,
+                            builtin_remote_setbranches_usage, 0);
+       if (argc == 0) {
+               error("no remote specified");
+               usage_with_options(builtin_remote_seturl_usage, options);
+       }
+       argv[argc] = NULL;
+
+       return set_remote_branches(argv[0], argv + 1, add_mode);
+}
+
 static int set_url(int argc, const char **argv)
 {
        int i, push_mode = 0, add_mode = 0, delete_mode = 0;
@@ -1430,6 +1530,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
                result = rm(argc, argv);
        else if (!strcmp(argv[0], "set-head"))
                result = set_head(argc, argv);
+       else if (!strcmp(argv[0], "set-branches"))
+               result = set_branches(argc, argv);
        else if (!strcmp(argv[0], "set-url"))
                result = set_url(argc, argv);
        else if (!strcmp(argv[0], "show"))
index 8fbf9d0db6f40aa8c7cb61d72d0f44446de46826..b676e296357c41cdca2f030d28b511ff986fdad9 100644 (file)
@@ -408,7 +408,8 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
        memset(opts + onb, 0, sizeof(opts[onb]));
        argc = parse_options(argc, argv, prefix, opts, usage,
                        keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0 |
-                       stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0);
+                       stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0 |
+                       PARSE_OPT_SHELL_EVAL);
 
        strbuf_addf(&parsed, " --");
        sq_quote_argv(&parsed, argv, 0);
index 7d68ef714eee5011d82952ca1829016c90827f61..7976b5a3295d4c70f003212485680f4dd3ceead2 100644 (file)
@@ -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(),
@@ -174,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)
@@ -271,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));
@@ -305,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";
@@ -403,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/cache.h b/cache.h
index 0d101e4913f380a0eda0b3bf9e1d8b76e34c6fd8..ff4a7c26d3b4dc6c9b28d8a28919b11fd71443bb 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -361,7 +361,7 @@ enum object_type {
        OBJ_OFS_DELTA = 6,
        OBJ_REF_DELTA = 7,
        OBJ_ANY,
-       OBJ_MAX,
+       OBJ_MAX
 };
 
 static inline enum object_type object_type(unsigned int mode)
@@ -547,7 +547,6 @@ extern int core_compression_seen;
 extern size_t packed_git_window_size;
 extern size_t packed_git_limit;
 extern size_t delta_base_cache_limit;
-extern int auto_crlf;
 extern int read_replace_refs;
 extern int fsync_object_files;
 extern int core_preload_index;
@@ -556,32 +555,53 @@ extern int core_apply_sparse_checkout;
 enum safe_crlf {
        SAFE_CRLF_FALSE = 0,
        SAFE_CRLF_FAIL = 1,
-       SAFE_CRLF_WARN = 2,
+       SAFE_CRLF_WARN = 2
 };
 
 extern enum safe_crlf safe_crlf;
 
+enum auto_crlf {
+       AUTO_CRLF_FALSE = 0,
+       AUTO_CRLF_TRUE = 1,
+       AUTO_CRLF_INPUT = -1,
+};
+
+extern enum auto_crlf auto_crlf;
+
+enum eol {
+       EOL_UNSET,
+       EOL_CRLF,
+       EOL_LF,
+#ifdef NATIVE_CRLF
+       EOL_NATIVE = EOL_CRLF
+#else
+       EOL_NATIVE = EOL_LF
+#endif
+};
+
+extern enum eol eol;
+
 enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
        BRANCH_TRACK_REMOTE,
        BRANCH_TRACK_ALWAYS,
        BRANCH_TRACK_EXPLICIT,
-       BRANCH_TRACK_OVERRIDE,
+       BRANCH_TRACK_OVERRIDE
 };
 
 enum rebase_setup_type {
        AUTOREBASE_NEVER = 0,
        AUTOREBASE_LOCAL,
        AUTOREBASE_REMOTE,
-       AUTOREBASE_ALWAYS,
+       AUTOREBASE_ALWAYS
 };
 
 enum push_default_type {
        PUSH_DEFAULT_NOTHING = 0,
        PUSH_DEFAULT_MATCHING,
        PUSH_DEFAULT_TRACKING,
-       PUSH_DEFAULT_CURRENT,
+       PUSH_DEFAULT_CURRENT
 };
 
 extern enum branch_track git_branch_track;
@@ -590,7 +610,7 @@ extern enum push_default_type push_default;
 
 enum object_creation_mode {
        OBJECT_CREATION_USES_HARDLINKS = 0,
-       OBJECT_CREATION_USES_RENAMES = 1,
+       OBJECT_CREATION_USES_RENAMES = 1
 };
 
 extern enum object_creation_mode object_creation_mode;
@@ -670,7 +690,7 @@ enum sharedrepo {
        OLD_PERM_GROUP      = 1,
        OLD_PERM_EVERYBODY  = 2,
        PERM_GROUP          = 0660,
-       PERM_EVERYBODY      = 0664,
+       PERM_EVERYBODY      = 0664
 };
 int git_config_perm(const char *var, const char *value);
 int set_shared_perm(const char *path, int mode);
@@ -718,6 +738,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)
 {
@@ -880,7 +902,7 @@ struct ref {
                REF_STATUS_REJECT_NODELETE,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
-               REF_STATUS_EXPECTING_REPORT,
+               REF_STATUS_EXPECTING_REPORT
        } status;
        char *remote_status;
        struct ref *peer_ref; /* when renaming */
@@ -937,12 +959,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 *);
@@ -950,6 +975,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 *);
@@ -1041,6 +1067,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;
@@ -1049,7 +1076,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 */
diff --git a/color.c b/color.c
index bcf4e2c192c31136729aabcc2575a94f809d2980..1b00554dd50d2960b4e66f229b0d2d0da8eb8c28 100644 (file)
--- a/color.c
+++ b/color.c
@@ -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;
-}
-
-
diff --git a/color.h b/color.h
index 5c264b0ce3b95edb5f86cc003d2aca04033d098f..03ca0647485e8044d4131f2c61c37b4e9408cd2a 100644 (file)
--- a/color.h
+++ b/color.h
@@ -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 */
index 731191e63bd39a89a8ea4ed0390c49d5605cdbed..e9b07509678f5a4f61e5bedadea14b726e290ed1 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -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;
+}
index 26ec8c0d1cebcf5e79564be690517cd2eb9c6413..e958a7c3dfbb3ff76410dc83e47d0ffa950af9ea 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -60,7 +60,7 @@ enum cmit_fmt {
        CMIT_FMT_EMAIL,
        CMIT_FMT_USERFORMAT,
 
-       CMIT_FMT_UNSPECIFIED,
+       CMIT_FMT_UNSPECIFIED
 };
 
 struct pretty_print_context
@@ -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 */
index 0e3e74304138ab2f279c74599ee934ade72ae37d..3b2477be5f658be665f19a12b48cc47fa07d1c6b 100644 (file)
@@ -80,7 +80,7 @@ static inline int fork(void)
 static inline unsigned int alarm(unsigned int seconds)
 { return 0; }
 static inline int fsync(int fd)
-{ return 0; }
+{ return _commit(fd); }
 static inline int getppid(void)
 { return 1; }
 static inline void sync(void)
@@ -89,7 +89,7 @@ static inline int getuid()
 { return 1; }
 static inline struct passwd *getpwnam(const char *name)
 { return NULL; }
-static inline int fcntl(int fd, int cmd, long arg)
+static inline int fcntl(int fd, int cmd, ...)
 {
        if (cmd == F_GETFD || cmd == F_SETFD)
                return 0;
index 0f949fc4250b5de4a3545931506fc48373ad8c6b..010e875ec4dd8d7154a0911661570165ac1ae874 100644 (file)
@@ -16,6 +16,7 @@
 static unsigned __stdcall win32_start_routine(void *arg)
 {
        pthread_t *thread = arg;
+       thread->tid = GetCurrentThreadId();
        thread->arg = thread->start_routine(thread->arg);
        return 0;
 }
@@ -49,6 +50,13 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr)
        }
 }
 
+pthread_t pthread_self(void)
+{
+       pthread_t t = { 0 };
+       t.tid = GetCurrentThreadId();
+       return t;
+}
+
 int pthread_cond_init(pthread_cond_t *cond, const void *unused)
 {
        cond->waiters = 0;
index a45f8d66df8d1e452d9392945bf12a74c32bbca9..2e205485570bf62a11112c665624203207c724a9 100644 (file)
@@ -58,6 +58,7 @@ typedef struct {
        HANDLE handle;
        void *(*start_routine)(void*);
        void *arg;
+       DWORD tid;
 } pthread_t;
 
 extern int pthread_create(pthread_t *thread, const void *unused,
@@ -71,4 +72,28 @@ extern int pthread_create(pthread_t *thread, const void *unused,
 
 extern int win32_pthread_join(pthread_t *thread, void **value_ptr);
 
+#define pthread_equal(t1, t2) ((t1).tid == (t2).tid)
+extern pthread_t pthread_self(void);
+
+static inline int pthread_exit(void *ret)
+{
+       ExitThread((DWORD)ret);
+}
+
+typedef DWORD pthread_key_t;
+static inline int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *value))
+{
+       return (*keyp = TlsAlloc()) == TLS_OUT_OF_INDEXES ? EAGAIN : 0;
+}
+
+static inline int pthread_setspecific(pthread_key_t key, const void *value)
+{
+       return TlsSetValue(key, (void *)value) ? 0 : EINVAL;
+}
+
+static inline void *pthread_getspecific(pthread_key_t key)
+{
+       return TlsGetValue(key);
+}
+
 #endif /* PTHREAD_H */
index 6963fbea43e6420f5f8dafc5b94fb5c27de6ffd2..cdcf5836c6c374eb59e80f89dbcf525fd6bf780f 100644 (file)
--- a/config.c
+++ b/config.c
@@ -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);
 }
@@ -461,7 +517,9 @@ static int git_default_core_config(const char *var, const char *value)
 
        if (!strcmp(var, "core.autocrlf")) {
                if (value && !strcasecmp(value, "input")) {
-                       auto_crlf = -1;
+                       if (eol == EOL_CRLF)
+                               return error("core.autocrlf=input conflicts with core.eol=crlf");
+                       auto_crlf = AUTO_CRLF_INPUT;
                        return 0;
                }
                auto_crlf = git_config_bool(var, value);
@@ -477,6 +535,20 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.eol")) {
+               if (value && !strcasecmp(value, "lf"))
+                       eol = EOL_LF;
+               else if (value && !strcasecmp(value, "crlf"))
+                       eol = EOL_CRLF;
+               else if (value && !strcasecmp(value, "native"))
+                       eol = EOL_NATIVE;
+               else
+                       eol = EOL_UNSET;
+               if (eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT)
+                       return error("core.autocrlf=input conflicts with core.eol=crlf");
+               return 0;
+       }
+
        if (!strcmp(var, "core.notesref")) {
                notes_ref_name = xstrdup(value);
                return 0;
@@ -683,7 +755,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 +771,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 +811,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;
index 0d4b64d076b8041a3701715a83ca46a4675ac9d6..b4e65c32b235eafafefeed1c755be7e1ad5c71ae 100644 (file)
@@ -3,10 +3,12 @@
 
 CC = @CC@
 CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
 LDFLAGS = @LDFLAGS@
 CC_LD_DYNPATH = @CC_LD_DYNPATH@
 AR = @AR@
 TAR = @TAR@
+DIFF = @DIFF@
 #INSTALL = @INSTALL@           # needs install-sh or install.sh in sources
 TCLTK_PATH = @TCLTK_PATH@
 
@@ -42,6 +44,7 @@ NO_D_TYPE_IN_DIRENT=@NO_D_TYPE_IN_DIRENT@
 NO_SOCKADDR_STORAGE=@NO_SOCKADDR_STORAGE@
 NO_IPV6=@NO_IPV6@
 NO_C99_FORMAT=@NO_C99_FORMAT@
+NO_HSTRERROR=@NO_HSTRERROR@
 NO_STRCASESTR=@NO_STRCASESTR@
 NO_MEMMEM=@NO_MEMMEM@
 NO_STRLCPY=@NO_STRLCPY@
@@ -51,10 +54,15 @@ NO_SETENV=@NO_SETENV@
 NO_UNSETENV=@NO_UNSETENV@
 NO_MKDTEMP=@NO_MKDTEMP@
 NO_MKSTEMPS=@NO_MKSTEMPS@
+NO_INET_NTOP=@NO_INET_NTOP@
+NO_INET_PTON=@NO_INET_PTON@
 NO_ICONV=@NO_ICONV@
 OLD_ICONV=@OLD_ICONV@
 NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@
+INLINE=@INLINE@
+SOCKLEN_T=@SOCKLEN_T@
 FREAD_READS_DIRECTORIES=@FREAD_READS_DIRECTORIES@
 SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@
 NO_PTHREADS=@NO_PTHREADS@
+PTHREAD_CFLAGS=@PTHREAD_CFLAGS@
 PTHREAD_LIBS=@PTHREAD_LIBS@
index 71038fcf1cd04fdfa3bcd133b6fc82974a135141..5601e8bac953c670e35f32ffe48d157dd5694ce7 100644 (file)
@@ -327,6 +327,12 @@ GIT_PARSE_WITH(tcltk))
 AC_MSG_NOTICE([CHECKS for programs])
 #
 AC_PROG_CC([cc gcc])
+AC_C_INLINE
+case $ac_cv_c_inline in
+  inline | yes | no)   ;;
+  *)                   AC_SUBST([INLINE], [$ac_cv_c_inline]) ;;
+esac
+
 # which switch to pass runtime path to dynamic libraries to the linker
 AC_CACHE_CHECK([if linker supports -R], git_cv_ld_dashr, [
    SAVE_LDFLAGS="${LDFLAGS}"
@@ -362,6 +368,7 @@ fi
 #AC_PROG_INSTALL               # needs install-sh or install.sh in sources
 AC_CHECK_TOOLS(AR, [gar ar], :)
 AC_CHECK_PROGS(TAR, [gtar tar])
+AC_CHECK_PROGS(DIFF, [gnudiff gdiff diff])
 # TCLTK_PATH will be set to some value if we want Tcl/Tk
 # or will be empty otherwise.
 if test -z "$NO_TCLTK"; then
@@ -544,13 +551,47 @@ AC_SUBST(NEEDS_SOCKET)
 test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket"
 
 #
-# Define NEEDS_RESOLV if linking with -lnsl and/or -lsocket is not enough.
-# Notably on Solaris hstrerror resides in libresolv and on Solaris 7
-# inet_ntop and inet_pton additionally reside there.
-AC_CHECK_LIB([c], [hstrerror],
-[NEEDS_RESOLV=],
-[NEEDS_RESOLV=YesPlease])
+# The next few tests will define NEEDS_RESOLV if linking with
+# libresolv provides some of the functions we would normally get
+# from libc.
+NEEDS_RESOLV=
 AC_SUBST(NEEDS_RESOLV)
+#
+# Define NO_INET_NTOP if linking with -lresolv is not enough.
+# Solaris 2.7 in particular hos inet_ntop in -lresolv.
+NO_INET_NTOP=
+AC_SUBST(NO_INET_NTOP)
+AC_CHECK_FUNC([inet_ntop],
+       [],
+    [AC_CHECK_LIB([resolv], [inet_ntop],
+           [NEEDS_RESOLV=YesPlease],
+       [NO_INET_NTOP=YesPlease])
+])
+#
+# Define NO_INET_PTON if linking with -lresolv is not enough.
+# Solaris 2.7 in particular hos inet_pton in -lresolv.
+NO_INET_PTON=
+AC_SUBST(NO_INET_PTON)
+AC_CHECK_FUNC([inet_pton],
+       [],
+    [AC_CHECK_LIB([resolv], [inet_pton],
+           [NEEDS_RESOLV=YesPlease],
+       [NO_INET_PTON=YesPlease])
+])
+#
+# Define NO_HSTRERROR if linking with -lresolv is not enough.
+# Solaris 2.6 in particular has no hstrerror, even in -lresolv.
+NO_HSTRERROR=
+AC_CHECK_FUNC([hstrerror],
+       [],
+    [AC_CHECK_LIB([resolv], [hstrerror],
+           [NEEDS_RESOLV=YesPlease],
+       [NO_HSTRERROR=YesPlease])
+])
+AC_SUBST(NO_HSTRERROR)
+#
+# If any of the above tests determined that -lresolv is needed at
+# build-time, also set it here for remaining configure-time checks.
 test -n "$NEEDS_RESOLV" && LIBS="$LIBS -lresolv"
 
 AC_CHECK_LIB([c], [basename],
@@ -598,6 +639,12 @@ AC_SUBST(OLD_ICONV)
 ## Checks for typedefs, structures, and compiler characteristics.
 AC_MSG_NOTICE([CHECKS for typedefs, structures, and compiler characteristics])
 #
+TYPE_SOCKLEN_T
+case $ac_cv_type_socklen_t in
+  yes) ;;
+  *)   AC_SUBST([SOCKLEN_T], [$git_cv_socklen_t_equiv]) ;;
+esac
+
 # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
 AC_CHECK_MEMBER(struct dirent.d_ino,
 [NO_D_INO_IN_DIRENT=],
@@ -808,7 +855,11 @@ AC_DEFUN([PTHREADTEST_SRC], [
 int main(void)
 {
        pthread_mutex_t test_mutex;
-       return (0);
+       int retcode = 0;
+       retcode |= pthread_mutex_init(&test_mutex,(void *)0);
+       retcode |= pthread_mutex_lock(&test_mutex);
+       retcode |= pthread_mutex_unlock(&test_mutex);
+       return retcode;
 }
 ])
 
@@ -825,7 +876,8 @@ if test -n "$USER_NOPTHREAD"; then
 # handle these separately since PTHREAD_CFLAGS could be '-lpthreads
 # -D_REENTRANT' or some such.
 elif test -z "$PTHREAD_CFLAGS"; then
-  for opt in -pthread -lpthread; do
+  threads_found=no
+  for opt in -mt -pthread -lpthread; do
      old_CFLAGS="$CFLAGS"
      CFLAGS="$opt $CFLAGS"
      AC_MSG_CHECKING([Checking for POSIX Threads with '$opt'])
@@ -833,11 +885,18 @@ elif test -z "$PTHREAD_CFLAGS"; then
        [AC_MSG_RESULT([yes])
                NO_PTHREADS=
                PTHREAD_LIBS="$opt"
+               PTHREAD_CFLAGS="$opt"
+               threads_found=yes
                break
        ],
        [AC_MSG_RESULT([no])])
       CFLAGS="$old_CFLAGS"
   done
+  if test $threads_found != yes; then
+    AC_CHECK_LIB([pthread], [pthread_create],
+       [PTHREAD_LIBS="-lpthread"],
+       [NO_PTHREADS=UnfortunatelyYes])
+  fi
 else
   old_CFLAGS="$CFLAGS"
   CFLAGS="$PTHREAD_CFLAGS $CFLAGS"
@@ -854,6 +913,7 @@ fi
 
 CFLAGS="$old_CFLAGS"
 
+AC_SUBST(PTHREAD_CFLAGS)
 AC_SUBST(PTHREAD_LIBS)
 AC_SUBST(NO_PTHREADS)
 
index 9ae991ac42544716599ff8bf3ebaaa376c8119e4..02e738a0146a5c46aaf3f1d8edc3c055a99e98b9 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -5,6 +5,7 @@
 #include "refs.h"
 #include "run-command.h"
 #include "remote.h"
+#include "url.h"
 
 static char *server_capabilities;
 
@@ -131,7 +132,7 @@ int path_match(const char *path, int nr, char **match)
 enum protocol {
        PROTO_LOCAL = 1,
        PROTO_SSH,
-       PROTO_GIT,
+       PROTO_GIT
 };
 
 static enum protocol get_protocol(const char *name)
@@ -450,7 +451,7 @@ static struct child_process no_fork;
 struct child_process *git_connect(int fd[2], const char *url_orig,
                                  const char *prog, int flags)
 {
-       char *url = xstrdup(url_orig);
+       char *url;
        char *host, *path;
        char *end;
        int c;
@@ -466,6 +467,11 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
         */
        signal(SIGCHLD, SIG_DFL);
 
+       if (is_url(url_orig))
+               url = url_decode(url_orig);
+       else
+               url = xstrdup(url_orig);
+
        host = strstr(url, "://");
        if (host) {
                *host = '\0';
index 57245a8c01fa3aba4f9e3f2bc258b40f38f446c0..aebb0b689040c3f1c1023e3ddf07779178a6bdfb 100755 (executable)
@@ -842,7 +842,7 @@ _git_checkout ()
        --*)
                __gitcomp "
                        --quiet --ours --theirs --track --no-track --merge
-                       --conflict= --patch
+                       --conflict= --orphan --patch
                        "
                ;;
        *)
index e44af2c86d8e7e44bc79aafcc8ccef3806804720..a314273bd51a865d9d5fc5cd899a51ffa70388a5 100755 (executable)
@@ -127,10 +127,12 @@ then
        orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
 fi
 
-# Allow --notags from remote.$1.tagopt
+# Allow --tags/--notags from remote.$1.tagopt
 case "$tags$no_tags" in
 '')
        case "$(git config --get "remote.$1.tagopt")" in
+       --tags)
+               tags=t ;;
        --no-tags)
                no_tags=t ;;
        esac
index c364dda696912037cfafdb5e182b6e58f99e0a7e..a4ed4c3c62f0d5abcee36000a6c3a8f43dc02112 100755 (executable)
@@ -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
 --
index 4f8fcb7bbb00b66f1eaa354119784e6ef57e1eb4..e41a31e4807e92e210854214d46767b9752c5181 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -8,13 +8,17 @@
  * This should use the pathname to decide on whether it wants to do some
  * more interesting conversions (automatic gzip/unzip, general format
  * conversions etc etc), but by default it just does automatic CRLF<->LF
- * translation when the "auto_crlf" option is set.
+ * translation when the "text" attribute or "auto_crlf" option is set.
  */
 
-#define CRLF_GUESS     (-1)
-#define CRLF_BINARY    0
-#define CRLF_TEXT      1
-#define CRLF_INPUT     2
+enum action {
+       CRLF_GUESS = -1,
+       CRLF_BINARY = 0,
+       CRLF_TEXT,
+       CRLF_INPUT,
+       CRLF_CRLF,
+       CRLF_AUTO,
+};
 
 struct text_stat {
        /* NUL, CR, LF and CRLF counts */
@@ -89,49 +93,111 @@ static int is_binary(unsigned long size, struct text_stat *stats)
        return 0;
 }
 
-static void check_safe_crlf(const char *path, int action,
+static enum eol determine_output_conversion(enum action action) {
+       switch (action) {
+       case CRLF_BINARY:
+               return EOL_UNSET;
+       case CRLF_CRLF:
+               return EOL_CRLF;
+       case CRLF_INPUT:
+               return EOL_LF;
+       case CRLF_GUESS:
+               if (!auto_crlf)
+                       return EOL_UNSET;
+               /* fall through */
+       case CRLF_TEXT:
+       case CRLF_AUTO:
+               if (auto_crlf == AUTO_CRLF_TRUE)
+                       return EOL_CRLF;
+               else if (auto_crlf == AUTO_CRLF_INPUT)
+                       return EOL_LF;
+               else if (eol == EOL_UNSET)
+                       return EOL_NATIVE;
+       }
+       return eol;
+}
+
+static void check_safe_crlf(const char *path, enum action action,
                             struct text_stat *stats, enum safe_crlf checksafe)
 {
        if (!checksafe)
                return;
 
-       if (action == CRLF_INPUT || auto_crlf <= 0) {
+       if (determine_output_conversion(action) == EOL_LF) {
                /*
                 * CRLFs would not be restored by checkout:
                 * check if we'd remove CRLFs
                 */
                if (stats->crlf) {
                        if (checksafe == SAFE_CRLF_WARN)
-                               warning("CRLF will be replaced by LF in %s.", path);
+                               warning("CRLF will be replaced by LF in %s.\nThe file will have its original line endings in your working directory.", path);
                        else /* i.e. SAFE_CRLF_FAIL */
                                die("CRLF would be replaced by LF in %s.", path);
                }
-       } else if (auto_crlf > 0) {
+       } else if (determine_output_conversion(action) == EOL_CRLF) {
                /*
                 * CRLFs would be added by checkout:
                 * check if we have "naked" LFs
                 */
                if (stats->lf != stats->crlf) {
                        if (checksafe == SAFE_CRLF_WARN)
-                               warning("LF will be replaced by CRLF in %s", path);
+                               warning("LF will be replaced by CRLF in %s.\nThe file will have its original line endings in your working directory.", path);
                        else /* i.e. SAFE_CRLF_FAIL */
                                die("LF would be replaced by CRLF in %s", path);
                }
        }
 }
 
+static int has_cr_in_index(const char *path)
+{
+       int pos, len;
+       unsigned long sz;
+       enum object_type type;
+       void *data;
+       int has_cr;
+       struct index_state *istate = &the_index;
+
+       len = strlen(path);
+       pos = index_name_pos(istate, path, len);
+       if (pos < 0) {
+               /*
+                * We might be in the middle of a merge, in which
+                * case we would read stage #2 (ours).
+                */
+               int i;
+               for (i = -pos - 1;
+                    (pos < 0 && i < istate->cache_nr &&
+                     !strcmp(istate->cache[i]->name, path));
+                    i++)
+                       if (ce_stage(istate->cache[i]) == 2)
+                               pos = i;
+       }
+       if (pos < 0)
+               return 0;
+       data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
+       if (!data || type != OBJ_BLOB) {
+               free(data);
+               return 0;
+       }
+
+       has_cr = memchr(data, '\r', sz) != NULL;
+       free(data);
+       return has_cr;
+}
+
 static int crlf_to_git(const char *path, const char *src, size_t len,
-                       struct strbuf *buf, int action, enum safe_crlf checksafe)
+                      struct strbuf *buf, enum action action, enum safe_crlf checksafe)
 {
        struct text_stat stats;
        char *dst;
 
-       if ((action == CRLF_BINARY) || !auto_crlf || !len)
+       if (action == CRLF_BINARY ||
+           (action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len)
                return 0;
 
        gather_stats(src, len, &stats);
 
-       if (action == CRLF_GUESS) {
+       if (action == CRLF_AUTO || action == CRLF_GUESS) {
                /*
                 * We're currently not going to even try to convert stuff
                 * that has bare CR characters. Does anybody do that crazy
@@ -145,6 +211,15 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
                 */
                if (is_binary(len, &stats))
                        return 0;
+
+               if (action == CRLF_GUESS) {
+                       /*
+                        * If the file in the index has any CR in it, do not convert.
+                        * This is the new safer autocrlf handling.
+                        */
+                       if (has_cr_in_index(path))
+                               return 0;
+               }
        }
 
        check_safe_crlf(path, action, &stats, checksafe);
@@ -157,7 +232,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
        if (strbuf_avail(buf) + buf->len < len)
                strbuf_grow(buf, len - buf->len);
        dst = buf->buf;
-       if (action == CRLF_GUESS) {
+       if (action == CRLF_AUTO || action == CRLF_GUESS) {
                /*
                 * If we guessed, we already know we rejected a file with
                 * lone CR, and we can strip a CR without looking at what
@@ -180,16 +255,12 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
 }
 
 static int crlf_to_worktree(const char *path, const char *src, size_t len,
-                            struct strbuf *buf, int action)
+                           struct strbuf *buf, enum action action)
 {
        char *to_free = NULL;
        struct text_stat stats;
 
-       if ((action == CRLF_BINARY) || (action == CRLF_INPUT) ||
-           auto_crlf <= 0)
-               return 0;
-
-       if (!len)
+       if (!len || determine_output_conversion(action) != EOL_CRLF)
                return 0;
 
        gather_stats(src, len, &stats);
@@ -202,7 +273,14 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len,
        if (stats.lf == stats.crlf)
                return 0;
 
-       if (action == CRLF_GUESS) {
+       if (action == CRLF_AUTO || action == CRLF_GUESS) {
+               if (action == CRLF_GUESS) {
+                       /* If we have any CR or CRLF line endings, we do not touch it */
+                       /* This is the new safer autocrlf-handling */
+                       if (stats.cr > 0 || stats.crlf > 0)
+                               return 0;
+               }
+
                /* If we have any bare CR characters, we're not going to touch it */
                if (stats.cr != stats.crlf)
                        return 0;
@@ -249,7 +327,9 @@ static int filter_buffer(int in, int out, void *data)
        struct child_process child_process;
        struct filter_params *params = (struct filter_params *)data;
        int write_err, status;
-       const char *argv[] = { params->cmd, NULL };
+       const char *argv[] = { NULL, NULL };
+
+       argv[0] = params->cmd;
 
        memset(&child_process, 0, sizeof(child_process));
        child_process.argv = argv;
@@ -374,12 +454,16 @@ static int read_convert_config(const char *var, const char *value, void *cb)
 
 static void setup_convert_check(struct git_attr_check *check)
 {
+       static struct git_attr *attr_text;
        static struct git_attr *attr_crlf;
+       static struct git_attr *attr_eol;
        static struct git_attr *attr_ident;
        static struct git_attr *attr_filter;
 
-       if (!attr_crlf) {
+       if (!attr_text) {
+               attr_text = git_attr("text");
                attr_crlf = git_attr("crlf");
+               attr_eol = git_attr("eol");
                attr_ident = git_attr("ident");
                attr_filter = git_attr("filter");
                user_convert_tail = &user_convert;
@@ -388,6 +472,8 @@ static void setup_convert_check(struct git_attr_check *check)
        check[0].attr = attr_crlf;
        check[1].attr = attr_ident;
        check[2].attr = attr_filter;
+       check[3].attr = attr_eol;
+       check[4].attr = attr_text;
 }
 
 static int count_ident(const char *cp, unsigned long size)
@@ -425,6 +511,8 @@ static int count_ident(const char *cp, unsigned long size)
                                cnt++;
                                break;
                        }
+                       if (ch == '\n')
+                               break;
                }
        }
        return cnt;
@@ -455,6 +543,11 @@ static int ident_to_git(const char *path, const char *src, size_t len,
                        dollar = memchr(src + 3, '$', len - 3);
                        if (!dollar)
                                break;
+                       if (memchr(src + 3, '\n', dollar - src - 3)) {
+                               /* Line break before the next dollar. */
+                               continue;
+                       }
+
                        memcpy(dst, "Id$", 3);
                        dst += 3;
                        len -= dollar + 1 - src;
@@ -470,7 +563,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
                              struct strbuf *buf, int ident)
 {
        unsigned char sha1[20];
-       char *to_free = NULL, *dollar;
+       char *to_free = NULL, *dollar, *spc;
        int cnt;
 
        if (!ident)
@@ -506,7 +599,10 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
                } else if (src[2] == ':') {
                        /*
                         * It's possible that an expanded Id has crept its way into the
-                        * repository, we cope with that by stripping the expansion out
+                        * repository, we cope with that by stripping the expansion out.
+                        * This is probably not a good idea, since it will cause changes
+                        * on checkout, which won't go away by stash, but let's keep it
+                        * for git-style ids.
                         */
                        dollar = memchr(src + 3, '$', len - 3);
                        if (!dollar) {
@@ -514,6 +610,20 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
                                break;
                        }
 
+                       if (memchr(src + 3, '\n', dollar - src - 3)) {
+                               /* Line break before the next dollar. */
+                               continue;
+                       }
+
+                       spc = memchr(src + 4, ' ', dollar - src - 4);
+                       if (spc && spc < dollar-1) {
+                               /* There are spaces in unexpected places.
+                                * This is probably an id from some other
+                                * versioning system. Keep it for now.
+                                */
+                               continue;
+                       }
+
                        len -= dollar + 1 - src;
                        src  = dollar + 1;
                } else {
@@ -544,9 +654,24 @@ static int git_path_check_crlf(const char *path, struct git_attr_check *check)
                ;
        else if (!strcmp(value, "input"))
                return CRLF_INPUT;
+       else if (!strcmp(value, "auto"))
+               return CRLF_AUTO;
        return CRLF_GUESS;
 }
 
+static int git_path_check_eol(const char *path, struct git_attr_check *check)
+{
+       const char *value = check->value;
+
+       if (ATTR_UNSET(value))
+               ;
+       else if (!strcmp(value, "lf"))
+               return EOL_LF;
+       else if (!strcmp(value, "crlf"))
+               return EOL_CRLF;
+       return EOL_UNSET;
+}
+
 static struct convert_driver *git_path_check_convert(const char *path,
                                             struct git_attr_check *check)
 {
@@ -568,20 +693,34 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check)
        return !!ATTR_TRUE(value);
 }
 
+enum action determine_action(enum action text_attr, enum eol eol_attr) {
+       if (text_attr == CRLF_BINARY)
+               return CRLF_BINARY;
+       if (eol_attr == EOL_LF)
+               return CRLF_INPUT;
+       if (eol_attr == EOL_CRLF)
+               return CRLF_CRLF;
+       return text_attr;
+}
+
 int convert_to_git(const char *path, const char *src, size_t len,
                    struct strbuf *dst, enum safe_crlf checksafe)
 {
-       struct git_attr_check check[3];
-       int crlf = CRLF_GUESS;
+       struct git_attr_check check[5];
+       enum action action = CRLF_GUESS;
+       enum eol eol_attr = EOL_UNSET;
        int ident = 0, ret = 0;
        const char *filter = NULL;
 
        setup_convert_check(check);
        if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
                struct convert_driver *drv;
-               crlf = git_path_check_crlf(path, check + 0);
+               action = git_path_check_crlf(path, check + 4);
+               if (action == CRLF_GUESS)
+                       action = git_path_check_crlf(path, check + 0);
                ident = git_path_check_ident(path, check + 1);
                drv = git_path_check_convert(path, check + 2);
+               eol_attr = git_path_check_eol(path, check + 3);
                if (drv && drv->clean)
                        filter = drv->clean;
        }
@@ -591,7 +730,8 @@ int convert_to_git(const char *path, const char *src, size_t len,
                src = dst->buf;
                len = dst->len;
        }
-       ret |= crlf_to_git(path, src, len, dst, crlf, checksafe);
+       action = determine_action(action, eol_attr);
+       ret |= crlf_to_git(path, src, len, dst, action, checksafe);
        if (ret) {
                src = dst->buf;
                len = dst->len;
@@ -601,17 +741,21 @@ int convert_to_git(const char *path, const char *src, size_t len,
 
 int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
 {
-       struct git_attr_check check[3];
-       int crlf = CRLF_GUESS;
+       struct git_attr_check check[5];
+       enum action action = CRLF_GUESS;
+       enum eol eol_attr = EOL_UNSET;
        int ident = 0, ret = 0;
        const char *filter = NULL;
 
        setup_convert_check(check);
        if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
                struct convert_driver *drv;
-               crlf = git_path_check_crlf(path, check + 0);
+               action = git_path_check_crlf(path, check + 4);
+               if (action == CRLF_GUESS)
+                       action = git_path_check_crlf(path, check + 0);
                ident = git_path_check_ident(path, check + 1);
                drv = git_path_check_convert(path, check + 2);
+               eol_attr = git_path_check_eol(path, check + 3);
                if (drv && drv->smudge)
                        filter = drv->smudge;
        }
@@ -621,7 +765,8 @@ int convert_to_working_tree(const char *path, const char *src, size_t len, struc
                src = dst->buf;
                len = dst->len;
        }
-       ret |= crlf_to_worktree(path, src, len, dst, crlf);
+       action = determine_action(action, eol_attr);
+       ret |= crlf_to_worktree(path, src, len, dst, action);
        if (ret) {
                src = dst->buf;
                len = dst->len;
diff --git a/ctype.c b/ctype.c
index 7ee64c7d77dd4a5665f70d80ffba1bcdecb9a408..de600279eef4765db497599e6654c2bedd978129 100644 (file)
--- a/ctype.c
+++ b/ctype.c
@@ -10,7 +10,7 @@ enum {
        A = GIT_ALPHA,
        D = GIT_DIGIT,
        G = GIT_GLOB_SPECIAL,   /* *, ?, [, \\ */
-       R = GIT_REGEX_SPECIAL,  /* $, (, ), +, ., ^, {, | */
+       R = GIT_REGEX_SPECIAL   /* $, (, ), +, ., ^, {, | */
 };
 
 unsigned char sane_ctype[256] = {
index a90ab10505a3694de83a0ffd8fc472518f12cf2b..e22a2b7fa5f6fcfc5b26541a21c10d3c8b7eda81 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -141,15 +141,14 @@ static char *path_ok(char *directory)
        }
        else if (interpolated_path && saw_extended_args) {
                struct strbuf expanded_path = STRBUF_INIT;
-               struct strbuf_expand_dict_entry dict[] = {
-                       { "H", hostname },
-                       { "CH", canon_hostname },
-                       { "IP", ip_address },
-                       { "P", tcp_port },
-                       { "D", directory },
-                       { NULL }
-               };
-
+               struct strbuf_expand_dict_entry dict[6];
+
+               dict[0].placeholder = "H"; dict[0].value = hostname;
+               dict[1].placeholder = "CH"; dict[1].value = canon_hostname;
+               dict[2].placeholder = "IP"; dict[2].value = ip_address;
+               dict[3].placeholder = "P"; dict[3].value = tcp_port;
+               dict[4].placeholder = "D"; dict[4].value = directory;
+               dict[5].placeholder = NULL; dict[5].value = NULL;
                if (*dir != '/') {
                        /* Allow only absolute */
                        logerror("'%s': Non-absolute path denied (interpolated-path active)", dir);
@@ -343,7 +342,9 @@ static int upload_pack(void)
 {
        /* Timeout as string */
        char timeout_buf[64];
-       const char *argv[] = { "upload-pack", "--strict", timeout_buf, ".", NULL };
+       const char *argv[] = { "upload-pack", "--strict", NULL, ".", NULL };
+
+       argv[2] = timeout_buf;
 
        snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
        return run_service_command(argv);
diff --git a/date.c b/date.c
index 6bae49ca330a69df5e2cf29e1e461f2490d99b2f..68cdcaa3f6268855539fcae02c8e87b965204b4d 100644 (file)
--- a/date.c
+++ b/date.c
@@ -586,11 +586,17 @@ static int date_string(unsigned long date, int offset, char *buf, int len)
 
 /* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
    (i.e. English) day/month names, and it doesn't work correctly with %z. */
-int parse_date(const char *date, char *result, int maxlen)
+int parse_date_toffset(const char *date, unsigned long *timestamp, int *offset)
 {
        struct tm tm;
-       int offset, tm_gmt;
-       time_t then;
+       int tm_gmt;
+       unsigned long dummy_timestamp;
+       int dummy_offset;
+
+       if (!timestamp)
+               timestamp = &dummy_timestamp;
+       if (!offset)
+               offset = &dummy_offset;
 
        memset(&tm, 0, sizeof(tm));
        tm.tm_year = -1;
@@ -600,7 +606,7 @@ int parse_date(const char *date, char *result, int maxlen)
        tm.tm_hour = -1;
        tm.tm_min = -1;
        tm.tm_sec = -1;
-       offset = -1;
+       *offset = -1;
        tm_gmt = 0;
 
        for (;;) {
@@ -612,11 +618,11 @@ int parse_date(const char *date, char *result, int maxlen)
                        break;
 
                if (isalpha(c))
-                       match = match_alpha(date, &tm, &offset);
+                       match = match_alpha(date, &tm, offset);
                else if (isdigit(c))
-                       match = match_digit(date, &tm, &offset, &tm_gmt);
+                       match = match_digit(date, &tm, offset, &tm_gmt);
                else if ((c == '-' || c == '+') && isdigit(date[1]))
-                       match = match_tz(date, &offset);
+                       match = match_tz(date, offset);
 
                if (!match) {
                        /* BAD CRAP */
@@ -627,16 +633,26 @@ int parse_date(const char *date, char *result, int maxlen)
        }
 
        /* mktime uses local timezone */
-       then = tm_to_time_t(&tm);
-       if (offset == -1)
-               offset = (then - mktime(&tm)) / 60;
+       *timestamp = tm_to_time_t(&tm);
+       if (*offset == -1)
+               *offset = (*timestamp - mktime(&tm)) / 60;
 
-       if (then == -1)
+       if (*timestamp == -1)
                return -1;
 
        if (!tm_gmt)
-               then -= offset * 60;
-       return date_string(then, offset, result, maxlen);
+               *timestamp -= *offset * 60;
+       return 1; /* success */
+}
+
+int parse_date(const char *date, char *result, int maxlen)
+{
+       unsigned long timestamp;
+       int offset;
+       if (parse_date_toffset(date, &timestamp, &offset) > 0)
+               return date_string(timestamp, offset, result, maxlen);
+       else
+               return -1;
 }
 
 enum date_mode parse_date_format(const char *format)
@@ -984,11 +1000,12 @@ static unsigned long approxidate_str(const char *date,
 
 unsigned long approxidate_relative(const char *date, const struct timeval *tv)
 {
-       char buffer[50];
+       unsigned long timestamp;
+       int offset;
        int errors = 0;
 
-       if (parse_date(date, buffer, sizeof(buffer)) > 0)
-               return strtoul(buffer, NULL, 0);
+       if (parse_date_toffset(date, &timestamp, &offset) > 0)
+               return timestamp;
 
        return approxidate_str(date, tv, &errors);
 }
@@ -996,14 +1013,15 @@ unsigned long approxidate_relative(const char *date, const struct timeval *tv)
 unsigned long approxidate_careful(const char *date, int *error_ret)
 {
        struct timeval tv;
-       char buffer[50];
+       unsigned long timestamp;
+       int offset;
        int dummy = 0;
        if (!error_ret)
                error_ret = &dummy;
 
-       if (parse_date(date, buffer, sizeof(buffer)) > 0) {
+       if (parse_date_toffset(date, &timestamp, &offset) > 0) {
                *error_ret = 0;
-               return strtoul(buffer, NULL, 0);
+               return timestamp;
        }
 
        gettimeofday(&tv, NULL);
diff --git a/diff.c b/diff.c
index 57e1212fbcc9783d3fdbd248762247d8e0efd886..c692526603358c189dce213f2d9729a7c26efa94 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -30,6 +30,7 @@ static const char *diff_word_regex_cfg;
 static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
+static int diff_no_prefix;
 
 static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@ -44,7 +45,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)
 {
@@ -100,6 +102,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                diff_mnemonic_prefix = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "diff.noprefix")) {
+               diff_no_prefix = git_config_bool(var, value);
+               return 0;
+       }
        if (!strcmp(var, "diff.external"))
                return git_config_string(&external_diff_cmd_cfg, var, value);
        if (!strcmp(var, "diff.wordregex"))
@@ -193,8 +199,8 @@ struct emit_callback {
        sane_truncate_fn truncate;
        const char **label_path;
        struct diff_words_data *diff_words;
+       struct diff_options *opt;
        int *found_changesp;
-       FILE *file;
        struct strbuf *header;
 };
 
@@ -281,11 +287,19 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
        ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
 }
 
-static void emit_line_0(FILE *file, const char *set, const char *reset,
+static void emit_line_0(struct diff_options *o, const char *set, const char *reset,
                        int first, const char *line, int len)
 {
        int has_trailing_newline, has_trailing_carriage_return;
        int nofirst;
+       FILE *file = o->file;
+
+       if (o->output_prefix) {
+               struct strbuf *msg = NULL;
+               msg = o->output_prefix(o, o->output_prefix_data);
+               assert(msg);
+               fwrite(msg->buf, msg->len, 1, file);
+       }
 
        if (len == 0) {
                has_trailing_newline = (first == '\n');
@@ -315,10 +329,10 @@ static void emit_line_0(FILE *file, const char *set, const char *reset,
                fputc('\n', file);
 }
 
-static void emit_line(FILE *file, const char *set, const char *reset,
+static void emit_line(struct diff_options *o, const char *set, const char *reset,
                      const char *line, int len)
 {
-       emit_line_0(file, set, reset, line[0], line+1, len-1);
+       emit_line_0(o, set, reset, line[0], line+1, len-1);
 }
 
 static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len)
@@ -340,15 +354,15 @@ static void emit_add_line(const char *reset,
        const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
 
        if (!*ws)
-               emit_line_0(ecbdata->file, set, reset, '+', line, len);
+               emit_line_0(ecbdata->opt, set, reset, '+', line, len);
        else if (new_blank_line_at_eof(ecbdata, line, len))
                /* Blank line at EOF - paint '+' as well */
-               emit_line_0(ecbdata->file, ws, reset, '+', line, len);
+               emit_line_0(ecbdata->opt, ws, reset, '+', line, len);
        else {
                /* Emit just the prefix, then the rest. */
-               emit_line_0(ecbdata->file, set, reset, '+', "", 0);
+               emit_line_0(ecbdata->opt, set, reset, '+', "", 0);
                ws_check_emit(line, len, ecbdata->ws_rule,
-                             ecbdata->file, set, reset, ws);
+                             ecbdata->opt->file, set, reset, ws);
        }
 }
 
@@ -361,6 +375,9 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
        static const char atat[2] = { '@', '@' };
        const char *cp, *ep;
+       struct strbuf msgbuf = STRBUF_INIT;
+       int org_len = len;
+       int i = 1;
 
        /*
         * As a hunk header must begin with "@@ -<old>, +<new> @@",
@@ -369,23 +386,42 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
        if (len < 10 ||
            memcmp(line, atat, 2) ||
            !(ep = memmem(line + 2, len - 2, atat, 2))) {
-               emit_line(ecbdata->file, plain, reset, line, len);
+               emit_line(ecbdata->opt, plain, reset, line, len);
                return;
        }
        ep += 2; /* skip over @@ */
 
        /* The hunk header in fraginfo color */
-       emit_line(ecbdata->file, frag, reset, line, ep - line);
+       strbuf_add(&msgbuf, frag, strlen(frag));
+       strbuf_add(&msgbuf, line, ep - line);
+       strbuf_add(&msgbuf, reset, strlen(reset));
+
+       /*
+        * trailing "\r\n"
+        */
+       for ( ; i < 3; i++)
+               if (line[len - i] == '\r' || line[len - i] == '\n')
+                       len--;
 
        /* blank before the func header */
        for (cp = ep; ep - line < len; ep++)
                if (*ep != ' ' && *ep != '\t')
                        break;
-       if (ep != cp)
-               emit_line(ecbdata->file, plain, reset, cp, ep - cp);
+       if (ep != cp) {
+               strbuf_add(&msgbuf, plain, strlen(plain));
+               strbuf_add(&msgbuf, cp, ep - cp);
+               strbuf_add(&msgbuf, reset, strlen(reset));
+       }
 
-       if (ep < line + len)
-               emit_line(ecbdata->file, func, reset, ep, line + len - ep);
+       if (ep < line + len) {
+               strbuf_add(&msgbuf, func, strlen(func));
+               strbuf_add(&msgbuf, ep, line + len - ep);
+               strbuf_add(&msgbuf, reset, strlen(reset));
+       }
+
+       strbuf_add(&msgbuf, line + len, org_len - len);
+       emit_line(ecbdata->opt, "", "", msgbuf.buf, msgbuf.len);
+       strbuf_release(&msgbuf);
 }
 
 static struct diff_tempfile *claim_diff_tempfile(void) {
@@ -445,7 +481,7 @@ static void emit_rewrite_lines(struct emit_callback *ecb,
                len = endp ? (endp - data + 1) : size;
                if (prefix != '+') {
                        ecb->lno_in_preimage++;
-                       emit_line_0(ecb->file, old, reset, '-',
+                       emit_line_0(ecb->opt, old, reset, '-',
                                    data, len);
                } else {
                        ecb->lno_in_postimage++;
@@ -457,7 +493,7 @@ static void emit_rewrite_lines(struct emit_callback *ecb,
        if (!endp) {
                const char *plain = diff_get_color(ecb->color_diff,
                                                   DIFF_PLAIN);
-               emit_line_0(ecb->file, plain, reset, '\\',
+               emit_line_0(ecb->opt, plain, reset, '\\',
                            nneof, strlen(nneof));
        }
 }
@@ -466,8 +502,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,9 +514,16 @@ 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;
+       char *line_prefix = "";
+       struct strbuf *msgbuf;
+
+       if (o && o->output_prefix) {
+               msgbuf = o->output_prefix(o, o->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
 
        if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
                a_prefix = o->b_prefix;
@@ -500,32 +543,14 @@ 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;
        ecbdata.found_changesp = &o->found_changes;
        ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
-       ecbdata.file = o->file;
+       ecbdata.opt = o;
        if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
                mmfile_t mf1, mf2;
                mf1.ptr = (char *)data_one;
@@ -540,9 +565,10 @@ static void emit_rewrite_diff(const char *name_a,
        lc_a = count_lines(data_one, size_one);
        lc_b = count_lines(data_two, size_two);
        fprintf(o->file,
-               "%s--- %s%s%s\n%s+++ %s%s%s\n%s@@ -",
-               metainfo, a_name.buf, name_a_tab, reset,
-               metainfo, b_name.buf, name_b_tab, reset, fraginfo);
+               "%s%s--- %s%s%s\n%s%s+++ %s%s%s\n%s%s@@ -",
+               line_prefix, metainfo, a_name.buf, name_a_tab, reset,
+               line_prefix, metainfo, b_name.buf, name_b_tab, reset,
+               line_prefix, fraginfo);
        print_line_count(o->file, lc_a);
        fprintf(o->file, " +");
        print_line_count(o->file, lc_b);
@@ -577,23 +603,134 @@ 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;
+       int last_minus;
+       struct diff_options *opt;
        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,
+                                         const char *line_prefix)
+{
+       int print = 0;
+
+       while (count) {
+               char *p = memchr(buf, '\n', count);
+               if (print)
+                       fputs(line_prefix, fp);
+               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;
+               print = 1;
+       }
+       return 0;
+}
+
+/*
+ * '--color-words' algorithm can be described as:
+ *
+ *   1. collect a the minus/plus lines of a diff hunk, divided into
+ *      minus-lines and plus-lines;
+ *
+ *   2. break both minus-lines and plus-lines into words and
+ *      place them into two mmfile_t with one word for each line;
+ *
+ *   3. use xdiff to run diff on the two mmfile_t to get the words level diff;
+ *
+ * And for the common parts of the both file, we output the plus side text.
+ * diff_words->current_plus is used to trace the current position of the plus file
+ * which printed. diff_words->last_minus is used to trace the last minus word
+ * printed.
+ *
+ * For '--graph' to work with '--color-words', we need to output the graph prefix
+ * on each line of color words output. Generally, there are two conditions on
+ * which we should output the prefix.
+ *
+ *   1. diff_words->last_minus == 0 &&
+ *      diff_words->current_plus == diff_words->plus.text.ptr
+ *
+ *      that is: the plus text must start as a new line, and if there is no minus
+ *      word printed, a graph prefix must be printed.
+ *
+ *   2. diff_words->current_plus > diff_words->plus.text.ptr &&
+ *      *(diff_words->current_plus - 1) == '\n'
+ *
+ *      that is: a graph prefix must be printed following a '\n'
+ */
+static int color_words_output_graph_prefix(struct diff_words_data *diff_words)
+{
+       if ((diff_words->last_minus == 0 &&
+               diff_words->current_plus == diff_words->plus.text.ptr) ||
+               (diff_words->current_plus > diff_words->plus.text.ptr &&
+               *(diff_words->current_plus - 1) == '\n')) {
+               return 1;
+       } else {
+               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;
+       struct diff_options *opt = diff_words->opt;
+       struct strbuf *msgbuf;
+       char *line_prefix = "";
 
        if (line[0] != '@' || parse_hunk_header(line, len,
                        &minus_first, &minus_len, &plus_first, &plus_len))
                return;
 
+       assert(opt);
+       if (opt->output_prefix) {
+               msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
+
        /* POSIX requires that first be decremented by one if len == 0... */
        if (minus_len) {
                minus_begin = diff_words->minus.orig[minus_first].begin;
@@ -609,20 +746,32 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
        } else
                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);
-       if (minus_begin != minus_end)
-               color_fwrite_lines(diff_words->file,
-                               diff_get_color(1, DIFF_FILE_OLD),
-                               minus_end - minus_begin, minus_begin);
-       if (plus_begin != plus_end)
-               color_fwrite_lines(diff_words->file,
-                               diff_get_color(1, DIFF_FILE_NEW),
-                               plus_end - plus_begin, plus_begin);
+       if (color_words_output_graph_prefix(diff_words)) {
+               fputs(line_prefix, diff_words->opt->file);
+       }
+       if (diff_words->current_plus != plus_begin) {
+               fn_out_diff_words_write_helper(diff_words->opt->file,
+                               &style->ctx, style->newline,
+                               plus_begin - diff_words->current_plus,
+                               diff_words->current_plus, line_prefix);
+               if (*(plus_begin - 1) == '\n')
+                       fputs(line_prefix, diff_words->opt->file);
+       }
+       if (minus_begin != minus_end) {
+               fn_out_diff_words_write_helper(diff_words->opt->file,
+                               &style->old, style->newline,
+                               minus_end - minus_begin, minus_begin,
+                               line_prefix);
+       }
+       if (plus_begin != plus_end) {
+               fn_out_diff_words_write_helper(diff_words->opt->file,
+                               &style->new, style->newline,
+                               plus_end - plus_begin, plus_begin,
+                               line_prefix);
+       }
 
        diff_words->current_plus = plus_end;
+       diff_words->last_minus = minus_first;
 }
 
 /* This function starts looking at *begin, and returns 0 iff a word was found. */
@@ -701,17 +850,31 @@ static void diff_words_show(struct diff_words_data *diff_words)
        xpparam_t xpp;
        xdemitconf_t xecfg;
        mmfile_t minus, plus;
+       struct diff_words_style *style = diff_words->style;
+
+       struct diff_options *opt = diff_words->opt;
+       struct strbuf *msgbuf;
+       char *line_prefix = "";
+
+       assert(opt);
+       if (opt->output_prefix) {
+               msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
 
        /* special case: only removal */
        if (!diff_words->plus.text.size) {
-               color_fwrite_lines(diff_words->file,
-                       diff_get_color(1, DIFF_FILE_OLD),
-                       diff_words->minus.text.size, diff_words->minus.text.ptr);
+               fputs(line_prefix, diff_words->opt->file);
+               fn_out_diff_words_write_helper(diff_words->opt->file,
+                       &style->old, style->newline,
+                       diff_words->minus.text.size,
+                       diff_words->minus.text.ptr, line_prefix);
                diff_words->minus.text.size = 0;
                return;
        }
 
        diff_words->current_plus = diff_words->plus.text.ptr;
+       diff_words->last_minus = 0;
 
        memset(&xpp, 0, sizeof(xpp));
        memset(&xecfg, 0, sizeof(xecfg));
@@ -725,11 +888,15 @@ static void diff_words_show(struct diff_words_data *diff_words)
        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,
+                       diff_words->plus.text.size) {
+               if (color_words_output_graph_prefix(diff_words))
+                       fputs(line_prefix, diff_words->opt->file);
+               fn_out_diff_words_write_helper(diff_words->opt->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,
+                       line_prefix);
+       }
        diff_words->minus.text.size = diff_words->plus.text.size = 0;
 }
 
@@ -801,9 +968,17 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
        const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+       struct diff_options *o = ecbdata->opt;
+       char *line_prefix = "";
+       struct strbuf *msgbuf;
+
+       if (o && o->output_prefix) {
+               msgbuf = o->output_prefix(o, o->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
 
        if (ecbdata->header) {
-               fprintf(ecbdata->file, "%s", ecbdata->header->buf);
+               fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf);
                strbuf_reset(ecbdata->header);
                ecbdata->header = NULL;
        }
@@ -815,10 +990,10 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : "";
                name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : "";
 
-               fprintf(ecbdata->file, "%s--- %s%s%s\n",
-                       meta, ecbdata->label_path[0], reset, name_a_tab);
-               fprintf(ecbdata->file, "%s+++ %s%s%s\n",
-                       meta, ecbdata->label_path[1], reset, name_b_tab);
+               fprintf(ecbdata->opt->file, "%s%s--- %s%s%s\n",
+                       line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab);
+               fprintf(ecbdata->opt->file, "%s%s+++ %s%s%s\n",
+                       line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab);
                ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
        }
 
@@ -835,12 +1010,15 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                find_lno(line, ecbdata);
                emit_hunk_header(ecbdata, line, len);
                if (line[len-1] != '\n')
-                       putc('\n', ecbdata->file);
+                       putc('\n', ecbdata->opt->file);
                return;
        }
 
        if (len < 1) {
-               emit_line(ecbdata->file, reset, reset, line, len);
+               emit_line(ecbdata->opt, reset, reset, line, len);
+               if (ecbdata->diff_words
+                   && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN)
+                       fputs("~\n", ecbdata->opt->file);
                return;
        }
 
@@ -855,9 +1033,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->opt, plain, reset, line, len);
+                       fputs("~\n", ecbdata->opt->file);
+               } else {
+                       /* don't print the prefix character */
+                       emit_line(ecbdata->opt, plain, reset, line+1, len-1);
+               }
                return;
        }
 
@@ -868,7 +1050,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                ecbdata->lno_in_preimage++;
                if (line[0] == ' ')
                        ecbdata->lno_in_postimage++;
-               emit_line(ecbdata->file, color, reset, line, len);
+               emit_line(ecbdata->opt, color, reset, line, len);
        } else {
                ecbdata->lno_in_postimage++;
                emit_add_line(reset, ecbdata, line + 1, len - 1);
@@ -1048,10 +1230,17 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        int total_files = data->nr;
        int width, name_width;
        const char *reset, *set, *add_c, *del_c;
+       const char *line_prefix = "";
+       struct strbuf *msg = NULL;
 
        if (data->nr == 0)
                return;
 
+       if (options->output_prefix) {
+               msg = options->output_prefix(options, options->output_prefix_data);
+               line_prefix = msg->buf;
+       }
+
        width = options->stat_width ? options->stat_width : 80;
        name_width = options->stat_name_width ? options->stat_name_width : 50;
 
@@ -1121,6 +1310,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                }
 
                if (data->files[i]->is_binary) {
+                       fprintf(options->file, "%s", line_prefix);
                        show_name(options->file, prefix, name, len);
                        fprintf(options->file, "  Bin ");
                        fprintf(options->file, "%s%"PRIuMAX"%s",
@@ -1133,6 +1323,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                        continue;
                }
                else if (data->files[i]->is_unmerged) {
+                       fprintf(options->file, "%s", line_prefix);
                        show_name(options->file, prefix, name, len);
                        fprintf(options->file, "  Unmerged\n");
                        continue;
@@ -1155,6 +1346,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                        add = scale_linear(add, width, max_change);
                        del = scale_linear(del, width, max_change);
                }
+               fprintf(options->file, "%s", line_prefix);
                show_name(options->file, prefix, name, len);
                fprintf(options->file, "%5"PRIuMAX"%s", added + deleted,
                                added + deleted ? " " : "");
@@ -1162,6 +1354,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
        }
+       fprintf(options->file, "%s", line_prefix);
        fprintf(options->file,
               " %d files changed, %d insertions(+), %d deletions(-)\n",
               total_files, adds, dels);
@@ -1188,6 +1381,12 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option
                        }
                }
        }
+       if (options->output_prefix) {
+               struct strbuf *msg = NULL;
+               msg = options->output_prefix(options,
+                               options->output_prefix_data);
+               fprintf(options->file, "%s", msg->buf);
+       }
        fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n",
               total_files, adds, dels);
 }
@@ -1202,6 +1401,13 @@ static void show_numstat(struct diffstat_t *data, struct diff_options *options)
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
 
+               if (options->output_prefix) {
+                       struct strbuf *msg = NULL;
+                       msg = options->output_prefix(options,
+                                       options->output_prefix_data);
+                       fprintf(options->file, "%s", msg->buf);
+               }
+
                if (file->is_binary)
                        fprintf(options->file, "-\t-\t");
                else
@@ -1237,10 +1443,18 @@ struct dirstat_dir {
        int alloc, nr, percent, cumulative;
 };
 
-static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long changed, const char *base, int baselen)
+static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
+               unsigned long changed, const char *base, int baselen)
 {
        unsigned long this_dir = 0;
        unsigned int sources = 0;
+       const char *line_prefix = "";
+       struct strbuf *msg = NULL;
+
+       if (opt->output_prefix) {
+               msg = opt->output_prefix(opt, opt->output_prefix_data);
+               line_prefix = msg->buf;
+       }
 
        while (dir->nr) {
                struct dirstat_file *f = dir->files;
@@ -1255,7 +1469,7 @@ static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long ch
                slash = strchr(f->name + baselen, '/');
                if (slash) {
                        int newbaselen = slash + 1 - f->name;
-                       this = gather_dirstat(file, dir, changed, f->name, newbaselen);
+                       this = gather_dirstat(opt, dir, changed, f->name, newbaselen);
                        sources++;
                } else {
                        this = f->changed;
@@ -1277,7 +1491,8 @@ static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long ch
                if (permille) {
                        int percent = permille / 10;
                        if (percent >= dir->percent) {
-                               fprintf(file, "%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base);
+                               fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix,
+                                       percent, permille % 10, baselen, base);
                                if (!dir->cumulative)
                                        return 0;
                        }
@@ -1357,7 +1572,7 @@ static void show_dirstat(struct diff_options *options)
 
        /* Show all directories with more than x% of the changes */
        qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
-       gather_dirstat(options->file, &dir, changed, "", 0);
+       gather_dirstat(options, &dir, changed, "", 0);
 }
 
 static void free_diffstat_info(struct diffstat_t *diffstat)
@@ -1415,6 +1630,15 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
        const char *reset = diff_get_color(color_diff, DIFF_RESET);
        const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
        char *err;
+       char *line_prefix = "";
+       struct strbuf *msgbuf;
+
+       assert(data->o);
+       if (data->o->output_prefix) {
+               msgbuf = data->o->output_prefix(data->o,
+                       data->o->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
 
        if (line[0] == '+') {
                unsigned bad;
@@ -1422,18 +1646,18 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
                if (is_conflict_marker(line + 1, marker_size, len - 1)) {
                        data->status |= 1;
                        fprintf(data->o->file,
-                               "%s:%d: leftover conflict marker\n",
-                               data->filename, data->lineno);
+                               "%s%s:%d: leftover conflict marker\n",
+                               line_prefix, data->filename, data->lineno);
                }
                bad = ws_check(line + 1, len - 1, data->ws_rule);
                if (!bad)
                        return;
                data->status |= bad;
                err = whitespace_error_string(bad);
-               fprintf(data->o->file, "%s:%d: %s.\n",
-                       data->filename, data->lineno, err);
+               fprintf(data->o->file, "%s%s:%d: %s.\n",
+                       line_prefix, data->filename, data->lineno, err);
                free(err);
-               emit_line(data->o->file, set, reset, line, 1);
+               emit_line(data->o, set, reset, line, 1);
                ws_check_emit(line + 1, len - 1, data->ws_rule,
                              data->o->file, set, reset, ws);
        } else if (line[0] == ' ') {
@@ -1471,7 +1695,7 @@ static unsigned char *deflate_it(char *data,
        return deflated;
 }
 
-static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two)
+static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix)
 {
        void *cp;
        void *delta;
@@ -1500,13 +1724,13 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two)
        }
 
        if (delta && delta_size < deflate_size) {
-               fprintf(file, "delta %lu\n", orig_size);
+               fprintf(file, "%sdelta %lu\n", prefix, orig_size);
                free(deflated);
                data = delta;
                data_size = delta_size;
        }
        else {
-               fprintf(file, "literal %lu\n", two->size);
+               fprintf(file, "%sliteral %lu\n", prefix, two->size);
                free(delta);
                data = deflated;
                data_size = deflate_size;
@@ -1524,18 +1748,19 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two)
                        line[0] = bytes - 26 + 'a' - 1;
                encode_85(line + 1, cp, bytes);
                cp = (char *) cp + bytes;
+               fprintf(file, "%s", prefix);
                fputs(line, file);
                fputc('\n', file);
        }
-       fprintf(file, "\n");
+       fprintf(file, "%s\n", prefix);
        free(data);
 }
 
-static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two)
+static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix)
 {
-       fprintf(file, "GIT binary patch\n");
-       emit_binary_diff_body(file, one, two);
-       emit_binary_diff_body(file, two, one);
+       fprintf(file, "%sGIT binary patch\n", prefix);
+       emit_binary_diff_body(file, one, two, prefix);
+       emit_binary_diff_body(file, two, one, prefix);
 }
 
 static void diff_filespec_load_driver(struct diff_filespec *one)
@@ -1585,14 +1810,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,8 +1847,16 @@ 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;
+       struct strbuf *msgbuf;
+       char *line_prefix = "";
+
+       if (o->output_prefix) {
+               msgbuf = o->output_prefix(o, o->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
 
        if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
                        (!one->mode || S_ISGITLINK(one->mode)) &&
@@ -1646,24 +1891,24 @@ static void builtin_diff(const char *name_a,
        b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
        lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
        lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
-       strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
+       strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, set, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
-               strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
+               strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, set, two->mode, reset);
                if (xfrm_msg)
                        strbuf_addstr(&header, xfrm_msg);
                must_show_header = 1;
        }
        else if (lbl[1][0] == '/') {
-               strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
+               strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, set, one->mode, reset);
                if (xfrm_msg)
                        strbuf_addstr(&header, xfrm_msg);
                must_show_header = 1;
        }
        else {
                if (one->mode != two->mode) {
-                       strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
-                       strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
+                       strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, set, one->mode, reset);
+                       strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, set, two->mode, reset);
                        must_show_header = 1;
                }
                if (xfrm_msg)
@@ -1687,12 +1932,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)) {
@@ -1703,10 +1947,10 @@ static void builtin_diff(const char *name_a,
                fprintf(o->file, "%s", header.buf);
                strbuf_reset(&header);
                if (DIFF_OPT_TST(o, BINARY))
-                       emit_binary_diff(o->file, &mf1, &mf2);
+                       emit_binary_diff(o->file, &mf1, &mf2, line_prefix);
                else
-                       fprintf(o->file, "Binary files %s and %s differ\n",
-                               lbl[0], lbl[1]);
+                       fprintf(o->file, "%sBinary files %s and %s differ\n",
+                               line_prefix, lbl[0], lbl[1]);
                o->found_changes = 1;
        }
        else {
@@ -1722,20 +1966,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)
@@ -1750,7 +1982,7 @@ static void builtin_diff(const char *name_a,
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
-               ecbdata.file = o->file;
+               ecbdata.opt = o;
                ecbdata.header = header.len ? &header : NULL;
                xpp.flags = o->xdl_opts;
                xecfg.ctxlen = o->context;
@@ -1764,10 +1996,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;
+                       ecbdata.diff_words->opt = o;
                        if (!o->word_regex)
                                o->word_regex = userdiff_word_regex(one);
                        if (!o->word_regex)
@@ -1783,10 +2018,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);
-               if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
+               if (o->word_diff)
                        free_diff_words_data(&ecbdata);
                if (textconv_one)
                        free(mf1.ptr);
@@ -2332,31 +2580,41 @@ static void fill_metainfo(struct strbuf *msg,
 {
        const char *set = diff_get_color(use_color, DIFF_METAINFO);
        const char *reset = diff_get_color(use_color, DIFF_RESET);
+       struct strbuf *msgbuf;
+       char *line_prefix = "";
 
        *must_show_header = 1;
+       if (o->output_prefix) {
+               msgbuf = o->output_prefix(o, o->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
        strbuf_init(msg, PATH_MAX * 2 + 300);
        switch (p->status) {
        case DIFF_STATUS_COPIED:
-               strbuf_addf(msg, "%ssimilarity index %d%%",
-                           set, similarity_index(p));
-               strbuf_addf(msg, "%s\n%scopy from ", reset, set);
+               strbuf_addf(msg, "%s%ssimilarity index %d%%",
+                           line_prefix, set, similarity_index(p));
+               strbuf_addf(msg, "%s\n%s%scopy from ",
+                           reset,  line_prefix, set);
                quote_c_style(name, msg, NULL, 0);
-               strbuf_addf(msg, "%s\n%scopy to ", reset, set);
+               strbuf_addf(msg, "%s\n%s%scopy to ", reset, line_prefix, set);
                quote_c_style(other, msg, NULL, 0);
                strbuf_addf(msg, "%s\n", reset);
                break;
        case DIFF_STATUS_RENAMED:
-               strbuf_addf(msg, "%ssimilarity index %d%%",
-                           set, similarity_index(p));
-               strbuf_addf(msg, "%s\n%srename from ", reset, set);
+               strbuf_addf(msg, "%s%ssimilarity index %d%%",
+                           line_prefix, set, similarity_index(p));
+               strbuf_addf(msg, "%s\n%s%srename from ",
+                           reset, line_prefix, set);
                quote_c_style(name, msg, NULL, 0);
-               strbuf_addf(msg, "%s\n%srename to ", reset, set);
+               strbuf_addf(msg, "%s\n%s%srename to ",
+                           reset, line_prefix, set);
                quote_c_style(other, msg, NULL, 0);
                strbuf_addf(msg, "%s\n", reset);
                break;
        case DIFF_STATUS_MODIFIED:
                if (p->score) {
-                       strbuf_addf(msg, "%sdissimilarity index %d%%%s\n",
+                       strbuf_addf(msg, "%s%sdissimilarity index %d%%%s\n",
+                                   line_prefix,
                                    set, similarity_index(p), reset);
                        break;
                }
@@ -2373,9 +2631,10 @@ static void fill_metainfo(struct strbuf *msg,
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
                                abbrev = 40;
                }
-               strbuf_addf(msg, "%sindex %.*s..%.*s", set,
-                           abbrev, sha1_to_hex(one->sha1),
-                           abbrev, sha1_to_hex(two->sha1));
+               strbuf_addf(msg, "%s%sindex %s..", set,
+                           line_prefix,
+                           find_unique_abbrev(one->sha1, abbrev));
+               strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev));
                if (one->mode == two->mode)
                        strbuf_addf(msg, " %06o", one->mode);
                strbuf_addf(msg, "%s\n", reset);
@@ -2560,6 +2819,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
 void diff_setup(struct diff_options *options)
 {
        memset(options, 0, sizeof(*options));
+       memset(&diff_queued_diff, 0, sizeof(diff_queued_diff));
 
        options->file = stdout;
 
@@ -2575,7 +2835,9 @@ void diff_setup(struct diff_options *options)
                DIFF_OPT_SET(options, COLOR_DIFF);
        options->detect_rename = diff_detect_rename_default;
 
-       if (!diff_mnemonic_prefix) {
+       if (diff_no_prefix) {
+               options->a_prefix = options->b_prefix = "";
+       } else if (!diff_mnemonic_prefix) {
                options->a_prefix = "a/";
                options->b_prefix = "b/";
        }
@@ -2738,7 +3000,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;
@@ -2866,13 +3128,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"))
@@ -3059,6 +3345,11 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt)
 {
        int line_termination = opt->line_termination;
        int inter_name_termination = line_termination ? '\t' : '\0';
+       if (opt->output_prefix) {
+               struct strbuf *msg = NULL;
+               msg = opt->output_prefix(opt, opt->output_prefix_data);
+               fprintf(opt->file, "%s", msg->buf);
+       }
 
        if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) {
                fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode,
@@ -3304,48 +3595,62 @@ static void show_file_mode_name(FILE *file, const char *newdelete, struct diff_f
 }
 
 
-static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name)
+static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name,
+               const char *line_prefix)
 {
        if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) {
-               fprintf(file, " mode change %06o => %06o%c", p->one->mode, p->two->mode,
-                       show_name ? ' ' : '\n');
+               fprintf(file, "%s mode change %06o => %06o%c", line_prefix, p->one->mode,
+                       p->two->mode, show_name ? ' ' : '\n');
                if (show_name) {
                        write_name_quoted(p->two->path, file, '\n');
                }
        }
 }
 
-static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p)
+static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p,
+                       const char *line_prefix)
 {
        char *names = pprint_rename(p->one->path, p->two->path);
 
        fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p));
        free(names);
-       show_mode_change(file, p, 0);
+       show_mode_change(file, p, 0, line_prefix);
 }
 
-static void diff_summary(FILE *file, struct diff_filepair *p)
+static void diff_summary(struct diff_options *opt, struct diff_filepair *p)
 {
+       FILE *file = opt->file;
+       char *line_prefix = "";
+
+       if (opt->output_prefix) {
+               struct strbuf *buf = opt->output_prefix(opt, opt->output_prefix_data);
+               line_prefix = buf->buf;
+       }
+
        switch(p->status) {
        case DIFF_STATUS_DELETED:
+               fputs(line_prefix, file);
                show_file_mode_name(file, "delete", p->one);
                break;
        case DIFF_STATUS_ADDED:
+               fputs(line_prefix, file);
                show_file_mode_name(file, "create", p->two);
                break;
        case DIFF_STATUS_COPIED:
-               show_rename_copy(file, "copy", p);
+               fputs(line_prefix, file);
+               show_rename_copy(file, "copy", p, line_prefix);
                break;
        case DIFF_STATUS_RENAMED:
-               show_rename_copy(file, "rename", p);
+               fputs(line_prefix, file);
+               show_rename_copy(file, "rename", p, line_prefix);
                break;
        default:
                if (p->score) {
-                       fputs(" rewrite ", file);
+                       fprintf(file, "%s rewrite ", line_prefix);
                        write_name_quoted(p->two->path, file, ' ');
                        fprintf(file, "(%d%%)\n", similarity_index(p));
                }
-               show_mode_change(file, p, !p->score);
+               show_mode_change(file, p, !p->score, line_prefix);
                break;
        }
 }
@@ -3477,8 +3782,7 @@ int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
                diff_free_filepair(q->queue[i]);
 
        free(q->queue);
-       q->queue = NULL;
-       q->nr = q->alloc = 0;
+       DIFF_QUEUE_CLEAR(q);
 
        return result;
 }
@@ -3555,8 +3859,9 @@ void diff_flush(struct diff_options *options)
                show_dirstat(options);
 
        if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
-               for (i = 0; i < q->nr; i++)
-                       diff_summary(options->file, q->queue[i]);
+               for (i = 0; i < q->nr; i++) {
+                       diff_summary(options, q->queue[i]);
+               }
                separator++;
        }
 
@@ -3606,8 +3911,7 @@ void diff_flush(struct diff_options *options)
                diff_free_filepair(q->queue[i]);
 free_queue:
        free(q->queue);
-       q->queue = NULL;
-       q->nr = q->alloc = 0;
+       DIFF_QUEUE_CLEAR(q);
        if (options->close_file)
                fclose(options->file);
 
@@ -3629,8 +3933,7 @@ static void diffcore_apply_filter(const char *filter)
        int i;
        struct diff_queue_struct *q = &diff_queued_diff;
        struct diff_queue_struct outq;
-       outq.queue = NULL;
-       outq.nr = outq.alloc = 0;
+       DIFF_QUEUE_CLEAR(&outq);
 
        if (!filter)
                return;
@@ -3698,8 +4001,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
        int i;
        struct diff_queue_struct *q = &diff_queued_diff;
        struct diff_queue_struct outq;
-       outq.queue = NULL;
-       outq.nr = outq.alloc = 0;
+       DIFF_QUEUE_CLEAR(&outq);
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
@@ -3760,6 +4062,12 @@ void diffcore_fix_diff_index(struct diff_options *options)
 
 void diffcore_std(struct diff_options *options)
 {
+       /* We never run this function more than one time, because the
+        * rename/copy detection logic can only run once.
+        */
+       if (diff_queued_diff.run)
+               return;
+
        if (options->skip_stat_unmatch)
                diffcore_skip_stat_unmatch(options);
        if (options->break_opt != -1)
@@ -3779,6 +4087,8 @@ void diffcore_std(struct diff_options *options)
                DIFF_OPT_SET(options, HAS_CHANGES);
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
+
+       diff_queued_diff.run = 1;
 }
 
 int diff_result_code(struct diff_options *opt, int status)
@@ -3932,3 +4242,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;
+}
diff --git a/diff.h b/diff.h
index 6a71013dc63fc0912fd4f3d27f70ae909917f1f6..48abe7a96a96df98ef91d5f5d7ba9f57cace777f 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -9,6 +9,7 @@
 struct rev_info;
 struct diff_options;
 struct diff_queue_struct;
+struct strbuf;
 
 typedef void (*change_fn_t)(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
@@ -25,6 +26,8 @@ typedef void (*add_remove_fn_t)(struct diff_options *options,
 typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
                struct diff_options *options, void *data);
 
+typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data);
+
 #define DIFF_FORMAT_RAW                0x0001
 #define DIFF_FORMAT_DIFFSTAT   0x0002
 #define DIFF_FORMAT_NUMSTAT    0x0004
@@ -54,7 +57,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 +82,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 +118,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;
@@ -122,6 +133,8 @@ struct diff_options {
        add_remove_fn_t add_remove;
        diff_format_fn_t format_callback;
        void *format_callback_data;
+       diff_prefix_fn_t output_prefix;
+       void *output_prefix_data;
 };
 
 enum color_diff {
@@ -133,7 +146,7 @@ enum color_diff {
        DIFF_FILE_NEW = 5,
        DIFF_COMMIT = 6,
        DIFF_WHITESPACE = 7,
-       DIFF_FUNCINFO = 8,
+       DIFF_FUNCINFO = 8
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
index 3a7b60a037b2e3c869afe76a23b671cfd5311338..44f8678d22ea466b0867591429bbcc3285cdaf91 100644 (file)
@@ -162,8 +162,7 @@ void diffcore_break(int break_score)
        if (!merge_score)
                merge_score = DEFAULT_MERGE_SCORE;
 
-       outq.nr = outq.alloc = 0;
-       outq.queue = NULL;
+       DIFF_QUEUE_CLEAR(&outq);
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
@@ -256,8 +255,7 @@ void diffcore_merge_broken(void)
        struct diff_queue_struct outq;
        int i, j;
 
-       outq.nr = outq.alloc = 0;
-       outq.queue = NULL;
+       DIFF_QUEUE_CLEAR(&outq);
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
index d0ef8397008824fb5139680856e3229ecf2c4eb1..929de15aa9228099b3a772be788d746e440a9e8c 100644 (file)
@@ -55,8 +55,7 @@ void diffcore_pickaxe(const char *needle, int opts)
        int i, has_changes;
        regex_t regex, *regexp = NULL;
        struct diff_queue_struct outq;
-       outq.queue = NULL;
-       outq.nr = outq.alloc = 0;
+       DIFF_QUEUE_CLEAR(&outq);
 
        if (opts & DIFF_PICKAXE_REGEX) {
                int err;
index d6fd3cacd6de4757994c61903dd07e0c4d74a9e9..df41be56deab60d4d39a45920a1e62b05d0474f6 100644 (file)
@@ -569,8 +569,7 @@ void diffcore_rename(struct diff_options *options)
        /* At this point, we have found some renames and copies and they
         * are recorded in rename_dst.  The original list is still in *q.
         */
-       outq.queue = NULL;
-       outq.nr = outq.alloc = 0;
+       DIFF_QUEUE_CLEAR(&outq);
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
                struct diff_filepair *pair_to_free = NULL;
index fcd00bf27aee4e1f2823f05ab3dba1d3c70a509d..491bea0b44963461cfce30b07ec96a9005a3c910 100644 (file)
@@ -91,7 +91,14 @@ struct diff_queue_struct {
        struct diff_filepair **queue;
        int alloc;
        int nr;
+       int run;
 };
+#define DIFF_QUEUE_CLEAR(q) \
+       do { \
+               (q)->queue = NULL; \
+               (q)->nr = (q)->alloc = 0; \
+               (q)->run = 0; \
+       } while(0);
 
 extern struct diff_queue_struct diff_queued_diff;
 extern struct diff_filepair *diff_queue(struct diff_queue_struct *,
diff --git a/dir.c b/dir.c
index 5615f33af187f381f8c2dfe7ab53910fe165fd59..5e36f8e616d63f8a9c5950897e0f06235d8e9acf 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -465,7 +465,7 @@ static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pat
 enum exist_status {
        index_nonexistent = 0,
        index_directory,
-       index_gitdir,
+       index_gitdir
 };
 
 /*
@@ -533,7 +533,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
 enum directory_treatment {
        show_directory,
        ignore_directory,
-       recurse_into_directory,
+       recurse_into_directory
 };
 
 static enum directory_treatment treat_directory(struct dir_struct *dir,
@@ -684,7 +684,7 @@ static int get_dtype(struct dirent *de, const char *path, int len)
 enum path_treatment {
        path_ignored,
        path_handled,
-       path_recurse,
+       path_recurse
 };
 
 static enum path_treatment treat_one_path(struct dir_struct *dir,
index 876c5e53410250b5f2744671e39907c234771af3..83d38d3c2354e8582d5af91c6d529a2f2836dc2c 100644 (file)
@@ -38,8 +38,9 @@ const char *pager_program;
 int pager_use_color = 1;
 const char *editor_program;
 const char *excludes_file;
-int auto_crlf = 0;     /* 1: both ways, -1: only when adding git objects */
+enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
 int read_replace_refs = 1;
+enum eol eol = EOL_UNSET;
 enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
 unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
 enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
index 309f2c58a2ba431a82a56f757482a5093cc8257d..1e5d66ed0ab3b605de670758887b13f146bdd8a5 100644 (file)
@@ -267,7 +267,7 @@ struct hash_list
 typedef enum {
        WHENSPEC_RAW = 1,
        WHENSPEC_RFC2822,
-       WHENSPEC_NOW,
+       WHENSPEC_NOW
 } whenspec_type;
 
 struct recent_command
@@ -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)
diff --git a/fsck.c b/fsck.c
index 89278c1459d36a3e2b718661ca71483522f587fd..3d05d4a794a4158a421ce1265422d241e28a5278 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -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,16 @@ 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 (!commit->tree)
                return error_func(&commit->object, FSCK_ERROR, "could not load commit's tree %s", sha1_to_hex(tree_sha1));
 
index 87ffae252b3f2ff88646d142ea9c2dfb38a28953..ef2d51a2b80d77dc5fe28d76b5047a97dc4c0fae 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -52,6 +52,16 @@ else
        HAS_HEAD=
 fi
 
+cmdline="git am"
+if test '' != "$interactive"
+then
+       cmdline="$cmdline -i"
+fi
+if test '' != "$threeway"
+then
+       cmdline="$cmdline -3"
+fi
+
 sq () {
        git rev-parse --sq-quote "$@"
 }
@@ -66,15 +76,6 @@ stop_here_user_resolve () {
            printf '%s\n' "$resolvemsg"
            stop_here $1
     fi
-    cmdline="git am"
-    if test '' != "$interactive"
-    then
-        cmdline="$cmdline -i"
-    fi
-    if test '' != "$threeway"
-    then
-        cmdline="$cmdline -3"
-    fi
     echo "When you have resolved this problem run \"$cmdline --resolved\"."
     echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
     echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
@@ -591,6 +592,8 @@ do
 
                test -s "$dotest/patch" || {
                        echo "Patch is empty.  Was it split wrong?"
+                       echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
+                       echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
                        stop_here $this
                }
                rm -f "$dotest/original-commit"
@@ -690,7 +693,13 @@ do
        else
            action=yes
        fi
-       FIRSTLINE=$(sed 1q "$dotest/final-commit")
+
+       if test -f "$dotest/final-commit"
+       then
+               FIRSTLINE=$(sed 1q "$dotest/final-commit")
+       else
+               FIRSTLINE=""
+       fi
 
        if test $action = skip
        then
@@ -726,6 +735,8 @@ do
                resolved=
                git diff-index --quiet --cached HEAD -- && {
                        echo "No changes - did you forget to use 'git add'?"
+                       echo "If there is nothing left to stage, chances are that something else"
+                       echo "already introduced the same changes; you might want to skip this patch."
                        stop_here_user_resolve $this
                }
                unmerged=$(git ls-files -u)
index c0198dde4c2c6a61718228ca2ade463c22e0bec3..02a73eeb667e798fca29de25fed6b0b2900f6912 100644 (file)
@@ -200,6 +200,7 @@ extern char *gitbasename(char *);
 #include "compat/bswap.h"
 
 /* General helper functions */
+extern void vreportf(const char *prefix, const char *err, va_list params);
 extern NORETURN void usage(const char *err);
 extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2)));
@@ -224,7 +225,6 @@ static inline const char *skip_prefix(const char *str, const char *prefix)
 #define PROT_READ 1
 #define PROT_WRITE 2
 #define MAP_PRIVATE 1
-#define MAP_FAILED ((void*)-1)
 #endif
 
 #define mmap git_mmap
@@ -253,6 +253,10 @@ extern int git_munmap(void *start, size_t length);
 
 #endif /* NO_MMAP */
 
+#ifndef MAP_FAILED
+#define MAP_FAILED ((void *)-1)
+#endif
+
 #ifdef NO_ST_BLOCKS_IN_STRUCT_STAT
 #define on_disk_bytes(st) ((st).st_size)
 #else
@@ -363,7 +367,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));
+typedef void (*try_to_free_t)(size_t);
+extern try_to_free_t set_try_to_free_routine(try_to_free_t);
 
 extern char *xstrdup(const char *str);
 extern void *xmalloc(size_t size);
index 13751db882dd2c40e2c78503eba0d3f941297644..e9f3037df351ceed0262a8995f19903464163af8 100755 (executable)
@@ -183,12 +183,58 @@ if ($state->{method} eq 'pserver') {
        exit 1;
     }
     $line = <STDIN>; chomp $line;
-    unless ($line eq 'anonymous') {
-       print "E Only anonymous user allowed via pserver\n";
-       print "I HATE YOU\n";
-       exit 1;
+    my $user = $line;
+    $line = <STDIN>; chomp $line;
+    my $password = $line;
+
+    if ($user eq 'anonymous') {
+        # "A" will be 1 byte, use length instead in case the
+        # encryption method ever changes (yeah, right!)
+        if (length($password) > 1 ) {
+            print "E Don't supply a password for the `anonymous' user\n";
+            print "I HATE YOU\n";
+            exit 1;
+        }
+
+        # Fall through to LOVE
+    } else {
+        # Trying to authenticate a user
+        if (not exists $cfg->{gitcvs}->{authdb}) {
+            print "E the repo config file needs a [gitcvs] section with an 'authdb' parameter set to the filename of the authentication database\n";
+            print "I HATE YOU\n";
+            exit 1;
+        }
+
+        my $authdb = $cfg->{gitcvs}->{authdb};
+
+        unless (-e $authdb) {
+            print "E The authentication database specified in [gitcvs.authdb] does not exist\n";
+            print "I HATE YOU\n";
+            exit 1;
+        }
+
+        my $auth_ok;
+        open my $passwd, "<", $authdb or die $!;
+        while (<$passwd>) {
+            if (m{^\Q$user\E:(.*)}) {
+                if (crypt($user, descramble($password)) eq $1) {
+                    $auth_ok = 1;
+                }
+            };
+        }
+        close $passwd;
+
+        unless ($auth_ok) {
+            print "I HATE YOU\n";
+            exit 1;
+        }
+
+        # Fall through to LOVE
     }
-    $line = <STDIN>; chomp $line;    # validate the password?
+
+    # For checking whether the user is anonymous on commit
+    $state->{user} = $user;
+
     $line = <STDIN>; chomp $line;
     unless ($line eq "END $request REQUEST") {
        die "E Do not understand $line -- expecting END $request REQUEST\n";
@@ -1271,9 +1317,9 @@ sub req_ci
 
     $log->info("req_ci : " . ( defined($data) ? $data : "[NULL]" ));
 
-    if ( $state->{method} eq 'pserver')
+    if ( $state->{method} eq 'pserver' and $state->{user} eq 'anonymous' )
     {
-        print "error 1 pserver access cannot commit\n";
+        print "error 1 anonymous user cannot commit via pserver\n";
         cleanupWorkTree();
         exit;
     }
@@ -2369,15 +2415,20 @@ sub kopts_from_path
     if ( defined ( $cfg->{gitcvs}{usecrlfattr} ) and
          $cfg->{gitcvs}{usecrlfattr} =~ /\s*(1|true|yes)\s*$/i )
     {
-        my ($val) = check_attr( "crlf", $path );
-        if ( $val eq "set" )
+        my ($val) = check_attr( "text", $path );
+        if ( $val eq "unspecified" )
         {
-            return "";
+            $val = check_attr( "crlf", $path );
         }
-        elsif ( $val eq "unset" )
+        if ( $val eq "unset" )
         {
             return "-kb"
         }
+        elsif ( check_attr( "eol", $path ) ne "unspecified" ||
+                $val eq "set" || $val eq "input" )
+        {
+            return "";
+        }
         else
         {
             $log->info("Unrecognized check_attr crlf $path : $val");
@@ -2586,6 +2637,43 @@ sub cvs_author
     $author;
 }
 
+
+sub descramble
+{
+    # This table is from src/scramble.c in the CVS source
+    my @SHIFTS = (
+        0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
+        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+        114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
+        111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
+        41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
+        125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
+        36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
+        58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
+        225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
+        199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
+        174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
+        207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
+        192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
+        227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
+        182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
+        243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152
+    );
+    my ($str) = @_;
+
+    # This should never happen, the same password format (A) has been
+    # used by CVS since the beginning of time
+    {
+        my $fmt = substr($str, 0, 1);
+        die "invalid password format `$fmt'" unless $fmt eq 'A';
+    }
+
+    my @str = unpack "C*", substr($str, 1);
+    my $ret = join '', map { chr $SHIFTS[$_] } @str;
+    return $ret;
+}
+
+
 package GITCVS::log;
 
 ####
index f6080149c22bad8b3434b874b9e4079ac508ca69..6635fbefdf2a06e556716f91a70181ecf4f3252a 100755 (executable)
@@ -24,6 +24,7 @@ restart        restart the web server
 fqgitdir="$GIT_DIR"
 local="$(git config --bool --get instaweb.local)"
 httpd="$(git config --get instaweb.httpd)"
+root="$(git config --get instaweb.gitwebdir)"
 port=$(git config --get instaweb.port)
 module_path="$(git config --get instaweb.modulepath)"
 
@@ -34,6 +35,9 @@ conf="$GIT_DIR/gitweb/httpd.conf"
 # if installed, it doesn't need further configuration (module_path)
 test -z "$httpd" && httpd='lighttpd -f'
 
+# Default is @@GITWEBDIR@@
+test -z "$root" && root='@@GITWEBDIR@@'
+
 # any untaken local port will do...
 test -z "$port" && port=1234
 
@@ -46,6 +50,12 @@ resolve_full_httpd () {
                        httpd="$httpd -f"
                fi
                ;;
+       *plackup*)
+               # server is started by running via generated gitweb.psgi in $fqgitdir/gitweb
+               full_httpd="$fqgitdir/gitweb/gitweb.psgi"
+               httpd_only="${httpd%% *}" # cut on first space
+               return
+               ;;
        esac
 
        httpd_only="$(echo $httpd | cut -f1 -d' ')"
@@ -57,7 +67,7 @@ resolve_full_httpd () {
                # these days and those are not in most users $PATHs
                # in addition, we may have generated a server script
                # in $fqgitdir/gitweb.
-               for i in /usr/local/sbin /usr/sbin "$fqgitdir/gitweb"
+               for i in /usr/local/sbin /usr/sbin "$root" "$fqgitdir/gitweb"
                do
                        if test -x "$i/$httpd_only"
                        then
@@ -83,8 +93,8 @@ start_httpd () {
 
        # don't quote $full_httpd, there can be arguments to it (-f)
        case "$httpd" in
-       *mongoose*)
-               #The mongoose server doesn't have a daemon mode so we'll have to fork it
+       *mongoose*|*plackup*)
+               #These servers don't have a daemon mode so we'll have to fork it
                $full_httpd "$fqgitdir/gitweb/httpd.conf" &
                #Save the pid before doing anything else (we'll print it later)
                pid=$!
@@ -110,6 +120,20 @@ EOF
 
 stop_httpd () {
        test -f "$fqgitdir/pid" && kill $(cat "$fqgitdir/pid")
+       rm -f "$fqgitdir/pid"
+}
+
+httpd_is_ready () {
+       "$PERL" -MIO::Socket::INET -e "
+local \$| = 1; # turn on autoflush
+exit if (IO::Socket::INET->new('127.0.0.1:$port'));
+print 'Waiting for \'$httpd\' to start ..';
+do {
+       print '.';
+       sleep(1);
+} until (IO::Socket::INET->new('127.0.0.1:$port'));
+print qq! (done)\n!;
+"
 }
 
 while test $# != 0
@@ -159,8 +183,8 @@ done
 mkdir -p "$GIT_DIR/gitweb/tmp"
 GIT_EXEC_PATH="$(git --exec-path)"
 GIT_DIR="$fqgitdir"
-export GIT_EXEC_PATH GIT_DIR
-
+GITWEB_CONFIG="$fqgitdir/gitweb/gitweb_config.perl"
+export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG
 
 webrick_conf () {
        # generate a standalone server script in $fqgitdir/gitweb.
@@ -192,7 +216,7 @@ EOF
 
        cat >"$conf" <<EOF
 :Port: $port
-:DocumentRoot: "$fqgitdir/gitweb"
+:DocumentRoot: "$root"
 :DirectoryIndex: ["gitweb.cgi"]
 :PidFile: "$fqgitdir/pid"
 EOF
@@ -201,18 +225,18 @@ EOF
 
 lighttpd_conf () {
        cat > "$conf" <<EOF
-server.document-root = "$fqgitdir/gitweb"
+server.document-root = "$root"
 server.port = $port
 server.modules = ( "mod_setenv", "mod_cgi" )
 server.indexfiles = ( "gitweb.cgi" )
 server.pid-file = "$fqgitdir/pid"
-server.errorlog = "$fqgitdir/gitweb/error.log"
+server.errorlog = "$fqgitdir/gitweb/$httpd_only/error.log"
 
 # to enable, add "mod_access", "mod_accesslog" to server.modules
 # variable above and uncomment this
-#accesslog.filename = "$fqgitdir/gitweb/access.log"
+#accesslog.filename = "$fqgitdir/gitweb/$httpd_only/access.log"
 
-setenv.add-environment = ( "PATH" => env.PATH )
+setenv.add-environment = ( "PATH" => env.PATH, "GITWEB_CONFIG" => env.GITWEB_CONFIG )
 
 cgi.assign = ( ".cgi" => "" )
 
@@ -277,14 +301,15 @@ EOF
 
 apache2_conf () {
        test -z "$module_path" && module_path=/usr/lib/apache2/modules
-       mkdir -p "$GIT_DIR/gitweb/logs"
        bind=
        test x"$local" = xtrue && bind='127.0.0.1:'
        echo 'text/css css' > "$fqgitdir/mime.types"
        cat > "$conf" <<EOF
 ServerName "git-instaweb"
-ServerRoot "$fqgitdir/gitweb"
-DocumentRoot "$fqgitdir/gitweb"
+ServerRoot "$root"
+DocumentRoot "$root"
+ErrorLog "$fqgitdir/gitweb/$httpd_only/error.log"
+CustomLog "$fqgitdir/gitweb/$httpd_only/access.log" combined
 PidFile "$fqgitdir/pid"
 Listen $bind$port
 EOF
@@ -303,13 +328,14 @@ EOF
        # check to see if Dennis Stosberg's mod_perl compatibility patch
        # (<20060621130708.Gcbc6e5c@leonov.stosberg.net>) has been applied
        if test -f "$module_path/mod_perl.so" &&
-          sane_grep 'MOD_PERL' "$GIT_DIR/gitweb/gitweb.cgi" >/dev/null
+          sane_grep 'MOD_PERL' "$root/gitweb.cgi" >/dev/null
        then
                # favor mod_perl if available
                cat >> "$conf" <<EOF
 LoadModule perl_module $module_path/mod_perl.so
 PerlPassEnv GIT_DIR
 PerlPassEnv GIT_EXEC_DIR
+PerlPassEnv GITWEB_CONFIG
 <Location /gitweb.cgi>
        SetHandler perl-script
        PerlResponseHandler ModPerl::Registry
@@ -353,15 +379,15 @@ mongoose_conf() {
 # For detailed description of every option, visit
 # http://code.google.com/p/mongoose/wiki/MongooseManual
 
-root           $fqgitdir/gitweb
+root           $root
 ports          $port
 index_files    gitweb.cgi
 #ssl_cert      $fqgitdir/gitweb/ssl_cert.pem
-error_log      $fqgitdir/gitweb/error.log
-access_log     $fqgitdir/gitweb/access.log
+error_log      $fqgitdir/gitweb/$httpd_only/error.log
+access_log     $fqgitdir/gitweb/$httpd_only/access.log
 
 #cgi setup
-cgi_env                PATH=$PATH,GIT_DIR=$GIT_DIR,GIT_EXEC_PATH=$GIT_EXEC_PATH
+cgi_env                PATH=$PATH,GIT_DIR=$GIT_DIR,GIT_EXEC_PATH=$GIT_EXEC_PATH,GITWEB_CONFIG=$GITWEB_CONFIG
 cgi_interp     $PERL
 cgi_ext                cgi,pl
 
@@ -370,41 +396,165 @@ mime_types       .gz=application/x-gzip,.tar.gz=application/x-tgz,.tgz=application/x-t
 EOF
 }
 
-
-script='
-s#^(my|our) \$projectroot =.*#$1 \$projectroot = "'$(dirname "$fqgitdir")'";#;
-s#(my|our) \$gitbin =.*#$1 \$gitbin = "'$GIT_EXEC_PATH'";#;
-s#(my|our) \$projects_list =.*#$1 \$projects_list = \$projectroot;#;
-s#(my|our) \$git_temp =.*#$1 \$git_temp = "'$fqgitdir/gitweb/tmp'";#;'
-
-gitweb_cgi () {
-       cat > "$1.tmp" <<\EOFGITWEB
-@@GITWEB_CGI@@
-EOFGITWEB
-       # Use the configured full path to perl to match the generated
-       # scripts' 'hashpling' line
-       "$PERL" -p -e "$script" "$1.tmp"  > "$1"
-       chmod +x "$1"
-       rm -f "$1.tmp"
+plackup_conf () {
+       # generate a standalone 'plackup' server script in $fqgitdir/gitweb
+       # with embedded configuration; it does not use "$conf" file
+       cat > "$fqgitdir/gitweb/gitweb.psgi" <<EOF
+#!$PERL
+
+# gitweb - simple web interface to track changes in git repositories
+#          PSGI wrapper and server starter (see http://plackperl.org)
+
+use strict;
+
+use IO::Handle;
+use Plack::MIME;
+use Plack::Builder;
+use Plack::App::WrapCGI;
+use CGI::Emulate::PSGI 0.07; # minimum version required to work with gitweb
+
+# mimetype mapping (from lighttpd_conf)
+Plack::MIME->add_type(
+       ".pdf"          =>      "application/pdf",
+       ".sig"          =>      "application/pgp-signature",
+       ".spl"          =>      "application/futuresplash",
+       ".class"        =>      "application/octet-stream",
+       ".ps"           =>      "application/postscript",
+       ".torrent"      =>      "application/x-bittorrent",
+       ".dvi"          =>      "application/x-dvi",
+       ".gz"           =>      "application/x-gzip",
+       ".pac"          =>      "application/x-ns-proxy-autoconfig",
+       ".swf"          =>      "application/x-shockwave-flash",
+       ".tar.gz"       =>      "application/x-tgz",
+       ".tgz"          =>      "application/x-tgz",
+       ".tar"          =>      "application/x-tar",
+       ".zip"          =>      "application/zip",
+       ".mp3"          =>      "audio/mpeg",
+       ".m3u"          =>      "audio/x-mpegurl",
+       ".wma"          =>      "audio/x-ms-wma",
+       ".wax"          =>      "audio/x-ms-wax",
+       ".ogg"          =>      "application/ogg",
+       ".wav"          =>      "audio/x-wav",
+       ".gif"          =>      "image/gif",
+       ".jpg"          =>      "image/jpeg",
+       ".jpeg"         =>      "image/jpeg",
+       ".png"          =>      "image/png",
+       ".xbm"          =>      "image/x-xbitmap",
+       ".xpm"          =>      "image/x-xpixmap",
+       ".xwd"          =>      "image/x-xwindowdump",
+       ".css"          =>      "text/css",
+       ".html"         =>      "text/html",
+       ".htm"          =>      "text/html",
+       ".js"           =>      "text/javascript",
+       ".asc"          =>      "text/plain",
+       ".c"            =>      "text/plain",
+       ".cpp"          =>      "text/plain",
+       ".log"          =>      "text/plain",
+       ".conf"         =>      "text/plain",
+       ".text"         =>      "text/plain",
+       ".txt"          =>      "text/plain",
+       ".dtd"          =>      "text/xml",
+       ".xml"          =>      "text/xml",
+       ".mpeg"         =>      "video/mpeg",
+       ".mpg"          =>      "video/mpeg",
+       ".mov"          =>      "video/quicktime",
+       ".qt"           =>      "video/quicktime",
+       ".avi"          =>      "video/x-msvideo",
+       ".asf"          =>      "video/x-ms-asf",
+       ".asx"          =>      "video/x-ms-asf",
+       ".wmv"          =>      "video/x-ms-wmv",
+       ".bz2"          =>      "application/x-bzip",
+       ".tbz"          =>      "application/x-bzip-compressed-tar",
+       ".tar.bz2"      =>      "application/x-bzip-compressed-tar",
+       ""              =>      "text/plain"
+);
+
+my \$app = builder {
+       # to be able to override \$SIG{__WARN__} to log build time warnings
+       use CGI::Carp; # it sets \$SIG{__WARN__} itself
+
+       my \$logdir = "$fqgitdir/gitweb/$httpd_only";
+       open my \$access_log_fh, '>>', "\$logdir/access.log"
+               or die "Couldn't open access log '\$logdir/access.log': \$!";
+       open my \$error_log_fh,  '>>', "\$logdir/error.log"
+               or die "Couldn't open error log '\$logdir/error.log': \$!";
+
+       \$access_log_fh->autoflush(1);
+       \$error_log_fh->autoflush(1);
+
+       # redirect build time warnings to error.log
+       \$SIG{'__WARN__'} = sub {
+               my \$msg = shift;
+               # timestamp warning like in CGI::Carp::warn
+               my \$stamp = CGI::Carp::stamp();
+               \$msg =~ s/^/\$stamp/gm;
+               print \$error_log_fh \$msg;
+       };
+
+       # write errors to error.log, access to access.log
+       enable 'AccessLog',
+               format => "combined",
+               logger => sub { print \$access_log_fh @_; };
+       enable sub {
+               my \$app = shift;
+               sub {
+                       my \$env = shift;
+                       \$env->{'psgi.errors'} = \$error_log_fh;
+                       \$app->(\$env);
+               }
+       };
+       # gitweb currently doesn't work with $SIG{CHLD} set to 'IGNORE',
+       # because it uses 'close $fd or die...' on piped filehandle $fh
+       # (which causes the parent process to wait for child to finish).
+       enable_if { \$SIG{'CHLD'} eq 'IGNORE' } sub {
+               my \$app = shift;
+               sub {
+                       my \$env = shift;
+                       local \$SIG{'CHLD'} = 'DEFAULT';
+                       local \$SIG{'CLD'}  = 'DEFAULT';
+                       \$app->(\$env);
+               }
+       };
+       # serve static files, i.e. stylesheet, images, script
+       enable 'Static',
+               path => sub { m!\.(js|css|png)\$! && s!^/gitweb/!! },
+               root => "$root/",
+               encoding => 'utf-8'; # encoding for 'text/plain' files
+       # convert CGI application to PSGI app
+       Plack::App::WrapCGI->new(script => "$root/gitweb.cgi")->to_app;
+};
+
+# make it runnable as standalone app,
+# like it would be run via 'plackup' utility
+if (__FILE__ eq \$0) {
+       require Plack::Runner;
+
+       my \$runner = Plack::Runner->new();
+       \$runner->parse_options(qw(--env deployment --port $port),
+                              "$local" ? qw(--host 127.0.0.1) : ());
+       \$runner->run(\$app);
 }
+__END__
+EOF
 
-gitweb_css () {
-       cat > "$1" <<\EOFGITWEB
-@@GITWEB_CSS@@
-
-EOFGITWEB
+       chmod a+x "$fqgitdir/gitweb/gitweb.psgi"
+       # configuration is embedded in server script file, gitweb.psgi
+       rm -f "$conf"
 }
 
-gitweb_js () {
-       cat > "$1" <<\EOFGITWEB
-@@GITWEB_JS@@
-
-EOFGITWEB
+gitweb_conf() {
+       cat > "$fqgitdir/gitweb/gitweb_config.perl" <<EOF
+#!/usr/bin/perl
+our \$projectroot = "$(dirname "$fqgitdir")";
+our \$git_temp = "$fqgitdir/gitweb/tmp";
+our \$projects_list = \$projectroot;
+EOF
 }
 
-gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi"
-gitweb_css "$GIT_DIR/@@GITWEB_CSS_NAME@@"
-gitweb_js  "$GIT_DIR/@@GITWEB_JS_NAME@@"
+gitweb_conf
+
+resolve_full_httpd
+mkdir -p "$fqgitdir/gitweb/$httpd_only"
 
 case "$httpd" in
 *lighttpd*)
@@ -419,6 +569,9 @@ webrick)
 *mongoose*)
        mongoose_conf
        ;;
+*plackup*)
+       plackup_conf
+       ;;
 *)
        echo "Unknown httpd specified: $httpd"
        exit 1
@@ -429,7 +582,7 @@ start_httpd
 url=http://127.0.0.1:$port
 
 if test -n "$browser"; then
-       git web--browse -b "$browser" $url || echo $url
+       httpd_is_ready && git web--browse -b "$browser" $url || echo $url
 else
-       git web--browse -c "instaweb.browser" $url || echo $url
+       httpd_is_ready && git web--browse -c "instaweb.browser" $url || echo $url
 fi
index d067894bf45fd0a50513e196ea2a5e671d901681..b86402afa5d079df8d1cf6eebb15e5727cc8a5de 100755 (executable)
@@ -107,7 +107,7 @@ case "${1:-.}${2:-.}${3:-.}" in
                # remove lines that are unique to ours.
                orig=`git-unpack-file $2`
                sz0=`wc -c <"$orig"`
-               diff -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add
+               @@DIFF@@ -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add
                sz1=`wc -c <"$orig"`
 
                # If we do not have enough common material, it is not
index 44f5c65fdb5e81c4b660a666c2edd71aa75c5c1a..ab4afa7dee377707484e7f76c7d44ac955f09397 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
+USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] (<upstream>|--root) [<branch>] [--quiet | -q]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -198,14 +198,6 @@ test -f "$GIT_DIR"/rebase-apply/applying &&
 
 is_interactive "$@" && exec git-rebase--interactive "$@"
 
-if test $# -eq 0
-then
-       test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || usage
-       test -d "$dotest" -o -f "$GIT_DIR"/rebase-apply/rebasing &&
-               die 'A rebase is in progress, try --continue, --skip or --abort.'
-       die "No arguments given and $GIT_DIR/rebase-apply already exists."
-fi
-
 while test $# != 0
 do
        case "$1" in
@@ -370,6 +362,13 @@ do
 done
 test $# -gt 2 && usage
 
+if test $# -eq 0 && test -z "$rebase_root"
+then
+       test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || usage
+       test -d "$dotest" -o -f "$GIT_DIR"/rebase-apply/rebasing &&
+               die 'A rebase is in progress, try --continue, --skip or --abort.'
+fi
+
 # Make sure we do not have $GIT_DIR/rebase-apply
 if test -z "$do_merge"
 then
diff --git a/git-remote-testgit.py b/git-remote-testgit.py
new file mode 100644 (file)
index 0000000..df9d512
--- /dev/null
@@ -0,0 +1,244 @@
+#!/usr/bin/env python
+
+# hashlib is only available in python >= 2.5
+try:
+    import hashlib
+    _digest = hashlib.sha1
+except ImportError:
+    import sha
+    _digest = sha.new
+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 = _digest()
+    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
+    if os.path.exists(path):
+        print path
+    else:
+        print ""
+    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))
index 8fd15f6df4926a89812437e04735bd924ee7088b..6fdea397ddec3cfa4ff37cdf672e7cced840bb60 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/sh -e
+#!/bin/sh
 # Copyright 2005, Ryan Anderson <ryan@michonline.com>
 #
 # This file is licensed under the GPL v2, or a later version
@@ -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
@@ -69,10 +70,10 @@ git show -s --format='The following changes since commit %H:
 
   %s (%ci)
 
-are available in the git repository at:' $baserev
-echo "  $url $branch"
-echo
+are available in the git repository at:' $baserev &&
+echo "  $url $branch" &&
+echo &&
 
-git shortlog ^$baserev $headrev
-git diff -M --stat --summary $patch $merge_base..$headrev
+git shortlog ^$baserev $headrev &&
+git diff -M --stat --summary $patch $merge_base..$headrev || exit
 exit $status
index 0f858d334c756f9cd4028b4950a3912d0f6182dd..1d95447d03f342811dd77386f2e83d0814beabf2 100755 (executable)
@@ -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
index 3319b836b217a26b4ac55ae5ced328ffafa0a015..8c562a72e6e95fce60b78c015623212b7fce1dab 100755 (executable)
@@ -271,6 +271,8 @@ cmd_foreach()
                shift
        done
 
+       toplevel=$(pwd)
+
        module_list |
        while read mode sha1 stage path
        do
@@ -650,7 +652,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)))"
                        ;;
index 2c86ea2e384e4b3ecf9f6c2a856c52a0203ce489..19d6848d0e7754b9afb9fdbbac6fce31286468a5 100755 (executable)
@@ -963,6 +963,7 @@ sub cmd_multi_init {
        }
        do_git_init_db();
        if (defined $_trunk) {
+               $_trunk =~ s#^/+##;
                my $trunk_ref = 'refs/remotes/' . $_prefix . 'trunk';
                # try both old-style and new-style lookups:
                my $gs_trunk = eval { Git::SVN->new($trunk_ref) };
@@ -1185,6 +1186,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";
 }
@@ -2053,6 +2055,9 @@ sub new {
                         "\":$ref_id\$\" in config\n";
                ($self->{path}, undef) = split(/\s*:\s*/, $fetch);
        }
+       $self->{path} =~ s{/+}{/}g;
+       $self->{path} =~ s{\A/}{};
+       $self->{path} =~ s{/\z}{};
        $self->{url} = command_oneline('config', '--get',
                                       "svn-remote.$repo_id.url") or
                   die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
@@ -2086,6 +2091,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;
 }
 
@@ -2827,8 +2840,9 @@ sub mkemptydirs {
        foreach my $d (sort keys %empty_dirs) {
                $d = uri_decode($d);
                $d =~ s/$strip//;
+               next unless length($d);
                next if -d $d;
-               if (-e _) {
+               if (-e $d) {
                        warn "$d exists but is not a directory\n";
                } else {
                        print "creating empty directory: $d\n";
@@ -3605,6 +3619,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 +4013,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/;
 
index a578c3a73203fbf1bf4abfb024b1e83c45f2b2ce..dbded76aaf5499dd116e5ad22f95d4518b79c0d8 100755 (executable)
@@ -31,7 +31,7 @@ valid_custom_tool()
 
 valid_tool() {
        case "$1" in
-               firefox | iceweasel | konqueror | w3m | links | lynx | dillo | open | start)
+               firefox | iceweasel | chrome | chromium | konqueror | w3m | links | lynx | dillo | open | start)
                        ;; # happy
                *)
                        valid_custom_tool "$1" || return 1
@@ -103,7 +103,7 @@ fi
 
 if test -z "$browser" ; then
     if test -n "$DISPLAY"; then
-       browser_candidates="firefox iceweasel konqueror w3m links lynx dillo"
+       browser_candidates="firefox iceweasel chrome chromium konqueror w3m links lynx dillo"
        if test "$KDE_FULL_SESSION" = "true"; then
            browser_candidates="konqueror $browser_candidates"
        fi
@@ -146,6 +146,11 @@ case "$browser" in
        test "$vers" -lt 2 && NEWTAB=''
        "$browser_path" $NEWTAB "$@" &
        ;;
+    chrome|chromium)
+       # Actual command for chromium is chromium-browser.
+       # No need to specify newTab. It's default in chromium
+       eval "$browser_path" "$@" &
+       ;;
     konqueror)
        case "$(basename "$browser_path")" in
            konqueror)
diff --git a/git.c b/git.c
index 6bae30545b85f19eb51c4b055f303f70909f0cf2..99f036302a7e6d884369d1d3f4ce428e437cbccd 100644 (file)
--- a/git.c
+++ b/git.c
@@ -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 (file)
index 0000000..f40f9d6
--- /dev/null
@@ -0,0 +1,53 @@
+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"]
+
+        child = subprocess.Popen(args, stdin=p1.stdout)
+        if child.wait() != 0:
+            raise CalledProcessError
diff --git a/git_remote_helpers/git/importer.py b/git_remote_helpers/git/importer.py
new file mode 100644 (file)
index 0000000..70a7127
--- /dev/null
@@ -0,0 +1,40 @@
+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)
+
+        child = subprocess.Popen(args)
+        if child.wait() != 0:
+            raise CalledProcessError
diff --git a/git_remote_helpers/git/non_local.py b/git_remote_helpers/git/non_local.py
new file mode 100644 (file)
index 0000000..f27389b
--- /dev/null
@@ -0,0 +1,69 @@
+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]
+
+        child = subprocess.Popen(args)
+        if child.wait() != 0:
+            raise CalledProcessError
+
+        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]
+        child = subprocess.Popen(args)
+        if child.wait() != 0:
+            raise CalledProcessError
+
+        args = ["git", "--git-dir=" + path, "update-ref", "refs/heads/master", "FETCH_HEAD"]
+        child = subprocess.Popen(args)
+        if child.wait() != 0:
+            raise CalledProcessError
+
+    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]
+        child = subprocess.Popen(args)
+        if child.wait() != 0:
+            raise CalledProcessError
diff --git a/git_remote_helpers/git/repo.py b/git_remote_helpers/git/repo.py
new file mode 100644 (file)
index 0000000..58e1cdb
--- /dev/null
@@ -0,0 +1,75 @@
+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"]
+
+    for prefix in prefixes:
+        if url.startswith(prefix):
+            return True
+    return False
+
+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")
+
+        child = subprocess.Popen(args, stdout=ofile)
+        if child.wait() != 0:
+            raise CalledProcessError
+        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, ' ')
index cbdc1364700d79b9d8b41850790baee103f31ca1..823053173c92e4097dad54945db16f42b4b5d994 100644 (file)
@@ -2,12 +2,13 @@ GIT web Interface (gitweb) Installation
 =======================================
 
 First you have to generate gitweb.cgi from gitweb.perl using
-"make gitweb", then copy appropriate files (gitweb.cgi, gitweb.js,
-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 gitweb", then "make install-gitweb" appropriate files
+(gitweb.cgi, gitweb.js, gitweb.css, git-logo.png and git-favicon.png)
+to their destination. For example if git was (or is) installed with
+/usr prefix and gitwebdir is /var/www/cgi-bin, 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 +17,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,21 +76,20 @@ 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" \
-            GITWEB_CSS="/gitweb/gitweb.css" \
-            GITWEB_LOGO="/gitweb/git-logo.png" \
-            GITWEB_FAVICON="/gitweb/git-favicon.png" \
+            GITWEB_JS="gitweb/static/gitweb.js" \
+            GITWEB_CSS="gitweb/static/gitweb.css" \
+            GITWEB_LOGO="gitweb/static/git-logo.png" \
+            GITWEB_FAVICON="gitweb/static/git-favicon.png" \
             bindir=/usr/local/bin \
             gitweb
 
-       cp -fv ~/git/gitweb/gitweb.{cgi,js,css} \
-              ~/git/gitweb/git-{favicon,logo}.png \
-            /var/www/cgi-bin/gitweb/
+       make gitwebdir=/var/www/cgi-bin/gitweb install-gitweb
 
 
 Gitweb config file
index e7dd2527736ddc6e47f864916115f9cc5c9e7dd9..2fb7c2d77bbd5f2041341822859dce51ae504d83 100644 (file)
@@ -4,15 +4,18 @@ all::
 # Define V=1 to have a more verbose compile.
 #
 # Define JSMIN to point to JavaScript minifier that functions as
-# a filter to have gitweb.js minified.
+# a filter to have static/gitweb.js minified.
 #
 # Define CSSMIN to point to a CSS minifier in order to generate a minified
-# version of gitweb.css
+# version of static/gitweb.css
 #
 
 prefix ?= $(HOME)
 bindir ?= $(prefix)/bin
+gitwebdir ?= /var/www/cgi-bin
+
 RM ?= rm -f
+INSTALL ?= install
 
 # default configuration for gitweb
 GITWEB_CONFIG = gitweb_config.perl
@@ -26,10 +29,10 @@ GITWEB_STRICT_EXPORT =
 GITWEB_BASE_URL =
 GITWEB_LIST =
 GITWEB_HOMETEXT = indextext.html
-GITWEB_CSS = gitweb.css
-GITWEB_LOGO = git-logo.png
-GITWEB_FAVICON = git-favicon.png
-GITWEB_JS = gitweb.js
+GITWEB_CSS = static/gitweb.css
+GITWEB_LOGO = static/git-logo.png
+GITWEB_FAVICON = static/git-favicon.png
+GITWEB_JS = static/gitweb.js
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 
@@ -49,9 +52,12 @@ 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))#'
+gitwebstaticdir_SQ = $(subst ','\'',$(gitwebdir)/static)#'
+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 +86,30 @@ endif
 
 all:: gitweb.cgi
 
+GITWEB_PROGRAMS = gitweb.cgi
+
 ifdef JSMIN
-GITWEB_JS = gitweb.min.js
-all:: gitweb.min.js
-gitweb.min.js: gitweb.js GITWEB-BUILD-OPTIONS
+GITWEB_FILES += static/gitweb.min.js
+GITWEB_JS = static/gitweb.min.js
+all:: static/gitweb.min.js
+static/gitweb.min.js: static/gitweb.js GITWEB-BUILD-OPTIONS
        $(QUIET_GEN)$(JSMIN) <$< >$@
+else
+GITWEB_FILES += static/gitweb.js
 endif
 
 ifdef CSSMIN
-GITWEB_CSS = gitweb.min.css
-all:: gitweb.min.css
-gitweb.min.css: gitweb.css GITWEB-BUILD-OPTIONS
+GITWEB_FILES += static/gitweb.min.css
+GITWEB_CSS = static/gitweb.min.css
+all:: static/gitweb.min.css
+static/gitweb.min.css: static/gitweb.css GITWEB-BUILD-OPTIONS
        $(QUIET_GEN)$(CSSMIN) <$< >$@
+else
+GITWEB_FILES += static/gitweb.css
 endif
 
+GITWEB_FILES += static/git-logo.png static/git-favicon.png
+
 GITWEB_REPLACE = \
        -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
        -e 's|++GIT_BINDIR++|$(bindir)|g' \
@@ -127,8 +143,18 @@ 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) -d -m 755 '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)'
+       $(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)'
+
+### Cleaning rules
+
 clean:
-       $(RM) gitweb.cgi gitweb.min.js gitweb.min.css GITWEB-BUILD-OPTIONS
+       $(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS
 
-.PHONY: all clean .FORCE-GIT-VERSION-FILE FORCE
+.PHONY: all clean install .FORCE-GIT-VERSION-FILE FORCE
 
index 71742b335dd786a24699b06b17519e264724ea5b..0e19be8d216aa813221c8663d6424f96f80bfb14 100644 (file)
@@ -80,24 +80,26 @@ You can specify the following configuration variables when building GIT:
    Points to the location where you put gitweb.css on your web server
    (or to be more generic, the URI of gitweb stylesheet).  Relative to the
    base URI of gitweb.  Note that you can setup multiple stylesheets from
-   the gitweb config file.  [Default: gitweb.css (or gitweb.min.css if the
-   CSSMIN variable is defined / CSS minifier is used)]
+   the gitweb config file.  [Default: static/gitweb.css (or
+   static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier
+   is used)]
  * GITWEB_LOGO
    Points to the location where you put git-logo.png on your web server
    (or to be more generic URI of logo, 72x27 size, displayed in top right
    corner of each gitweb page, and used as logo for Atom feed).  Relative
-   to base URI of gitweb.  [Default: git-logo.png]
+   to base URI of gitweb.  [Default: static/git-logo.png]
  * GITWEB_FAVICON
    Points to the location where you put git-favicon.png on your web server
    (or to be more generic URI of favicon, assumed to be image/png type;
    web browsers that support favicons (website icons) may display them
    in the browser's URL bar and next to site name in bookmarks).  Relative
-   to base URI of gitweb.  [Default: git-favicon.png]
+   to base URI of gitweb.  [Default: static/git-favicon.png]
  * GITWEB_JS
    Points to the localtion where you put gitweb.js on your web server
    (or to be more generic URI of JavaScript code used by gitweb).
-   Relative to base URI of gitweb.  [Default: gitweb.js (or gitweb.min.js
-   if JSMIN build variable is defined / JavaScript minifier is used)]
+   Relative to base URI of gitweb.  [Default: static/gitweb.js (or
+   static/gitweb.min.js if JSMIN build variable is defined / JavaScript
+   minifier is used)]
  * GITWEB_CONFIG
    This Perl file will be loaded using 'do' and can be used to override any
    of the options above as well as some other options -- see the "Runtime
index c356e95f18f0f784584ce644c08e1966ca1be52f..9e5e0bf3ee6a86df6329da145f7f597e7abf491f 100755 (executable)
@@ -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();
@@ -28,34 +28,42 @@ BEGIN {
        CGI->compile() if $ENV{'MOD_PERL'};
 }
 
-our $cgi = new CGI;
 our $version = "++GIT_VERSION++";
-our $my_url = $cgi->url();
-our $my_uri = $cgi->url(-absolute => 1);
 
-# Base URL for relative URLs in gitweb ($logo, $favicon, ...),
-# needed and used only for URLs with nonempty PATH_INFO
-our $base_url = $my_url;
+our ($my_url, $my_uri, $base_url, $path_info, $home_link);
+sub evaluate_uri {
+       our $cgi;
 
-# When the script is used as DirectoryIndex, the URL does not contain the name
-# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
-# have to do it ourselves. We make $path_info global because it's also used
-# later on.
-#
-# Another issue with the script being the DirectoryIndex is that the resulting
-# $my_url data is not the full script URL: this is good, because we want
-# generated links to keep implying the script name if it wasn't explicitly
-# indicated in the URL we're handling, but it means that $my_url cannot be used
-# as base URL.
-# Therefore, if we needed to strip PATH_INFO, then we know that we have
-# to build the base URL ourselves:
-our $path_info = $ENV{"PATH_INFO"};
-if ($path_info) {
-       if ($my_url =~ s,\Q$path_info\E$,, &&
-           $my_uri =~ s,\Q$path_info\E$,, &&
-           defined $ENV{'SCRIPT_NAME'}) {
-               $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+       our $my_url = $cgi->url();
+       our $my_uri = $cgi->url(-absolute => 1);
+
+       # Base URL for relative URLs in gitweb ($logo, $favicon, ...),
+       # needed and used only for URLs with nonempty PATH_INFO
+       our $base_url = $my_url;
+
+       # When the script is used as DirectoryIndex, the URL does not contain the name
+       # of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
+       # have to do it ourselves. We make $path_info global because it's also used
+       # later on.
+       #
+       # Another issue with the script being the DirectoryIndex is that the resulting
+       # $my_url data is not the full script URL: this is good, because we want
+       # generated links to keep implying the script name if it wasn't explicitly
+       # indicated in the URL we're handling, but it means that $my_url cannot be used
+       # as base URL.
+       # Therefore, if we needed to strip PATH_INFO, then we know that we have
+       # to build the base URL ourselves:
+       our $path_info = $ENV{"PATH_INFO"};
+       if ($path_info) {
+               if ($my_url =~ s,\Q$path_info\E$,, &&
+                   $my_uri =~ s,\Q$path_info\E$,, &&
+                   defined $ENV{'SCRIPT_NAME'}) {
+                       $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+               }
        }
+
+       # target of the home link on top of all pages
+       our $home_link = $my_uri || "/";
 }
 
 # core git executable to use
@@ -70,9 +78,6 @@ our $projectroot = "++GITWEB_PROJECTROOT++";
 # the number is relative to the projectroot
 our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
 
-# target of the home link on top of all pages
-our $home_link = $my_uri || "/";
-
 # string of the home link on top of all pages
 our $home_link_str = "++GITWEB_HOME_LINK_STR++";
 
@@ -445,6 +450,19 @@ our %feature = (
        'javascript-actions' => {
                'override' => 0,
                'default' => [0]},
+
+       # Syntax highlighting support. This is based on Daniel Svensson's
+       # and Sham Chukoury's work in gitweb-xmms2.git.
+       # It requires the 'highlight' program present in $PATH,
+       # and therefore is disabled by default.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'highlight'}{'default'} = [1];
+
+       'highlight' => {
+               'sub' => sub { feature_bool('highlight', @_) },
+               'override' => 0,
+               'default' => [0]},
 );
 
 sub gitweb_get_feature {
@@ -553,15 +571,18 @@ sub filter_snapshot_fmts {
                !$known_snapshot_formats{$_}{'disabled'}} @fmts;
 }
 
-our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
-our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
-# die if there are errors parsing config file
-if (-e $GITWEB_CONFIG) {
-       do $GITWEB_CONFIG;
-       die $@ if $@;
-} elsif (-e $GITWEB_CONFIG_SYSTEM) {
-       do $GITWEB_CONFIG_SYSTEM;
-       die $@ if $@;
+our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
+sub evaluate_gitweb_config {
+       our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
+       our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
+       # die if there are errors parsing config file
+       if (-e $GITWEB_CONFIG) {
+               do $GITWEB_CONFIG;
+               die $@ if $@;
+       } elsif (-e $GITWEB_CONFIG_SYSTEM) {
+               do $GITWEB_CONFIG_SYSTEM;
+               die $@ if $@;
+       }
 }
 
 # Get loadavg of system, to compare against $maxload.
@@ -587,13 +608,16 @@ sub get_loadavg {
 }
 
 # version of the core git binary
-our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
-$number_of_git_cmds++;
-
-$projects_list ||= $projectroot;
+our $git_version;
+sub evaluate_git_version {
+       our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+       $number_of_git_cmds++;
+}
 
-if (defined $maxload && get_loadavg() > $maxload) {
-       die_error(503, "The load average on the server is too high");
+sub check_loadavg {
+       if (defined $maxload && get_loadavg() > $maxload) {
+               die_error(503, "The load average on the server is too high");
+       }
 }
 
 # ======================================================================
@@ -680,11 +704,15 @@ our %allowed_options = (
 # should be single values, but opt can be an array. We should probably
 # build an array of parameters that can be multi-valued, but since for the time
 # being it's only this one, we just single it out
-while (my ($name, $symbol) = each %cgi_param_mapping) {
-       if ($symbol eq 'opt') {
-               $input_params{$name} = [ $cgi->param($symbol) ];
-       } else {
-               $input_params{$name} = $cgi->param($symbol);
+sub evaluate_query_params {
+       our $cgi;
+
+       while (my ($name, $symbol) = each %cgi_param_mapping) {
+               if ($symbol eq 'opt') {
+                       $input_params{$name} = [ $cgi->param($symbol) ];
+               } else {
+                       $input_params{$name} = $cgi->param($symbol);
+               }
        }
 }
 
@@ -831,152 +859,263 @@ sub evaluate_path_info {
                }
        }
 }
-evaluate_path_info();
 
-our $action = $input_params{'action'};
-if (defined $action) {
-       if (!validate_action($action)) {
-               die_error(400, "Invalid action parameter");
+our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
+     $hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp,
+     $searchtext, $search_regexp);
+sub evaluate_and_validate_params {
+       our $action = $input_params{'action'};
+       if (defined $action) {
+               if (!validate_action($action)) {
+                       die_error(400, "Invalid action parameter");
+               }
        }
-}
 
-# parameters which are pathnames
-our $project = $input_params{'project'};
-if (defined $project) {
-       if (!validate_project($project)) {
-               undef $project;
-               die_error(404, "No such project");
+       # parameters which are pathnames
+       our $project = $input_params{'project'};
+       if (defined $project) {
+               if (!validate_project($project)) {
+                       undef $project;
+                       die_error(404, "No such project");
+               }
        }
-}
 
-our $file_name = $input_params{'file_name'};
-if (defined $file_name) {
-       if (!validate_pathname($file_name)) {
-               die_error(400, "Invalid file parameter");
+       our $file_name = $input_params{'file_name'};
+       if (defined $file_name) {
+               if (!validate_pathname($file_name)) {
+                       die_error(400, "Invalid file parameter");
+               }
        }
-}
 
-our $file_parent = $input_params{'file_parent'};
-if (defined $file_parent) {
-       if (!validate_pathname($file_parent)) {
-               die_error(400, "Invalid file parent parameter");
+       our $file_parent = $input_params{'file_parent'};
+       if (defined $file_parent) {
+               if (!validate_pathname($file_parent)) {
+                       die_error(400, "Invalid file parent parameter");
+               }
        }
-}
 
-# parameters which are refnames
-our $hash = $input_params{'hash'};
-if (defined $hash) {
-       if (!validate_refname($hash)) {
-               die_error(400, "Invalid hash parameter");
+       # parameters which are refnames
+       our $hash = $input_params{'hash'};
+       if (defined $hash) {
+               if (!validate_refname($hash)) {
+                       die_error(400, "Invalid hash parameter");
+               }
        }
-}
 
-our $hash_parent = $input_params{'hash_parent'};
-if (defined $hash_parent) {
-       if (!validate_refname($hash_parent)) {
-               die_error(400, "Invalid hash parent parameter");
+       our $hash_parent = $input_params{'hash_parent'};
+       if (defined $hash_parent) {
+               if (!validate_refname($hash_parent)) {
+                       die_error(400, "Invalid hash parent parameter");
+               }
        }
-}
 
-our $hash_base = $input_params{'hash_base'};
-if (defined $hash_base) {
-       if (!validate_refname($hash_base)) {
-               die_error(400, "Invalid hash base parameter");
+       our $hash_base = $input_params{'hash_base'};
+       if (defined $hash_base) {
+               if (!validate_refname($hash_base)) {
+                       die_error(400, "Invalid hash base parameter");
+               }
        }
-}
 
-our @extra_options = @{$input_params{'extra_options'}};
-# @extra_options is always defined, since it can only be (currently) set from
-# CGI, and $cgi->param() returns the empty array in array context if the param
-# is not set
-foreach my $opt (@extra_options) {
-       if (not exists $allowed_options{$opt}) {
-               die_error(400, "Invalid option parameter");
-       }
-       if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
-               die_error(400, "Invalid option parameter for this action");
+       our @extra_options = @{$input_params{'extra_options'}};
+       # @extra_options is always defined, since it can only be (currently) set from
+       # CGI, and $cgi->param() returns the empty array in array context if the param
+       # is not set
+       foreach my $opt (@extra_options) {
+               if (not exists $allowed_options{$opt}) {
+                       die_error(400, "Invalid option parameter");
+               }
+               if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
+                       die_error(400, "Invalid option parameter for this action");
+               }
        }
-}
 
-our $hash_parent_base = $input_params{'hash_parent_base'};
-if (defined $hash_parent_base) {
-       if (!validate_refname($hash_parent_base)) {
-               die_error(400, "Invalid hash parent base parameter");
+       our $hash_parent_base = $input_params{'hash_parent_base'};
+       if (defined $hash_parent_base) {
+               if (!validate_refname($hash_parent_base)) {
+                       die_error(400, "Invalid hash parent base parameter");
+               }
        }
-}
 
-# other parameters
-our $page = $input_params{'page'};
-if (defined $page) {
-       if ($page =~ m/[^0-9]/) {
-               die_error(400, "Invalid page parameter");
+       # other parameters
+       our $page = $input_params{'page'};
+       if (defined $page) {
+               if ($page =~ m/[^0-9]/) {
+                       die_error(400, "Invalid page parameter");
+               }
        }
-}
 
-our $searchtype = $input_params{'searchtype'};
-if (defined $searchtype) {
-       if ($searchtype =~ m/[^a-z]/) {
-               die_error(400, "Invalid searchtype parameter");
+       our $searchtype = $input_params{'searchtype'};
+       if (defined $searchtype) {
+               if ($searchtype =~ m/[^a-z]/) {
+                       die_error(400, "Invalid searchtype parameter");
+               }
        }
-}
 
-our $search_use_regexp = $input_params{'search_use_regexp'};
+       our $search_use_regexp = $input_params{'search_use_regexp'};
 
-our $searchtext = $input_params{'searchtext'};
-our $search_regexp;
-if (defined $searchtext) {
-       if (length($searchtext) < 2) {
-               die_error(403, "At least two characters are required for search parameter");
+       our $searchtext = $input_params{'searchtext'};
+       our $search_regexp;
+       if (defined $searchtext) {
+               if (length($searchtext) < 2) {
+                       die_error(403, "At least two characters are required for search parameter");
+               }
+               $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
        }
-       $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
 }
 
 # path to the current git repository
 our $git_dir;
-$git_dir = "$projectroot/$project" if $project;
+sub evaluate_git_dir {
+       our $git_dir = "$projectroot/$project" if $project;
+}
+
+our (@snapshot_fmts, $git_avatar);
+sub configure_gitweb_features {
+       # list of supported snapshot formats
+       our @snapshot_fmts = gitweb_get_feature('snapshot');
+       @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+
+       # check that the avatar feature is set to a known provider name,
+       # and for each provider check if the dependencies are satisfied.
+       # if the provider name is invalid or the dependencies are not met,
+       # reset $git_avatar to the empty string.
+       our ($git_avatar) = gitweb_get_feature('avatar');
+       if ($git_avatar eq 'gravatar') {
+               $git_avatar = '' unless (eval { require Digest::MD5; 1; });
+       } elsif ($git_avatar eq 'picon') {
+               # no dependencies
+       } else {
+               $git_avatar = '';
+       }
+}
 
-# list of supported snapshot formats
-our @snapshot_fmts = gitweb_get_feature('snapshot');
-@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+# custom error handler: 'die <message>' is Internal Server Error
+sub handle_errors_html {
+       my $msg = shift; # it is already HTML escaped
 
-# check that the avatar feature is set to a known provider name,
-# and for each provider check if the dependencies are satisfied.
-# if the provider name is invalid or the dependencies are not met,
-# reset $git_avatar to the empty string.
-our ($git_avatar) = gitweb_get_feature('avatar');
-if ($git_avatar eq 'gravatar') {
-       $git_avatar = '' unless (eval { require Digest::MD5; 1; });
-} elsif ($git_avatar eq 'picon') {
-       # no dependencies
-} else {
-       $git_avatar = '';
+       # 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) {
-               $action = git_get_type($hash);
-       } elsif (defined $hash_base && defined $file_name) {
-               $action = git_get_type("$hash_base:$file_name");
-       } elsif (defined $project) {
-               $action = 'summary';
-       } else {
-               $action = 'project_list';
+sub dispatch {
+       if (!defined $action) {
+               if (defined $hash) {
+                       $action = git_get_type($hash);
+               } elsif (defined $hash_base && defined $file_name) {
+                       $action = git_get_type("$hash_base:$file_name");
+               } elsif (defined $project) {
+                       $action = 'summary';
+               } else {
+                       $action = 'project_list';
+               }
+       }
+       if (!defined($actions{$action})) {
+               die_error(400, "Unknown action");
        }
+       if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
+           !$project) {
+               die_error(400, "Project needed");
+       }
+       $actions{$action}->();
 }
-if (!defined($actions{$action})) {
-       die_error(400, "Unknown action");
+
+sub run_request {
+       our $t0 = [Time::HiRes::gettimeofday()]
+               if defined $t0;
+
+       evaluate_uri();
+       evaluate_gitweb_config();
+       evaluate_git_version();
+       check_loadavg();
+
+       # $projectroot and $projects_list might be set in gitweb config file
+       $projects_list ||= $projectroot;
+
+       evaluate_query_params();
+       evaluate_path_info();
+       evaluate_and_validate_params();
+       evaluate_git_dir();
+
+       configure_gitweb_features();
+
+       dispatch();
 }
-if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
-    !$project) {
-       die_error(400, "Project needed");
+
+our $is_last_request = sub { 1 };
+our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
+our $CGI = 'CGI';
+our $cgi;
+sub configure_as_fcgi {
+       require CGI::Fast;
+       our $CGI = 'CGI::Fast';
+
+       my $request_number = 0;
+       # let each child service 100 requests
+       our $is_last_request = sub { ++$request_number > 100 };
 }
-$actions{$action}->();
-exit;
+sub evaluate_argv {
+       my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__;
+       configure_as_fcgi()
+               if $script_name =~ /\.fcgi$/;
+
+       return unless (@ARGV);
+
+       require Getopt::Long;
+       Getopt::Long::GetOptions(
+               'fastcgi|fcgi|f' => \&configure_as_fcgi,
+               'nproc|n=i' => sub {
+                       my ($arg, $val) = @_;
+                       return unless eval { require FCGI::ProcManager; 1; };
+                       my $proc_manager = FCGI::ProcManager->new({
+                               n_processes => $val,
+                       });
+                       our $pre_listen_hook    = sub { $proc_manager->pm_manage()        };
+                       our $pre_dispatch_hook  = sub { $proc_manager->pm_pre_dispatch()  };
+                       our $post_dispatch_hook = sub { $proc_manager->pm_post_dispatch() };
+               },
+       );
+}
+
+sub run {
+       evaluate_argv();
+
+       $pre_listen_hook->()
+               if $pre_listen_hook;
+
+ REQUEST:
+       while ($cgi = $CGI->new()) {
+               $pre_dispatch_hook->()
+                       if $pre_dispatch_hook;
+
+               run_request();
+
+               $pre_dispatch_hook->()
+                       if $post_dispatch_hook;
+
+               last REQUEST if ($is_last_request->());
+       }
+
+ DONE_GITWEB:
+       1;
+}
+
+run();
 
 ## ======================================================================
 ## 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 +1132,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 +2560,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
@@ -3155,26 +3298,88 @@ sub blob_contenttype {
        return $type;
 }
 
+# guess file syntax for syntax highlighting; return undef if no highlighting
+# the name of syntax can (in the future) depend on syntax highlighter used
+sub guess_file_syntax {
+       my ($highlight, $mimetype, $file_name) = @_;
+       return undef unless ($highlight && defined $file_name);
+
+       # configuration for 'highlight' (http://www.andre-simon.de/)
+       # match by basename
+       my %highlight_basename = (
+               #'Program' => 'py',
+               #'Library' => 'py',
+               'SConstruct' => 'py', # SCons equivalent of Makefile
+               'Makefile' => 'make',
+       );
+       # match by extension
+       my %highlight_ext = (
+               # main extensions, defining name of syntax;
+               # see files in /usr/share/highlight/langDefs/ directory
+               map { $_ => $_ }
+                       qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
+               # alternate extensions, see /etc/highlight/filetypes.conf
+               'h' => 'c',
+               map { $_ => 'cpp' } qw(cxx c++ cc),
+               map { $_ => 'php' } qw(php3 php4),
+               map { $_ => 'pl'  } qw(perl pm), # perhaps also 'cgi'
+               'mak' => 'make',
+               map { $_ => 'xml' } qw(xhtml html htm),
+       );
+
+       my $basename = basename($file_name, '.in');
+       return $highlight_basename{$basename}
+               if exists $highlight_basename{$basename};
+
+       $basename =~ /\.([^.]*)$/;
+       my $ext = $1 or return undef;
+       return $highlight_ext{$ext}
+               if exists $highlight_ext{$ext};
+
+       return undef;
+}
+
+# run highlighter and return FD of its output,
+# or return original FD if no highlighting
+sub run_highlighter {
+       my ($fd, $highlight, $syntax) = @_;
+       return $fd unless ($highlight && defined $syntax);
+
+       close $fd
+               or die_error(404, "Reading blob failed");
+       open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
+                 "highlight --xhtml --fragment --syntax $syntax |"
+               or die_error(500, "Couldn't open file or run syntax highlighter");
+       return $fd;
+}
+
 ## ======================================================================
 ## 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 +3393,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_header'});
        my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
        print <<EOF;
 <?xml version="1.0" encoding="utf-8"?>
@@ -3405,6 +3611,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 +3620,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 +3634,8 @@ EOF
        print "</div>\n";
 
        git_footer_html();
-       exit;
+       goto DONE_GITWEB
+               unless ($opts{'-error_handler'});
 }
 
 ## ----------------------------------------------------------------------
@@ -5346,6 +5554,7 @@ sub git_blob {
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(500, "Couldn't cat $file_name, $hash");
        my $mimetype = blob_mimetype($fd, $file_name);
+       # use 'blob_plain' (aka 'raw') view for files that cannot be displayed
        if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
                close $fd;
                return git_blob_plain($mimetype);
@@ -5353,6 +5562,11 @@ sub git_blob {
        # we can have blame only for text/* mimetype
        $have_blame &&= ($mimetype =~ m!^text/!);
 
+       my $highlight = gitweb_check_feature('highlight');
+       my $syntax = guess_file_syntax($highlight, $mimetype, $file_name);
+       $fd = run_highlighter($fd, $highlight, $syntax)
+               if $syntax;
+
        git_header_html(undef, $expires);
        my $formats_nav = '';
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
@@ -5402,9 +5616,8 @@ sub git_blob {
                        chomp $line;
                        $nr++;
                        $line = untabify($line);
-                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1)
-                               . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
-                              $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+                       printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
+                              $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
                }
        }
        close $fd
@@ -6117,8 +6330,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");
similarity index 93%
rename from gitweb/gitweb.css
rename to gitweb/static/gitweb.css
index 50067f2e0dea2c1e17a41e9a5e8b2a8f6240c13d..4132aabcdb4ef402ceb1a890b61778b3254fdf18 100644 (file)
@@ -572,3 +572,21 @@ span.match {
 div.binary {
        font-style: italic;
 }
+
+/* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */
+
+/* Highlighting theme definition: */
+
+.num    { color:#2928ff; }
+.esc    { color:#ff00ff; }
+.str    { color:#ff0000; }
+.dstr   { color:#818100; }
+.slc    { color:#838183; font-style:italic; }
+.com    { color:#838183; font-style:italic; }
+.dir    { color:#008200; }
+.sym    { color:#000000; }
+.line   { color:#555555; }
+.kwa    { color:#000000; font-weight:bold; }
+.kwb    { color:#830000; }
+.kwc    { color:#000000; font-weight:bold; }
+.kwd    { color:#010181; }
similarity index 100%
rename from gitweb/gitweb.js
rename to gitweb/static/gitweb.js
diff --git a/graph.c b/graph.c
index e6bbcaa8c4655add3ecaca578e948355795e36ca..ac7c60540645761d3c99fb405a69cb2d06aaddc2 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -211,6 +211,18 @@ struct git_graph {
        unsigned short default_column_color;
 };
 
+static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void *data)
+{
+       struct git_graph *graph = data;
+       static struct strbuf msgbuf = STRBUF_INIT;
+
+       assert(graph);
+
+       strbuf_reset(&msgbuf);
+       graph_padding_line(graph, &msgbuf);
+       return &msgbuf;
+}
+
 struct git_graph *graph_init(struct rev_info *opt)
 {
        struct git_graph *graph = xmalloc(sizeof(struct git_graph));
@@ -244,6 +256,13 @@ struct git_graph *graph_init(struct rev_info *opt)
        graph->mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity);
        graph->new_mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity);
 
+       /*
+        * The diff output prefix callback, with this we can make
+        * all the diff output to align with the graph lines.
+        */
+       opt->diffopt.output_prefix = diff_output_prefix_callback;
+       opt->diffopt.output_prefix_data = graph;
+
        return graph;
 }
 
diff --git a/grep.c b/grep.c
index 543b1d53784c16020dea089f23c431c7f2608425..82fb349be6f6ef8211d4828101cd8e88bcbb304f 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -7,6 +7,7 @@ void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field fie
 {
        struct grep_pat *p = xcalloc(1, sizeof(*p));
        p->pattern = pat;
+       p->patternlen = strlen(pat);
        p->origin = "header";
        p->no = 0;
        p->token = GREP_PATTERN_HEAD;
@@ -18,9 +19,16 @@ void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field fie
 
 void append_grep_pattern(struct grep_opt *opt, const char *pat,
                         const char *origin, int no, enum grep_pat_token t)
+{
+       append_grep_pat(opt, pat, strlen(pat), origin, no, t);
+}
+
+void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen,
+                    const char *origin, int no, enum grep_pat_token t)
 {
        struct grep_pat *p = xcalloc(1, sizeof(*p));
        p->pattern = pat;
+       p->patternlen = patlen;
        p->origin = origin;
        p->no = no;
        p->token = t;
@@ -44,8 +52,8 @@ struct grep_opt *grep_opt_dup(const struct grep_opt *opt)
                        append_header_grep_pattern(ret, pat->field,
                                                   pat->pattern);
                else
-                       append_grep_pattern(ret, pat->pattern, pat->origin,
-                                           pat->no, pat->token);
+                       append_grep_pat(ret, pat->pattern, pat->patternlen,
+                                       pat->origin, pat->no, pat->token);
        }
 
        return ret;
@@ -329,14 +337,21 @@ static void show_name(struct grep_opt *opt, const char *name)
        opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
 }
 
-
-static int fixmatch(const char *pattern, char *line, int ignore_case, regmatch_t *match)
+static int fixmatch(struct grep_pat *p, char *line, char *eol,
+                   regmatch_t *match)
 {
        char *hit;
-       if (ignore_case)
-               hit = strcasestr(line, pattern);
-       else
-               hit = strstr(line, pattern);
+
+       if (p->ignore_case) {
+               char *s = line;
+               do {
+                       hit = strcasestr(s, p->pattern);
+                       if (hit)
+                               break;
+                       s += strlen(s) + 1;
+               } while (s < eol);
+       } else
+               hit = memmem(line, eol - line, p->pattern, p->patternlen);
 
        if (!hit) {
                match->rm_so = match->rm_eo = -1;
@@ -344,11 +359,22 @@ static int fixmatch(const char *pattern, char *line, int ignore_case, regmatch_t
        }
        else {
                match->rm_so = hit - line;
-               match->rm_eo = match->rm_so + strlen(pattern);
+               match->rm_eo = match->rm_so + p->patternlen;
                return 0;
        }
 }
 
+static int regmatch(const regex_t *preg, char *line, char *eol,
+                   regmatch_t *match, int eflags)
+{
+#ifdef REG_STARTEND
+       match->rm_so = 0;
+       match->rm_eo = eol - line;
+       eflags |= REG_STARTEND;
+#endif
+       return regexec(preg, line, 1, match, eflags);
+}
+
 static int strip_timestamp(char *bol, char **eol_p)
 {
        char *eol = *eol_p;
@@ -399,9 +425,9 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
 
  again:
        if (p->fixed)
-               hit = !fixmatch(p->pattern, bol, p->ignore_case, pmatch);
+               hit = !fixmatch(p, bol, eol, pmatch);
        else
-               hit = !regexec(&p->regexp, bol, 1, pmatch, eflags);
+               hit = !regmatch(&p->regexp, bol, eol, pmatch, eflags);
 
        if (hit && p->word_regexp) {
                if ((pmatch[0].rm_so < 0) ||
@@ -726,16 +752,9 @@ static int look_ahead(struct grep_opt *opt,
                regmatch_t m;
 
                if (p->fixed)
-                       hit = !fixmatch(p->pattern, bol, p->ignore_case, &m);
-               else {
-#ifdef REG_STARTEND
-                       m.rm_so = 0;
-                       m.rm_eo = *left_p;
-                       hit = !regexec(&p->regexp, bol, 1, &m, REG_STARTEND);
-#else
-                       hit = !regexec(&p->regexp, bol, 1, &m, 0);
-#endif
-               }
+                       hit = !fixmatch(p, bol, bol + *left_p, &m);
+               else
+                       hit = !regmatch(&p->regexp, bol, bol + *left_p, &m, 0);
                if (!hit || m.rm_so < 0 || m.rm_eo < 0)
                        continue;
                if (earliest < 0 || m.rm_so < earliest)
@@ -800,17 +819,19 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                opt->show_hunk_mark = 1;
        opt->last_shown = 0;
 
-       if (buffer_is_binary(buf, size)) {
-               switch (opt->binary) {
-               case GREP_BINARY_DEFAULT:
+       switch (opt->binary) {
+       case GREP_BINARY_DEFAULT:
+               if (buffer_is_binary(buf, size))
                        binary_match_only = 1;
-                       break;
-               case GREP_BINARY_NOMATCH:
+               break;
+       case GREP_BINARY_NOMATCH:
+               if (buffer_is_binary(buf, size))
                        return 0; /* Assume unmatch */
-                       break;
-               default:
-                       break;
-               }
+               break;
+       case GREP_BINARY_TEXT:
+               break;
+       default:
+               die("bug: unknown binary handling mode");
        }
 
        memset(&xecfg, 0, sizeof(xecfg));
@@ -871,6 +892,12 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                        count++;
                        if (opt->status_only)
                                return 1;
+                       if (opt->name_only) {
+                               show_name(opt, name);
+                               return 1;
+                       }
+                       if (opt->count)
+                               goto next_line;
                        if (binary_match_only) {
                                opt->output(opt, "Binary file ", 12);
                                output_color(opt, name, strlen(name),
@@ -878,22 +905,14 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                                opt->output(opt, " matches\n", 9);
                                return 1;
                        }
-                       if (opt->name_only) {
-                               show_name(opt, name);
-                               return 1;
-                       }
                        /* Hit at this line.  If we haven't shown the
                         * pre-context lines, we would need to show them.
-                        * When asked to do "count", this still show
-                        * the context which is nonsense, but the user
-                        * deserves to get that ;-).
                         */
                        if (opt->pre_context)
                                show_pre_context(opt, name, buf, bol, lno);
                        else if (opt->funcname)
                                show_funcname_line(opt, name, buf, bol, lno);
-                       if (!opt->count)
-                               show_line(opt, bol, eol, name, lno, ':');
+                       show_line(opt, bol, eol, name, lno, ':');
                        last_hit = lno;
                }
                else if (last_hit &&
@@ -937,6 +956,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                output_sep(opt, ':');
                snprintf(buf, sizeof(buf), "%u\n", count);
                opt->output(opt, buf, strlen(buf));
+               return 1;
        }
        return !!last_hit;
 }
diff --git a/grep.h b/grep.h
index 89342e5b47f6d63dd546e738e4cbf023c447b382..efa8cff980af2b2c06dad080876637d16b5c4985 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -10,17 +10,17 @@ enum grep_pat_token {
        GREP_OPEN_PAREN,
        GREP_CLOSE_PAREN,
        GREP_NOT,
-       GREP_OR,
+       GREP_OR
 };
 
 enum grep_context {
        GREP_CONTEXT_HEAD,
-       GREP_CONTEXT_BODY,
+       GREP_CONTEXT_BODY
 };
 
 enum grep_header_field {
        GREP_HEADER_AUTHOR = 0,
-       GREP_HEADER_COMMITTER,
+       GREP_HEADER_COMMITTER
 };
 
 struct grep_pat {
@@ -29,6 +29,7 @@ struct grep_pat {
        int no;
        enum grep_pat_token token;
        const char *pattern;
+       size_t patternlen;
        enum grep_header_field field;
        regex_t regexp;
        unsigned fixed:1;
@@ -40,7 +41,7 @@ enum grep_expr_node {
        GREP_NODE_ATOM,
        GREP_NODE_NOT,
        GREP_NODE_AND,
-       GREP_NODE_OR,
+       GREP_NODE_OR
 };
 
 struct grep_expr {
@@ -104,6 +105,7 @@ struct grep_opt {
        void *output_priv;
 };
 
+extern void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);
 extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
 extern void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *);
 extern void compile_grep_patterns(struct grep_opt *opt);
index d1e83d0906dbc9d677630cdf74615ec3f9dfc46d..44ce6bb32b0e98681f7c523537082065e8cb5fc1 100644 (file)
@@ -6,6 +6,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "string-list.h"
+#include "url.h"
 
 static const char content_type[] = "Content-Type";
 static const char content_length[] = "Content-Length";
@@ -25,60 +26,6 @@ static struct rpc_service rpc_service[] = {
        { "receive-pack", "receivepack", -1 },
 };
 
-static int decode_char(const char *q)
-{
-       int i;
-       unsigned char val = 0;
-       for (i = 0; i < 2; i++) {
-               unsigned char c = *q++;
-               val <<= 4;
-               if (c >= '0' && c <= '9')
-                       val += c - '0';
-               else if (c >= 'a' && c <= 'f')
-                       val += c - 'a' + 10;
-               else if (c >= 'A' && c <= 'F')
-                       val += c - 'A' + 10;
-               else
-                       return -1;
-       }
-       return val;
-}
-
-static char *decode_parameter(const char **query, int is_name)
-{
-       const char *q = *query;
-       struct strbuf out;
-
-       strbuf_init(&out, 16);
-       do {
-               unsigned char c = *q;
-
-               if (!c)
-                       break;
-               if (c == '&' || (is_name && c == '=')) {
-                       q++;
-                       break;
-               }
-
-               if (c == '%') {
-                       int val = decode_char(q + 1);
-                       if (0 <= val) {
-                               strbuf_addch(&out, val);
-                               q += 3;
-                               continue;
-                       }
-               }
-
-               if (c == '+')
-                       strbuf_addch(&out, ' ');
-               else
-                       strbuf_addch(&out, c);
-               q++;
-       } while (1);
-       *query = q;
-       return strbuf_detach(&out, NULL);
-}
-
 static struct string_list *get_parameters(void)
 {
        if (!query_params) {
@@ -86,8 +33,8 @@ static struct string_list *get_parameters(void)
 
                query_params = xcalloc(1, sizeof(*query_params));
                while (query && *query) {
-                       char *name = decode_parameter(&query, 1);
-                       char *value = decode_parameter(&query, 0);
+                       char *name = url_decode_parameter_name(&query);
+                       char *value = url_decode_parameter_value(&query);
                        struct string_list_item *i;
 
                        i = string_list_lookup(name, query_params);
@@ -541,14 +488,12 @@ static NORETURN void die_webcgi(const char *err, va_list params)
        static int dead;
 
        if (!dead) {
-               char buffer[1000];
                dead = 1;
-
-               vsnprintf(buffer, sizeof(buffer), err, params);
-               fprintf(stderr, "fatal: %s\n", buffer);
                http_status(500, "Internal Server Error");
                hdr_nocache();
                end_headers();
+
+               vreportf("fatal: ", err, params);
        }
        exit(0); /* we successfully reported a failure ;-) */
 }
index 415b1ab0a7f0a98e3a16f82c39bbcc9b04f85ac2..c9bcd116975635cabf30eceb7909abfc7f341948 100644 (file)
@@ -105,7 +105,7 @@ enum transfer_state {
        RUN_PUT,
        RUN_MOVE,
        ABORTED,
-       COMPLETE,
+       COMPLETE
 };
 
 struct transfer_request
index 8ca76d0507bdc1d95283e1f5fee3f88180cdce26..18bd6504beb99ab68f360c4fa93011efae42fdfb 100644 (file)
@@ -15,7 +15,7 @@ enum object_request_state {
        WAITING,
        ABORTED,
        ACTIVE,
-       COMPLETE,
+       COMPLETE
 };
 
 struct object_request
index 9d0097ca02960460ff3a104f1739982fee453987..1a577a0a094342315bd3f68e8d13ef7f57a22f50 100644 (file)
@@ -230,7 +230,7 @@ enum CAPABILITY {
        LITERALPLUS,
        NAMESPACE,
        STARTTLS,
-       AUTH_CRAM_MD5,
+       AUTH_CRAM_MD5
 };
 
 static const char *cap_list[] = {
index f9b3d854a921ab6fa3eed6f4dbf01af1a8657602..3764a1ab72354a3ce643c899c1e8cf5443d4e7cb 100644 (file)
@@ -139,17 +139,17 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
 {
        char temp[4][50];
        struct strbuf cmd = STRBUF_INIT;
-       struct strbuf_expand_dict_entry dict[] = {
-               { "O", temp[0] },
-               { "A", temp[1] },
-               { "B", temp[2] },
-               { "L", temp[3] },
-               { NULL }
-       };
+       struct strbuf_expand_dict_entry dict[5];
        const char *args[] = { NULL, NULL };
        int status, fd, i;
        struct stat st;
 
+       dict[0].placeholder = "O"; dict[0].value = temp[0];
+       dict[1].placeholder = "A"; dict[1].value = temp[1];
+       dict[2].placeholder = "B"; dict[2].value = temp[2];
+       dict[3].placeholder = "L"; dict[3].value = temp[3];
+       dict[4].placeholder = NULL; dict[4].value = NULL;
+
        if (fn->cmdline == NULL)
                die("custom merge driver %s lacks command line.", fn->name);
 
index d3ae969f608b50a469aa60b2e925558a6bb437e5..2e2be7c40f996949a69f597168b620976cf55b2f 100644 (file)
@@ -469,6 +469,12 @@ int log_tree_diff_flush(struct rev_info *opt)
                        int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
                        if ((pch & opt->diffopt.output_format) == pch)
                                printf("---");
+                       if (opt->diffopt.output_prefix) {
+                               struct strbuf *msg = NULL;
+                               msg = opt->diffopt.output_prefix(&opt->diffopt,
+                                       opt->diffopt.output_prefix_data);
+                               fwrite(msg->buf, msg->len, 1, stdout);
+                       }
                        putchar('\n');
                }
        }
index d1192f56d797a1664f5bfb5028d52ad679928e23..b831293b3865ae52584565039e4a893c3c6f45ff 100644 (file)
@@ -10,7 +10,7 @@ struct merge_options {
        enum {
                MERGE_RECURSIVE_NORMAL = 0,
                MERGE_RECURSIVE_OURS,
-               MERGE_RECURSIVE_THEIRS,
+               MERGE_RECURSIVE_THEIRS
        } recursive_variant;
        const char *subtree_shift;
        unsigned buffer_output : 1;
@@ -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 (file)
index 0000000..dee6d62
--- /dev/null
@@ -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 (file)
index 0000000..356f88f
--- /dev/null
@@ -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/notes.c b/notes.c
index e425e198278bfb5c6a039dc88825568f1518e875..6ee04e79e903901db3594feae69ceca4374994e2 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -1083,7 +1083,7 @@ int write_notes_tree(struct notes_tree *t, unsigned char *result)
        return ret;
 }
 
-void prune_notes(struct notes_tree *t)
+void prune_notes(struct notes_tree *t, int flags)
 {
        struct note_delete_list *l = NULL;
 
@@ -1094,7 +1094,10 @@ void prune_notes(struct notes_tree *t)
        for_each_note(t, 0, prune_notes_helper, &l);
 
        while (l) {
-               remove_note(t, l->sha1);
+               if (flags & NOTES_PRUNE_VERBOSE)
+                       printf("%s\n", sha1_to_hex(l->sha1));
+               if (!(flags & NOTES_PRUNE_DRYRUN))
+                       remove_note(t, l->sha1);
                l = l->next;
        }
 }
diff --git a/notes.h b/notes.h
index 9f59277c516f7ac78bcc66f91a7d7013b2a77f65..cc2dff22a12f2f7e78d37fb316e70f0a1ebaa2cd 100644 (file)
--- a/notes.h
+++ b/notes.h
@@ -171,6 +171,9 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
  */
 int write_notes_tree(struct notes_tree *t, unsigned char *result);
 
+/* Flags controlling the operation of prune */
+#define NOTES_PRUNE_VERBOSE 1
+#define NOTES_PRUNE_DRYRUN 2
 /*
  * Remove all notes annotating non-existing objects from the given notes tree
  *
@@ -181,7 +184,7 @@ int write_notes_tree(struct notes_tree *t, unsigned char *result);
  * structure are not persistent until a subsequent call to write_notes_tree()
  * returns zero.
  */
-void prune_notes(struct notes_tree *t);
+void prune_notes(struct notes_tree *t, int flags);
 
 /*
  * Free (and de-initialize) the given notes_tree structure
index 8546d8526f311e2a2703c258a4da3f02f8650df4..0fa79bc31d322e2aab8fce738ca6489a35a51ca4 100644 (file)
@@ -4,8 +4,9 @@
 #include "commit.h"
 #include "color.h"
 
-static int parse_options_usage(const char * const *usagestr,
-                              const struct option *opts);
+static int parse_options_usage(struct parse_opt_ctx_t *ctx,
+                              const char * const *usagestr,
+                              const struct option *opts, int err);
 
 #define OPT_SHORT 1
 #define OPT_UNSET 2
@@ -351,8 +352,9 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
                die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
 }
 
-static int usage_with_options_internal(const char * const *,
-                                      const struct option *, int);
+static int usage_with_options_internal(struct parse_opt_ctx_t *,
+                                      const char * const *,
+                                      const struct option *, int, int);
 
 int parse_options_step(struct parse_opt_ctx_t *ctx,
                       const struct option *options,
@@ -380,10 +382,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                if (arg[1] != '-') {
                        ctx->opt = arg + 1;
                        if (internal_help && *ctx->opt == 'h')
-                               return parse_options_usage(usagestr, options);
+                               return parse_options_usage(ctx, usagestr, options, 0);
                        switch (parse_short_opt(ctx, options)) {
                        case -1:
-                               return parse_options_usage(usagestr, options);
+                               return parse_options_usage(ctx, usagestr, options, 1);
                        case -2:
                                goto unknown;
                        }
@@ -391,10 +393,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                                check_typos(arg + 1, options);
                        while (ctx->opt) {
                                if (internal_help && *ctx->opt == 'h')
-                                       return parse_options_usage(usagestr, options);
+                                       return parse_options_usage(ctx, usagestr, options, 0);
                                switch (parse_short_opt(ctx, options)) {
                                case -1:
-                                       return parse_options_usage(usagestr, options);
+                                       return parse_options_usage(ctx, usagestr, options, 1);
                                case -2:
                                        /* fake a short option thing to hide the fact that we may have
                                         * started to parse aggregated stuff
@@ -418,12 +420,12 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                }
 
                if (internal_help && !strcmp(arg + 2, "help-all"))
-                       return usage_with_options_internal(usagestr, options, 1);
+                       return usage_with_options_internal(ctx, usagestr, options, 1, 0);
                if (internal_help && !strcmp(arg + 2, "help"))
-                       return parse_options_usage(usagestr, options);
+                       return parse_options_usage(ctx, usagestr, options, 0);
                switch (parse_long_opt(ctx, arg + 2, options)) {
                case -1:
-                       return parse_options_usage(usagestr, options);
+                       return parse_options_usage(ctx, usagestr, options, 1);
                case -2:
                        goto unknown;
                }
@@ -468,7 +470,7 @@ int parse_options(int argc, const char **argv, const char *prefix,
        return parse_options_end(&ctx);
 }
 
-static int usage_argh(const struct option *opts)
+static int usage_argh(const struct option *opts, FILE *outfile)
 {
        const char *s;
        int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) || !opts->argh;
@@ -479,72 +481,81 @@ static int usage_argh(const struct option *opts)
                        s = literal ? "[%s]" : "[<%s>]";
        else
                s = literal ? " %s" : " <%s>";
-       return fprintf(stderr, s, opts->argh ? opts->argh : "...");
+       return fprintf(outfile, s, opts->argh ? opts->argh : "...");
 }
 
 #define USAGE_OPTS_WIDTH 24
 #define USAGE_GAP         2
 
-static int usage_with_options_internal(const char * const *usagestr,
-                               const struct option *opts, int full)
+static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
+                                      const char * const *usagestr,
+                                      const struct option *opts, int full, int err)
 {
+       FILE *outfile = err ? stderr : stdout;
+
        if (!usagestr)
                return PARSE_OPT_HELP;
 
-       fprintf(stderr, "usage: %s\n", *usagestr++);
+       if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
+               fprintf(outfile, "cat <<\\EOF\n");
+
+       fprintf(outfile, "usage: %s\n", *usagestr++);
        while (*usagestr && **usagestr)
-               fprintf(stderr, "   or: %s\n", *usagestr++);
+               fprintf(outfile, "   or: %s\n", *usagestr++);
        while (*usagestr) {
-               fprintf(stderr, "%s%s\n",
+               fprintf(outfile, "%s%s\n",
                                **usagestr ? "    " : "",
                                *usagestr);
                usagestr++;
        }
 
        if (opts->type != OPTION_GROUP)
-               fputc('\n', stderr);
+               fputc('\n', outfile);
 
        for (; opts->type != OPTION_END; opts++) {
                size_t pos;
                int pad;
 
                if (opts->type == OPTION_GROUP) {
-                       fputc('\n', stderr);
+                       fputc('\n', outfile);
                        if (*opts->help)
-                               fprintf(stderr, "%s\n", opts->help);
+                               fprintf(outfile, "%s\n", opts->help);
                        continue;
                }
                if (!full && (opts->flags & PARSE_OPT_HIDDEN))
                        continue;
 
-               pos = fprintf(stderr, "    ");
+               pos = fprintf(outfile, "    ");
                if (opts->short_name && !(opts->flags & PARSE_OPT_NEGHELP)) {
                        if (opts->flags & PARSE_OPT_NODASH)
-                               pos += fprintf(stderr, "%c", opts->short_name);
+                               pos += fprintf(outfile, "%c", opts->short_name);
                        else
-                               pos += fprintf(stderr, "-%c", opts->short_name);
+                               pos += fprintf(outfile, "-%c", opts->short_name);
                }
                if (opts->long_name && opts->short_name)
-                       pos += fprintf(stderr, ", ");
+                       pos += fprintf(outfile, ", ");
                if (opts->long_name)
-                       pos += fprintf(stderr, "--%s%s",
+                       pos += fprintf(outfile, "--%s%s",
                                (opts->flags & PARSE_OPT_NEGHELP) ?  "no-" : "",
                                opts->long_name);
                if (opts->type == OPTION_NUMBER)
-                       pos += fprintf(stderr, "-NUM");
+                       pos += fprintf(outfile, "-NUM");
 
                if (!(opts->flags & PARSE_OPT_NOARG))
-                       pos += usage_argh(opts);
+                       pos += usage_argh(opts, outfile);
 
                if (pos <= USAGE_OPTS_WIDTH)
                        pad = USAGE_OPTS_WIDTH - pos;
                else {
-                       fputc('\n', stderr);
+                       fputc('\n', outfile);
                        pad = USAGE_OPTS_WIDTH;
                }
-               fprintf(stderr, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
+               fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
        }
-       fputc('\n', stderr);
+       fputc('\n', outfile);
+
+       if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
+               fputs("EOF\n", outfile);
 
        return PARSE_OPT_HELP;
 }
@@ -552,7 +563,7 @@ static int usage_with_options_internal(const char * const *usagestr,
 void usage_with_options(const char * const *usagestr,
                        const struct option *opts)
 {
-       usage_with_options_internal(usagestr, opts, 0);
+       usage_with_options_internal(NULL, usagestr, opts, 0, 1);
        exit(129);
 }
 
@@ -564,10 +575,11 @@ void usage_msg_opt(const char *msg,
        usage_with_options(usagestr, options);
 }
 
-static int parse_options_usage(const char * const *usagestr,
-                              const struct option *opts)
+static int parse_options_usage(struct parse_opt_ctx_t *ctx,
+                              const char * const *usagestr,
+                              const struct option *opts, int err)
 {
-       return usage_with_options_internal(usagestr, opts, 0);
+       return usage_with_options_internal(ctx, usagestr, opts, 0, err);
 }
 
 
index 7581e931da13151473739036a89d9d19303eb18b..7435cdbf1d94378cf1c3408ae17cb610165a00ba 100644 (file)
@@ -25,7 +25,7 @@ enum parse_opt_flags {
        PARSE_OPT_STOP_AT_NON_OPTION = 2,
        PARSE_OPT_KEEP_ARGV0 = 4,
        PARSE_OPT_KEEP_UNKNOWN = 8,
-       PARSE_OPT_NO_INTERNAL_HELP = 16,
+       PARSE_OPT_NO_INTERNAL_HELP = 16
 };
 
 enum parse_opt_option_flags {
@@ -37,6 +37,7 @@ enum parse_opt_option_flags {
        PARSE_OPT_NODASH = 32,
        PARSE_OPT_LITERAL_ARGHELP = 64,
        PARSE_OPT_NEGHELP = 128,
+       PARSE_OPT_SHELL_EVAL = 256
 };
 
 struct option;
@@ -160,7 +161,7 @@ extern NORETURN void usage_msg_opt(const char *msg,
 enum {
        PARSE_OPT_HELP = -1,
        PARSE_OPT_DONE,
-       PARSE_OPT_UNKNOWN,
+       PARSE_OPT_UNKNOWN
 };
 
 /*
index 2777d30224618fb0ec823961c3c35ede1ac4e2e5..4b85373ba6797069eefa96a63141438bd5de8803 100644 (file)
--- a/pretty.c
+++ b/pretty.c
 #include "reflog-walk.h"
 
 static char *user_format;
+static struct cmt_fmt_map {
+       const char *name;
+       enum cmit_fmt format;
+       int is_tformat;
+       int is_alias;
+       const char *user_format;
+} *commit_formats;
+static size_t builtin_formats_len;
+static size_t commit_formats_len;
+static size_t commit_formats_alloc;
+static struct cmt_fmt_map *find_commit_format(const char *sought);
 
 static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)
 {
@@ -21,22 +32,118 @@ static void save_user_format(struct rev_info *rev, const char *cp, int is_tforma
        rev->commit_format = CMIT_FMT_USERFORMAT;
 }
 
-void get_commit_format(const char *arg, struct rev_info *rev)
+static int git_pretty_formats_config(const char *var, const char *value, void *cb)
 {
+       struct cmt_fmt_map *commit_format = NULL;
+       const char *name;
+       const char *fmt;
        int i;
-       static struct cmt_fmt_map {
-               const char *n;
-               size_t cmp_len;
-               enum cmit_fmt v;
-       } cmt_fmts[] = {
-               { "raw",        1,      CMIT_FMT_RAW },
-               { "medium",     1,      CMIT_FMT_MEDIUM },
-               { "short",      1,      CMIT_FMT_SHORT },
-               { "email",      1,      CMIT_FMT_EMAIL },
-               { "full",       5,      CMIT_FMT_FULL },
-               { "fuller",     5,      CMIT_FMT_FULLER },
-               { "oneline",    1,      CMIT_FMT_ONELINE },
+
+       if (prefixcmp(var, "pretty."))
+               return 0;
+
+       name = var + strlen("pretty.");
+       for (i = 0; i < builtin_formats_len; i++) {
+               if (!strcmp(commit_formats[i].name, name))
+                       return 0;
+       }
+
+       for (i = builtin_formats_len; i < commit_formats_len; i++) {
+               if (!strcmp(commit_formats[i].name, name)) {
+                       commit_format = &commit_formats[i];
+                       break;
+               }
+       }
+
+       if (!commit_format) {
+               ALLOC_GROW(commit_formats, commit_formats_len+1,
+                          commit_formats_alloc);
+               commit_format = &commit_formats[commit_formats_len];
+               memset(commit_format, 0, sizeof(*commit_format));
+               commit_formats_len++;
+       }
+
+       commit_format->name = xstrdup(name);
+       commit_format->format = CMIT_FMT_USERFORMAT;
+       git_config_string(&fmt, var, value);
+       if (!prefixcmp(fmt, "format:") || !prefixcmp(fmt, "tformat:")) {
+               commit_format->is_tformat = fmt[0] == 't';
+               fmt = strchr(fmt, ':') + 1;
+       } else if (strchr(fmt, '%'))
+               commit_format->is_tformat = 1;
+       else
+               commit_format->is_alias = 1;
+       commit_format->user_format = fmt;
+
+       return 0;
+}
+
+static void setup_commit_formats(void)
+{
+       struct cmt_fmt_map builtin_formats[] = {
+               { "raw",        CMIT_FMT_RAW,           0 },
+               { "medium",     CMIT_FMT_MEDIUM,        0 },
+               { "short",      CMIT_FMT_SHORT,         0 },
+               { "email",      CMIT_FMT_EMAIL,         0 },
+               { "fuller",     CMIT_FMT_FULLER,        0 },
+               { "full",       CMIT_FMT_FULL,          0 },
+               { "oneline",    CMIT_FMT_ONELINE,       1 }
        };
+       commit_formats_len = ARRAY_SIZE(builtin_formats);
+       builtin_formats_len = commit_formats_len;
+       ALLOC_GROW(commit_formats, commit_formats_len, commit_formats_alloc);
+       memcpy(commit_formats, builtin_formats,
+              sizeof(*builtin_formats)*ARRAY_SIZE(builtin_formats));
+
+       git_config(git_pretty_formats_config, NULL);
+}
+
+static struct cmt_fmt_map *find_commit_format_recursive(const char *sought,
+                                                       const char *original,
+                                                       int num_redirections)
+{
+       struct cmt_fmt_map *found = NULL;
+       size_t found_match_len = 0;
+       int i;
+
+       if (num_redirections >= commit_formats_len)
+               die("invalid --pretty format: "
+                   "'%s' references an alias which points to itself",
+                   original);
+
+       for (i = 0; i < commit_formats_len; i++) {
+               size_t match_len;
+
+               if (prefixcmp(commit_formats[i].name, sought))
+                       continue;
+
+               match_len = strlen(commit_formats[i].name);
+               if (found == NULL || found_match_len > match_len) {
+                       found = &commit_formats[i];
+                       found_match_len = match_len;
+               }
+       }
+
+       if (found && found->is_alias) {
+               found = find_commit_format_recursive(found->user_format,
+                                                    original,
+                                                    num_redirections+1);
+       }
+
+       return found;
+}
+
+static struct cmt_fmt_map *find_commit_format(const char *sought)
+{
+       if (!commit_formats)
+               setup_commit_formats();
+
+       return find_commit_format_recursive(sought, sought, 0);
+}
+
+void get_commit_format(const char *arg, struct rev_info *rev)
+{
+       struct cmt_fmt_map *commit_format;
 
        rev->use_terminator = 0;
        if (!arg || !*arg) {
@@ -47,21 +154,22 @@ void get_commit_format(const char *arg, struct rev_info *rev)
                save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't');
                return;
        }
-       for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
-               if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
-                   !strncmp(arg, cmt_fmts[i].n, strlen(arg))) {
-                       if (cmt_fmts[i].v == CMIT_FMT_ONELINE)
-                               rev->use_terminator = 1;
-                       rev->commit_format = cmt_fmts[i].v;
-                       return;
-               }
-       }
+
        if (strchr(arg, '%')) {
                save_user_format(rev, arg, 1);
                return;
        }
 
-       die("invalid --pretty format: %s", arg);
+       commit_format = find_commit_format(arg);
+       if (!commit_format)
+               die("invalid --pretty format: %s", arg);
+
+       rev->commit_format = commit_format->format;
+       rev->use_terminator = commit_format->is_tformat;
+       if (commit_format->format == CMIT_FMT_USERFORMAT) {
+               save_user_format(rev, commit_format->user_format,
+                                commit_format->is_tformat);
+       }
 }
 
 /*
@@ -801,6 +909,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. */
@@ -829,7 +941,7 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
        enum {
                NO_MAGIC,
                ADD_LF_BEFORE_NON_EMPTY,
-               DEL_LF_BEFORE_EMPTY,
+               DEL_LF_BEFORE_EMPTY
        } magic = NO_MAGIC;
 
        switch (placeholder[0]) {
diff --git a/refs.c b/refs.c
index d3db15a76cc46f6f6a31d4448816c09e6c48e543..6f486ae62d8b4605520e75286ca36217d0715363 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -314,7 +314,11 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha
 
 void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
 {
-       struct warn_if_dangling_data data = { fp, refname, msg_fmt };
+       struct warn_if_dangling_data data;
+
+       data.fp = fp;
+       data.refname = refname;
+       data.msg_fmt = msg_fmt;
        for_each_rawref(warn_if_dangling_symref, &data);
 }
 
@@ -1258,52 +1262,65 @@ static int copy_msg(char *buf, const char *msg)
        return cp - buf;
 }
 
-static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
-                        const unsigned char *new_sha1, const char *msg)
+int log_ref_setup(const char *ref_name, char *logfile, int bufsize)
 {
-       int logfd, written, oflags = O_APPEND | O_WRONLY;
-       unsigned maxlen, len;
-       int msglen;
-       char log_file[PATH_MAX];
-       char *logrec;
-       const char *committer;
-
-       if (log_all_ref_updates < 0)
-               log_all_ref_updates = !is_bare_repository();
-
-       git_snpath(log_file, sizeof(log_file), "logs/%s", ref_name);
+       int logfd, oflags = O_APPEND | O_WRONLY;
 
+       git_snpath(logfile, bufsize, "logs/%s", ref_name);
        if (log_all_ref_updates &&
            (!prefixcmp(ref_name, "refs/heads/") ||
             !prefixcmp(ref_name, "refs/remotes/") ||
             !prefixcmp(ref_name, "refs/notes/") ||
             !strcmp(ref_name, "HEAD"))) {
-               if (safe_create_leading_directories(log_file) < 0)
+               if (safe_create_leading_directories(logfile) < 0)
                        return error("unable to create directory for %s",
-                                    log_file);
+                                    logfile);
                oflags |= O_CREAT;
        }
 
-       logfd = open(log_file, oflags, 0666);
+       logfd = open(logfile, oflags, 0666);
        if (logfd < 0) {
                if (!(oflags & O_CREAT) && errno == ENOENT)
                        return 0;
 
                if ((oflags & O_CREAT) && errno == EISDIR) {
-                       if (remove_empty_directories(log_file)) {
+                       if (remove_empty_directories(logfile)) {
                                return error("There are still logs under '%s'",
-                                            log_file);
+                                            logfile);
                        }
-                       logfd = open(log_file, oflags, 0666);
+                       logfd = open(logfile, oflags, 0666);
                }
 
                if (logfd < 0)
                        return error("Unable to append to %s: %s",
-                                    log_file, strerror(errno));
+                                    logfile, strerror(errno));
        }
 
-       adjust_shared_perm(log_file);
+       adjust_shared_perm(logfile);
+       close(logfd);
+       return 0;
+}
+
+static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
+                        const unsigned char *new_sha1, const char *msg)
+{
+       int logfd, result, written, oflags = O_APPEND | O_WRONLY;
+       unsigned maxlen, len;
+       int msglen;
+       char log_file[PATH_MAX];
+       char *logrec;
+       const char *committer;
+
+       if (log_all_ref_updates < 0)
+               log_all_ref_updates = !is_bare_repository();
 
+       result = log_ref_setup(ref_name, log_file, sizeof(log_file));
+       if (result)
+               return result;
+
+       logfd = open(log_file, oflags);
+       if (logfd < 0)
+               return 0;
        msglen = msg ? strlen(msg) : 0;
        committer = git_committer_info(0);
        maxlen = strlen(committer) + msglen + 100;
diff --git a/refs.h b/refs.h
index 4a18b083f52a15e5216d583644e590d666cae097..762ce504b5eba3aacff204321a7c41db2b1e6053 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -68,6 +68,9 @@ extern void unlock_ref(struct ref_lock *lock);
 /** Writes sha1 into the ref specified by the lock. **/
 extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
+/** Setup reflog before using. **/
+int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
+
 /** Reads log for the value of ref during at_time. **/
 extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
 
index 26ce56046dd14fec4ae59c7e7df3c2072178c51d..e51cd22d6bcbd29bd47392434d37fb50d7b97588 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -443,6 +443,8 @@ static int handle_config(const char *key, const char *value, void *cb)
        } else if (!strcmp(subkey, ".tagopt")) {
                if (!strcmp(value, "--no-tags"))
                        remote->fetch_tags = -1;
+               else if (!strcmp(value, "--tags"))
+                       remote->fetch_tags = 2;
        } else if (!strcmp(subkey, ".proxy")) {
                return git_config_string((const char **)&remote->http_proxy,
                                         key, value);
@@ -657,10 +659,9 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
 
 int valid_fetch_refspec(const char *fetch_refspec_str)
 {
-       const char *fetch_refspec[] = { fetch_refspec_str };
        struct refspec *refspec;
 
-       refspec = parse_refspec_internal(1, fetch_refspec, 1, 1);
+       refspec = parse_refspec_internal(1, &fetch_refspec_str, 1, 1);
        free_refspecs(refspec, 1);
        return !!refspec;
 }
index 6e13643cabb6fa9a5b619f53dd148345d9161ad4..888d7c15de2eacc56d869a96d4463aefca7a7a06 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -145,7 +145,7 @@ int branch_merge_matches(struct branch *, int n, const char *);
 enum match_refs_flags {
        MATCH_REFS_NONE         = 0,
        MATCH_REFS_ALL          = (1 << 0),
-       MATCH_REFS_MIRROR       = (1 << 1),
+       MATCH_REFS_MIRROR       = (1 << 1)
 };
 
 /* Reporting of tracking info */
index f221bed1e97a0f1b41d1845edac270a109e3a4dd..2197890982e55d7ff0832b6c59b6661874fa4223 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -153,7 +153,7 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz
        git_SHA_CTX ctx;
        int hunk_no = 0;
        enum {
-               RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL,
+               RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL
        } hunk = RR_CONTEXT;
        struct strbuf one = STRBUF_INIT, two = STRBUF_INIT;
        struct strbuf buf = STRBUF_INIT;
index f4b8b383153b2330be1729fa29488bab1848e019..b209d493e169ae58130a998f7dc1239f5a385c44 100644 (file)
@@ -1781,7 +1781,7 @@ int prepare_revision_walk(struct rev_info *revs)
 enum rewrite_result {
        rewrite_one_ok,
        rewrite_one_noparents,
-       rewrite_one_error,
+       rewrite_one_error
 };
 
 static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
index c7793f50fbe0a43495c2b2d36a47c0b5aac37483..2a1041ef6599c84fff6a8d9faf5dea23a2af3ab0 100644 (file)
@@ -84,6 +84,7 @@ static NORETURN void die_child(const char *err, va_list params)
        unused = write(child_err, "\n", 1);
        exit(128);
 }
+#endif
 
 static inline void set_cloexec(int fd)
 {
@@ -91,7 +92,6 @@ static inline void set_cloexec(int fd)
        if (flags >= 0)
                fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
 }
-#endif
 
 static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
 {
@@ -449,11 +449,35 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const
        return run_command(&cmd);
 }
 
-#ifdef WIN32
-static unsigned __stdcall run_thread(void *data)
+#ifndef NO_PTHREADS
+static pthread_t main_thread;
+static int main_thread_set;
+static pthread_key_t async_key;
+
+static void *run_thread(void *data)
 {
        struct async *async = data;
-       return async->proc(async->proc_in, async->proc_out, async->data);
+       intptr_t ret;
+
+       pthread_setspecific(async_key, async);
+       ret = async->proc(async->proc_in, async->proc_out, async->data);
+       return (void *)ret;
+}
+
+static NORETURN void die_async(const char *err, va_list params)
+{
+       vreportf("fatal: ", err, params);
+
+       if (!pthread_equal(main_thread, pthread_self())) {
+               struct async *async = pthread_getspecific(async_key);
+               if (async->proc_in >= 0)
+                       close(async->proc_in);
+               if (async->proc_out >= 0)
+                       close(async->proc_out);
+               pthread_exit((void *)128);
+       }
+
+       exit(128);
 }
 #endif
 
@@ -499,7 +523,7 @@ int start_async(struct async *async)
        else
                proc_out = -1;
 
-#ifndef WIN32
+#ifdef NO_PTHREADS
        /* Flush stdio before fork() to avoid cloning buffers */
        fflush(NULL);
 
@@ -526,12 +550,29 @@ int start_async(struct async *async)
        else if (async->out)
                close(async->out);
 #else
+       if (!main_thread_set) {
+               /*
+                * We assume that the first time that start_async is called
+                * it is from the main thread.
+                */
+               main_thread_set = 1;
+               main_thread = pthread_self();
+               pthread_key_create(&async_key, NULL);
+               set_die_routine(die_async);
+       }
+
+       if (proc_in >= 0)
+               set_cloexec(proc_in);
+       if (proc_out >= 0)
+               set_cloexec(proc_out);
        async->proc_in = proc_in;
        async->proc_out = proc_out;
-       async->tid = (HANDLE) _beginthreadex(NULL, 0, run_thread, async, 0, NULL);
-       if (!async->tid) {
-               error("cannot create thread: %s", strerror(errno));
-               goto error;
+       {
+               int err = pthread_create(&async->tid, NULL, run_thread, async);
+               if (err) {
+                       error("cannot create thread: %s", strerror(err));
+                       goto error;
+               }
        }
 #endif
        return 0;
@@ -551,17 +592,15 @@ error:
 
 int finish_async(struct async *async)
 {
-#ifndef WIN32
-       int ret = wait_or_whine(async->pid, "child process", 0);
+#ifdef NO_PTHREADS
+       return wait_or_whine(async->pid, "child process", 0);
 #else
-       DWORD ret = 0;
-       if (WaitForSingleObject(async->tid, INFINITE) != WAIT_OBJECT_0)
-               ret = error("waiting for thread failed: %lu", GetLastError());
-       else if (!GetExitCodeThread(async->tid, &ret))
-               ret = error("cannot get thread exit code: %lu", GetLastError());
-       CloseHandle(async->tid);
+       void *ret = (void *)(intptr_t)(-1);
+
+       if (pthread_join(async->tid, &ret))
+               error("pthread_join failed");
+       return (int)(intptr_t)ret;
 #endif
-       return ret;
 }
 
 int run_hook(const char *index_file, const char *name, ...)
index 94619f52d95888b320664b7f19db3eeb7d6d8cca..56491b9f2344541c02bd0da2928a535f11193bd8 100644 (file)
@@ -1,6 +1,10 @@
 #ifndef RUN_COMMAND_H
 #define RUN_COMMAND_H
 
+#ifndef NO_PTHREADS
+#include <pthread.h>
+#endif
+
 struct child_process {
        const char **argv;
        pid_t pid;
@@ -74,10 +78,10 @@ struct async {
        void *data;
        int in;         /* caller writes here and closes it */
        int out;        /* caller reads from here and closes it */
-#ifndef WIN32
+#ifdef NO_PTHREADS
        pid_t pid;
 #else
-       HANDLE tid;
+       pthread_t tid;
        int proc_in;
        int proc_out;
 #endif
diff --git a/setup.c b/setup.c
index 0e4cfe603f1afe515365c9daf1ad8406de8e7fa8..7e0460205dc1d15007849f367d21e999f2da044c 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -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;
index 72de38909ef6b05f0aa709b78fd803c632a02044..e42ef96d457f6aa12ab7d0057acf936db0335d40 100644 (file)
@@ -2525,3 +2525,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));
+}
index 25c559bb49d04586c69242cd7ef03713f0939e9d..f9de24b4d2baa9fad1d6caa72fea651da78d6878 100644 (file)
@@ -3,6 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
+-include ../config.mak.autogen
 -include ../config.mak
 
 #GIT_TEST_OPTS=--verbose --debug
@@ -35,7 +36,9 @@ aggregate-results-and-cleanup: $(T)
        $(MAKE) clean
 
 aggregate-results:
-       '$(SHELL_PATH_SQ)' ./aggregate-results.sh test-results/t*-*
+       for f in test-results/t*-*; do \
+               echo "$$f"; \
+       done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh
 
 # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
 full-svn-test:
index d5bab75d7da49ebb53e368d67f6b867f5417a125..d206b7c4cfa4f7d61d151cebc35a0f296fbe4c6b 100755 (executable)
@@ -6,7 +6,7 @@ failed=0
 broken=0
 total=0
 
-for file
+while read file
 do
        while read type value
        do
index 5a734b1b7b2df4a5c5c35f5347c618f3735317ac..b70b891b628d1e5d7fe68b4d303c17fdd0416981 100644 (file)
@@ -19,9 +19,9 @@ our \$site_name = '[localhost]';
 our \$site_header = '';
 our \$site_footer = '';
 our \$home_text = 'indextext.html';
-our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/gitweb.css');
-our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/git-logo.png';
-our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/git-favicon.png';
+our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/static/gitweb.css');
+our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/static/git-logo.png';
+our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/static/git-favicon.png';
 our \$projects_list = '';
 our \$export_ok = '';
 our \$strict_export = '';
index 985d517a1ce4cf2f80eed3d54c25b5fc912d5c6b..ea25dd89e505525507c16c62d91fb07c092b064a 100644 (file)
@@ -91,7 +91,7 @@ check_output()
        shift 1
        if eval "$*" | entag > $_name.actual
        then
-               diff $_name.expected $_name.actual
+               test_cmp $_name.expected $_name.actual
        else
                return 1;
        fi
index 3ec9cbef2c88f65e5fb254d10cc551c6c4062c88..f2c73369a5b93544ee837229da965d7efbdc45bf 100755 (executable)
@@ -301,7 +301,7 @@ $expectfilter >expected <<\EOF
 EOF
 test_expect_success \
     'validate git diff-files output for a know cache/work tree state.' \
-    'git diff-files >current && diff >/dev/null -b current expected'
+    'git diff-files >current && test_cmp current expected >/dev/null'
 
 test_expect_success \
     'git update-index --refresh should succeed.' \
index c3e7e322a8d62b988999b166d2abcf08930adbf7..234a94f3e6311c529b5bb476ee5a6a2ea705017f 100755 (executable)
@@ -453,5 +453,57 @@ test_expect_success 'invalid .gitattributes (must not crash)' '
        git diff
 
 '
+# Some more tests here to add new autocrlf functionality.
+# We want to have a known state here, so start a bit from scratch
+
+test_expect_success 'setting up for new autocrlf tests' '
+       git config core.autocrlf false &&
+       git config core.safecrlf false &&
+       rm -rf .????* * &&
+       for w in I am all LF; do echo $w; done >alllf &&
+       for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed &&
+       for w in I am all CRLF; do echo $w; done | append_cr >allcrlf &&
+       git add -A . &&
+       git commit -m "alllf, allcrlf and mixed only" &&
+       git tag -a -m "message" autocrlf-checkpoint
+'
+
+test_expect_success 'report no change after setting autocrlf' '
+       git config core.autocrlf true &&
+       touch * &&
+       git diff --exit-code
+'
+
+test_expect_success 'files are clean after checkout' '
+       rm * &&
+       git checkout -f &&
+       git diff --exit-code
+'
+
+cr_to_Q_no_NL () {
+    tr '\015' Q | tr -d '\012'
+}
+
+test_expect_success 'LF only file gets CRLF with autocrlf' '
+       test "$(cr_to_Q_no_NL < alllf)" = "IQamQallQLFQ"
+'
+
+test_expect_success 'Mixed file is still mixed with autocrlf' '
+       test "$(cr_to_Q_no_NL < mixed)" = "OhhereisCRLFQintext"
+'
+
+test_expect_success 'CRLF only file has CRLF with autocrlf' '
+       test "$(cr_to_Q_no_NL < allcrlf)" = "IQamQallQCRLFQ"
+'
+
+test_expect_success 'New CRLF file gets LF in repo' '
+       tr -d "\015" < alllf | append_cr > alllf2 &&
+       git add alllf2 &&
+       git commit -m "alllf2 added" &&
+       git config core.autocrlf false &&
+       rm * &&
+       git checkout -f &&
+       test_cmp alllf alllf2
+'
 
 test_done
index 6cb8d60ea2649495c0e3c8bbb8b7cc75c36799b7..828e35baf72d94908ad1f30dbd2e1aa6f9376e69 100755 (executable)
@@ -65,17 +65,21 @@ test_expect_success expanded_in_repo '
                echo "\$Id:NoSpaceAtFront \$"
                echo "\$Id:NoSpaceAtEitherEnd\$"
                echo "\$Id: NoTerminatingSymbol"
+               echo "\$Id: Foreign Commit With Spaces \$"
+               echo "\$Id: NoTerminatingSymbolAtEOF"
        } > expanded-keywords &&
 
        {
                echo "File with expanded keywords"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
                echo "\$Id: NoTerminatingSymbol"
+               echo "\$Id: Foreign Commit With Spaces \$"
+               echo "\$Id: NoTerminatingSymbolAtEOF"
        } > expected-output &&
 
        git add expanded-keywords &&
diff --git a/t/t0025-crlf-auto.sh b/t/t0025-crlf-auto.sh
new file mode 100755 (executable)
index 0000000..f5f67a6
--- /dev/null
@@ -0,0 +1,155 @@
+#!/bin/sh
+
+test_description='CRLF conversion'
+
+. ./test-lib.sh
+
+has_cr() {
+       tr '\015' Q <"$1" | grep Q >/dev/null
+}
+
+test_expect_success setup '
+
+       git config core.autocrlf false &&
+
+       for w in Hello world how are you; do echo $w; done >one &&
+       for w in I am very very fine thank you; do echo ${w}Q; done | q_to_cr >two &&
+       for w in Oh here is a QNUL byte how alarming; do echo ${w}; done | q_to_nul >three &&
+       git add . &&
+
+       git commit -m initial &&
+
+       one=`git rev-parse HEAD:one` &&
+       two=`git rev-parse HEAD:two` &&
+       three=`git rev-parse HEAD:three` &&
+
+       echo happy.
+'
+
+test_expect_success 'default settings cause no changes' '
+
+       rm -f .gitattributes tmp one two three &&
+       git read-tree --reset -u HEAD &&
+
+       ! has_cr one &&
+       has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       threediff=`git diff three` &&
+       test -z "$onediff" -a -z "$twodiff" -a -z "$threediff"
+'
+
+test_expect_success 'crlf=true causes a CRLF file to be normalized' '
+
+       # Backwards compatibility check
+       rm -f .gitattributes tmp one two three &&
+       echo "two crlf" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       # Note, "normalized" means that git will normalize it if added
+       has_cr two &&
+       twodiff=`git diff two` &&
+       test -n "$twodiff"
+'
+
+test_expect_success 'text=true causes a CRLF file to be normalized' '
+
+       rm -f .gitattributes tmp one two three &&
+       echo "two text" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       # Note, "normalized" means that git will normalize it if added
+       has_cr two &&
+       twodiff=`git diff two` &&
+       test -n "$twodiff"
+'
+
+test_expect_success 'eol=crlf gives a normalized file CRLFs with autocrlf=false' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf false &&
+       echo "one eol=crlf" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       onediff=`git diff one` &&
+       test -z "$onediff"
+'
+
+test_expect_success 'eol=crlf gives a normalized file CRLFs with autocrlf=input' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf input &&
+       echo "one eol=crlf" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       onediff=`git diff one` &&
+       test -z "$onediff"
+'
+
+test_expect_success 'eol=lf gives a normalized file LFs with autocrlf=true' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf true &&
+       echo "one eol=lf" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       ! has_cr one &&
+       onediff=`git diff one` &&
+       test -z "$onediff"
+'
+
+test_expect_success 'autocrlf=true does not normalize CRLF files' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf true &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       threediff=`git diff three` &&
+       test -z "$onediff" -a -z "$twodiff" -a -z "$threediff"
+'
+
+test_expect_success 'text=auto, autocrlf=true _does_ normalize CRLF files' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf true &&
+       echo "* text=auto" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       threediff=`git diff three` &&
+       test -z "$onediff" -a -n "$twodiff" -a -z "$threediff"
+'
+
+test_expect_success 'text=auto, autocrlf=true does not normalize binary files' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf true &&
+       echo "* text=auto" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       ! has_cr three &&
+       threediff=`git diff three` &&
+       test -z "$threediff"
+'
+
+test_expect_success 'eol=crlf _does_ normalize binary files' '
+
+       rm -f .gitattributes tmp one two three &&
+       echo "three eol=crlf" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr three &&
+       threediff=`git diff three` &&
+       test -z "$threediff"
+'
+
+test_done
diff --git a/t/t0026-eol-config.sh b/t/t0026-eol-config.sh
new file mode 100755 (executable)
index 0000000..f37ac8f
--- /dev/null
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+test_description='CRLF conversion'
+
+. ./test-lib.sh
+
+has_cr() {
+       tr '\015' Q <"$1" | grep Q >/dev/null
+}
+
+test_expect_success setup '
+
+       git config core.autocrlf false &&
+
+       echo "one text" > .gitattributes
+
+       for w in Hello world how are you; do echo $w; done >one &&
+       for w in I am very very fine thank you; do echo $w; done >two &&
+       git add . &&
+
+       git commit -m initial &&
+
+       one=`git rev-parse HEAD:one` &&
+       two=`git rev-parse HEAD:two` &&
+
+       echo happy.
+'
+
+test_expect_success 'eol=lf puts LFs in normalized file' '
+
+       rm -f .gitattributes tmp one two &&
+       git config core.eol lf &&
+       git read-tree --reset -u HEAD &&
+
+       ! has_cr one &&
+       ! has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       test -z "$onediff" -a -z "$twodiff"
+'
+
+test_expect_success 'eol=crlf puts CRLFs in normalized file' '
+
+       rm -f .gitattributes tmp one two &&
+       git config core.eol crlf &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       ! has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       test -z "$onediff" -a -z "$twodiff"
+'
+
+test_expect_success 'autocrlf=true overrides eol=lf' '
+
+       rm -f .gitattributes tmp one two &&
+       git config core.eol lf &&
+       git config core.autocrlf true &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       test -z "$onediff" -a -z "$twodiff"
+'
+
+test_expect_success 'autocrlf=true overrides unset eol' '
+
+       rm -f .gitattributes tmp one two &&
+       git config --unset-all core.eol &&
+       git config core.autocrlf true &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       test -z "$onediff" -a -z "$twodiff"
+'
+
+test_done
index 3d450ed379fcd1bf480fc75feb0e6ddb8f05e5be..20924506af886c8d0ce28f3fcb2742214d80020f 100755 (executable)
@@ -7,7 +7,7 @@ test_description='our own option parser'
 
 . ./test-lib.sh
 
-cat > expect.err << EOF
+cat > expect << EOF
 usage: test-parse-options <options>
 
     -b, --boolean         get a boolean
@@ -46,10 +46,12 @@ EOF
 
 test_expect_success 'test help' '
        test_must_fail test-parse-options -h > output 2> output.err &&
-       test ! -s output &&
-       test_cmp expect.err output.err
+       test ! -s output.err &&
+       test_cmp expect output
 '
 
+mv expect expect.err
+
 cat > expect << EOF
 boolean: 2
 integer: 1729
index f11f98c3ce7e35f61d06542ce707d61b98079fda..64f05080b65c2b9506d1e34748b47ee721026aef 100755 (executable)
@@ -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
index 49cae3ed524d70ae39114b35a95e7070f1a4e575..759cf12e16bcddc3dab9d6b3ad8abd5590245a94 100755 (executable)
@@ -5,7 +5,9 @@ test_description='git fsck random collection of tests'
 . ./test-lib.sh
 
 test_expect_success setup '
+       git config i18n.commitencoding ISO-8859-1 &&
        test_commit A fileA one &&
+       git config --unset i18n.commitencoding &&
        git checkout HEAD^0 &&
        test_commit B fileB two &&
        git tag -d A B &&
@@ -28,6 +30,12 @@ test_expect_success 'loose objects borrowed from alternate are not missing' '
        )
 '
 
+test_expect_success 'valid objects appear valid' '
+       { git fsck 2>out; true; } &&
+       ! grep error out &&
+       ! grep fatal out
+'
+
 # Corruption tests follow.  Make sure to remove all traces of the
 # specific corruption you test afterwards, lest a later test trip over
 # it.
@@ -57,6 +65,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
index e5040580626f4df6159c929c1ad54f085f03d830..434679585555c660f76f42a82f2ff84df3119f01 100755 (executable)
@@ -3,7 +3,8 @@
 test_description='test git rev-parse --parseopt'
 . ./test-lib.sh
 
-cat > expect.err <<EOF
+cat > expect <<\END_EXPECT
+cat <<\EOF
 usage: some-command [options] <args>...
 
     some-command does foo and bar!
@@ -19,6 +20,7 @@ Extras
     --extra1              line above used to cause a segfault but no longer does
 
 EOF
+END_EXPECT
 
 cat > optionspec << EOF
 some-command [options] <args>...
@@ -38,8 +40,8 @@ extra1    line above used to cause a segfault but no longer does
 EOF
 
 test_expect_success 'test --parseopt help output' '
-       git rev-parse --parseopt -- -h 2> output.err < optionspec
-       test_cmp expect.err output.err
+       git rev-parse --parseopt -- -h > output < optionspec
+       test_cmp expect output
 '
 
 cat > expect <<EOF
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
new file mode 100755 (executable)
index 0000000..be88d4b
--- /dev/null
@@ -0,0 +1,126 @@
+#!/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 must be rejected with -t' '
+       git checkout master &&
+       test_must_fail git checkout --orphan new -t master &&
+       test refs/heads/master = "$(git symbolic-ref HEAD)"
+'
+
+test_expect_success '--orphan ignores branch.autosetupmerge' '
+       git checkout master &&
+       git config branch.autosetupmerge always &&
+       git checkout --orphan gamma &&
+       test -z "$(git config branch.gamma.merge)" &&
+       test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
+       test_must_fail git rev-parse --verify HEAD^
+'
+
+test_expect_success '--orphan makes reflog by default' '
+       git checkout master &&
+       git config --unset core.logAllRefUpdates &&
+       git checkout --orphan delta &&
+       ! test -f .git/logs/refs/heads/delta &&
+       test_must_fail PAGER= git reflog show delta &&
+       git commit -m Delta &&
+       test -f .git/logs/refs/heads/delta &&
+       PAGER= git reflog show delta
+'
+
+test_expect_success '--orphan does not make reflog when core.logAllRefUpdates = false' '
+       git checkout master &&
+       git config core.logAllRefUpdates false &&
+       git checkout --orphan epsilon &&
+       ! test -f .git/logs/refs/heads/epsilon &&
+       test_must_fail PAGER= git reflog show epsilon &&
+       git commit -m Epsilon &&
+       ! test -f .git/logs/refs/heads/epsilon &&
+       test_must_fail PAGER= git reflog show epsilon
+'
+
+test_expect_success '--orphan with -l makes reflog when core.logAllRefUpdates = false' '
+       git checkout master &&
+       git checkout -l --orphan zeta &&
+       test -f .git/logs/refs/heads/zeta &&
+       test_must_fail PAGER= git reflog show zeta &&
+       git commit -m Zeta &&
+       PAGER= git reflog show zeta
+'
+
+test_expect_success 'giving up --orphan not committed when -l and core.logAllRefUpdates = false deletes reflog' '
+       git checkout master &&
+       git checkout -l --orphan eta &&
+       test -f .git/logs/refs/heads/eta &&
+       test_must_fail PAGER= git reflog show eta &&
+       git checkout master &&
+       ! test -f .git/logs/refs/heads/eta &&
+       test_must_fail PAGER= git reflog show eta
+'
+
+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 new 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_done
index 9929f82021deeb20358016b487400b80e27b596f..d541544537d47587abad8d27565cf9197387cfd6 100755 (executable)
@@ -22,6 +22,7 @@ test_expect_success 'setup 1' '
        git branch df-2 &&
        git branch df-3 &&
        git branch remove &&
+       git branch submod &&
 
        echo hello >>a &&
        cp a d/e &&
@@ -236,6 +237,17 @@ test_expect_success 'setup 6' '
        test_cmp expected actual
 '
 
+test_expect_success 'setup 7' '
+
+       git checkout submod &&
+       git rm d/e &&
+       test_tick &&
+       git commit -m "remove d/e" &&
+       git update-index --add --cacheinfo 160000 $c1 d &&
+       test_tick &&
+       git commit -m "make d/ a submodule"
+'
+
 test_expect_success 'merge-recursive simple' '
 
        rm -fr [abcd] &&
@@ -551,4 +563,21 @@ test_expect_success 'merge removes empty directories' '
        test_must_fail test -d d
 '
 
+test_expect_failure 'merge-recursive simple w/submodule' '
+
+       git checkout submod &&
+       git merge remove
+'
+
+test_expect_failure 'merge-recursive simple w/submodule result' '
+
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o5 0      a"
+               echo "100644 $o0 0      c"
+               echo "160000 $c1 0      d"
+       ) >expected &&
+       test_cmp expected actual
+'
+
 test_done
index e0b760513cfc065126cecd6e273180826c8f6bc9..859b99abf1cc62c966322fcfc552c0a7eb7356df 100755 (executable)
@@ -43,7 +43,7 @@ test_expect_success \
      git branch -l d/e/f &&
         test -f .git/refs/heads/d/e/f &&
         test -f .git/logs/refs/heads/d/e/f &&
-        diff expect .git/logs/refs/heads/d/e/f'
+        test_cmp expect .git/logs/refs/heads/d/e/f'
 
 test_expect_success \
     'git branch -d d/e/f should delete a branch and a log' \
@@ -222,7 +222,31 @@ test_expect_success \
      git checkout -b g/h/i -l master &&
         test -f .git/refs/heads/g/h/i &&
         test -f .git/logs/refs/heads/g/h/i &&
-        diff expect .git/logs/refs/heads/g/h/i'
+        test_cmp expect .git/logs/refs/heads/g/h/i'
+
+test_expect_success 'checkout -b makes reflog by default' '
+       git checkout master &&
+       git config --unset core.logAllRefUpdates &&
+       git checkout -b alpha &&
+       test -f .git/logs/refs/heads/alpha &&
+       PAGER= git reflog show alpha
+'
+
+test_expect_success 'checkout -b does not make reflog when core.logAllRefUpdates = false' '
+       git checkout master &&
+       git config core.logAllRefUpdates false &&
+       git checkout -b beta &&
+       ! test -f .git/logs/refs/heads/beta &&
+       test_must_fail PAGER= git reflog show beta
+'
+
+test_expect_success 'checkout -b with -l makes reflog when core.logAllRefUpdates = false' '
+       git checkout master &&
+       git checkout -lb gamma &&
+       git config --unset core.logAllRefUpdates &&
+       test -f .git/logs/refs/heads/gamma &&
+       PAGER= git reflog show gamma
+'
 
 test_expect_success 'avoid ambiguous track' '
        git config branch.autosetupmerge true &&
index 413019acafc98646a61e77960527f042a8f96ac6..525174013c4c33eab5bdbde8831d43f1ddbaeaae 100755 (executable)
@@ -28,7 +28,7 @@ test_expect_success \
      SHA1=`cat .git/refs/heads/a` &&
      echo "$SHA1 refs/heads/a" >expect &&
      git show-ref a >result &&
-     diff expect result'
+     test_cmp expect result'
 
 test_expect_success \
     'see if a branch still exists when packed' \
@@ -37,7 +37,7 @@ test_expect_success \
      rm -f .git/refs/heads/b &&
      echo "$SHA1 refs/heads/b" >expect &&
      git show-ref b >result &&
-     diff expect result'
+     test_cmp expect result'
 
 test_expect_success 'git branch c/d should barf if branch c exists' '
      git branch c &&
@@ -52,7 +52,7 @@ test_expect_success \
      git pack-refs --all --prune &&
      echo "$SHA1 refs/heads/e" >expect &&
      git show-ref e >result &&
-     diff expect result'
+     test_cmp expect result'
 
 test_expect_success 'see if git pack-refs --prune remove ref files' '
      git branch f &&
@@ -109,7 +109,7 @@ test_expect_success 'pack, prune and repack' '
        git show-ref >all-of-them &&
        git pack-refs &&
        git show-ref >again &&
-       diff all-of-them again
+       test_cmp all-of-them again
 '
 
 test_done
index a0ed0353e69dd6dbc96f1a8e3c9251757b4f408f..b4554041b49d1ab7b56430f2e85b76a23d360fb4 100755 (executable)
@@ -60,7 +60,7 @@ test_expect_success 'verify commits and notes' '
 
 test_expect_success 'remove some commits' '
 
-       git reset --hard HEAD~2 &&
+       git reset --hard HEAD~1 &&
        git reflog expire --expire=now HEAD &&
        git gc --prune=now
 '
@@ -68,7 +68,7 @@ test_expect_success 'remove some commits' '
 test_expect_success 'verify that commits are gone' '
 
        ! git cat-file -p 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
-       git cat-file -p 08341ad9e94faa089d60fd3f523affb25c6da189 &&
+       git cat-file -p 08341ad9e94faa089d60fd3f523affb25c6da189 &&
        git cat-file -p ab5f302035f2e7aaf04265f08b42034c23256e1f
 '
 
@@ -79,11 +79,55 @@ test_expect_success 'verify that notes are still present' '
        git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f
 '
 
+test_expect_success 'prune -n does not remove notes' '
+
+       git notes list > expect &&
+       git notes prune -n &&
+       git notes list > actual &&
+       test_cmp expect actual
+'
+
+cat > expect <<EOF
+5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29
+EOF
+
+test_expect_success 'prune -n lists prunable notes' '
+
+
+       git notes prune -n > actual &&
+       test_cmp expect actual
+'
+
+
 test_expect_success 'prune notes' '
 
        git notes prune
 '
 
+test_expect_success 'verify that notes are gone' '
+
+       ! git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
+       git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&
+       git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f
+'
+
+test_expect_success 'remove some commits' '
+
+       git reset --hard HEAD~1 &&
+       git reflog expire --expire=now HEAD &&
+       git gc --prune=now
+'
+
+cat > expect <<EOF
+08341ad9e94faa089d60fd3f523affb25c6da189
+EOF
+
+test_expect_success 'prune -v notes' '
+
+       git notes prune -v > actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'verify that notes are gone' '
 
        ! git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
index dbf7dfba9b55e906d44a35d7b11ca2ceaad7e6f5..e5691bc5edc8d139c7238b59b0690834ba9e85f4 100755 (executable)
@@ -126,9 +126,20 @@ test_expect_success 'Show verbose error when HEAD could not be detached' '
      test_must_fail git rebase topic 2> output.err > output.out &&
      grep "Untracked working tree file .B. would be overwritten" output.err
 '
+rm -f B
+
+test_expect_success 'dump usage when upstream arg is missing' '
+     git checkout -b usage topic &&
+     test_must_fail git rebase 2>error1 &&
+     grep "[Uu]sage" error1 &&
+     test_must_fail git rebase --abort 2>error2 &&
+     grep "No rebase in progress" error2 &&
+     test_must_fail git rebase --onto master 2>error3 &&
+     grep "[Uu]sage" error3 &&
+     ! grep "can.t shift" error3
+'
 
 test_expect_success 'rebase -q is quiet' '
-     rm B &&
      git checkout -b quiet topic &&
      git rebase -q master > output.out 2>&1 &&
      test ! -s output.out
index dadbbc2a9f9b70a4e33f5aa825b8f9fe14eec124..f038f34b7c03b419b9341770a6924767a0b8e8d7 100755 (executable)
@@ -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) )" : "+ [^ ]* + .*"
index 8fe14ccc5444df95960bee88e5b79eb68156572b..62e208aadd592ddaa5c519cf328e636d948221c0 100755 (executable)
@@ -81,7 +81,7 @@ test_expect_success 'drop top stash' '
        git stash &&
        git stash drop &&
        git stash list > stashlist2 &&
-       diff stashlist1 stashlist2 &&
+       test_cmp stashlist1 stashlist2 &&
        git stash apply &&
        test 3 = $(cat file) &&
        test 1 = $(git show :file) &&
index 18695ce8218a7b383258eeb0bad84b4d4bde45be..73441a516572dcf826582c04f3fe9d3ad0c1a88c 100755 (executable)
@@ -135,7 +135,7 @@ cmp_diff_files_output () {
     # filesystem.
     sed <"$2" >.test-tmp \
        -e '/^:000000 /d;s/'$x40'\( [MCRNDU][0-9]*\)    /'$z40'\1       /' &&
-    diff "$1" .test-tmp
+    test_cmp "$1" .test-tmp
 }
 
 test_expect_success \
index d7e327cc5bc5984546032fb085fb581de5755e11..e12fbea1b5df970f22a1526c4d9ba8fc32a027ba 100755 (executable)
@@ -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'
 
index 7e78851a113918f798d1b9e1b3f59e74e6406e78..935d101fe8d880ff426452ca6f611ce8d30b8bc1 100755 (executable)
@@ -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 &&
index 2e2e103b31332ea2f74de5d5e6e49c00b13dfa8a..6f7548c3a144ecada6642eccee6b0f687baafb2d 100755 (executable)
@@ -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 (executable)
index 0000000..91f8198
--- /dev/null
@@ -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/t4044-diff-index-unique-abbrev.sh b/t/t4044-diff-index-unique-abbrev.sh
new file mode 100755 (executable)
index 0000000..d5ce72b
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description='test unique sha1 abbreviation on "index from..to" line'
+. ./test-lib.sh
+
+cat >expect_initial <<EOF
+100644 blob 51d2738463ea4ca66f8691c91e33ce64b7d41bb1   foo
+EOF
+
+cat >expect_update <<EOF
+100644 blob 51d2738efb4ad8a1e40bed839ab8e116f0a15e47   foo
+EOF
+
+test_expect_success 'setup' '
+       echo 4827 > foo &&
+       git add foo &&
+       git commit -m "initial" &&
+       git cat-file -p HEAD: > actual &&
+       test_cmp expect_initial actual &&
+       echo 11742 > foo &&
+       git commit -a -m "update" &&
+       git cat-file -p HEAD: > actual &&
+       test_cmp expect_update actual
+'
+
+cat >expect <<EOF
+index 51d27384..51d2738e 100644
+EOF
+
+test_expect_success 'diff does not produce ambiguous index line' '
+       git diff HEAD^..HEAD | grep index > actual &&
+       test_cmp expect actual
+'
+
+test_done
index 451d75e3fb2ea3efb17825f6af92fa3213c620aa..8a676a5dcd113418c2bd4ea4aa885fddd5951a3a 100755 (executable)
@@ -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,12 +40,11 @@ apply_patch () {
 }
 
 test_fix () {
-
        # fix should not barf
        apply_patch --whitespace=fix || return 1
 
        # find touched lines
-       diff file target | sed -n -e "s/^> //p" >fixed
+       $DIFF file target | sed -n -e "s/^> //p" >fixed
 
        # the changed lines are all expeced to change
        fixed_cnt=$(wc -l <fixed)
@@ -85,14 +85,14 @@ test_expect_success setup '
 test_expect_success 'whitespace=nowarn, default rule' '
 
        apply_patch --whitespace=nowarn &&
-       diff file target
+       test_cmp file target
 
 '
 
 test_expect_success 'whitespace=warn, default rule' '
 
        apply_patch --whitespace=warn &&
-       diff file target
+       test_cmp file target
 
 '
 
@@ -108,7 +108,7 @@ test_expect_success 'whitespace=error-all, no rule' '
 
        git config core.whitespace -trailing,-space-before,-indent &&
        apply_patch --whitespace=error-all &&
-       diff file target
+       test_cmp file target
 
 '
 
@@ -117,7 +117,7 @@ test_expect_success 'whitespace=error-all, no rule (attribute)' '
        git config --unset core.whitespace &&
        echo "target -whitespace" >.gitattributes &&
        apply_patch --whitespace=error-all &&
-       diff file target
+       test_cmp file target
 
 '
 
@@ -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
index 3a8202ea9311b1c90158ad0d115dda985060fdeb..77200c0b2d969d621623f3be8621a3f9925d50a5 100755 (executable)
@@ -27,7 +27,7 @@ test_expect_success 'apply same filename with independent changes' '
        cp same_fn same_fn2 &&
        git reset --hard &&
        git apply patch0 &&
-       diff same_fn same_fn2
+       test_cmp same_fn same_fn2
 '
 
 test_expect_success 'apply same filename with overlapping changes' '
@@ -40,7 +40,7 @@ test_expect_success 'apply same filename with overlapping changes' '
        cp same_fn same_fn2 &&
        git reset --hard &&
        git apply patch0 &&
-       diff same_fn same_fn2
+       test_cmp same_fn same_fn2
 '
 
 test_expect_success 'apply same new filename after rename' '
@@ -54,7 +54,7 @@ test_expect_success 'apply same new filename after rename' '
        cp new_fn new_fn2 &&
        git reset --hard &&
        git apply --index patch1 &&
-       diff new_fn new_fn2
+       test_cmp new_fn new_fn2
 '
 
 test_expect_success 'apply same old filename after rename -- should fail.' '
index 1dc224f6fbf074434728d326b8a36160e9032192..2230e606edea0c4f3dcd0b6b99298074d5341e4a 100755 (executable)
@@ -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
 
index 04f7bae8503f7605f1403f55d0bf4d9cd146913d..68e2652814c6a52265407b0fdfb70162eb634d53 100755 (executable)
@@ -18,6 +18,11 @@ test_expect_success 'patch-id output is well-formed' '
        grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output
 '
 
+calc_patch_id () {
+       git patch-id |
+               sed "s# .*##" > patch-id_"$1"
+}
+
 get_patch_id () {
        git log -p -1 "$1" | git patch-id |
                sed "s# .*##" > patch-id_"$1"
@@ -35,4 +40,27 @@ test_expect_success 'patch-id detects inequality' '
        ! test_cmp patch-id_master patch-id_notsame
 '
 
+test_expect_success 'patch-id supports git-format-patch output' '
+       get_patch_id master &&
+       git checkout same &&
+       git format-patch -1 --stdout | calc_patch_id same &&
+       test_cmp patch-id_master patch-id_same &&
+       set `git format-patch -1 --stdout | git patch-id` &&
+       test "$2" = `git rev-parse HEAD`
+'
+
+test_expect_success 'whitespace is irrelevant in footer' '
+       get_patch_id master &&
+       git checkout same &&
+       git format-patch -1 --stdout | sed "s/ \$//" | calc_patch_id same &&
+       test_cmp patch-id_master patch-id_same
+'
+
+test_expect_success 'patch-id supports git-format-patch MIME output' '
+       get_patch_id master &&
+       git checkout same &&
+       git format-patch -1 --attach --stdout | calc_patch_id same &&
+       test_cmp patch-id_master patch-id_same
+'
+
 test_done
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
new file mode 100755 (executable)
index 0000000..cb9f2bd
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# Copyright (c) 2010, Will Palmer
+#
+
+test_description='Test pretty formats'
+. ./test-lib.sh
+
+test_expect_success 'set up basic repos' '
+       >foo &&
+       >bar &&
+       git add foo &&
+       test_tick &&
+       git commit -m initial &&
+       git add bar &&
+       test_tick &&
+       git commit -m "add bar"
+'
+
+test_expect_success 'alias builtin format' '
+       git log --pretty=oneline >expected &&
+       git config pretty.test-alias oneline &&
+       git log --pretty=test-alias >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias masking builtin format' '
+       git log --pretty=oneline >expected &&
+       git config pretty.oneline "%H" &&
+       git log --pretty=oneline >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias user-defined format' '
+       git log --pretty="format:%h" >expected &&
+       git config pretty.test-alias "format:%h" &&
+       git log --pretty=test-alias >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias user-defined tformat' '
+       git log --pretty="tformat:%h" >expected &&
+       git config pretty.test-alias "tformat:%h" &&
+       git log --pretty=test-alias >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias non-existant format' '
+       git config pretty.test-alias format-that-will-never-exist &&
+       test_must_fail git log --pretty=test-alias
+'
+
+test_expect_success 'alias of an alias' '
+       git log --pretty="tformat:%h" >expected &&
+       git config pretty.test-foo "tformat:%h" &&
+       git config pretty.test-bar test-foo &&
+       git log --pretty=test-bar >actual && test_cmp expected actual
+'
+
+test_expect_success 'alias masking an alias' '
+       git log --pretty=format:"Two %H" >expected &&
+       git config pretty.duplicate "format:One %H" &&
+       git config --add pretty.duplicate "format:Two %H" &&
+       git log --pretty=duplicate >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias loop' '
+       git config pretty.test-foo test-bar &&
+       git config pretty.test-bar test-foo &&
+       test_must_fail git log --pretty=test-foo
+'
+
+test_done
diff --git a/t/t4206-log-follow-harder-copies.sh b/t/t4206-log-follow-harder-copies.sh
new file mode 100755 (executable)
index 0000000..ad29e65
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Bo Yang
+#
+
+test_description='Test --follow should always find copies hard in git log.
+
+'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+echo >path0 'Line 1
+Line 2
+Line 3
+'
+
+test_expect_success \
+    'add a file path0 and commit.' \
+    'git add path0 &&
+     git commit -m "Add path0"'
+
+echo >path0 'New line 1
+New line 2
+New line 3
+'
+test_expect_success \
+    'Change path0.' \
+    'git add path0 &&
+     git commit -m "Change path0"'
+
+cat <path0 >path1
+test_expect_success \
+    'copy path0 to path1.' \
+    'git add path1 &&
+     git commit -m "Copy path1 from path0"'
+
+test_expect_success \
+    'find the copy path0 -> path1 harder' \
+    'git log --follow --name-status --pretty="format:%s"  path1 > current'
+
+cat >expected <<\EOF
+Copy path1 from path0
+C100   path0   path1
+
+Change path0
+M      path0
+
+Add path0
+A      path0
+EOF
+
+test_expect_success \
+    'validate the output.' \
+    'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh
new file mode 100755 (executable)
index 0000000..9cc0a42
--- /dev/null
@@ -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
index 7649b810b1469724ff738fb0bf8b23ea61b37bda..bbb9c1251d97bdaf5bca8f8260e481c3cac56dea 100755 (executable)
@@ -147,7 +147,7 @@ test_expect_success \
            git cat-file $t $object || return 1
         done <obj-list
     } >current &&
-    diff expect current'
+    test_cmp expect current'
 
 test_expect_success \
     'use packed deltified (REF_DELTA) objects' \
@@ -162,7 +162,7 @@ test_expect_success \
            git cat-file $t $object || return 1
         done <obj-list
     } >current &&
-    diff expect current'
+    test_cmp expect current'
 
 test_expect_success \
     'use packed deltified (OFS_DELTA) objects' \
@@ -177,7 +177,7 @@ test_expect_success \
            git cat-file $t $object || return 1
         done <obj-list
     } >current &&
-    diff expect current'
+    test_cmp expect current'
 
 unset GIT_OBJECT_DIRECTORY
 
index 230c0cd784b317856749609cf64a5067e35a6965..4c498b1902e4b0c74ea8d6bfc039d583bcc4364e 100755 (executable)
@@ -320,6 +320,69 @@ test_expect_success 'add alt && prune' '
         git rev-parse --verify refs/remotes/origin/side2)
 '
 
+cat >test/expect <<\EOF
+some-tag
+EOF
+
+test_expect_success 'add with reachable tags (default)' '
+       (cd one &&
+        >foobar &&
+        git add foobar &&
+        git commit -m "Foobar" &&
+        git tag -a -m "Foobar tag" foobar-tag &&
+        git reset --hard HEAD~1 &&
+        git tag -a -m "Some tag" some-tag) &&
+       (mkdir add-tags &&
+        cd add-tags &&
+        git init &&
+        git remote add -f origin ../one &&
+        git tag -l some-tag >../test/output &&
+        git tag -l foobar-tag >>../test/output &&
+        test_must_fail git config remote.origin.tagopt) &&
+       test_cmp test/expect test/output
+'
+
+cat >test/expect <<\EOF
+some-tag
+foobar-tag
+--tags
+EOF
+
+test_expect_success 'add --tags' '
+       (rm -rf add-tags &&
+        mkdir add-tags &&
+        cd add-tags &&
+        git init &&
+        git remote add -f --tags origin ../one &&
+        git tag -l some-tag >../test/output &&
+        git tag -l foobar-tag >>../test/output &&
+        git config remote.origin.tagopt >>../test/output) &&
+       test_cmp test/expect test/output
+'
+
+cat >test/expect <<\EOF
+--no-tags
+EOF
+
+test_expect_success 'add --no-tags' '
+       (rm -rf add-tags &&
+        mkdir add-no-tags &&
+        cd add-no-tags &&
+        git init &&
+        git remote add -f --no-tags origin ../one &&
+        git tag -l some-tag >../test/output &&
+        git tag -l foobar-tag >../test/output &&
+        git config remote.origin.tagopt >>../test/output) &&
+       (cd one &&
+        git tag -d some-tag foobar-tag) &&
+       test_cmp test/expect test/output
+'
+
+test_expect_success 'reject --no-no-tags' '
+       (cd add-no-tags &&
+        test_must_fail git remote add -f --no-no-tags neworigin ../one)
+'
+
 cat > one/expect << EOF
   apis/master
   apis/side
@@ -534,6 +597,94 @@ test_expect_success 'show empty remote' '
        )
 '
 
+test_expect_success 'remote set-branches requires a remote' '
+       test_must_fail git remote set-branches &&
+       test_must_fail git remote set-branches --add
+'
+
+test_expect_success 'remote set-branches' '
+       echo "+refs/heads/*:refs/remotes/scratch/*" >expect.initial &&
+       sort <<-\EOF >expect.add &&
+       +refs/heads/*:refs/remotes/scratch/*
+       +refs/heads/other:refs/remotes/scratch/other
+       EOF
+       sort <<-\EOF >expect.replace &&
+       +refs/heads/maint:refs/remotes/scratch/maint
+       +refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       EOF
+       sort <<-\EOF >expect.add-two &&
+       +refs/heads/maint:refs/remotes/scratch/maint
+       +refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       +refs/heads/pu:refs/remotes/scratch/pu
+       +refs/heads/t/topic:refs/remotes/scratch/t/topic
+       EOF
+       sort <<-\EOF >expect.setup-ffonly &&
+       refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       EOF
+       sort <<-\EOF >expect.respect-ffonly &&
+       refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       +refs/heads/pu:refs/remotes/scratch/pu
+       EOF
+
+       git clone .git/ setbranches &&
+       (
+               cd setbranches &&
+               git remote rename origin scratch &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.initial &&
+
+               git remote set-branches scratch --add other &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.add &&
+
+               git remote set-branches scratch maint master next &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.replace &&
+
+               git remote set-branches --add scratch pu t/topic &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.add-two &&
+
+               git config --unset-all remote.scratch.fetch &&
+               git config remote.scratch.fetch \
+                       refs/heads/master:refs/remotes/scratch/master &&
+               git config --add remote.scratch.fetch \
+                       +refs/heads/next:refs/remotes/scratch/next &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.setup-ffonly &&
+
+               git remote set-branches --add scratch pu &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.respect-ffonly
+       ) &&
+       test_cmp expect.initial actual.initial &&
+       test_cmp expect.add actual.add &&
+       test_cmp expect.replace actual.replace &&
+       test_cmp expect.add-two actual.add-two &&
+       test_cmp expect.setup-ffonly actual.setup-ffonly &&
+       test_cmp expect.respect-ffonly actual.respect-ffonly
+'
+
+test_expect_success 'remote set-branches with --mirror' '
+       echo "+refs/*:refs/*" >expect.initial &&
+       echo "+refs/heads/master:refs/heads/master" >expect.replace &&
+       git clone --mirror .git/ setbranches-mirror &&
+       (
+               cd setbranches-mirror &&
+               git remote rename origin scratch &&
+               git config --get-all remote.scratch.fetch >../actual.initial &&
+
+               git remote set-branches scratch heads/master &&
+               git config --get-all remote.scratch.fetch >../actual.replace
+       ) &&
+       test_cmp expect.initial actual.initial &&
+       test_cmp expect.replace actual.replace
+'
+
 test_expect_success 'new remote' '
        git remote add someremote foo &&
        echo foo >expect &&
index 721821ec92e476ed9a16222bc49b72f2fd9b2c68..4eb10f602fdcba354c6d140ed6b910ee89641708 100755 (executable)
@@ -71,7 +71,7 @@ test_expect_success "fetch test for-merge" '
                echo "$one_in_two       "
        } >expected &&
        cut -f -2 .git/FETCH_HEAD >actual &&
-       diff expected actual'
+       test_cmp expected actual'
 
 test_expect_success 'fetch tags when there is no tags' '
 
index 1dd8eed5bb3cb0f320a8f0780452e52fa7d8da16..d1912351db7da4a7c457fd44895b5ea076c84e7e 100755 (executable)
@@ -49,4 +49,78 @@ 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' '
+
+       URL="$(pwd)/.git" &&
+       echo "From $URL" >exp_err &&
+
+       git remote add origin "$URL" &&
+       git ls-remote 2>actual_err >actual &&
+
+       test_cmp exp_err actual_err &&
+       test_cmp expected.all actual
+
+'
+
+test_expect_success 'suppress "From <url>" with -q' '
+
+       git ls-remote -q 2>actual_err &&
+       test_must_fail test_cmp exp_err actual_err
+
+'
+
+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 &&
+
+       URL="other.git" &&
+       echo "From $URL" >exp_err &&
+
+       git remote add other $URL &&
+       git config branch.master.remote other &&
+
+       git ls-remote 2>actual_err >actual &&
+       test_cmp exp_err actual_err &&
+       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
index 6a37a4d993df3fa4958a719fdfdb82f0dd2de623..b11da79c9cafebb5af572bd8e9f85dfc6f3c3f77 100755 (executable)
@@ -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
+       )
 
 '
 
@@ -530,11 +540,13 @@ test_expect_success 'push does not update local refs on failure' '
        mkdir testrepo/.git/hooks &&
        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" &&
index dd2ee842e020c23b49ed4e2070c4e31cdb7ac055..319e389ed0dbb9c920d8d619cdf94ab52145e9f4 100755 (executable)
@@ -26,7 +26,7 @@ cd "$D"
 test_expect_success 'checking the results' '
        test -f file &&
        test -f cloned/file &&
-       diff file cloned/file
+       test_cmp file cloned/file
 '
 
 test_expect_success 'pulling into void using master:master' '
index a696b8791b7caa44ae2bd16d6970a791f3a28d3d..044603c26ed62e3ddf03ebb2542f783e4dd7d9ff 100755 (executable)
@@ -32,9 +32,9 @@ test_expect_success 'fsck fails' '
 
 test_expect_success 'upload-pack fails due to error in pack-objects packing' '
 
-       ! echo "0032want $(git rev-parse HEAD)
-00000009done
-0000" | git upload-pack . > /dev/null 2> output.err &&
+       printf "0032want %s\n00000009done\n0000" \
+               $(git rev-parse HEAD) >input &&
+       test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
        grep "unable to read" output.err &&
        grep "pack-objects died" output.err
 '
@@ -51,9 +51,9 @@ test_expect_success 'fsck fails' '
 '
 test_expect_success 'upload-pack fails due to error in rev-list' '
 
-       ! echo "0032want $(git rev-parse HEAD)
-0034shallow $(git rev-parse HEAD^)00000009done
-0000" | git upload-pack . > /dev/null 2> output.err &&
+       printf "0032want %s\n0034shallow %s00000009done\n0000" \
+               $(git rev-parse HEAD) $(git rev-parse HEAD^) >input &&
+       test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
        # pack-objects survived
        grep "Total.*, reused" output.err &&
        # but there was an error, which must have been in rev-list
@@ -62,9 +62,9 @@ test_expect_success 'upload-pack fails due to error in rev-list' '
 
 test_expect_success 'upload-pack fails due to error in pack-objects enumeration' '
 
-       ! echo "0032want $(git rev-parse HEAD)
-00000009done
-0000" | git upload-pack . > /dev/null 2> output.err &&
+       printf "0032want %s\n00000009done\n0000" \
+               $(git rev-parse HEAD) >input &&
+       test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
        grep "bad tree object" output.err &&
        grep "pack-objects died" output.err
 '
index 678cee502de54e5a9c18a43f114446d3392ec7f3..8abb71afcd4d7389260baa6f82ecb9b53bb9524c 100755 (executable)
@@ -176,4 +176,16 @@ test_expect_success 'clone respects global branch.autosetuprebase' '
        )
 '
 
+test_expect_success 'respect url-encoding of file://' '
+       git init x+y &&
+       test_must_fail git clone "file://$PWD/x+y" xy-url &&
+       git clone "file://$PWD/x%2By" xy-url
+'
+
+test_expect_success 'do not respect url-encoding of non-url path' '
+       git init x+y &&
+       test_must_fail git clone x%2By xy-regular &&
+       git clone x+y xy-regular
+'
+
 test_done
index 1c109160690d273451f7a089be42e45f36a3b5bb..895f5595aee9341276e79497b9c4a8736c78e5e7 100755 (executable)
@@ -48,7 +48,7 @@ test_expect_success 'that reference gets used' \
 'cd C &&
 echo "0 objects, 0 kilobytes" > expected &&
 git count-objects > current &&
-diff expected current'
+test_cmp expected current'
 
 cd "$base_dir"
 
@@ -75,7 +75,7 @@ cd "$base_dir"
 test_expect_success 'that reference gets used' \
 'cd D && echo "0 objects, 0 kilobytes" > expected &&
 git count-objects > current &&
-diff expected current'
+test_cmp expected current'
 
 cd "$base_dir"
 
@@ -100,7 +100,7 @@ test_expect_success 'that alternate to origin gets used' \
 'cd C &&
 echo "2 objects" > expected &&
 git count-objects | cut -d, -f1 > current &&
-diff expected current'
+test_cmp expected current'
 
 cd "$base_dir"
 
@@ -116,7 +116,7 @@ test_expect_success 'check objects expected to exist locally' \
 'cd D &&
 echo "5 objects" > expected &&
 git count-objects | cut -d, -f1 > current &&
-diff expected current'
+test_cmp expected current'
 
 cd "$base_dir"
 
diff --git a/t/t5800-remote-helpers.sh b/t/t5800-remote-helpers.sh
new file mode 100755 (executable)
index 0000000..4ee7b65
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Sverre Rabbelier
+#
+
+test_description='Test remote-helper import and export commands'
+
+. ./test-lib.sh
+
+if test_have_prereq PYTHON && "$PYTHON_PATH" -c '
+import sys
+if sys.hexversion < 0x02040000:
+    sys.exit(1)
+'
+then
+       :
+else
+       say 'skipping git remote-testgit tests: requires Python 2.4 or newer'
+       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
index b2131cdacd93e0b62f4ef8fdc62b6a81c6aef6fc..fc57e7d3fd69c60144ee3fb3f66b252a67369b42 100755 (executable)
@@ -84,7 +84,7 @@ check () {
                git rev-list --parents --pretty=raw $arg |
                sed -n -e 's/^commit //p' >test.actual
        fi
-       diff test.expect test.actual
+       test_cmp test.expect test.actual
 }
 
 for type in basic parents parents-raw
index e8fde5c19c16f3fa2305ed319edb3289f4f758b9..9b77073df880d4597742e20d738273244d2e4a10 100755 (executable)
@@ -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
 \e[31mfoo\e[32mbar\e[34mbaz\e[mxyzzy
index e3f7ae8120aa2a46b25dd3830597cb863a5f5e20..b66544b76d545a396ef068438f3980b3f544efdd 100755 (executable)
@@ -280,7 +280,7 @@ test_expect_success 'updated working tree file should prevent the merge' '
                echo "BAD: should have complained"
                return 1
        }
-       diff M M.saved || {
+       test_cmp M M.saved || {
                echo "BAD: should have left M intact"
                return 1
        }
@@ -301,7 +301,7 @@ test_expect_success 'updated working tree file should prevent the merge' '
                echo "BAD: should have complained"
                return 1
        }
-       diff M M.saved || {
+       test_cmp M M.saved || {
                echo "BAD: should have left M intact"
                return 1
        }
index 8052c86ad3516505765ab214f4801940c8cc1684..7dc8a510c7f33d048cd3268424298fdda2f8c2bb 100755 (executable)
@@ -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)"
 '
index e249c3ed4176b4934ae57541ab8b671801968eae..8a6322765c965bfc3a9158cc14312fdf03295090 100755 (executable)
@@ -60,7 +60,7 @@ do
                        echo ${HC}file:5:foo_mmap bar mmap baz
                } >expected &&
                git grep -n -w -e mmap $H >actual &&
-               diff expected actual
+               test_cmp expected actual
        '
 
        test_expect_success "grep -w $L (w)" '
@@ -74,7 +74,7 @@ do
                        echo ${HC}x:1:x x xx x
                } >expected &&
                git grep -n -w -e "x xx* x" $H >actual &&
-               diff expected actual
+               test_cmp expected actual
        '
 
        test_expect_success "grep -w $L (y-1)" '
@@ -82,7 +82,7 @@ do
                        echo ${HC}y:1:y yy
                } >expected &&
                git grep -n -w -e "^y" $H >actual &&
-               diff expected actual
+               test_cmp expected actual
        '
 
        test_expect_success "grep -w $L (y-2)" '
@@ -93,7 +93,7 @@ do
                        cat actual
                        false
                else
-                       diff expected actual
+                       test_cmp expected actual
                fi
        '
 
@@ -105,14 +105,14 @@ do
                        cat actual
                        false
                else
-                       diff expected actual
+                       test_cmp expected actual
                fi
        '
 
        test_expect_success "grep $L (t-1)" '
                echo "${HC}t/t:1:test" >expected &&
                git grep -n -e test $H >actual &&
-               diff expected actual
+               test_cmp expected actual
        '
 
        test_expect_success "grep $L (t-2)" '
@@ -121,7 +121,7 @@ do
                        cd t &&
                        git grep -n -e test $H
                ) >actual &&
-               diff expected actual
+               test_cmp expected actual
        '
 
        test_expect_success "grep $L (t-3)" '
@@ -130,7 +130,7 @@ do
                        cd t &&
                        git grep --full-name -n -e test $H
                ) >actual &&
-               diff expected actual
+               test_cmp expected actual
        '
 
        test_expect_success "grep -c $L (no /dev/null)" '
index 6355698c6b5d133988270b5c5ded3b0209b1bf03..fe60d699a3d6d00a734e9f0bbc502828c5244b7e 100755 (executable)
@@ -38,7 +38,7 @@ test_expect_success setup '
        test_commit "$msg" &&
        echo "$msg" >expect &&
        git show -s --format=%s > actual &&
-       diff actual expect
+       test_cmp actual expect
 
 '
 
@@ -85,7 +85,7 @@ do
                git --exec-path=. commit --amend &&
                git show -s --pretty=oneline |
                sed -e "s/^[0-9a-f]* //" >actual &&
-               diff actual expect
+               test_cmp actual expect
        '
 done
 
@@ -107,7 +107,7 @@ do
                git --exec-path=. commit --amend &&
                git show -s --pretty=oneline |
                sed -e "s/^[0-9a-f]* //" >actual &&
-               diff actual expect
+               test_cmp actual expect
        '
 done
 
index 0f6b367832ed164a126abc6206e724a7a0e4feb0..9a83241c942f63c7c7b2f5c739164c34c80ab68d 100755 (executable)
@@ -40,7 +40,7 @@ else
 fi
 
 test_expect_success 'setup' '
-       unset GIT_PAGER GIT_PAGER_IN_USE &&
+       unset GIT_PAGER GIT_PAGER_IN_USE;
        test_might_fail git config --unset core.pager &&
 
        PAGER="cat >paginated.out" &&
@@ -159,7 +159,7 @@ test_expect_success 'color when writing to a file intended for a pager' '
 '
 
 test_expect_success 'determine default pager' '
-       unset PAGER GIT_PAGER &&
+       unset PAGER GIT_PAGER;
        test_might_fail git config --unset core.pager ||
        cleanup_fail &&
 
@@ -173,7 +173,7 @@ then
 fi
 
 test_expect_success SIMPLEPAGER 'default pager is used by default' '
-       unset PAGER GIT_PAGER &&
+       unset PAGER GIT_PAGER;
        test_might_fail git config --unset core.pager &&
        rm -f default_pager_used ||
        cleanup_fail &&
@@ -192,7 +192,7 @@ test_expect_success SIMPLEPAGER 'default pager is used by default' '
 '
 
 test_expect_success TTY 'PAGER overrides default pager' '
-       unset GIT_PAGER &&
+       unset GIT_PAGER;
        test_might_fail git config --unset core.pager &&
        rm -f PAGER_used ||
        cleanup_fail &&
@@ -204,7 +204,7 @@ test_expect_success TTY 'PAGER overrides default pager' '
 '
 
 test_expect_success TTY 'core.pager overrides PAGER' '
-       unset GIT_PAGER &&
+       unset GIT_PAGER;
        rm -f core.pager_used ||
        cleanup_fail &&
 
diff --git a/t/t7008-grep-binary.sh b/t/t7008-grep-binary.sh
new file mode 100755 (executable)
index 0000000..eb8ca88
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+test_description='git grep in binary files'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' "
+       printf 'binary\000file\n' >a &&
+       git add a &&
+       git commit -m.
+"
+
+test_expect_success 'git grep ina a' '
+       echo Binary file a matches >expect &&
+       git grep ina a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -ah ina a' '
+       git grep -ah ina a >actual &&
+       test_cmp a actual
+'
+
+test_expect_success 'git grep -I ina a' '
+       : >expect &&
+       test_must_fail git grep -I ina a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -c ina a' '
+       echo a:1 >expect &&
+       git grep -c ina a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -l ina a' '
+       echo a >expect &&
+       git grep -l ina a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -L bar a' '
+       echo a >expect &&
+       git grep -L bar a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -q ina a' '
+       : >expect &&
+       git grep -q ina a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -F ile a' '
+       git grep -F ile a
+'
+
+test_expect_success 'git grep -Fi iLE a' '
+       git grep -Fi iLE a
+'
+
+# This test actually passes on platforms where regexec() supports the
+# flag REG_STARTEND.
+test_expect_failure 'git grep ile a' '
+       git grep ile a
+'
+
+test_expect_failure 'git grep .fi a' '
+       git grep .fi a
+'
+
+test_expect_success 'git grep -F y<NUL>f a' "
+       printf 'y\000f' >f &&
+       git grep -f f -F a
+"
+
+test_expect_success 'git grep -F y<NUL>x a' "
+       printf 'y\000x' >f &&
+       test_must_fail git grep -f f -F a
+"
+
+test_expect_success 'git grep -Fi Y<NUL>f a' "
+       printf 'Y\000f' >f &&
+       git grep -f f -Fi a
+"
+
+test_expect_failure 'git grep -Fi Y<NUL>x a' "
+       printf 'Y\000x' >f &&
+       test_must_fail git grep -f f -Fi a
+"
+
+test_expect_success 'git grep y<NUL>f a' "
+       printf 'y\000f' >f &&
+       git grep -f f a
+"
+
+test_expect_failure 'git grep y<NUL>x a' "
+       printf 'y\000x' >f &&
+       test_must_fail git grep -f f a
+"
+
+test_done
index 1a4dc5f89353df7d7bda4bea539ee5bd7a3b9bae..97ff074da768cbf3418f22b366ff935d82915f85 100755 (executable)
@@ -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' '
index 2a527750ce4a81bc168b8ad3d8b09174f487b142..db9365b6454b18eb07f5c0798e13e18d696498c6 100755 (executable)
@@ -59,11 +59,13 @@ test_expect_success 'setup a submodule tree' '
 sub1sha1=$(cd super/sub1 && git rev-parse HEAD)
 sub3sha1=$(cd super/sub3 && git rev-parse HEAD)
 
+pwd=$(pwd)
+
 cat > expect <<EOF
 Entering 'sub1'
-foo1-sub1-$sub1sha1
+$pwd/clone-foo1-sub1-$sub1sha1
 Entering 'sub3'
-foo3-sub3-$sub3sha1
+$pwd/clone-foo3-sub3-$sub3sha1
 EOF
 
 test_expect_success 'test basic "submodule foreach" usage' '
@@ -71,7 +73,9 @@ test_expect_success 'test basic "submodule foreach" usage' '
        (
                cd clone &&
                git submodule update --init -- sub1 sub3 &&
-               git submodule foreach "echo \$name-\$path-\$sha1" > ../actual
+               git submodule foreach "echo \$toplevel-\$name-\$path-\$sha1" > ../actual &&
+               git config foo.bar zar &&
+               git submodule foreach "git config --file \"\$toplevel/.git/config\" foo.bar"
        ) &&
        test_cmp expect actual
 '
index 9f5c3edb0392c321092e56e2bf46fd096f74cf75..aa9c577e9e306bce05cd0fe076c56017c1b97e41 100755 (executable)
@@ -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
index 008d5711b818a315136290c141c830cdf5af80a0..9e081073fbed00da362137446d78a7be7f86350b 100755 (executable)
@@ -107,13 +107,32 @@ A  dir2/added
 ?? untracked
 EOF
 
-test_expect_success 'status -s (2)' '
+test_expect_success 'status -s' '
 
        git status -s >output &&
        test_cmp expect output
 
 '
 
+cat >expect <<\EOF
+## master
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status -s -b' '
+
+       git status -s -b >output &&
+       test_cmp expect output
+
+'
+
 cat >expect <<EOF
 # On branch master
 # Changes to be committed:
@@ -436,6 +455,25 @@ test_expect_success 'status -s with color.status' '
 
 '
 
+cat >expect <<\EOF
+## <GREEN>master<RESET>
+ <RED>M<RESET> dir1/modified
+<GREEN>A<RESET>  dir2/added
+<BLUE>??<RESET> dir1/untracked
+<BLUE>??<RESET> dir2/modified
+<BLUE>??<RESET> dir2/untracked
+<BLUE>??<RESET> expect
+<BLUE>??<RESET> output
+<BLUE>??<RESET> untracked
+EOF
+
+test_expect_success 'status -s -b with color.status' '
+
+       git status -s -b | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
 cat >expect <<\EOF
  M dir1/modified
 A  dir2/added
@@ -469,6 +507,13 @@ test_expect_success 'status --porcelain ignores color.status' '
 git config --unset color.status
 git config --unset color.ui
 
+test_expect_success 'status --porcelain ignores -b' '
+
+       git status --porcelain -b >output &&
+       test_cmp expect output
+
+'
+
 cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
index 57f6d2bae7c63f13ee18e11737dbdcc0c080ab10..cde8390c1b9df6e6537a3538d8da11511b6644ec 100755 (executable)
@@ -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
 '
 
index f4aa0547501a19fe570304e830504ff984f5a9a9..c2f66ff1703c35d5f5b92285e31e8df157e51642 100755 (executable)
@@ -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
        '
index 5babdf26e625933268b911cc6e81f6a448f7f78d..200ab61278643e8b9deb4f624a95378e7e4c5b67 100755 (executable)
@@ -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" &&
index ac52bff0ef540bce48e4ee02a53adbbec3662939..7d7acc30b4a64ba97afe06e03c65ebca4ca7f242 100755 (executable)
@@ -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
index fc3795dc98803bd98e2ebd6f38a249c331038d54..61bcb8fc86bc8b771a4843197a40fa38f350a1ff 100755 (executable)
@@ -63,10 +63,10 @@ test_expect_success \
      check_entries B "newfile2.txt/1.1/" &&
      check_entries C "newfile3.png/1.1/-kb" &&
      check_entries D "newfile4.png/1.1/-kb" &&
-     diff A/newfile1.txt ../A/newfile1.txt &&
-     diff B/newfile2.txt ../B/newfile2.txt &&
-     diff C/newfile3.png ../C/newfile3.png &&
-     diff D/newfile4.png ../D/newfile4.png
+     test_cmp A/newfile1.txt ../A/newfile1.txt &&
+     test_cmp B/newfile2.txt ../B/newfile2.txt &&
+     test_cmp C/newfile3.png ../C/newfile3.png &&
+     test_cmp D/newfile4.png ../D/newfile4.png
      )'
 
 test_expect_success \
@@ -89,10 +89,10 @@ test_expect_success \
      check_entries D "newfile4.png/1.2/-kb" &&
      check_entries E "newfile5.txt/1.1/" &&
      check_entries F "newfile6.png/1.1/-kb" &&
-     diff A/newfile1.txt ../A/newfile1.txt &&
-     diff D/newfile4.png ../D/newfile4.png &&
-     diff E/newfile5.txt ../E/newfile5.txt &&
-     diff F/newfile6.png ../F/newfile6.png
+     test_cmp A/newfile1.txt ../A/newfile1.txt &&
+     test_cmp D/newfile4.png ../D/newfile4.png &&
+     test_cmp E/newfile5.txt ../E/newfile5.txt &&
+     test_cmp F/newfile6.png ../F/newfile6.png
      )'
 
 # Should fail (but only on the git cvsexportcommit stage)
@@ -137,9 +137,9 @@ test_expect_success \
      check_entries D "" &&
      check_entries E "newfile5.txt/1.1/" &&
      check_entries F "newfile6.png/1.1/-kb" &&
-     diff A/newfile1.txt ../A/newfile1.txt &&
-     diff E/newfile5.txt ../E/newfile5.txt &&
-     diff F/newfile6.png ../F/newfile6.png
+     test_cmp A/newfile1.txt ../A/newfile1.txt &&
+     test_cmp E/newfile5.txt ../E/newfile5.txt &&
+     test_cmp F/newfile6.png ../F/newfile6.png
      )'
 
 test_expect_success \
@@ -155,8 +155,8 @@ test_expect_success \
      check_entries D "" &&
      check_entries E "newfile5.txt/1.1/" &&
      check_entries F "newfile6.png/1.1/-kb" &&
-     diff E/newfile5.txt ../E/newfile5.txt &&
-     diff F/newfile6.png ../F/newfile6.png
+     test_cmp E/newfile5.txt ../E/newfile5.txt &&
+     test_cmp F/newfile6.png ../F/newfile6.png
      )'
 
 test_expect_success \
index daef2d6c230fb617395feeff6cf330b74ad57832..86395065cfb8827ba5a999aa97cd5552a72322e3 100755 (executable)
@@ -48,7 +48,9 @@ test_expect_success 'setup' '
   git pull secondroot master &&
   git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
   GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
-  GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log"
+  GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" &&
+  GIT_DIR="$SERVERDIR" git config gitcvs.authdb "$SERVERDIR/auth.db" &&
+  echo cvsuser:cvGVEarMLnhlA > "$SERVERDIR/auth.db"
 '
 
 # note that cvs doesn't accept absolute pathnames
@@ -94,6 +96,14 @@ git
 END VERIFICATION REQUEST
 EOF
 
+cat >login-git-ok <<EOF
+BEGIN VERIFICATION REQUEST
+$SERVERDIR
+cvsuser
+Ah<Z:yZZ30 e
+END VERIFICATION REQUEST
+EOF
+
 test_expect_success 'pserver authentication' \
   'cat request-anonymous | git-cvsserver pserver >log 2>&1 &&
    sed -ne \$p log | grep "^I LOVE YOU\$"'
@@ -107,6 +117,10 @@ test_expect_success 'pserver authentication failure (non-anonymous user)' \
    fi &&
    sed -ne \$p log | grep "^I HATE YOU\$"'
 
+test_expect_success 'pserver authentication success (non-anonymous user with password)' \
+  'cat login-git-ok | git-cvsserver pserver >log 2>&1 &&
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
+
 test_expect_success 'pserver authentication (login)' \
   'cat login-anonymous | git-cvsserver pserver >log 2>&1 &&
    sed -ne \$p log | grep "^I LOVE YOU\$"'
@@ -435,7 +449,7 @@ test_expect_success 'cvs update (-p)' '
     rm -f failures &&
     for i in merge no-lf empty really-empty; do
         GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out
-        diff $i.out ../$i >>failures 2>&1
+       test_cmp $i.out ../$i >>failures 2>&1
     done &&
     test -z "$(cat failures)"
 '
index 63b6b060e64948771319294251838394264c84a9..4f2b9b062b08302aedab42dcdd0a33776559c05e 100755 (executable)
@@ -647,4 +647,33 @@ test_expect_success \
         gitweb_run "p=.git;a=summary"'
 test_debug 'cat gitweb.log'
 
+# ----------------------------------------------------------------------
+# syntax highlighting
+
+cat >>gitweb_config.perl <<\EOF
+$feature{'highlight'}{'override'} = 1;
+EOF
+
+highlight --version >/dev/null 2>&1
+if [ $? -eq 127 ]; then
+       say "Skipping syntax highlighting test, because 'highlight' was not found"
+else
+       test_set_prereq HIGHLIGHT
+fi
+
+test_expect_success HIGHLIGHT \
+       'syntax highlighting (no highlight)' \
+       'git config gitweb.highlight yes &&
+        gitweb_run "p=.git;a=blob;f=file"'
+test_debug 'cat gitweb.log'
+
+test_expect_success HIGHLIGHT \
+       'syntax highlighting (highlighted)' \
+       'git config gitweb.highlight yes &&
+        echo "#!/usr/bin/sh" > test.sh &&
+        git add test.sh &&
+        git commit -m "Add test.sh" &&
+        gitweb_run "p=.git;a=blob;f=test.sh"'
+test_debug 'cat gitweb.log'
+
 test_done
index 9bfa14be7f1c3935013d04318eeaecc68b4ef88c..367f0537cd87c29bfdca15b4744f1aef3ddd8d7e 100644 (file)
@@ -75,7 +75,6 @@ export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
 export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
 export EDITOR
-GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
 
 # Protect ourselves from common misconfiguration to export
 # CDPATH into the environment
@@ -473,6 +472,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.
@@ -737,6 +739,16 @@ export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOB
 
 . ../GIT-BUILD-OPTIONS
 
+if test -z "$GIT_TEST_CMP"
+then
+       if test -n "$GIT_TEST_CMP_USE_COPIED_CONTEXT"
+       then
+               GIT_TEST_CMP="$DIFF -c"
+       else
+               GIT_TEST_CMP="$DIFF -u"
+       fi
+fi
+
 GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
 export GITPERLLIB
 test -d ../templates/blt || {
diff --git a/trace.c b/trace.c
index 4229ae1231d69aedd9f1aa8350989ddbe2bdb845..1e560cb0b977e19beeb095497c8e4f6280b7f0d6 100644 (file)
--- a/trace.c
+++ b/trace.c
 #include "cache.h"
 #include "quote.h"
 
+void do_nothing(size_t unused)
+{
+}
+
 /* Get a trace file descriptor from GIT_TRACE env variable. */
 static int get_trace_fd(int *need_close)
 {
@@ -72,6 +76,7 @@ void trace_printf(const char *fmt, ...)
        if (!fd)
                return;
 
+       set_try_to_free_routine(do_nothing);    /* is never reset */
        strbuf_init(&buf, 64);
        va_start(ap, fmt);
        len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
@@ -103,6 +108,7 @@ void trace_argv_printf(const char **argv, const char *fmt, ...)
        if (!fd)
                return;
 
+       set_try_to_free_routine(do_nothing);    /* is never reset */
        strbuf_init(&buf, 64);
        va_start(ap, fmt);
        len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
index 2638781c5b89024294f3c4c32861b275e339ad1d..0381de536846e6fef958ece86f07bbc5b4cf7e09 100644 (file)
@@ -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)
index 8ce39364a1e58b338bc45a4eb524b637ce3a8881..4dba6f8815a80093a8ac9edc226d99bba1bb6394 100644 (file)
@@ -9,6 +9,7 @@
 #include "dir.h"
 #include "refs.h"
 #include "branch.h"
+#include "url.h"
 
 /* rsync support */
 
@@ -871,54 +872,6 @@ static int is_file(const char *url)
        return S_ISREG(buf.st_mode);
 }
 
-static int isurlschemechar(int first_flag, int ch)
-{
-       /*
-        * The set of valid URL schemes, as per STD66 (RFC3986) is
-        * '[A-Za-z][A-Za-z0-9+.-]*'. But use sightly looser check
-        * of '[A-Za-z0-9][A-Za-z0-9+.-]*' because earlier version
-        * of check used '[A-Za-z0-9]+' so not to break any remote
-        * helpers.
-        */
-       int alphanumeric, special;
-       alphanumeric = ch > 0 && isalnum(ch);
-       special = ch == '+' || ch == '-' || ch == '.';
-       return alphanumeric || (!first_flag && special);
-}
-
-static int is_url(const char *url)
-{
-       const char *url2, *first_slash;
-
-       if (!url)
-               return 0;
-       url2 = url;
-       first_slash = strchr(url, '/');
-
-       /* Input with no slash at all or slash first can't be URL. */
-       if (!first_slash || first_slash == url)
-               return 0;
-       /* Character before must be : and next must be /. */
-       if (first_slash[-1] != ':' || first_slash[1] != '/')
-               return 0;
-       /* There must be something before the :// */
-       if (first_slash == url + 1)
-               return 0;
-       /*
-        * Check all characters up to first slash - 1. Only alphanum
-        * is allowed.
-        */
-       url2 = url;
-       while (url2 < first_slash - 1) {
-               if (!isurlschemechar(url2 == url, (unsigned char)*url2))
-                       return 0;
-               url2++;
-       }
-
-       /* Valid enough. */
-       return 1;
-}
-
 static int external_specification_len(const char *url)
 {
        return strchr(url, ':') - url;
@@ -946,7 +899,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
        if (url) {
                const char *p = url;
 
-               while (isurlschemechar(p == url, *p))
+               while (is_urlschemechar(p == url, *p))
                        p++;
                if (!prefixcmp(p, "::"))
                        helper = xstrndup(url, p - url);
index fe9f52c4796512f40869e511b55a2d97fa84532e..1fb3e94614de4973a242b1f039ee7711a74fad9d 100644 (file)
@@ -346,7 +346,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
 
        diff_setup(&diff_opts);
        DIFF_OPT_SET(&diff_opts, RECURSIVE);
-       diff_opts.detect_rename = DIFF_DETECT_RENAME;
+       DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_opts.single_follow = opt->paths[0];
        diff_opts.break_opt = opt->break_opt;
index 490cd5f6f4779cbff68405722b98e522cbeb3cde..85045fd03fd7ab2a2ad8daf5ec584e010372673b 100644 (file)
@@ -279,9 +279,11 @@ static void add_same_unmerged(struct cache_entry *ce,
 static int unpack_index_entry(struct cache_entry *ce,
                              struct unpack_trees_options *o)
 {
-       struct cache_entry *src[5] = { ce, NULL, };
+       struct cache_entry *src[5] = { NULL };
        int ret;
 
+       src[0] = ce;
+
        mark_ce_used(ce, o);
        if (ce_stage(ce)) {
                if (o->skip_unmerged) {
diff --git a/url.c b/url.c
new file mode 100644 (file)
index 0000000..cd32b92
--- /dev/null
+++ b/url.c
@@ -0,0 +1,118 @@
+#include "cache.h"
+
+int is_urlschemechar(int first_flag, int ch)
+{
+       /*
+        * The set of valid URL schemes, as per STD66 (RFC3986) is
+        * '[A-Za-z][A-Za-z0-9+.-]*'. But use sightly looser check
+        * of '[A-Za-z0-9][A-Za-z0-9+.-]*' because earlier version
+        * of check used '[A-Za-z0-9]+' so not to break any remote
+        * helpers.
+        */
+       int alphanumeric, special;
+       alphanumeric = ch > 0 && isalnum(ch);
+       special = ch == '+' || ch == '-' || ch == '.';
+       return alphanumeric || (!first_flag && special);
+}
+
+int is_url(const char *url)
+{
+       const char *url2, *first_slash;
+
+       if (!url)
+               return 0;
+       url2 = url;
+       first_slash = strchr(url, '/');
+
+       /* Input with no slash at all or slash first can't be URL. */
+       if (!first_slash || first_slash == url)
+               return 0;
+       /* Character before must be : and next must be /. */
+       if (first_slash[-1] != ':' || first_slash[1] != '/')
+               return 0;
+       /* There must be something before the :// */
+       if (first_slash == url + 1)
+               return 0;
+       /*
+        * Check all characters up to first slash - 1. Only alphanum
+        * is allowed.
+        */
+       url2 = url;
+       while (url2 < first_slash - 1) {
+               if (!is_urlschemechar(url2 == url, (unsigned char)*url2))
+                       return 0;
+               url2++;
+       }
+
+       /* Valid enough. */
+       return 1;
+}
+
+static int url_decode_char(const char *q)
+{
+       int i;
+       unsigned char val = 0;
+       for (i = 0; i < 2; i++) {
+               unsigned char c = *q++;
+               val <<= 4;
+               if (c >= '0' && c <= '9')
+                       val += c - '0';
+               else if (c >= 'a' && c <= 'f')
+                       val += c - 'a' + 10;
+               else if (c >= 'A' && c <= 'F')
+                       val += c - 'A' + 10;
+               else
+                       return -1;
+       }
+       return val;
+}
+
+static char *url_decode_internal(const char **query, const char *stop_at)
+{
+       const char *q = *query;
+       struct strbuf out;
+
+       strbuf_init(&out, 16);
+       do {
+               unsigned char c = *q;
+
+               if (!c)
+                       break;
+               if (stop_at && strchr(stop_at, c)) {
+                       q++;
+                       break;
+               }
+
+               if (c == '%') {
+                       int val = url_decode_char(q + 1);
+                       if (0 <= val) {
+                               strbuf_addch(&out, val);
+                               q += 3;
+                               continue;
+                       }
+               }
+
+               if (c == '+')
+                       strbuf_addch(&out, ' ');
+               else
+                       strbuf_addch(&out, c);
+               q++;
+       } while (1);
+       *query = q;
+       return strbuf_detach(&out, NULL);
+}
+
+char *url_decode(const char *url)
+{
+       return url_decode_internal(&url, NULL);
+}
+
+char *url_decode_parameter_name(const char **query)
+{
+       return url_decode_internal(query, "&=");
+}
+
+char *url_decode_parameter_value(const char **query)
+{
+       return url_decode_internal(query, "&");
+}
diff --git a/url.h b/url.h
new file mode 100644 (file)
index 0000000..15817f8
--- /dev/null
+++ b/url.h
@@ -0,0 +1,10 @@
+#ifndef URL_H
+#define URL_H
+
+extern int is_url(const char *url);
+extern int is_urlschemechar(int first_flag, int ch);
+extern char *url_decode(const char *url);
+extern char *url_decode_parameter_name(const char **query);
+extern char *url_decode_parameter_value(const char **query);
+
+#endif /* URL_H */
diff --git a/usage.c b/usage.c
index 79856a2b9f5bc4603252cb10b471a0815416a617..ec4cf53b6bf16f7f9b3528a430891d6c68ef64cc 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -5,7 +5,7 @@
  */
 #include "git-compat-util.h"
 
-static void report(const char *prefix, const char *err, va_list params)
+void vreportf(const char *prefix, const char *err, va_list params)
 {
        char msg[4096];
        vsnprintf(msg, sizeof(msg), err, params);
@@ -14,24 +14,24 @@ static void report(const char *prefix, const char *err, va_list params)
 
 static NORETURN void usage_builtin(const char *err, va_list params)
 {
-       report("usage: ", err, params);
+       vreportf("usage: ", err, params);
        exit(129);
 }
 
 static NORETURN void die_builtin(const char *err, va_list params)
 {
-       report("fatal: ", err, params);
+       vreportf("fatal: ", err, params);
        exit(128);
 }
 
 static void error_builtin(const char *err, va_list params)
 {
-       report("error: ", err, params);
+       vreportf("error: ", err, params);
 }
 
 static void warn_builtin(const char *warn, va_list params)
 {
-       report("warning: ", warn, params);
+       vreportf("warning: ", warn, params);
 }
 
 /* If we are in a dlopen()ed .so write to a global variable would segfault
index 38563daa3c7582ca87565886d74f16d11b292a57..c49cc1b67e1164c561fb20839272bd8b1f7dde6d 100644 (file)
@@ -1,3 +1,4 @@
+#include "cache.h"
 #include "userdiff.h"
 #include "cache.h"
 #include "attr.h"
@@ -169,6 +170,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;
@@ -183,6 +190,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);
 
index c3151594f5c0643fead757accc27bf1093cf4a68..942d5949501027fb5d30e0d59629aab38fd2669a 100644 (file)
@@ -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);
index 58201b6bcb8d362cffd1222bcb8cd77072c34cf5..afb4f6f773a3ed909433778f4b4da3740ded2b18 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -10,9 +10,11 @@ static void try_to_free_builtin(size_t size)
 
 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_t set_try_to_free_routine(try_to_free_t routine)
 {
-       try_to_free_routine = (routine) ? routine : try_to_free_builtin;
+       try_to_free_t old = try_to_free_routine;
+       try_to_free_routine = routine;
+       return old;
 }
 
 char *xstrdup(const char *str)
diff --git a/ws.c b/ws.c
index c0893386e6c8aa3af002e847d228dfc5ef64a9cf..d7b8c33f14195ba7ebe86bdbca2fea8f1b22ad37 100644 (file)
--- a/ws.c
+++ b/ws.c
@@ -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;
 }
index d44486c826ee46d258e88c5013d038243aad2756..9d9cb9556225301c98e8cc98d51bc516881afd73 100644 (file)
@@ -9,6 +9,7 @@
 #include "quote.h"
 #include "run-command.h"
 #include "remote.h"
+#include "refs.h"
 
 static char default_wt_status_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
@@ -17,6 +18,8 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
        GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
        GIT_COLOR_RED,    /* WT_STATUS_UNMERGED */
+       GIT_COLOR_GREEN,  /* WT_STATUS_LOCAL_BRANCH */
+       GIT_COLOR_RED,    /* WT_STATUS_REMOTE_BRANCH */
 };
 
 static const char *color(int slot, struct wt_status *s)
@@ -42,6 +45,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 +100,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 +384,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)
@@ -498,17 +521,18 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt
        struct child_process sm_summary;
        char summary_limit[64];
        char index[PATH_MAX];
-       const char *env[] = { index, NULL };
-       const char *argv[] = {
-               "submodule",
-               "summary",
-               uncommitted ? "--files" : "--cached",
-               "--for-status",
-               "--summary-limit",
-               summary_limit,
-               uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD"),
-               NULL
-       };
+       const char *env[] = { NULL, NULL };
+       const char *argv[8];
+
+       env[0] =        index;
+       argv[0] =       "submodule";
+       argv[1] =       "summary";
+       argv[2] =       uncommitted ? "--files" : "--cached";
+       argv[3] =       "--for-status";
+       argv[4] =       "--summary-limit";
+       argv[5] =       summary_limit;
+       argv[6] =       uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD");
+       argv[7] =       NULL;
 
        sprintf(summary_limit, "%d", s->submodule_summary);
        snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
@@ -523,7 +547,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 +558,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 +650,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%s\n",
                        advice_status_hints
                        ? " (use -u option to show untracked files)" : "");
@@ -715,24 +745,84 @@ 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);
        }
 }
 
-void wt_shortstatus_print(struct wt_status *s, int null_termination)
+static void wt_shortstatus_print_tracking(struct wt_status *s)
+{
+       struct branch *branch;
+       const char *header_color = color(WT_STATUS_HEADER, s);
+       const char *branch_color_local = color(WT_STATUS_LOCAL_BRANCH, s);
+       const char *branch_color_remote = color(WT_STATUS_REMOTE_BRANCH, s);
+
+       const char *base;
+       const char *branch_name;
+       int num_ours, num_theirs;
+
+       color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
+
+       if (!s->branch)
+               return;
+       branch_name = s->branch;
+
+       if (!prefixcmp(branch_name, "refs/heads/"))
+               branch_name += 11;
+       else if (!strcmp(branch_name, "HEAD")) {
+               branch_name = "HEAD (no branch)";
+               branch_color_local = color(WT_STATUS_NOBRANCH, s);
+       }
+
+       branch = branch_get(s->branch + 11);
+       if (s->is_initial)
+               color_fprintf(s->fp, header_color, "Initial commit on ");
+       if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
+               color_fprintf_ln(s->fp, branch_color_local,
+                       "%s", branch_name);
+               return;
+       }
+
+       base = branch->merge[0]->dst;
+       base = shorten_unambiguous_ref(base, 0);
+       color_fprintf(s->fp, branch_color_local, "%s", branch_name);
+       color_fprintf(s->fp, header_color, "...");
+       color_fprintf(s->fp, branch_color_remote, "%s", base);
+
+       color_fprintf(s->fp, header_color, " [");
+       if (!num_ours) {
+               color_fprintf(s->fp, header_color, "behind ");
+               color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
+       } else if (!num_theirs) {
+               color_fprintf(s->fp, header_color, "ahead ");
+               color_fprintf(s->fp, branch_color_local, "%d", num_ours);
+       } else {
+               color_fprintf(s->fp, header_color, "ahead ");
+               color_fprintf(s->fp, branch_color_local, "%d", num_ours);
+               color_fprintf(s->fp, header_color, ", behind ");
+               color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
+       }
+
+       color_fprintf_ln(s->fp, header_color, "]");
+}
+
+void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch)
 {
        int i;
+
+       if (show_branch)
+               wt_shortstatus_print_tracking(s);
+
        for (i = 0; i < s->change.nr; i++) {
                struct wt_status_change_data *d;
                struct string_list_item *it;
@@ -748,7 +838,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, "!!");
        }
 }
 
@@ -757,5 +853,5 @@ void wt_porcelain_print(struct wt_status *s, int null_termination)
        s->use_color = 0;
        s->relative_paths = 0;
        s->prefix = NULL;
-       wt_shortstatus_print(s, null_termination);
+       wt_shortstatus_print(s, null_termination, 0);
 }
index 91206739f318233811dbd594d5ff37aa77c06eef..4cd74c4b32f51dbe575b0a04a894a520df79e9bf 100644 (file)
@@ -12,6 +12,8 @@ enum color_wt_status {
        WT_STATUS_UNTRACKED,
        WT_STATUS_NOBRANCH,
        WT_STATUS_UNMERGED,
+       WT_STATUS_LOCAL_BRANCH,
+       WT_STATUS_REMOTE_BRANCH
 };
 
 enum untracked_status_type {
@@ -41,25 +43,26 @@ 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];
+       char color_palette[WT_STATUS_REMOTE_BRANCH+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);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);
 
-void wt_shortstatus_print(struct wt_status *s, int null_termination);
+void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch);
 void wt_porcelain_print(struct wt_status *s, int null_termination);
 
 #endif /* STATUS_H */