]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'jk/sha1dc'
authorJunio C Hamano <gitster@pobox.com>
Tue, 9 Apr 2019 17:14:26 +0000 (02:14 +0900)
committerJunio C Hamano <gitster@pobox.com>
Tue, 9 Apr 2019 17:14:26 +0000 (02:14 +0900)
Build update for SHA-1 with collision detection.

* jk/sha1dc:
  Makefile: fix unaligned loads in sha1dc with UBSan

197 files changed:
.clang-format
.github/CONTRIBUTING.md
.github/PULL_REQUEST_TEMPLATE.md
.travis.yml
Documentation/RelNotes/2.22.0.txt [new file with mode: 0644]
Documentation/config/branch.txt
Documentation/config/pull.txt
Documentation/config/user.txt
Documentation/diff-format.txt
Documentation/diff-generate-patch.txt
Documentation/diff-options.txt
Documentation/git-add.txt
Documentation/git-branch.txt
Documentation/git-checkout.txt
Documentation/git-clean.txt
Documentation/git-commit-tree.txt
Documentation/git-config.txt
Documentation/git-diff-tree.txt
Documentation/git-fsck.txt
Documentation/git-gc.txt
Documentation/git-rebase.txt
Documentation/git-reset.txt
Documentation/git-submodule.txt
Documentation/gitattributes.txt
Documentation/githooks.txt
Documentation/gitignore.txt
Documentation/merge-options.txt
Documentation/pretty-formats.txt
Documentation/rev-list-options.txt
Documentation/technical/api-trace2.txt [new file with mode: 0644]
Documentation/technical/directory-rename-detection.txt
Documentation/technical/protocol-capabilities.txt
GIT-VERSION-GEN
Makefile
RelNotes
apply.c
attr.c
bisect.c
blame.c
builtin/am.c
builtin/blame.c
builtin/branch.c
builtin/checkout.c
builtin/commit-tree.c
builtin/commit.c
builtin/diff-tree.c
builtin/diff.c
builtin/fetch.c
builtin/fsck.c
builtin/help.c
builtin/init-db.c
builtin/log.c
builtin/merge.c
builtin/pack-objects.c
builtin/pack-redundant.c
builtin/prune-packed.c
builtin/prune.c
builtin/pull.c
builtin/rebase.c
builtin/receive-pack.c
builtin/replace.c
builtin/reset.c
builtin/rev-list.c
builtin/submodule--helper.c
builtin/update-index.c
builtin/worktree.c
cache.h
check-racy.c [deleted file]
ci/run-windows-build.sh [deleted file]
combine-diff.c
common-main.c
compat/bswap.h
compat/mingw.c
compat/mingw.h
compat/win32/trace2_win32_process_info.c [new file with mode: 0644]
config.c
config.mak.dev
config.mak.uname
connect.c
contrib/completion/git-completion.bash
convert.c
date.c
diff-lib.c
diff-no-index.c
diff.c
diff.h
editor.c
entry.c
exec-cmd.c
fetch-pack.c
git-compat-util.h
git-send-email.perl
git-submodule.sh
git.c
gitk-git/po/bg.po
ident.c
line-log.c
list-objects-filter-options.c
log-tree.c
merge-recursive.c
pack-objects.c
pack-objects.h
pager.c
parse-options-cb.c
parse-options.c
parse-options.h
path.c
pkt-line.c
pkt-line.h
pretty.c
reachable.c
read-cache.c
ref-filter.c
refs/files-backend.c
remote-curl.c
repository.c
repository.h
revision.c
revision.h
run-command.c
run-command.h
sequencer.c
setup.c
sh-i18n--envsubst.c
split-index.c
strbuf.c
strbuf.h
sub-process.c
submodule-config.c
submodule.c
t/README
t/helper/test-parse-options.c
t/helper/test-tool.c
t/helper/test-tool.h
t/helper/test-trace2.c [new file with mode: 0644]
t/perf/p5304-prune.sh [new file with mode: 0755]
t/t0001-init.sh
t/t0210-trace2-normal.sh [new file with mode: 0755]
t/t0210/scrub_normal.perl [new file with mode: 0644]
t/t0211-trace2-perf.sh [new file with mode: 0755]
t/t0211/scrub_perf.perl [new file with mode: 0644]
t/t0212-trace2-event.sh [new file with mode: 0755]
t/t0212/parse_events.perl [new file with mode: 0644]
t/t1060-object-corruption.sh
t/t1415-worktree-refs.sh
t/t1450-fsck.sh
t/t1700-split-index.sh
t/t2025-checkout-no-overlay.sh [new file with mode: 0755]
t/t2400-worktree-add.sh [moved from t/t2025-worktree-add.sh with 100% similarity]
t/t2401-worktree-prune.sh [moved from t/t2026-worktree-prune.sh with 100% similarity]
t/t2402-worktree-list.sh [moved from t/t2027-worktree-list.sh with 100% similarity]
t/t2403-worktree-move.sh [moved from t/t2028-worktree-move.sh with 100% similarity]
t/t2404-worktree-config.sh [moved from t/t2029-worktree-config.sh with 100% similarity]
t/t3203-branch-output.sh
t/t3400-rebase.sh
t/t3600-rm.sh
t/t4038-diff-combined.sh
t/t4053-diff-no-index.sh
t/t4150-am.sh
t/t4205-log-pretty-formats.sh
t/t4211-line-log.sh
t/t5304-prune.sh
t/t5323-pack-redundant.sh [new file with mode: 0755]
t/t5702-protocol-v2.sh
t/t6030-bisect-porcelain.sh
t/t7517-per-repo-email.sh
t/t7800-difftool.sh
t/t9902-completion.sh
t/test-lib-functions.sh
t/test-lib.sh
trace2.c [new file with mode: 0644]
trace2.h [new file with mode: 0644]
trace2/tr2_cfg.c [new file with mode: 0644]
trace2/tr2_cfg.h [new file with mode: 0644]
trace2/tr2_cmd_name.c [new file with mode: 0644]
trace2/tr2_cmd_name.h [new file with mode: 0644]
trace2/tr2_dst.c [new file with mode: 0644]
trace2/tr2_dst.h [new file with mode: 0644]
trace2/tr2_sid.c [new file with mode: 0644]
trace2/tr2_sid.h [new file with mode: 0644]
trace2/tr2_tbuf.c [new file with mode: 0644]
trace2/tr2_tbuf.h [new file with mode: 0644]
trace2/tr2_tgt.h [new file with mode: 0644]
trace2/tr2_tgt_event.c [new file with mode: 0644]
trace2/tr2_tgt_normal.c [new file with mode: 0644]
trace2/tr2_tgt_perf.c [new file with mode: 0644]
trace2/tr2_tls.c [new file with mode: 0644]
trace2/tr2_tls.h [new file with mode: 0644]
trailer.c
trailer.h
transport-helper.c
transport.c
unpack-trees.c
usage.c
worktree.c
wt-status.c
xdiff-interface.c

index de1c8b5c77f7566d9e41949e5e397db3cc1b487c..41d4cd23fd97f599053a19555a173894da71e560 100644 (file)
@@ -149,7 +149,7 @@ Cpp11BracedListStyle: false
 
 # A list of macros that should be interpreted as foreach loops instead of as
 # function calls.
-ForEachMacros: ['for_each_string_list_item']
+ForEachMacros: ['for_each_string_list_item', 'for_each_wanted_builtin', 'for_each_builtin', 'for_each_ut']
 
 # The maximum number of consecutive empty lines to keep.
 MaxEmptyLinesToKeep: 1
index 64e605a02b71c51e9f59c429b28961c3152039b9..e7b4e2f3c204c2c94c60222abbc702bd7d72de39 100644 (file)
@@ -5,7 +5,7 @@ Git community does not use github.com for their contributions. Instead, we use
 a mailing list (git@vger.kernel.org) for code submissions, code
 reviews, and bug reports.
 
-Nevertheless, you can use [submitGit](http://submitgit.herokuapp.com/) to
+Nevertheless, you can use [GitGitGadget](https://gitgitgadget.github.io/) to
 conveniently send your Pull Requests commits to our mailing list.
 
 Please read ["A note from the maintainer"](https://git.kernel.org/pub/scm/git/git.git/plain/MaintNotes?h=todo)
index adba13e5baf4603de72341068532e2c7d7d05f75..952c7c3a2aa11ea1087390be61eab6f7c0013599 100644 (file)
@@ -1,7 +1,7 @@
 Thanks for taking the time to contribute to Git! Please be advised that the
 Git community does not use github.com for their contributions. Instead, we use
 a mailing list (git@vger.kernel.org) for code submissions, code reviews, and
-bug reports. Nevertheless, you can use submitGit to conveniently send your Pull
-Requests commits to our mailing list.
+bug reports. Nevertheless, you can use GitGitGadget (https://gitgitgadget.github.io/)
+to conveniently send your Pull Requests commits to our mailing list.
 
 Please read the "guidelines for contributing" linked above!
index 36cbdea7f4f37e7eb4d5172daa4431cffcb4b46c..ffb1bc46f2d9605f7c3fba478f918fcc288bbdd6 100644 (file)
@@ -21,16 +21,6 @@ matrix:
       compiler:
       addons:
       before_install:
-    - env: jobname=Windows
-      os: linux
-      compiler:
-      addons:
-      before_install:
-      script:
-        - >
-          test "$TRAVIS_REPO_SLUG" != "git/git" ||
-          ci/run-windows-build.sh $TRAVIS_BRANCH $(git rev-parse HEAD)
-      after_failure:
     - env: jobname=Linux32
       os: linux
       compiler:
diff --git a/Documentation/RelNotes/2.22.0.txt b/Documentation/RelNotes/2.22.0.txt
new file mode 100644 (file)
index 0000000..ca774e8
--- /dev/null
@@ -0,0 +1,150 @@
+Git 2.22 Release Notes
+======================
+
+Updates since v2.21
+-------------------
+
+UI, Workflows & Features
+
+ * "git checkout --no-overlay" can be used to trigger a new mode of
+   checking out paths out of the tree-ish, that allows paths that
+   match the pathspec that are in the current index and working tree
+   and are not in the tree-ish.
+
+ * The %(trailers) formatter in "git log --format=..."  now allows to
+   optionally pick trailers selectively by keyword, show only values,
+   etc.
+
+ * Four new configuration variables {author,committer}.{name,email}
+   have been introduced to override user.{name,email} in more specific
+   cases.
+
+ * Command-line completion (in contrib/) learned to tab-complete the
+   "git submodule absorbgitdirs" subcommand.
+
+ * "git branch" learned a new subcommand "--show-current".
+
+ * Output from "diff --cc" did not show the original paths when the
+   merge involved renames.  A new option adds the paths in the
+   original trees to the output.
+
+ * The command line completion (in contrib/) has been taught to
+   complete more subcommand parameters.
+
+ * The final report from "git bisect" used to show the suspected
+   culprit using a raw "diff-tree", with which there is no output for
+   a merge commit.  This has been updated to use a more modern and
+   human readable output that still is concise enough.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The diff machinery, one of the oldest parts of the system, which
+   long predates the parse-options API, uses fairly long and complex
+   handcrafted option parser.  This is being rewritten to use the
+   parse-options API.
+
+ * The implementation of pack-redundant has been updated for
+   performance in a repository with many packfiles.
+
+ * A more structured way to obtain execution trace has been added.
+
+ * "git prune" has been taught to take advantage of reachability
+   bitmap when able.
+
+ * The command line parser of "git commit-tree" has been rewritten to
+   use the parse-options API.
+
+
+Fixes since v2.21
+-----------------
+
+ * "git prune-packed" did not notice and complain against excess
+   arguments given from the command line, which now it does.
+   (merge 9b0bd87ed2 rj/prune-packed-excess-args later to maint).
+
+ * Split-index fix.
+   (merge 6e37c8ed3c nd/split-index-null-base-fix later to maint).
+
+ * "git diff --no-index" may still want to access Git goodies like
+   --ext-diff and --textconv, but so far these have been ignored,
+   which has been corrected.
+   (merge 287ab28bfa jk/diff-no-index-initialize later to maint).
+
+ * Unify RPC code for smart http in protocol v0/v1 and v2, which fixes
+   a bug in the latter (lack of authentication retry) and generally
+   improves the code base.
+   (merge a97d00799a jt/http-auth-proto-v2-fix later to maint).
+
+ * The include file compat/bswap.h has been updated so that it is safe
+   to (accidentally) include it more than once.
+   (merge 33aa579a55 jk/guard-bswap-header later to maint).
+
+ * The set of header files used by "make hdr-check" unconditionally
+   included sha256/gcrypt.h, even when it is not used, causing the
+   make target to fail.  We now skip it when GCRYPT_SHA256 is not in
+   use.
+   (merge f23aa18e7f rj/hdr-check-gcrypt-fix later to maint).
+
+ * The Makefile uses 'find' utility to enumerate all the *.h header
+   files, which is expensive on platforms with slow filesystems; it
+   now optionally uses "ls-files" if working within a repository,
+   which is a trick similar to how all sources are enumerated to run
+   ETAGS on.
+   (merge 92b88eba9f js/find-lib-h-with-ls-files-when-possible later to maint).
+
+ * "git rebase" that was reimplemented in C did not set ORIG_HEAD
+   correctly, which has been corrected.
+   (merge cbd29ead92 js/rebase-orig-head-fix later to maint).
+
+ * Dev support.
+   (merge f545737144 js/stress-test-ui-tweak later to maint).
+
+ * CFLAGS now can be tweaked when invoking Make while using
+   DEVELOPER=YesPlease; this did not work well before.
+   (merge 6d5d4b4e93 ab/makefile-help-devs-more later to maint).
+
+ * "git fsck --connectivity-only" omits computation necessary to sift
+   the objects that are not reachable from any of the refs into
+   unreachable and dangling.  This is now enabled when dangling
+   objects are requested (which is done by default, but can be
+   overridden with the "--no-dangling" option).
+   (merge 8d8c2a5aef jk/fsck-doc later to maint).
+
+ * On platforms where "git fetch" is killed with SIGPIPE (e.g. OSX),
+   the upload-pack that runs on the other end that hangs up after
+   detecting an error could cause "git fetch" to die with a signal,
+   which led to a flakey test.  "git fetch" now ignores SIGPIPE during
+   the network portion of its operation (this is not a problem as we
+   check the return status from our write(2)s).
+   (merge 143588949c jk/no-sigpipe-during-network-transport later to maint).
+
+ * A recent update broke "is this object available to us?" check for
+   well-known objects like an empty tree (which should yield "yes",
+   even when there is no on-disk object for an empty tree), which has
+   been corrected.
+   (merge f06ab027ef jk/virtual-objects-do-exist later to maint).
+
+ * The setup code has been cleaned up to avoid leaks around the
+   repository_format structure.
+   (merge e8805af1c3 ma/clear-repository-format later to maint).
+
+ * "git config --type=color ..." is meant to replace "git config --get-color"
+   but there is a slight difference that wasn't documented, which is
+   now fixed.
+   (merge cd8e7593b9 jk/config-type-color-ends-with-lf later to maint).
+
+ * Code cleanup, docfix, build fix, etc.
+   (merge 11f470aee7 jc/test-yes-doc later to maint).
+   (merge 90503a240b js/doc-symref-in-proto-v1 later to maint).
+   (merge 5c326d1252 jk/unused-params later to maint).
+   (merge 68cabbfda3 dl/doc-submodule-wo-subcommand later to maint).
+   (merge 9903623761 ab/receive-pack-use-after-free-fix later to maint).
+   (merge 1ede45e44b en/merge-options-doc later to maint).
+   (merge 3e14dd2c8e rd/doc-hook-used-in-sample later to maint).
+   (merge c271dc28fd nd/no-more-check-racy later to maint).
+   (merge e6e15194a8 yb/utf-16le-bom-spellfix later to maint).
+   (merge bb101aaf0c rd/attr.c-comment-typofix later to maint).
+   (merge 716a5af812 rd/gc-prune-doc-fix later to maint).
+   (merge 50b206371d js/untravis-windows later to maint).
+   (merge dbf47215e3 js/rebase-recreate-merge later to maint).
index 019d60ede2873ffb76e96e75ab0502c8840f75b6..8f4b3faadd47b6c655f403528615ae4f0e0df9f6 100644 (file)
@@ -85,9 +85,9 @@ When `merges`, pass the `--rebase-merges` option to 'git rebase'
 so that the local merge commits are included in the rebase (see
 linkgit:git-rebase[1] for details).
 +
-When preserve, also pass `--preserve-merges` along to 'git rebase'
-so that locally committed merge commits will not be flattened
-by running 'git pull'.
+When `preserve` (deprecated in favor of `merges`), also pass
+`--preserve-merges` along to 'git rebase' so that locally committed merge
+commits will not be flattened by running 'git pull'.
 +
 When the value is `interactive`, the rebase is run in interactive mode.
 +
index bb23a9947d78dcbf528b27b0ebb11d3e6e01811a..b87cab31b39c4469fc2528c04bd19b6b96912dd7 100644 (file)
@@ -18,9 +18,9 @@ When `merges`, pass the `--rebase-merges` option to 'git rebase'
 so that the local merge commits are included in the rebase (see
 linkgit:git-rebase[1] for details).
 +
-When preserve, also pass `--preserve-merges` along to 'git rebase'
-so that locally committed merge commits will not be flattened
-by running 'git pull'.
+When `preserve` (deprecated in favor of `merges`), also pass
+`--preserve-merges` along to 'git rebase' so that locally committed merge
+commits will not be flattened by running 'git pull'.
 +
 When the value is `interactive`, the rebase is run in interactive mode.
 +
index b5b2ba1199c98aaa9bbe9c74ce72c9f9dcb41f82..0557cbbceb8159148e8d0e7ab4771a8f53a67c0a 100644 (file)
@@ -1,12 +1,19 @@
-user.email::
-       Your email address to be recorded in any newly created commits.
-       Can be overridden by the `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_EMAIL`, and
-       `EMAIL` environment variables.  See linkgit:git-commit-tree[1].
-
 user.name::
-       Your full name to be recorded in any newly created commits.
-       Can be overridden by the `GIT_AUTHOR_NAME` and `GIT_COMMITTER_NAME`
-       environment variables.  See linkgit:git-commit-tree[1].
+user.email::
+author.name::
+author.email::
+committer.name::
+committer.email::
+       The `user.name` and `user.email` variables determine what ends
+       up in the `author` and `committer` field of commit
+       objects.
+       If you need the `author` or `committer` to be different, the
+       `author.name`, `author.email`, `committer.name` or
+       `committer.email` variables can be set.
+       Also, all of these can be overridden by the `GIT_AUTHOR_NAME`,
+       `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME`,
+       `GIT_COMMITTER_EMAIL` and `EMAIL` environment variables.
+       See linkgit:git-commit-tree[1] for more information.
 
 user.useConfigOnly::
        Instruct Git to avoid trying to guess defaults for `user.email`
index cdcc17f0ad574b435bac76c0876a3b344e2839e0..4d846d73463c520046e8fa22d5da375733fa3fe9 100644 (file)
@@ -95,12 +95,26 @@ from the format described above in the following way:
 . there are more "src" modes and "src" sha1
 . status is concatenated status characters for each parent
 . no optional "score" number
-. single path, only for "dst"
+. tab-separated pathname(s) of the file
 
-Example:
+For `-c` and `--cc`, only the destination or final path is shown even
+if the file was renamed on any side of history.  With
+`--combined-all-paths`, the name of the path in each parent is shown
+followed by the name of the path in the merge commit.
+
+Examples for `-c` and `--cc` without `--combined-all-paths`:
+------------------------------------------------
+::100644 100644 100644 fabadb8 cc95eb0 4866510 MM      desc.c
+::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM      bar.sh
+::100644 100644 100644 e07d6c5 9042e82 ee91881 RR      phooey.c
+------------------------------------------------
+
+Examples when `--combined-all-paths` added to either `-c` or `--cc`:
 
 ------------------------------------------------
-::100644 100644 100644 fabadb8 cc95eb0 4866510 MM      describe.c
+::100644 100644 100644 fabadb8 cc95eb0 4866510 MM      desc.c  desc.c  desc.c
+::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM      foo.sh  bar.sh  bar.sh
+::100644 100644 100644 e07d6c5 9042e82 ee91881 RR      fooey.c fuey.c  phooey.c
 ------------------------------------------------
 
 Note that 'combined diff' lists only files which were modified from
index 231105cff48d9c109b2001623c67d51690c595ba..f10ca410ad8a937e1acc62864c6b33b1db965ac4 100644 (file)
@@ -143,6 +143,19 @@ copying detection) are designed to work with diff of two
 Similar to two-line header for traditional 'unified' diff
 format, `/dev/null` is used to signal created or deleted
 files.
++
+However, if the --combined-all-paths option is provided, instead of a
+two-line from-file/to-file you get a N+1 line from-file/to-file header,
+where N is the number of parents in the merge commit
+
+       --- a/file
+       --- a/file
+       --- a/file
+       +++ b/file
++
+This extended format can be useful if rename or copy detection is
+active, to allow you to see the original name of the file in different
+parents.
 
 4.   Chunk header format is modified to prevent people from
      accidentally feeding it to `patch -p1`. Combined diff format
index 554a34080d7081da917cd54cd34eceb7bf4cd95c..5ebc56867bf8323c2715389875c2ea732b5c2ba8 100644 (file)
@@ -36,11 +36,21 @@ endif::git-format-patch[]
 -U<n>::
 --unified=<n>::
        Generate diffs with <n> lines of context instead of
-       the usual three.
+       the usual three. Implies `--patch`.
 ifndef::git-format-patch[]
        Implies `-p`.
 endif::git-format-patch[]
 
+--output=<file>::
+       Output to a specific file instead of stdout.
+
+--output-indicator-new=<char>::
+--output-indicator-old=<char>::
+--output-indicator-context=<char>::
+       Specify the character used to indicate new, old or context
+       lines in the generated patch. Normally they are '+', '-' and
+       ' ' respectively.
+
 ifndef::git-format-patch[]
 --raw::
 ifndef::git-log[]
@@ -148,6 +158,7 @@ These parameters can also be set individually with `--stat-width=<width>`,
        number of modified files, as well as number of added and deleted
        lines.
 
+-X[<param1,param2,...>]::
 --dirstat[=<param1,param2,...>]::
        Output the distribution of relative amount of changes for each
        sub-directory. The behavior of `--dirstat` can be customized by
@@ -192,6 +203,12 @@ directories with less than 10% of the total amount of changed files,
 and accumulating child directory counts in the parent directories:
 `--dirstat=files,10,cumulative`.
 
+--cumulative::
+       Synonym for --dirstat=cumulative
+
+--dirstat-by-file[=<param1,param2>...]::
+       Synonym for --dirstat=files,param1,param2...
+
 --summary::
        Output a condensed summary of extended header information
        such as creations, renames and mode changes.
@@ -386,6 +403,9 @@ endif::git-format-patch[]
        Turn off rename detection, even when the configuration
        file gives the default to do so.
 
+--[no-]rename-empty::
+       Whether to use empty blobs as rename source.
+
 ifndef::git-format-patch[]
 --check::
        Warn if changes introduce conflict markers or whitespace errors.
index 37bcab94d5e435849c22ddd0473be5cfb1c56140..8b0e4c7fa8c5922801164a7f847a7baecc456402 100644 (file)
@@ -193,15 +193,6 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files.
        for command-line options).
 
 
-CONFIGURATION
--------------
-
-The optional configuration variable `core.excludesFile` indicates a path to a
-file containing patterns of file names to exclude from git-add, similar to
-$GIT_DIR/info/exclude.  Patterns in the exclude file are used in addition to
-those in info/exclude.  See linkgit:gitignore[5].
-
-
 EXAMPLES
 --------
 
index 3bd83a7cbdd9d62c9b10057bd4bf3f03d57b1085..0cd87ddeff205ac6b5a92260f690e642f16d0875 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git branch' [--color[=<when>] | --no-color] [-r | -a]
-       [--list] [-v [--abbrev=<length> | --no-abbrev]]
+       [--list] [--show-current] [-v [--abbrev=<length> | --no-abbrev]]
        [--column[=<options>] | --no-column] [--sort=<key>]
        [(--merged | --no-merged) [<commit>]]
        [--contains [<commit]] [--no-contains [<commit>]]
@@ -160,6 +160,10 @@ This option is only applicable in non-verbose mode.
        branch --list 'maint-*'`, list only the branches that match
        the pattern(s).
 
+--show-current::
+       Print the name of the current branch. In detached HEAD state,
+       nothing is printed.
+
 -v::
 -vv::
 --verbose::
index 8c3d4128c2871274967cd30029e2390e48d0dcc1..f179b43732aa2a7e211a95889847294890c90d38 100644 (file)
@@ -260,6 +260,9 @@ the conflicted merge in the specified paths.
 This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
++
+Note that this option uses the no overlay mode by default (see also
+`--[no-]overlay`), and currently doesn't support overlay mode.
 
 --ignore-other-worktrees::
        `git checkout` refuses when the wanted ref is already checked
@@ -280,6 +283,13 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
        Do not attempt to create a branch if a remote tracking branch
        of the same name exists.
 
+--[no-]overlay::
+       In the default overlay mode, `git checkout` never
+       removes files from the index or the working tree.  When
+       specifying `--no-overlay`, files that appear in the index and
+       working tree, but not in <tree-ish> are removed, to make them
+       match <tree-ish> exactly.
+
 <branch>::
        Branch to checkout; if it refers to a branch (i.e., a name that,
        when prepended with "refs/heads/", is a valid ref), then that
index 03056dad0de5ab6121c1f7a506dc4dacf1f83c7c..db876f7dde9237c47d1083d1649f8f8b077b9c82 100644 (file)
@@ -55,14 +55,13 @@ OPTIONS
 
 -e <pattern>::
 --exclude=<pattern>::
-       In addition to those found in .gitignore (per directory) and
-       $GIT_DIR/info/exclude, also consider these patterns to be in the
-       set of the ignore rules in effect.
+       Use the given exclude pattern in addition to the standard ignore rules
+       (see linkgit:gitignore[5]).
 
 -x::
-       Don't use the standard ignore rules read from .gitignore (per
-       directory) and $GIT_DIR/info/exclude, but do still use the ignore
-       rules given with `-e` options.  This allows removing all untracked
+       Don't use the standard ignore rules (see linkgit:gitignore[5]), but
+       still use the ignore rules given with `-e` options from the command
+       line.  This allows removing all untracked
        files, including build products.  This can be used (possibly in
        conjunction with 'git reset') to create a pristine
        working directory to test a clean build.
index 002dae625e5ab7cb834fd548dc0b4c23072a0b1b..4b90b9c12a4a87fe563d367ab8252750021969d7 100644 (file)
@@ -23,6 +23,10 @@ Creates a new commit object based on the provided tree object and
 emits the new commit object id on stdout. The log message is read
 from the standard input, unless `-m` or `-F` options are given.
 
+The `-m` and `-F` options can be given any number of times, in any
+order. The commit log message will be composed in the order in which
+the options are given.
+
 A commit object may have any number of parents. With exactly one
 parent, it is an ordinary commit. Having more than one parent makes
 the commit a merge between several lines of history. Initial (root)
@@ -41,7 +45,7 @@ state was.
 OPTIONS
 -------
 <tree>::
-       An existing tree object
+       An existing tree object.
 
 -p <parent>::
        Each `-p` indicates the id of a parent commit object.
@@ -52,7 +56,8 @@ OPTIONS
 
 -F <file>::
        Read the commit log message from the given file. Use `-` to read
-       from the standard input.
+       from the standard input. This can be given more than once and the
+       content of each file becomes its own paragraph.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
index 1bfe9f56a7b9b983a2d927406dc17ff384e715a2..d0b9c50d20489e421139b531b66d0b0e693c9781 100644 (file)
@@ -240,7 +240,9 @@ Valid `<type>`'s include:
        output.  The optional `default` parameter is used instead, if
        there is no color configured for `name`.
 +
-`--type=color [--default=<default>]` is preferred over `--get-color`.
+`--type=color [--default=<default>]` is preferred over `--get-color`
+(but note that `--get-color` will omit the trailing newline printed by
+`--type=color`).
 
 -e::
 --edit::
index 43daa7c046f472ac856d89292ede96f5e5dcb64e..24f32e8c544a455829b25193ac6b646504ab5f34 100644 (file)
@@ -10,8 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty]
-             [-t] [-r] [-c | --cc] [--root] [<common diff options>]
-             <tree-ish> [<tree-ish>] [<path>...]
+             [-t] [-r] [-c | --cc] [--combined-all-paths] [--root]
+             [<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
 
 DESCRIPTION
 -----------
@@ -105,6 +105,13 @@ include::pretty-options.txt[]
        itself and the commit log message is not shown, just like in any other
        "empty diff" case.
 
+--combined-all-paths::
+       This flag causes combined diffs (used for merge commits) to
+       list the name of the file from all parents.  It thus only has
+       effect when -c or --cc are specified, and is likely only
+       useful if filename changes are detected (i.e. when either
+       rename or copy detection have been requested).
+
 --always::
        Show the commit itself and the commit log message even
        if the diff itself is empty.
index 55950d9eea9edb5157352fa3e6316ef3507e99bf..e0eae642c10f75cff57c0a9d07420e3218235423 100644 (file)
@@ -62,9 +62,17 @@ index file, all SHA-1 references in `refs` namespace, and all reflogs
        with --no-full.
 
 --connectivity-only::
-       Check only the connectivity of tags, commits and tree objects. By
-       avoiding to unpack blobs, this speeds up the operation, at the
-       expense of missing corrupt objects or other problematic issues.
+       Check only the connectivity of reachable objects, making sure
+       that any objects referenced by a reachable tag, commit, or tree
+       is present. This speeds up the operation by avoiding reading
+       blobs entirely (though it does still check that referenced blobs
+       exist). This will detect corruption in commits and trees, but
+       not do any semantic checks (e.g., for format errors). Corruption
+       in blob objects will not be detected at all.
++
+Unreachable tags, commits, and trees will also be accessed to find the
+tips of dangling segments of history. Use `--no-dangling` if you don't
+care about this output and want to speed it up further.
 
 --strict::
        Enable more strict checking, namely to catch a file mode
index a7442499f6d3985fa2ca57fa827d454b11479705..a7c1b0f60edb6ccd38ddb3ea4624fe0d213820b4 100644 (file)
@@ -76,7 +76,7 @@ be performed as well.
 --prune=<date>::
        Prune loose objects older than date (default is 2 weeks ago,
        overridable by the config variable `gc.pruneExpire`).
-       --prune=all prunes loose objects regardless of their age and
+       --prune=now prunes loose objects regardless of their age and
        increases the risk of corruption if another process is writing to
        the repository concurrently; see "NOTES" below. --prune is on by
        default.
index 5629ba4c5d2b06385911d2c8ba25a496ce78c263..44e00329e1b0e6b1c3d55316121992492f5b1ea9 100644 (file)
@@ -410,14 +410,14 @@ See also INCOMPATIBLE OPTIONS below.
 +
 By default, or when `no-rebase-cousins` was specified, commits which do not
 have `<upstream>` as direct ancestor will keep their original branch point,
-i.e. commits that would be excluded by gitlink:git-log[1]'s
+i.e. commits that would be excluded by linkgit:git-log[1]'s
 `--ancestry-path` option will keep their original ancestry by default. If
 the `rebase-cousins` mode is turned on, such commits are instead rebased
 onto `<upstream>` (or `<onto>`, if specified).
 +
-The `--rebase-merges` mode is similar in spirit to `--preserve-merges`, but
-in contrast to that option works well in interactive rebases: commits can be
-reordered, inserted and dropped at will.
+The `--rebase-merges` mode is similar in spirit to the deprecated
+`--preserve-merges`, but in contrast to that option works well in interactive
+rebases: commits can be reordered, inserted and dropped at will.
 +
 It is currently only possible to recreate the merge commits using the
 `recursive` merge strategy; Different merge strategies can be used only via
@@ -427,9 +427,10 @@ See also REBASING MERGES and INCOMPATIBLE OPTIONS below.
 
 -p::
 --preserve-merges::
-       Recreate merge commits instead of flattening the history by replaying
-       commits a merge commit introduces. Merge conflict resolutions or manual
-       amendments to merge commits are not preserved.
+       [DEPRECATED: use `--rebase-merges` instead] Recreate merge commits
+       instead of flattening the history by replaying commits a merge commit
+       introduces. Merge conflict resolutions or manual amendments to merge
+       commits are not preserved.
 +
 This uses the `--interactive` machinery internally, but combining it
 with the `--interactive` option explicitly is generally not a good
@@ -1020,11 +1021,11 @@ merge cmake
 
 BUGS
 ----
-The todo list presented by `--preserve-merges --interactive` does not
-represent the topology of the revision graph.  Editing commits and
-rewording their commit messages should work fine, but attempts to
-reorder commits tend to produce counterintuitive results. Use
-`--rebase-merges` in such scenarios instead.
+The todo list presented by the deprecated `--preserve-merges --interactive`
+does not represent the topology of the revision graph (use `--rebase-merges`
+instead).  Editing commits and rewording their commit messages should work
+fine, but attempts to reorder commits tend to produce counterintuitive results.
+Use `--rebase-merges` in such scenarios instead.
 
 For example, an attempt to rearrange
 ------------
index 132f8e55f67b2e2a40f0058888f4f8260c51f270..26e746c53f25ce833e7dace94b37b178aca48f4e 100644 (file)
@@ -428,8 +428,8 @@ working index HEAD target         working index HEAD
 
 `reset --merge` is meant to be used when resetting out of a conflicted
 merge. Any mergy operation guarantees that the working tree file that is
-involved in the merge does not have local change wrt the index before
-it starts, and that it writes the result out to the working tree. So if
+involved in the merge does not have a local change with respect to the index
+before it starts, and that it writes the result out to the working tree. So if
 we see some difference between the index and the target and also
 between the index and the working tree, then it means that we are not
 resetting out from a state that a mergy operation left after failing
index ba3c4df550acfeb04a0a6b753a778816f031492c..2794e2978021c08b613b07aebf36090c7943fe09 100644 (file)
@@ -9,6 +9,7 @@ git-submodule - Initialize, update or inspect submodules
 SYNOPSIS
 --------
 [verse]
+'git submodule' [--quiet] [--cached]
 'git submodule' [--quiet] add [<options>] [--] <repository> [<path>]
 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
 'git submodule' [--quiet] init [--] [<path>...]
@@ -28,6 +29,9 @@ For more information about submodules, see linkgit:gitsubmodules[7].
 
 COMMANDS
 --------
+With no arguments, shows the status of existing submodules.  Several
+subcommands are available to perform operations on the submodules.
+
 add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--depth <depth>] [--] <repository> [<path>]::
        Add the given repository as a submodule at the given path
        to the changeset to be committed next to the current
index 9b41f81c06ef9fd2386b0e424d4d62cb27195ae3..bdd11a2dddf81647937f1cf002b37e0b192b47d1 100644 (file)
@@ -346,7 +346,7 @@ automatic line ending conversion based on your platform.
 
 Use the following attributes if your '*.ps1' files are UTF-16 little
 endian encoded without BOM and you want Git to use Windows line endings
-in the working directory (use `UTF-16-LE-BOM` instead of `UTF-16LE` if
+in the working directory (use `UTF-16LE-BOM` instead of `UTF-16LE` if
 you want UTF-16 little endian with BOM).
 Please note, it is highly recommended to
 explicitly define the line endings with `eol` if the `working-tree-encoding`
index 959044347e5ee0daf78b3503794e2bf3c51f0efb..5bf653c111d07e0958840e38187320a433b20d07 100644 (file)
@@ -99,6 +99,10 @@ All the `git commit` hooks are invoked with the environment
 variable `GIT_EDITOR=:` if the command will not bring up an editor
 to modify the commit message.
 
+The default 'pre-commit' hook, when enabled--and with the
+`hooks.allownonascii` config option unset or set to false--prevents
+the use of non-ASCII filenames.
+
 prepare-commit-msg
 ~~~~~~~~~~~~~~~~~~
 
index 1c94f08ff4dd9af173c659fb0b536f6a71a1c252..b5bc9dbff05b293caae7c42e865b19bc067c4d82 100644 (file)
@@ -132,6 +132,14 @@ full pathname may have special meaning:
  - Other consecutive asterisks are considered regular asterisks and
    will match according to the previous rules.
 
+CONFIGURATION
+-------------
+
+The optional configuration variable `core.excludesFile` indicates a path to a
+file containing patterns of file names to exclude, similar to
+`$GIT_DIR/info/exclude`.  Patterns in the exclude file are used in addition to
+those in `$GIT_DIR/info/exclude`.
+
 NOTES
 -----
 
index 63a3fc09548abe8d34faab98f183e1817b21b878..92a7d936c1a8fbcc6746a0ddd127e131edb8f2c1 100644 (file)
@@ -3,9 +3,14 @@
        Perform the merge and commit the result. This option can
        be used to override --no-commit.
 +
-With --no-commit perform the merge but pretend the merge
-failed and do not autocommit, to give the user a chance to
-inspect and further tweak the merge result before committing.
+With --no-commit perform the merge and stop just before creating
+a merge commit, to give the user a chance to inspect and further
+tweak the merge result before committing.
++
+Note that fast-forward updates do not create a merge commit and
+therefore there is no way to stop those merges with --no-commit.
+Thus, if you want to ensure your branch is not changed or updated
+by the merge command, use --no-ff with --no-commit.
 
 --edit::
 -e::
index 7bfffae7652815596dcc29962e67e22efcf3075b..079598307a3897d913de030a4737ce2e43c1810d 100644 (file)
@@ -102,120 +102,160 @@ The title was >>t4119: test autocomputing -p<n> for traditional diff input.<<
 +
 The placeholders are:
 
-- '%H': commit hash
-- '%h': abbreviated commit hash
-- '%T': tree hash
-- '%t': abbreviated tree hash
-- '%P': parent hashes
-- '%p': abbreviated parent hashes
-- '%an': author name
-- '%aN': author name (respecting .mailmap, see linkgit:git-shortlog[1]
-  or linkgit:git-blame[1])
-- '%ae': author email
-- '%aE': author email (respecting .mailmap, see
-  linkgit:git-shortlog[1] or linkgit:git-blame[1])
-- '%ad': author date (format respects --date= option)
-- '%aD': author date, RFC2822 style
-- '%ar': author date, relative
-- '%at': author date, UNIX timestamp
-- '%ai': author date, ISO 8601-like format
-- '%aI': author date, strict ISO 8601 format
-- '%cn': committer name
-- '%cN': committer name (respecting .mailmap, see
-  linkgit:git-shortlog[1] or linkgit:git-blame[1])
-- '%ce': committer email
-- '%cE': committer email (respecting .mailmap, see
-  linkgit:git-shortlog[1] or linkgit:git-blame[1])
-- '%cd': committer date (format respects --date= option)
-- '%cD': committer date, RFC2822 style
-- '%cr': committer date, relative
-- '%ct': committer date, UNIX timestamp
-- '%ci': committer date, ISO 8601-like format
-- '%cI': committer date, strict ISO 8601 format
-- '%d': ref names, like the --decorate option of linkgit:git-log[1]
-- '%D': ref names without the " (", ")" wrapping.
-- '%S': ref name given on the command line by which the commit was reached
-  (like `git log --source`), only works with `git log`
-- '%e': encoding
-- '%s': subject
-- '%f': sanitized subject line, suitable for a filename
-- '%b': body
-- '%B': raw body (unwrapped subject and body)
+- Placeholders that expand to a single literal character:
+'%n':: newline
+'%%':: a raw '%'
+'%x00':: print a byte from a hex code
+
+- Placeholders that affect formatting of later placeholders:
+'%Cred':: switch color to red
+'%Cgreen':: switch color to green
+'%Cblue':: switch color to blue
+'%Creset':: reset color
+'%C(...)':: color specification, as described under Values in the
+           "CONFIGURATION FILE" section of linkgit:git-config[1].  By
+           default, colors are shown only when enabled for log output
+           (by `color.diff`, `color.ui`, or `--color`, and respecting
+           the `auto` settings of the former if we are going to a
+           terminal). `%C(auto,...)` is accepted as a historical
+           synonym for the default (e.g., `%C(auto,red)`). Specifying
+           `%C(always,...)` will show the colors even when color is
+           not otherwise enabled (though consider just using
+           `--color=always` to enable color for the whole output,
+           including this format and anything else git might color).
+           `auto` alone (i.e. `%C(auto)`) will turn on auto coloring
+           on the next placeholders until the color is switched
+           again.
+'%m':: left (`<`), right (`>`) or boundary (`-`) mark
+'%w([<w>[,<i1>[,<i2>]]])':: switch line wrapping, like the -w option of
+                           linkgit:git-shortlog[1].
+'%<(<N>[,trunc|ltrunc|mtrunc])':: make the next placeholder take at
+                                 least N columns, padding spaces on
+                                 the right if necessary.  Optionally
+                                 truncate at the beginning (ltrunc),
+                                 the middle (mtrunc) or the end
+                                 (trunc) if the output is longer than
+                                 N columns.  Note that truncating
+                                 only works correctly with N >= 2.
+'%<|(<N>)':: make the next placeholder take at least until Nth
+            columns, padding spaces on the right if necessary
+'%>(<N>)', '%>|(<N>)':: similar to '%<(<N>)', '%<|(<N>)' respectively,
+                       but padding spaces on the left
+'%>>(<N>)', '%>>|(<N>)':: similar to '%>(<N>)', '%>|(<N>)'
+                         respectively, except that if the next
+                         placeholder takes more spaces than given and
+                         there are spaces on its left, use those
+                         spaces
+'%><(<N>)', '%><|(<N>)':: similar to '%<(<N>)', '%<|(<N>)'
+                         respectively, but padding both sides
+                         (i.e. the text is centered)
+
+- Placeholders that expand to information extracted from the commit:
+'%H':: commit hash
+'%h':: abbreviated commit hash
+'%T':: tree hash
+'%t':: abbreviated tree hash
+'%P':: parent hashes
+'%p':: abbreviated parent hashes
+'%an':: author name
+'%aN':: author name (respecting .mailmap, see linkgit:git-shortlog[1]
+       or linkgit:git-blame[1])
+'%ae':: author email
+'%aE':: author email (respecting .mailmap, see linkgit:git-shortlog[1]
+       or linkgit:git-blame[1])
+'%ad':: author date (format respects --date= option)
+'%aD':: author date, RFC2822 style
+'%ar':: author date, relative
+'%at':: author date, UNIX timestamp
+'%ai':: author date, ISO 8601-like format
+'%aI':: author date, strict ISO 8601 format
+'%cn':: committer name
+'%cN':: committer name (respecting .mailmap, see
+       linkgit:git-shortlog[1] or linkgit:git-blame[1])
+'%ce':: committer email
+'%cE':: committer email (respecting .mailmap, see
+       linkgit:git-shortlog[1] or linkgit:git-blame[1])
+'%cd':: committer date (format respects --date= option)
+'%cD':: committer date, RFC2822 style
+'%cr':: committer date, relative
+'%ct':: committer date, UNIX timestamp
+'%ci':: committer date, ISO 8601-like format
+'%cI':: committer date, strict ISO 8601 format
+'%d':: ref names, like the --decorate option of linkgit:git-log[1]
+'%D':: ref names without the " (", ")" wrapping.
+'%S':: ref name given on the command line by which the commit was reached
+       (like `git log --source`), only works with `git log`
+'%e':: encoding
+'%s':: subject
+'%f':: sanitized subject line, suitable for a filename
+'%b':: body
+'%B':: raw body (unwrapped subject and body)
 ifndef::git-rev-list[]
-- '%N': commit notes
+'%N':: commit notes
 endif::git-rev-list[]
-- '%GG': raw verification message from GPG for a signed commit
-- '%G?': show "G" for a good (valid) signature,
-  "B" for a bad signature,
-  "U" for a good signature with unknown validity,
-  "X" for a good signature that has expired,
-  "Y" for a good signature made by an expired key,
-  "R" for a good signature made by a revoked key,
-  "E" if the signature cannot be checked (e.g. missing key)
-  and "N" for no signature
-- '%GS': show the name of the signer for a signed commit
-- '%GK': show the key used to sign a signed commit
-- '%GF': show the fingerprint of the key used to sign a signed commit
-- '%GP': show the fingerprint of the primary key whose subkey was used
-  to sign a signed commit
-- '%gD': reflog selector, e.g., `refs/stash@{1}` or
-  `refs/stash@{2 minutes ago`}; the format follows the rules described
-  for the `-g` option. The portion before the `@` is the refname as
-  given on the command line (so `git log -g refs/heads/master` would
-  yield `refs/heads/master@{0}`).
-- '%gd': shortened reflog selector; same as `%gD`, but the refname
-  portion is shortened for human readability (so `refs/heads/master`
-  becomes just `master`).
-- '%gn': reflog identity name
-- '%gN': reflog identity name (respecting .mailmap, see
-  linkgit:git-shortlog[1] or linkgit:git-blame[1])
-- '%ge': reflog identity email
-- '%gE': reflog identity email (respecting .mailmap, see
-  linkgit:git-shortlog[1] or linkgit:git-blame[1])
-- '%gs': reflog subject
-- '%Cred': switch color to red
-- '%Cgreen': switch color to green
-- '%Cblue': switch color to blue
-- '%Creset': reset color
-- '%C(...)': color specification, as described under Values in the
-  "CONFIGURATION FILE" section of linkgit:git-config[1].
-  By default, colors are shown only when enabled for log output (by
-  `color.diff`, `color.ui`, or `--color`, and respecting the `auto`
-  settings of the former if we are going to a terminal). `%C(auto,...)`
-  is accepted as a historical synonym for the default (e.g.,
-  `%C(auto,red)`). Specifying `%C(always,...)` will show the colors
-  even when color is not otherwise enabled (though consider
-  just using `--color=always` to enable color for the whole output,
-  including this format and anything else git might color).  `auto`
-  alone (i.e. `%C(auto)`) will turn on auto coloring on the next
-  placeholders until the color is switched again.
-- '%m': left (`<`), right (`>`) or boundary (`-`) mark
-- '%n': newline
-- '%%': a raw '%'
-- '%x00': print a byte from a hex code
-- '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of
-  linkgit:git-shortlog[1].
-- '%<(<N>[,trunc|ltrunc|mtrunc])': make the next placeholder take at
-  least N columns, padding spaces on the right if necessary.
-  Optionally truncate at the beginning (ltrunc), the middle (mtrunc)
-  or the end (trunc) if the output is longer than N columns.
-  Note that truncating only works correctly with N >= 2.
-- '%<|(<N>)': make the next placeholder take at least until Nth
-  columns, padding spaces on the right if necessary
-- '%>(<N>)', '%>|(<N>)': similar to '%<(<N>)', '%<|(<N>)'
-  respectively, but padding spaces on the left
-- '%>>(<N>)', '%>>|(<N>)': similar to '%>(<N>)', '%>|(<N>)'
-  respectively, except that if the next placeholder takes more spaces
-  than given and there are spaces on its left, use those spaces
-- '%><(<N>)', '%><|(<N>)': similar to '%<(<N>)', '%<|(<N>)'
-  respectively, but padding both sides (i.e. the text is centered)
-- %(trailers[:options]): display the trailers of the body as interpreted
-  by linkgit:git-interpret-trailers[1]. The `trailers` string may be
-  followed by a colon and zero or more comma-separated options. If the
-  `only` option is given, omit non-trailer lines from the trailer block.
-  If the `unfold` option is given, behave as if interpret-trailer's
-  `--unfold` option was given.  E.g., `%(trailers:only,unfold)` to do
-  both.
+'%GG':: raw verification message from GPG for a signed commit
+'%G?':: show "G" for a good (valid) signature,
+       "B" for a bad signature,
+       "U" for a good signature with unknown validity,
+       "X" for a good signature that has expired,
+       "Y" for a good signature made by an expired key,
+       "R" for a good signature made by a revoked key,
+       "E" if the signature cannot be checked (e.g. missing key)
+       and "N" for no signature
+'%GS':: show the name of the signer for a signed commit
+'%GK':: show the key used to sign a signed commit
+'%GF':: show the fingerprint of the key used to sign a signed commit
+'%GP':: show the fingerprint of the primary key whose subkey was used
+       to sign a signed commit
+'%gD':: reflog selector, e.g., `refs/stash@{1}` or `refs/stash@{2
+       minutes ago`}; the format follows the rules described for the
+       `-g` option. The portion before the `@` is the refname as
+       given on the command line (so `git log -g refs/heads/master`
+       would yield `refs/heads/master@{0}`).
+'%gd':: shortened reflog selector; same as `%gD`, but the refname
+       portion is shortened for human readability (so
+       `refs/heads/master` becomes just `master`).
+'%gn':: reflog identity name
+'%gN':: reflog identity name (respecting .mailmap, see
+       linkgit:git-shortlog[1] or linkgit:git-blame[1])
+'%ge':: reflog identity email
+'%gE':: reflog identity email (respecting .mailmap, see
+       linkgit:git-shortlog[1] or linkgit:git-blame[1])
+'%gs':: reflog subject
+'%(trailers[:options])':: display the trailers of the body as
+                         interpreted by
+                         linkgit:git-interpret-trailers[1]. The
+                         `trailers` string may be followed by a colon
+                         and zero or more comma-separated options:
+** 'key=<K>': only show trailers with specified key. Matching is done
+   case-insensitively and trailing colon is optional. If option is
+   given multiple times trailer lines matching any of the keys are
+   shown. This option automatically enables the `only` option so that
+   non-trailer lines in the trailer block are hidden. If that is not
+   desired it can be disabled with `only=false`.  E.g.,
+   `%(trailers:key=Reviewed-by)` shows trailer lines with key
+   `Reviewed-by`.
+** 'only[=val]': select whether non-trailer lines from the trailer
+   block should be included. The `only` keyword may optionally be
+   followed by an equal sign and one of `true`, `on`, `yes` to omit or
+   `false`, `off`, `no` to show the non-trailer lines. If option is
+   given without value it is enabled. If given multiple times the last
+   value is used.
+** 'separator=<SEP>': specify a separator inserted between trailer
+   lines. When this option is not given each trailer line is
+   terminated with a line feed character. The string SEP may contain
+   the literal formatting codes described above. To use comma as
+   separator one must use `%x2C` as it would otherwise be parsed as
+   next option. If separator option is given multiple times only the
+   last one is used. E.g., `%(trailers:key=Ticket,separator=%x2C )`
+   shows all trailer lines whose key is "Ticket" separated by a comma
+   and a space.
+** 'unfold[=val]': make it behave as if interpret-trailer's `--unfold`
+   option was given. In same way as to for `only` it can be followed
+   by an equal sign and explicit value. E.g.,
+   `%(trailers:only,unfold=true)` unfolds and shows all trailer lines.
+** 'valueonly[=val]': skip over the key part of the trailer line and only
+   show the value part. Also this optionally allows explicit value.
 
 NOTE: Some placeholders may depend on other options given to the
 revision traversal engine. For example, the `%g*` reflog options will
index cad711ce0ac060d9356e49599ffc13d130fc593c..ca959a72862048c205d67576fcd6c538f0c91b26 100644 (file)
@@ -960,6 +960,13 @@ options may be given. See linkgit:git-diff-files[1] for more options.
        the parents have only two variants and the merge result picks
        one of them without modification.
 
+--combined-all-paths::
+       This flag causes combined diffs (used for merge commits) to
+       list the name of the file from all parents.  It thus only has
+       effect when -c or --cc are specified, and is likely only
+       useful if filename changes are detected (i.e. when either
+       rename or copy detection have been requested).
+
 -m::
        This flag makes the merge commits show the full diff like
        regular commits; for each merge parent, a separate log entry
diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt
new file mode 100644 (file)
index 0000000..2de565f
--- /dev/null
@@ -0,0 +1,1349 @@
+= Trace2 API
+
+The Trace2 API can be used to print debug, performance, and telemetry
+information to stderr or a file.  The Trace2 feature is inactive unless
+explicitly enabled by enabling one or more Trace2 Targets.
+
+The Trace2 API is intended to replace the existing (Trace1)
+printf-style tracing provided by the existing `GIT_TRACE` and
+`GIT_TRACE_PERFORMANCE` facilities.  During initial implementation,
+Trace2 and Trace1 may operate in parallel.
+
+The Trace2 API defines a set of high-level messages with known fields,
+such as (`start`: `argv`) and (`exit`: {`exit-code`, `elapsed-time`}).
+
+Trace2 instrumentation throughout the Git code base sends Trace2
+messages to the enabled Trace2 Targets.  Targets transform these
+messages content into purpose-specific formats and write events to
+their data streams.  In this manner, the Trace2 API can drive
+many different types of analysis.
+
+Targets are defined using a VTable allowing easy extension to other
+formats in the future.  This might be used to define a binary format,
+for example.
+
+== Trace2 Targets
+
+Trace2 defines the following set of Trace2 Targets.
+Format details are given in a later section.
+
+`GIT_TR2` (NORMAL)::
+
+       a simple printf format like GIT_TRACE.
++
+------------
+$ export GIT_TR2=~/log.normal
+$ git version
+git version 2.20.1.155.g426c96fcdb
+------------
++
+------------
+$ cat ~/log.normal
+12:28:42.620009 common-main.c:38                  version 2.20.1.155.g426c96fcdb
+12:28:42.620989 common-main.c:39                  start git version
+12:28:42.621101 git.c:432                         cmd_name version (version)
+12:28:42.621215 git.c:662                         exit elapsed:0.001227 code:0
+12:28:42.621250 trace2/tr2_tgt_normal.c:124       atexit elapsed:0.001265 code:0
+------------
+
+`GIT_TR2_PERF` (PERF)::
+
+       a column-based format to replace GIT_TRACE_PERFORMANCE suitable for
+       development and testing, possibly to complement tools like gprof.
++
+------------
+$ export GIT_TR2_PERF=~/log.perf
+$ git version
+git version 2.20.1.155.g426c96fcdb
+------------
++
+------------
+$ cat ~/log.perf
+12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb
+12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version
+12:28:42.621111 git.c:432                         | d0 | main                     | cmd_name     |     |           |           |            | version (version)
+12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0
+12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0
+------------
+
+`GIT_TR2_EVENT` (EVENT)::
+
+       a JSON-based format of event data suitable for telemetry analysis.
++
+------------
+$ export GIT_TR2_EVENT=~/log.event
+$ git version
+git version 2.20.1.155.g426c96fcdb
+------------
++
+------------
+$ cat ~/log.event
+{"event":"version","sid":"1547659722619736-11614","thread":"main","time":"2019-01-16 17:28:42.620713","file":"common-main.c","line":38,"evt":"1","exe":"2.20.1.155.g426c96fcdb"}
+{"event":"start","sid":"1547659722619736-11614","thread":"main","time":"2019-01-16 17:28:42.621027","file":"common-main.c","line":39,"argv":["git","version"]}
+{"event":"cmd_name","sid":"1547659722619736-11614","thread":"main","time":"2019-01-16 17:28:42.621122","file":"git.c","line":432,"name":"version","hierarchy":"version"}
+{"event":"exit","sid":"1547659722619736-11614","thread":"main","time":"2019-01-16 17:28:42.621236","file":"git.c","line":662,"t_abs":0.001227,"code":0}
+{"event":"atexit","sid":"1547659722619736-11614","thread":"main","time":"2019-01-16 17:28:42.621268","file":"trace2/tr2_tgt_event.c","line":163,"t_abs":0.001265,"code":0}
+------------
+
+== Enabling a Target
+
+A Trace2 Target is enabled when the corresponding environment variable
+(`GIT_TR2`, `GIT_TR2_PERF`, or `GIT_TR2_EVENT`) is set.  The following
+values are recognized.
+
+`0`::
+`false`::
+
+       Disables the target.
+
+`1`::
+`true`::
+
+       Enables the target and writes stream to `STDERR`.
+
+`[2-9]`::
+
+       Enables the target and writes to the already opened file descriptor.
+
+`<absolute-pathname>`::
+
+       Enables the target, opens and writes to the file in append mode.
+
+`af_unix:[<socket_type>:]<absolute-pathname>`::
+
+       Enables the target, opens and writes to a Unix Domain Socket
+       (on platforms that support them).
++
+Socket type can be either `stream` or `dgram`.  If the socket type is
+omitted, Git will try both.
+
+== Trace2 API
+
+All public Trace2 functions and macros are defined in `trace2.h` and
+`trace2.c`.  All public symbols are prefixed with `trace2_`.
+
+There are no public Trace2 data structures.
+
+The Trace2 code also defines a set of private functions and data types
+in the `trace2/` directory.  These symbols are prefixed with `tr2_`
+and should only be used by functions in `trace2.c`.
+
+== Conventions for Public Functions and Macros
+
+The functions defined by the Trace2 API are declared and documented
+in `trace2.h`.  It defines the API functions and wrapper macros for
+Trace2.
+
+Some functions have a `_fl()` suffix to indicate that they take `file`
+and `line-number` arguments.
+
+Some functions have a `_va_fl()` suffix to indicate that they also
+take a `va_list` argument.
+
+Some functions have a `_printf_fl()` suffix to indicate that they also
+take a varargs argument.
+
+There are CPP wrapper macros and ifdefs to hide most of these details.
+See `trace2.h` for more details.  The following discussion will only
+describe the simplified forms.
+
+== Public API
+
+All Trace2 API functions send a messsage to all of the active
+Trace2 Targets.  This section describes the set of available
+messages.
+
+It helps to divide these functions into groups for discussion
+purposes.
+
+=== Basic Command Messages
+
+These are concerned with the lifetime of the overall git process.
+
+`void trace2_initialize()`::
+
+       Determines if any Trace2 Targets should be enabled and
+       initializes the Trace2 facility.  This includes starting the
+       elapsed time clocks and thread local storage (TLS).
++
+This function emits a "version" message containing the version of git
+and the Trace2 protocol.
++
+This function should be called from `main()` as early as possible in
+the life of the process.
+
+`int trace2_is_enabled()`::
+
+       Returns 1 if Trace2 is enabled (at least one target is
+       active).
+
+`void trace2_cmd_start(int argc, const char **argv)`::
+
+       Emits a "start" message containing the process command line
+       arguments.
+
+`int trace2_cmd_exit(int exit_code)`::
+
+       Emits an "exit" message containing the process exit-code and
+       elapsed time.
++
+Returns the exit-code.
+
+`void trace2_cmd_error(const char *fmt, va_list ap)`::
+
+       Emits an "error" message containing a formatted error message.
+
+`void trace2_cmd_path(const char *pathname)`::
+
+       Emits a "cmd_path" message with the full pathname of the
+       current process.
+
+=== Command Detail Messages
+
+These are concerned with describing the specific Git command
+after the command line, config, and environment are inspected.
+
+`void trace2_cmd_name(const char *name)`::
+
+       Emits a "cmd_name" message with the canonical name of the
+       command, for example "status" or "checkout".
+
+`void trace2_cmd_mode(const char *mode)`::
+
+       Emits a "cmd_mode" message with a qualifier name to further
+       describe the current git command.
++
+This message is intended to be used with git commands having multiple
+major modes.  For example, a "checkout" command can checkout a new
+branch or it can checkout a single file, so the checkout code could
+emit a cmd_mode message of "branch" or "file".
+
+`void trace2_cmd_alias(const char *alias, const char **argv_expansion)`::
+
+       Emits an "alias" message containing the alias used and the
+       argument expansion.
+
+`void trace2_def_param(const char *parameter, const char *value)`::
+
+       Emits a "def_param" message containing a key/value pair.
++
+This message is intended to report some global aspect of the current
+command, such as a configuration setting or command line switch that
+significantly affects program performance or behavior, such as
+`core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.
+
+`void trace2_cmd_list_config()`::
+
+       Emits a "def_param" messages for "important" configuration
+       settings.
++
+The environment variable `GIT_TR2_CONFIG_PARAMS` can be set to a
+list of patterns of important configuration settings, for example:
+`core.*,remote.*.url`.  This function will iterate over all config
+settings and emit a "def_param" message for each match.
+
+`void trace2_cmd_set_config(const char *key, const char *value)`::
+
+       Emits a "def_param" message for a specific configuration
+       setting IFF it matches the `GIT_TR2_CONFIG_PARAMS` pattern.
++
+This is used to hook into `git_config_set()` and catch any
+configuration changes and update a value previously reported by
+`trace2_cmd_list_config()`.
+
+`void trace2_def_repo(struct repository *repo)`::
+
+       Registers a repository with the Trace2 layer.  Assigns a
+       unique "repo-id" to `repo->trace2_repo_id`.
++
+Emits a "worktree" messages containing the repo-id and the worktree
+pathname.
++
+Region and data messages (described later) may refer to this repo-id.
++
+The main/top-level repository will have repo-id value 1 (aka "r1").
++
+The repo-id field is in anticipation of future in-proc submodule
+repositories.
+
+=== Child Process Messages
+
+These are concerned with the various spawned child processes,
+including shell scripts, git commands, editors, pagers, and hooks.
+
+`void trace2_child_start(struct child_process *cmd)`::
+
+       Emits a "child_start" message containing the "child-id",
+       "child-argv", and "child-classification".
++
+Before calling this, set `cmd->trace2_child_class` to a name
+describing the type of child process, for example "editor".
++
+This function assigns a unique "child-id" to `cmd->trace2_child_id`.
+This field is used later during the "child_exit" message to associate
+it with the "child_start" message.
++
+This function should be called before spawning the child process.
+
+`void trace2_child_exit(struct child_proess *cmd, int child_exit_code)`::
+
+       Emits a "child_exit" message containing the "child-id",
+       the child's elapsed time and exit-code.
++
+The reported elapsed time includes the process creation overhead and
+time spend waiting for it to exit, so it may be slightly longer than
+the time reported by the child itself.
++
+This function should be called after reaping the child process.
+
+`int trace2_exec(const char *exe, const char **argv)`::
+
+       Emits a "exec" message containing the "exec-id" and the
+       argv of the new process.
++
+This function should be called before calling one of the `exec()`
+variants, such as `execvp()`.
++
+This function returns a unique "exec-id".  This value is used later
+if the exec() fails and a "exec-result" message is necessary.
+
+`void trace2_exec_result(int exec_id, int error_code)`::
+
+       Emits a "exec_result" message containing the "exec-id"
+       and the error code.
++
+On Unix-based systems, `exec()` does not return if successful.
+This message is used to indicate that the `exec()` failed and
+that the current program is continuing.
+
+=== Git Thread Messages
+
+These messages are concerned with Git thread usage.
+
+`void trace2_thread_start(const char *thread_name)`::
+
+       Emits a "thread_start" message.
++
+The `thread_name` field should be a descriptive name, such as the
+unique name of the thread-proc.  A unique "thread-id" will be added
+to the name to uniquely identify thread instances.
++
+Region and data messages (described later) may refer to this thread
+name.
++
+This function must be called by the thread-proc of the new thread
+(so that TLS data is properly initialized) and not by the caller
+of `pthread_create()`.
+
+`void trace2_thread_exit()`::
+
+       Emits a "thread_exit" message containing the thread name
+       and the thread elapsed time.
++
+This function must be called by the thread-proc before it returns
+(so that the coorect TLS data is used and cleaned up.  It should
+not be called by the caller of `pthread_join()`.
+
+=== Region and Data Messages
+
+These are concerned with recording performance data
+over regions or spans of code.
+
+`void trace2_region_enter(const char *category, const char *label, const struct repository *repo)`::
+
+`void trace2_region_enter_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::
+
+`void trace2_region_enter_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::
+
+       Emits a thread-relative "region_enter" message with optional
+       printf string.
++
+This function pushes a new region nesting stack level on the current
+thread and starts a clock for the new stack frame.
++
+The `category` field is an arbitrary category name used to classify
+regions by feature area, such as "status" or "index".  At this time
+it is only just printed along with the rest of the message.  It may
+be used in the future to filter messages.
++
+The `label` field is an arbitrary label used to describe the activity
+being started, such as "read_recursive" or "do_read_index".
++
+The `repo` field, if set, will be used to get the "repo-id", so that
+recursive oerations can be attributed to the correct repository.
+
+`void trace2_region_leave(const char *category, const char *label, const struct repository *repo)`::
+
+`void trace2_region_leave_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::
+
+`void trace2_region_leave_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::
+
+       Emits a thread-relative "region_leave" message with optional
+       printf string.
++
+This function pops the region nesting stack on the current thread
+and reports the elapsed time of the stack frame.
++
+The `category`, `label`, and `repo` fields are the same as above.
+The `category` and `label` do not need to match the correpsonding
+"region_enter" message, but it makes the data stream easier to
+understand.
+
+`void trace2_data_string(const char *category, const struct repository *repo, const char *key, const char * value)`::
+
+`void trace2_data_intmax(const char *category, const struct repository *repo, const char *key, intmax value)`::
+
+`void trace2_data_json(const char *category, const struct repository *repo, const char *key, const struct json_writer *jw)`::
+
+       Emits a region- and thread-relative "data" or "data_json" message.
++
+This is a key/value pair message containing information about the
+current thread, region stack, and repository.  This could be used
+to print the number of files in a directory during a multi-threaded
+recursive tree walk.
+
+`void trace2_printf(const char *fmt, ...)`::
+
+`void trace2_printf_va(const char *fmt, va_list ap)`::
+
+       Emits a region- and thread-relative "printf" message.
+
+== Trace2 Target Formats
+
+=== NORMAL Format
+
+NORMAL format is enabled when the `GIT_TR2` environment variable is
+set.
+
+Events are written as lines of the form:
+
+------------
+[<time> SP <filename>:<line> SP+] <event-name> [[SP] <event-message>] LF
+------------
+
+`<event-name>`::
+
+       is the event name.
+
+`<event-message>`::
+       is a free-form printf message intended for human consumption.
++
+Note that this may contain embedded LF or CRLF characters that are
+not escaped, so the event may spill across multiple lines.
+
+If `GIT_TR2_BRIEF` is true, the `time`, `filename`, and `line` fields
+are omitted.
+
+This target is intended to be more of a summary (like GIT_TRACE) and
+less detailed than the other targets.  It ignores thread, region, and
+data messages, for example.
+
+=== PERF Format
+
+PERF format is enabled when the `GIT_TR2_PERF` environment variable
+is set.
+
+Events are written as lines of the form:
+
+------------
+[<time> SP <filename>:<line> SP+
+    BAR SP] d<depth> SP
+    BAR SP <thread-name> SP+
+    BAR SP <event-name> SP+
+    BAR SP [r<repo-id>] SP+
+    BAR SP [<t_abs>] SP+
+    BAR SP [<t_rel>] SP+
+    BAR SP [<category>] SP+
+    BAR SP DOTS* <perf-event-message>
+    LF
+------------
+
+`<depth>`::
+       is the git process depth.  This is the number of parent
+       git processes.  A top-level git command has depth value "d0".
+       A child of it has depth value "d1".  A second level child
+       has depth value "d2" and so on.
+
+`<thread-name>`::
+       is a unique name for the thread.  The primary thread
+       is called "main".  Other thread names are of the form "th%d:%s"
+       and include a unique number and the name of the thread-proc.
+
+`<event-name>`::
+       is the event name.
+
+`<repo-id>`::
+       when present, is a number indicating the repository
+       in use.  A `def_repo` event is emitted when a repository is
+       opened.  This defines the repo-id and associated worktree.
+       Subsequent repo-specific events will reference this repo-id.
++
+Currently, this is always "r1" for the main repository.
+This field is in anticipation of in-proc submodules in the future.
+
+`<t_abs>`::
+       when present, is the absolute time in seconds since the
+       program started.
+
+`<t_rel>`::
+       when present, is time in seconds relative to the start of
+       the current region.  For a thread-exit event, it is the elapsed
+       time of the thread.
+
+`<category>`::
+       is present on region and data events and is used to
+       indicate a broad category, such as "index" or "status".
+
+`<perf-event-message>`::
+       is a free-form printf message intended for human consumption.
+
+------------
+15:33:33.532712 wt-status.c:2310                  | d0 | main                     | region_enter | r1  |  0.126064 |           | status     | label:print
+15:33:33.532712 wt-status.c:2331                  | d0 | main                     | region_leave | r1  |  0.127568 |  0.001504 | status     | label:print
+------------
+
+If `GIT_TR2_PERF_BRIEF` is true, the `time`, `file`, and `line`
+fields are omitted.
+
+------------
+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload
+------------
+
+The PERF target is intended for interactive performance analysis
+during development and is quite noisy.
+
+=== EVENT Format
+
+EVENT format is enabled when the `GIT_TR2_EVENT` environment
+variable is set.
+
+Each event is a JSON-object containing multiple key/value pairs
+written as a single line and followed by a LF.
+
+------------
+'{' <key> ':' <value> [',' <key> ':' <value>]* '}' LF
+------------
+
+Some key/value pairs are common to all events and some are
+event-specific.
+
+==== Common Key/Value Pairs
+
+The following key/value pairs are common to all events:
+
+------------
+{
+       "event":"version",
+       "sid":"1547659722619736-11614",
+       "thread":"main",
+       "time":"2019-01-16 17:28:42.620713",
+       "file":"common-main.c",
+       "line":38,
+       ...
+}
+------------
+
+`"event":<event>`::
+       is the event name.
+
+`"sid":<sid>`::
+       is the session-id.  This is a unique string to identify the
+       process instance to allow all events emitted by a process to
+       be identified.  A session-id is used instead of a PID because
+       PIDs are recycled by the OS.  For child git processes, the
+       session-id is prepended with the session-id of the parent git
+       process to allow parent-child relationships to be identified
+       during post-processing.
+
+`"thread":<thread>`::
+       is the thread name.
+
+`"time":<time>`::
+       is the UTC time of the event.
+
+`"file":<filename>`::
+       is source file generating the event.
+
+`"line":<line-number>`::
+       is the integer source line number generating the event.
+
+`"repo":<repo-id>`::
+       when present, is the integer repo-id as described previously.
+
+If `GIT_TR2_EVENT_BRIEF` is true, the `file` and `line` fields are omitted
+from all events and the `time` field is only present on the "start" and
+"atexit" events.
+
+==== Event-Specific Key/Value Pairs
+
+`"version"`::
+       This event gives the version of the executable and the EVENT format.
++
+------------
+{
+       "event":"version",
+       ...
+       "evt":"1",                     # EVENT format version
+       "exe":"2.20.1.155.g426c96fcdb" # git version
+}
+------------
+
+`"start"`::
+       This event contains the complete argv received by main().
++
+------------
+{
+       "event":"start",
+       ...
+       "argv":["git","version"]
+}
+------------
+
+`"exit"`::
+       This event is emitted when git calls `exit()`.
++
+------------
+{
+       "event":"exit",
+       ...
+       "t_abs":0.001227, # elapsed time in seconds
+       "code":0          # exit code
+}
+------------
+
+`"atexit"`::
+       This event is emitted by the Trace2 `atexit` routine during
+       final shutdown.  It should be the last event emitted by the
+       process.
++
+(The elapsed time reported here is greater than the time reported in
+the "exit" event because it runs after all other atexit tasks have
+completed.)
++
+------------
+{
+       "event":"atexit",
+       ...
+       "t_abs":0.001227, # elapsed time in seconds
+       "code":0          # exit code
+}
+------------
+
+`"signal"`::
+       This event is emitted when the program is terminated by a user
+       signal.  Depending on the platform, the signal event may
+       prevent the "atexit" event from being generated.
++
+------------
+{
+       "event":"signal",
+       ...
+       "t_abs":0.001227,  # elapsed time in seconds
+       "signal":13        # SIGTERM, SIGINT, etc.
+}
+------------
+
+`"error"`::
+       This event is emitted when one of the `error()`, `die()`,
+       or `usage()` functions are called.
++
+------------
+{
+       "event":"error",
+       ...
+       "msg":"invalid option: --cahced", # formatted error message
+       "fmt":"invalid option: %s"        # error format string
+}
+------------
++
+The error event may be emitted more than once.  The format string
+allows post-processors to group errors by type without worrying
+about specific error arguments.
+
+`"cmd_path"`::
+       This event contains the discovered full path of the git
+       executable (on platforms that are configured to resolve it).
++
+------------
+{
+       "event":"cmd_path",
+       ...
+       "path":"C:/work/gfw/git.exe"
+}
+------------
+
+`"cmd_name"`::
+       This event contains the command name for this git process
+       and the hierarchy of commands from parent git processes.
++
+------------
+{
+       "event":"cmd_name",
+       ...
+       "name":"pack-objects",
+       "hierarchy":"push/pack-objects"
+}
+------------
++
+Normally, the "name" field contains the canonical name of the
+command.  When a canonical name is not available, one of
+these special values are used:
++
+------------
+"_query_"            # "git --html-path"
+"_run_dashed_"       # when "git foo" tries to run "git-foo"
+"_run_shell_alias_"  # alias expansion to a shell command
+"_run_git_alias_"    # alias expansion to a git command
+"_usage_"            # usage error
+------------
+
+`"cmd_mode"`::
+       This event, when present, describes the command variant This
+       event may be emitted more than once.
++
+------------
+{
+       "event":"cmd_mode",
+       ...
+       "name":"branch"
+}
+------------
++
+The "name" field is an arbitrary string to describe the command mode.
+For example, checkout can checkout a branch or an individual file.
+And these variations typically have different performance
+characteristics that are not comparable.
+
+`"alias"`::
+       This event is present when an alias is expanded.
++
+------------
+{
+       "event":"alias",
+       ...
+       "alias":"l",             # registered alias
+       "argv":["log","--graph"] # alias expansion
+}
+------------
+
+`"child_start"`::
+       This event describes a child process that is about to be
+       spawned.
++
+------------
+{
+       "event":"child_start",
+       ...
+       "child_id":2,
+       "child_class":"?",
+       "use_shell":false,
+       "argv":["git","rev-list","--objects","--stdin","--not","--all","--quiet"]
+
+       "hook_name":"<hook_name>"  # present when child_class is "hook"
+       "cd":"<path>"              # present when cd is required
+}
+------------
++
+The "child_id" field can be used to match this child_start with the
+corresponding child_exit event.
++
+The "child_class" field is a rough classification, such as "editor",
+"pager", "transport/*", and "hook".  Unclassified children are classified
+with "?".
+
+`"child_exit"`::
+       This event is generated after the current process has returned
+       from the waitpid() and collected the exit information from the
+       child.
++
+------------
+{
+       "event":"child_exit",
+       ...
+       "child_id":2,
+       "pid":14708,     # child PID
+       "code":0,        # child exit-code
+       "t_rel":0.110605 # observed run-time of child process
+}
+------------
++
+Note that the session-id of the child process is not available to
+the current/spawning process, so the child's PID is reported here as
+a hint for post-processing.  (But it is only a hint because the child
+proces may be a shell script which doesn't have a session-id.)
++
+Note that the `t_rel` field contains the observed run time in seconds
+for the child process (starting before the fork/exec/spawn and
+stopping after the waitpid() and includes OS process creation overhead).
+So this time will be slightly larger than the atexit time reported by
+the child process itself.
+
+`"exec"`::
+       This event is generated before git attempts to `exec()`
+       another command rather than starting a child process.
++
+------------
+{
+       "event":"exec",
+       ...
+       "exec_id":0,
+       "exe":"git",
+       "argv":["foo", "bar"]
+}
+------------
++
+The "exec_id" field is a command-unique id and is only useful if the
+`exec()` fails and a corresponding exec_result event is generated.
+
+`"exec_result"`::
+       This event is generated if the `exec()` fails and control
+       returns to the current git command.
++
+------------
+{
+       "event":"exec_result",
+       ...
+       "exec_id":0,
+       "code":1      # error code (errno) from exec()
+}
+------------
+
+`"thread_start"`::
+       This event is generated when a thread is started.  It is
+       generated from *within* the new thread's thread-proc (for TLS
+       reasons).
++
+------------
+{
+       "event":"thread_start",
+       ...
+       "thread":"th02:preload_thread" # thread name
+}
+------------
+
+`"thread_exit"`::
+       This event is generated when a thread exits.  It is generated
+       from *within* the thread's thread-proc (for TLS reasons).
++
+------------
+{
+       "event":"thread_exit",
+       ...
+       "thread":"th02:preload_thread", # thread name
+       "t_rel":0.007328                # thread elapsed time
+}
+------------
+
+`"def_param"`::
+       This event is generated to log a global parameter.
++
+------------
+{
+       "event":"def_param",
+       ...
+       "param":"core.abbrev",
+       "value":"7"
+}
+------------
+
+`"def_repo"`::
+       This event defines a repo-id and associates it with the root
+       of the worktree.
++
+------------
+{
+       "event":"def_repo",
+       ...
+       "repo":1,
+       "worktree":"/Users/jeffhost/work/gfw"
+}
+------------
++
+As stated earlier, the repo-id is currently always 1, so there will
+only be one def_repo event.  Later, if in-proc submodules are
+supported, a def_repo event should be emitted for each submodule
+visited.
+
+`"region_enter"`::
+       This event is generated when entering a region.
++
+------------
+{
+       "event":"region_enter",
+       ...
+       "repo":1,                # optional
+       "nesting":1,             # current region stack depth
+       "category":"index",      # optional
+       "label":"do_read_index", # optional
+       "msg":".git/index"       # optional
+}
+------------
++
+The `category` field may be used in a future enhancement to
+do category-based filtering.
++
+The `GIT_TR2_EVENT_NESTING` environment variable can be used to
+filter deeply nested regions and data events.  It defaults to "2".
+
+`"region_leave"`::
+       This event is generated when leaving a region.
++
+------------
+{
+       "event":"region_leave",
+       ...
+       "repo":1,                # optional
+       "t_rel":0.002876,        # time spent in region in seconds
+       "nesting":1,             # region stack depth
+       "category":"index",      # optional
+       "label":"do_read_index", # optional
+       "msg":".git/index"       # optional
+}
+------------
+
+`"data"`::
+       This event is generated to log a thread- and region-local
+       key/value pair.
++
+------------
+{
+       "event":"data",
+       ...
+       "repo":1,              # optional
+       "t_abs":0.024107,      # absolute elapsed time
+       "t_rel":0.001031,      # elapsed time in region/thread
+       "nesting":2,           # region stack depth
+       "category":"index",
+       "key":"read/cache_nr",
+       "value":"3552"
+}
+------------
++
+The "value" field may be an integer or a string.
+
+`"data-json"`::
+       This event is generated to log a pre-formatted JSON string
+       containing structured data.
++
+------------
+{
+       "event":"data_json",
+       ...
+       "repo":1,              # optional
+       "t_abs":0.015905,
+       "t_rel":0.015905,
+       "nesting":1,
+       "category":"process",
+       "key":"windows/ancestry",
+       "value":["bash.exe","bash.exe"]
+}
+------------
+
+== Example Trace2 API Usage
+
+Here is a hypothetical usage of the Trace2 API showing the intended
+usage (without worrying about the actual Git details).
+
+Initialization::
+
+       Initialization happens in `main()`.  Behind the scenes, an
+       `atexit` and `signal` handler are registered.
++
+----------------
+int main(int argc, const char **argv)
+{
+       int exit_code;
+
+       trace2_initialize();
+       trace2_cmd_start(argv);
+
+       exit_code = cmd_main(argc, argv);
+
+       trace2_cmd_exit(exit_code);
+
+       return exit_code;
+}
+----------------
+
+Command Details::
+
+       After the basics are established, additional command
+       information can be sent to Trace2 as it is discovered.
++
+----------------
+int cmd_checkout(int argc, const char **argv)
+{
+       trace2_cmd_name("checkout");
+       trace2_cmd_mode("branch");
+       trace2_def_repo(the_repository);
+
+       // emit "def_param" messages for "interesting" config settings.
+       trace2_cmd_list_config();
+
+       if (do_something())
+           trace2_cmd_error("Path '%s': cannot do something", path);
+
+       return 0;
+}
+----------------
+
+Child Processes::
+
+       Wrap code spawning child processes.
++
+----------------
+void run_child(...)
+{
+       int child_exit_code;
+       struct child_process cmd = CHILD_PROCESS_INIT;
+       ...
+       cmd.trace2_child_class = "editor";
+
+       trace2_child_start(&cmd);
+       child_exit_code = spawn_child_and_wait_for_it();
+       trace2_child_exit(&cmd, child_exit_code);
+}
+----------------
++
+For example, the following fetch command spawned ssh, index-pack,
+rev-list, and gc.  This example also shows that fetch took
+5.199 seconds and of that 4.932 was in ssh.
++
+----------------
+$ export GIT_TR2_BRIEF=1
+$ export GIT_TR2=~/log.normal
+$ git fetch origin
+...
+----------------
++
+----------------
+$ cat ~/log.normal
+version 2.20.1.vfs.1.1.47.g534dbe1ad1
+start git fetch origin
+worktree /Users/jeffhost/work/gfw
+cmd_name fetch (fetch)
+child_start[0] ssh git@github.com ...
+child_start[1] git index-pack ...
+... (Trace2 events from child processes omitted)
+child_exit[1] pid:14707 code:0 elapsed:0.076353
+child_exit[0] pid:14706 code:0 elapsed:4.931869
+child_start[2] git rev-list ...
+... (Trace2 events from child process omitted)
+child_exit[2] pid:14708 code:0 elapsed:0.110605
+child_start[3] git gc --auto
+... (Trace2 events from child process omitted)
+child_exit[3] pid:14709 code:0 elapsed:0.006240
+exit elapsed:5.198503 code:0
+atexit elapsed:5.198541 code:0
+----------------
++
+When a git process is a (direct or indirect) child of another
+git process, it inherits Trace2 context information.  This
+allows the child to print the command hierarchy.  This example
+shows gc as child[3] of fetch.  When the gc process reports
+its name as "gc", it also reports the hierarchy as "fetch/gc".
+(In this example, trace2 messages from the child process is
+indented for clarity.)
++
+----------------
+$ export GIT_TR2_BRIEF=1
+$ export GIT_TR2=~/log.normal
+$ git fetch origin
+...
+----------------
++
+----------------
+$ cat ~/log.normal
+version 2.20.1.160.g5676107ecd.dirty
+start git fetch official
+worktree /Users/jeffhost/work/gfw
+cmd_name fetch (fetch)
+...
+child_start[3] git gc --auto
+    version 2.20.1.160.g5676107ecd.dirty
+    start /Users/jeffhost/work/gfw/git gc --auto
+    worktree /Users/jeffhost/work/gfw
+    cmd_name gc (fetch/gc)
+    exit elapsed:0.001959 code:0
+    atexit elapsed:0.001997 code:0
+child_exit[3] pid:20303 code:0 elapsed:0.007564
+exit elapsed:3.868938 code:0
+atexit elapsed:3.868970 code:0
+----------------
+
+Regions::
+
+       Regions can be use to time an interesting section of code.
++
+----------------
+void wt_status_collect(struct wt_status *s)
+{
+       trace2_region_enter("status", "worktrees", s->repo);
+       wt_status_collect_changes_worktree(s);
+       trace2_region_leave("status", "worktrees", s->repo);
+
+       trace2_region_enter("status", "index", s->repo);
+       wt_status_collect_changes_index(s);
+       trace2_region_leave("status", "index", s->repo);
+
+       trace2_region_enter("status", "untracked", s->repo);
+       wt_status_collect_untracked(s);
+       trace2_region_leave("status", "untracked", s->repo);
+}
+
+void wt_status_print(struct wt_status *s)
+{
+       trace2_region_enter("status", "print", s->repo);
+       switch (s->status_format) {
+           ...
+       }
+       trace2_region_leave("status", "print", s->repo);
+}
+----------------
++
+In this example, scanning for untracked files ran from +0.012568 to
++0.027149 (since the process started) and took 0.014581 seconds.
++
+----------------
+$ export GIT_TR2_PERF_BRIEF=1
+$ export GIT_TR2_PERF=~/log.perf
+$ git status
+...
+
+$ cat ~/log.perf
+d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty
+d0 | main                     | start        |     |           |           |            | git status
+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw
+d0 | main                     | cmd_name     |     |           |           |            | status (status)
+...
+d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees
+d0 | main                     | region_leave | r1  |  0.011236 |  0.000248 | status     | label:worktrees
+d0 | main                     | region_enter | r1  |  0.011260 |           | status     | label:index
+d0 | main                     | region_leave | r1  |  0.012542 |  0.001282 | status     | label:index
+d0 | main                     | region_enter | r1  |  0.012568 |           | status     | label:untracked
+d0 | main                     | region_leave | r1  |  0.027149 |  0.014581 | status     | label:untracked
+d0 | main                     | region_enter | r1  |  0.027411 |           | status     | label:print
+d0 | main                     | region_leave | r1  |  0.028741 |  0.001330 | status     | label:print
+d0 | main                     | exit         |     |  0.028778 |           |            | code:0
+d0 | main                     | atexit       |     |  0.028809 |           |            | code:0
+----------------
++
+Regions may be nested.  This causes messages to be indented in the
+PERF target, for example.
+Elapsed times are relative to the start of the correpsonding nesting
+level as expected.  For example, if we add region message to:
++
+----------------
+static enum path_treatment read_directory_recursive(struct dir_struct *dir,
+       struct index_state *istate, const char *base, int baselen,
+       struct untracked_cache_dir *untracked, int check_only,
+       int stop_at_first_file, const struct pathspec *pathspec)
+{
+       enum path_treatment state, subdir_state, dir_state = path_none;
+
+       trace2_region_enter_printf("dir", "read_recursive", NULL, "%.*s", baselen, base);
+       ...
+       trace2_region_leave_printf("dir", "read_recursive", NULL, "%.*s", baselen, base);
+       return dir_state;
+}
+----------------
++
+We can further investigate the time spent scanning for untracked files.
++
+----------------
+$ export GIT_TR2_PERF_BRIEF=1
+$ export GIT_TR2_PERF=~/log.perf
+$ git status
+...
+$ cat ~/log.perf
+d0 | main                     | version      |     |           |           |            | 2.20.1.162.gb4ccea44db.dirty
+d0 | main                     | start        |     |           |           |            | git status
+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw
+d0 | main                     | cmd_name     |     |           |           |            | status (status)
+...
+d0 | main                     | region_enter | r1  |  0.015047 |           | status     | label:untracked
+d0 | main                     | region_enter |     |  0.015132 |           | dir        | ..label:read_recursive
+d0 | main                     | region_enter |     |  0.016341 |           | dir        | ....label:read_recursive vcs-svn/
+d0 | main                     | region_leave |     |  0.016422 |  0.000081 | dir        | ....label:read_recursive vcs-svn/
+d0 | main                     | region_enter |     |  0.016446 |           | dir        | ....label:read_recursive xdiff/
+d0 | main                     | region_leave |     |  0.016522 |  0.000076 | dir        | ....label:read_recursive xdiff/
+d0 | main                     | region_enter |     |  0.016612 |           | dir        | ....label:read_recursive git-gui/
+d0 | main                     | region_enter |     |  0.016698 |           | dir        | ......label:read_recursive git-gui/po/
+d0 | main                     | region_enter |     |  0.016810 |           | dir        | ........label:read_recursive git-gui/po/glossary/
+d0 | main                     | region_leave |     |  0.016863 |  0.000053 | dir        | ........label:read_recursive git-gui/po/glossary/
+...
+d0 | main                     | region_enter |     |  0.031876 |           | dir        | ....label:read_recursive builtin/
+d0 | main                     | region_leave |     |  0.032270 |  0.000394 | dir        | ....label:read_recursive builtin/
+d0 | main                     | region_leave |     |  0.032414 |  0.017282 | dir        | ..label:read_recursive
+d0 | main                     | region_leave | r1  |  0.032454 |  0.017407 | status     | label:untracked
+...
+d0 | main                     | exit         |     |  0.034279 |           |            | code:0
+d0 | main                     | atexit       |     |  0.034322 |           |            | code:0
+----------------
++
+Trace2 regions are similar to the existing trace_performance_enter()
+and trace_performance_leave() routines, but are thread safe and
+maintain per-thread stacks of timers.
+
+Data Messages::
+
+       Data messages added to a region.
++
+----------------
+int read_index_from(struct index_state *istate, const char *path,
+       const char *gitdir)
+{
+       trace2_region_enter_printf("index", "do_read_index", the_repository, "%s", path);
+
+       ...
+
+       trace2_data_intmax("index", the_repository, "read/version", istate->version);
+       trace2_data_intmax("index", the_repository, "read/cache_nr", istate->cache_nr);
+
+       trace2_region_leave_printf("index", "do_read_index", the_repository, "%s", path);
+}
+----------------
++
+This example shows that the index contained 3552 entries.
++
+----------------
+$ export GIT_TR2_PERF_BRIEF=1
+$ export GIT_TR2_PERF=~/log.perf
+$ git status
+...
+$ cat ~/log.perf
+d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty
+d0 | main                     | start        |     |           |           |            | git status
+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw
+d0 | main                     | cmd_name     |     |           |           |            | status (status)
+d0 | main                     | region_enter | r1  |  0.001791 |           | index      | label:do_read_index .git/index
+d0 | main                     | data         | r1  |  0.002494 |  0.000703 | index      | ..read/version:2
+d0 | main                     | data         | r1  |  0.002520 |  0.000729 | index      | ..read/cache_nr:3552
+d0 | main                     | region_leave | r1  |  0.002539 |  0.000748 | index      | label:do_read_index .git/index
+...
+----------------
+
+Thread Events::
+
+       Thread messages added to a thread-proc.
++
+For example, the multithreaded preload-index code can be
+instrumented with a region around the thread pool and then
+per-thread start and exit events within the threadproc.
++
+----------------
+static void *preload_thread(void *_data)
+{
+       // start the per-thread clock and emit a message.
+       trace2_thread_start("preload_thread");
+
+       // report which chunk of the array this thread was assigned.
+       trace2_data_intmax("index", the_repository, "offset", p->offset);
+       trace2_data_intmax("index", the_repository, "count", nr);
+
+       do {
+           ...
+       } while (--nr > 0);
+       ...
+
+       // report elapsed time taken by this thread.
+       trace2_thread_exit();
+       return NULL;
+}
+
+void preload_index(struct index_state *index,
+       const struct pathspec *pathspec,
+       unsigned int refresh_flags)
+{
+       trace2_region_enter("index", "preload", the_repository);
+
+       for (i = 0; i < threads; i++) {
+           ... /* create thread */
+       }
+
+       for (i = 0; i < threads; i++) {
+           ... /* join thread */
+       }
+
+       trace2_region_leave("index", "preload", the_repository);
+}
+----------------
++
+In this example preload_index() was executed by the `main` thread
+and started the `preload` region.  Seven threads, named
+`th01:preload_thread` through `th07:preload_thread`, were started.
+Events from each thread are atomically appended to the shared target
+stream as they occur so they may appear in random order with respect
+other threads. Finally, the main thread waits for the threads to
+finish and leaves the region.
++
+Data events are tagged with the active thread name.  They are used
+to report the per-thread parameters.
++
+----------------
+$ export GIT_TR2_PERF_BRIEF=1
+$ export GIT_TR2_PERF=~/log.perf
+$ git status
+...
+$ cat ~/log.perf
+...
+d0 | main                     | region_enter | r1  |  0.002595 |           | index      | label:preload
+d0 | th01:preload_thread      | thread_start |     |  0.002699 |           |            |
+d0 | th02:preload_thread      | thread_start |     |  0.002721 |           |            |
+d0 | th01:preload_thread      | data         | r1  |  0.002736 |  0.000037 | index      | offset:0
+d0 | th02:preload_thread      | data         | r1  |  0.002751 |  0.000030 | index      | offset:2032
+d0 | th03:preload_thread      | thread_start |     |  0.002711 |           |            |
+d0 | th06:preload_thread      | thread_start |     |  0.002739 |           |            |
+d0 | th01:preload_thread      | data         | r1  |  0.002766 |  0.000067 | index      | count:508
+d0 | th06:preload_thread      | data         | r1  |  0.002856 |  0.000117 | index      | offset:2540
+d0 | th03:preload_thread      | data         | r1  |  0.002824 |  0.000113 | index      | offset:1016
+d0 | th04:preload_thread      | thread_start |     |  0.002710 |           |            |
+d0 | th02:preload_thread      | data         | r1  |  0.002779 |  0.000058 | index      | count:508
+d0 | th06:preload_thread      | data         | r1  |  0.002966 |  0.000227 | index      | count:508
+d0 | th07:preload_thread      | thread_start |     |  0.002741 |           |            |
+d0 | th07:preload_thread      | data         | r1  |  0.003017 |  0.000276 | index      | offset:3048
+d0 | th05:preload_thread      | thread_start |     |  0.002712 |           |            |
+d0 | th05:preload_thread      | data         | r1  |  0.003067 |  0.000355 | index      | offset:1524
+d0 | th05:preload_thread      | data         | r1  |  0.003090 |  0.000378 | index      | count:508
+d0 | th07:preload_thread      | data         | r1  |  0.003037 |  0.000296 | index      | count:504
+d0 | th03:preload_thread      | data         | r1  |  0.002971 |  0.000260 | index      | count:508
+d0 | th04:preload_thread      | data         | r1  |  0.002983 |  0.000273 | index      | offset:508
+d0 | th04:preload_thread      | data         | r1  |  0.007311 |  0.004601 | index      | count:508
+d0 | th05:preload_thread      | thread_exit  |     |  0.008781 |  0.006069 |            |
+d0 | th01:preload_thread      | thread_exit  |     |  0.009561 |  0.006862 |            |
+d0 | th03:preload_thread      | thread_exit  |     |  0.009742 |  0.007031 |            |
+d0 | th06:preload_thread      | thread_exit  |     |  0.009820 |  0.007081 |            |
+d0 | th02:preload_thread      | thread_exit  |     |  0.010274 |  0.007553 |            |
+d0 | th07:preload_thread      | thread_exit  |     |  0.010477 |  0.007736 |            |
+d0 | th04:preload_thread      | thread_exit  |     |  0.011657 |  0.008947 |            |
+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload
+...
+d0 | main                     | exit         |     |  0.029996 |           |            | code:0
+d0 | main                     | atexit       |     |  0.030027 |           |            | code:0
+----------------
++
+In this example, the preload region took 0.009122 seconds.  The 7 threads
+took between 0.006069 and 0.008947 seconds to work on their portion of
+the index.  Thread "th01" worked on 508 items at offset 0.  Thread "th02"
+worked on 508 items at offset 2032.  Thread "th04" worked on 508 itemts
+at offset 508.
++
+This example also shows that thread names are assigned in a racy manner
+as each thread starts and allocates TLS storage.
+
+== Future Work
+
+=== Relationship to the Existing Trace Api (api-trace.txt)
+
+There are a few issues to resolve before we can completely
+switch to Trace2.
+
+* Updating existing tests that assume GIT_TRACE format messages.
+
+* How to best handle custom GIT_TRACE_<key> messages?
+
+** The GIT_TRACE_<key> mechanism allows each <key> to write to a
+different file (in addition to just stderr).
+
+** Do we want to maintain that ability or simply write to the existing
+Trace2 targets (and convert <key> to a "category").
index 1c0086e287eadec2f6e1ffa1ca0dad49d7bbbe28..844629c8c441e786734c42e749293f2a718ed288 100644 (file)
@@ -20,8 +20,8 @@ More interesting possibilities exist, though, such as:
   * one side of history renames x -> z, and the other renames some file to
     x/e, causing the need for the merge to do a transitive rename.
 
-  * one side of history renames x -> z, but also renames all files within
-    x.  For example, x/a -> z/alpha, x/b -> z/bravo, etc.
+  * one side of history renames x -> z, but also renames all files within x.
+    For example, x/a -> z/alpha, x/b -> z/bravo, etc.
 
   * both 'x' and 'y' being merged into a single directory 'z', with a
     directory rename being detected for both x->z and y->z.
index 332d209b58ca42dc303c757f2b31f7a4f3d033f4..2b267c0da6b2d88c8ab9425745b7239e3f867b6e 100644 (file)
@@ -1,6 +1,10 @@
 Git Protocol Capabilities
 =========================
 
+NOTE: this document describes capabilities for versions 0 and 1 of the pack
+protocol. For version 2, please refer to the link:protocol-v2.html[protocol-v2]
+doc.
+
 Servers SHOULD support all capabilities defined in this document.
 
 On the very first line of the initial server response of either
@@ -172,6 +176,20 @@ agent strings are purely informative for statistics and debugging
 purposes, and MUST NOT be used to programmatically assume the presence
 or absence of particular features.
 
+symref
+------
+
+This parameterized capability is used to inform the receiver which symbolic ref
+points to which ref; for example, "symref=HEAD:refs/heads/master" tells the
+receiver that HEAD points to master. This capability can be repeated to
+represent multiple symrefs.
+
+Servers SHOULD include this capability for the HEAD symref if it is one of the
+refs being sent.
+
+Clients MAY use the parameters from this capability to select the proper initial
+branch when cloning a repository.
+
 shallow
 -------
 
index 13f835f9cde4fd2e238db6c11bb2cf38a626a1b2..3fc4065da2ecdb7176766f1c5f5b9243cf4ab718 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.21.0
+DEF_VER=v2.21.GIT
 
 LF='
 '
index abbbdf0a68790ecae25119e8f9598354e78e4e10..9398de8f6e39f9e4e7bbbc2f37ea90d64de84acd 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -479,7 +479,11 @@ all::
 #
 # Define DEVELOPER to enable more compiler warnings. Compiler version
 # and family are auto detected, but could be overridden by defining
-# COMPILER_FEATURES (see config.mak.dev)
+# COMPILER_FEATURES (see config.mak.dev). You can still set
+# CFLAGS="..." in combination with DEVELOPER enables, whether that's
+# for tweaking something unrelated (e.g. optimization level), or for
+# selectively overriding something DEVELOPER or one of the DEVOPTS
+# (see just below) brings in.
 #
 # When DEVELOPER is set, DEVOPTS can be used to control compiler
 # options.  This variable contains keywords separated by
@@ -506,17 +510,8 @@ GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
 -include GIT-VERSION-FILE
 
-# CFLAGS and LDFLAGS are for the users to override from the command line.
-
-CFLAGS = -g -O2 -Wall
-LDFLAGS =
-ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS)
-ALL_LDFLAGS = $(LDFLAGS)
-STRIP ?= strip
-
-# Create as necessary, replace existing, make ranlib unneeded.
-ARFLAGS = rcs
-
+# Set our default configuration.
+#
 # Among the variables below, these:
 #   gitexecdir
 #   template_dir
@@ -561,6 +556,7 @@ perllibdir_relative = $(patsubst $(prefix)/%,%,$(perllibdir))
 
 export prefix bindir sharedir sysconfdir gitwebdir perllibdir localedir
 
+# Set our default programs
 CC = cc
 AR = ar
 RM = rm -f
@@ -573,29 +569,14 @@ TCLTK_PATH = wish
 XGETTEXT = xgettext
 MSGFMT = msgfmt
 CURL_CONFIG = curl-config
-PTHREAD_LIBS = -lpthread
-PTHREAD_CFLAGS =
 GCOV = gcov
+STRIP = strip
 SPATCH = spatch
 
 export TCL_PATH TCLTK_PATH
 
-# user customisation variable for 'sparse' target
-SPARSE_FLAGS ?=
-# internal/platform customisation variable for 'sparse'
-SP_EXTRA_FLAGS =
-
-SPATCH_FLAGS = --all-includes --patch .
-
-
-
-### --- END CONFIGURATION SECTION ---
-
-# Those must not be GNU-specific; they are shared with perl/ which may
-# be built by a different compiler. (Note that this is an artifact now
-# but it still might be nice to keep that distinction.)
-BASIC_CFLAGS = -I.
-BASIC_LDFLAGS =
+# Set our default LIBS variables
+PTHREAD_LIBS = -lpthread
 
 # Guard against environment variables
 BUILTIN_OBJS =
@@ -773,6 +754,7 @@ TEST_BUILTINS_OBJS += test-string-list.o
 TEST_BUILTINS_OBJS += test-submodule-config.o
 TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o
 TEST_BUILTINS_OBJS += test-subprocess.o
+TEST_BUILTINS_OBJS += test-trace2.o
 TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
 TEST_BUILTINS_OBJS += test-xml-encode.o
 TEST_BUILTINS_OBJS += test-wildmatch.o
@@ -841,7 +823,8 @@ VCSSVN_LIB = vcs-svn/lib.a
 
 GENERATED_H += command-list.h
 
-LIB_H = $(shell $(FIND) . \
+LIB_H := $(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null || \
+       $(FIND) . \
        -name .git -prune -o \
        -name t -prune -o \
        -name Documentation -prune -o \
@@ -1017,6 +1000,16 @@ LIB_OBJS += tempfile.o
 LIB_OBJS += thread-utils.o
 LIB_OBJS += tmp-objdir.o
 LIB_OBJS += trace.o
+LIB_OBJS += trace2.o
+LIB_OBJS += trace2/tr2_cfg.o
+LIB_OBJS += trace2/tr2_cmd_name.o
+LIB_OBJS += trace2/tr2_dst.o
+LIB_OBJS += trace2/tr2_sid.o
+LIB_OBJS += trace2/tr2_tbuf.o
+LIB_OBJS += trace2/tr2_tgt_event.o
+LIB_OBJS += trace2/tr2_tgt_normal.o
+LIB_OBJS += trace2/tr2_tgt_perf.o
+LIB_OBJS += trace2/tr2_tls.o
 LIB_OBJS += trailer.o
 LIB_OBJS += transport.o
 LIB_OBJS += transport-helper.o
@@ -1165,6 +1158,25 @@ ifeq ($(wildcard sha1collisiondetection/lib/sha1.h),sha1collisiondetection/lib/s
 DC_SHA1_SUBMODULE = auto
 endif
 
+# Set CFLAGS, LDFLAGS and other *FLAGS variables. These might be
+# tweaked by config.* below as well as the command-line, both of
+# which'll override these defaults.
+CFLAGS = -g -O2 -Wall
+LDFLAGS =
+BASIC_CFLAGS = -I.
+BASIC_LDFLAGS =
+
+# library flags
+ARFLAGS = rcs
+PTHREAD_CFLAGS =
+
+# For the 'sparse' target
+SPARSE_FLAGS ?=
+SP_EXTRA_FLAGS =
+
+# For the 'coccicheck' target
+SPATCH_FLAGS = --all-includes --patch .
+
 include config.mak.uname
 -include config.mak.autogen
 -include config.mak
@@ -1173,6 +1185,9 @@ ifdef DEVELOPER
 include config.mak.dev
 endif
 
+ALL_CFLAGS = $(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS)
+ALL_LDFLAGS = $(LDFLAGS)
+
 comma := ,
 empty :=
 space := $(empty) $(empty)
@@ -1597,7 +1612,9 @@ ifdef NO_INET_PTON
        LIB_OBJS += compat/inet_pton.o
        BASIC_CFLAGS += -DNO_INET_PTON
 endif
-ifndef NO_UNIX_SOCKETS
+ifdef NO_UNIX_SOCKETS
+       BASIC_CFLAGS += -DNO_UNIX_SOCKETS
+else
        LIB_OBJS += unix-socket.o
        PROGRAM_OBJS += credential-cache.o
        PROGRAM_OBJS += credential-cache--daemon.o
@@ -2364,7 +2381,7 @@ else
 # should _not_ be included here, since they are necessary even when
 # building an object for the first time.
 
-$(OBJECTS): $(LIB_H)
+$(OBJECTS): $(LIB_H) $(GENERATED_H)
 endif
 
 exec-cmd.sp exec-cmd.s exec-cmd.o: GIT-PREFIX
@@ -2737,7 +2754,10 @@ $(SP_OBJ): %.sp: %.c GIT-CFLAGS FORCE
 sparse: $(SP_OBJ)
 
 GEN_HDRS := command-list.h unicode-width.h
-EXCEPT_HDRS := $(GEN_HDRS) compat% xdiff%
+EXCEPT_HDRS := $(GEN_HDRS) compat/% xdiff/%
+ifndef GCRYPT_SHA256
+       EXCEPT_HDRS += sha256/gcrypt.h
+endif
 CHK_HDRS = $(filter-out $(EXCEPT_HDRS),$(patsubst ./%,%,$(LIB_H)))
 HCO = $(patsubst %.h,%.hco,$(CHK_HDRS))
 
index 31e21359a89066faf934359889ac76924dd86893..0b6d9fdbcf0cc440914446e494fde666c893562f 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.21.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.22.0.txt
\ No newline at end of file
diff --git a/apply.c b/apply.c
index 892ede5a318f751c80fcceab036a8cbaee391774..f15afa9f6af98ae41af60eb2296ee35d38ccc7fd 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -56,6 +56,10 @@ static int parse_whitespace_option(struct apply_state *state, const char *option
                state->ws_error_action = correct_ws_error;
                return 0;
        }
+       /*
+        * Please update $__git_whitespacelist in git-completion.bash
+        * when you add new options.
+        */
        return error(_("unrecognized whitespace option '%s'"), option);
 }
 
diff --git a/attr.c b/attr.c
index fdd110bec55cb3bc5c8ebbb8cffbc33cced530b2..93dc16b59cd6eff1d5c962ac2c4b01662948a93e 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -431,14 +431,14 @@ fail_return:
  * Like info/exclude and .gitignore, the attribute information can
  * come from many places.
  *
- * (1) .gitattribute file of the same directory;
- * (2) .gitattribute file of the parent directory if (1) does not have
+ * (1) .gitattributes file of the same directory;
+ * (2) .gitattributes file of the parent directory if (1) does not have
  *      any match; this goes recursively upwards, just like .gitignore.
  * (3) $GIT_DIR/info/attributes, which overrides both of the above.
  *
  * In the same file, later entries override the earlier match, so in the
  * global list, we would have entries from info/attributes the earliest
- * (reading the file from top to bottom), .gitattribute of the root
+ * (reading the file from top to bottom), .gitattributes of the root
  * directory (again, reading the file from top to bottom) down to the
  * current directory, and then scan the list backwards to find the first match.
  * This is exactly the same as what is_excluded() does in dir.c to deal with
@@ -899,7 +899,7 @@ static void prepare_attr_stack(const struct index_state *istate,
         * set of attribute definitions, followed by the contents
         * of $(prefix)/etc/gitattributes and a file specified by
         * core.attributesfile.  Then, contents from
-        * .gitattribute files from directories closer to the
+        * .gitattributes files from directories closer to the
         * root to the ones in deeper directories are pushed
         * to the stack.  Finally, at the very top of the stack
         * we always keep the contents of $GIT_DIR/info/attributes.
index 3af955c4bc4e1c7cd155a5f2e460cee38af732cd..e87ac29a51be4126415aed1bc9f4638660232010 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -896,24 +896,15 @@ static void show_diff_tree(struct repository *r,
                           const char *prefix,
                           struct commit *commit)
 {
+       const char *argv[] = {
+               "diff-tree", "--pretty", "--stat", "--summary", "--cc", NULL
+       };
        struct rev_info opt;
 
-       /* diff-tree init */
+       git_config(git_diff_ui_config, NULL);
        repo_init_revisions(r, &opt, prefix);
-       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
-       opt.abbrev = 0;
-       opt.diff = 1;
 
-       /* This is what "--pretty" does */
-       opt.verbose_header = 1;
-       opt.use_terminator = 0;
-       opt.commit_format = CMIT_FMT_DEFAULT;
-
-       /* diff-tree init */
-       if (!opt.diffopt.output_format)
-               opt.diffopt.output_format = DIFF_FORMAT_RAW;
-
-       setup_revisions(0, NULL, &opt, NULL);
+       setup_revisions(ARRAY_SIZE(argv) - 1, argv, &opt, NULL);
        log_tree_commit(&opt, commit);
 }
 
diff --git a/blame.c b/blame.c
index da57233cbbd98d41bdfeba46e861b1f5f61dfbe2..5c07dec1903545738642c17e94cc1fa060ba15d3 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -204,7 +204,8 @@ static struct commit *fake_working_tree_commit(struct repository *r,
 
        origin = make_origin(commit, path);
 
-       ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
+       ident = fmt_ident("Not Committed Yet", "not.committed.yet",
+                       WANT_BLANK_IDENT, NULL, 0);
        strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n");
        for (parent = commit->parents; parent; parent = parent->next)
                strbuf_addf(&msg, "parent %s\n",
index 58a2aef28bbcd715660422dc37042e79d4d350c1..4fb107a9d1bdf36ff4c71d761abdda4d50d0e24b 100644 (file)
@@ -453,6 +453,7 @@ static int run_post_rewrite_hook(const struct am_state *state)
 
        cp.in = xopen(am_path(state, "rewritten"), O_RDONLY);
        cp.stdout_to_stderr = 1;
+       cp.trace2_hook_name = "post-rewrite";
 
        ret = run_command(&cp);
 
@@ -1579,6 +1580,7 @@ static void do_commit(const struct am_state *state)
        }
 
        author = fmt_ident(state->author_name, state->author_email,
+               WANT_AUTHOR_IDENT,
                        state->ignore_date ? NULL : state->author_date,
                        IDENT_STRICT);
 
@@ -2119,6 +2121,10 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
                *opt_value = PATCH_FORMAT_HG;
        else if (!strcmp(arg, "mboxrd"))
                *opt_value = PATCH_FORMAT_MBOXRD;
+       /*
+        * Please update $__git_patchformat in git-completion.bash
+        * when you add new options
+        */
        else
                return error(_("Invalid value for --patch-format: %s"), arg);
        return 0;
index 581de0d8322681ef11026b3e36a81c7baf0fda38..177c1022a0c46dbd8983224719d4d9cc0106a9fb 100644 (file)
@@ -814,7 +814,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                 * and are only included here to get included in the "-h"
                 * output:
                 */
-               { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb },
+               { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, NULL, 0, parse_opt_unknown_cb },
 
                OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL),
                OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")),
index 1be727209b7ceaead46d0bc5e4b9666fbe7ef7a1..4c830557306398360e06f0e53166e78a4c590da4 100644 (file)
@@ -443,6 +443,21 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
        free(to_free);
 }
 
+static void print_current_branch_name(void)
+{
+       int flags;
+       const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+       const char *shortname;
+       if (!refname)
+               die(_("could not resolve HEAD"));
+       else if (!(flags & REF_ISSYMREF))
+               return;
+       else if (skip_prefix(refname, "refs/heads/", &shortname))
+               puts(shortname);
+       else
+               die(_("HEAD (%s) points outside of refs/heads/"), refname);
+}
+
 static void reject_rebase_or_bisect_branch(const char *target)
 {
        struct worktree **worktrees = get_worktrees(0);
@@ -581,6 +596,7 @@ static int edit_branch_description(const char *branch_name)
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
        int delete = 0, rename = 0, copy = 0, force = 0, list = 0;
+       int show_current = 0;
        int reflog = 0, edit_description = 0;
        int quiet = 0, unset_upstream = 0;
        const char *new_upstream = NULL;
@@ -620,6 +636,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_BIT('c', "copy", &copy, N_("copy a branch and its reflog"), 1),
                OPT_BIT('C', NULL, &copy, N_("copy a branch, even if target exists"), 2),
                OPT_BOOL('l', "list", &list, N_("list branch names")),
+               OPT_BOOL(0, "show-current", &show_current, N_("show current branch name")),
                OPT_BOOL(0, "create-reflog", &reflog, N_("create the branch's reflog")),
                OPT_BOOL(0, "edit-description", &edit_description,
                         N_("edit the description for the branch")),
@@ -662,14 +679,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
 
-       if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc == 0)
+       if (!delete && !rename && !copy && !edit_description && !new_upstream &&
+           !show_current && !unset_upstream && argc == 0)
                list = 1;
 
        if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
            filter.no_commit)
                list = 1;
 
-       if (!!delete + !!rename + !!copy + !!new_upstream +
+       if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +
            list + unset_upstream > 1)
                usage_with_options(builtin_branch_usage, options);
 
@@ -697,6 +715,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                if (!argc)
                        die(_("branch name required"));
                return delete_branches(argc, argv, delete > 1, filter.kind, quiet);
+       } else if (show_current) {
+               print_current_branch_name();
+               return 0;
        } else if (list) {
                /*  git branch --local also shows HEAD when it is detached */
                if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
index 24b8593b938b67baf4a662f2463efdd35562873a..0e6037b2968f326ca2781fa141d74683e52c983c 100644 (file)
@@ -46,6 +46,7 @@ struct checkout_opts {
        int ignore_other_worktrees;
        int show_progress;
        int count_checkout_paths;
+       int overlay_mode;
        /*
         * If new checkout options are added, skip_merge_working_tree
         * should be updated accordingly.
@@ -135,7 +136,8 @@ static int skip_same_name(const struct cache_entry *ce, int pos)
        return pos;
 }
 
-static int check_stage(int stage, const struct cache_entry *ce, int pos)
+static int check_stage(int stage, const struct cache_entry *ce, int pos,
+                      int overlay_mode)
 {
        while (pos < active_nr &&
               !strcmp(active_cache[pos]->name, ce->name)) {
@@ -143,6 +145,8 @@ static int check_stage(int stage, const struct cache_entry *ce, int pos)
                        return 0;
                pos++;
        }
+       if (!overlay_mode)
+               return 0;
        if (stage == 2)
                return error(_("path '%s' does not have our version"), ce->name);
        else
@@ -168,7 +172,8 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos)
 }
 
 static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
-                         const struct checkout *state, int *nr_checkouts)
+                         const struct checkout *state, int *nr_checkouts,
+                         int overlay_mode)
 {
        while (pos < active_nr &&
               !strcmp(active_cache[pos]->name, ce->name)) {
@@ -177,6 +182,10 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
                                              NULL, nr_checkouts);
                pos++;
        }
+       if (!overlay_mode) {
+               unlink_entry(ce);
+               return 0;
+       }
        if (stage == 2)
                return error(_("path '%s' does not have our version"), ce->name);
        else
@@ -251,6 +260,59 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko
        return status;
 }
 
+static void mark_ce_for_checkout_overlay(struct cache_entry *ce,
+                                        char *ps_matched,
+                                        const struct checkout_opts *opts)
+{
+       ce->ce_flags &= ~CE_MATCHED;
+       if (!opts->ignore_skipworktree && ce_skip_worktree(ce))
+               return;
+       if (opts->source_tree && !(ce->ce_flags & CE_UPDATE))
+               /*
+                * "git checkout tree-ish -- path", but this entry
+                * is in the original index but is not in tree-ish
+                * or does not match the pathspec; it will not be
+                * checked out to the working tree.  We will not do
+                * anything to this entry at all.
+                */
+               return;
+       /*
+        * Either this entry came from the tree-ish we are
+        * checking the paths out of, or we are checking out
+        * of the index.
+        *
+        * If it comes from the tree-ish, we already know it
+        * matches the pathspec and could just stamp
+        * CE_MATCHED to it from update_some(). But we still
+        * need ps_matched and read_tree_recursive (and
+        * eventually tree_entry_interesting) cannot fill
+        * ps_matched yet. Once it can, we can avoid calling
+        * match_pathspec() for _all_ entries when
+        * opts->source_tree != NULL.
+        */
+       if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched))
+               ce->ce_flags |= CE_MATCHED;
+}
+
+static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce,
+                                           char *ps_matched,
+                                           const struct checkout_opts *opts)
+{
+       ce->ce_flags &= ~CE_MATCHED;
+       if (!opts->ignore_skipworktree && ce_skip_worktree(ce))
+               return;
+       if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) {
+               ce->ce_flags |= CE_MATCHED;
+               if (opts->source_tree && !(ce->ce_flags & CE_UPDATE))
+                       /*
+                        * In overlay mode, but the path is not in
+                        * tree-ish, which means we should remove it
+                        * from the index and the working tree.
+                        */
+                       ce->ce_flags |= CE_REMOVE | CE_WT_REMOVE;
+       }
+}
+
 static int checkout_paths(const struct checkout_opts *opts,
                          const char *revision)
 {
@@ -263,6 +325,8 @@ static int checkout_paths(const struct checkout_opts *opts,
        struct lock_file lock_file = LOCK_INIT;
        int nr_checkouts = 0, nr_unmerged = 0;
 
+       trace2_cmd_mode(opts->patch_mode ? "patch" : "path");
+
        if (opts->track != BRANCH_TRACK_UNSPECIFIED)
                die(_("'%s' cannot be used with updating paths"), "--track");
 
@@ -302,37 +366,15 @@ static int checkout_paths(const struct checkout_opts *opts,
         * Make sure all pathspecs participated in locating the paths
         * to be checked out.
         */
-       for (pos = 0; pos < active_nr; pos++) {
-               struct cache_entry *ce = active_cache[pos];
-               ce->ce_flags &= ~CE_MATCHED;
-               if (!opts->ignore_skipworktree && ce_skip_worktree(ce))
-                       continue;
-               if (opts->source_tree && !(ce->ce_flags & CE_UPDATE))
-                       /*
-                        * "git checkout tree-ish -- path", but this entry
-                        * is in the original index; it will not be checked
-                        * out to the working tree and it does not matter
-                        * if pathspec matched this entry.  We will not do
-                        * anything to this entry at all.
-                        */
-                       continue;
-               /*
-                * Either this entry came from the tree-ish we are
-                * checking the paths out of, or we are checking out
-                * of the index.
-                *
-                * If it comes from the tree-ish, we already know it
-                * matches the pathspec and could just stamp
-                * CE_MATCHED to it from update_some(). But we still
-                * need ps_matched and read_tree_recursive (and
-                * eventually tree_entry_interesting) cannot fill
-                * ps_matched yet. Once it can, we can avoid calling
-                * match_pathspec() for _all_ entries when
-                * opts->source_tree != NULL.
-                */
-               if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched))
-                       ce->ce_flags |= CE_MATCHED;
-       }
+       for (pos = 0; pos < active_nr; pos++)
+               if (opts->overlay_mode)
+                       mark_ce_for_checkout_overlay(active_cache[pos],
+                                                    ps_matched,
+                                                    opts);
+               else
+                       mark_ce_for_checkout_no_overlay(active_cache[pos],
+                                                       ps_matched,
+                                                       opts);
 
        if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) {
                free(ps_matched);
@@ -353,7 +395,7 @@ static int checkout_paths(const struct checkout_opts *opts,
                        if (opts->force) {
                                warning(_("path '%s' is unmerged"), ce->name);
                        } else if (opts->writeout_stage) {
-                               errs |= check_stage(opts->writeout_stage, ce, pos);
+                               errs |= check_stage(opts->writeout_stage, ce, pos, opts->overlay_mode);
                        } else if (opts->merge) {
                                errs |= check_stages((1<<2) | (1<<3), ce, pos);
                        } else {
@@ -383,13 +425,16 @@ static int checkout_paths(const struct checkout_opts *opts,
                        if (opts->writeout_stage)
                                errs |= checkout_stage(opts->writeout_stage,
                                                       ce, pos,
-                                                      &state, &nr_checkouts);
+                                                      &state,
+                                                      &nr_checkouts, opts->overlay_mode);
                        else if (opts->merge)
                                errs |= checkout_merged(pos, &state,
                                                        &nr_unmerged);
                        pos = skip_same_name(ce, pos) - 1;
                }
        }
+       remove_marked_cache_entries(&the_index, 1);
+       remove_scheduled_dirs();
        errs |= finish_delayed_checkout(&state, &nr_checkouts);
 
        if (opts->count_checkout_paths) {
@@ -571,6 +616,11 @@ static int skip_merge_working_tree(const struct checkout_opts *opts,
         * opts->show_progress only impacts output so doesn't require a merge
         */
 
+       /*
+        * opts->overlay_mode cannot be used with switching branches so is
+        * not tested here
+        */
+
        /*
         * If we aren't creating a new branch any changes or updates will
         * happen in the existing branch.  Since that could only be updating
@@ -966,6 +1016,9 @@ static int switch_branches(const struct checkout_opts *opts,
        void *path_to_free;
        struct object_id rev;
        int flag, writeout_error = 0;
+
+       trace2_cmd_mode("branch");
+
        memset(&old_branch_info, 0, sizeof(old_branch_info));
        old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
        if (old_branch_info.path)
@@ -1203,6 +1256,8 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
        int status;
        struct strbuf branch_ref = STRBUF_INIT;
 
+       trace2_cmd_mode("unborn");
+
        if (!opts->new_branch)
                die(_("You are on a branch yet to be born"));
        strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch);
@@ -1224,6 +1279,10 @@ static int checkout_branch(struct checkout_opts *opts,
                die(_("'%s' cannot be used with switching branches"),
                    "--patch");
 
+       if (!opts->overlay_mode)
+               die(_("'%s' cannot be used with switching branches"),
+                   "--no-overlay");
+
        if (opts->writeout_stage)
                die(_("'%s' cannot be used with switching branches"),
                    "--ours/--theirs");
@@ -1312,6 +1371,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                            "checkout", "control recursive updating of submodules",
                            PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
                OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
+               OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
                OPT_END(),
        };
 
@@ -1320,6 +1380,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        opts.overwrite_ignore = 1;
        opts.prefix = prefix;
        opts.show_progress = -1;
+       opts.overlay_mode = -1;
 
        git_config(git_checkout_config, &opts);
 
@@ -1344,6 +1405,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
                die(_("-b, -B and --orphan are mutually exclusive"));
 
+       if (opts.overlay_mode == 1 && opts.patch_mode)
+               die(_("-p and --overlay are mutually exclusive"));
+
        /*
         * From here on, new_branch will contain the branch to be checked out,
         * and new_branch_force and new_orphan_branch will tell us which one of
index 12cc403bd7eac3f3c195ce6533feee1865139bae..b866d8395104e61b37ba6eedc1c538d9ba1faabf 100644 (file)
 #include "builtin.h"
 #include "utf8.h"
 #include "gpg-interface.h"
+#include "parse-options.h"
 
-static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1>";
+static const char * const commit_tree_usage[] = {
+       N_("git commit-tree [(-p <parent>)...] [-S[<keyid>]] [(-m <message>)...] "
+               "[(-F <file>)...] <tree>"),
+       NULL
+};
 
 static const char *sign_commit;
 
@@ -23,7 +28,7 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)
        struct commit_list *parents;
        for (parents = *parents_p; parents; parents = parents->next) {
                if (parents->item == parent) {
-                       error("duplicate parent %s ignored", oid_to_hex(oid));
+                       error(_("duplicate parent %s ignored"), oid_to_hex(oid));
                        return;
                }
                parents_p = &parents->next;
@@ -39,91 +44,100 @@ static int commit_tree_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
+static int parse_parent_arg_callback(const struct option *opt,
+               const char *arg, int unset)
+{
+       struct object_id oid;
+       struct commit_list **parents = opt->value;
+
+       BUG_ON_OPT_NEG_NOARG(unset, arg);
+
+       if (get_oid_commit(arg, &oid))
+               die(_("not a valid object name %s"), arg);
+
+       assert_oid_type(&oid, OBJ_COMMIT);
+       new_parent(lookup_commit(the_repository, &oid), parents);
+       return 0;
+}
+
+static int parse_message_arg_callback(const struct option *opt,
+               const char *arg, int unset)
+{
+       struct strbuf *buf = opt->value;
+
+       BUG_ON_OPT_NEG_NOARG(unset, arg);
+
+       if (buf->len)
+               strbuf_addch(buf, '\n');
+       strbuf_addstr(buf, arg);
+       strbuf_complete_line(buf);
+
+       return 0;
+}
+
+static int parse_file_arg_callback(const struct option *opt,
+               const char *arg, int unset)
+{
+       int fd;
+       struct strbuf *buf = opt->value;
+
+       BUG_ON_OPT_NEG_NOARG(unset, arg);
+
+       if (buf->len)
+               strbuf_addch(buf, '\n');
+       if (!strcmp(arg, "-"))
+               fd = 0;
+       else {
+               fd = open(arg, O_RDONLY);
+               if (fd < 0)
+                       die_errno(_("git commit-tree: failed to open '%s'"), arg);
+       }
+       if (strbuf_read(buf, fd, 0) < 0)
+               die_errno(_("git commit-tree: failed to read '%s'"), arg);
+       if (fd && close(fd))
+               die_errno(_("git commit-tree: failed to close '%s'"), arg);
+
+       return 0;
+}
+
 int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 {
-       int i, got_tree = 0;
+       static struct strbuf buffer = STRBUF_INIT;
        struct commit_list *parents = NULL;
        struct object_id tree_oid;
        struct object_id commit_oid;
-       struct strbuf buffer = STRBUF_INIT;
+
+       struct option options[] = {
+               { OPTION_CALLBACK, 'p', NULL, &parents, N_("parent"),
+                       N_("id of a parent commit object"), PARSE_OPT_NONEG,
+                       parse_parent_arg_callback },
+               { OPTION_CALLBACK, 'm', NULL, &buffer, N_("message"),
+                       N_("commit message"), PARSE_OPT_NONEG,
+                       parse_message_arg_callback },
+               { OPTION_CALLBACK, 'F', NULL, &buffer, N_("file"),
+                       N_("read commit log message from file"), PARSE_OPT_NONEG,
+                       parse_file_arg_callback },
+               { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
+                       N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+               OPT_END()
+       };
 
        git_config(commit_tree_config, NULL);
 
        if (argc < 2 || !strcmp(argv[1], "-h"))
-               usage(commit_tree_usage);
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (!strcmp(arg, "-p")) {
-                       struct object_id oid;
-                       if (argc <= ++i)
-                               usage(commit_tree_usage);
-                       if (get_oid_commit(argv[i], &oid))
-                               die("Not a valid object name %s", argv[i]);
-                       assert_oid_type(&oid, OBJ_COMMIT);
-                       new_parent(lookup_commit(the_repository, &oid),
-                                                &parents);
-                       continue;
-               }
+               usage_with_options(commit_tree_usage, options);
 
-               if (!strcmp(arg, "--gpg-sign")) {
-                   sign_commit = "";
-                   continue;
-               }
+       argc = parse_options(argc, argv, prefix, options, commit_tree_usage, 0);
 
-               if (skip_prefix(arg, "-S", &sign_commit) ||
-                       skip_prefix(arg, "--gpg-sign=", &sign_commit))
-                       continue;
+       if (argc != 1)
+               die(_("must give exactly one tree"));
 
-               if (!strcmp(arg, "--no-gpg-sign")) {
-                       sign_commit = NULL;
-                       continue;
-               }
-
-               if (!strcmp(arg, "-m")) {
-                       if (argc <= ++i)
-                               usage(commit_tree_usage);
-                       if (buffer.len)
-                               strbuf_addch(&buffer, '\n');
-                       strbuf_addstr(&buffer, argv[i]);
-                       strbuf_complete_line(&buffer);
-                       continue;
-               }
-
-               if (!strcmp(arg, "-F")) {
-                       int fd;
-
-                       if (argc <= ++i)
-                               usage(commit_tree_usage);
-                       if (buffer.len)
-                               strbuf_addch(&buffer, '\n');
-                       if (!strcmp(argv[i], "-"))
-                               fd = 0;
-                       else {
-                               fd = open(argv[i], O_RDONLY);
-                               if (fd < 0)
-                                       die_errno("git commit-tree: failed to open '%s'",
-                                                 argv[i]);
-                       }
-                       if (strbuf_read(&buffer, fd, 0) < 0)
-                               die_errno("git commit-tree: failed to read '%s'",
-                                         argv[i]);
-                       if (fd && close(fd))
-                               die_errno("git commit-tree: failed to close '%s'",
-                                         argv[i]);
-                       continue;
-               }
-
-               if (get_oid_tree(arg, &tree_oid))
-                       die("Not a valid object name %s", arg);
-               if (got_tree)
-                       die("Cannot give more than one trees");
-               got_tree = 1;
-       }
+       if (get_oid_tree(argv[0], &tree_oid))
+               die(_("not a valid object name %s"), argv[0]);
 
        if (!buffer.len) {
                if (strbuf_read(&buffer, 0, 0) < 0)
-                       die_errno("git commit-tree: failed to read");
+                       die_errno(_("git commit-tree: failed to read"));
        }
 
        if (commit_tree(buffer.buf, buffer.len, &tree_oid, parents, &commit_oid,
index 2986553d5ffb97f0798e4d4eb921073439fb9a92..f17537474a9d0ed9672393fc5dc3845932dd4dd4 100644 (file)
@@ -609,7 +609,8 @@ static void determine_author_info(struct strbuf *author_ident)
                set_ident_var(&date, strbuf_detach(&date_buf, NULL));
        }
 
-       strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT));
+       strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date,
+                               IDENT_STRICT));
        assert_split_ident(&author, author_ident);
        export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
        export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
@@ -1038,6 +1039,10 @@ static void handle_untracked_files_arg(struct wt_status *s)
                s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
        else if (!strcmp(untracked_files_arg, "all"))
                s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+       /*
+        * Please update $__git_untracked_file_modes in
+        * git-completion.bash when you add new options
+        */
        else
                die(_("Invalid untracked files mode '%s'"), untracked_files_arg);
 }
@@ -1179,6 +1184,10 @@ static int parse_and_validate_options(int argc, const char *argv[],
        else if (!strcmp(cleanup_arg, "scissors"))
                cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_SCISSORS :
                                            COMMIT_MSG_CLEANUP_SPACE;
+       /*
+        * Please update _git_commit() in git-completion.bash when you
+        * add new options.
+        */
        else
                die(_("Invalid cleanup mode %s"), cleanup_arg);
 
index a90681bcbafcba04af3b669f0758c78ccecdd11f..cb9ea793675c850fb4bf9ab87a8e1b62f1695b02 100644 (file)
@@ -83,9 +83,13 @@ static int diff_tree_stdin(char *line)
 }
 
 static const char diff_tree_usage[] =
-"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"git diff-tree [--stdin] [-m] [-c | --cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
 "[<common-diff-options>] <tree-ish> [<tree-ish>] [<path>...]\n"
 "  -r            diff recursively\n"
+"  -c            show combined diff for merge commits\n"
+"  --cc          show combined diff for merge commits removing uninteresting hunks\n"
+"  --combined-all-paths\n"
+"                show name of file in all parents for combined diffs\n"
 "  --root        include the initial commit as diff against /dev/null\n"
 COMMON_DIFF_OPTIONS_HELP;
 
index 9f6109224b7ea75689e9780c5f79343f55e1e3e5..53d4234ff4812845459556b1257e779c528250b8 100644 (file)
@@ -338,21 +338,23 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                       "--no-index" : "[--no-index]");
 
        }
-       if (no_index)
-               /* If this is a no-index diff, just run it and exit there. */
-               diff_no_index(the_repository, &rev, argc, argv);
-
-       /* Otherwise, we are doing the usual "git" diff */
-       rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
 
-       /* Scale to real terminal size and respect statGraphWidth config */
+       /* Set up defaults that will apply to both no-index and regular diffs. */
        rev.diffopt.stat_width = -1;
        rev.diffopt.stat_graph_width = -1;
-
-       /* Default to let external and textconv be used */
        rev.diffopt.flags.allow_external = 1;
        rev.diffopt.flags.allow_textconv = 1;
 
+       /* If this is a no-index diff, just run it and exit there. */
+       if (no_index)
+               diff_no_index(&rev, argc, argv);
+
+       /*
+        * Otherwise, we are doing the usual "git" diff; set up any
+        * further defaults that apply to regular diffs.
+        */
+       rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
+
        /*
         * Default to intent-to-add entries invisible in the
         * index. This makes them show up as new files in diff-files
index b620fd54b4e95cad77532ec3fe02e2ff1179c431..4ba63d5ac642844832a5c832cea93ddf99507764 100644 (file)
@@ -1556,7 +1556,9 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, int pru
 
        sigchain_push_common(unlock_pack_on_signal);
        atexit(unlock_pack);
+       sigchain_push(SIGPIPE, SIG_IGN);
        exit_code = do_fetch(gtransport, &rs);
+       sigchain_pop(SIGPIPE);
        refspec_clear(&rs);
        transport_disconnect(gtransport);
        gtransport = NULL;
index bb4227bebc606dfac8337d7aa3e78705113a79b5..d26fb0a04472b18a856658cf2800dd9655a6bf24 100644 (file)
@@ -235,6 +235,48 @@ static int mark_used(struct object *obj, int type, void *data, struct fsck_optio
        return 0;
 }
 
+static void mark_unreachable_referents(const struct object_id *oid)
+{
+       struct fsck_options options = FSCK_OPTIONS_DEFAULT;
+       struct object *obj = lookup_object(the_repository, oid->hash);
+
+       if (!obj || !(obj->flags & HAS_OBJ))
+               return; /* not part of our original set */
+       if (obj->flags & REACHABLE)
+               return; /* reachable objects already traversed */
+
+       /*
+        * Avoid passing OBJ_NONE to fsck_walk, which will parse the object
+        * (and we want to avoid parsing blobs).
+        */
+       if (obj->type == OBJ_NONE) {
+               enum object_type type = oid_object_info(the_repository,
+                                                       &obj->oid, NULL);
+               if (type > 0)
+                       object_as_type(the_repository, obj, type, 0);
+       }
+
+       options.walk = mark_used;
+       fsck_walk(obj, NULL, &options);
+}
+
+static int mark_loose_unreachable_referents(const struct object_id *oid,
+                                           const char *path,
+                                           void *data)
+{
+       mark_unreachable_referents(oid);
+       return 0;
+}
+
+static int mark_packed_unreachable_referents(const struct object_id *oid,
+                                            struct packed_git *pack,
+                                            uint32_t pos,
+                                            void *data)
+{
+       mark_unreachable_referents(oid);
+       return 0;
+}
+
 /*
  * Check a single reachable object
  */
@@ -347,6 +389,26 @@ static void check_connectivity(void)
        /* Traverse the pending reachable objects */
        traverse_reachable();
 
+       /*
+        * With --connectivity-only, we won't have actually opened and marked
+        * unreachable objects with USED. Do that now to make --dangling, etc
+        * accurate.
+        */
+       if (connectivity_only && (show_dangling || write_lost_and_found)) {
+               /*
+                * Even though we already have a "struct object" for each of
+                * these in memory, we must not iterate over the internal
+                * object hash as we do below. Our loop would potentially
+                * resize the hash, making our iteration invalid.
+                *
+                * Instead, we'll just go back to the source list of objects,
+                * and ignore any that weren't present in our earlier
+                * traversal.
+                */
+               for_each_loose_object(mark_loose_unreachable_referents, NULL, 0);
+               for_each_packed_object(mark_packed_unreachable_referents, NULL, 0);
+       }
+
        /* Look up all the requirements, warn about missing objects.. */
        max = get_max_object_index();
        if (verbose)
index 7739a5c1551426fd2302aaf5daa10e454d174195..e5590d7787c50af121db0ba7e6221e89c6785df1 100644 (file)
@@ -70,6 +70,10 @@ static enum help_format parse_help_format(const char *format)
                return HELP_FORMAT_INFO;
        if (!strcmp(format, "web") || !strcmp(format, "html"))
                return HELP_FORMAT_WEB;
+       /*
+        * Please update _git_config() in git-completion.bash when you
+        * add new help formats.
+        */
        die(_("unrecognized help format '%s'"), format);
 }
 
index 93eff7618cf1b98e3c69a7f1f159063e9e73dbe3..6090217025a27281b0723537e570357bf9c86629 100644 (file)
@@ -96,7 +96,7 @@ static void copy_templates(const char *template_dir)
        struct strbuf path = STRBUF_INIT;
        struct strbuf template_path = STRBUF_INIT;
        size_t template_len;
-       struct repository_format template_format;
+       struct repository_format template_format = REPOSITORY_FORMAT_INIT;
        struct strbuf err = STRBUF_INIT;
        DIR *dir;
        char *to_free = NULL;
@@ -148,6 +148,7 @@ free_return:
        free(to_free);
        strbuf_release(&path);
        strbuf_release(&template_path);
+       clear_repository_format(&template_format);
 }
 
 static int git_init_db_config(const char *k, const char *v, void *cb)
index 57869267d8d75e06a1a34e4dea104102aac21a65..ab859f5904191fd733b852897aac1840aa6dd734 100644 (file)
@@ -84,6 +84,10 @@ static int parse_decoration_style(const char *value)
                return DECORATE_SHORT_REFS;
        else if (!strcmp(value, "auto"))
                return auto_decoration_style();
+       /*
+        * Please update _git_log() in git-completion.bash when you
+        * add new decoration styles.
+        */
        return -1;
 }
 
@@ -1228,6 +1232,10 @@ static int thread_callback(const struct option *opt, const char *arg, int unset)
                *thread = THREAD_SHALLOW;
        else if (!strcmp(arg, "deep"))
                *thread = THREAD_DEEP;
+       /*
+        * Please update _git_formatpatch() in git-completion.bash
+        * when you add new options.
+        */
        else
                return 1;
        return 0;
index e47d77baeebe888cfb67f8e03804ffc3ee3715c0..5ce8946d390c1c42b885a2c86a363f79aec1b27a 100644 (file)
@@ -113,12 +113,15 @@ static int option_parse_message(const struct option *opt,
        return 0;
 }
 
-static int option_read_message(struct parse_opt_ctx_t *ctx,
-                              const struct option *opt, int unset)
+static enum parse_opt_result option_read_message(struct parse_opt_ctx_t *ctx,
+                                                const struct option *opt,
+                                                const char *arg_not_used,
+                                                int unset)
 {
        struct strbuf *buf = opt->value;
        const char *arg;
 
+       BUG_ON_OPT_ARG(arg_not_used);
        if (unset)
                BUG("-F cannot be negated");
 
@@ -262,7 +265,7 @@ static struct option builtin_merge_options[] = {
                option_parse_message),
        { OPTION_LOWLEVEL_CALLBACK, 'F', "file", &merge_msg, N_("path"),
                N_("read message from file"), PARSE_OPT_NONEG,
-               (parse_opt_cb *) option_read_message },
+               NULL, 0, option_read_message },
        OPT__VERBOSITY(&verbosity),
        OPT_BOOL(0, "abort", &abort_current_merge,
                N_("abort the current in-progress merge")),
index a9fac7c128c1eb45026965a79db1387a8bdb92de..a154fc29f6b7841c2417ca302b631437e7376baa 100644 (file)
@@ -33,6 +33,7 @@
 #include "object-store.h"
 #include "dir.h"
 #include "midx.h"
+#include "trace2.h"
 
 #define IN_PACK(obj) oe_in_pack(&to_pack, obj)
 #define SIZE(obj) oe_size(&to_pack, obj)
@@ -3473,6 +3474,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                }
        }
 
+       trace2_region_enter("pack-objects", "enumerate-objects",
+                           the_repository);
        prepare_packing_data(the_repository, &to_pack);
 
        if (progress)
@@ -3487,12 +3490,23 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (include_tag && nr_result)
                for_each_ref(add_ref_tag, NULL);
        stop_progress(&progress_state);
+       trace2_region_leave("pack-objects", "enumerate-objects",
+                           the_repository);
 
        if (non_empty && !nr_result)
                return 0;
-       if (nr_result)
+       if (nr_result) {
+               trace2_region_enter("pack-objects", "prepare-pack",
+                                   the_repository);
                prepare_pack(window, depth);
+               trace2_region_leave("pack-objects", "prepare-pack",
+                                   the_repository);
+       }
+
+       trace2_region_enter("pack-objects", "write-pack-file", the_repository);
        write_pack_file();
+       trace2_region_leave("pack-objects", "write-pack-file", the_repository);
+
        if (progress)
                fprintf_ln(stderr,
                           _("Total %"PRIu32" (delta %"PRIu32"),"
index 11bc51456631e65a35f11d7b5118dc33c0f324e9..68c1e547c244e09f6447c361b9c665efbb27192d 100644 (file)
@@ -32,14 +32,10 @@ static struct pack_list {
        struct pack_list *next;
        struct packed_git *pack;
        struct llist *unique_objects;
-       struct llist *all_objects;
+       struct llist *remaining_objects;
+       size_t all_objects_size;
 } *local_packs = NULL, *altodb_packs = NULL;
 
-struct pll {
-       struct pll *next;
-       struct pack_list *pl;
-};
-
 static struct llist_item *free_nodes;
 
 static inline void llist_item_put(struct llist_item *item)
@@ -63,15 +59,6 @@ static inline struct llist_item *llist_item_get(void)
        return new_item;
 }
 
-static void llist_free(struct llist *list)
-{
-       while ((list->back = list->front)) {
-               list->front = list->front->next;
-               llist_item_put(list->back);
-       }
-       free(list);
-}
-
 static inline void llist_init(struct llist **list)
 {
        *list = xmalloc(sizeof(struct llist));
@@ -254,6 +241,11 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
        struct llist_item *p1_hint = NULL, *p2_hint = NULL;
        const unsigned int hashsz = the_hash_algo->rawsz;
 
+       if (!p1->unique_objects)
+               p1->unique_objects = llist_copy(p1->remaining_objects);
+       if (!p2->unique_objects)
+               p2->unique_objects = llist_copy(p2->remaining_objects);
+
        p1_base = p1->pack->index_data;
        p2_base = p2->pack->index_data;
        p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8);
@@ -285,78 +277,6 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
        }
 }
 
-static void pll_free(struct pll *l)
-{
-       struct pll *old;
-       struct pack_list *opl;
-
-       while (l) {
-               old = l;
-               while (l->pl) {
-                       opl = l->pl;
-                       l->pl = opl->next;
-                       free(opl);
-               }
-               l = l->next;
-               free(old);
-       }
-}
-
-/* all the permutations have to be free()d at the same time,
- * since they refer to each other
- */
-static struct pll * get_permutations(struct pack_list *list, int n)
-{
-       struct pll *subset, *ret = NULL, *new_pll = NULL;
-
-       if (list == NULL || pack_list_size(list) < n || n == 0)
-               return NULL;
-
-       if (n == 1) {
-               while (list) {
-                       new_pll = xmalloc(sizeof(*new_pll));
-                       new_pll->pl = NULL;
-                       pack_list_insert(&new_pll->pl, list);
-                       new_pll->next = ret;
-                       ret = new_pll;
-                       list = list->next;
-               }
-               return ret;
-       }
-
-       while (list->next) {
-               subset = get_permutations(list->next, n - 1);
-               while (subset) {
-                       new_pll = xmalloc(sizeof(*new_pll));
-                       new_pll->pl = subset->pl;
-                       pack_list_insert(&new_pll->pl, list);
-                       new_pll->next = ret;
-                       ret = new_pll;
-                       subset = subset->next;
-               }
-               list = list->next;
-       }
-       return ret;
-}
-
-static int is_superset(struct pack_list *pl, struct llist *list)
-{
-       struct llist *diff;
-
-       diff = llist_copy(list);
-
-       while (pl) {
-               llist_sorted_difference_inplace(diff, pl->all_objects);
-               if (diff->size == 0) { /* we're done */
-                       llist_free(diff);
-                       return 1;
-               }
-               pl = pl->next;
-       }
-       llist_free(diff);
-       return 0;
-}
-
 static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
 {
        size_t ret = 0;
@@ -421,14 +341,58 @@ static inline off_t pack_set_bytecount(struct pack_list *pl)
        return ret;
 }
 
+static int cmp_remaining_objects(const void *a, const void *b)
+{
+       struct pack_list *pl_a = *((struct pack_list **)a);
+       struct pack_list *pl_b = *((struct pack_list **)b);
+
+       if (pl_a->remaining_objects->size == pl_b->remaining_objects->size) {
+               /* have the same remaining_objects, big pack first */
+               if (pl_a->all_objects_size == pl_b->all_objects_size)
+                       return 0;
+               else if (pl_a->all_objects_size < pl_b->all_objects_size)
+                       return 1;
+               else
+                       return -1;
+       } else if (pl_a->remaining_objects->size < pl_b->remaining_objects->size) {
+               /* sort by remaining objects, more objects first */
+               return 1;
+       } else {
+               return -1;
+       }
+}
+
+/* Sort pack_list, greater size of remaining_objects first */
+static void sort_pack_list(struct pack_list **pl)
+{
+       struct pack_list **ary, *p;
+       int i;
+       size_t n = pack_list_size(*pl);
+
+       if (n < 2)
+               return;
+
+       /* prepare an array of packed_list for easier sorting */
+       ary = xcalloc(n, sizeof(struct pack_list *));
+       for (n = 0, p = *pl; p; p = p->next)
+               ary[n++] = p;
+
+       QSORT(ary, n, cmp_remaining_objects);
+
+       /* link them back again */
+       for (i = 0; i < n - 1; i++)
+               ary[i]->next = ary[i + 1];
+       ary[n - 1]->next = NULL;
+       *pl = ary[0];
+
+       free(ary);
+}
+
+
 static void minimize(struct pack_list **min)
 {
-       struct pack_list *pl, *unique = NULL,
-               *non_unique = NULL, *min_perm = NULL;
-       struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm;
-       struct llist *missing;
-       off_t min_perm_size = 0, perm_size;
-       int n;
+       struct pack_list *pl, *unique = NULL, *non_unique = NULL;
+       struct llist *missing, *unique_pack_objects;
 
        pl = local_packs;
        while (pl) {
@@ -442,53 +406,41 @@ static void minimize(struct pack_list **min)
        missing = llist_copy(all_objects);
        pl = unique;
        while (pl) {
-               llist_sorted_difference_inplace(missing, pl->all_objects);
+               llist_sorted_difference_inplace(missing, pl->remaining_objects);
                pl = pl->next;
        }
 
+       *min = unique;
+
        /* return if there are no objects missing from the unique set */
        if (missing->size == 0) {
-               *min = unique;
                free(missing);
                return;
        }
 
-       /* find the permutations which contain all missing objects */
-       for (n = 1; n <= pack_list_size(non_unique) && !perm_ok; n++) {
-               perm_all = perm = get_permutations(non_unique, n);
-               while (perm) {
-                       if (is_superset(perm->pl, missing)) {
-                               new_perm = xmalloc(sizeof(struct pll));
-                               memcpy(new_perm, perm, sizeof(struct pll));
-                               new_perm->next = perm_ok;
-                               perm_ok = new_perm;
-                       }
-                       perm = perm->next;
-               }
-               if (perm_ok)
-                       break;
-               pll_free(perm_all);
-       }
-       if (perm_ok == NULL)
-               die("Internal error: No complete sets found!");
-
-       /* find the permutation with the smallest size */
-       perm = perm_ok;
-       while (perm) {
-               perm_size = pack_set_bytecount(perm->pl);
-               if (!min_perm_size || min_perm_size > perm_size) {
-                       min_perm_size = perm_size;
-                       min_perm = perm->pl;
-               }
-               perm = perm->next;
-       }
-       *min = min_perm;
-       /* add the unique packs to the list */
-       pl = unique;
+       unique_pack_objects = llist_copy(all_objects);
+       llist_sorted_difference_inplace(unique_pack_objects, missing);
+
+       /* remove unique pack objects from the non_unique packs */
+       pl = non_unique;
        while (pl) {
-               pack_list_insert(min, pl);
+               llist_sorted_difference_inplace(pl->remaining_objects, unique_pack_objects);
                pl = pl->next;
        }
+
+       while (non_unique) {
+               /* sort the non_unique packs, greater size of remaining_objects first */
+               sort_pack_list(&non_unique);
+               if (non_unique->remaining_objects->size == 0)
+                       break;
+
+               pack_list_insert(min, non_unique);
+
+               for (pl = non_unique->next; pl && pl->remaining_objects->size > 0;  pl = pl->next)
+                       llist_sorted_difference_inplace(pl->remaining_objects, non_unique->remaining_objects);
+
+               non_unique = non_unique->next;
+       }
 }
 
 static void load_all_objects(void)
@@ -500,7 +452,7 @@ static void load_all_objects(void)
 
        while (pl) {
                hint = NULL;
-               l = pl->all_objects->front;
+               l = pl->remaining_objects->front;
                while (l) {
                        hint = llist_insert_sorted_unique(all_objects,
                                                          l->oid, hint);
@@ -511,7 +463,7 @@ static void load_all_objects(void)
        /* remove objects present in remote packs */
        pl = altodb_packs;
        while (pl) {
-               llist_sorted_difference_inplace(all_objects, pl->all_objects);
+               llist_sorted_difference_inplace(all_objects, pl->remaining_objects);
                pl = pl->next;
        }
 }
@@ -536,11 +488,10 @@ static void scan_alt_odb_packs(void)
        while (alt) {
                local = local_packs;
                while (local) {
-                       llist_sorted_difference_inplace(local->unique_objects,
-                                                       alt->all_objects);
+                       llist_sorted_difference_inplace(local->remaining_objects,
+                                                       alt->remaining_objects);
                        local = local->next;
                }
-               llist_sorted_difference_inplace(all_objects, alt->all_objects);
                alt = alt->next;
        }
 }
@@ -555,7 +506,7 @@ static struct pack_list * add_pack(struct packed_git *p)
                return NULL;
 
        l.pack = p;
-       llist_init(&l.all_objects);
+       llist_init(&l.remaining_objects);
 
        if (open_pack_index(p))
                return NULL;
@@ -564,11 +515,11 @@ static struct pack_list * add_pack(struct packed_git *p)
        base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
        step = the_hash_algo->rawsz + ((p->index_version < 2) ? 4 : 0);
        while (off < p->num_objects * step) {
-               llist_insert_back(l.all_objects, (const struct object_id *)(base + off));
+               llist_insert_back(l.remaining_objects, (const struct object_id *)(base + off));
                off += step;
        }
-       /* this list will be pruned in cmp_two_packs later */
-       l.unique_objects = llist_copy(l.all_objects);
+       l.all_objects_size = l.remaining_objects->size;
+       l.unique_objects = NULL;
        if (p->pack_local)
                return pack_list_insert(&local_packs, &l);
        else
@@ -603,7 +554,7 @@ static void load_all(void)
 int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
 {
        int i;
-       struct pack_list *min, *red, *pl;
+       struct pack_list *min = NULL, *red, *pl;
        struct llist *ignore;
        struct object_id *oid;
        char buf[GIT_MAX_HEXSZ + 2]; /* hex hash + \n + \0 */
@@ -646,7 +597,6 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
 
        load_all_objects();
 
-       cmp_local_packs();
        if (alt_odb)
                scan_alt_odb_packs();
 
@@ -663,10 +613,12 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
        llist_sorted_difference_inplace(all_objects, ignore);
        pl = local_packs;
        while (pl) {
-               llist_sorted_difference_inplace(pl->unique_objects, ignore);
+               llist_sorted_difference_inplace(pl->remaining_objects, ignore);
                pl = pl->next;
        }
 
+       cmp_local_packs();
+
        minimize(&min);
 
        if (verbose) {
index a9e7b552b9d117c5f1078a0dd5864bbed724c6b6..48c5e78e339dbd32db255382f729ca2e65b514c5 100644 (file)
@@ -63,6 +63,11 @@ int cmd_prune_packed(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix, prune_packed_options,
                             prune_packed_usage, 0);
 
+       if (argc > 0)
+               usage_msg_opt(_("too many arguments"),
+                             prune_packed_usage,
+                             prune_packed_options);
+
        prune_packed_objects(opts);
        return 0;
 }
index 1ec9ddd751df6644d2c39ace41a1494800082638..97613eccb54dcba31d9fb35df54673fdf1f57817 100644 (file)
@@ -31,16 +31,39 @@ static int prune_tmp_file(const char *fullpath)
        return 0;
 }
 
+static void perform_reachability_traversal(struct rev_info *revs)
+{
+       static int initialized;
+       struct progress *progress = NULL;
+
+       if (initialized)
+               return;
+
+       if (show_progress)
+               progress = start_delayed_progress(_("Checking connectivity"), 0);
+       mark_reachable_objects(revs, 1, expire, progress);
+       stop_progress(&progress);
+       initialized = 1;
+}
+
+static int is_object_reachable(const struct object_id *oid,
+                              struct rev_info *revs)
+{
+       struct object *obj;
+
+       perform_reachability_traversal(revs);
+
+       obj = lookup_object(the_repository, oid->hash);
+       return obj && (obj->flags & SEEN);
+}
+
 static int prune_object(const struct object_id *oid, const char *fullpath,
                        void *data)
 {
+       struct rev_info *revs = data;
        struct stat st;
 
-       /*
-        * Do we know about this object?
-        * It must have been reachable
-        */
-       if (lookup_object(the_repository, oid->hash))
+       if (is_object_reachable(oid, revs))
                return 0;
 
        if (lstat(fullpath, &st)) {
@@ -102,7 +125,6 @@ static void remove_temporary_files(const char *path)
 int cmd_prune(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
-       struct progress *progress = NULL;
        int exclude_promisor_objects = 0;
        const struct option options[] = {
                OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
@@ -142,17 +164,13 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 
        if (show_progress == -1)
                show_progress = isatty(2);
-       if (show_progress)
-               progress = start_delayed_progress(_("Checking connectivity"), 0);
        if (exclude_promisor_objects) {
                fetch_if_missing = 0;
                revs.exclude_promisor_objects = 1;
        }
 
-       mark_reachable_objects(&revs, 1, expire, progress);
-       stop_progress(&progress);
        for_each_loose_file_in_objdir(get_object_directory(), prune_object,
-                                     prune_cruft, prune_subdir, NULL);
+                                     prune_cruft, prune_subdir, &revs);
 
        prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);
        remove_temporary_files(get_object_directory());
@@ -160,8 +178,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
        remove_temporary_files(s);
        free(s);
 
-       if (is_repository_shallow(the_repository))
+       if (is_repository_shallow(the_repository)) {
+               perform_reachability_traversal(&revs);
                prune_shallow(show_only ? PRUNE_SHOW_ONLY : 0);
+       }
 
        return 0;
 }
index 701d1473dc59e018247f337d37f00fe04f83cb98..33db889955402168f95083d741d4094db89cb503 100644 (file)
@@ -56,6 +56,10 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,
                return REBASE_MERGES;
        else if (!strcmp(value, "interactive") || !strcmp(value, "i"))
                return REBASE_INTERACTIVE;
+       /*
+        * Please update _git_config() in git-completion.bash when you
+        * add new rebase modes.
+        */
 
        if (fatal)
                die(_("Invalid value for %s: %s"), key, value);
index 7c7bc13e912aa8ba5d2c4dfa781fb74cd20ac6d4..551b72f52d67718c61305794e6bffbb32cd728ee 100644 (file)
@@ -369,6 +369,7 @@ static void add_var(struct strbuf *buf, const char *name, const char *value)
 #define RESET_HEAD_HARD (1<<1)
 #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
 #define RESET_HEAD_REFS_ONLY (1<<3)
+#define RESET_ORIG_HEAD (1<<4)
 
 static int reset_head(struct object_id *oid, const char *action,
                      const char *switch_to_branch, unsigned flags,
@@ -378,6 +379,7 @@ static int reset_head(struct object_id *oid, const char *action,
        unsigned reset_hard = flags & RESET_HEAD_HARD;
        unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
        unsigned refs_only = flags & RESET_HEAD_REFS_ONLY;
+       unsigned update_orig_head = flags & RESET_ORIG_HEAD;
        struct object_id head_oid;
        struct tree_desc desc[2] = { { NULL }, { NULL } };
        struct lock_file lock = LOCK_INIT;
@@ -454,18 +456,21 @@ reset_head_refs:
        strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase");
        prefix_len = msg.len;
 
-       if (!get_oid("ORIG_HEAD", &oid_old_orig))
-               old_orig = &oid_old_orig;
-       if (!get_oid("HEAD", &oid_orig)) {
-               orig = &oid_orig;
-               if (!reflog_orig_head) {
-                       strbuf_addstr(&msg, "updating ORIG_HEAD");
-                       reflog_orig_head = msg.buf;
-               }
-               update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0,
-                          UPDATE_REFS_MSG_ON_ERR);
-       } else if (old_orig)
-               delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
+       if (update_orig_head) {
+               if (!get_oid("ORIG_HEAD", &oid_old_orig))
+                       old_orig = &oid_old_orig;
+               if (!get_oid("HEAD", &oid_orig)) {
+                       orig = &oid_orig;
+                       if (!reflog_orig_head) {
+                               strbuf_addstr(&msg, "updating ORIG_HEAD");
+                               reflog_orig_head = msg.buf;
+                       }
+                       update_ref(reflog_orig_head, "ORIG_HEAD", orig,
+                                  old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
+               } else if (old_orig)
+                       delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
+       }
+
        if (!reflog_head) {
                strbuf_setlen(&msg, prefix_len);
                strbuf_addstr(&msg, "updating HEAD");
@@ -476,7 +481,7 @@ reset_head_refs:
                                 detach_head ? REF_NO_DEREF : 0,
                                 UPDATE_REFS_MSG_ON_ERR);
        else {
-               ret = update_ref(reflog_orig_head, switch_to_branch, oid,
+               ret = update_ref(reflog_head, switch_to_branch, oid,
                                 NULL, 0, UPDATE_REFS_MSG_ON_ERR);
                if (!ret)
                        ret = create_symref("HEAD", switch_to_branch,
@@ -1027,6 +1032,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                ACTION_EDIT_TODO,
                ACTION_SHOW_CURRENT_PATCH,
        } action = NO_ACTION;
+       static const char *action_names[] = { N_("undefined"),
+                                             N_("continue"),
+                                             N_("skip"),
+                                             N_("abort"),
+                                             N_("quit"),
+                                             N_("edit_todo"),
+                                             N_("show_current_patch"),
+                                             NULL };
        const char *gpg_sign = NULL;
        struct string_list exec = STRING_LIST_INIT_NODUP;
        const char *rebase_merges = NULL;
@@ -1092,8 +1105,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
                        parse_opt_interactive },
                OPT_SET_INT('p', "preserve-merges", &options.type,
-                           N_("try to recreate merges instead of ignoring "
-                              "them"), REBASE_PRESERVE_MERGES),
+                           N_("(DEPRECATED) try to recreate merges instead of "
+                              "ignoring them"), REBASE_PRESERVE_MERGES),
                OPT_BOOL(0, "rerere-autoupdate",
                         &options.allow_rerere_autoupdate,
                         N_("allow rerere to update index with resolved "
@@ -1204,6 +1217,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                usage_with_options(builtin_rebase_usage,
                                   builtin_rebase_options);
 
+       if (options.type == REBASE_PRESERVE_MERGES)
+               warning(_("git rebase --preserve-merges is deprecated. "
+                         "Use --rebase-merges instead."));
+
        if (action != NO_ACTION && !in_progress)
                die(_("No rebase in progress?"));
        setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
@@ -1212,6 +1229,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                die(_("The --edit-todo action can only be used during "
                      "interactive rebase."));
 
+       if (trace2_is_enabled()) {
+               if (is_interactive(&options))
+                       trace2_cmd_mode("interactive");
+               else if (exec.nr)
+                       trace2_cmd_mode("interactive-exec");
+               else
+                       trace2_cmd_mode(action_names[action]);
+       }
+
        switch (action) {
        case ACTION_CONTINUE: {
                struct object_id head;
@@ -1760,7 +1786,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        strbuf_addf(&msg, "%s: checkout %s",
                    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
        if (reset_head(&options.onto->object.oid, "checkout", NULL,
-                      RESET_HEAD_DETACH | RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
+                      RESET_HEAD_DETACH | RESET_ORIG_HEAD | 
+                      RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
                       NULL, msg.buf))
                die(_("Could not detach HEAD"));
        strbuf_release(&msg);
@@ -1776,8 +1803,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                strbuf_addf(&msg, "rebase finished: %s onto %s",
                        options.head_name ? options.head_name : "detached HEAD",
                        oid_to_hex(&options.onto->object.oid));
-               reset_head(NULL, "Fast-forwarded", options.head_name, 0,
-                          "HEAD", msg.buf);
+               reset_head(NULL, "Fast-forwarded", options.head_name,
+                          RESET_HEAD_REFS_ONLY, "HEAD", msg.buf);
                strbuf_release(&msg);
                ret = !!finish_rebase(&options);
                goto cleanup;
index d58b7750b6b46565ca4ebb8299e5260943aa364e..29f165d8bd3513a6f85667b070e8f157994364bb 100644 (file)
@@ -694,6 +694,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
        proc.argv = argv;
        proc.in = -1;
        proc.stdout_to_stderr = 1;
+       proc.trace2_hook_name = hook_name;
+
        if (feed_state->push_options) {
                int i;
                for (i = 0; i < feed_state->push_options->nr; i++)
@@ -807,6 +809,7 @@ static int run_update_hook(struct command *cmd)
        proc.stdout_to_stderr = 1;
        proc.err = use_sideband ? -1 : 0;
        proc.argv = argv;
+       proc.trace2_hook_name = "update";
 
        code = start_command(&proc);
        if (code)
@@ -1190,6 +1193,7 @@ static void run_update_post_hook(struct command *commands)
        proc.no_stdin = 1;
        proc.stdout_to_stderr = 1;
        proc.err = use_sideband ? -1 : 0;
+       proc.trace2_hook_name = "post-update";
 
        if (!start_command(&proc)) {
                if (use_sideband)
@@ -1198,17 +1202,12 @@ static void run_update_post_hook(struct command *commands)
        }
 }
 
-static void check_aliased_update(struct command *cmd, struct string_list *list)
+static void check_aliased_update_internal(struct command *cmd,
+                                         struct string_list *list,
+                                         const char *dst_name, int flag)
 {
-       struct strbuf buf = STRBUF_INIT;
-       const char *dst_name;
        struct string_list_item *item;
        struct command *dst_cmd;
-       int flag;
-
-       strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
-       dst_name = resolve_ref_unsafe(buf.buf, 0, NULL, &flag);
-       strbuf_release(&buf);
 
        if (!(flag & REF_ISSYMREF))
                return;
@@ -1247,6 +1246,18 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
                "inconsistent aliased update";
 }
 
+static void check_aliased_update(struct command *cmd, struct string_list *list)
+{
+       struct strbuf buf = STRBUF_INIT;
+       const char *dst_name;
+       int flag;
+
+       strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
+       dst_name = resolve_ref_unsafe(buf.buf, 0, NULL, &flag);
+       check_aliased_update_internal(cmd, list, dst_name, flag);
+       strbuf_release(&buf);
+}
+
 static void check_aliased_updates(struct command *commands)
 {
        struct command *cmd;
index 5b80b7f21141d6f85d8444d669dffe289f3a60f5..f5701629a8a5698a64c91ca92226e6da72697441 100644 (file)
@@ -82,6 +82,10 @@ static int list_replace_refs(const char *pattern, const char *format)
                data.format = REPLACE_FORMAT_MEDIUM;
        else if (!strcmp(format, "long"))
                data.format = REPLACE_FORMAT_LONG;
+       /*
+        * Please update _git_replace() in git-completion.bash when
+        * you add new format
+        */
        else
                return error(_("invalid replace format '%s'\n"
                               "valid formats are 'short', 'medium' and 'long'"),
index 4d18a461fab631dd752adec3b95197a3a2657a62..7882829a95d8294ea5bb5bcba3335e651233d79a 100644 (file)
@@ -341,6 +341,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
        if (patch_mode) {
                if (reset_type != NONE)
                        die(_("--patch is incompatible with --{hard,mixed,soft}"));
+               trace2_cmd_mode("patch-interactive");
                return run_add_interactive(rev, "--patch=reset", &pathspec);
        }
 
@@ -357,6 +358,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
        if (reset_type == NONE)
                reset_type = MIXED; /* by default */
 
+       if (pathspec.nr)
+               trace2_cmd_mode("path");
+       else
+               trace2_cmd_mode(reset_type_names[reset_type]);
+
        if (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))
                setup_work_tree();
 
index 5b5b6dbb1c9b6f9893f3d0420c99655eb19501a8..425a5774db0697774df499fc16a626ab1e1efcf9 100644 (file)
@@ -238,7 +238,7 @@ static inline void finish_object__ma(struct object *obj)
 static int finish_object(struct object *obj, const char *name, void *cb_data)
 {
        struct rev_list_info *info = cb_data;
-       if (!has_object_file(&obj->oid)) {
+       if (oid_object_info_extended(the_repository, &obj->oid, NULL, 0) < 0) {
                finish_object__ma(obj);
                return 1;
        }
index b80fc4ba3d88b56436fa3f6b9a132bfe9e82b79b..6bcc4f1bd773b38f5895d3404419e362ded63d2a 100644 (file)
@@ -1816,11 +1816,10 @@ static int update_submodules(struct submodule_update_clone *suc)
 {
        int i;
 
-       run_processes_parallel(suc->max_jobs,
-                              update_clone_get_next_task,
-                              update_clone_start_failure,
-                              update_clone_task_finished,
-                              suc);
+       run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
+                                  update_clone_start_failure,
+                                  update_clone_task_finished, suc, "submodule",
+                                  "parallel/update");
 
        /*
         * We saved the output and put it out all at once now.
index 02ace602b9ae23d6ab74abac016948410b30fcde..1b6c42f748fd52ece4cf5f109f92a50bc2a87be3 100644 (file)
@@ -848,14 +848,16 @@ static int parse_new_style_cacheinfo(const char *arg,
        return 0;
 }
 
-static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
-                               const struct option *opt, int unset)
+static enum parse_opt_result cacheinfo_callback(
+       struct parse_opt_ctx_t *ctx, const struct option *opt,
+       const char *arg, int unset)
 {
        struct object_id oid;
        unsigned int mode;
        const char *path;
 
        BUG_ON_OPT_NEG(unset);
+       BUG_ON_OPT_ARG(arg);
 
        if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, &oid, &path)) {
                if (add_cacheinfo(mode, &oid, path, 0))
@@ -874,12 +876,14 @@ static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
        return 0;
 }
 
-static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx,
-                             const struct option *opt, int unset)
+static enum parse_opt_result stdin_cacheinfo_callback(
+       struct parse_opt_ctx_t *ctx, const struct option *opt,
+       const char *arg, int unset)
 {
        int *nul_term_line = opt->value;
 
        BUG_ON_OPT_NEG(unset);
+       BUG_ON_OPT_ARG(arg);
 
        if (ctx->argc != 1)
                return error("option '%s' must be the last argument", opt->long_name);
@@ -888,12 +892,14 @@ static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx,
        return 0;
 }
 
-static int stdin_callback(struct parse_opt_ctx_t *ctx,
-                               const struct option *opt, int unset)
+static enum parse_opt_result stdin_callback(
+       struct parse_opt_ctx_t *ctx, const struct option *opt,
+       const char *arg, int unset)
 {
        int *read_from_stdin = opt->value;
 
        BUG_ON_OPT_NEG(unset);
+       BUG_ON_OPT_ARG(arg);
 
        if (ctx->argc != 1)
                return error("option '%s' must be the last argument", opt->long_name);
@@ -901,13 +907,15 @@ static int stdin_callback(struct parse_opt_ctx_t *ctx,
        return 0;
 }
 
-static int unresolve_callback(struct parse_opt_ctx_t *ctx,
-                               const struct option *opt, int unset)
+static enum parse_opt_result unresolve_callback(
+       struct parse_opt_ctx_t *ctx, const struct option *opt,
+       const char *arg, int unset)
 {
        int *has_errors = opt->value;
        const char *prefix = startup_info->prefix;
 
        BUG_ON_OPT_NEG(unset);
+       BUG_ON_OPT_ARG(arg);
 
        /* consume remaining arguments. */
        *has_errors = do_unresolve(ctx->argc, ctx->argv,
@@ -920,13 +928,15 @@ static int unresolve_callback(struct parse_opt_ctx_t *ctx,
        return 0;
 }
 
-static int reupdate_callback(struct parse_opt_ctx_t *ctx,
-                               const struct option *opt, int unset)
+static enum parse_opt_result reupdate_callback(
+       struct parse_opt_ctx_t *ctx, const struct option *opt,
+       const char *arg, int unset)
 {
        int *has_errors = opt->value;
        const char *prefix = startup_info->prefix;
 
        BUG_ON_OPT_NEG(unset);
+       BUG_ON_OPT_ARG(arg);
 
        /* consume remaining arguments. */
        setup_work_tree();
@@ -986,7 +996,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                        N_("add the specified entry to the index"),
                        PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */
                        PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
-                       (parse_opt_cb *) cacheinfo_callback},
+                       NULL, 0,
+                       cacheinfo_callback},
                {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+|-)x",
                        N_("override the executable bit of the listed files"),
                        PARSE_OPT_NONEG,
@@ -1012,19 +1023,19 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL,
                        N_("read list of paths to be updated from standard input"),
                        PARSE_OPT_NONEG | PARSE_OPT_NOARG,
-                       (parse_opt_cb *) stdin_callback},
+                       NULL, 0, stdin_callback},
                {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL,
                        N_("add entries from standard input to the index"),
                        PARSE_OPT_NONEG | PARSE_OPT_NOARG,
-                       (parse_opt_cb *) stdin_cacheinfo_callback},
+                       NULL, 0, stdin_cacheinfo_callback},
                {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL,
                        N_("repopulate stages #2 and #3 for the listed paths"),
                        PARSE_OPT_NONEG | PARSE_OPT_NOARG,
-                       (parse_opt_cb *) unresolve_callback},
+                       NULL, 0, unresolve_callback},
                {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL,
                        N_("only update entries that differ from HEAD"),
                        PARSE_OPT_NONEG | PARSE_OPT_NOARG,
-                       (parse_opt_cb *) reupdate_callback},
+                       NULL, 0, reupdate_callback},
                OPT_BIT(0, "ignore-missing", &refresh_args.flags,
                        N_("ignore files missing from worktree"),
                        REFRESH_IGNORE_MISSING),
index 3f9907fcc994d248ba604509f6feb5bdc0499329..d2a7e2f3f18ba411d065a52ac868db612de5beaa 100644 (file)
@@ -268,10 +268,10 @@ static int add_worktree(const char *path, const char *refname,
        struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
        const char *name;
-       struct stat st;
        struct child_process cp = CHILD_PROCESS_INIT;
        struct argv_array child_env = ARGV_ARRAY_INIT;
-       int counter = 0, len, ret;
+       unsigned int counter = 0;
+       int len, ret;
        struct strbuf symref = STRBUF_INIT;
        struct commit *commit = NULL;
        int is_branch = 0;
@@ -295,8 +295,12 @@ static int add_worktree(const char *path, const char *refname,
        if (safe_create_leading_directories_const(sb_repo.buf))
                die_errno(_("could not create leading directories of '%s'"),
                          sb_repo.buf);
-       while (!stat(sb_repo.buf, &st)) {
+
+       while (mkdir(sb_repo.buf, 0777)) {
                counter++;
+               if ((errno != EEXIST) || !counter /* overflow */)
+                       die_errno(_("could not create directory of '%s'"),
+                                 sb_repo.buf);
                strbuf_setlen(&sb_repo, len);
                strbuf_addf(&sb_repo, "%d", counter);
        }
@@ -306,8 +310,6 @@ static int add_worktree(const char *path, const char *refname,
        atexit(remove_junk);
        sigchain_push_common(remove_junk_on_signal);
 
-       if (mkdir(sb_repo.buf, 0777))
-               die_errno(_("could not create directory of '%s'"), sb_repo.buf);
        junk_git_dir = xstrdup(sb_repo.buf);
        is_junk = 1;
 
@@ -402,6 +404,7 @@ done:
                        cp.dir = path;
                        cp.env = env;
                        cp.argv = NULL;
+                       cp.trace2_hook_name = "post-checkout";
                        argv_array_pushl(&cp.args, absolute_path(hook),
                                         oid_to_hex(&null_oid),
                                         oid_to_hex(&commit->object.oid),
diff --git a/cache.h b/cache.h
index 27fe635f622c49e2a21ffcb91d3113d3b3fbad8c..ac92421f3a8bc814da3f01731f2d8d8a52dc8364 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -9,6 +9,7 @@
 #include "gettext.h"
 #include "convert.h"
 #include "trace.h"
+#include "trace2.h"
 #include "string-list.h"
 #include "pack-revindex.h"
 #include "hash.h"
@@ -758,7 +759,7 @@ extern void rename_index_entry_at(struct index_state *, int pos, const char *new
 /* Remove entry, return true if there are more entries to go. */
 extern int remove_index_entry_at(struct index_state *, int pos);
 
-extern void remove_marked_cache_entries(struct index_state *istate);
+extern void remove_marked_cache_entries(struct index_state *istate, int invalidate);
 extern int remove_file_from_index(struct index_state *, const char *path);
 #define ADD_CACHE_VERBOSE 1
 #define ADD_CACHE_PRETEND 2
@@ -961,6 +962,10 @@ extern char *repository_format_partial_clone;
 extern const char *core_partial_clone_filter_default;
 extern int repository_format_worktree_config;
 
+/*
+ * You _have_ to initialize a `struct repository_format` using
+ * `= REPOSITORY_FORMAT_INIT` before calling `read_repository_format()`.
+ */
 struct repository_format {
        int version;
        int precious_objects;
@@ -972,14 +977,35 @@ struct repository_format {
        struct string_list unknown_extensions;
 };
 
+/*
+ * Always use this to initialize a `struct repository_format`
+ * to a well-defined, default state before calling
+ * `read_repository()`.
+ */
+#define REPOSITORY_FORMAT_INIT \
+{ \
+       .version = -1, \
+       .is_bare = -1, \
+       .hash_algo = GIT_HASH_SHA1, \
+       .unknown_extensions = STRING_LIST_INIT_DUP, \
+}
+
 /*
  * Read the repository format characteristics from the config file "path" into
- * "format" struct. Returns the numeric version. On error, -1 is returned,
- * format->version is set to -1, and all other fields in the struct are
- * undefined.
+ * "format" struct. Returns the numeric version. On error, or if no version is
+ * found in the configuration, -1 is returned, format->version is set to -1,
+ * and all other fields in the struct are set to the default configuration
+ * (REPOSITORY_FORMAT_INIT). Always initialize the struct using
+ * REPOSITORY_FORMAT_INIT before calling this function.
  */
 int read_repository_format(struct repository_format *format, const char *path);
 
+/*
+ * Free the memory held onto by `format`, but not the struct itself.
+ * (No need to use this after `read_repository_format()` fails.)
+ */
+void clear_repository_format(struct repository_format *format);
+
 /*
  * Verify that the repository described by repository_format is something we
  * can read. If it is, return 0. Otherwise, return -1, and "err" will describe
@@ -1506,10 +1532,19 @@ int date_overflows(timestamp_t date);
 #define IDENT_STRICT          1
 #define IDENT_NO_DATE         2
 #define IDENT_NO_NAME         4
+
+enum want_ident {
+       WANT_BLANK_IDENT,
+       WANT_AUTHOR_IDENT,
+       WANT_COMMITTER_IDENT
+};
+
 extern const char *git_author_info(int);
 extern const char *git_committer_info(int);
-extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
-extern const char *fmt_name(const char *name, const char *email);
+extern const char *fmt_ident(const char *name, const char *email,
+               enum want_ident whose_ident,
+               const char *date_str, int);
+extern const char *fmt_name(enum want_ident);
 extern const char *ident_default_name(void);
 extern const char *ident_default_email(void);
 extern const char *git_editor(void);
@@ -1569,6 +1604,11 @@ struct checkout {
 extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts);
 extern void enable_delayed_checkout(struct checkout *state);
 extern int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
+/*
+ * Unlink the last component and schedule the leading directories for
+ * removal, such that empty directories get removed.
+ */
+extern void unlink_entry(const struct cache_entry *ce);
 
 struct cache_def {
        struct strbuf path;
diff --git a/check-racy.c b/check-racy.c
deleted file mode 100644 (file)
index 24b6542..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "cache.h"
-
-int main(int ac, char **av)
-{
-       int i;
-       int dirty, clean, racy;
-
-       dirty = clean = racy = 0;
-       read_cache();
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               struct stat st;
-
-               if (lstat(ce->name, &st)) {
-                       error_errno("lstat(%s)", ce->name);
-                       continue;
-               }
-
-               if (ce_match_stat(ce, &st, 0))
-                       dirty++;
-               else if (ce_match_stat(ce, &st, CE_MATCH_RACY_IS_DIRTY))
-                       racy++;
-               else
-                       clean++;
-       }
-       printf("dirty %d, clean %d, racy %d\n", dirty, clean, racy);
-       return 0;
-}
diff --git a/ci/run-windows-build.sh b/ci/run-windows-build.sh
deleted file mode 100755 (executable)
index a73a4ec..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env bash
-#
-# Script to trigger the Git for Windows build and test run.
-# Set the $GFW_CI_TOKEN as environment variable.
-# Pass the branch (only branches on https://github.com/git/git are
-# supported) and a commit hash.
-#
-
-. ${0%/*}/lib.sh
-
-test $# -ne 2 && echo "Unexpected number of parameters" && exit 1
-test -z "$GFW_CI_TOKEN" && echo "GFW_CI_TOKEN not defined" && exit
-
-BRANCH=$1
-COMMIT=$2
-
-gfwci () {
-       local CURL_ERROR_CODE HTTP_CODE
-       CONTENT_FILE=$(mktemp -t "git-windows-ci-XXXXXX")
-       while test -z $HTTP_CODE
-       do
-       HTTP_CODE=$(curl \
-               -H "Authentication: Bearer $GFW_CI_TOKEN" \
-               --silent --retry 5 --write-out '%{HTTP_CODE}' \
-               --output >(sed "$(printf '1s/^\xef\xbb\xbf//')" >$CONTENT_FILE) \
-               "https://git-for-windows-ci.azurewebsites.net/api/TestNow?$1" \
-       )
-       CURL_ERROR_CODE=$?
-               # The GfW CI web app sometimes returns HTTP errors of
-               # "502 bad gateway" or "503 service unavailable".
-               # We also need to check the HTTP content because the GfW web
-               # app seems to pass through (error) results from other Azure
-               # calls with HTTP code 200.
-               # Wait a little and retry if we detect this error. More info:
-               # https://docs.microsoft.com/en-in/azure/app-service-web/app-service-web-troubleshoot-http-502-http-503
-               if test $HTTP_CODE -eq 502 ||
-                  test $HTTP_CODE -eq 503 ||
-                  grep "502 - Web server received an invalid response" $CONTENT_FILE >/dev/null
-               then
-                       sleep 10
-                       HTTP_CODE=
-               fi
-       done
-       cat $CONTENT_FILE
-       rm $CONTENT_FILE
-       if test $CURL_ERROR_CODE -ne 0
-       then
-               return $CURL_ERROR_CODE
-       fi
-       if test "$HTTP_CODE" -ge 400 && test "$HTTP_CODE" -lt 600
-       then
-               return 127
-       fi
-}
-
-# Trigger build job
-BUILD_ID=$(gfwci "action=trigger&branch=$BRANCH&commit=$COMMIT&skipTests=false")
-if test $? -ne 0
-then
-       echo "Unable to trigger Visual Studio Team Services Build"
-       echo "$BUILD_ID"
-       exit 1
-fi
-
-# Check if the $BUILD_ID contains a number
-case $BUILD_ID in
-''|*[!0-9]*) echo "Unexpected build number: $BUILD_ID" && exit 1
-esac
-
-echo "Visual Studio Team Services Build #${BUILD_ID}"
-
-# Tracing execued commands would produce too much noise in the waiting
-# loop below.
-set +x
-
-# Wait until build job finished
-STATUS=
-RESULT=
-while true
-do
-       LAST_STATUS=$STATUS
-       STATUS=$(gfwci "action=status&buildId=$BUILD_ID")
-       test "$STATUS" = "$LAST_STATUS" || printf "\nStatus: %s " "$STATUS"
-       printf "."
-
-       case "$STATUS" in
-       inProgress|postponed|notStarted) sleep 10               ;; # continue
-                "completed: succeeded") RESULT="success"; break;; # success
-                   "completed: failed")                   break;; # failure
-       *) echo "Unhandled status: $STATUS";               break;; # unknown
-       esac
-done
-
-# Print log
-echo ""
-echo ""
-set -x
-gfwci "action=log&buildId=$BUILD_ID" | cut -c 30-
-
-# Set exit code for TravisCI
-test "$RESULT" = "success"
-
-save_good_tree
index 23d8fabe75d9d80b2811acfc20f62d210ec6bba8..3e49f3bda84b929f5ac6846dccc51df8de902b89 100644 (file)
@@ -23,11 +23,20 @@ static int compare_paths(const struct combine_diff_path *one,
                                 two->path, strlen(two->path), two->mode);
 }
 
-static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
+static int filename_changed(char status)
+{
+       return status == 'R' || status == 'C';
+}
+
+static struct combine_diff_path *intersect_paths(
+       struct combine_diff_path *curr,
+       int n,
+       int num_parent,
+       int combined_all_paths)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
        struct combine_diff_path *p, **tail = &curr;
-       int i, cmp;
+       int i, j, cmp;
 
        if (!n) {
                for (i = 0; i < q->nr; i++) {
@@ -50,6 +59,13 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
                        oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid);
                        p->parent[n].mode = q->queue[i]->one->mode;
                        p->parent[n].status = q->queue[i]->status;
+
+                       if (combined_all_paths &&
+                           filename_changed(p->parent[n].status)) {
+                               strbuf_init(&p->parent[n].path, 0);
+                               strbuf_addstr(&p->parent[n].path,
+                                             q->queue[i]->one->path);
+                       }
                        *tail = p;
                        tail = &p->next;
                }
@@ -68,6 +84,10 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
                if (cmp < 0) {
                        /* p->path not in q->queue[]; drop it */
                        *tail = p->next;
+                       for (j = 0; j < num_parent; j++)
+                               if (combined_all_paths &&
+                                   filename_changed(p->parent[j].status))
+                                       strbuf_release(&p->parent[j].path);
                        free(p);
                        continue;
                }
@@ -81,6 +101,10 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
                oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid);
                p->parent[n].mode = q->queue[i]->one->mode;
                p->parent[n].status = q->queue[i]->status;
+               if (combined_all_paths &&
+                   filename_changed(p->parent[n].status))
+                       strbuf_addstr(&p->parent[n].path,
+                                     q->queue[i]->one->path);
 
                tail = &p->next;
                i++;
@@ -960,12 +984,25 @@ static void show_combined_header(struct combine_diff_path *elem,
        if (!show_file_header)
                return;
 
-       if (added)
-               dump_quoted_path("--- ", "", "/dev/null",
-                                line_prefix, c_meta, c_reset);
-       else
-               dump_quoted_path("--- ", a_prefix, elem->path,
-                                line_prefix, c_meta, c_reset);
+       if (rev->combined_all_paths) {
+               for (i = 0; i < num_parent; i++) {
+                       char *path = filename_changed(elem->parent[i].status)
+                               ? elem->parent[i].path.buf : elem->path;
+                       if (elem->parent[i].status == DIFF_STATUS_ADDED)
+                               dump_quoted_path("--- ", "", "/dev/null",
+                                                line_prefix, c_meta, c_reset);
+                       else
+                               dump_quoted_path("--- ", a_prefix, path,
+                                                line_prefix, c_meta, c_reset);
+               }
+       } else {
+               if (added)
+                       dump_quoted_path("--- ", "", "/dev/null",
+                                        line_prefix, c_meta, c_reset);
+               else
+                       dump_quoted_path("--- ", a_prefix, elem->path,
+                                        line_prefix, c_meta, c_reset);
+       }
        if (deleted)
                dump_quoted_path("+++ ", "", "/dev/null",
                                 line_prefix, c_meta, c_reset);
@@ -1227,6 +1264,15 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
                putchar(inter_name_termination);
        }
 
+       for (i = 0; i < num_parent; i++)
+               if (rev->combined_all_paths) {
+                       if (filename_changed(p->parent[i].status))
+                               write_name_quoted(p->parent[i].path.buf, stdout,
+                                                 inter_name_termination);
+                       else
+                               write_name_quoted(p->path, stdout,
+                                                 inter_name_termination);
+               }
        write_name_quoted(p->path, stdout, line_termination);
 }
 
@@ -1332,7 +1378,9 @@ static const char *path_path(void *obj)
 
 /* find set of paths that every parent touches */
 static struct combine_diff_path *find_paths_generic(const struct object_id *oid,
-       const struct oid_array *parents, struct diff_options *opt)
+       const struct oid_array *parents,
+       struct diff_options *opt,
+       int combined_all_paths)
 {
        struct combine_diff_path *paths = NULL;
        int i, num_parent = parents->nr;
@@ -1357,7 +1405,8 @@ static struct combine_diff_path *find_paths_generic(const struct object_id *oid,
                        opt->output_format = DIFF_FORMAT_NO_OUTPUT;
                diff_tree_oid(&parents->oid[i], oid, "", opt);
                diffcore_std(opt);
-               paths = intersect_paths(paths, i, num_parent);
+               paths = intersect_paths(paths, i, num_parent,
+                                       combined_all_paths);
 
                /* if showing diff, show it in requested order */
                if (opt->output_format != DIFF_FORMAT_NO_OUTPUT &&
@@ -1467,7 +1516,8 @@ void diff_tree_combined(const struct object_id *oid,
                 * diff(sha1,parent_i) for all i to do the job, specifically
                 * for parent0.
                 */
-               paths = find_paths_generic(oid, parents, &diffopts);
+               paths = find_paths_generic(oid, parents, &diffopts,
+                                          rev->combined_all_paths);
        }
        else {
                int stat_opt;
@@ -1540,6 +1590,10 @@ void diff_tree_combined(const struct object_id *oid,
        while (paths) {
                struct combine_diff_path *tmp = paths;
                paths = paths->next;
+               for (i = 0; i < num_parent; i++)
+                       if (rev->combined_all_paths &&
+                           filename_changed(tmp->parent[i].status))
+                               strbuf_release(&tmp->parent[i].path);
                free(tmp);
        }
 
index 3728f66b4cce80d298aab0e551a2d3c03e2c4357..d484aec20979ac9a1ea6d49b166f51436a24dfad 100644 (file)
@@ -25,12 +25,19 @@ static void restore_sigpipe_to_default(void)
 
 int main(int argc, const char **argv)
 {
+       int result;
+
        /*
         * Always open file descriptors 0/1/2 to avoid clobbering files
         * in die().  It also avoids messing up when the pipes are dup'ed
         * onto stdin/stdout/stderr in the child processes we spawn.
         */
        sanitize_stdfds();
+       restore_sigpipe_to_default();
+
+       trace2_initialize();
+       trace2_cmd_start(argv);
+       trace2_collect_process_info();
 
        git_resolve_executable_dir(argv[0]);
 
@@ -40,7 +47,9 @@ int main(int argc, const char **argv)
 
        attr_start();
 
-       restore_sigpipe_to_default();
+       result = cmd_main(argc, argv);
+
+       trace2_cmd_exit(result);
 
-       return cmd_main(argc, argv);
+       return result;
 }
index 5078ce5eccfaf17620c21e0a7f7133b3badef3d3..e4e25735ceb332e0a3c3601ba36b07bb933c7ba5 100644 (file)
@@ -1,3 +1,6 @@
+#ifndef COMPAT_BSWAP_H
+#define COMPAT_BSWAP_H
+
 /*
  * Let's make sure we always have a sane definition for ntohl()/htonl().
  * Some libraries define those as a function call, just to perform byte
@@ -210,3 +213,5 @@ static inline void put_be64(void *ptr, uint64_t value)
 }
 
 #endif
+
+#endif /* COMPAT_BSWAP_H */
index 8141f77189c66b07a1e1cd5ab92418d0de4617e5..6b04514cdc62167da027b48bae816e72811f1584 100644 (file)
@@ -1551,19 +1551,23 @@ static int try_shell_exec(const char *cmd, char *const *argv)
                return 0;
        prog = path_lookup(interpr, 1);
        if (prog) {
+               int exec_id;
                int argc = 0;
                const char **argv2;
                while (argv[argc]) argc++;
                ALLOC_ARRAY(argv2, argc + 1);
                argv2[0] = (char *)cmd; /* full path to the script file */
                memcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);
+               exec_id = trace2_exec(prog, argv2);
                pid = mingw_spawnv(prog, argv2, 1);
                if (pid >= 0) {
                        int status;
                        if (waitpid(pid, &status, 0) < 0)
                                status = 255;
+                       trace2_exec_result(exec_id, status);
                        exit(status);
                }
+               trace2_exec_result(exec_id, -1);
                pid = 1;        /* indicate that we tried but failed */
                free(prog);
                free(argv2);
@@ -1576,12 +1580,17 @@ int mingw_execv(const char *cmd, char *const *argv)
        /* check if git_command is a shell script */
        if (!try_shell_exec(cmd, argv)) {
                int pid, status;
+               int exec_id;
 
+               exec_id = trace2_exec(cmd, (const char **)argv);
                pid = mingw_spawnv(cmd, (const char **)argv, 0);
-               if (pid < 0)
+               if (pid < 0) {
+                       trace2_exec_result(exec_id, -1);
                        return -1;
+               }
                if (waitpid(pid, &status, 0) < 0)
                        status = 255;
+               trace2_exec_result(exec_id, status);
                exit(status);
        }
        return -1;
index 30d9fb3e36274657e5d2a63ef2f5eb3e1c55ce61..4d73f8aa9d0d0937b1fcbe8d854485327fc3896c 100644 (file)
@@ -147,8 +147,7 @@ static inline int fcntl(int fd, int cmd, ...)
        errno = EINVAL;
        return -1;
 }
-/* bash cannot reliably detect negative return codes as failure */
-#define exit(code) exit((code) & 0xff)
+
 #define sigemptyset(x) (void)0
 static inline int sigaddset(sigset_t *set, int signum)
 { return 0; }
diff --git a/compat/win32/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c
new file mode 100644 (file)
index 0000000..52bd620
--- /dev/null
@@ -0,0 +1,147 @@
+#include "../../cache.h"
+#include "../../json-writer.h"
+#include <Psapi.h>
+#include <tlHelp32.h>
+
+/*
+ * An arbitrarily chosen value to limit the size of the ancestor
+ * array built in git_processes().
+ */
+#define NR_PIDS_LIMIT 10
+
+/*
+ * Find the process data for the given PID in the given snapshot
+ * and update the PROCESSENTRY32 data.
+ */
+static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)
+{
+       pe32->dwSize = sizeof(PROCESSENTRY32);
+
+       if (Process32First(hSnapshot, pe32)) {
+               do {
+                       if (pe32->th32ProcessID == pid)
+                               return 1;
+               } while (Process32Next(hSnapshot, pe32));
+       }
+       return 0;
+}
+
+/*
+ * Accumulate JSON array of our parent processes:
+ *     [
+ *         exe-name-parent,
+ *         exe-name-grand-parent,
+ *         ...
+ *     ]
+ *
+ * Note: we only report the filename of the process executable; the
+ *       only way to get its full pathname is to use OpenProcess()
+ *       and GetModuleFileNameEx() or QueryfullProcessImageName()
+ *       and that seems rather expensive (on top of the cost of
+ *       getting the snapshot).
+ *
+ * Note: we compute the set of parent processes by walking the PPID
+ *       link in each visited PROCESSENTRY32 record.  This search
+ *       stops when an ancestor process is not found in the snapshot
+ *       (because it exited before the current or intermediate parent
+ *       process exited).
+ *
+ *       This search may compute an incorrect result if the PPID link
+ *       refers to the PID of an exited parent and that PID has been
+ *       recycled and given to a new unrelated process.
+ *
+ *       Worse, it is possible for a child or descendant of the
+ *       current process to be given the recycled PID and cause a
+ *       PPID-cycle.  This would cause an infinite loop building our
+ *       parent process array.
+ *
+ * Note: for completeness, the "System Idle" process has PID=0 and
+ *       PPID=0 and could cause another PPID-cycle.  We don't expect
+ *       Git to be a descendant of the idle process, but because of
+ *       PID recycling, it might be possible to get a PPID link value
+ *       of 0.  This too would cause an infinite loop.
+ *
+ * Therefore, we keep an array of the visited PPIDs to guard against
+ * cycles.
+ *
+ * We use a fixed-size array rather than ALLOC_GROW to keep things
+ * simple and avoid the alloc/realloc overhead.  It is OK if we
+ * truncate the search and return a partial answer.
+ */
+static void get_processes(struct json_writer *jw, HANDLE hSnapshot)
+{
+       PROCESSENTRY32 pe32;
+       DWORD pid;
+       DWORD pid_list[NR_PIDS_LIMIT];
+       int k, nr_pids = 0;
+
+       pid = GetCurrentProcessId();
+       while (find_pid(pid, hSnapshot, &pe32)) {
+               /* Only report parents. Omit self from the JSON output. */
+               if (nr_pids)
+                       jw_array_string(jw, pe32.szExeFile);
+
+               /* Check for cycle in snapshot. (Yes, it happened.) */
+               for (k = 0; k < nr_pids; k++)
+                       if (pid == pid_list[k]) {
+                               jw_array_string(jw, "(cycle)");
+                               return;
+                       }
+
+               if (nr_pids == NR_PIDS_LIMIT) {
+                       jw_array_string(jw, "(truncated)");
+                       return;
+               }
+
+               pid_list[nr_pids++] = pid;
+
+               pid = pe32.th32ParentProcessID;
+       }
+}
+
+/*
+ * Emit JSON data for the current and parent processes.  Individual
+ * trace2 targets can decide how to actually print it.
+ */
+static void get_ancestry(void)
+{
+       HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+
+       if (hSnapshot != INVALID_HANDLE_VALUE) {
+               struct json_writer jw = JSON_WRITER_INIT;
+
+               jw_array_begin(&jw, 0);
+               get_processes(&jw, hSnapshot);
+               jw_end(&jw);
+
+               trace2_data_json("process", the_repository, "windows/ancestry",
+                                &jw);
+
+               jw_release(&jw);
+               CloseHandle(hSnapshot);
+       }
+}
+
+/*
+ * Is a debugger attached to the current process?
+ *
+ * This will catch debug runs (where the debugger started the process).
+ * This is the normal case.  Since this code is called during our startup,
+ * it will not report instances where a debugger is attached dynamically
+ * to a running git process, but that is relatively rare.
+ */
+static void get_is_being_debugged(void)
+{
+       if (IsDebuggerPresent())
+               trace2_data_intmax("process", the_repository,
+                                  "windows/debugger_present", 1);
+}
+
+void trace2_collect_process_info(void)
+{
+       if (!trace2_is_enabled())
+               return;
+
+       get_is_being_debugged();
+       get_ancestry();
+}
index 24ad1a985400168e33778781d01d6e9e6c6ededf..0f0cdd8c0fc9ee40c1f56d1935c1816eb03601ad 100644 (file)
--- a/config.c
+++ b/config.c
@@ -1445,7 +1445,9 @@ int git_default_config(const char *var, const char *value, void *cb)
        if (starts_with(var, "core."))
                return git_default_core_config(var, value, cb);
 
-       if (starts_with(var, "user."))
+       if (starts_with(var, "user.") ||
+           starts_with(var, "author.") ||
+           starts_with(var, "committer."))
                return git_ident_config(var, value, cb);
 
        if (starts_with(var, "i18n."))
@@ -2655,6 +2657,8 @@ int git_config_set_gently(const char *key, const char *value)
 void git_config_set(const char *key, const char *value)
 {
        git_config_set_multivar(key, value, NULL, 0);
+
+       trace2_cmd_set_config(key, value);
 }
 
 /*
index 7354fe15b3d93737b1c15c74943da2b7630108d6..bf1f3fcdeed3a819b7098baf1d710be29f16c93e 100644 (file)
@@ -1,41 +1,41 @@
 ifeq ($(filter no-error,$(DEVOPTS)),)
-CFLAGS += -Werror
+DEVELOPER_CFLAGS += -Werror
 endif
 ifneq ($(filter pedantic,$(DEVOPTS)),)
-CFLAGS += -pedantic
+DEVELOPER_CFLAGS += -pedantic
 # don't warn for each N_ use
-CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=0
-endif
-CFLAGS += -Wall
-CFLAGS += -Wdeclaration-after-statement
-CFLAGS += -Wformat-security
-CFLAGS += -Wno-format-zero-length
-CFLAGS += -Wold-style-definition
-CFLAGS += -Woverflow
-CFLAGS += -Wpointer-arith
-CFLAGS += -Wstrict-prototypes
-CFLAGS += -Wunused
-CFLAGS += -Wvla
+DEVELOPER_CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=0
+endif
+DEVELOPER_CFLAGS += -Wall
+DEVELOPER_CFLAGS += -Wdeclaration-after-statement
+DEVELOPER_CFLAGS += -Wformat-security
+DEVELOPER_CFLAGS += -Wno-format-zero-length
+DEVELOPER_CFLAGS += -Wold-style-definition
+DEVELOPER_CFLAGS += -Woverflow
+DEVELOPER_CFLAGS += -Wpointer-arith
+DEVELOPER_CFLAGS += -Wstrict-prototypes
+DEVELOPER_CFLAGS += -Wunused
+DEVELOPER_CFLAGS += -Wvla
 
 ifndef COMPILER_FEATURES
 COMPILER_FEATURES := $(shell ./detect-compiler $(CC))
 endif
 
 ifneq ($(filter clang4,$(COMPILER_FEATURES)),)
-CFLAGS += -Wtautological-constant-out-of-range-compare
+DEVELOPER_CFLAGS += -Wtautological-constant-out-of-range-compare
 endif
 
 ifneq ($(or $(filter gcc6,$(COMPILER_FEATURES)),$(filter clang4,$(COMPILER_FEATURES))),)
-CFLAGS += -Wextra
+DEVELOPER_CFLAGS += -Wextra
 # if a function is public, there should be a prototype and the right
 # header file should be included. If not, it should be static.
-CFLAGS += -Wmissing-prototypes
+DEVELOPER_CFLAGS += -Wmissing-prototypes
 ifeq ($(filter extra-all,$(DEVOPTS)),)
 # These are disabled because we have these all over the place.
-CFLAGS += -Wno-empty-body
-CFLAGS += -Wno-missing-field-initializers
-CFLAGS += -Wno-sign-compare
-CFLAGS += -Wno-unused-parameter
+DEVELOPER_CFLAGS += -Wno-empty-body
+DEVELOPER_CFLAGS += -Wno-missing-field-initializers
+DEVELOPER_CFLAGS += -Wno-sign-compare
+DEVELOPER_CFLAGS += -Wno-unused-parameter
 endif
 endif
 
@@ -43,6 +43,6 @@ endif
 # not worth fixing since newer compilers correctly stop complaining
 ifneq ($(filter gcc4,$(COMPILER_FEATURES)),)
 ifeq ($(filter gcc5,$(COMPILER_FEATURES)),)
-CFLAGS += -Wno-uninitialized
+DEVELOPER_CFLAGS += -Wno-uninitialized
 endif
 endif
index 2011bac6d54ebb66544c94aa27453e2545a7a34a..41e85fab1c8d1ed5b6b7ffdbe8b840bdc12d77de 100644 (file)
@@ -393,6 +393,7 @@ ifeq ($(uname_S),Windows)
        BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
        COMPAT_OBJS = compat/msvc.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
+               compat/win32/trace2_win32_process_info.o \
                compat/win32/dirent.o
        COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
        BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE
@@ -529,7 +530,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_STRTOUMAX = YesPlease
        NO_MKDTEMP = YesPlease
        NO_SVN_TESTS = YesPlease
-       NO_PERL_MAKEMAKER = YesPlease
        RUNTIME_PREFIX = YesPlease
        HAVE_WPGMPTR = YesWeDo
        NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
@@ -546,6 +546,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/winansi.o \
+               compat/win32/trace2_win32_process_info.o \
                compat/win32/path-utils.o \
                compat/win32/pthread.o compat/win32/syslog.o \
                compat/win32/dirent.o
index 4813f005ab05279a72ef2894cfae8887965d9640..2778481264d0e1fdd8864743fc860ef63718524e 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -1248,6 +1248,7 @@ struct child_process *git_connect(int fd[2], const char *url,
                conn = NULL;
        } else if (protocol == PROTO_GIT) {
                conn = git_connect_git(fd, hostandport, path, prog, version, flags);
+               conn->trace2_child_class = "transport/git";
        } else {
                struct strbuf cmd = STRBUF_INIT;
                const char *const *var;
@@ -1290,9 +1291,11 @@ struct child_process *git_connect(int fd[2], const char *url,
                                strbuf_release(&cmd);
                                return NULL;
                        }
+                       conn->trace2_child_class = "transport/ssh";
                        fill_ssh_args(conn, ssh_host, port, version, flags);
                } else {
                        transport_check_allowed("file");
+                       conn->trace2_child_class = "transport/file";
                        if (version > 0) {
                                argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
                                                 version);
index 499e56f83d0a0592abd53686612c79358780eef2..976e4a6548fdedf630b2f5e4539df1f43d83809f 100644 (file)
@@ -853,6 +853,11 @@ __git_compute_merge_strategies ()
        __git_merge_strategies=$(__git_list_merge_strategies)
 }
 
+__git_merge_strategy_options="ours theirs subtree subtree= patience
+       histogram diff-algorithm= ignore-space-change ignore-all-space
+       ignore-space-at-eol renormalize no-renormalize no-renames
+       find-renames find-renames= rename-threshold="
+
 __git_complete_revlist_file ()
 {
        local dequoted_word pfx ls ref cur_="$cur"
@@ -996,12 +1001,21 @@ __git_complete_strategy ()
        -s|--strategy)
                __gitcomp "$__git_merge_strategies"
                return 0
+               ;;
+       -X)
+               __gitcomp "$__git_merge_strategy_options"
+               return 0
+               ;;
        esac
        case "$cur" in
        --strategy=*)
                __gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}"
                return 0
                ;;
+       --strategy-option=*)
+               __gitcomp "$__git_merge_strategy_options" "" "${cur##--strategy-option=}"
+               return 0
+               ;;
        esac
        return 1
 }
@@ -1163,6 +1177,7 @@ __git_count_arguments ()
 }
 
 __git_whitespacelist="nowarn warn error error-all fix"
+__git_patchformat="mbox stgit stgit-series hg mboxrd"
 __git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch"
 
 _git_am ()
@@ -1177,6 +1192,10 @@ _git_am ()
                __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
                return
                ;;
+       --patch-format=*)
+               __gitcomp "$__git_patchformat" "" "${cur##--patch-format=}"
+               return
+               ;;
        --*)
                __gitcomp_builtin am "" \
                        "$__git_am_inprogress_options"
@@ -1200,6 +1219,10 @@ _git_apply ()
 _git_add ()
 {
        case "$cur" in
+       --chmod=*)
+               __gitcomp "+x -x" "" "${cur##--chmod=}"
+               return
+               ;;
        --*)
                __gitcomp_builtin add
                return
@@ -1260,6 +1283,8 @@ _git_bisect ()
        esac
 }
 
+__git_ref_fieldlist="refname objecttype objectsize objectname upstream push HEAD symref"
+
 _git_branch ()
 {
        local i c=1 only_local_ref="n" has_r="n"
@@ -1343,6 +1368,9 @@ _git_cherry_pick ()
                __gitcomp "$__git_cherry_pick_inprogress_options"
                return
        fi
+
+       __git_complete_strategy && return
+
        case "$cur" in
        --*)
                __gitcomp_builtin cherry-pick "" \
@@ -1506,6 +1534,10 @@ _git_fetch ()
                __gitcomp "$__git_fetch_recurse_submodules" "" "${cur##--recurse-submodules=}"
                return
                ;;
+       --filter=*)
+               __gitcomp "blob:none blob:limit= sparse:oid= sparse:path=" "" "${cur##--filter=}"
+               return
+               ;;
        --*)
                __gitcomp_builtin fetch
                return
@@ -1702,8 +1734,8 @@ __git_log_shortlog_options="
        --all-match --invert-grep
 "
 
-__git_log_pretty_formats="oneline short medium full fuller email raw format:"
-__git_log_date_formats="relative iso8601 rfc2822 short local default raw"
+__git_log_pretty_formats="oneline short medium full fuller email raw format: mboxrd"
+__git_log_date_formats="relative iso8601 iso8601-strict rfc2822 short local default raw unix format:"
 
 _git_log ()
 {
@@ -2221,7 +2253,7 @@ _git_config ()
                return
                ;;
        diff.submodule)
-               __gitcomp "log short"
+               __gitcomp "$__git_diff_submodule_formats"
                return
                ;;
        help.format)
@@ -2388,6 +2420,10 @@ _git_remote ()
 _git_replace ()
 {
        case "$cur" in
+       --format=*)
+               __gitcomp "short medium long" "" "${cur##--format=}"
+               return
+               ;;
        --*)
                __gitcomp_builtin replace
                return
@@ -2429,6 +2465,7 @@ _git_revert ()
                __gitcomp "$__git_revert_inprogress_options"
                return
        fi
+       __git_complete_strategy && return
        case "$cur" in
        --*)
                __gitcomp_builtin revert "" \
@@ -2573,7 +2610,7 @@ _git_submodule ()
 {
        __git_has_doubledash && return
 
-       local subcommands="add status init deinit update summary foreach sync"
+       local subcommands="add status init deinit update summary foreach sync absorbgitdirs"
        local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
                case "$cur" in
index 5d0307fc1004f215c48a6f653e21da41bee71baa..94ff8376492257782a1af5b3d2851ee8724c2edd 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -731,7 +731,7 @@ static int apply_single_file_filter(const char *path, const char *src, size_t le
        if (start_async(&async))
                return 0;       /* error was already reported */
 
-       if (strbuf_read(&nbuf, async.out, len) < 0) {
+       if (strbuf_read(&nbuf, async.out, 0) < 0) {
                err = error(_("read from external filter '%s' failed"), cmd);
        }
        if (close(async.out)) {
diff --git a/date.c b/date.c
index 9c5870e102951ed25f5def2103dab15bf8ed5c99..8126146c5069c9ab9d9f25f1f1bba1323f63cf52 100644 (file)
--- a/date.c
+++ b/date.c
@@ -921,6 +921,10 @@ static enum date_mode_type parse_date_type(const char *format, const char **end)
                return DATE_UNIX;
        if (skip_prefix(format, "format", end))
                return DATE_STRFTIME;
+       /*
+        * Please update $__git_log_date_formats in
+        * git-completion.bash when you add new formats.
+        */
 
        die("unknown date format %s", format);
 }
index 23c8d351b3aa5912c49c9da0eecd8727cf06ac72..a838c219ec044b9fb78b4bda058d078e66d7aaf8 100644 (file)
@@ -531,7 +531,7 @@ int run_diff_index(struct rev_info *revs, int cached)
                exit(128);
 
        diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
-       diffcore_fix_diff_index(&revs->diffopt);
+       diffcore_fix_diff_index();
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
        trace_performance_leave("diff-index");
index 9414e922d164e96ffedfa2504045930b058dbc80..6001baecd4c61ae22dd0aaec11bd4bc48a9152f8 100644 (file)
@@ -233,8 +233,7 @@ static void fixup_paths(const char **path, struct strbuf *replacement)
        }
 }
 
-void diff_no_index(struct repository *r,
-                  struct rev_info *revs,
+void diff_no_index(struct rev_info *revs,
                   int argc, const char **argv)
 {
        int i;
@@ -242,11 +241,6 @@ void diff_no_index(struct repository *r,
        struct strbuf replacement = STRBUF_INIT;
        const char *prefix = revs->prefix;
 
-       /*
-        * FIXME: --no-index should not look at index and we should be
-        * able to pass NULL repo. Maybe later.
-        */
-       repo_diff_setup(r, &revs->diffopt);
        for (i = 1; i < argc - 2; ) {
                int j;
                if (!strcmp(argv[i], "--no-index"))
diff --git a/diff.c b/diff.c
index 5306c48652db59e84c26383d68cf4a7d896647d4..ec5c095199b3953cc74dabdfccf4536d4c6df831 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -23,6 +23,7 @@
 #include "argv-array.h"
 #include "graph.h"
 #include "packfile.h"
+#include "parse-options.h"
 #include "help.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
@@ -103,11 +104,6 @@ static const char *color_diff_slots[] = {
        [DIFF_FILE_NEW_BOLD]          = "newBold",
 };
 
-static NORETURN void die_want_option(const char *option_name)
-{
-       die(_("option '%s' requires a value"), option_name);
-}
-
 define_list_config_array_extra(color_diff_slots, {"plain"});
 
 static int parse_diff_color_slot(const char *var)
@@ -178,6 +174,10 @@ static int parse_submodule_params(struct diff_options *options, const char *valu
                options->submodule_format = DIFF_SUBMODULE_SHORT;
        else if (!strcmp(value, "diff"))
                options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF;
+       /*
+        * Please update $__git_diff_submodule_formats in
+        * git-completion.bash when you add new formats.
+        */
        else
                return -1;
        return 0;
@@ -204,6 +204,10 @@ long parse_algorithm_value(const char *value)
                return XDF_PATIENCE_DIFF;
        else if (!strcasecmp(value, "histogram"))
                return XDF_HISTOGRAM_DIFF;
+       /*
+        * Please update $__git_diff_algorithms in git-completion.bash
+        * when you add new algorithms.
+        */
        return -1;
 }
 
@@ -1613,8 +1617,7 @@ static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line
        return ws_blank_line(line, len, ecbdata->ws_rule);
 }
 
-static void emit_add_line(const char *reset,
-                         struct emit_callback *ecbdata,
+static void emit_add_line(struct emit_callback *ecbdata,
                          const char *line, int len)
 {
        unsigned flags = WSEH_NEW | ecbdata->ws_rule;
@@ -1624,16 +1627,14 @@ static void emit_add_line(const char *reset,
        emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_PLUS, line, len, flags);
 }
 
-static void emit_del_line(const char *reset,
-                         struct emit_callback *ecbdata,
+static void emit_del_line(struct emit_callback *ecbdata,
                          const char *line, int len)
 {
        unsigned flags = WSEH_OLD | ecbdata->ws_rule;
        emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_MINUS, line, len, flags);
 }
 
-static void emit_context_line(const char *reset,
-                             struct emit_callback *ecbdata,
+static void emit_context_line(struct emit_callback *ecbdata,
                              const char *line, int len)
 {
        unsigned flags = WSEH_CONTEXT | ecbdata->ws_rule;
@@ -1742,7 +1743,6 @@ static void emit_rewrite_lines(struct emit_callback *ecb,
                               int prefix, const char *data, int size)
 {
        const char *endp = NULL;
-       const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
 
        while (0 < size) {
                int len;
@@ -1751,10 +1751,10 @@ static void emit_rewrite_lines(struct emit_callback *ecb,
                len = endp ? (endp - data + 1) : size;
                if (prefix != '+') {
                        ecb->lno_in_preimage++;
-                       emit_del_line(reset, ecb, data, len);
+                       emit_del_line(ecb, data, len);
                } else {
                        ecb->lno_in_postimage++;
-                       emit_add_line(reset, ecb, data, len);
+                       emit_add_line(ecb, data, len);
                }
                size -= len;
                data += len;
@@ -2291,7 +2291,7 @@ const char *diff_line_prefix(struct diff_options *opt)
        return msgbuf->buf;
 }
 
-static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
+static unsigned long sane_truncate_line(char *line, unsigned long len)
 {
        const char *cp;
        unsigned long allot;
@@ -2325,7 +2325,6 @@ static void find_lno(const char *line, struct emit_callback *ecbdata)
 static void fn_out_consume(void *priv, char *line, unsigned long len)
 {
        struct emit_callback *ecbdata = priv;
-       const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
        struct diff_options *o = ecbdata->opt;
 
        o->found_changes = 1;
@@ -2356,7 +2355,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
        if (line[0] == '@') {
                if (ecbdata->diff_words)
                        diff_words_flush(ecbdata);
-               len = sane_truncate_line(ecbdata, line, len);
+               len = sane_truncate_line(line, len);
                find_lno(line, ecbdata);
                emit_hunk_header(ecbdata, line, len);
                return;
@@ -2392,16 +2391,16 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
        switch (line[0]) {
        case '+':
                ecbdata->lno_in_postimage++;
-               emit_add_line(reset, ecbdata, line + 1, len - 1);
+               emit_add_line(ecbdata, line + 1, len - 1);
                break;
        case '-':
                ecbdata->lno_in_preimage++;
-               emit_del_line(reset, ecbdata, line + 1, len - 1);
+               emit_del_line(ecbdata, line + 1, len - 1);
                break;
        case ' ':
                ecbdata->lno_in_postimage++;
                ecbdata->lno_in_preimage++;
-               emit_context_line(reset, ecbdata, line + 1, len - 1);
+               emit_context_line(ecbdata, line + 1, len - 1);
                break;
        default:
                /* incomplete line at the end */
@@ -4183,7 +4182,6 @@ static void run_external_diff(const char *pgm,
                              struct diff_filespec *one,
                              struct diff_filespec *two,
                              const char *xfrm_msg,
-                             int complete_rewrite,
                              struct diff_options *o)
 {
        struct argv_array argv = ARGV_ARRAY_INIT;
@@ -4341,8 +4339,7 @@ static void run_diff_cmd(const char *pgm,
        }
 
        if (pgm) {
-               run_external_diff(pgm, name, other, one, two, xfrm_msg,
-                                 complete_rewrite, o);
+               run_external_diff(pgm, name, other, one, two, xfrm_msg, o);
                return;
        }
        if (one && two)
@@ -4491,6 +4488,8 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
        builtin_checkdiff(name, other, attr_path, p->one, p->two, o);
 }
 
+static void prep_parse_options(struct diff_options *options);
+
 void repo_diff_setup(struct repository *r, struct diff_options *options)
 {
        memcpy(options, &default_diff_options, sizeof(*options));
@@ -4532,6 +4531,8 @@ void repo_diff_setup(struct repository *r, struct diff_options *options)
 
        options->color_moved = diff_color_moved_default;
        options->color_moved_ws_handling = diff_color_moved_ws_default;
+
+       prep_parse_options(options);
 }
 
 void diff_setup_done(struct diff_options *options)
@@ -4635,6 +4636,8 @@ void diff_setup_done(struct diff_options *options)
 
        if (!options->use_color || external_diff())
                options->color_moved = 0;
+
+       FREE_AND_NULL(options->parseopts);
 }
 
 static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
@@ -4681,8 +4684,6 @@ static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *va
        return 1;
 }
 
-static int diff_scoreopt_parse(const char *opt);
-
 static inline int short_opt(char opt, const char **argv,
                            const char **optarg)
 {
@@ -4720,77 +4721,56 @@ int parse_long_opt(const char *opt, const char **argv,
        return 2;
 }
 
-static int stat_opt(struct diff_options *options, const char **av)
+static int diff_opt_stat(const struct option *opt, const char *value, int unset)
 {
-       const char *arg = av[0];
-       char *end;
+       struct diff_options *options = opt->value;
        int width = options->stat_width;
        int name_width = options->stat_name_width;
        int graph_width = options->stat_graph_width;
        int count = options->stat_count;
-       int argcount = 1;
+       char *end;
 
-       if (!skip_prefix(arg, "--stat", &arg))
-               BUG("stat option does not begin with --stat: %s", arg);
-       end = (char *)arg;
+       BUG_ON_OPT_NEG(unset);
 
-       switch (*arg) {
-       case '-':
-               if (skip_prefix(arg, "-width", &arg)) {
-                       if (*arg == '=')
-                               width = strtoul(arg + 1, &end, 10);
-                       else if (!*arg && !av[1])
-                               die_want_option("--stat-width");
-                       else if (!*arg) {
-                               width = strtoul(av[1], &end, 10);
-                               argcount = 2;
-                       }
-               } else if (skip_prefix(arg, "-name-width", &arg)) {
-                       if (*arg == '=')
-                               name_width = strtoul(arg + 1, &end, 10);
-                       else if (!*arg && !av[1])
-                               die_want_option("--stat-name-width");
-                       else if (!*arg) {
-                               name_width = strtoul(av[1], &end, 10);
-                               argcount = 2;
-                       }
-               } else if (skip_prefix(arg, "-graph-width", &arg)) {
-                       if (*arg == '=')
-                               graph_width = strtoul(arg + 1, &end, 10);
-                       else if (!*arg && !av[1])
-                               die_want_option("--stat-graph-width");
-                       else if (!*arg) {
-                               graph_width = strtoul(av[1], &end, 10);
-                               argcount = 2;
-                       }
-               } else if (skip_prefix(arg, "-count", &arg)) {
-                       if (*arg == '=')
-                               count = strtoul(arg + 1, &end, 10);
-                       else if (!*arg && !av[1])
-                               die_want_option("--stat-count");
-                       else if (!*arg) {
-                               count = strtoul(av[1], &end, 10);
-                               argcount = 2;
-                       }
+       if (!strcmp(opt->long_name, "stat")) {
+               if (value) {
+                       width = strtoul(value, &end, 10);
+                       if (*end == ',')
+                               name_width = strtoul(end+1, &end, 10);
+                       if (*end == ',')
+                               count = strtoul(end+1, &end, 10);
+                       if (*end)
+                               return error(_("invalid --stat value: %s"), value);
                }
-               break;
-       case '=':
-               width = strtoul(arg+1, &end, 10);
-               if (*end == ',')
-                       name_width = strtoul(end+1, &end, 10);
-               if (*end == ',')
-                       count = strtoul(end+1, &end, 10);
-       }
+       } else if (!strcmp(opt->long_name, "stat-width")) {
+               width = strtoul(value, &end, 10);
+               if (*end)
+                       return error(_("%s expects a numerical value"),
+                                    opt->long_name);
+       } else if (!strcmp(opt->long_name, "stat-name-width")) {
+               name_width = strtoul(value, &end, 10);
+               if (*end)
+                       return error(_("%s expects a numerical value"),
+                                    opt->long_name);
+       } else if (!strcmp(opt->long_name, "stat-graph-width")) {
+               graph_width = strtoul(value, &end, 10);
+               if (*end)
+                       return error(_("%s expects a numerical value"),
+                                    opt->long_name);
+       } else if (!strcmp(opt->long_name, "stat-count")) {
+               count = strtoul(value, &end, 10);
+               if (*end)
+                       return error(_("%s expects a numerical value"),
+                                    opt->long_name);
+       } else
+               BUG("%s should not get here", opt->long_name);
 
-       /* Important! This checks all the error cases! */
-       if (*end)
-               return 0;
        options->output_format |= DIFF_FORMAT_DIFFSTAT;
        options->stat_name_width = name_width;
        options->stat_graph_width = graph_width;
        options->stat_width = width;
        options->stat_count = count;
-       return argcount;
+       return 0;
 }
 
 static int parse_dirstat_opt(struct diff_options *options, const char *params)
@@ -4926,6 +4906,308 @@ static int parse_objfind_opt(struct diff_options *opt, const char *arg)
        return 1;
 }
 
+static int diff_opt_break_rewrites(const struct option *opt,
+                                  const char *arg, int unset)
+{
+       int *break_opt = opt->value;
+       int opt1, opt2;
+
+       BUG_ON_OPT_NEG(unset);
+       if (!arg)
+               arg = "";
+       opt1 = parse_rename_score(&arg);
+       if (*arg == 0)
+               opt2 = 0;
+       else if (*arg != '/')
+               return error(_("%s expects <n>/<m> form"), opt->long_name);
+       else {
+               arg++;
+               opt2 = parse_rename_score(&arg);
+       }
+       if (*arg != 0)
+               return error(_("%s expects <n>/<m> form"), opt->long_name);
+       *break_opt = opt1 | (opt2 << 16);
+       return 0;
+}
+
+static int diff_opt_char(const struct option *opt,
+                        const char *arg, int unset)
+{
+       char *value = opt->value;
+
+       BUG_ON_OPT_NEG(unset);
+       if (arg[1])
+               return error(_("%s expects a character, got '%s'"),
+                            opt->long_name, arg);
+       *value = arg[0];
+       return 0;
+}
+
+static int diff_opt_compact_summary(const struct option *opt,
+                                   const char *arg, int unset)
+{
+       struct diff_options *options = opt->value;
+
+       BUG_ON_OPT_ARG(arg);
+       if (unset) {
+               options->flags.stat_with_summary = 0;
+       } else {
+               options->flags.stat_with_summary = 1;
+               options->output_format |= DIFF_FORMAT_DIFFSTAT;
+       }
+       return 0;
+}
+
+static int diff_opt_dirstat(const struct option *opt,
+                           const char *arg, int unset)
+{
+       struct diff_options *options = opt->value;
+
+       BUG_ON_OPT_NEG(unset);
+       if (!strcmp(opt->long_name, "cumulative")) {
+               if (arg)
+                       BUG("how come --cumulative take a value?");
+               arg = "cumulative";
+       } else if (!strcmp(opt->long_name, "dirstat-by-file"))
+               parse_dirstat_opt(options, "files");
+       parse_dirstat_opt(options, arg ? arg : "");
+       return 0;
+}
+
+static int diff_opt_find_copies(const struct option *opt,
+                               const char *arg, int unset)
+{
+       struct diff_options *options = opt->value;
+
+       BUG_ON_OPT_NEG(unset);
+       if (!arg)
+               arg = "";
+       options->rename_score = parse_rename_score(&arg);
+       if (*arg != 0)
+               return error(_("invalid argument to %s"), opt->long_name);
+
+       if (options->detect_rename == DIFF_DETECT_COPY)
+               options->flags.find_copies_harder = 1;
+       else
+               options->detect_rename = DIFF_DETECT_COPY;
+
+       return 0;
+}
+
+static int diff_opt_find_renames(const struct option *opt,
+                                const char *arg, int unset)
+{
+       struct diff_options *options = opt->value;
+
+       BUG_ON_OPT_NEG(unset);
+       if (!arg)
+               arg = "";
+       options->rename_score = parse_rename_score(&arg);
+       if (*arg != 0)
+               return error(_("invalid argument to %s"), opt->long_name);
+
+       options->detect_rename = DIFF_DETECT_RENAME;
+       return 0;
+}
+
+static enum parse_opt_result diff_opt_output(struct parse_opt_ctx_t *ctx,
+                                            const struct option *opt,
+                                            const char *arg, int unset)
+{
+       struct diff_options *options = opt->value;
+       char *path;
+
+       BUG_ON_OPT_NEG(unset);
+       path = prefix_filename(ctx->prefix, arg);
+       options->file = xfopen(path, "w");
+       options->close_file = 1;
+       if (options->use_color != GIT_COLOR_ALWAYS)
+               options->use_color = GIT_COLOR_NEVER;
+       free(path);
+       return 0;
+}
+
+static int diff_opt_relative(const struct option *opt,
+                            const char *arg, int unset)
+{
+       struct diff_options *options = opt->value;
+
+       BUG_ON_OPT_NEG(unset);
+       options->flags.relative_name = 1;
+       if (arg)
+               options->prefix = arg;
+       return 0;
+}
+
+static int diff_opt_unified(const struct option *opt,
+                           const char *arg, int unset)
+{
+       struct diff_options *options = opt->value;
+       char *s;
+
+       BUG_ON_OPT_NEG(unset);
+
+       options->context = strtol(arg, &s, 10);
+       if (*s)
+               return error(_("%s expects a numerical value"), "--unified");
+       enable_patch_output(&options->output_format);
+
+       return 0;
+}
+
+static void prep_parse_options(struct diff_options *options)
+{
+       struct option parseopts[] = {
+               OPT_GROUP(N_("Diff output format options")),
+               OPT_BITOP('p', "patch", &options->output_format,
+                         N_("generate patch"),
+                         DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT),
+               OPT_BIT_F('s', "no-patch", &options->output_format,
+                         N_("suppress diff output"),
+                         DIFF_FORMAT_NO_OUTPUT, PARSE_OPT_NONEG),
+               OPT_BITOP('u', NULL, &options->output_format,
+                         N_("generate patch"),
+                         DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT),
+               OPT_CALLBACK_F('U', "unified", options, N_("<n>"),
+                              N_("generate diffs with <n> lines context"),
+                              PARSE_OPT_NONEG, diff_opt_unified),
+               OPT_BOOL('W', "function-context", &options->flags.funccontext,
+                        N_("generate diffs with <n> lines context")),
+               OPT_BIT_F(0, "raw", &options->output_format,
+                         N_("generate the diff in raw format"),
+                         DIFF_FORMAT_RAW, PARSE_OPT_NONEG),
+               OPT_BITOP(0, "patch-with-raw", &options->output_format,
+                         N_("synonym for '-p --raw'"),
+                         DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW,
+                         DIFF_FORMAT_NO_OUTPUT),
+               OPT_BITOP(0, "patch-with-stat", &options->output_format,
+                         N_("synonym for '-p --stat'"),
+                         DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT,
+                         DIFF_FORMAT_NO_OUTPUT),
+               OPT_BIT_F(0, "numstat", &options->output_format,
+                         N_("machine friendly --stat"),
+                         DIFF_FORMAT_NUMSTAT, PARSE_OPT_NONEG),
+               OPT_BIT_F(0, "shortstat", &options->output_format,
+                         N_("output only the last line of --stat"),
+                         DIFF_FORMAT_SHORTSTAT, PARSE_OPT_NONEG),
+               OPT_CALLBACK_F('X', "dirstat", options, N_("<param1,param2>..."),
+                              N_("output the distribution of relative amount of changes for each sub-directory"),
+                              PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
+                              diff_opt_dirstat),
+               OPT_CALLBACK_F(0, "cumulative", options, NULL,
+                              N_("synonym for --dirstat=cumulative"),
+                              PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+                              diff_opt_dirstat),
+               OPT_CALLBACK_F(0, "dirstat-by-file", options, N_("<param1,param2>..."),
+                              N_("synonym for --dirstat=files,param1,param2..."),
+                              PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
+                              diff_opt_dirstat),
+               OPT_BIT_F(0, "check", &options->output_format,
+                         N_("warn if changes introduce conflict markers or whitespace errors"),
+                         DIFF_FORMAT_CHECKDIFF, PARSE_OPT_NONEG),
+               OPT_BIT_F(0, "summary", &options->output_format,
+                         N_("condensed summary such as creations, renames and mode changes"),
+                         DIFF_FORMAT_SUMMARY, PARSE_OPT_NONEG),
+               OPT_BIT_F(0, "name-only", &options->output_format,
+                         N_("show only names of changed files"),
+                         DIFF_FORMAT_NAME, PARSE_OPT_NONEG),
+               OPT_BIT_F(0, "name-status", &options->output_format,
+                         N_("show only names and status of changed files"),
+                         DIFF_FORMAT_NAME_STATUS, PARSE_OPT_NONEG),
+               OPT_CALLBACK_F(0, "stat", options, N_("<width>[,<name-width>[,<count>]]"),
+                              N_("generate diffstat"),
+                              PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_stat),
+               OPT_CALLBACK_F(0, "stat-width", options, N_("<width>"),
+                              N_("generate diffstat with a given width"),
+                              PARSE_OPT_NONEG, diff_opt_stat),
+               OPT_CALLBACK_F(0, "stat-name-width", options, N_("<width>"),
+                              N_("generate diffstat with a given name width"),
+                              PARSE_OPT_NONEG, diff_opt_stat),
+               OPT_CALLBACK_F(0, "stat-graph-width", options, N_("<width>"),
+                              N_("generate diffstat with a given graph width"),
+                              PARSE_OPT_NONEG, diff_opt_stat),
+               OPT_CALLBACK_F(0, "stat-count", options, N_("<count>"),
+                              N_("generate diffstat with limited lines"),
+                              PARSE_OPT_NONEG, diff_opt_stat),
+               OPT_CALLBACK_F(0, "compact-summary", options, NULL,
+                              N_("generate compact summary in diffstat"),
+                              PARSE_OPT_NOARG, diff_opt_compact_summary),
+               OPT_CALLBACK_F(0, "output-indicator-new",
+                              &options->output_indicators[OUTPUT_INDICATOR_NEW],
+                              N_("<char>"),
+                              N_("specify the character to indicate a new line instead of '+'"),
+                              PARSE_OPT_NONEG, diff_opt_char),
+               OPT_CALLBACK_F(0, "output-indicator-old",
+                              &options->output_indicators[OUTPUT_INDICATOR_OLD],
+                              N_("<char>"),
+                              N_("specify the character to indicate an old line instead of '-'"),
+                              PARSE_OPT_NONEG, diff_opt_char),
+               OPT_CALLBACK_F(0, "output-indicator-context",
+                              &options->output_indicators[OUTPUT_INDICATOR_CONTEXT],
+                              N_("<char>"),
+                              N_("specify the character to indicate a context instead of ' '"),
+                              PARSE_OPT_NONEG, diff_opt_char),
+
+               OPT_GROUP(N_("Diff rename options")),
+               OPT_CALLBACK_F('B', "break-rewrites", &options->break_opt, N_("<n>[/<m>]"),
+                              N_("break complete rewrite changes into pairs of delete and create"),
+                              PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
+                              diff_opt_break_rewrites),
+               OPT_CALLBACK_F('M', "find-renames", options, N_("<n>"),
+                              N_("detect renames"),
+                              PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
+                              diff_opt_find_renames),
+               OPT_SET_INT_F('D', "irreversible-delete", &options->irreversible_delete,
+                             N_("omit the preimage for deletes"),
+                             1, PARSE_OPT_NONEG),
+               OPT_CALLBACK_F('C', "find-copies", options, N_("<n>"),
+                              N_("detect copies"),
+                              PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
+                              diff_opt_find_copies),
+               OPT_BOOL(0, "find-copies-harder", &options->flags.find_copies_harder,
+                        N_("use unmodified files as source to find copies")),
+               OPT_SET_INT_F(0, "no-renames", &options->detect_rename,
+                             N_("disable rename detection"),
+                             0, PARSE_OPT_NONEG),
+               OPT_BOOL(0, "rename-empty", &options->flags.rename_empty,
+                        N_("use empty blobs as rename source")),
+
+               OPT_GROUP(N_("Diff algorithm options")),
+               OPT_BIT(0, "minimal", &options->xdl_opts,
+                       N_("produce the smallest possible diff"),
+                       XDF_NEED_MINIMAL),
+               OPT_BIT_F('w', "ignore-all-space", &options->xdl_opts,
+                         N_("ignore whitespace when comparing lines"),
+                         XDF_IGNORE_WHITESPACE, PARSE_OPT_NONEG),
+               OPT_BIT_F('b', "ignore-space-change", &options->xdl_opts,
+                         N_("ignore changes in amount of whitespace"),
+                         XDF_IGNORE_WHITESPACE_CHANGE, PARSE_OPT_NONEG),
+               OPT_BIT_F(0, "ignore-space-at-eol", &options->xdl_opts,
+                         N_("ignore changes in whitespace at EOL"),
+                         XDF_IGNORE_WHITESPACE_AT_EOL, PARSE_OPT_NONEG),
+               OPT_BIT_F(0, "ignore-cr-at-eol", &options->xdl_opts,
+                         N_("ignore carrier-return at the end of line"),
+                         XDF_IGNORE_CR_AT_EOL, PARSE_OPT_NONEG),
+               OPT_BIT_F(0, "ignore-blank-lines", &options->xdl_opts,
+                         N_("ignore changes whose lines are all blank"),
+                         XDF_IGNORE_BLANK_LINES, PARSE_OPT_NONEG),
+
+               OPT_GROUP(N_("Diff other options")),
+               OPT_CALLBACK_F(0, "relative", options, N_("<prefix>"),
+                              N_("when run from subdir, exclude changes outside and show relative paths"),
+                              PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
+                              diff_opt_relative),
+               { OPTION_CALLBACK, 0, "output", options, N_("<file>"),
+                 N_("Output to a specific file"),
+                 PARSE_OPT_NONEG, NULL, 0, diff_opt_output },
+
+               OPT_END()
+       };
+
+       ALLOC_ARRAY(options->parseopts, ARRAY_SIZE(parseopts));
+       memcpy(options->parseopts, parseopts, sizeof(parseopts));
+}
+
 int diff_opt_parse(struct diff_options *options,
                   const char **av, int ac, const char *prefix)
 {
@@ -4936,107 +5218,18 @@ int diff_opt_parse(struct diff_options *options,
        if (!prefix)
                prefix = "";
 
-       /* Output format options */
-       if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch")
-           || opt_arg(arg, 'U', "unified", &options->context))
-               enable_patch_output(&options->output_format);
-       else if (!strcmp(arg, "--raw"))
-               options->output_format |= DIFF_FORMAT_RAW;
-       else if (!strcmp(arg, "--patch-with-raw")) {
-               enable_patch_output(&options->output_format);
-               options->output_format |= DIFF_FORMAT_RAW;
-       } else if (!strcmp(arg, "--numstat"))
-               options->output_format |= DIFF_FORMAT_NUMSTAT;
-       else if (!strcmp(arg, "--shortstat"))
-               options->output_format |= DIFF_FORMAT_SHORTSTAT;
-       else if (skip_prefix(arg, "-X", &arg) ||
-                skip_to_optional_arg(arg, "--dirstat", &arg))
-               return parse_dirstat_opt(options, arg);
-       else if (!strcmp(arg, "--cumulative"))
-               return parse_dirstat_opt(options, "cumulative");
-       else if (skip_to_optional_arg(arg, "--dirstat-by-file", &arg)) {
-               parse_dirstat_opt(options, "files");
-               return parse_dirstat_opt(options, arg);
-       }
-       else if (!strcmp(arg, "--check"))
-               options->output_format |= DIFF_FORMAT_CHECKDIFF;
-       else if (!strcmp(arg, "--summary"))
-               options->output_format |= DIFF_FORMAT_SUMMARY;
-       else if (!strcmp(arg, "--patch-with-stat")) {
-               enable_patch_output(&options->output_format);
-               options->output_format |= DIFF_FORMAT_DIFFSTAT;
-       } else if (!strcmp(arg, "--name-only"))
-               options->output_format |= DIFF_FORMAT_NAME;
-       else if (!strcmp(arg, "--name-status"))
-               options->output_format |= DIFF_FORMAT_NAME_STATUS;
-       else if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch"))
-               options->output_format |= DIFF_FORMAT_NO_OUTPUT;
-       else if (starts_with(arg, "--stat"))
-               /* --stat, --stat-width, --stat-name-width, or --stat-count */
-               return stat_opt(options, av);
-       else if (!strcmp(arg, "--compact-summary")) {
-                options->flags.stat_with_summary = 1;
-                options->output_format |= DIFF_FORMAT_DIFFSTAT;
-       } else if (!strcmp(arg, "--no-compact-summary"))
-                options->flags.stat_with_summary = 0;
-       else if (skip_prefix(arg, "--output-indicator-new=", &arg))
-               options->output_indicators[OUTPUT_INDICATOR_NEW] = arg[0];
-       else if (skip_prefix(arg, "--output-indicator-old=", &arg))
-               options->output_indicators[OUTPUT_INDICATOR_OLD] = arg[0];
-       else if (skip_prefix(arg, "--output-indicator-context=", &arg))
-               options->output_indicators[OUTPUT_INDICATOR_CONTEXT] = arg[0];
-
-       /* renames options */
-       else if (starts_with(arg, "-B") ||
-                skip_to_optional_arg(arg, "--break-rewrites", NULL)) {
-               if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
-                       return error("invalid argument to -B: %s", arg+2);
-       }
-       else if (starts_with(arg, "-M") ||
-                skip_to_optional_arg(arg, "--find-renames", NULL)) {
-               if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
-                       return error("invalid argument to -M: %s", arg+2);
-               options->detect_rename = DIFF_DETECT_RENAME;
-       }
-       else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) {
-               options->irreversible_delete = 1;
-       }
-       else if (starts_with(arg, "-C") ||
-                skip_to_optional_arg(arg, "--find-copies", NULL)) {
-               if (options->detect_rename == DIFF_DETECT_COPY)
-                       options->flags.find_copies_harder = 1;
-               if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
-                       return error("invalid argument to -C: %s", arg+2);
-               options->detect_rename = DIFF_DETECT_COPY;
-       }
-       else if (!strcmp(arg, "--no-renames"))
-               options->detect_rename = 0;
-       else if (!strcmp(arg, "--rename-empty"))
-               options->flags.rename_empty = 1;
-       else if (!strcmp(arg, "--no-rename-empty"))
-               options->flags.rename_empty = 0;
-       else if (skip_to_optional_arg_default(arg, "--relative", &arg, NULL)) {
-               options->flags.relative_name = 1;
-               if (arg)
-                       options->prefix = arg;
-       }
+       ac = parse_options(ac, av, prefix, options->parseopts, NULL,
+                          PARSE_OPT_KEEP_DASHDASH |
+                          PARSE_OPT_KEEP_UNKNOWN |
+                          PARSE_OPT_NO_INTERNAL_HELP |
+                          PARSE_OPT_ONE_SHOT |
+                          PARSE_OPT_STOP_AT_NON_OPTION);
+
+       if (ac)
+               return ac;
 
        /* xdiff options */
-       else if (!strcmp(arg, "--minimal"))
-               DIFF_XDL_SET(options, NEED_MINIMAL);
-       else if (!strcmp(arg, "--no-minimal"))
-               DIFF_XDL_CLR(options, NEED_MINIMAL);
-       else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
-               DIFF_XDL_SET(options, IGNORE_WHITESPACE);
-       else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
-               DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
-       else if (!strcmp(arg, "--ignore-space-at-eol"))
-               DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
-       else if (!strcmp(arg, "--ignore-cr-at-eol"))
-               DIFF_XDL_SET(options, IGNORE_CR_AT_EOL);
-       else if (!strcmp(arg, "--ignore-blank-lines"))
-               DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
-       else if (!strcmp(arg, "--indent-heuristic"))
+       if (!strcmp(arg, "--indent-heuristic"))
                DIFF_XDL_SET(options, INDENT_HEURISTIC);
        else if (!strcmp(arg, "--no-indent-heuristic"))
                DIFF_XDL_CLR(options, INDENT_HEURISTIC);
@@ -5081,8 +5274,6 @@ int diff_opt_parse(struct diff_options *options,
                options->flags.text = 1;
        else if (!strcmp(arg, "-R"))
                options->flags.reverse_diff = 1;
-       else if (!strcmp(arg, "--find-copies-harder"))
-               options->flags.find_copies_harder = 1;
        else if (!strcmp(arg, "--follow"))
                options->flags.follow_renames = 1;
        else if (!strcmp(arg, "--no-follow")) {
@@ -5230,21 +5421,7 @@ int diff_opt_parse(struct diff_options *options,
        else if (opt_arg(arg, '\0', "inter-hunk-context",
                         &options->interhunkcontext))
                ;
-       else if (!strcmp(arg, "-W"))
-               options->flags.funccontext = 1;
-       else if (!strcmp(arg, "--function-context"))
-               options->flags.funccontext = 1;
-       else if (!strcmp(arg, "--no-function-context"))
-               options->flags.funccontext = 0;
-       else if ((argcount = parse_long_opt("output", av, &optarg))) {
-               char *path = prefix_filename(prefix, optarg);
-               options->file = xfopen(path, "w");
-               options->close_file = 1;
-               if (options->use_color != GIT_COLOR_ALWAYS)
-                       options->use_color = GIT_COLOR_NEVER;
-               free(path);
-               return argcount;
-       } else
+       else
                return 0;
        return 1;
 }
@@ -5285,47 +5462,6 @@ int parse_rename_score(const char **cp_p)
        return (int)((num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale));
 }
 
-static int diff_scoreopt_parse(const char *opt)
-{
-       int opt1, opt2, cmd;
-
-       if (*opt++ != '-')
-               return -1;
-       cmd = *opt++;
-       if (cmd == '-') {
-               /* convert the long-form arguments into short-form versions */
-               if (skip_prefix(opt, "break-rewrites", &opt)) {
-                       if (*opt == 0 || *opt++ == '=')
-                               cmd = 'B';
-               } else if (skip_prefix(opt, "find-copies", &opt)) {
-                       if (*opt == 0 || *opt++ == '=')
-                               cmd = 'C';
-               } else if (skip_prefix(opt, "find-renames", &opt)) {
-                       if (*opt == 0 || *opt++ == '=')
-                               cmd = 'M';
-               }
-       }
-       if (cmd != 'M' && cmd != 'C' && cmd != 'B')
-               return -1; /* that is not a -M, -C, or -B option */
-
-       opt1 = parse_rename_score(&opt);
-       if (cmd != 'B')
-               opt2 = 0;
-       else {
-               if (*opt == 0)
-                       opt2 = 0;
-               else if (*opt != '/')
-                       return -1; /* we expect -B80/99 or -B80 */
-               else {
-                       opt++;
-                       opt2 = parse_rename_score(&opt);
-               }
-       }
-       if (*opt != 0)
-               return -1;
-       return opt1 | (opt2 << 16);
-}
-
 struct diff_queue_struct diff_queued_diff;
 
 void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
@@ -6224,7 +6360,7 @@ static int diffnamecmp(const void *a_, const void *b_)
        return strcmp(name_a, name_b);
 }
 
-void diffcore_fix_diff_index(struct diff_options *options)
+void diffcore_fix_diff_index(void)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
        QSORT(q->queue, q->nr, diffnamecmp);
diff --git a/diff.h b/diff.h
index b512d0477ac3a4a0338094a4d3b21770ecb57dd8..c9db9825bb782fda45f463c3f51600fb3756b7c1 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -9,16 +9,17 @@
 #include "object.h"
 #include "oidset.h"
 
-struct rev_info;
+struct combine_diff_path;
+struct commit;
+struct diff_filespec;
 struct diff_options;
 struct diff_queue_struct;
-struct strbuf;
-struct diff_filespec;
-struct userdiff_driver;
 struct oid_array;
-struct commit;
-struct combine_diff_path;
+struct option;
 struct repository;
+struct rev_info;
+struct strbuf;
+struct userdiff_driver;
 
 typedef int (*pathchange_fn_t)(struct diff_options *options,
                 struct combine_diff_path *path);
@@ -64,39 +65,39 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 
 #define DIFF_FLAGS_INIT { 0 }
 struct diff_flags {
-       unsigned recursive:1;
-       unsigned tree_in_recursive:1;
-       unsigned binary:1;
-       unsigned text:1;
-       unsigned full_index:1;
-       unsigned silent_on_remove:1;
-       unsigned find_copies_harder:1;
-       unsigned follow_renames:1;
-       unsigned rename_empty:1;
-       unsigned has_changes:1;
-       unsigned quick:1;
-       unsigned no_index:1;
-       unsigned allow_external:1;
-       unsigned exit_with_status:1;
-       unsigned reverse_diff:1;
-       unsigned check_failed:1;
-       unsigned relative_name:1;
-       unsigned ignore_submodules:1;
-       unsigned dirstat_cumulative:1;
-       unsigned dirstat_by_file:1;
-       unsigned allow_textconv:1;
-       unsigned textconv_set_via_cmdline:1;
-       unsigned diff_from_contents:1;
-       unsigned dirty_submodules:1;
-       unsigned ignore_untracked_in_submodules:1;
-       unsigned ignore_dirty_submodules:1;
-       unsigned override_submodule_config:1;
-       unsigned dirstat_by_line:1;
-       unsigned funccontext:1;
-       unsigned default_follow_renames:1;
-       unsigned stat_with_summary:1;
-       unsigned suppress_diff_headers:1;
-       unsigned dual_color_diffed_diffs:1;
+       unsigned recursive;
+       unsigned tree_in_recursive;
+       unsigned binary;
+       unsigned text;
+       unsigned full_index;
+       unsigned silent_on_remove;
+       unsigned find_copies_harder;
+       unsigned follow_renames;
+       unsigned rename_empty;
+       unsigned has_changes;
+       unsigned quick;
+       unsigned no_index;
+       unsigned allow_external;
+       unsigned exit_with_status;
+       unsigned reverse_diff;
+       unsigned check_failed;
+       unsigned relative_name;
+       unsigned ignore_submodules;
+       unsigned dirstat_cumulative;
+       unsigned dirstat_by_file;
+       unsigned allow_textconv;
+       unsigned textconv_set_via_cmdline;
+       unsigned diff_from_contents;
+       unsigned dirty_submodules;
+       unsigned ignore_untracked_in_submodules;
+       unsigned ignore_dirty_submodules;
+       unsigned override_submodule_config;
+       unsigned dirstat_by_line;
+       unsigned funccontext;
+       unsigned default_follow_renames;
+       unsigned stat_with_summary;
+       unsigned suppress_diff_headers;
+       unsigned dual_color_diffed_diffs;
 };
 
 static inline void diff_flags_or(struct diff_flags *a,
@@ -229,6 +230,7 @@ struct diff_options {
        unsigned color_moved_ws_handling;
 
        struct repository *repo;
+       struct option *parseopts;
 };
 
 void diff_emit_submodule_del(struct diff_options *o, const char *line);
@@ -294,6 +296,7 @@ struct combine_diff_path {
                char status;
                unsigned int mode;
                struct object_id oid;
+               struct strbuf path;
        } parent[FLEX_ARRAY];
 };
 #define combine_diff_path_size(n, l) \
@@ -367,7 +370,7 @@ int git_config_rename(const char *var, const char *value);
 #define DIFF_PICKAXE_IGNORE_CASE       32
 
 void diffcore_std(struct diff_options *);
-void diffcore_fix_diff_index(struct diff_options *);
+void diffcore_fix_diff_index(void);
 
 #define COMMON_DIFF_OPTIONS_HELP \
 "\ncommon diff options:\n" \
@@ -435,7 +438,7 @@ int diff_flush_patch_id(struct diff_options *, struct object_id *, int);
 
 int diff_result_code(struct diff_options *, int);
 
-void diff_no_index(struct repository *, struct rev_info *, int, const char **);
+void diff_no_index(struct rev_info *, int, const char **);
 
 int index_differs_from(struct repository *r, const char *def,
                       const struct diff_flags *flags,
index c985eee1f9d1c7307b7f533cb2d38de5a42ccb23..71547674ab4e885bd06560a547e78fddb0849158 100644 (file)
--- a/editor.c
+++ b/editor.c
@@ -78,6 +78,7 @@ static int launch_specified_editor(const char *editor, const char *path,
                p.argv = args;
                p.env = env;
                p.use_shell = 1;
+               p.trace2_child_class = "editor";
                if (start_command(&p) < 0)
                        return error("unable to start editor '%s'", editor);
 
diff --git a/entry.c b/entry.c
index 6fd72b30c8768677f044923ef5e715949d32fdae..0e4f2f29101f913d0db2c401851f279eac496f02 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -441,6 +441,17 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,
        static struct strbuf path = STRBUF_INIT;
        struct stat st;
 
+       if (ce->ce_flags & CE_WT_REMOVE) {
+               if (topath)
+                       /*
+                        * No content and thus no path to create, so we have
+                        * no pathname to return.
+                        */
+                       BUG("Can't remove entry to a path");
+               unlink_entry(ce);
+               return 0;
+       }
+
        if (topath)
                return write_entry(ce, topath, state, 1);
 
@@ -510,3 +521,18 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,
                (*nr_checkouts)++;
        return write_entry(ce, path.buf, state, 0);
 }
+
+void unlink_entry(const struct cache_entry *ce)
+{
+       const struct submodule *sub = submodule_from_ce(ce);
+       if (sub) {
+               /* state.force is set at the caller. */
+               submodule_move_head(ce->name, "HEAD", NULL,
+                                   SUBMODULE_MOVE_HEAD_FORCE);
+       }
+       if (!check_leading_path(ce->name, ce_namelen(ce)))
+               return;
+       if (remove_or_warn(ce->ce_mode, ce->name))
+               return;
+       schedule_dir_for_removal(ce->name, ce_namelen(ce));
+}
index 4f81f443105f071e488a0436b7fe0579e22bc816..7deeab30397cf2edbee622717acd94de87256c8e 100644 (file)
@@ -209,6 +209,8 @@ static int git_get_exec_path(struct strbuf *buf, const char *argv0)
                return -1;
        }
 
+       trace2_cmd_path(buf->buf);
+
        return 0;
 }
 
index 812be15d7e568522973227bb939343d94c008ceb..e69993b2eb56885253af21b6de33fd487725e313 100644 (file)
@@ -191,8 +191,10 @@ static void send_request(struct fetch_pack_args *args,
        if (args->stateless_rpc) {
                send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX);
                packet_flush(fd);
-       } else
-               write_or_die(fd, buf->buf, buf->len);
+       } else {
+               if (write_in_full(fd, buf->buf, buf->len) < 0)
+                       die_errno(_("unable to write to remote"));
+       }
 }
 
 static void insert_one_alternate_object(struct fetch_negotiator *negotiator,
@@ -1163,7 +1165,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
 
        /* Send request */
        packet_buf_flush(&req_buf);
-       write_or_die(fd_out, req_buf.buf, req_buf.len);
+       if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)
+               die_errno(_("unable to write request to remote"));
 
        strbuf_release(&req_buf);
        return ret;
index 6573808ebd991b52eca7cc644af4c9611b9c3747..31b47932bd5144f306a9aa1b122fdf3cb0886284 100644 (file)
@@ -1259,6 +1259,13 @@ static inline int is_missing_file_error(int errno_)
 
 extern int cmd_main(int, const char **);
 
+/*
+ * Intercept all calls to exit() and route them to trace2 to
+ * optionally emit a message before calling the real exit().
+ */
+int trace2_cmd_exit_fl(const char *file, int line, int code);
+#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))
+
 /*
  * You can mark a stack variable with UNLEAK(var) to avoid it being
  * reported as a leak by tools like LSAN or valgrind. The argument
index 8eb63b5a2f8d07038f6c8ef645a483cd77ffd99f..8200d58cdc62b628f77923305ecb79ff260155ea 100755 (executable)
@@ -465,6 +465,8 @@ $smtp_encryption = '' unless (defined $smtp_encryption);
 my(%suppress_cc);
 if (@suppress_cc) {
        foreach my $entry (@suppress_cc) {
+               # Please update $__git_send_email_suppresscc_options
+               # in git-completion.bash when you add new options.
                die sprintf(__("Unknown --suppress-cc field: '%s'\n"), $entry)
                        unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc|misc-by)$/;
                $suppress_cc{$entry} = 1;
@@ -494,6 +496,8 @@ my $confirm_unconfigured = !defined $confirm;
 if ($confirm_unconfigured) {
        $confirm = scalar %suppress_cc ? 'compose' : 'auto';
 };
+# Please update $__git_send_email_confirm_options in
+# git-completion.bash when you add new options.
 die sprintf(__("Unknown --confirm setting: '%s'\n"), $confirm)
        unless $confirm =~ /^(?:auto|cc|compose|always|never)/;
 
@@ -587,6 +591,8 @@ my %parse_alias = (
                if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {
                        $aliases{$1} = [ $2 ];
                }}}
+       # Please update _git_config() in git-completion.bash when you
+       # add new MUAs.
 );
 
 if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
index b5f2beee60ab59a04b5b236da8df39403009db1b..514ede259660d387fd392b950facc706f7c4d5e7 100755 (executable)
@@ -5,7 +5,8 @@
 # Copyright (c) 2007 Lars Hjemli
 
 dashless=$(basename "$0" | sed -e 's/-/ /')
-USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]
+USAGE="[--quiet] [--cached]
+   or: $dashless [--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]
    or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
    or: $dashless [--quiet] init [--] [<path>...]
    or: $dashless [--quiet] deinit [-f|--force] (--all| [--] <path>...)
diff --git a/git.c b/git.c
index 2dd588674f621e2df2af1a3833c2cb3fa8417de4..2014aab6b83c61695d50ac39a18864a8d77858e0 100644 (file)
--- a/git.c
+++ b/git.c
@@ -147,16 +147,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
                                git_set_exec_path(cmd + 1);
                        else {
                                puts(git_exec_path());
+                               trace2_cmd_name("_query_");
                                exit(0);
                        }
                } else if (!strcmp(cmd, "--html-path")) {
                        puts(system_path(GIT_HTML_PATH));
+                       trace2_cmd_name("_query_");
                        exit(0);
                } else if (!strcmp(cmd, "--man-path")) {
                        puts(system_path(GIT_MAN_PATH));
+                       trace2_cmd_name("_query_");
                        exit(0);
                } else if (!strcmp(cmd, "--info-path")) {
                        puts(system_path(GIT_INFO_PATH));
+                       trace2_cmd_name("_query_");
                        exit(0);
                } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
                        use_pager = 1;
@@ -285,6 +289,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
                        (*argv)++;
                        (*argc)--;
                } else if (skip_prefix(cmd, "--list-cmds=", &cmd)) {
+                       trace2_cmd_name("_query_");
                        if (!strcmp(cmd, "parseopt")) {
                                struct string_list list = STRING_LIST_INIT_DUP;
                                int i;
@@ -332,9 +337,14 @@ static int handle_alias(int *argcp, const char ***argv)
                        commit_pager_choice();
 
                        child.use_shell = 1;
+                       child.trace2_child_class = "shell_alias";
                        argv_array_push(&child.args, alias_string + 1);
                        argv_array_pushv(&child.args, (*argv) + 1);
 
+                       trace2_cmd_alias(alias_command, child.args.argv);
+                       trace2_cmd_list_config();
+                       trace2_cmd_name("_run_shell_alias_");
+
                        ret = run_command(&child);
                        if (ret >= 0)   /* normal exit */
                                exit(ret);
@@ -369,6 +379,9 @@ static int handle_alias(int *argcp, const char ***argv)
                /* insert after command name */
                memcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);
 
+               trace2_cmd_alias(alias_command, new_argv);
+               trace2_cmd_list_config();
+
                *argv = new_argv;
                *argcp += count - 1;
 
@@ -417,6 +430,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
                setup_work_tree();
 
        trace_argv_printf(argv, "trace: built-in: git");
+       trace2_cmd_name(p->cmd);
+       trace2_cmd_list_config();
 
        validate_cache_entries(the_repository->index);
        status = p->fn(argc, argv, prefix);
@@ -666,7 +681,14 @@ static void execv_dashed_external(const char **argv)
        cmd.clean_on_exit = 1;
        cmd.wait_after_clean = 1;
        cmd.silent_exec_failure = 1;
+       cmd.trace2_child_class = "dashed";
 
+       trace2_cmd_name("_run_dashed_");
+
+       /*
+        * The code in run_command() logs trace2 child_start/child_exit
+        * events, so we do not need to report exec/exec_result events here.
+        */
        trace_argv_printf(cmd.args.argv, "trace: exec:");
 
        /*
@@ -676,6 +698,12 @@ static void execv_dashed_external(const char **argv)
         * the program.
         */
        status = run_command(&cmd);
+
+       /*
+        * If the child process ran and we are now going to exit, emit a
+        * generic string as our trace2 command verb to indicate that we
+        * launched a dashed command.
+        */
        if (status >= 0)
                exit(status);
        else if (errno != ENOENT)
@@ -701,6 +729,43 @@ static int run_argv(int *argcp, const char ***argv)
                if (!done_alias)
                        handle_builtin(*argcp, *argv);
 
+#if 0 // TODO In GFW, need to amend a7924b655e940b06cb547c235d6bed9767929673 to include trace2_ and _tr2 lines.
+               else if (get_builtin(**argv)) {
+                       struct argv_array args = ARGV_ARRAY_INIT;
+                       int i;
+
+                       /*
+                        * The current process is committed to launching a
+                        * child process to run the command named in (**argv)
+                        * and exiting.  Log a generic string as the trace2
+                        * command verb to indicate this.  Note that the child
+                        * process will log the actual verb when it runs.
+                        */
+                       trace2_cmd_name("_run_git_alias_");
+
+                       if (get_super_prefix())
+                               die("%s doesn't support --super-prefix", **argv);
+
+                       commit_pager_choice();
+
+                       argv_array_push(&args, "git");
+                       for (i = 0; i < *argcp; i++)
+                               argv_array_push(&args, (*argv)[i]);
+
+                       trace_argv_printf(args.argv, "trace: exec:");
+
+                       /*
+                        * if we fail because the command is not found, it is
+                        * OK to return. Otherwise, we just pass along the status code.
+                        */
+                       i = run_command_v_opt_tr2(args.argv, RUN_SILENT_EXEC_FAILURE |
+                                                 RUN_CLEAN_ON_EXIT, "git_alias");
+                       if (i >= 0 || errno != ENOENT)
+                               exit(i);
+                       die("could not execute builtin %s", **argv);
+               }
+#endif // a7924b655e940b06cb547c235d6bed9767929673
+
                /* .. then try the external ones */
                execv_dashed_external(*argv);
 
index 407d5550b1aba947549ad509a641134c34f28d46..87ab1fac24d6aa7a35f3bec0ac909366b43e46d9 100644 (file)
@@ -1,15 +1,15 @@
 # Bulgarian translation of gitk po-file.
-# Copyright (C) 2014, 2015 Alexander Shopov <ash@kambanaria.org>.
+# Copyright (C) 2014, 2015, 2019 Alexander Shopov <ash@kambanaria.org>.
 # This file is distributed under the same license as the git package.
-# Alexander Shopov <ash@kambanaria.org>, 2014, 2015.
+# Alexander Shopov <ash@kambanaria.org>, 2014, 2015, 2019.
 #
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: gitk master\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-12-19 11:48+0200\n"
-"PO-Revision-Date: 2015-12-19 11:49+0200\n"
+"POT-Creation-Date: 2019-03-04 11:27+0100\n"
+"PO-Revision-Date: 2019-03-04 11:39+0100\n"
 "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n"
 "Language-Team: Bulgarian <dict@fsa-bg.org>\n"
 "Language: bg\n"
@@ -22,17 +22,17 @@ msgstr ""
 msgid "Couldn't get list of unmerged files:"
 msgstr "Списъкът с неслети файлове не може да бъде получен:"
 
-#: gitk:212 gitk:2399
+#: gitk:212 gitk:2403
 msgid "Color words"
 msgstr "Оцветяване на думите"
 
-#: gitk:217 gitk:2399 gitk:8239 gitk:8272
+#: gitk:217 gitk:2403 gitk:8249 gitk:8282
 msgid "Markup words"
 msgstr "Отбелязване на думите"
 
 #: gitk:324
 msgid "Error parsing revisions:"
-msgstr "Грешка при разбор на версиите:"
+msgstr "Грешка при анализ на версиите:"
 
 #: gitk:380
 msgid "Error executing --argscmd command:"
@@ -59,314 +59,318 @@ msgstr "Грешка при изпълнение на „git log“:"
 msgid "Reading"
 msgstr "Прочитане"
 
-#: gitk:496 gitk:4544
+#: gitk:496 gitk:4549
 msgid "Reading commits..."
 msgstr "Прочитане на подаванията…"
 
-#: gitk:499 gitk:1637 gitk:4547
+#: gitk:499 gitk:1641 gitk:4552
 msgid "No commits selected"
 msgstr "Не са избрани подавания"
 
-#: gitk:1445 gitk:4064 gitk:12469
+#: gitk:1449 gitk:4069 gitk:12583
 msgid "Command line"
 msgstr "Команден ред"
 
-#: gitk:1511
+#: gitk:1515
 msgid "Can't parse git log output:"
 msgstr "Изходът от „git log“ не може да се анализира:"
 
-#: gitk:1740
+#: gitk:1744
 msgid "No commit information available"
 msgstr "Липсва информация за подавания"
 
-#: gitk:1903 gitk:1932 gitk:4334 gitk:9702 gitk:11274 gitk:11554
+#: gitk:1907 gitk:1936 gitk:4339 gitk:9789 gitk:11388 gitk:11668
 msgid "OK"
 msgstr "Добре"
 
-#: gitk:1934 gitk:4336 gitk:9215 gitk:9294 gitk:9424 gitk:9473 gitk:9704
-#: gitk:11275 gitk:11555
+#: gitk:1938 gitk:4341 gitk:9225 gitk:9304 gitk:9434 gitk:9520 gitk:9791
+#: gitk:11389 gitk:11669
 msgid "Cancel"
 msgstr "Отказ"
 
-#: gitk:2083
+#: gitk:2087
 msgid "&Update"
 msgstr "&Обновяване"
 
-#: gitk:2084
+#: gitk:2088
 msgid "&Reload"
 msgstr "&Презареждане"
 
-#: gitk:2085
+#: gitk:2089
 msgid "Reread re&ferences"
-msgstr "&Наново прочитане на настройките"
+msgstr "&Наново прочитане"
 
-#: gitk:2086
+#: gitk:2090
 msgid "&List references"
 msgstr "&Изброяване на указателите"
 
-#: gitk:2088
+#: gitk:2092
 msgid "Start git &gui"
 msgstr "&Стартиране на „git gui“"
 
-#: gitk:2090
+#: gitk:2094
 msgid "&Quit"
 msgstr "&Спиране на програмата"
 
-#: gitk:2082
+#: gitk:2086
 msgid "&File"
 msgstr "&Файл"
 
-#: gitk:2094
+#: gitk:2098
 msgid "&Preferences"
 msgstr "&Настройки"
 
-#: gitk:2093
+#: gitk:2097
 msgid "&Edit"
 msgstr "&Редактиране"
 
-#: gitk:2098
+#: gitk:2102
 msgid "&New view..."
 msgstr "&Нов изглед…"
 
-#: gitk:2099
+#: gitk:2103
 msgid "&Edit view..."
 msgstr "&Редактиране на изгледа…"
 
-#: gitk:2100
+#: gitk:2104
 msgid "&Delete view"
 msgstr "&Изтриване на изгледа"
 
-#: gitk:2102
+#: gitk:2106
 msgid "&All files"
 msgstr "&Всички файлове"
 
-#: gitk:2097
+#: gitk:2101
 msgid "&View"
 msgstr "&Изглед"
 
-#: gitk:2107 gitk:2117
+#: gitk:2111 gitk:2121
 msgid "&About gitk"
 msgstr "&Относно gitk"
 
-#: gitk:2108 gitk:2122
+#: gitk:2112 gitk:2126
 msgid "&Key bindings"
 msgstr "&Клавишни комбинации"
 
-#: gitk:2106 gitk:2121
+#: gitk:2110 gitk:2125
 msgid "&Help"
 msgstr "Помо&щ"
 
-#: gitk:2199 gitk:8671
+#: gitk:2203 gitk:8681
 msgid "SHA1 ID:"
 msgstr "SHA1:"
 
-#: gitk:2243
+#: gitk:2247
 msgid "Row"
 msgstr "Ред"
 
-#: gitk:2281
+#: gitk:2285
 msgid "Find"
 msgstr "Търсене"
 
-#: gitk:2309
+#: gitk:2313
 msgid "commit"
 msgstr "подаване"
 
-#: gitk:2313 gitk:2315 gitk:4706 gitk:4729 gitk:4753 gitk:6774 gitk:6846
-#: gitk:6931
+#: gitk:2317 gitk:2319 gitk:4711 gitk:4734 gitk:4758 gitk:6779 gitk:6851
+#: gitk:6936
 msgid "containing:"
 msgstr "съдържащо:"
 
-#: gitk:2316 gitk:3545 gitk:3550 gitk:4782
+#: gitk:2320 gitk:3550 gitk:3555 gitk:4787
 msgid "touching paths:"
-msgstr "заÑ\81Ñ\8fгаÑ\89о пътищата:"
+msgstr "в пътищата:"
 
-#: gitk:2317 gitk:4796
+#: gitk:2321 gitk:4801
 msgid "adding/removing string:"
 msgstr "добавящо/премахващо низ"
 
-#: gitk:2318 gitk:4798
+#: gitk:2322 gitk:4803
 msgid "changing lines matching:"
 msgstr "променящо редове напасващи:"
 
-#: gitk:2327 gitk:2329 gitk:4785
+#: gitk:2331 gitk:2333 gitk:4790
 msgid "Exact"
 msgstr "Точно"
 
-#: gitk:2329 gitk:4873 gitk:6742
+#: gitk:2333 gitk:4878 gitk:6747
 msgid "IgnCase"
 msgstr "Без регистър"
 
-#: gitk:2329 gitk:4755 gitk:4871 gitk:6738
+#: gitk:2333 gitk:4760 gitk:4876 gitk:6743
 msgid "Regexp"
 msgstr "Рег. израз"
 
-#: gitk:2331 gitk:2332 gitk:4893 gitk:4923 gitk:4930 gitk:6867 gitk:6935
+#: gitk:2335 gitk:2336 gitk:4898 gitk:4928 gitk:4935 gitk:6872 gitk:6940
 msgid "All fields"
 msgstr "Всички полета"
 
-#: gitk:2332 gitk:4890 gitk:4923 gitk:6805
+#: gitk:2336 gitk:4895 gitk:4928 gitk:6810
 msgid "Headline"
 msgstr "Първи ред"
 
-#: gitk:2333 gitk:4890 gitk:6805 gitk:6935 gitk:7408
+#: gitk:2337 gitk:4895 gitk:6810 gitk:6940 gitk:7413
 msgid "Comments"
 msgstr "Коментари"
 
-#: gitk:2333 gitk:4890 gitk:4895 gitk:4930 gitk:6805 gitk:7343 gitk:8849
-#: gitk:8864
+#: gitk:2337 gitk:4895 gitk:4900 gitk:4935 gitk:6810 gitk:7348 gitk:8859
+#: gitk:8874
 msgid "Author"
 msgstr "Автор"
 
-#: gitk:2333 gitk:4890 gitk:6805 gitk:7345
+#: gitk:2337 gitk:4895 gitk:6810 gitk:7350
 msgid "Committer"
 msgstr "Подаващ"
 
-#: gitk:2367
+#: gitk:2371
 msgid "Search"
 msgstr "Търсене"
 
-#: gitk:2375
+#: gitk:2379
 msgid "Diff"
 msgstr "Разлики"
 
-#: gitk:2377
+#: gitk:2381
 msgid "Old version"
 msgstr "Стара версия"
 
-#: gitk:2379
+#: gitk:2383
 msgid "New version"
 msgstr "Нова версия"
 
-#: gitk:2382
+#: gitk:2386
 msgid "Lines of context"
 msgstr "Контекст в редове"
 
-#: gitk:2392
+#: gitk:2396
 msgid "Ignore space change"
 msgstr "Празните знаци без значение"
 
-#: gitk:2396 gitk:2398 gitk:7978 gitk:8225
+#: gitk:2400 gitk:2402 gitk:7983 gitk:8235
 msgid "Line diff"
 msgstr "Поредови разлики"
 
-#: gitk:2463
+#: gitk:2467
 msgid "Patch"
 msgstr "Кръпка"
 
-#: gitk:2465
+#: gitk:2469
 msgid "Tree"
 msgstr "Дърво"
 
-#: gitk:2635 gitk:2656
+#: gitk:2639 gitk:2660
 msgid "Diff this -> selected"
 msgstr "Разлики между това и избраното"
 
-#: gitk:2636 gitk:2657
+#: gitk:2640 gitk:2661
 msgid "Diff selected -> this"
 msgstr "Разлики между избраното и това"
 
-#: gitk:2637 gitk:2658
+#: gitk:2641 gitk:2662
 msgid "Make patch"
 msgstr "Създаване на кръпка"
 
-#: gitk:2638 gitk:9273
+#: gitk:2642 gitk:9283
 msgid "Create tag"
 msgstr "Създаване на етикет"
 
-#: gitk:2639
+#: gitk:2643
 msgid "Copy commit summary"
 msgstr "Копиране на информацията за подаване"
 
-#: gitk:2640 gitk:9404
+#: gitk:2644 gitk:9414
 msgid "Write commit to file"
 msgstr "Запазване на подаването във файл"
 
-#: gitk:2641 gitk:9461
+#: gitk:2645
 msgid "Create new branch"
 msgstr "Създаване на нов клон"
 
-#: gitk:2642
+#: gitk:2646
 msgid "Cherry-pick this commit"
 msgstr "Отбиране на това подаване"
 
-#: gitk:2643
+#: gitk:2647
 msgid "Reset HEAD branch to here"
 msgstr "Привеждане на върха на клона към текущото подаване"
 
-#: gitk:2644
+#: gitk:2648
 msgid "Mark this commit"
 msgstr "Отбелязване на това подаване"
 
-#: gitk:2645
+#: gitk:2649
 msgid "Return to mark"
 msgstr "Връщане към отбелязаното подаване"
 
-#: gitk:2646
+#: gitk:2650
 msgid "Find descendant of this and mark"
 msgstr "Откриване и отбелязване на наследниците"
 
-#: gitk:2647
+#: gitk:2651
 msgid "Compare with marked commit"
 msgstr "Сравнение с отбелязаното подаване"
 
-#: gitk:2648 gitk:2659
+#: gitk:2652 gitk:2663
 msgid "Diff this -> marked commit"
 msgstr "Разлики между това и отбелязаното"
 
-#: gitk:2649 gitk:2660
+#: gitk:2653 gitk:2664
 msgid "Diff marked commit -> this"
 msgstr "Разлики между отбелязаното и това"
 
-#: gitk:2650
+#: gitk:2654
 msgid "Revert this commit"
 msgstr "Отмяна на това подаване"
 
-#: gitk:2666
+#: gitk:2670
 msgid "Check out this branch"
 msgstr "Изтегляне на този клон"
 
-#: gitk:2667
+#: gitk:2671
+msgid "Rename this branch"
+msgstr "Преименуване на този клон"
+
+#: gitk:2672
 msgid "Remove this branch"
 msgstr "Изтриване на този клон"
 
-#: gitk:2668
+#: gitk:2673
 msgid "Copy branch name"
 msgstr "Копиране на името на клона"
 
-#: gitk:2675
+#: gitk:2680
 msgid "Highlight this too"
 msgstr "Отбелязване и на това"
 
-#: gitk:2676
+#: gitk:2681
 msgid "Highlight this only"
 msgstr "Отбелязване само на това"
 
-#: gitk:2677
+#: gitk:2682
 msgid "External diff"
 msgstr "Външна програма за разлики"
 
-#: gitk:2678
+#: gitk:2683
 msgid "Blame parent commit"
 msgstr "Анотиране на родителското подаване"
 
-#: gitk:2679
+#: gitk:2684
 msgid "Copy path"
 msgstr "Копиране на пътя"
 
-#: gitk:2686
+#: gitk:2691
 msgid "Show origin of this line"
 msgstr "Показване на произхода на този ред"
 
-#: gitk:2687
+#: gitk:2692
 msgid "Run git gui blame on this line"
 msgstr "Изпълнение на „git gui blame“ върху този ред"
 
-#: gitk:3031
+#: gitk:3036
 msgid "About gitk"
 msgstr "Относно gitk"
 
-#: gitk:3033
+#: gitk:3038
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
@@ -382,324 +386,324 @@ msgstr ""
 "\n"
 "Използвайте и разпространявайте при условията на ОПЛ на ГНУ"
 
-#: gitk:3041 gitk:3108 gitk:9890
+#: gitk:3046 gitk:3113 gitk:10004
 msgid "Close"
 msgstr "Затваряне"
 
-#: gitk:3062
+#: gitk:3067
 msgid "Gitk key bindings"
 msgstr "Клавишни комбинации"
 
-#: gitk:3065
+#: gitk:3070
 msgid "Gitk key bindings:"
 msgstr "Клавишни комбинации:"
 
-#: gitk:3067
+#: gitk:3072
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tСпиране на програмата"
 
-#: gitk:3068
+#: gitk:3073
 #, tcl-format
 msgid "<%s-W>\t\tClose window"
 msgstr "<%s-W>\t\tЗатваряне на прозореца"
 
-#: gitk:3069
+#: gitk:3074
 msgid "<Home>\t\tMove to first commit"
 msgstr "<Home>\t\tКъм първото подаване"
 
-#: gitk:3070
+#: gitk:3075
 msgid "<End>\t\tMove to last commit"
 msgstr "<End>\t\tКъм последното подаване"
 
-#: gitk:3071
+#: gitk:3076
 msgid "<Up>, p, k\tMove up one commit"
 msgstr "<Up>, p, k\tЕдно подаване нагоре"
 
-#: gitk:3072
+#: gitk:3077
 msgid "<Down>, n, j\tMove down one commit"
 msgstr "<Down>, n, j\tЕдно подаване надолу"
 
-#: gitk:3073
+#: gitk:3078
 msgid "<Left>, z, h\tGo back in history list"
 msgstr "<Left>, z, h\tНазад в историята"
 
-#: gitk:3074
+#: gitk:3079
 msgid "<Right>, x, l\tGo forward in history list"
 msgstr "<Right>, x, l\tНапред в историята"
 
-#: gitk:3075
+#: gitk:3080
 #, tcl-format
 msgid "<%s-n>\tGo to n-th parent of current commit in history list"
 msgstr "<%s-n>\tКъм n-тия родител на текущото подаване в историята"
 
-#: gitk:3076
+#: gitk:3081
 msgid "<PageUp>\tMove up one page in commit list"
 msgstr "<PageUp>\tСтраница нагоре в списъка с подаванията"
 
-#: gitk:3077
+#: gitk:3082
 msgid "<PageDown>\tMove down one page in commit list"
 msgstr "<PageDown>\tСтраница надолу в списъка с подаванията"
 
-#: gitk:3078
+#: gitk:3083
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Home>\tКъм началото на списъка с подаванията"
 
-#: gitk:3079
+#: gitk:3084
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-End>\tКъм края на списъка с подаванията"
 
-#: gitk:3080
+#: gitk:3085
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
 msgstr "<%s-Up>\tРед нагоре в списъка с подавания"
 
-#: gitk:3081
+#: gitk:3086
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
 msgstr "<%s-Down>\tРед надолу в списъка с подавания"
 
-#: gitk:3082
+#: gitk:3087
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
 msgstr "<%s-PageUp>\tСтраница нагоре в списъка с подавания"
 
-#: gitk:3083
+#: gitk:3088
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
 msgstr "<%s-PageDown>\tСтраница надолу в списъка с подавания"
 
-#: gitk:3084
+#: gitk:3089
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
 msgstr "<Shift-Up>\tТърсене назад (визуално нагоре, исторически — последващи)"
 
-#: gitk:3085
+#: gitk:3090
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
 msgstr ""
 "<Shift-Down>\tТърсене напред (визуално надолу, исторически — предхождащи)"
 
-#: gitk:3086
+#: gitk:3091
 msgid "<Delete>, b\tScroll diff view up one page"
 msgstr "<Delete>, b\tСтраница нагоре в изгледа за разлики"
 
-#: gitk:3087
+#: gitk:3092
 msgid "<Backspace>\tScroll diff view up one page"
 msgstr "<Backspace>\tСтраница надолу в изгледа за разлики"
 
-#: gitk:3088
+#: gitk:3093
 msgid "<Space>\t\tScroll diff view down one page"
 msgstr "<Space>\t\tСтраница надолу в изгледа за разлики"
 
-#: gitk:3089
+#: gitk:3094
 msgid "u\t\tScroll diff view up 18 lines"
 msgstr "u\t\t18 реда нагоре в изгледа за разлики"
 
-#: gitk:3090
+#: gitk:3095
 msgid "d\t\tScroll diff view down 18 lines"
 msgstr "d\t\t18 реда надолу в изгледа за разлики"
 
-#: gitk:3091
+#: gitk:3096
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tТърсене"
 
-#: gitk:3092
+#: gitk:3097
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
 msgstr "<%s-G>\t\tКъм следващата поява"
 
-#: gitk:3093
+#: gitk:3098
 msgid "<Return>\tMove to next find hit"
 msgstr "<Return>\tКъм следващата поява"
 
-#: gitk:3094
+#: gitk:3099
 msgid "g\t\tGo to commit"
 msgstr "g\t\tКъм последното подаване"
 
-#: gitk:3095
+#: gitk:3100
 msgid "/\t\tFocus the search box"
 msgstr "/\t\tФокус върху полето за търсене"
 
-#: gitk:3096
+#: gitk:3101
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tКъм предишната поява"
 
-#: gitk:3097
+#: gitk:3102
 msgid "f\t\tScroll diff view to next file"
 msgstr "f\t\tСледващ файл в изгледа за разлики"
 
-#: gitk:3098
+#: gitk:3103
 #, tcl-format
 msgid "<%s-S>\t\tSearch for next hit in diff view"
 msgstr "<%s-S>\t\tТърсене на следващата поява в изгледа за разлики"
 
-#: gitk:3099
+#: gitk:3104
 #, tcl-format
 msgid "<%s-R>\t\tSearch for previous hit in diff view"
 msgstr "<%s-R>\t\tТърсене на предишната поява в изгледа за разлики"
 
-#: gitk:3100
+#: gitk:3105
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
 msgstr "<%s-KP+>\tПо-голям размер на шрифта"
 
-#: gitk:3101
+#: gitk:3106
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
 msgstr "<%s-plus>\tПо-голям размер на шрифта"
 
-#: gitk:3102
+#: gitk:3107
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
 msgstr "<%s-KP->\tПо-малък размер на шрифта"
 
-#: gitk:3103
+#: gitk:3108
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
 msgstr "<%s-minus>\tПо-малък размер на шрифта"
 
-#: gitk:3104
+#: gitk:3109
 msgid "<F5>\t\tUpdate"
 msgstr "<F5>\t\tОбновяване"
 
-#: gitk:3569 gitk:3578
+#: gitk:3574 gitk:3583
 #, tcl-format
 msgid "Error creating temporary directory %s:"
 msgstr "Грешка при създаването на временната директория „%s“:"
 
-#: gitk:3591
+#: gitk:3596
 #, tcl-format
 msgid "Error getting \"%s\" from %s:"
 msgstr "Грешка при получаването на „%s“ от %s:"
 
-#: gitk:3654
+#: gitk:3659
 msgid "command failed:"
 msgstr "неуспешно изпълнение на команда:"
 
-#: gitk:3803
+#: gitk:3808
 msgid "No such commit"
 msgstr "Такова подаване няма"
 
-#: gitk:3817
+#: gitk:3822
 msgid "git gui blame: command failed:"
 msgstr "„git gui blame“: неуспешно изпълнение на команда:"
 
-#: gitk:3848
+#: gitk:3853
 #, tcl-format
 msgid "Couldn't read merge head: %s"
 msgstr "Върхът за сливане не може да бъде прочетен: %s"
 
-#: gitk:3856
+#: gitk:3861
 #, tcl-format
 msgid "Error reading index: %s"
 msgstr "Грешка при прочитане на индекса: %s"
 
-#: gitk:3881
+#: gitk:3886
 #, tcl-format
 msgid "Couldn't start git blame: %s"
 msgstr "Командата „git blame“ не може да бъде стартирана: %s"
 
-#: gitk:3884 gitk:6773
+#: gitk:3889 gitk:6778
 msgid "Searching"
 msgstr "Търсене"
 
-#: gitk:3916
+#: gitk:3921
 #, tcl-format
 msgid "Error running git blame: %s"
 msgstr "Грешка при изпълнението на „git blame“: %s"
 
-#: gitk:3944
+#: gitk:3949
 #, tcl-format
 msgid "That line comes from commit %s,  which is not in this view"
 msgstr "Този ред идва от подаването %s, което не е в изгледа"
 
-#: gitk:3958
+#: gitk:3963
 msgid "External diff viewer failed:"
 msgstr "Неуспешно изпълнение на външната програма за разлики:"
 
-#: gitk:4062
+#: gitk:4067
 msgid "All files"
 msgstr "Всички файлове"
 
-#: gitk:4086
+#: gitk:4091
 msgid "View"
 msgstr "Изглед"
 
-#: gitk:4089
+#: gitk:4094
 msgid "Gitk view definition"
 msgstr "Дефиниция на изглед в Gitk"
 
-#: gitk:4093
+#: gitk:4098
 msgid "Remember this view"
 msgstr "Запазване на този изглед"
 
-#: gitk:4094
+#: gitk:4099
 msgid "References (space separated list):"
 msgstr "Указатели (списък с разделител интервал):"
 
-#: gitk:4095
+#: gitk:4100
 msgid "Branches & tags:"
 msgstr "Клони и етикети:"
 
-#: gitk:4096
+#: gitk:4101
 msgid "All refs"
 msgstr "Всички указатели"
 
-#: gitk:4097
+#: gitk:4102
 msgid "All (local) branches"
 msgstr "Всички (локални) клони"
 
-#: gitk:4098
+#: gitk:4103
 msgid "All tags"
 msgstr "Всички етикети"
 
-#: gitk:4099
+#: gitk:4104
 msgid "All remote-tracking branches"
 msgstr "Всички следящи клони"
 
-#: gitk:4100
+#: gitk:4105
 msgid "Commit Info (regular expressions):"
 msgstr "Информация за подаване (рег. изр.):"
 
-#: gitk:4101
+#: gitk:4106
 msgid "Author:"
 msgstr "Автор:"
 
-#: gitk:4102
+#: gitk:4107
 msgid "Committer:"
 msgstr "Подал:"
 
-#: gitk:4103
+#: gitk:4108
 msgid "Commit Message:"
 msgstr "Съобщение при подаване:"
 
-#: gitk:4104
+#: gitk:4109
 msgid "Matches all Commit Info criteria"
 msgstr "Съвпадение по всички характеристики на подаването"
 
-#: gitk:4105
+#: gitk:4110
 msgid "Matches no Commit Info criteria"
 msgstr "Не съвпада по никоя от характеристиките на подаването"
 
-#: gitk:4106
+#: gitk:4111
 msgid "Changes to Files:"
 msgstr "Промени по файловете:"
 
-#: gitk:4107
+#: gitk:4112
 msgid "Fixed String"
 msgstr "Дословен низ"
 
-#: gitk:4108
+#: gitk:4113
 msgid "Regular Expression"
 msgstr "Регулярен израз"
 
-#: gitk:4109
+#: gitk:4114
 msgid "Search string:"
 msgstr "Низ за търсене:"
 
-#: gitk:4110
+#: gitk:4115
 msgid ""
 "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
 "15:27:38\"):"
@@ -707,204 +711,204 @@ msgstr ""
 "Дата на подаване („2 weeks ago“ (преди 2 седмици), „2009-03-17 15:27:38“, "
 "„March 17, 2009 15:27:38“):"
 
-#: gitk:4111
+#: gitk:4116
 msgid "Since:"
 msgstr "От:"
 
-#: gitk:4112
+#: gitk:4117
 msgid "Until:"
 msgstr "До:"
 
-#: gitk:4113
+#: gitk:4118
 msgid "Limit and/or skip a number of revisions (positive integer):"
 msgstr ""
 "Ограничаване и/или прескачане на определен брой версии (неотрицателно цяло "
 "число):"
 
-#: gitk:4114
+#: gitk:4119
 msgid "Number to show:"
 msgstr "Брой показани:"
 
-#: gitk:4115
+#: gitk:4120
 msgid "Number to skip:"
 msgstr "Брой прескочени:"
 
-#: gitk:4116
+#: gitk:4121
 msgid "Miscellaneous options:"
 msgstr "Разни:"
 
-#: gitk:4117
+#: gitk:4122
 msgid "Strictly sort by date"
 msgstr "Подреждане по дата"
 
-#: gitk:4118
+#: gitk:4123
 msgid "Mark branch sides"
 msgstr "Отбелязване на страните по клона"
 
-#: gitk:4119
+#: gitk:4124
 msgid "Limit to first parent"
 msgstr "Само първия родител"
 
-#: gitk:4120
+#: gitk:4125
 msgid "Simple history"
 msgstr "Опростена история"
 
-#: gitk:4121
+#: gitk:4126
 msgid "Additional arguments to git log:"
 msgstr "Допълнителни аргументи към „git log“:"
 
-#: gitk:4122
+#: gitk:4127
 msgid "Enter files and directories to include, one per line:"
 msgstr "Въведете файловете и директориите за включване, по елемент на ред"
 
-#: gitk:4123
+#: gitk:4128
 msgid "Command to generate more commits to include:"
 msgstr ""
 "Команда за генерирането на допълнителни подавания, които да бъдат включени:"
 
-#: gitk:4247
+#: gitk:4252
 msgid "Gitk: edit view"
 msgstr "Gitk: редактиране на изглед"
 
-#: gitk:4255
+#: gitk:4260
 msgid "-- criteria for selecting revisions"
 msgstr "— критерии за избор на версии"
 
-#: gitk:4260
+#: gitk:4265
 msgid "View Name"
 msgstr "Име на изглед"
 
-#: gitk:4335
+#: gitk:4340
 msgid "Apply (F5)"
 msgstr "Прилагане (F5)"
 
-#: gitk:4373
+#: gitk:4378
 msgid "Error in commit selection arguments:"
 msgstr "Грешка в аргументите за избор на подавания:"
 
-#: gitk:4428 gitk:4481 gitk:4943 gitk:4957 gitk:6227 gitk:12410 gitk:12411
+#: gitk:4433 gitk:4486 gitk:4948 gitk:4962 gitk:6232 gitk:12524 gitk:12525
 msgid "None"
 msgstr "Няма"
 
-#: gitk:5040 gitk:5045
+#: gitk:5045 gitk:5050
 msgid "Descendant"
 msgstr "Наследник"
 
-#: gitk:5041
+#: gitk:5046
 msgid "Not descendant"
 msgstr "Не е наследник"
 
-#: gitk:5048 gitk:5053
+#: gitk:5053 gitk:5058
 msgid "Ancestor"
 msgstr "Предшественик"
 
-#: gitk:5049
+#: gitk:5054
 msgid "Not ancestor"
 msgstr "Не е предшественик"
 
-#: gitk:5343
+#: gitk:5348
 msgid "Local changes checked in to index but not committed"
 msgstr "Локални промени добавени към индекса, но неподадени"
 
-#: gitk:5379
+#: gitk:5384
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Локални промени извън индекса"
 
-#: gitk:7153
+#: gitk:7158
 msgid "and many more"
 msgstr "и още много"
 
-#: gitk:7156
+#: gitk:7161
 msgid "many"
 msgstr "много"
 
-#: gitk:7347
+#: gitk:7352
 msgid "Tags:"
 msgstr "Етикети:"
 
-#: gitk:7364 gitk:7370 gitk:8844
+#: gitk:7369 gitk:7375 gitk:8854
 msgid "Parent"
 msgstr "Родител"
 
-#: gitk:7375
+#: gitk:7380
 msgid "Child"
 msgstr "Дете"
 
-#: gitk:7384
+#: gitk:7389
 msgid "Branch"
 msgstr "Клон"
 
-#: gitk:7387
+#: gitk:7392
 msgid "Follows"
 msgstr "Следва"
 
-#: gitk:7390
+#: gitk:7395
 msgid "Precedes"
 msgstr "Предшества"
 
-#: gitk:7985
+#: gitk:7990
 #, tcl-format
 msgid "Error getting diffs: %s"
 msgstr "Грешка при получаването на разликите: %s"
 
-#: gitk:8669
+#: gitk:8679
 msgid "Goto:"
 msgstr "Към ред:"
 
-#: gitk:8690
+#: gitk:8700
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "Съкратената сума по SHA1 %s не е еднозначна"
 
-#: gitk:8697
+#: gitk:8707
 #, tcl-format
 msgid "Revision %s is not known"
 msgstr "Непозната версия %s"
 
-#: gitk:8707
+#: gitk:8717
 #, tcl-format
 msgid "SHA1 id %s is not known"
 msgstr "Непозната сума по SHA1 %s"
 
-#: gitk:8709
+#: gitk:8719
 #, tcl-format
 msgid "Revision %s is not in the current view"
 msgstr "Версия %s не е в текущия изглед"
 
-#: gitk:8851 gitk:8866
+#: gitk:8861 gitk:8876
 msgid "Date"
 msgstr "Дата"
 
-#: gitk:8854
+#: gitk:8864
 msgid "Children"
 msgstr "Деца"
 
-#: gitk:8917
+#: gitk:8927
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Зануляване на клона „%s“ към текущото подаване"
 
-#: gitk:8919
+#: gitk:8929
 msgid "Detached head: can't reset"
 msgstr "Несвързан връх: невъзможно зануляване"
 
-#: gitk:9024 gitk:9030
+#: gitk:9034 gitk:9040
 msgid "Skipping merge commit "
 msgstr "Пропускане на подаването на сливането"
 
-#: gitk:9039 gitk:9044
+#: gitk:9049 gitk:9054
 msgid "Error getting patch ID for "
 msgstr "Грешка при получаването на идентификатора на "
 
-#: gitk:9040 gitk:9045
+#: gitk:9050 gitk:9055
 msgid " - stopping\n"
 msgstr " — спиране\n"
 
-#: gitk:9050 gitk:9053 gitk:9061 gitk:9075 gitk:9084
+#: gitk:9060 gitk:9063 gitk:9071 gitk:9085 gitk:9094
 msgid "Commit "
 msgstr "Подаване"
 
-#: gitk:9054
+#: gitk:9064
 msgid ""
 " is the same patch as\n"
 "       "
@@ -912,7 +916,7 @@ msgstr ""
 " е същата кръпка като\n"
 "       "
 
-#: gitk:9062
+#: gitk:9072
 msgid ""
 " differs from\n"
 "       "
@@ -920,7 +924,7 @@ msgstr ""
 " се различава от\n"
 "       "
 
-#: gitk:9064
+#: gitk:9074
 msgid ""
 "Diff of commits:\n"
 "\n"
@@ -928,130 +932,147 @@ msgstr ""
 "Разлика между подаванията:\n"
 "\n"
 
-#: gitk:9076 gitk:9085
+#: gitk:9086 gitk:9095
 #, tcl-format
 msgid " has %s children - stopping\n"
 msgstr " има %s деца — спиране\n"
 
-#: gitk:9104
+#: gitk:9114
 #, tcl-format
 msgid "Error writing commit to file: %s"
 msgstr "Грешка при запазването на подаването във файл: %s"
 
-#: gitk:9110
+#: gitk:9120
 #, tcl-format
 msgid "Error diffing commits: %s"
 msgstr "Грешка при изчисляването на разликите между подаванията: %s"
 
-#: gitk:9156
+#: gitk:9166
 msgid "Top"
 msgstr "Най-горе"
 
-#: gitk:9157
+#: gitk:9167
 msgid "From"
 msgstr "От"
 
-#: gitk:9162
+#: gitk:9172
 msgid "To"
 msgstr "До"
 
-#: gitk:9186
+#: gitk:9196
 msgid "Generate patch"
 msgstr "Генериране на кръпка"
 
-#: gitk:9188
+#: gitk:9198
 msgid "From:"
 msgstr "От:"
 
-#: gitk:9197
+#: gitk:9207
 msgid "To:"
 msgstr "До:"
 
-#: gitk:9206
+#: gitk:9216
 msgid "Reverse"
 msgstr "Обръщане"
 
-#: gitk:9208 gitk:9418
+#: gitk:9218 gitk:9428
 msgid "Output file:"
 msgstr "Запазване във файла:"
 
-#: gitk:9214
+#: gitk:9224
 msgid "Generate"
 msgstr "Генериране"
 
-#: gitk:9252
+#: gitk:9262
 msgid "Error creating patch:"
 msgstr "Грешка при създаването на кръпка:"
 
-#: gitk:9275 gitk:9406 gitk:9463
+#: gitk:9285 gitk:9416 gitk:9504
 msgid "ID:"
 msgstr "Идентификатор:"
 
-#: gitk:9284
+#: gitk:9294
 msgid "Tag name:"
 msgstr "Име на етикет:"
 
-#: gitk:9287
+#: gitk:9297
 msgid "Tag message is optional"
 msgstr "Съобщението за етикет е незадължително"
 
-#: gitk:9289
+#: gitk:9299
 msgid "Tag message:"
 msgstr "Съобщение за етикет:"
 
-#: gitk:9293 gitk:9472
+#: gitk:9303 gitk:9474
 msgid "Create"
 msgstr "Създаване"
 
-#: gitk:9311
+#: gitk:9321
 msgid "No tag name specified"
 msgstr "Липсва име на етикет"
 
-#: gitk:9315
+#: gitk:9325
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "Етикетът „%s“ вече съществува"
 
-#: gitk:9325
+#: gitk:9335
 msgid "Error creating tag:"
 msgstr "Грешка при създаването на етикет:"
 
-#: gitk:9415
+#: gitk:9425
 msgid "Command:"
 msgstr "Команда:"
 
-#: gitk:9423
+#: gitk:9433
 msgid "Write"
 msgstr "Запазване"
 
-#: gitk:9441
+#: gitk:9451
 msgid "Error writing commit:"
 msgstr "Грешка при запазването на подаването:"
 
-#: gitk:9468
+#: gitk:9473
+msgid "Create branch"
+msgstr "Създаване на клон"
+
+#: gitk:9489
+#, tcl-format
+msgid "Rename branch %s"
+msgstr "Преименуване на клона „%s“"
+
+#: gitk:9490
+msgid "Rename"
+msgstr "Преименуване"
+
+#: gitk:9514
 msgid "Name:"
 msgstr "Име:"
 
-#: gitk:9491
+#: gitk:9538
 msgid "Please specify a name for the new branch"
 msgstr "Укажете име за новия клон"
 
-#: gitk:9496
+#: gitk:9543
 #, tcl-format
 msgid "Branch '%s' already exists. Overwrite?"
 msgstr "Клонът „%s“ вече съществува. Да бъде ли презаписан?"
 
-#: gitk:9563
+#: gitk:9587
+msgid "Please specify a new name for the branch"
+msgstr "Укажете ново име за клона"
+
+#: gitk:9650
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr ""
 "Подаването „%s“ вече е включено в клона „%s“ — да бъде ли приложено отново?"
 
-#: gitk:9568
+#: gitk:9655
 msgid "Cherry-picking"
 msgstr "Отбиране"
 
-#: gitk:9577
+#: gitk:9664
 #, tcl-format
 msgid ""
 "Cherry-pick failed because of local changes to file '%s'.\n"
@@ -1060,7 +1081,7 @@ msgstr ""
 "Неуспешно отбиране, защото във файла „%s“ има локални промени.\n"
 "Подайте, занулете или ги скатайте и пробвайте отново."
 
-#: gitk:9583
+#: gitk:9670
 msgid ""
 "Cherry-pick failed because of merge conflict.\n"
 "Do you wish to run git citool to resolve it?"
@@ -1068,29 +1089,29 @@ msgstr ""
 "Неуспешно отбиране поради конфликти при сливане.\n"
 "Искате ли да ги коригирате чрез „git citool“?"
 
-#: gitk:9599 gitk:9657
+#: gitk:9686 gitk:9744
 msgid "No changes committed"
 msgstr "Не са подадени промени"
 
-#: gitk:9626
+#: gitk:9713
 #, tcl-format
 msgid "Commit %s is not included in branch %s -- really revert it?"
 msgstr "Подаването „%s“ не е включено в клона „%s“. Да бъде ли отменено?"
 
-#: gitk:9631
+#: gitk:9718
 msgid "Reverting"
 msgstr "Отмяна"
 
-#: gitk:9639
+#: gitk:9726
 #, tcl-format
 msgid ""
 "Revert failed because of local changes to the following files:%s Please "
 "commit, reset or stash  your changes and try again."
 msgstr ""
 "Неуспешна отмяна, защото във файла „%s“ има локални промени.\n"
-"Подайте, занулете или ги скатайте и пробвайте отново.<"
+"Подайте, занулете или ги скатайте и пробвайте отново."
 
-#: gitk:9643
+#: gitk:9730
 msgid ""
 "Revert failed because of merge conflict.\n"
 " Do you wish to run git citool to resolve it?"
@@ -1098,66 +1119,71 @@ msgstr ""
 "Неуспешно отмяна поради конфликти при сливане.\n"
 "Искате ли да ги коригирате чрез „git citool“?"
 
-#: gitk:9686
+#: gitk:9773
 msgid "Confirm reset"
 msgstr "Потвърждаване на зануляването"
 
-#: gitk:9688
+#: gitk:9775
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Да се занули ли клонът „%s“ към „%s“?"
 
-#: gitk:9690
+#: gitk:9777
 msgid "Reset type:"
 msgstr "Вид зануляване:"
 
-#: gitk:9693
+#: gitk:9780
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Слабо: работното дърво и индекса остават същите"
 
-#: gitk:9696
+#: gitk:9783
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr "Смесено: работното дърво остава същото, индексът се занулява"
 
-#: gitk:9699
+#: gitk:9786
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
 msgstr ""
 "Силно: зануляване и на работното дърво, и на индекса\n"
-"(*ВСИЧКИ* локални промени ще бъдат безвъзвратно загубени)"
+"(ВСИЧКИ локални промени ще бъдат безвъзвратно загубени)"
 
-#: gitk:9716
+#: gitk:9803
 msgid "Resetting"
 msgstr "Зануляване"
 
-#: gitk:9776
+#: gitk:9876
+#, tcl-format
+msgid "A local branch named %s exists already"
+msgstr "Вече съществува локален клон „%s“."
+
+#: gitk:9884
 msgid "Checking out"
 msgstr "Изтегляне"
 
-#: gitk:9829
+#: gitk:9943
 msgid "Cannot delete the currently checked-out branch"
 msgstr "Текущо изтегленият клон не може да бъде изтрит"
 
-#: gitk:9835
+#: gitk:9949
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
 "Really delete branch %s?"
 msgstr ""
 "Подаванията на клона „%s“ не са на никой друг клон.\n"
-"Ð\9dаиÑ\81Ñ\82ина Ð»Ð¸ Ð´Ð° Ñ\81е Ð¸Ð·Ñ\82Ñ\80ие ÐºÐ»Ð¾Ð½Ñ\8aÑ\82 „%s“?"
+"Ð\9dаиÑ\81Ñ\82ина Ð»Ð¸ Ð¸Ñ\81каÑ\82е Ð´Ð° Ð¸Ð·Ñ\82Ñ\80иеÑ\82е ÐºÐ»Ð¾Ð½Ð° „%s“?"
 
-#: gitk:9866
+#: gitk:9980
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Етикети и върхове: %s"
 
-#: gitk:9883
+#: gitk:9997
 msgid "Filter"
 msgstr "Филтриране"
 
-#: gitk:10179
+#: gitk:10293
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -1165,201 +1191,201 @@ msgstr ""
 "Грешка при прочитането на топологията на подаванията. Информацията за клона "
 "и предшестващите/следващите етикети ще е непълна."
 
-#: gitk:11156
+#: gitk:11270
 msgid "Tag"
 msgstr "Етикет"
 
-#: gitk:11160
+#: gitk:11274
 msgid "Id"
 msgstr "Идентификатор"
 
-#: gitk:11243
+#: gitk:11357
 msgid "Gitk font chooser"
 msgstr "Избор на шрифт за Gitk"
 
-#: gitk:11260
+#: gitk:11374
 msgid "B"
 msgstr "Ч"
 
-#: gitk:11263
+#: gitk:11377
 msgid "I"
 msgstr "К"
 
-#: gitk:11381
+#: gitk:11495
 msgid "Commit list display options"
 msgstr "Настройки на списъка с подавания"
 
-#: gitk:11384
+#: gitk:11498
 msgid "Maximum graph width (lines)"
 msgstr "Максимална широчина на графа (в редове)"
 
-#: gitk:11388
+#: gitk:11502
 #, no-tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Максимална широчина на графа (% от панела)"
 
-#: gitk:11391
+#: gitk:11505
 msgid "Show local changes"
 msgstr "Показване на локалните промени"
 
-#: gitk:11394
+#: gitk:11508
 msgid "Auto-select SHA1 (length)"
 msgstr "Автоматично избиране на SHA1 (дължина)"
 
-#: gitk:11398
+#: gitk:11512
 msgid "Hide remote refs"
 msgstr "Скриване на отдалечените указатели"
 
-#: gitk:11402
+#: gitk:11516
 msgid "Diff display options"
 msgstr "Настройки на показването на разликите"
 
-#: gitk:11404
+#: gitk:11518
 msgid "Tab spacing"
 msgstr "Широчина на табулатора"
 
-#: gitk:11407
+#: gitk:11521
 msgid "Display nearby tags/heads"
 msgstr "Извеждане на близките етикети и върхове"
 
-#: gitk:11410
+#: gitk:11524
 msgid "Maximum # tags/heads to show"
 msgstr "Максимален брой етикети/върхове за показване"
 
-#: gitk:11413
+#: gitk:11527
 msgid "Limit diffs to listed paths"
 msgstr "Разлика само в избраните пътища"
 
-#: gitk:11416
+#: gitk:11530
 msgid "Support per-file encodings"
 msgstr "Поддръжка на различни кодирания за всеки файл"
 
-#: gitk:11422 gitk:11569
+#: gitk:11536 gitk:11683
 msgid "External diff tool"
 msgstr "Външен инструмент за разлики"
 
-#: gitk:11423
+#: gitk:11537
 msgid "Choose..."
 msgstr "Избор…"
 
-#: gitk:11428
+#: gitk:11542
 msgid "General options"
 msgstr "Общи настройки"
 
-#: gitk:11431
+#: gitk:11545
 msgid "Use themed widgets"
 msgstr "Използване на тема за графичните обекти"
 
-#: gitk:11433
+#: gitk:11547
 msgid "(change requires restart)"
 msgstr "(промяната изисква рестартиране на Gitk)"
 
-#: gitk:11435
+#: gitk:11549
 msgid "(currently unavailable)"
 msgstr "(в момента недостъпно)"
 
-#: gitk:11446
+#: gitk:11560
 msgid "Colors: press to choose"
 msgstr "Цветове: избира се с натискане"
 
-#: gitk:11449
+#: gitk:11563
 msgid "Interface"
 msgstr "Интерфейс"
 
-#: gitk:11450
+#: gitk:11564
 msgid "interface"
 msgstr "интерфейс"
 
-#: gitk:11453
+#: gitk:11567
 msgid "Background"
 msgstr "Фон"
 
-#: gitk:11454 gitk:11484
+#: gitk:11568 gitk:11598
 msgid "background"
 msgstr "фон"
 
-#: gitk:11457
+#: gitk:11571
 msgid "Foreground"
 msgstr "Знаци"
 
-#: gitk:11458
+#: gitk:11572
 msgid "foreground"
 msgstr "знаци"
 
-#: gitk:11461
+#: gitk:11575
 msgid "Diff: old lines"
 msgstr "Разлика: стари редове"
 
-#: gitk:11462
+#: gitk:11576
 msgid "diff old lines"
 msgstr "разлика, стари редове"
 
-#: gitk:11466
+#: gitk:11580
 msgid "Diff: new lines"
 msgstr "Разлика: нови редове"
 
-#: gitk:11467
+#: gitk:11581
 msgid "diff new lines"
 msgstr "разлика, нови редове"
 
-#: gitk:11471
+#: gitk:11585
 msgid "Diff: hunk header"
 msgstr "Разлика: начало на парче"
 
-#: gitk:11473
+#: gitk:11587
 msgid "diff hunk header"
 msgstr "разлика, начало на парче"
 
-#: gitk:11477
+#: gitk:11591
 msgid "Marked line bg"
 msgstr "Фон на отбелязан ред"
 
-#: gitk:11479
+#: gitk:11593
 msgid "marked line background"
 msgstr "фон на отбелязан ред"
 
-#: gitk:11483
+#: gitk:11597
 msgid "Select bg"
 msgstr "Избор на фон"
 
-#: gitk:11492
+#: gitk:11606
 msgid "Fonts: press to choose"
 msgstr "Шрифтове: избира се с натискане"
 
-#: gitk:11494
+#: gitk:11608
 msgid "Main font"
 msgstr "Основен шрифт"
 
-#: gitk:11495
+#: gitk:11609
 msgid "Diff display font"
 msgstr "Шрифт за разликите"
 
-#: gitk:11496
+#: gitk:11610
 msgid "User interface font"
 msgstr "Шрифт на интерфейса"
 
-#: gitk:11518
+#: gitk:11632
 msgid "Gitk preferences"
 msgstr "Настройки на Gitk"
 
-#: gitk:11527
+#: gitk:11641
 msgid "General"
 msgstr "Общи"
 
-#: gitk:11528
+#: gitk:11642
 msgid "Colors"
 msgstr "Цветове"
 
-#: gitk:11529
+#: gitk:11643
 msgid "Fonts"
 msgstr "Шрифтове"
 
-#: gitk:11579
+#: gitk:11693
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: избор на цвят на „%s“"
 
-#: gitk:12092
+#: gitk:12206
 msgid ""
 "Sorry, gitk cannot run with this version of Tcl/Tk.\n"
 " Gitk requires at least Tcl/Tk 8.4."
@@ -1367,15 +1393,15 @@ msgstr ""
 "Тази версия на Tcl/Tk не се поддържа от Gitk.\n"
 " Необходима ви е поне Tcl/Tk 8.4."
 
-#: gitk:12302
+#: gitk:12416
 msgid "Cannot find a git repository here."
 msgstr "Тук липсва хранилище на Git."
 
-#: gitk:12349
+#: gitk:12463
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Нееднозначен аргумент „%s“: има и такава версия, и такъв файл"
 
-#: gitk:12361
+#: gitk:12475
 msgid "Bad arguments to gitk:"
 msgstr "Неправилни аргументи на gitk:"
diff --git a/ident.c b/ident.c
index 33bcf40644cdf23434a7cb622b6be71bb7cd867c..9c2eb0a2d02ac823a6bf3f01603dfdc7164728c8 100644 (file)
--- a/ident.c
+++ b/ident.c
 static struct strbuf git_default_name = STRBUF_INIT;
 static struct strbuf git_default_email = STRBUF_INIT;
 static struct strbuf git_default_date = STRBUF_INIT;
+static struct strbuf git_author_name = STRBUF_INIT;
+static struct strbuf git_author_email = STRBUF_INIT;
+static struct strbuf git_committer_name = STRBUF_INIT;
+static struct strbuf git_committer_email = STRBUF_INIT;
 static int default_email_is_bogus;
 static int default_name_is_bogus;
 
@@ -355,13 +359,19 @@ N_("\n"
    "\n");
 
 const char *fmt_ident(const char *name, const char *email,
-                     const char *date_str, int flag)
+                     enum want_ident whose_ident, const char *date_str, int flag)
 {
        static struct strbuf ident = STRBUF_INIT;
        int strict = (flag & IDENT_STRICT);
        int want_date = !(flag & IDENT_NO_DATE);
        int want_name = !(flag & IDENT_NO_NAME);
 
+       if (!email) {
+               if (whose_ident == WANT_AUTHOR_IDENT && git_author_email.len)
+                       email = git_author_email.buf;
+               else if (whose_ident == WANT_COMMITTER_IDENT && git_committer_email.len)
+                       email = git_committer_email.buf;
+       }
        if (!email) {
                if (strict && ident_use_config_only
                    && !(ident_config_given & IDENT_MAIL_GIVEN)) {
@@ -377,6 +387,13 @@ const char *fmt_ident(const char *name, const char *email,
 
        if (want_name) {
                int using_default = 0;
+               if (!name) {
+                       if (whose_ident == WANT_AUTHOR_IDENT && git_author_name.len)
+                               name = git_author_name.buf;
+                       else if (whose_ident == WANT_COMMITTER_IDENT &&
+                                       git_committer_name.len)
+                               name = git_committer_name.buf;
+               }
                if (!name) {
                        if (strict && ident_use_config_only
                            && !(ident_config_given & IDENT_NAME_GIVEN)) {
@@ -425,9 +442,25 @@ const char *fmt_ident(const char *name, const char *email,
        return ident.buf;
 }
 
-const char *fmt_name(const char *name, const char *email)
+const char *fmt_name(enum want_ident whose_ident)
 {
-       return fmt_ident(name, email, NULL, IDENT_STRICT | IDENT_NO_DATE);
+       char *name = NULL;
+       char *email = NULL;
+
+       switch (whose_ident) {
+       case WANT_BLANK_IDENT:
+               break;
+       case WANT_AUTHOR_IDENT:
+               name = getenv("GIT_AUTHOR_NAME");
+               email = getenv("GIT_AUTHOR_EMAIL");
+               break;
+       case WANT_COMMITTER_IDENT:
+               name = getenv("GIT_COMMITTER_NAME");
+               email = getenv("GIT_COMMITTER_EMAIL");
+               break;
+       }
+       return fmt_ident(name, email, whose_ident, NULL,
+                       IDENT_STRICT | IDENT_NO_DATE);
 }
 
 const char *git_author_info(int flag)
@@ -438,6 +471,7 @@ const char *git_author_info(int flag)
                author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
        return fmt_ident(getenv("GIT_AUTHOR_NAME"),
                         getenv("GIT_AUTHOR_EMAIL"),
+                        WANT_AUTHOR_IDENT,
                         getenv("GIT_AUTHOR_DATE"),
                         flag);
 }
@@ -450,6 +484,7 @@ const char *git_committer_info(int flag)
                committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
        return fmt_ident(getenv("GIT_COMMITTER_NAME"),
                         getenv("GIT_COMMITTER_EMAIL"),
+                        WANT_COMMITTER_IDENT,
                         getenv("GIT_COMMITTER_DATE"),
                         flag);
 }
@@ -473,10 +508,45 @@ int author_ident_sufficiently_given(void)
        return ident_is_sufficient(author_ident_explicitly_given);
 }
 
-int git_ident_config(const char *var, const char *value, void *data)
+static int set_ident(const char *var, const char *value)
 {
-       if (!strcmp(var, "user.useconfigonly")) {
-               ident_use_config_only = git_config_bool(var, value);
+       if (!strcmp(var, "author.name")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               strbuf_reset(&git_author_name);
+               strbuf_addstr(&git_author_name, value);
+               author_ident_explicitly_given |= IDENT_NAME_GIVEN;
+               ident_config_given |= IDENT_NAME_GIVEN;
+               return 0;
+       }
+
+       if (!strcmp(var, "author.email")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               strbuf_reset(&git_author_email);
+               strbuf_addstr(&git_author_email, value);
+               author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
+               ident_config_given |= IDENT_MAIL_GIVEN;
+               return 0;
+       }
+
+       if (!strcmp(var, "committer.name")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               strbuf_reset(&git_committer_name);
+               strbuf_addstr(&git_committer_name, value);
+               committer_ident_explicitly_given |= IDENT_NAME_GIVEN;
+               ident_config_given |= IDENT_NAME_GIVEN;
+               return 0;
+       }
+
+       if (!strcmp(var, "committer.email")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               strbuf_reset(&git_committer_email);
+               strbuf_addstr(&git_committer_email, value);
+               committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
+               ident_config_given |= IDENT_MAIL_GIVEN;
                return 0;
        }
 
@@ -505,6 +575,16 @@ int git_ident_config(const char *var, const char *value, void *data)
        return 0;
 }
 
+int git_ident_config(const char *var, const char *value, void *data)
+{
+       if (!strcmp(var, "user.useconfigonly")) {
+               ident_use_config_only = git_config_bool(var, value);
+               return 0;
+       }
+
+       return set_ident(var, value);
+}
+
 static int buf_cmp(const char *a_begin, const char *a_end,
                   const char *b_begin, const char *b_end)
 {
index 24e21731c4315f414578b33178a7b784176bb24d..59248e37cc3cec8fd309df77fb58b09c8612f336 100644 (file)
@@ -1103,10 +1103,12 @@ static int process_all_files(struct line_log_data **range_out,
 
 int line_log_print(struct rev_info *rev, struct commit *commit)
 {
-       struct line_log_data *range = lookup_line_range(rev, commit);
 
        show_log(rev);
-       dump_diff_hacky(rev, range);
+       if (!(rev->diffopt.output_format & DIFF_FORMAT_NO_OUTPUT)) {
+               struct line_log_data *range = lookup_line_range(rev, commit);
+               dump_diff_hacky(rev, range);
+       }
        return 1;
 }
 
index b71bd1fb65bc6b9d6c6889b7dcf8d210d57f1e53..c0036f73789d1a6a3ff88a76c3838241e65ca868 100644 (file)
@@ -82,6 +82,10 @@ static int gently_parse_list_objects_filter(
                filter_options->sparse_path_value = strdup(v0);
                return 0;
        }
+       /*
+        * Please update _git_fetch() in git-completion.bash when you
+        * add new filters
+        */
 
        if (errbuf)
                strbuf_addf(errbuf, "invalid filter-spec '%s'", arg);
index 3cb14256ec5b7a2712e23b8feb12b1a91328bc34..1e56df62a79c0d221fa55ba351b6b16f8027d12b 100644 (file)
@@ -687,8 +687,7 @@ void show_log(struct rev_info *opt)
         */
        if (ctx.need_8bit_cte >= 0 && opt->add_signoff)
                ctx.need_8bit_cte =
-                       has_non_ascii(fmt_name(getenv("GIT_COMMITTER_NAME"),
-                                              getenv("GIT_COMMITTER_EMAIL")));
+                       has_non_ascii(fmt_name(WANT_COMMITTER_IDENT));
        ctx.date_mode = opt->date_mode;
        ctx.date_mode_explicit = opt->date_mode_explicit;
        ctx.abbrev = opt->diffopt.abbrev;
index 4851825aebf29d129aa4b82d860adcbb2697a300..6c40c61c4728d9224006768599d7bf2ded744bb1 100644 (file)
@@ -1402,8 +1402,7 @@ static int merge_mode_and_contents(struct merge_options *o,
 
 static int handle_rename_via_dir(struct merge_options *o,
                                 struct diff_filepair *pair,
-                                const char *rename_branch,
-                                const char *other_branch)
+                                const char *rename_branch)
 {
        /*
         * Handle file adds that need to be renamed due to directory rename
@@ -2213,8 +2212,7 @@ static void handle_directory_level_conflicts(struct merge_options *o,
        remove_hashmap_entries(dir_re_merge, &remove_from_merge);
 }
 
-static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
-                                            struct tree *tree)
+static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs)
 {
        struct hashmap *dir_renames;
        struct hashmap_iter iter;
@@ -2460,8 +2458,7 @@ static void apply_directory_rename_modifications(struct merge_options *o,
                                                 struct tree *o_tree,
                                                 struct tree *a_tree,
                                                 struct tree *b_tree,
-                                                struct string_list *entries,
-                                                int *clean)
+                                                struct string_list *entries)
 {
        struct string_list_item *item;
        int stage = (tree == a_tree ? 2 : 3);
@@ -2632,8 +2629,7 @@ static struct string_list *get_renames(struct merge_options *o,
                        apply_directory_rename_modifications(o, pair, new_path,
                                                             re, tree, o_tree,
                                                             a_tree, b_tree,
-                                                            entries,
-                                                            clean_merge);
+                                                            entries);
        }
 
        hashmap_iter_init(&collisions, &iter);
@@ -2944,8 +2940,8 @@ static int detect_and_process_renames(struct merge_options *o,
        merge_pairs = get_diffpairs(o, common, merge);
 
        if (o->detect_directory_renames) {
-               dir_re_head = get_directory_renames(head_pairs, head);
-               dir_re_merge = get_directory_renames(merge_pairs, merge);
+               dir_re_head = get_directory_renames(head_pairs);
+               dir_re_merge = get_directory_renames(merge_pairs);
 
                handle_directory_level_conflicts(o,
                                                 dir_re_head, head,
@@ -3268,8 +3264,7 @@ static int process_entry(struct merge_options *o,
                        clean_merge = 1;
                        if (handle_rename_via_dir(o,
                                                  conflict_info->pair1,
-                                                 conflict_info->branch1,
-                                                 conflict_info->branch2))
+                                                 conflict_info->branch1))
                                clean_merge = -1;
                        break;
                case RENAME_ADD:
@@ -3764,6 +3759,10 @@ int parse_merge_opt(struct merge_options *o, const char *s)
                        return -1;
                o->merge_detect_rename = 1;
        }
+       /*
+        * Please update $__git_merge_strategy_options in
+        * git-completion.bash when you add new options
+        */
        else
                return -1;
        return 0;
index e7cd337bee53cb02282a14c8aa88f2342edd2eb2..ce33b8906e5c589813c7d873c9173aa3aa2afddc 100644 (file)
@@ -119,8 +119,7 @@ static void prepare_in_pack_by_idx(struct packing_data *pdata)
  * this fall back code, just stay simple and fall back to using
  * in_pack[] array.
  */
-void oe_map_new_pack(struct packing_data *pack,
-                    struct packed_git *p)
+void oe_map_new_pack(struct packing_data *pack)
 {
        uint32_t i;
 
index 6bfacc7d2cedd7f399d7807b7746b6ed66737eed..6fde7ce27cbc15b33a3d994bad87ab140f3164a7 100644 (file)
@@ -247,14 +247,14 @@ static inline struct packed_git *oe_in_pack(const struct packing_data *pack,
                return pack->in_pack[e - pack->objects];
 }
 
-void oe_map_new_pack(struct packing_data *pack,
-                    struct packed_git *p);
+void oe_map_new_pack(struct packing_data *pack);
+
 static inline void oe_set_in_pack(struct packing_data *pack,
                                  struct object_entry *e,
                                  struct packed_git *p)
 {
        if (!p->index)
-               oe_map_new_pack(pack, p);
+               oe_map_new_pack(pack);
        if (pack->in_pack_by_idx)
                e->in_pack_idx = p->index;
        else
diff --git a/pager.c b/pager.c
index a768797fcfcc44de4dbe4d983a45ade8c81b2e1c..4168460ae92ceb18b4a31c4d0524b411231cf137 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -100,6 +100,7 @@ void prepare_pager_args(struct child_process *pager_process, const char *pager)
        argv_array_push(&pager_process->args, pager);
        pager_process->use_shell = 1;
        setup_pager_env(&pager_process->env_array);
+       pager_process->trace2_child_class = "pager";
 }
 
 void setup_pager(void)
index e2f3eaed072f77d63890ec814d810199f57248d5..2733393546d4d4dd9a55c5934531398f4a827ead 100644 (file)
@@ -170,9 +170,12 @@ int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset)
  * "-h" output even if it's not being handled directly by
  * parse_options().
  */
-int parse_opt_unknown_cb(const struct option *opt, const char *arg, int unset)
+enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
+                                          const struct option *opt,
+                                          const char *arg, int unset)
 {
-       return -2;
+       BUG_ON_OPT_ARG(arg);
+       return PARSE_OPT_UNKNOWN;
 }
 
 /**
index 9f84bacce64e72d117be2bdbe91db6d4c190e257..cec74522e56b084fbb0c8eeec47456d0bb2f7d3e 100644 (file)
@@ -20,8 +20,9 @@ int optbug(const struct option *opt, const char *reason)
        return error("BUG: switch '%c' %s", opt->short_name, reason);
 }
 
-static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
-                  int flags, const char **arg)
+static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p,
+                                    const struct option *opt,
+                                    int flags, const char **arg)
 {
        if (p->opt) {
                *arg = p->opt;
@@ -44,9 +45,10 @@ static void fix_filename(const char *prefix, const char **file)
        *file = prefix_filename(prefix, *file);
 }
 
-static int opt_command_mode_error(const struct option *opt,
-                                 const struct option *all_opts,
-                                 int flags)
+static enum parse_opt_result opt_command_mode_error(
+       const struct option *opt,
+       const struct option *all_opts,
+       int flags)
 {
        const struct option *that;
        struct strbuf that_name = STRBUF_INIT;
@@ -69,16 +71,16 @@ static int opt_command_mode_error(const struct option *opt,
                error(_("%s is incompatible with %s"),
                      optname(opt, flags), that_name.buf);
                strbuf_release(&that_name);
-               return -1;
+               return PARSE_OPT_ERROR;
        }
        return error(_("%s : incompatible with something else"),
                     optname(opt, flags));
 }
 
-static int get_value(struct parse_opt_ctx_t *p,
-                    const struct option *opt,
-                    const struct option *all_opts,
-                    int flags)
+static enum parse_opt_result get_value(struct parse_opt_ctx_t *p,
+                                      const struct option *opt,
+                                      const struct option *all_opts,
+                                      int flags)
 {
        const char *s, *arg;
        const int unset = flags & OPT_UNSET;
@@ -93,7 +95,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 
        switch (opt->type) {
        case OPTION_LOWLEVEL_CALLBACK:
-               return (*(parse_opt_ll_cb *)opt->callback)(p, opt, unset);
+               return opt->ll_callback(p, opt, NULL, unset);
 
        case OPTION_BIT:
                if (unset)
@@ -109,6 +111,13 @@ static int get_value(struct parse_opt_ctx_t *p,
                        *(int *)opt->value &= ~opt->defval;
                return 0;
 
+       case OPTION_BITOP:
+               if (unset)
+                       BUG("BITOP can't have unset form");
+               *(int *)opt->value &= ~opt->extra;
+               *(int *)opt->value |= opt->defval;
+               return 0;
+
        case OPTION_COUNTUP:
                if (*(int *)opt->value < 0)
                        *(int *)opt->value = 0;
@@ -152,16 +161,27 @@ static int get_value(struct parse_opt_ctx_t *p,
                return err;
 
        case OPTION_CALLBACK:
+       {
+               const char *p_arg = NULL;
+               int p_unset;
+
                if (unset)
-                       return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
-               if (opt->flags & PARSE_OPT_NOARG)
-                       return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
-               if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
-                       return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
-               if (get_arg(p, opt, flags, &arg))
+                       p_unset = 1;
+               else if (opt->flags & PARSE_OPT_NOARG)
+                       p_unset = 0;
+               else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+                       p_unset = 0;
+               else if (get_arg(p, opt, flags, &arg))
                        return -1;
-               return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
-
+               else {
+                       p_unset = 0;
+                       p_arg = arg;
+               }
+               if (opt->callback)
+                       return (*opt->callback)(opt, p_arg, p_unset) ? (-1) : 0;
+               else
+                       return (*opt->ll_callback)(p, opt, p_arg, p_unset);
+       }
        case OPTION_INTEGER:
                if (unset) {
                        *(int *)opt->value = 0;
@@ -201,7 +221,8 @@ static int get_value(struct parse_opt_ctx_t *p,
        }
 }
 
-static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
+static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p,
+                                            const struct option *options)
 {
        const struct option *all_opts = options;
        const struct option *numopt = NULL;
@@ -228,15 +249,19 @@ static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *optio
                        len++;
                arg = xmemdupz(p->opt, len);
                p->opt = p->opt[len] ? p->opt + len : NULL;
-               rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
+               if (numopt->callback)
+                       rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
+               else
+                       rc = (*numopt->ll_callback)(p, numopt, arg, 0);
                free(arg);
                return rc;
        }
-       return -2;
+       return PARSE_OPT_UNKNOWN;
 }
 
-static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
-                         const struct option *options)
+static enum parse_opt_result parse_long_opt(
+       struct parse_opt_ctx_t *p, const char *arg,
+       const struct option *options)
 {
        const struct option *all_opts = options;
        const char *arg_end = strchrnul(arg, '=');
@@ -262,11 +287,12 @@ again:
                        if (*rest)
                                continue;
                        p->out[p->cpidx++] = arg - 2;
-                       return 0;
+                       return PARSE_OPT_DONE;
                }
                if (!rest) {
                        /* abbreviated? */
-                       if (!strncmp(long_name, arg, arg_end - arg)) {
+                       if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) &&
+                           !strncmp(long_name, arg, arg_end - arg)) {
 is_abbreviated:
                                if (abbrev_option) {
                                        /*
@@ -326,11 +352,11 @@ is_abbreviated:
                        ambiguous_option->long_name,
                        (abbrev_flags & OPT_UNSET) ?  "no-" : "",
                        abbrev_option->long_name);
-               return -3;
+               return PARSE_OPT_HELP;
        }
        if (abbrev_option)
                return get_value(p, abbrev_option, all_opts, abbrev_flags);
-       return -2;
+       return PARSE_OPT_UNKNOWN;
 }
 
 static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg,
@@ -400,6 +426,19 @@ static void parse_options_check(const struct option *opts)
                        if ((opts->flags & PARSE_OPT_OPTARG) ||
                            !(opts->flags & PARSE_OPT_NOARG))
                                err |= optbug(opts, "should not accept an argument");
+                       break;
+               case OPTION_CALLBACK:
+                       if (!opts->callback && !opts->ll_callback)
+                               BUG("OPTION_CALLBACK needs one callback");
+                       if (opts->callback && opts->ll_callback)
+                               BUG("OPTION_CALLBACK can't have two callbacks");
+                       break;
+               case OPTION_LOWLEVEL_CALLBACK:
+                       if (!opts->ll_callback)
+                               BUG("OPTION_LOWLEVEL_CALLBACK needs a callback");
+                       if (opts->callback)
+                               BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback");
+                       break;
                default:
                        ; /* ok. (usually accepts an argument) */
                }
@@ -416,15 +455,24 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
                         const struct option *options, int flags)
 {
        memset(ctx, 0, sizeof(*ctx));
-       ctx->argc = ctx->total = argc - 1;
-       ctx->argv = argv + 1;
-       ctx->out  = argv;
+       ctx->argc = argc;
+       ctx->argv = argv;
+       if (!(flags & PARSE_OPT_ONE_SHOT)) {
+               ctx->argc--;
+               ctx->argv++;
+       }
+       ctx->total = ctx->argc;
+       ctx->out   = argv;
        ctx->prefix = prefix;
        ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
        ctx->flags = flags;
        if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
-           (flags & PARSE_OPT_STOP_AT_NON_OPTION))
+           (flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
+           !(flags & PARSE_OPT_ONE_SHOT))
                BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
+       if ((flags & PARSE_OPT_ONE_SHOT) &&
+           (flags & PARSE_OPT_KEEP_ARGV0))
+               BUG("Can't keep argv0 if you don't have it");
        parse_options_check(options);
 }
 
@@ -536,6 +584,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
        for (; ctx->argc; ctx->argc--, ctx->argv++) {
                const char *arg = ctx->argv[0];
 
+               if (ctx->flags & PARSE_OPT_ONE_SHOT &&
+                   ctx->argc != ctx->total)
+                       break;
+
                if (*arg != '-' || !arg[1]) {
                        if (parse_nodash_opt(ctx, arg, options) == 0)
                                continue;
@@ -556,22 +608,28 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                if (arg[1] != '-') {
                        ctx->opt = arg + 1;
                        switch (parse_short_opt(ctx, options)) {
-                       case -1:
+                       case PARSE_OPT_ERROR:
                                return PARSE_OPT_ERROR;
-                       case -2:
+                       case PARSE_OPT_UNKNOWN:
                                if (ctx->opt)
                                        check_typos(arg + 1, options);
                                if (internal_help && *ctx->opt == 'h')
                                        goto show_usage;
                                goto unknown;
+                       case PARSE_OPT_NON_OPTION:
+                       case PARSE_OPT_HELP:
+                       case PARSE_OPT_COMPLETE:
+                               BUG("parse_short_opt() cannot return these");
+                       case PARSE_OPT_DONE:
+                               break;
                        }
                        if (ctx->opt)
                                check_typos(arg + 1, options);
                        while (ctx->opt) {
                                switch (parse_short_opt(ctx, options)) {
-                               case -1:
+                               case PARSE_OPT_ERROR:
                                        return PARSE_OPT_ERROR;
-                               case -2:
+                               case PARSE_OPT_UNKNOWN:
                                        if (internal_help && *ctx->opt == 'h')
                                                goto show_usage;
 
@@ -583,6 +641,12 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                                        ctx->argv[0] = xstrdup(ctx->opt - 1);
                                        *(char *)ctx->argv[0] = '-';
                                        goto unknown;
+                               case PARSE_OPT_NON_OPTION:
+                               case PARSE_OPT_COMPLETE:
+                               case PARSE_OPT_HELP:
+                                       BUG("parse_short_opt() cannot return these");
+                               case PARSE_OPT_DONE:
+                                       break;
                                }
                        }
                        continue;
@@ -601,15 +665,22 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                if (internal_help && !strcmp(arg + 2, "help"))
                        goto show_usage;
                switch (parse_long_opt(ctx, arg + 2, options)) {
-               case -1:
+               case PARSE_OPT_ERROR:
                        return PARSE_OPT_ERROR;
-               case -2:
+               case PARSE_OPT_UNKNOWN:
                        goto unknown;
-               case -3:
+               case PARSE_OPT_HELP:
                        goto show_usage;
+               case PARSE_OPT_NON_OPTION:
+               case PARSE_OPT_COMPLETE:
+                       BUG("parse_long_opt() cannot return these");
+               case PARSE_OPT_DONE:
+                       break;
                }
                continue;
 unknown:
+               if (ctx->flags & PARSE_OPT_ONE_SHOT)
+                       break;
                if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
                        return PARSE_OPT_UNKNOWN;
                ctx->out[ctx->cpidx++] = ctx->argv[0];
@@ -623,6 +694,9 @@ unknown:
 
 int parse_options_end(struct parse_opt_ctx_t *ctx)
 {
+       if (ctx->flags & PARSE_OPT_ONE_SHOT)
+               return ctx->total - ctx->argc;
+
        MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc);
        ctx->out[ctx->cpidx + ctx->argc] = NULL;
        return ctx->cpidx + ctx->argc;
index 14fe32428e57aee0716517219e3fc925ac5e8a0d..74cce4e7fce74237292e0b34bec2b02e0e945ca7 100644 (file)
@@ -10,6 +10,7 @@ enum parse_opt_type {
        /* options with no arguments */
        OPTION_BIT,
        OPTION_NEGBIT,
+       OPTION_BITOP,
        OPTION_COUNTUP,
        OPTION_SET_INT,
        OPTION_CMDMODE,
@@ -27,7 +28,8 @@ 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,
+       PARSE_OPT_ONE_SHOT = 32
 };
 
 enum parse_opt_option_flags {
@@ -47,8 +49,9 @@ struct option;
 typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
 
 struct parse_opt_ctx_t;
-typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
-                               const struct option *opt, int unset);
+typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
+                                             const struct option *opt,
+                                             const char *arg, int unset);
 
 /*
  * `type`::
@@ -98,13 +101,16 @@ typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
  *                      the option takes optional argument.
  *
  * `callback`::
- *   pointer to the callback to use for OPTION_CALLBACK or
- *   OPTION_LOWLEVEL_CALLBACK.
+ *   pointer to the callback to use for OPTION_CALLBACK
  *
  * `defval`::
  *   default value to fill (*->value) with for PARSE_OPT_OPTARG.
  *   OPTION_{BIT,SET_INT} store the {mask,integer} to put in the value when met.
  *   CALLBACKS can use it like they want.
+ *
+ * `ll_callback`::
+ *   pointer to the callback to use for OPTION_LOWLEVEL_CALLBACK
+ *
  */
 struct option {
        enum parse_opt_type type;
@@ -117,6 +123,8 @@ struct option {
        int flags;
        parse_opt_cb *callback;
        intptr_t defval;
+       parse_opt_ll_cb *ll_callback;
+       intptr_t extra;
 };
 
 #define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \
@@ -126,12 +134,17 @@ struct option {
 #define OPT_SET_INT_F(s, l, v, h, i, f) { OPTION_SET_INT, (s), (l), (v), NULL, \
                                          (h), PARSE_OPT_NOARG | (f), NULL, (i) }
 #define OPT_BOOL_F(s, l, v, h, f)   OPT_SET_INT_F(s, l, v, h, 1, f)
+#define OPT_CALLBACK_F(s, l, v, a, h, f, cb)                   \
+       { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), (cb) }
 
 #define OPT_END()                   { OPTION_END }
 #define OPT_ARGUMENT(l, h)          { OPTION_ARGUMENT, 0, (l), NULL, NULL, \
                                      (h), PARSE_OPT_NOARG}
 #define OPT_GROUP(h)                { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
 #define OPT_BIT(s, l, v, h, b)      OPT_BIT_F(s, l, v, h, b, 0)
+#define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \
+                                           PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, \
+                                           (set), NULL, (clear) }
 #define OPT_NEGBIT(s, l, v, h, b)   { OPTION_NEGBIT, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG, NULL, (b) }
 #define OPT_COUNTUP(s, l, v, h)     OPT_COUNTUP_F(s, l, v, h, 0)
@@ -153,8 +166,7 @@ struct option {
 #define OPT_EXPIRY_DATE(s, l, v, h) \
        { OPTION_CALLBACK, (s), (l), (v), N_("expiry-date"),(h), 0,     \
          parse_opt_expiry_date_cb }
-#define OPT_CALLBACK(s, l, v, a, h, f) \
-       { OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }
+#define OPT_CALLBACK(s, l, v, a, h, f) OPT_CALLBACK_F(s, l, v, a, h, 0, f)
 #define OPT_NUMBER_CALLBACK(v, h, f) \
        { OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
          PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
@@ -169,23 +181,31 @@ struct option {
          N_("no-op (backward compatibility)"),         \
          PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb }
 
-/* parse_options() will filter out the processed options and leave the
- * non-option arguments in argv[]. usagestr strings should be marked
- * for translation with N_().
+/*
+ * parse_options() will filter out the processed options and leave the
+ * non-option arguments in argv[]. argv0 is assumed program name and
+ * skipped.
+ *
+ * usagestr strings should be marked for translation with N_().
+ *
  * Returns the number of arguments left in argv[].
+ *
+ * In one-shot mode, argv0 is not a program name, argv[] is left
+ * untouched and parse_options() returns the number of options
+ * processed.
  */
-extern int parse_options(int argc, const char **argv, const char *prefix,
-                        const struct option *options,
-                        const char * const usagestr[], int flags);
+int parse_options(int argc, const char **argv, const char *prefix,
+                 const struct option *options,
+                 const char * const usagestr[], int flags);
 
-extern NORETURN void usage_with_options(const char * const *usagestr,
-                                       const struct option *options);
+NORETURN void usage_with_options(const char * const *usagestr,
+                                const struct option *options);
 
-extern NORETURN void usage_msg_opt(const char *msg,
-                                  const char * const *usagestr,
-                                  const struct option *options);
+NORETURN void usage_msg_opt(const char *msg,
+                           const char * const *usagestr,
+                           const struct option *options);
 
-extern int optbug(const struct option *opt, const char *reason);
+int optbug(const struct option *opt, const char *reason);
 const char *optname(const struct option *opt, int flags);
 
 /*
@@ -202,14 +222,25 @@ const char *optname(const struct option *opt, int flags);
                BUG("option callback does not expect an argument"); \
 } while (0)
 
+/*
+ * Similar to the assertions above, but checks that "arg" is always non-NULL.
+ * This assertion also implies BUG_ON_OPT_NEG(), letting you declare both
+ * assertions in a single line.
+ */
+#define BUG_ON_OPT_NEG_NOARG(unset, arg) do { \
+       BUG_ON_OPT_NEG(unset); \
+       if(!(arg)) \
+               BUG("option callback expects an argument"); \
+} while(0)
+
 /*----- incremental advanced APIs -----*/
 
-enum {
-       PARSE_OPT_COMPLETE = -2,
-       PARSE_OPT_HELP = -1,
-       PARSE_OPT_DONE,
+enum parse_opt_result {
+       PARSE_OPT_COMPLETE = -3,
+       PARSE_OPT_HELP = -2,
+       PARSE_OPT_ERROR = -1,   /* must be the same as error() */
+       PARSE_OPT_DONE = 0,     /* fixed so that "return 0" works */
        PARSE_OPT_NON_OPTION,
-       PARSE_OPT_ERROR,
        PARSE_OPT_UNKNOWN
 };
 
@@ -227,31 +258,31 @@ struct parse_opt_ctx_t {
        const char *prefix;
 };
 
-extern void parse_options_start(struct parse_opt_ctx_t *ctx,
-                               int argc, const char **argv, const char *prefix,
-                               const struct option *options, int flags);
+void parse_options_start(struct parse_opt_ctx_t *ctx,
+                        int argc, const char **argv, const char *prefix,
+                        const struct option *options, int flags);
 
-extern int parse_options_step(struct parse_opt_ctx_t *ctx,
-                             const struct option *options,
-                             const char * const usagestr[]);
+int parse_options_step(struct parse_opt_ctx_t *ctx,
+                      const struct option *options,
+                      const char * const usagestr[]);
 
-extern int parse_options_end(struct parse_opt_ctx_t *ctx);
+int parse_options_end(struct parse_opt_ctx_t *ctx);
 
-extern struct option *parse_options_concat(struct option *a, struct option *b);
+struct option *parse_options_concat(struct option *a, struct option *b);
 
 /*----- some often used options -----*/
-extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
-extern int parse_opt_expiry_date_cb(const struct option *, const char *, int);
-extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
-extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
-extern int parse_opt_object_name(const struct option *, const char *, int);
-extern int parse_opt_commits(const struct option *, const char *, int);
-extern int parse_opt_tertiary(const struct option *, const char *, int);
-extern int parse_opt_string_list(const struct option *, const char *, int);
-extern int parse_opt_noop_cb(const struct option *, const char *, int);
-extern int parse_opt_unknown_cb(const struct option *, const char *, int);
-extern int parse_opt_passthru(const struct option *, const char *, int);
-extern int parse_opt_passthru_argv(const struct option *, const char *, int);
+int parse_opt_abbrev_cb(const struct option *, const char *, int);
+int parse_opt_expiry_date_cb(const struct option *, const char *, int);
+int parse_opt_color_flag_cb(const struct option *, const char *, int);
+int parse_opt_verbosity_cb(const struct option *, const char *, int);
+int parse_opt_object_name(const struct option *, const char *, int);
+int parse_opt_commits(const struct option *, const char *, int);
+int parse_opt_tertiary(const struct option *, const char *, int);
+int parse_opt_string_list(const struct option *, const char *, int);
+int parse_opt_noop_cb(const struct option *, const char *, int);
+int parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx, const struct option *, const char *, int);
+int parse_opt_passthru(const struct option *, const char *, int);
+int parse_opt_passthru_argv(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
diff --git a/path.c b/path.c
index 03ab712839a7878e3d3b5d79c698f4e2bb29c8b1..25e97b8c3f76ce9246d8d985adba9777acd5f43c 100644 (file)
--- a/path.c
+++ b/path.c
@@ -115,10 +115,13 @@ static struct common_dir common_list[] = {
        { 1, 1, 0, "logs" },
        { 1, 1, 1, "logs/HEAD" },
        { 0, 1, 1, "logs/refs/bisect" },
+       { 0, 1, 1, "logs/refs/rewritten" },
+       { 0, 1, 1, "logs/refs/worktree" },
        { 0, 1, 0, "lost-found" },
        { 0, 1, 0, "objects" },
        { 0, 1, 0, "refs" },
        { 0, 1, 1, "refs/bisect" },
+       { 0, 1, 1, "refs/rewritten" },
        { 0, 1, 1, "refs/worktree" },
        { 0, 1, 0, "remotes" },
        { 0, 1, 0, "worktrees" },
index d4b71d3e82b0e54ef4105748fdfdc15b438badfb..ffd722054489e6ea837bb417578afd085cebb467 100644 (file)
@@ -88,13 +88,15 @@ static void packet_trace(const char *buf, unsigned int len, int write)
 void packet_flush(int fd)
 {
        packet_trace("0000", 4, 1);
-       write_or_die(fd, "0000", 4);
+       if (write_in_full(fd, "0000", 4) < 0)
+               die_errno(_("unable to write flush packet"));
 }
 
 void packet_delim(int fd)
 {
        packet_trace("0001", 4, 1);
-       write_or_die(fd, "0001", 4);
+       if (write_in_full(fd, "0001", 4) < 0)
+               die_errno(_("unable to write delim packet"));
 }
 
 int packet_flush_gently(int fd)
@@ -117,7 +119,7 @@ void packet_buf_delim(struct strbuf *buf)
        strbuf_add(buf, "0001", 4);
 }
 
-static void set_packet_header(char *buf, const int size)
+void set_packet_header(char *buf, const int size)
 {
        static char hexchar[] = "0123456789abcdef";
 
index ad9a4a2cd7c332cb1c4d1f26268f1ef45f2d7639..c36cb788ed47f388e9c51fca46cb6ec4e73830ef 100644 (file)
@@ -25,6 +25,7 @@ void packet_delim(int fd);
 void packet_write_fmt(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 void packet_buf_flush(struct strbuf *buf);
 void packet_buf_delim(struct strbuf *buf);
+void set_packet_header(char *buf, int size);
 void packet_write(int fd_out, const char *buf, size_t size);
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);
index 0ab45d10d702373e212fb36e4fa66feb26669ee7..f496f0f1284db4ea7c6a20022311381bd9dc1b64 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -98,6 +98,10 @@ static void setup_commit_formats(void)
                { "fuller",     CMIT_FMT_FULLER,        0,      8 },
                { "full",       CMIT_FMT_FULL,          0,      8 },
                { "oneline",    CMIT_FMT_ONELINE,       1,      0 }
+               /*
+                * Please update $__git_log_pretty_formats in
+                * git-completion.bash when you add new formats.
+                */
        };
        commit_formats_len = ARRAY_SIZE(builtin_formats);
        builtin_formats_len = commit_formats_len;
@@ -1057,13 +1061,26 @@ static size_t parse_padding_placeholder(struct strbuf *sb,
        return 0;
 }
 
-static int match_placeholder_arg(const char *to_parse, const char *candidate,
-                                const char **end)
+static int match_placeholder_arg_value(const char *to_parse, const char *candidate,
+                                      const char **end, const char **valuestart,
+                                      size_t *valuelen)
 {
        const char *p;
 
        if (!(skip_prefix(to_parse, candidate, &p)))
                return 0;
+       if (valuestart) {
+               if (*p == '=') {
+                       *valuestart = p + 1;
+                       *valuelen = strcspn(*valuestart, ",)");
+                       p = *valuestart + *valuelen;
+               } else {
+                       if (*p != ',' && *p != ')')
+                               return 0;
+                       *valuestart = NULL;
+                       *valuelen = 0;
+               }
+       }
        if (*p == ',') {
                *end = p + 1;
                return 1;
@@ -1075,6 +1092,47 @@ static int match_placeholder_arg(const char *to_parse, const char *candidate,
        return 0;
 }
 
+static int match_placeholder_bool_arg(const char *to_parse, const char *candidate,
+                                     const char **end, int *val)
+{
+       const char *argval;
+       char *strval;
+       size_t arglen;
+       int v;
+
+       if (!match_placeholder_arg_value(to_parse, candidate, end, &argval, &arglen))
+               return 0;
+
+       if (!argval) {
+               *val = 1;
+               return 1;
+       }
+
+       strval = xstrndup(argval, arglen);
+       v = git_parse_maybe_bool(strval);
+       free(strval);
+
+       if (v == -1)
+               return 0;
+
+       *val = v;
+
+       return 1;
+}
+
+static int format_trailer_match_cb(const struct strbuf *key, void *ud)
+{
+       const struct string_list *list = ud;
+       const struct string_list_item *item;
+
+       for_each_string_list_item (item, list) {
+               if (key->len == (uintptr_t)item->util &&
+                   !strncasecmp(item->string, key->buf, key->len))
+                       return 1;
+       }
+       return 0;
+}
+
 static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                                const char *placeholder,
                                void *context)
@@ -1084,10 +1142,14 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
        const char *msg = c->message;
        struct commit_list *p;
        const char *arg;
-       int ch;
+       size_t res;
        char **slot;
 
        /* these are independent of the commit */
+       res = strbuf_expand_literal_cb(sb, placeholder, NULL);
+       if (res)
+               return res;
+
        switch (placeholder[0]) {
        case 'C':
                if (starts_with(placeholder + 1, "(auto)")) {
@@ -1106,16 +1168,6 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                         */
                        return ret;
                }
-       case 'n':               /* newline */
-               strbuf_addch(sb, '\n');
-               return 1;
-       case 'x':
-               /* %x00 == NUL, %x0a == LF, etc. */
-               ch = hex2chr(placeholder + 1);
-               if (ch < 0)
-                       return 0;
-               strbuf_addch(sb, ch);
-               return 3;
        case 'w':
                if (placeholder[1] == '(') {
                        unsigned long width = 0, indent1 = 0, indent2 = 0;
@@ -1322,24 +1374,53 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 
        if (skip_prefix(placeholder, "(trailers", &arg)) {
                struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
+               struct string_list filter_list = STRING_LIST_INIT_NODUP;
+               struct strbuf sepbuf = STRBUF_INIT;
+               size_t ret = 0;
 
                opts.no_divider = 1;
 
                if (*arg == ':') {
                        arg++;
                        for (;;) {
-                               if (match_placeholder_arg(arg, "only", &arg))
+                               const char *argval;
+                               size_t arglen;
+
+                               if (match_placeholder_arg_value(arg, "key", &arg, &argval, &arglen)) {
+                                       uintptr_t len = arglen;
+
+                                       if (!argval)
+                                               goto trailer_out;
+
+                                       if (len && argval[len - 1] == ':')
+                                               len--;
+                                       string_list_append(&filter_list, argval)->util = (char *)len;
+
+                                       opts.filter = format_trailer_match_cb;
+                                       opts.filter_data = &filter_list;
                                        opts.only_trailers = 1;
-                               else if (match_placeholder_arg(arg, "unfold", &arg))
-                                       opts.unfold = 1;
-                               else
+                               } else if (match_placeholder_arg_value(arg, "separator", &arg, &argval, &arglen)) {
+                                       char *fmt;
+
+                                       strbuf_reset(&sepbuf);
+                                       fmt = xstrndup(argval, arglen);
+                                       strbuf_expand(&sepbuf, fmt, strbuf_expand_literal_cb, NULL);
+                                       free(fmt);
+                                       opts.separator = &sepbuf;
+                               } else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) &&
+                                          !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold) &&
+                                          !match_placeholder_bool_arg(arg, "valueonly", &arg, &opts.value_only))
                                        break;
                        }
                }
                if (*arg == ')') {
                        format_trailers_from_commit(sb, msg + c->subject_off, &opts);
-                       return arg - placeholder + 1;
+                       ret = arg - placeholder + 1;
                }
+       trailer_out:
+               string_list_clear(&filter_list, 0);
+               strbuf_release(&sepbuf);
+               return ret;
        }
 
        return 0;       /* unknown placeholder */
index 6e9b810d2a5e03dc613fe1e58938086f1ed9b684..0d00a91de4c848dff499edb7dbbfda45ffda05fb 100644 (file)
@@ -12,6 +12,7 @@
 #include "packfile.h"
 #include "worktree.h"
 #include "object-store.h"
+#include "pack-bitmap.h"
 
 struct connectivity_progress {
        struct progress *progress;
@@ -158,10 +159,44 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,
                                      FOR_EACH_OBJECT_LOCAL_ONLY);
 }
 
+static void *lookup_object_by_type(struct repository *r,
+                                  const struct object_id *oid,
+                                  enum object_type type)
+{
+       switch (type) {
+       case OBJ_COMMIT:
+               return lookup_commit(r, oid);
+       case OBJ_TREE:
+               return lookup_tree(r, oid);
+       case OBJ_TAG:
+               return lookup_tag(r, oid);
+       case OBJ_BLOB:
+               return lookup_blob(r, oid);
+       default:
+               die("BUG: unknown object type %d", type);
+       }
+}
+
+static int mark_object_seen(const struct object_id *oid,
+                            enum object_type type,
+                            int exclude,
+                            uint32_t name_hash,
+                            struct packed_git *found_pack,
+                            off_t found_offset)
+{
+       struct object *obj = lookup_object_by_type(the_repository, oid, type);
+       if (!obj)
+               die("unable to create object '%s'", oid_to_hex(oid));
+
+       obj->flags |= SEEN;
+       return 0;
+}
+
 void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
                            timestamp_t mark_recent, struct progress *progress)
 {
        struct connectivity_progress cp;
+       struct bitmap_index *bitmap_git;
 
        /*
         * Set up revision parsing, and mark us as being interested
@@ -188,6 +223,13 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
        cp.progress = progress;
        cp.count = 0;
 
+       bitmap_git = prepare_bitmap_walk(revs);
+       if (bitmap_git) {
+               traverse_bitmap_commit_list(bitmap_git, mark_object_seen);
+               free_bitmap_index(bitmap_git);
+               return;
+       }
+
        /*
         * Set up the revision walk - this will move all commits
         * from the pending list to the commit walking list.
index 0e0c93edc9be5a7af5de1097eafe9d860e7d183e..4dc6de1b55b0a6047e49f188b28c431f0167d7ef 100644 (file)
@@ -588,13 +588,19 @@ int remove_index_entry_at(struct index_state *istate, int pos)
  * CE_REMOVE is set in ce_flags.  This is much more effective than
  * calling remove_index_entry_at() for each entry to be removed.
  */
-void remove_marked_cache_entries(struct index_state *istate)
+void remove_marked_cache_entries(struct index_state *istate, int invalidate)
 {
        struct cache_entry **ce_array = istate->cache;
        unsigned int i, j;
 
        for (i = j = 0; i < istate->cache_nr; i++) {
                if (ce_array[i]->ce_flags & CE_REMOVE) {
+                       if (invalidate) {
+                               cache_tree_invalidate_path(istate,
+                                                          ce_array[i]->name);
+                               untracked_cache_remove_from_index(istate,
+                                                                 ce_array[i]->name);
+                       }
                        remove_name_hash(istate, ce_array[i]);
                        save_or_free_index_entry(istate, ce_array[i]);
                }
@@ -2220,6 +2226,16 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
                load_index_extensions(&p);
        }
        munmap((void *)mmap, mmap_size);
+
+       /*
+        * TODO trace2: replace "the_repository" with the actual repo instance
+        * that is associated with the given "istate".
+        */
+       trace2_data_intmax("index", the_repository, "read/version",
+                          istate->version);
+       trace2_data_intmax("index", the_repository, "read/cache_nr",
+                          istate->cache_nr);
+
        return istate->cache_nr;
 
 unmap:
@@ -2251,9 +2267,17 @@ int read_index_from(struct index_state *istate, const char *path,
        if (istate->initialized)
                return istate->cache_nr;
 
+       /*
+        * TODO trace2: replace "the_repository" with the actual repo instance
+        * that is associated with the given "istate".
+        */
+       trace2_region_enter_printf("index", "do_read_index", the_repository,
+                                  "%s", path);
        trace_performance_enter();
        ret = do_read_index(istate, path, 0);
        trace_performance_leave("read cache %s", path);
+       trace2_region_leave_printf("index", "do_read_index", the_repository,
+                                  "%s", path);
 
        split_index = istate->split_index;
        if (!split_index || is_null_oid(&split_index->base_oid)) {
@@ -2269,7 +2293,11 @@ int read_index_from(struct index_state *istate, const char *path,
 
        base_oid_hex = oid_to_hex(&split_index->base_oid);
        base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex);
+       trace2_region_enter_printf("index", "shared/do_read_index",
+                                  the_repository, "%s", base_path);
        ret = do_read_index(split_index->base, base_path, 1);
+       trace2_region_leave_printf("index", "shared/do_read_index",
+                                  the_repository, "%s", base_path);
        if (!oideq(&split_index->base_oid, &split_index->base->oid))
                die(_("broken index, expect %s in %s, got %s"),
                    base_oid_hex, base_path,
@@ -2894,7 +2922,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
                        return -1;
        }
 
-       if (!strip_extensions && istate->split_index) {
+       if (!strip_extensions && istate->split_index &&
+           !is_null_oid(&istate->split_index->base_oid)) {
                struct strbuf sb = STRBUF_INIT;
 
                err = write_link_extension(&sb, istate) < 0 ||
@@ -2976,6 +3005,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
        istate->timestamp.sec = (unsigned int)st.st_mtime;
        istate->timestamp.nsec = ST_MTIME_NSEC(st);
        trace_performance_since(start, "write index, changed mask = %x", istate->cache_changed);
+
+       /*
+        * TODO trace2: replace "the_repository" with the actual repo instance
+        * that is associated with the given "istate".
+        */
+       trace2_data_intmax("index", the_repository, "write/version",
+                          istate->version);
+       trace2_data_intmax("index", the_repository, "write/cache_nr",
+                          istate->cache_nr);
+
        return 0;
 }
 
@@ -2995,7 +3034,18 @@ static int commit_locked_index(struct lock_file *lk)
 static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,
                                 unsigned flags)
 {
-       int ret = do_write_index(istate, lock->tempfile, 0);
+       int ret;
+
+       /*
+        * TODO trace2: replace "the_repository" with the actual repo instance
+        * that is associated with the given "istate".
+        */
+       trace2_region_enter_printf("index", "do_write_index", the_repository,
+                                  "%s", lock->tempfile->filename.buf);
+       ret = do_write_index(istate, lock->tempfile, 0);
+       trace2_region_leave_printf("index", "do_write_index", the_repository,
+                                  "%s", lock->tempfile->filename.buf);
+
        if (ret)
                return ret;
        if (flags & COMMIT_LOCK)
@@ -3080,7 +3130,13 @@ static int write_shared_index(struct index_state *istate,
        int ret;
 
        move_cache_to_base_index(istate);
+
+       trace2_region_enter_printf("index", "shared/do_write_index",
+                                  the_repository, "%s", (*temp)->filename.buf);
        ret = do_write_index(si->base, *temp, 1);
+       trace2_region_enter_printf("index", "shared/do_write_index",
+                                  the_repository, "%s", (*temp)->filename.buf);
+
        if (ret)
                return ret;
        ret = adjust_shared_perm(get_tempfile_path(*temp));
@@ -3189,7 +3245,7 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
        ret = write_split_index(istate, lock, flags);
 
        /* Freshen the shared index only if the split-index was written */
-       if (!ret && !new_shared_index) {
+       if (!ret && !new_shared_index && !is_null_oid(&si->base_oid)) {
                const char *shared_index = git_path("sharedindex.%s",
                                                    oid_to_hex(&si->base_oid));
                freshen_shared_index(shared_index, 1);
index 422a9c9ae3fd2cfd552ce48c5e68ce18919b1eb9..3aca105307863f5342699babfbea2ac3035fdacb 100644 (file)
@@ -485,6 +485,10 @@ static struct {
        { "if", SOURCE_NONE, FIELD_STR, if_atom_parser },
        { "then", SOURCE_NONE },
        { "else", SOURCE_NONE },
+       /*
+        * Please update $__git_ref_fieldlist in git-completion.bash
+        * when you add new atoms
+        */
 };
 
 #define REF_FORMATTING_STATE_INIT  { 0, NULL }
@@ -913,7 +917,7 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_
 }
 
 /* See grab_values */
-static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+static void grab_tag_values(struct atom_value *val, int deref, struct object *obj)
 {
        int i;
        struct tag *tag = (struct tag *) obj;
@@ -935,7 +939,7 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob
 }
 
 /* See grab_values */
-static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+static void grab_commit_values(struct atom_value *val, int deref, struct object *obj)
 {
        int i;
        struct commit *commit = (struct commit *) obj;
@@ -968,7 +972,7 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
        }
 }
 
-static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
+static const char *find_wholine(const char *who, int wholen, const char *buf)
 {
        const char *eol;
        while (*buf) {
@@ -1064,7 +1068,7 @@ static void grab_date(const char *buf, struct atom_value *v, const char *atomnam
 }
 
 /* See grab_values */
-static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+static void grab_person(const char *who, struct atom_value *val, int deref, void *buf)
 {
        int i;
        int wholen = strlen(who);
@@ -1085,7 +1089,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
                    !starts_with(name + wholen, "date"))
                        continue;
                if (!wholine)
-                       wholine = find_wholine(who, wholen, buf, sz);
+                       wholine = find_wholine(who, wholen, buf);
                if (!wholine)
                        return; /* no point looking for it */
                if (name[wholen] == 0)
@@ -1105,7 +1109,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
        if (strcmp(who, "tagger") && strcmp(who, "committer"))
                return; /* "author" for commit object is not wanted */
        if (!wholine)
-               wholine = find_wholine(who, wholen, buf, sz);
+               wholine = find_wholine(who, wholen, buf);
        if (!wholine)
                return;
        for (i = 0; i < used_atom_cnt; i++) {
@@ -1123,7 +1127,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
        }
 }
 
-static void find_subpos(const char *buf, unsigned long sz,
+static void find_subpos(const char *buf,
                        const char **sub, unsigned long *sublen,
                        const char **body, unsigned long *bodylen,
                        unsigned long *nonsiglen,
@@ -1192,7 +1196,7 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size
 }
 
 /* See grab_values */
-static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
 {
        int i;
        const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
@@ -1212,7 +1216,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                    !starts_with(name, "contents"))
                        continue;
                if (!subpos)
-                       find_subpos(buf, sz,
+                       find_subpos(buf,
                                    &subpos, &sublen,
                                    &bodypos, &bodylen, &nonsiglen,
                                    &sigpos, &siglen);
@@ -1265,19 +1269,19 @@ static void fill_missing_values(struct atom_value *val)
  * pointed at by the ref itself; otherwise it is the object the
  * ref (which is a tag) refers to.
  */
-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)
 {
        switch (obj->type) {
        case OBJ_TAG:
-               grab_tag_values(val, deref, obj, buf, sz);
-               grab_sub_body_contents(val, deref, obj, buf, sz);
-               grab_person("tagger", val, deref, obj, buf, sz);
+               grab_tag_values(val, deref, obj);
+               grab_sub_body_contents(val, deref, buf);
+               grab_person("tagger", val, deref, buf);
                break;
        case OBJ_COMMIT:
-               grab_commit_values(val, deref, obj, buf, sz);
-               grab_sub_body_contents(val, deref, obj, buf, sz);
-               grab_person("author", val, deref, obj, buf, sz);
-               grab_person("committer", val, deref, obj, buf, sz);
+               grab_commit_values(val, deref, obj);
+               grab_sub_body_contents(val, deref, buf);
+               grab_person("author", val, deref, buf);
+               grab_person("committer", val, deref, buf);
                break;
        case OBJ_TREE:
                /* grab_tree_values(val, deref, obj, buf, sz); */
@@ -1516,7 +1520,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj
                        return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
                                               oid_to_hex(&oi->oid), ref->refname);
                }
-               grab_values(ref->value, deref, *obj, oi->content, oi->size);
+               grab_values(ref->value, deref, *obj, oi->content);
        }
 
        grab_common_values(ref->value, deref, oi);
index dd8abe918508027ed46c3234bac1d91107c6ca94..5848f32ef8f206f489416cd3cfe52d12cc54ad36 100644 (file)
@@ -214,6 +214,33 @@ static void files_ref_path(struct files_ref_store *refs,
        }
 }
 
+/*
+ * Manually add refs/bisect, refs/rewritten and refs/worktree, which, being
+ * per-worktree, might not appear in the directory listing for
+ * refs/ in the main repo.
+ */
+static void add_per_worktree_entries_to_dir(struct ref_dir *dir, const char *dirname)
+{
+       const char *prefixes[] = { "refs/bisect/", "refs/worktree/", "refs/rewritten/" };
+       int ip;
+
+       if (strcmp(dirname, "refs/"))
+               return;
+
+       for (ip = 0; ip < ARRAY_SIZE(prefixes); ip++) {
+               const char *prefix = prefixes[ip];
+               int prefix_len = strlen(prefix);
+               struct ref_entry *child_entry;
+               int pos;
+
+               pos = search_ref_dir(dir, prefix, prefix_len);
+               if (pos >= 0)
+                       continue;
+               child_entry = create_dir_entry(dir->cache, prefix, prefix_len, 1);
+               add_entry_to_dir(dir, child_entry);
+       }
+}
+
 /*
  * Read the loose references from the namespace dirname into dir
  * (without recursing).  dirname must end with '/'.  dir must be the
@@ -297,28 +324,7 @@ static void loose_fill_ref_dir(struct ref_store *ref_store,
        strbuf_release(&path);
        closedir(d);
 
-       /*
-        * Manually add refs/bisect and refs/worktree, which, being
-        * per-worktree, might not appear in the directory listing for
-        * refs/ in the main repo.
-        */
-       if (!strcmp(dirname, "refs/")) {
-               int pos = search_ref_dir(dir, "refs/bisect/", 12);
-
-               if (pos < 0) {
-                       struct ref_entry *child_entry = create_dir_entry(
-                                       dir->cache, "refs/bisect/", 12, 1);
-                       add_entry_to_dir(dir, child_entry);
-               }
-
-               pos = search_ref_dir(dir, "refs/worktree/", 11);
-
-               if (pos < 0) {
-                       struct ref_entry *child_entry = create_dir_entry(
-                                       dir->cache, "refs/worktree/", 11, 1);
-                       add_entry_to_dir(dir, child_entry);
-               }
-       }
+       add_per_worktree_entries_to_dir(dir, dirname);
 }
 
 static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs)
@@ -2254,8 +2260,7 @@ static int split_head_update(struct ref_update *update,
  * Note that the new update will itself be subject to splitting when
  * the iteration gets to it.
  */
-static int split_symref_update(struct files_ref_store *refs,
-                              struct ref_update *update,
+static int split_symref_update(struct ref_update *update,
                               const char *referent,
                               struct ref_transaction *transaction,
                               struct string_list *affected_refnames,
@@ -2449,7 +2454,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
                         * of processing the split-off update, so we
                         * don't have to do it here.
                         */
-                       ret = split_symref_update(refs, update,
+                       ret = split_symref_update(update,
                                                  referent.buf, transaction,
                                                  affected_refnames, err);
                        if (ret)
index bb7421023ba584d59592124aa6f3ff2028bc5356..5b447949228cbf853f35ff9c3cccd4ea7a0f8afc 100644 (file)
@@ -505,8 +505,6 @@ static void output_refs(struct ref *refs)
 
 struct rpc_state {
        const char *service_name;
-       const char **argv;
-       struct strbuf *stdin_preamble;
        char *service_url;
        char *hdr_content_type;
        char *hdr_accept;
@@ -518,25 +516,121 @@ struct rpc_state {
        int in;
        int out;
        int any_written;
-       struct strbuf result;
        unsigned gzip_request : 1;
        unsigned initial_buffer : 1;
+
+       /*
+        * Whenever a pkt-line is read into buf, append the 4 characters
+        * denoting its length before appending the payload.
+        */
+       unsigned write_line_lengths : 1;
+
+       /*
+        * Used by rpc_out; initialize to 0. This is true if a flush has been
+        * read, but the corresponding line length (if write_line_lengths is
+        * true) and EOF have not been sent to libcurl. Since each flush marks
+        * the end of a request, each flush must be completely sent before any
+        * further reading occurs.
+        */
+       unsigned flush_read_but_not_sent : 1;
 };
 
+/*
+ * Appends the result of reading from rpc->out to the string represented by
+ * rpc->buf and rpc->len if there is enough space. Returns 1 if there was
+ * enough space, 0 otherwise.
+ *
+ * If rpc->write_line_lengths is true, appends the line length as a 4-byte
+ * hexadecimal string before appending the result described above.
+ *
+ * Writes the total number of bytes appended into appended.
+ */
+static int rpc_read_from_out(struct rpc_state *rpc, int options,
+                            size_t *appended,
+                            enum packet_read_status *status) {
+       size_t left;
+       char *buf;
+       int pktlen_raw;
+
+       if (rpc->write_line_lengths) {
+               left = rpc->alloc - rpc->len - 4;
+               buf = rpc->buf + rpc->len + 4;
+       } else {
+               left = rpc->alloc - rpc->len;
+               buf = rpc->buf + rpc->len;
+       }
+
+       if (left < LARGE_PACKET_MAX)
+               return 0;
+
+       *status = packet_read_with_status(rpc->out, NULL, NULL, buf,
+                       left, &pktlen_raw, options);
+       if (*status != PACKET_READ_EOF) {
+               *appended = pktlen_raw + (rpc->write_line_lengths ? 4 : 0);
+               rpc->len += *appended;
+       }
+
+       if (rpc->write_line_lengths) {
+               switch (*status) {
+               case PACKET_READ_EOF:
+                       if (!(options & PACKET_READ_GENTLE_ON_EOF))
+                               die("shouldn't have EOF when not gentle on EOF");
+                       break;
+               case PACKET_READ_NORMAL:
+                       set_packet_header(buf - 4, *appended);
+                       break;
+               case PACKET_READ_DELIM:
+                       memcpy(buf - 4, "0001", 4);
+                       break;
+               case PACKET_READ_FLUSH:
+                       memcpy(buf - 4, "0000", 4);
+                       break;
+               }
+       }
+
+       return 1;
+}
+
 static size_t rpc_out(void *ptr, size_t eltsize,
                size_t nmemb, void *buffer_)
 {
        size_t max = eltsize * nmemb;
        struct rpc_state *rpc = buffer_;
        size_t avail = rpc->len - rpc->pos;
+       enum packet_read_status status;
 
        if (!avail) {
                rpc->initial_buffer = 0;
-               avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0);
-               if (!avail)
-                       return 0;
+               rpc->len = 0;
                rpc->pos = 0;
-               rpc->len = avail;
+               if (!rpc->flush_read_but_not_sent) {
+                       if (!rpc_read_from_out(rpc, 0, &avail, &status))
+                               BUG("The entire rpc->buf should be larger than LARGE_PACKET_MAX");
+                       if (status == PACKET_READ_FLUSH)
+                               rpc->flush_read_but_not_sent = 1;
+               }
+               /*
+                * If flush_read_but_not_sent is true, we have already read one
+                * full request but have not fully sent it + EOF, which is why
+                * we need to refrain from reading.
+                */
+       }
+       if (rpc->flush_read_but_not_sent) {
+               if (!avail) {
+                       /*
+                        * The line length either does not need to be sent at
+                        * all or has already been completely sent. Now we can
+                        * return 0, indicating EOF, meaning that the flush has
+                        * been fully sent.
+                        */
+                       rpc->flush_read_but_not_sent = 0;
+                       return 0;
+               }
+               /*
+                * If avail is non-zerp, the line length for the flush still
+                * hasn't been fully sent. Proceed with sending the line
+                * length.
+                */
        }
 
        if (max < avail)
@@ -664,7 +758,11 @@ static curl_off_t xcurl_off_t(size_t len)
        return (curl_off_t)size;
 }
 
-static int post_rpc(struct rpc_state *rpc)
+/*
+ * If flush_received is true, do not attempt to read any more; just use what's
+ * in rpc->buf.
+ */
+static int post_rpc(struct rpc_state *rpc, int flush_received)
 {
        struct active_request_slot *slot;
        struct curl_slist *headers = http_copy_default_headers();
@@ -679,21 +777,19 @@ static int post_rpc(struct rpc_state *rpc)
         * allocated buffer space we can use HTTP/1.0 and avoid the
         * chunked encoding mess.
         */
-       while (1) {
-               size_t left = rpc->alloc - rpc->len;
-               char *buf = rpc->buf + rpc->len;
-               int n;
-
-               if (left < LARGE_PACKET_MAX) {
-                       large_request = 1;
-                       use_gzip = 0;
-                       break;
+       if (!flush_received) {
+               while (1) {
+                       size_t n;
+                       enum packet_read_status status;
+
+                       if (!rpc_read_from_out(rpc, 0, &n, &status)) {
+                               large_request = 1;
+                               use_gzip = 0;
+                               break;
+                       }
+                       if (status == PACKET_READ_FLUSH)
+                               break;
                }
-
-               n = packet_read(rpc->out, NULL, NULL, buf, left, 0);
-               if (!n)
-                       break;
-               rpc->len += n;
        }
 
        if (large_request) {
@@ -829,22 +925,22 @@ retry:
        return err;
 }
 
-static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
+static int rpc_service(struct rpc_state *rpc, struct discovery *heads,
+                      const char **client_argv, const struct strbuf *preamble,
+                      struct strbuf *rpc_result)
 {
        const char *svc = rpc->service_name;
        struct strbuf buf = STRBUF_INIT;
-       struct strbuf *preamble = rpc->stdin_preamble;
        struct child_process client = CHILD_PROCESS_INIT;
        int err = 0;
 
        client.in = -1;
        client.out = -1;
        client.git_cmd = 1;
-       client.argv = rpc->argv;
+       client.argv = client_argv;
        if (start_command(&client))
                exit(1);
-       if (preamble)
-               write_or_die(client.in, preamble->buf, preamble->len);
+       write_or_die(client.in, preamble->buf, preamble->len);
        if (heads)
                write_or_die(client.in, heads->buf, heads->len);
 
@@ -852,7 +948,6 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
        rpc->buf = xmalloc(rpc->alloc);
        rpc->in = client.in;
        rpc->out = client.out;
-       strbuf_init(&rpc->result, 0);
 
        strbuf_addf(&buf, "%s%s", url.buf, svc);
        rpc->service_url = strbuf_detach(&buf, NULL);
@@ -874,13 +969,13 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
                        break;
                rpc->pos = 0;
                rpc->len = n;
-               err |= post_rpc(rpc);
+               err |= post_rpc(rpc, 0);
        }
 
        close(client.in);
        client.in = -1;
        if (!err) {
-               strbuf_read(&rpc->result, client.out, 0);
+               strbuf_read(rpc_result, client.out, 0);
        } else {
                char buf[4096];
                for (;;)
@@ -933,6 +1028,7 @@ static int fetch_git(struct discovery *heads,
        struct strbuf preamble = STRBUF_INIT;
        int i, err;
        struct argv_array args = ARGV_ARRAY_INIT;
+       struct strbuf rpc_result = STRBUF_INIT;
 
        argv_array_pushl(&args, "fetch-pack", "--stateless-rpc",
                         "--stdin", "--lock-pack", NULL);
@@ -978,14 +1074,12 @@ static int fetch_git(struct discovery *heads,
 
        memset(&rpc, 0, sizeof(rpc));
        rpc.service_name = "git-upload-pack",
-       rpc.argv = args.argv;
-       rpc.stdin_preamble = &preamble;
        rpc.gzip_request = 1;
 
-       err = rpc_service(&rpc, heads);
-       if (rpc.result.len)
-               write_or_die(1, rpc.result.buf, rpc.result.len);
-       strbuf_release(&rpc.result);
+       err = rpc_service(&rpc, heads, args.argv, &preamble, &rpc_result);
+       if (rpc_result.len)
+               write_or_die(1, rpc_result.buf, rpc_result.len);
+       strbuf_release(&rpc_result);
        strbuf_release(&preamble);
        argv_array_clear(&args);
        return err;
@@ -1080,6 +1174,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
        struct argv_array args;
        struct string_list_item *cas_option;
        struct strbuf preamble = STRBUF_INIT;
+       struct strbuf rpc_result = STRBUF_INIT;
 
        argv_array_init(&args);
        argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status",
@@ -1112,13 +1207,11 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
 
        memset(&rpc, 0, sizeof(rpc));
        rpc.service_name = "git-receive-pack",
-       rpc.argv = args.argv;
-       rpc.stdin_preamble = &preamble;
 
-       err = rpc_service(&rpc, heads);
-       if (rpc.result.len)
-               write_or_die(1, rpc.result.buf, rpc.result.len);
-       strbuf_release(&rpc.result);
+       err = rpc_service(&rpc, heads, args.argv, &preamble, &rpc_result);
+       if (rpc_result.len)
+               write_or_die(1, rpc_result.buf, rpc_result.len);
+       strbuf_release(&rpc_result);
        strbuf_release(&preamble);
        argv_array_clear(&args);
        return err;
@@ -1170,165 +1263,11 @@ static void parse_push(struct strbuf *buf)
        free(specs);
 }
 
-/*
- * Used to represent the state of a connection to an HTTP server when
- * communicating using git's wire-protocol version 2.
- */
-struct proxy_state {
-       char *service_name;
-       char *service_url;
-       struct curl_slist *headers;
-       struct strbuf request_buffer;
-       int in;
-       int out;
-       struct packet_reader reader;
-       size_t pos;
-       int seen_flush;
-};
-
-static void proxy_state_init(struct proxy_state *p, const char *service_name,
-                            enum protocol_version version)
-{
-       struct strbuf buf = STRBUF_INIT;
-
-       memset(p, 0, sizeof(*p));
-       p->service_name = xstrdup(service_name);
-
-       p->in = 0;
-       p->out = 1;
-       strbuf_init(&p->request_buffer, 0);
-
-       strbuf_addf(&buf, "%s%s", url.buf, p->service_name);
-       p->service_url = strbuf_detach(&buf, NULL);
-
-       p->headers = http_copy_default_headers();
-
-       strbuf_addf(&buf, "Content-Type: application/x-%s-request", p->service_name);
-       p->headers = curl_slist_append(p->headers, buf.buf);
-       strbuf_reset(&buf);
-
-       strbuf_addf(&buf, "Accept: application/x-%s-result", p->service_name);
-       p->headers = curl_slist_append(p->headers, buf.buf);
-       strbuf_reset(&buf);
-
-       p->headers = curl_slist_append(p->headers, "Transfer-Encoding: chunked");
-
-       /* Add the Git-Protocol header */
-       if (get_protocol_http_header(version, &buf))
-               p->headers = curl_slist_append(p->headers, buf.buf);
-
-       packet_reader_init(&p->reader, p->in, NULL, 0,
-                          PACKET_READ_GENTLE_ON_EOF |
-                          PACKET_READ_DIE_ON_ERR_PACKET);
-
-       strbuf_release(&buf);
-}
-
-static void proxy_state_clear(struct proxy_state *p)
-{
-       free(p->service_name);
-       free(p->service_url);
-       curl_slist_free_all(p->headers);
-       strbuf_release(&p->request_buffer);
-}
-
-/*
- * CURLOPT_READFUNCTION callback function.
- * Attempts to copy over a single packet-line at a time into the
- * curl provided buffer.
- */
-static size_t proxy_in(char *buffer, size_t eltsize,
-                      size_t nmemb, void *userdata)
-{
-       size_t max;
-       struct proxy_state *p = userdata;
-       size_t avail = p->request_buffer.len - p->pos;
-
-
-       if (eltsize != 1)
-               BUG("curl read callback called with size = %"PRIuMAX" != 1",
-                   (uintmax_t)eltsize);
-       max = nmemb;
-
-       if (!avail) {
-               if (p->seen_flush) {
-                       p->seen_flush = 0;
-                       return 0;
-               }
-
-               strbuf_reset(&p->request_buffer);
-               switch (packet_reader_read(&p->reader)) {
-               case PACKET_READ_EOF:
-                       die("unexpected EOF when reading from parent process");
-               case PACKET_READ_NORMAL:
-                       packet_buf_write_len(&p->request_buffer, p->reader.line,
-                                            p->reader.pktlen);
-                       break;
-               case PACKET_READ_DELIM:
-                       packet_buf_delim(&p->request_buffer);
-                       break;
-               case PACKET_READ_FLUSH:
-                       packet_buf_flush(&p->request_buffer);
-                       p->seen_flush = 1;
-                       break;
-               }
-               p->pos = 0;
-               avail = p->request_buffer.len;
-       }
-
-       if (max < avail)
-               avail = max;
-       memcpy(buffer, p->request_buffer.buf + p->pos, avail);
-       p->pos += avail;
-       return avail;
-}
-
-static size_t proxy_out(char *buffer, size_t eltsize,
-                       size_t nmemb, void *userdata)
-{
-       size_t size;
-       struct proxy_state *p = userdata;
-
-       if (eltsize != 1)
-               BUG("curl read callback called with size = %"PRIuMAX" != 1",
-                   (uintmax_t)eltsize);
-       size = nmemb;
-
-       write_or_die(p->out, buffer, size);
-       return size;
-}
-
-/* Issues a request to the HTTP server configured in `p` */
-static int proxy_request(struct proxy_state *p)
-{
-       struct active_request_slot *slot;
-
-       slot = get_active_slot();
-
-       curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
-       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
-       curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
-       curl_easy_setopt(slot->curl, CURLOPT_URL, p->service_url);
-       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, p->headers);
-
-       /* Setup function to read request from client */
-       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, proxy_in);
-       curl_easy_setopt(slot->curl, CURLOPT_READDATA, p);
-
-       /* Setup function to write server response to client */
-       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, proxy_out);
-       curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, p);
-
-       if (run_slot(slot, NULL) != HTTP_OK)
-               return -1;
-
-       return 0;
-}
-
 static int stateless_connect(const char *service_name)
 {
        struct discovery *discover;
-       struct proxy_state p;
+       struct rpc_state rpc;
+       struct strbuf buf = STRBUF_INIT;
 
        /*
         * Run the info/refs request and see if the server supports protocol
@@ -1348,23 +1287,58 @@ static int stateless_connect(const char *service_name)
                fflush(stdout);
        }
 
-       proxy_state_init(&p, service_name, discover->version);
+       rpc.service_name = service_name;
+       rpc.service_url = xstrfmt("%s%s", url.buf, rpc.service_name);
+       rpc.hdr_content_type = xstrfmt("Content-Type: application/x-%s-request", rpc.service_name);
+       rpc.hdr_accept = xstrfmt("Accept: application/x-%s-result", rpc.service_name);
+       if (get_protocol_http_header(discover->version, &buf)) {
+               rpc.protocol_header = strbuf_detach(&buf, NULL);
+       } else {
+               rpc.protocol_header = NULL;
+               strbuf_release(&buf);
+       }
+       rpc.buf = xmalloc(http_post_buffer);
+       rpc.alloc = http_post_buffer;
+       rpc.len = 0;
+       rpc.pos = 0;
+       rpc.in = 1;
+       rpc.out = 0;
+       rpc.any_written = 0;
+       rpc.gzip_request = 1;
+       rpc.initial_buffer = 0;
+       rpc.write_line_lengths = 1;
+       rpc.flush_read_but_not_sent = 0;
 
        /*
         * Dump the capability listing that we got from the server earlier
         * during the info/refs request.
         */
-       write_or_die(p.out, discover->buf, discover->len);
+       write_or_die(rpc.in, discover->buf, discover->len);
 
-       /* Peek the next packet line.  Until we see EOF keep sending POSTs */
-       while (packet_reader_peek(&p.reader) != PACKET_READ_EOF) {
-               if (proxy_request(&p)) {
+       /* Until we see EOF keep sending POSTs */
+       while (1) {
+               size_t avail;
+               enum packet_read_status status;
+
+               if (!rpc_read_from_out(&rpc, PACKET_READ_GENTLE_ON_EOF, &avail,
+                                      &status))
+                       BUG("The entire rpc->buf should be larger than LARGE_PACKET_MAX");
+               if (status == PACKET_READ_EOF)
+                       break;
+               if (post_rpc(&rpc, status == PACKET_READ_FLUSH))
                        /* We would have an err here */
                        break;
-               }
+               /* Reset the buffer for next request */
+               rpc.len = 0;
        }
 
-       proxy_state_clear(&p);
+       free(rpc.service_url);
+       free(rpc.hdr_content_type);
+       free(rpc.hdr_accept);
+       free(rpc.protocol_header);
+       free(rpc.buf);
+       strbuf_release(&buf);
+
        return 0;
 }
 
@@ -1385,6 +1359,13 @@ int cmd_main(int argc, const char **argv)
        string_list_init(&options.deepen_not, 1);
        string_list_init(&options.push_options, 1);
 
+       /*
+        * Just report "remote-curl" here (folding all the various aliases
+        * ("git-remote-http", "git-remote-https", and etc.) here since they
+        * are all just copies of the same actual executable.
+        */
+       trace2_cmd_name("remote-curl");
+
        remote = remote_get(argv[1]);
 
        if (argc > 2) {
index 65e6f8b8fdfcf89e5c86cbee9590aa555f7b0b47..682c239fe3eda6c16b7be45fc5c920a325d61817 100644 (file)
@@ -126,6 +126,8 @@ out:
 void repo_set_worktree(struct repository *repo, const char *path)
 {
        repo->worktree = real_pathdup(path, 1);
+
+       trace2_def_repo(repo);
 }
 
 static int read_and_verify_repository_format(struct repository_format *format,
@@ -155,7 +157,7 @@ int repo_init(struct repository *repo,
              const char *gitdir,
              const char *worktree)
 {
-       struct repository_format format;
+       struct repository_format format = REPOSITORY_FORMAT_INIT;
        memset(repo, 0, sizeof(*repo));
 
        repo->objects = raw_object_store_new();
@@ -172,6 +174,7 @@ int repo_init(struct repository *repo,
        if (worktree)
                repo_set_worktree(repo, worktree);
 
+       clear_repository_format(&format);
        return 0;
 
 error:
index 8981649d43736ee283fcd23cd3185288252d5357..4fb6a5885f794dea9ff7a1ecf37f27bc34e4d218 100644 (file)
@@ -92,6 +92,9 @@ struct repository {
        /* Repository's current hash algorithm, as serialized on disk. */
        const struct git_hash_algo *hash_algo;
 
+       /* A unique-id for tracing purposes. */
+       int trace2_repo_id;
+
        /* Configurations */
 
        /* Indicate if a repository has a different 'commondir' from 'gitdir' */
index 162d511d4686bc43f2032a57fa95e3601eb3d6b2..cb69a227d5cb9cb58c0173fac666e1ee384ba6ef 100644 (file)
@@ -2151,6 +2151,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->diff = 1;
                revs->dense_combined_merges = 0;
                revs->combine_merges = 1;
+       } else if (!strcmp(arg, "--combined-all-paths")) {
+               revs->diff = 1;
+               revs->combined_all_paths = 1;
        } else if (!strcmp(arg, "--cc")) {
                revs->diff = 1;
                revs->dense_combined_merges = 1;
@@ -2647,6 +2650,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        }
        if (revs->combine_merges)
                revs->ignore_merges = 0;
+       if (revs->combined_all_paths && !revs->combine_merges)
+               die("--combined-all-paths makes no sense without -c or --cc");
+
        revs->diffopt.abbrev = revs->abbrev;
 
        if (revs->line_level_traverse) {
@@ -2683,6 +2689,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        if (revs->first_parent_only && revs->bisect)
                die(_("--first-parent is incompatible with --bisect"));
 
+       if (revs->line_level_traverse &&
+           (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))
+               die(_("-L does not yet support diff formats besides -p and -s"));
+
        if (revs->expand_tabs_in_log < 0)
                revs->expand_tabs_in_log = revs->expand_tabs_in_log_default;
 
index d32d62abc6a7e65ba04a9f25c5024d6f5bbb5f49..4134dc6029c40f39659b39927ba239aed845673b 100644 (file)
@@ -172,6 +172,7 @@ struct rev_info {
                        verbose_header:1,
                        ignore_merges:1,
                        combine_merges:1,
+                       combined_all_paths:1,
                        dense_combined_merges:1,
                        always_show_header:1;
 
index 3db26b7b0e2ab9b6b615387b36a2cd6b64800e9e..3449db319b95d17133abcdb39048730a4c922515 100644 (file)
@@ -219,9 +219,29 @@ static int exists_in_PATH(const char *file)
 
 int sane_execvp(const char *file, char * const argv[])
 {
+#ifndef GIT_WINDOWS_NATIVE
+       /*
+        * execvp() doesn't return, so we all we can do is tell trace2
+        * what we are about to do and let it leave a hint in the log
+        * (unless of course the execvp() fails).
+        *
+        * we skip this for Windows because the compat layer already
+        * has to emulate the execvp() call anyway.
+        */
+       int exec_id = trace2_exec(file, (const char **)argv);
+#endif
+
        if (!execvp(file, argv))
                return 0; /* cannot happen ;-) */
 
+#ifndef GIT_WINDOWS_NATIVE
+       {
+               int ec = errno;
+               trace2_exec_result(exec_id, ec);
+               errno = ec;
+       }
+#endif
+
        /*
         * When a command can't be found because one of the directories
         * listed in $PATH is unsearchable, execvp reports EACCES, but
@@ -712,6 +732,7 @@ fail_pipe:
                cmd->err = fderr[0];
        }
 
+       trace2_child_start(cmd);
        trace_run_command(cmd);
 
        fflush(NULL);
@@ -926,6 +947,8 @@ end_of_spawn:
 #endif
 
        if (cmd->pid < 0) {
+               trace2_child_exit(cmd, -1);
+
                if (need_in)
                        close_pair(fdin);
                else if (cmd->in)
@@ -964,13 +987,16 @@ end_of_spawn:
 int finish_command(struct child_process *cmd)
 {
        int ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);
+       trace2_child_exit(cmd, ret);
        child_process_clear(cmd);
        return ret;
 }
 
 int finish_command_in_signal(struct child_process *cmd)
 {
-       return wait_or_whine(cmd->pid, cmd->argv[0], 1);
+       int ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);
+       trace2_child_exit(cmd, ret);
+       return ret;
 }
 
 
@@ -992,7 +1018,18 @@ int run_command_v_opt(const char **argv, int opt)
        return run_command_v_opt_cd_env(argv, opt, NULL, NULL);
 }
 
+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class)
+{
+       return run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class);
+}
+
 int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)
+{
+       return run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);
+}
+
+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,
+                                const char *const *env, const char *tr2_class)
 {
        struct child_process cmd = CHILD_PROCESS_INIT;
        cmd.argv = argv;
@@ -1004,6 +1041,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const
        cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
        cmd.dir = dir;
        cmd.env = env;
+       cmd.trace2_child_class = tr2_class;
        return run_command(&cmd);
 }
 
@@ -1319,6 +1357,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)
        hook.env = env;
        hook.no_stdin = 1;
        hook.stdout_to_stderr = 1;
+       hook.trace2_hook_name = name;
 
        return run_command(&hook);
 }
@@ -1807,3 +1846,21 @@ int run_processes_parallel(int n,
        pp_cleanup(&pp);
        return 0;
 }
+
+int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,
+                              start_failure_fn start_failure,
+                              task_finished_fn task_finished, void *pp_cb,
+                              const char *tr2_category, const char *tr2_label)
+{
+       int result;
+
+       trace2_region_enter_printf(tr2_category, tr2_label, NULL, "max:%d",
+                                  ((n < 1) ? online_cpus() : n));
+
+       result = run_processes_parallel(n, get_next_task, start_failure,
+                                       task_finished, pp_cb);
+
+       trace2_region_leave(tr2_category, tr2_label, NULL);
+
+       return result;
+}
index 68f5369fc2d432e8b4b504e9625b541056814216..a6950691c042e6234b46c0b8c94f5312da58f734 100644 (file)
@@ -10,6 +10,12 @@ struct child_process {
        struct argv_array args;
        struct argv_array env_array;
        pid_t pid;
+
+       int trace2_child_id;
+       uint64_t trace2_child_us_start;
+       const char *trace2_child_class;
+       const char *trace2_hook_name;
+
        /*
         * Using .in, .out, .err:
         * - Specify 0 for no redirections (child inherits stdin, stdout,
@@ -73,12 +79,14 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);
 #define RUN_USING_SHELL 16
 #define RUN_CLEAN_ON_EXIT 32
 int run_command_v_opt(const char **argv, int opt);
-
+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class);
 /*
  * env (the environment) is to be formatted like environ: "VAR=VALUE".
  * To unset an environment variable use just "VAR".
  */
 int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);
+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,
+                                const char *const *env, const char *tr2_class);
 
 /**
  * Execute the given command, sending "in" to its stdin, and capturing its
@@ -220,5 +228,8 @@ int run_processes_parallel(int n,
                           start_failure_fn,
                           task_finished_fn,
                           void *pp_cb);
+int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,
+                              task_finished_fn, void *pp_cb,
+                              const char *tr2_category, const char *tr2_label);
 
 #endif
index 0db410d59023e4b2fdd21bc342872324e7c2e7b3..95dda23eee450d1d60a70434d254071c6c05146b 100644 (file)
@@ -837,7 +837,7 @@ static const char *read_author_ident(struct strbuf *buf)
        }
 
        strbuf_reset(&out);
-       strbuf_addstr(&out, fmt_ident(name, email, date, 0));
+       strbuf_addstr(&out, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, 0));
        strbuf_swap(buf, &out);
        strbuf_release(&out);
        free(name);
@@ -1103,6 +1103,7 @@ static int run_rewrite_hook(const struct object_id *oldoid,
        proc.argv = argv;
        proc.in = -1;
        proc.stdout_to_stderr = 1;
+       proc.trace2_hook_name = "post-rewrite";
 
        code = start_command(&proc);
        if (code)
@@ -3786,6 +3787,7 @@ cleanup_head_ref:
                                hook.in = open(rebase_path_rewritten_list(),
                                        O_RDONLY);
                                hook.stdout_to_stderr = 1;
+                               hook.trace2_hook_name = "post-rewrite";
                                argv_array_push(&hook.args, post_rewrite_hook);
                                argv_array_push(&hook.args, "rebase");
                                /* we don't care if this hook failed */
@@ -4098,8 +4100,7 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
        int has_footer;
 
        strbuf_addstr(&sob, sign_off_header);
-       strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
-                               getenv("GIT_COMMITTER_EMAIL")));
+       strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));
        strbuf_addch(&sob, '\n');
 
        if (!ignore_footer)
diff --git a/setup.c b/setup.c
index ca9e8a949ed869183f16c57520f19fb361100a02..d0c958c3b2a4a7d4b53970b0e623ed8f6e6a4be4 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -411,6 +411,7 @@ static int read_worktree_config(const char *var, const char *value, void *vdata)
        } else if (strcmp(var, "core.worktree") == 0) {
                if (!value)
                        return config_error_nonbool(var);
+               free(data->work_tree);
                data->work_tree = xstrdup(value);
        }
        return 0;
@@ -476,7 +477,7 @@ static int check_repository_format_gently(const char *gitdir, struct repository_
        }
 
        repository_format_precious_objects = candidate->precious_objects;
-       repository_format_partial_clone = candidate->partial_clone;
+       repository_format_partial_clone = xstrdup_or_null(candidate->partial_clone);
        repository_format_worktree_config = candidate->worktree_config;
        string_list_clear(&candidate->unknown_extensions, 0);
 
@@ -499,27 +500,38 @@ static int check_repository_format_gently(const char *gitdir, struct repository_
                }
                if (candidate->work_tree) {
                        free(git_work_tree_cfg);
-                       git_work_tree_cfg = candidate->work_tree;
+                       git_work_tree_cfg = xstrdup(candidate->work_tree);
                        inside_work_tree = -1;
                }
-       } else {
-               free(candidate->work_tree);
        }
 
        return 0;
 }
 
+static void init_repository_format(struct repository_format *format)
+{
+       const struct repository_format fresh = REPOSITORY_FORMAT_INIT;
+
+       memcpy(format, &fresh, sizeof(fresh));
+}
+
 int read_repository_format(struct repository_format *format, const char *path)
 {
-       memset(format, 0, sizeof(*format));
-       format->version = -1;
-       format->is_bare = -1;
-       format->hash_algo = GIT_HASH_SHA1;
-       string_list_init(&format->unknown_extensions, 1);
+       clear_repository_format(format);
        git_config_from_file(check_repo_format, path, format);
+       if (format->version == -1)
+               clear_repository_format(format);
        return format->version;
 }
 
+void clear_repository_format(struct repository_format *format)
+{
+       string_list_clear(&format->unknown_extensions, 0);
+       free(format->work_tree);
+       free(format->partial_clone);
+       init_repository_format(format);
+}
+
 int verify_repository_format(const struct repository_format *format,
                             struct strbuf *err)
 {
@@ -997,7 +1009,7 @@ int discover_git_directory(struct strbuf *commondir,
        struct strbuf dir = STRBUF_INIT, err = STRBUF_INIT;
        size_t gitdir_offset = gitdir->len, cwd_len;
        size_t commondir_offset = commondir->len;
-       struct repository_format candidate;
+       struct repository_format candidate = REPOSITORY_FORMAT_INIT;
 
        if (strbuf_getcwd(&dir))
                return -1;
@@ -1034,9 +1046,11 @@ int discover_git_directory(struct strbuf *commondir,
                strbuf_release(&err);
                strbuf_setlen(commondir, commondir_offset);
                strbuf_setlen(gitdir, gitdir_offset);
+               clear_repository_format(&candidate);
                return -1;
        }
 
+       clear_repository_format(&candidate);
        return 0;
 }
 
@@ -1045,7 +1059,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
        static struct strbuf cwd = STRBUF_INIT;
        struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
        const char *prefix = NULL;
-       struct repository_format repo_fmt;
+       struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
 
        /*
         * We may have read an incomplete configuration before
@@ -1157,6 +1171,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
 
        strbuf_release(&dir);
        strbuf_release(&gitdir);
+       clear_repository_format(&repo_fmt);
 
        return prefix;
 }
@@ -1214,9 +1229,10 @@ int git_config_perm(const char *var, const char *value)
 
 void check_repository_format(void)
 {
-       struct repository_format repo_fmt;
+       struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
        check_repository_format_gently(get_git_dir(), &repo_fmt, NULL);
        startup_info->have_repository = 1;
+       clear_repository_format(&repo_fmt);
 }
 
 /*
index 09c6b445b9631202c69d2a1804a757148b43beb0..cecfdd36c7e696dce6188aea3398fbe8857d798b 100644 (file)
@@ -14,6 +14,7 @@
  */
 
 #include "git-compat-util.h"
+#include "trace2.h"
 
 /* Substitution of environment variables in shell format strings.
    Copyright (C) 2003-2007 Free Software Foundation, Inc.
@@ -67,6 +68,8 @@ cmd_main (int argc, const char *argv[])
   /* Default values for command line options.  */
   /* unsigned short int show_variables = 0; */
 
+  trace2_cmd_name("sh-i18n--envsubst");
+
   switch (argc)
        {
        case 1:
index 5820412dc5203032d1247575d79c6107a163295a..e6154e4ea9552cfc2b868453f017ba786cd24a2f 100644 (file)
@@ -162,7 +162,7 @@ void merge_base_index(struct index_state *istate)
        ewah_each_bit(si->replace_bitmap, replace_entry, istate);
        ewah_each_bit(si->delete_bitmap, mark_entry_for_delete, istate);
        if (si->nr_deletions)
-               remove_marked_cache_entries(istate);
+               remove_marked_cache_entries(istate, 0);
 
        for (i = si->nr_replacements; i < si->saved_cache_nr; i++) {
                if (!ce_namelen(si->saved_cache[i]))
@@ -440,24 +440,26 @@ void add_split_index(struct index_state *istate)
 void remove_split_index(struct index_state *istate)
 {
        if (istate->split_index) {
-               /*
-                * When removing the split index, we need to move
-                * ownership of the mem_pool associated with the
-                * base index to the main index. There may be cache entries
-                * allocated from the base's memory pool that are shared with
-                * the_index.cache[].
-                */
-               mem_pool_combine(istate->ce_mem_pool, istate->split_index->base->ce_mem_pool);
+               if (istate->split_index->base) {
+                       /*
+                        * When removing the split index, we need to move
+                        * ownership of the mem_pool associated with the
+                        * base index to the main index. There may be cache entries
+                        * allocated from the base's memory pool that are shared with
+                        * the_index.cache[].
+                        */
+                       mem_pool_combine(istate->ce_mem_pool,
+                                        istate->split_index->base->ce_mem_pool);
 
-               /*
-                * The split index no longer owns the mem_pool backing
-                * its cache array. As we are discarding this index,
-                * mark the index as having no cache entries, so it
-                * will not attempt to clean up the cache entries or
-                * validate them.
-                */
-               if (istate->split_index->base)
+                       /*
+                        * The split index no longer owns the mem_pool backing
+                        * its cache array. As we are discarding this index,
+                        * mark the index as having no cache entries, so it
+                        * will not attempt to clean up the cache entries or
+                        * validate them.
+                        */
                        istate->split_index->base->cache_nr = 0;
+               }
 
                /*
                 * We can discard the split index because its
index f6a6cf78b9426abfa73a2053c17326fd645645e7..78eecd29f7e64109203af17cd6e7c2f7e95e3dfc 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -380,6 +380,27 @@ void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
        }
 }
 
+size_t strbuf_expand_literal_cb(struct strbuf *sb,
+                               const char *placeholder,
+                               void *context)
+{
+       int ch;
+
+       switch (placeholder[0]) {
+       case 'n':               /* newline */
+               strbuf_addch(sb, '\n');
+               return 1;
+       case 'x':
+               /* %x00 == NUL, %x0a == LF, etc. */
+               ch = hex2chr(placeholder + 1);
+               if (ch < 0)
+                       return 0;
+               strbuf_addch(sb, ch);
+               return 3;
+       }
+       return 0;
+}
+
 size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder,
                void *context)
 {
index fc40873b65124fc98a2e06150b7c8043f05ed4ab..52e44c9ab83b3409f4f861db41a138123ba433b1 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -320,6 +320,14 @@ void strbuf_expand(struct strbuf *sb,
                   expand_fn_t fn,
                   void *context);
 
+/**
+ * Used as callback for `strbuf_expand` to only expand literals
+ * (i.e. %n and %xNN). The context argument is ignored.
+ */
+size_t strbuf_expand_literal_cb(struct strbuf *sb,
+                               const char *placeholder,
+                               void *context);
+
 /**
  * Used as callback for `strbuf_expand()`, expects an array of
  * struct strbuf_expand_dict_entry as context, i.e. pairs of
index 8d2a1707cfe1a7d0fb6b589f0857a6389a8a3d70..3f4af935557c5ee22b0b1b9b43d2778161210765 100644 (file)
@@ -88,6 +88,7 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co
        process->out = -1;
        process->clean_on_exit = 1;
        process->clean_on_exit_handler = subprocess_exit_handler;
+       process->trace2_child_class = "subprocess";
 
        err = start_command(process);
        if (err) {
index 52702c62d9e3a205c14bdd9f468509f98fe4ced1..66653e86b9f87de01332e3d1405edd218cbf1581 100644 (file)
@@ -281,7 +281,10 @@ static int parse_fetch_recurse(const char *opt, const char *arg,
        default:
                if (!strcmp(arg, "on-demand"))
                        return RECURSE_SUBMODULES_ON_DEMAND;
-
+               /*
+                * Please update $__git_fetch_recurse_submodules in
+                * git-completion.bash when you add new options.
+                */
                if (die_on_error)
                        die("bad %s argument: %s", opt, arg);
                else
@@ -362,6 +365,10 @@ static int parse_push_recurse(const char *opt, const char *arg,
                        return RECURSE_SUBMODULES_CHECK;
                else if (!strcmp(arg, "only"))
                        return RECURSE_SUBMODULES_ONLY;
+               /*
+                * Please update $__git_push_recurse_submodules in
+                * git-completion.bash when you add new modes.
+                */
                else if (die_on_error)
                        die("bad %s argument: %s", opt, arg);
                else
index 934ecfa2943bcb5ee87e7ac431b5332a5f3adcde..21cf50ca15e124edb67b4aa76ae87577816eb1a5 100644 (file)
@@ -432,6 +432,10 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt,
                diffopt->flags.ignore_dirty_submodules = 1;
        else if (strcmp(arg, "none"))
                die("bad --ignore-submodules argument: %s", arg);
+       /*
+        * Please update _git_status() in git-completion.bash when you
+        * add new options
+        */
 }
 
 static int prepare_submodule_summary(struct rev_info *rev, const char *path,
@@ -1609,11 +1613,12 @@ int fetch_populated_submodules(struct repository *r,
 
        calculate_changed_submodule_paths(r, &spf.changed_submodule_names);
        string_list_sort(&spf.changed_submodule_names);
-       run_processes_parallel(max_parallel_jobs,
-                              get_next_submodule,
-                              fetch_start_failure,
-                              fetch_finish,
-                              &spf);
+       run_processes_parallel_tr2(max_parallel_jobs,
+                                  get_next_submodule,
+                                  fetch_start_failure,
+                                  fetch_finish,
+                                  &spf,
+                                  "submodule", "parallel/fetch");
 
        argv_array_clear(&spf.args);
 out:
index 886bbec5bc8e40995817c042874be4860a4b58b8..656288edcea5a23a5893ade5c2d81ee429eb26e8 100644 (file)
--- a/t/README
+++ b/t/README
@@ -196,11 +196,10 @@ appropriately before running "make".
        variable to "1" or "0", respectively.
 
 --stress::
---stress=<N>::
        Run the test script repeatedly in multiple parallel jobs until
        one of them fails.  Useful for reproducing rare failures in
        flaky tests.  The number of parallel jobs is, in order of
-       precedence: <N>, or the value of the GIT_TEST_STRESS_LOAD
+       precedence: the value of the GIT_TEST_STRESS_LOAD
        environment variable, or twice the number of available
        processors (as shown by the 'getconf' utility), or 8.
        Implies `--verbose -x --immediate` to get the most information
@@ -211,10 +210,13 @@ appropriately before running "make".
        '.stress-<nr>' suffix, and the trash directory of the failed
        test job is renamed to end with a '.stress-failed' suffix.
 
+--stress-jobs=<N>::
+       Override the number of parallel jobs. Implies `--stress`.
+
 --stress-limit=<N>::
        When combined with --stress run the test script repeatedly
        this many times in each of the parallel jobs or until one of
-       them fails, whichever comes first.
+       them fails, whichever comes first. Implies `--stress`.
 
 You can also set the GIT_TEST_INSTALLED environment variable to
 the bindir of an existing git installation to test that installation.
@@ -932,6 +934,15 @@ library for your script to use.
    test_oid_init or test_oid_cache.  Providing an unknown key is an
    error.
 
+ - yes [<string>]
+
+   This is often seen in modern UNIX but some platforms lack it, so
+   the test harness overrides the platform implementation with a
+   more limited one.  Use this only when feeding a handful lines of
+   output to the downstream---unlike the real version, it generates
+   only up to 99 lines.
+
+
 Prerequisites
 -------------
 
index 47fee660b86a6e6977735e2c940de79d4330d519..cc88fba05752cbf67c2669c219cc0a65d4a354ee 100644 (file)
@@ -2,6 +2,7 @@
 #include "cache.h"
 #include "parse-options.h"
 #include "string-list.h"
+#include "trace2.h"
 
 static int boolean = 0;
 static int integer = 0;
@@ -153,6 +154,8 @@ int cmd__parse_options(int argc, const char **argv)
        int i;
        int ret = 0;
 
+       trace2_cmd_name("_parse_");
+
        argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);
 
        if (length_cb.called) {
index 99db7409b812898b461e75c8bd14e910f9e10e5c..53c06932c45c17bcb2981c9b1cc081df8135dded 100644 (file)
@@ -1,5 +1,6 @@
 #include "git-compat-util.h"
 #include "test-tool.h"
+#include "trace2.h"
 
 struct test_cmd {
        const char *name;
@@ -51,6 +52,7 @@ static struct test_cmd cmds[] = {
        { "submodule-config", cmd__submodule_config },
        { "submodule-nested-repo-config", cmd__submodule_nested_repo_config },
        { "subprocess", cmd__subprocess },
+       { "trace2", cmd__trace2 },
        { "urlmatch-normalization", cmd__urlmatch_normalization },
        { "xml-encode", cmd__xml_encode },
        { "wildmatch", cmd__wildmatch },
@@ -82,6 +84,8 @@ int cmd_main(int argc, const char **argv)
                if (!strcmp(cmds[i].name, argv[1])) {
                        argv++;
                        argc--;
+                       trace2_cmd_name(cmds[i].name);
+                       trace2_cmd_list_config();
                        return cmds[i].fn(argc, argv);
                }
        }
index 25abed1cf2acc9e84cf1ed78eebe97c7c4d728ca..ffab4d19d701e79cac1283a749e03ae8cf437e2e 100644 (file)
@@ -48,6 +48,7 @@ int cmd__string_list(int argc, const char **argv);
 int cmd__submodule_config(int argc, const char **argv);
 int cmd__submodule_nested_repo_config(int argc, const char **argv);
 int cmd__subprocess(int argc, const char **argv);
+int cmd__trace2(int argc, const char **argv);
 int cmd__urlmatch_normalization(int argc, const char **argv);
 int cmd__xml_encode(int argc, const char **argv);
 int cmd__wildmatch(int argc, const char **argv);
diff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c
new file mode 100644 (file)
index 0000000..197819c
--- /dev/null
@@ -0,0 +1,273 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "argv-array.h"
+#include "run-command.h"
+#include "exec-cmd.h"
+#include "config.h"
+
+typedef int(fn_unit_test)(int argc, const char **argv);
+
+struct unit_test {
+       fn_unit_test *ut_fn;
+       const char *ut_name;
+       const char *ut_usage;
+};
+
+#define MyOk 0
+#define MyError 1
+
+static int get_i(int *p_value, const char *data)
+{
+       char *endptr;
+
+       if (!data || !*data)
+               return MyError;
+
+       *p_value = strtol(data, &endptr, 10);
+       if (*endptr || errno == ERANGE)
+               return MyError;
+
+       return MyOk;
+}
+
+/*
+ * Cause process to exit with the requested value via "return".
+ *
+ * Rely on test-tool.c:cmd_main() to call trace2_cmd_exit()
+ * with our result.
+ *
+ * Test harness can confirm:
+ * [] the process-exit value.
+ * [] the "code" field in the "exit" trace2 event.
+ * [] the "code" field in the "atexit" trace2 event.
+ * [] the "name" field in the "cmd_name" trace2 event.
+ * [] "def_param" events for all of the "interesting" pre-defined
+ * config settings.
+ */
+static int ut_001return(int argc, const char **argv)
+{
+       int rc;
+
+       if (get_i(&rc, argv[0]))
+               die("expect <exit_code>");
+
+       return rc;
+}
+
+/*
+ * Cause the process to exit with the requested value via "exit()".
+ *
+ * Test harness can confirm:
+ * [] the "code" field in the "exit" trace2 event.
+ * [] the "code" field in the "atexit" trace2 event.
+ * [] the "name" field in the "cmd_name" trace2 event.
+ * [] "def_param" events for all of the "interesting" pre-defined
+ * config settings.
+ */
+static int ut_002exit(int argc, const char **argv)
+{
+       int rc;
+
+       if (get_i(&rc, argv[0]))
+               die("expect <exit_code>");
+
+       exit(rc);
+}
+
+/*
+ * Send an "error" event with each value in argv.  Normally, git only issues
+ * a single "error" event immediately before issuing an "exit" event (such
+ * as in die() or BUG()), but multiple "error" events are allowed.
+ *
+ * Test harness can confirm:
+ * [] a trace2 "error" event for each value in argv.
+ * [] the "name" field in the "cmd_name" trace2 event.
+ * [] (optional) the file:line in the "exit" event refers to this function.
+ */
+static int ut_003error(int argc, const char **argv)
+{
+       int k;
+
+       if (!argv[0] || !*argv[0])
+               die("expect <error_message>");
+
+       for (k = 0; k < argc; k++)
+               error("%s", argv[k]);
+
+       return 0;
+}
+
+/*
+ * Run a child process and wait for it to finish and exit with its return code.
+ * test-tool trace2 004child [<child-command-line>]
+ *
+ * For example:
+ * test-tool trace2 004child git version
+ * test-tool trace2 004child test-tool trace2 001return 0
+ * test-tool trace2 004child test-tool trace2 004child test-tool trace2 004child
+ * test-tool trace2 004child git -c alias.xyz=version xyz
+ *
+ * Test harness can confirm:
+ * [] the "name" field in the "cmd_name" trace2 event.
+ * [] that the outer process has a single component SID (or depth "d0" in
+ *    the PERF stream).
+ * [] that "child_start" and "child_exit" events are generated for the child.
+ * [] if the child process is an instrumented executable:
+ *    [] that "version", "start", ..., "exit", and "atexit" events are
+ *       generated by the child process.
+ *    [] that the child process events have a multiple component SID (or
+ *       depth "dN+1" in the PERF stream).
+ * [] that the child exit code is propagated to the parent process "exit"
+ *    and "atexit" events..
+ * [] (optional) that the "t_abs" field in the child process "atexit" event
+ *    is less than the "t_rel" field in the "child_exit" event of the parent
+ *    process.
+ * [] if the child process is like the alias example above,
+ *    [] (optional) the child process attempts to run "git-xyx" as a dashed
+ *       command.
+ *    [] the child process emits an "alias" event with "xyz" => "version"
+ *    [] the child process runs "git version" as a child process.
+ *    [] the child process has a 3 component SID (or depth "d2" in the PERF
+ *       stream).
+ */
+static int ut_004child(int argc, const char **argv)
+{
+       int result;
+
+       /*
+        * Allow empty <child_command_line> so we can do arbitrarily deep
+        * command nesting and let the last one be null.
+        */
+       if (!argc)
+               return 0;
+
+       result = run_command_v_opt(argv, 0);
+       exit(result);
+}
+
+/*
+ * Exec a git command.  This may either create a child process (Windows)
+ * or replace the existing process.
+ * test-tool trace2 005exec <git_command_args>
+ *
+ * For example:
+ * test-tool trace2 005exec version
+ *
+ * Test harness can confirm (on Windows):
+ * [] the "name" field in the "cmd_name" trace2 event.
+ * [] that the outer process has a single component SID (or depth "d0" in
+ *    the PERF stream).
+ * [] that "exec" and "exec_result" events are generated for the child
+ *    process (since the Windows compatibility layer fakes an exec() with
+ *    a CreateProcess(), WaitForSingleObject(), and exit()).
+ * [] that the child process has multiple component SID (or depth "dN+1"
+ *    in the PERF stream).
+ *
+ * Test harness can confirm (on platforms with a real exec() function):
+ * [] TODO talk about process replacement and how it affects SID.
+ */
+static int ut_005exec(int argc, const char **argv)
+{
+       int result;
+
+       if (!argc)
+               return 0;
+
+       result = execv_git_cmd(argv);
+       return result;
+}
+
+static int ut_006data(int argc, const char **argv)
+{
+       const char *usage_error =
+               "expect <cat0> <k0> <v0> [<cat1> <k1> <v1> [...]]";
+
+       if (argc % 3 != 0)
+               die("%s", usage_error);
+
+       while (argc) {
+               if (!argv[0] || !*argv[0] || !argv[1] || !*argv[1] ||
+                   !argv[2] || !*argv[2])
+                       die("%s", usage_error);
+
+               trace2_data_string(argv[0], the_repository, argv[1], argv[2]);
+               argv += 3;
+               argc -= 3;
+       }
+
+       return 0;
+}
+
+/*
+ * Usage:
+ *     test-tool trace2 <ut_name_1> <ut_usage_1>
+ *     test-tool trace2 <ut_name_2> <ut_usage_2>
+ *     ...
+ */
+#define USAGE_PREFIX "test-tool trace2"
+
+/* clang-format off */
+static struct unit_test ut_table[] = {
+       { ut_001return,   "001return", "<exit_code>" },
+       { ut_002exit,     "002exit",   "<exit_code>" },
+       { ut_003error,    "003error",  "<error_message>+" },
+       { ut_004child,    "004child",  "[<child_command_line>]" },
+       { ut_005exec,     "005exec",   "<git_command_args>" },
+       { ut_006data,     "006data",   "[<category> <key> <value>]+" },
+};
+/* clang-format on */
+
+/* clang-format off */
+#define for_each_ut(k, ut_k)                   \
+       for (k = 0, ut_k = &ut_table[k];        \
+            k < ARRAY_SIZE(ut_table);          \
+            k++, ut_k = &ut_table[k])
+/* clang-format on */
+
+static int print_usage(void)
+{
+       int k;
+       struct unit_test *ut_k;
+
+       fprintf(stderr, "usage:\n");
+       for_each_ut (k, ut_k)
+               fprintf(stderr, "\t%s %s %s\n", USAGE_PREFIX, ut_k->ut_name,
+                       ut_k->ut_usage);
+
+       return 129;
+}
+
+/*
+ * Issue various trace2 events for testing.
+ *
+ * We assume that these trace2 routines has already been called:
+ *    [] trace2_initialize()      [common-main.c:main()]
+ *    [] trace2_cmd_start()       [common-main.c:main()]
+ *    [] trace2_cmd_name()        [test-tool.c:cmd_main()]
+ *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]
+ * So that:
+ *    [] the various trace2 streams are open.
+ *    [] the process SID has been created.
+ *    [] the "version" event has been generated.
+ *    [] the "start" event has been generated.
+ *    [] the "cmd_name" event has been generated.
+ *    [] this writes various "def_param" events for interesting config values.
+ *
+ * We further assume that if we return (rather than exit()), trace2_cmd_exit()
+ * will be called by test-tool.c:cmd_main().
+ */
+int cmd__trace2(int argc, const char **argv)
+{
+       int k;
+       struct unit_test *ut_k;
+
+       argc--; /* skip over "trace2" arg */
+       argv++;
+
+       if (argc)
+               for_each_ut (k, ut_k)
+                       if (!strcmp(argv[0], ut_k->ut_name))
+                               return ut_k->ut_fn(argc - 1, argv + 1);
+
+       return print_usage();
+}
diff --git a/t/perf/p5304-prune.sh b/t/perf/p5304-prune.sh
new file mode 100755 (executable)
index 0000000..83baedb
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description='performance tests of prune'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_expect_success 'remove reachable loose objects' '
+       git repack -ad
+'
+
+test_expect_success 'remove unreachable loose objects' '
+       git prune
+'
+
+test_expect_success 'confirm there are no loose objects' '
+       git count-objects | grep ^0
+'
+
+test_perf 'prune with no objects' '
+       git prune
+'
+
+test_expect_success 'repack with bitmaps' '
+       git repack -adb
+'
+
+# We have to create the object in each trial run, since otherwise
+# runs after the first see no object and just skip the traversal entirely!
+test_perf 'prune with bitmaps' '
+       echo "probably not present in repo" | git hash-object -w --stdin &&
+       git prune
+'
+
+test_done
index 42a263cadafe55803bacf5e8bc071b9999618fc4..5e27604b24a7357575c1d51c7cafda9f5cb06471 100755 (executable)
@@ -93,6 +93,7 @@ test_expect_success 'No extra GIT_* on alias scripts' '
                sed -n \
                        -e "/^GIT_PREFIX=/d" \
                        -e "/^GIT_TEXTDOMAINDIR=/d" \
+                       -e "/^GIT_TR2_PARENT/d" \
                        -e "/^GIT_/s/=.*//p" |
                sort
        EOF
diff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh
new file mode 100755 (executable)
index 0000000..03a0aed
--- /dev/null
@@ -0,0 +1,135 @@
+#!/bin/sh
+
+test_description='test trace2 facility (normal target)'
+. ./test-lib.sh
+
+# Add t/helper directory to PATH so that we can use a relative
+# path to run nested instances of test-tool.exe (see 004child).
+# This helps with HEREDOC comparisons later.
+TTDIR="$GIT_BUILD_DIR/t/helper/" && export TTDIR
+PATH="$TTDIR:$PATH" && export PATH
+
+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe
+# Warning: to do the actual diff/comparison, so the HEREDOCs here
+# Warning: only cover our actual calls to test-tool and/or git.
+# Warning: So you may see extra lines in artifact files when
+# Warning: interactively debugging.
+
+# Turn off any inherited trace2 settings for this test.
+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT
+unset GIT_TR2_BRIEF
+unset GIT_TR2_CONFIG_PARAMS
+
+V=$(git version | sed -e 's/^git version //') && export V
+
+# There are multiple trace2 targets: normal, perf, and event.
+# Trace2 events will/can be written to each active target (subject
+# to whatever filtering that target decides to do).
+# This script tests the normal target in isolation.
+#
+# Defer setting GIT_TR2 until the actual command line we want to test
+# because hidden git and test-tool commands run by the test harness
+# can contaminate our output.
+
+# Enable "brief" feature which turns off "<clock> <file>:<line> " prefix.
+GIT_TR2_BRIEF=1 && export GIT_TR2_BRIEF
+
+# Basic tests of the trace2 normal stream.  Since this stream is used
+# primarily with printf-style debugging/tracing, we do limited testing
+# here.
+#
+# We do confirm the following API features:
+# [] the 'version <v>' event
+# [] the 'start <argv>' event
+# [] the 'cmd_name <name>' event
+# [] the 'exit <time> code:<code>' event
+# [] the 'atexit <time> code:<code>' event
+#
+# Fields of the form _FIELD_ are tokens that have been replaced (such
+# as the elapsed time).
+
+# Verb 001return
+#
+# Implicit return from cmd_<verb> function propagates <code>.
+
+test_expect_success 'normal stream, return code 0' '
+       test_when_finished "rm trace.normal actual expect" &&
+       GIT_TR2="$(pwd)/trace.normal" test-tool trace2 001return 0 &&
+       perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
+       cat >expect <<-EOF &&
+               version $V
+               start _EXE_ trace2 001return 0
+               cmd_name trace2 (trace2)
+               exit elapsed:_TIME_ code:0
+               atexit elapsed:_TIME_ code:0
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'normal stream, return code 1' '
+       test_when_finished "rm trace.normal actual expect" &&
+       test_must_fail env GIT_TR2="$(pwd)/trace.normal" test-tool trace2 001return 1 &&
+       perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
+       cat >expect <<-EOF &&
+               version $V
+               start _EXE_ trace2 001return 1
+               cmd_name trace2 (trace2)
+               exit elapsed:_TIME_ code:1
+               atexit elapsed:_TIME_ code:1
+       EOF
+       test_cmp expect actual
+'
+
+# Verb 002exit
+#
+# Explicit exit(code) from within cmd_<verb> propagates <code>.
+
+test_expect_success 'normal stream, exit code 0' '
+       test_when_finished "rm trace.normal actual expect" &&
+       GIT_TR2="$(pwd)/trace.normal" test-tool trace2 002exit 0 &&
+       perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
+       cat >expect <<-EOF &&
+               version $V
+               start _EXE_ trace2 002exit 0
+               cmd_name trace2 (trace2)
+               exit elapsed:_TIME_ code:0
+               atexit elapsed:_TIME_ code:0
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'normal stream, exit code 1' '
+       test_when_finished "rm trace.normal actual expect" &&
+       test_must_fail env GIT_TR2="$(pwd)/trace.normal" test-tool trace2 002exit 1 &&
+       perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
+       cat >expect <<-EOF &&
+               version $V
+               start _EXE_ trace2 002exit 1
+               cmd_name trace2 (trace2)
+               exit elapsed:_TIME_ code:1
+               atexit elapsed:_TIME_ code:1
+       EOF
+       test_cmp expect actual
+'
+
+# Verb 003error
+#
+# To the above, add multiple 'error <msg>' events
+
+test_expect_success 'normal stream, error event' '
+       test_when_finished "rm trace.normal actual expect" &&
+       GIT_TR2="$(pwd)/trace.normal" test-tool trace2 003error "hello world" "this is a test" &&
+       perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
+       cat >expect <<-EOF &&
+               version $V
+               start _EXE_ trace2 003error '\''hello world'\'' '\''this is a test'\''
+               cmd_name trace2 (trace2)
+               error hello world
+               error this is a test
+               exit elapsed:_TIME_ code:0
+               atexit elapsed:_TIME_ code:0
+       EOF
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0210/scrub_normal.perl b/t/t0210/scrub_normal.perl
new file mode 100644 (file)
index 0000000..c65d1a8
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/perl
+#
+# Scrub the variable fields from the normal trace2 output to
+# make testing easier.
+
+use strict;
+use warnings;
+
+my $float = '[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?';
+
+# This code assumes that the trace2 data was written with bare
+# turned on (which omits the "<clock> <file>:<line>" prefix.
+
+while (<>) {
+    # Various messages include an elapsed time in the middle
+    # of the message.  Replace the time with a placeholder to
+    # simplify our HEREDOC in the test script.
+    s/elapsed:$float/elapsed:_TIME_/g;
+
+    my $line = $_;
+
+    # we expect:
+    #    start <argv0> [<argv1> [<argv2> [...]]]
+    #
+    # where argv0 might be a relative or absolute path, with
+    # or without quotes, and platform dependent.  Replace argv0
+    # with a token for HEREDOC matching in the test script.
+
+    if ($line =~ m/^start/) {
+       $line =~ /^start\s+(.*)/;
+       my $argv = $1;
+       $argv =~ m/(\'[^\']*\'|[^ ]+)\s+(.*)/;
+       my $argv_0 = $1;
+       my $argv_rest = $2;
+
+       print "start _EXE_ $argv_rest\n";
+    }
+    elsif ($line =~ m/^cmd_path/) {
+       # Likewise, the 'cmd_path' message breaks out argv[0].
+       #
+       # This line is only emitted when RUNTIME_PREFIX is defined,
+       # so just omit it for testing purposes.
+       # print "cmd_path _EXE_\n";
+    }
+    else {
+       print "$line";
+    }
+}
diff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh
new file mode 100755 (executable)
index 0000000..953e2f7
--- /dev/null
@@ -0,0 +1,153 @@
+#!/bin/sh
+
+test_description='test trace2 facility (perf target)'
+. ./test-lib.sh
+
+# Add t/helper directory to PATH so that we can use a relative
+# path to run nested instances of test-tool.exe (see 004child).
+# This helps with HEREDOC comparisons later.
+TTDIR="$GIT_BUILD_DIR/t/helper/" && export TTDIR
+PATH="$TTDIR:$PATH" && export PATH
+
+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe
+# Warning: to do the actual diff/comparison, so the HEREDOCs here
+# Warning: only cover our actual calls to test-tool and/or git.
+# Warning: So you may see extra lines in artifact files when
+# Warning: interactively debugging.
+
+# Turn off any inherited trace2 settings for this test.
+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT
+unset GIT_TR2_PERF_BRIEF
+unset GIT_TR2_CONFIG_PARAMS
+
+V=$(git version | sed -e 's/^git version //') && export V
+
+# There are multiple trace2 targets: normal, perf, and event.
+# Trace2 events will/can be written to each active target (subject
+# to whatever filtering that target decides to do).
+# Test each target independently.
+#
+# Defer setting GIT_TR2_PERF until the actual command we want to
+# test because hidden git and test-tool commands in the test
+# harness can contaminate our output.
+
+# Enable "brief" feature which turns off the prefix:
+#     "<clock> <file>:<line> | <nr_parents> | "
+GIT_TR2_PERF_BRIEF=1 && export GIT_TR2_PERF_BRIEF
+
+# Repeat some of the t0210 tests using the perf target stream instead of
+# the normal stream.
+#
+# Tokens here of the form _FIELD_ have been replaced in the observed output.
+
+# Verb 001return
+#
+# Implicit return from cmd_<verb> function propagates <code>.
+
+test_expect_success 'perf stream, return code 0' '
+       test_when_finished "rm trace.perf actual expect" &&
+       GIT_TR2_PERF="$(pwd)/trace.perf" test-tool trace2 001return 0 &&
+       perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
+       cat >expect <<-EOF &&
+               d0|main|version|||||$V
+               d0|main|start|||||_EXE_ trace2 001return 0
+               d0|main|cmd_name|||||trace2 (trace2)
+               d0|main|exit||_T_ABS_|||code:0
+               d0|main|atexit||_T_ABS_|||code:0
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'perf stream, return code 1' '
+       test_when_finished "rm trace.perf actual expect" &&
+       test_must_fail env GIT_TR2_PERF="$(pwd)/trace.perf" test-tool trace2 001return 1 &&
+       perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
+       cat >expect <<-EOF &&
+               d0|main|version|||||$V
+               d0|main|start|||||_EXE_ trace2 001return 1
+               d0|main|cmd_name|||||trace2 (trace2)
+               d0|main|exit||_T_ABS_|||code:1
+               d0|main|atexit||_T_ABS_|||code:1
+       EOF
+       test_cmp expect actual
+'
+
+# Verb 003error
+#
+# To the above, add multiple 'error <msg>' events
+
+test_expect_success 'perf stream, error event' '
+       test_when_finished "rm trace.perf actual expect" &&
+       GIT_TR2_PERF="$(pwd)/trace.perf" test-tool trace2 003error "hello world" "this is a test" &&
+       perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
+       cat >expect <<-EOF &&
+               d0|main|version|||||$V
+               d0|main|start|||||_EXE_ trace2 003error '\''hello world'\'' '\''this is a test'\''
+               d0|main|cmd_name|||||trace2 (trace2)
+               d0|main|error|||||hello world
+               d0|main|error|||||this is a test
+               d0|main|exit||_T_ABS_|||code:0
+               d0|main|atexit||_T_ABS_|||code:0
+       EOF
+       test_cmp expect actual
+'
+
+# Verb 004child
+#
+# Test nested spawning of child processes.
+#
+# Conceptually, this looks like:
+#    P1: TT trace2 004child
+#    P2: |--- TT trace2 004child
+#    P3:      |--- TT trace2 001return 0
+#
+# Which should generate events:
+#    P1: version
+#    P1: start
+#    P1: cmd_name
+#    P1: child_start
+#        P2: version
+#        P2: start
+#        P2: cmd_name
+#        P2: child_start
+#            P3: version
+#            P3: start
+#            P3: cmd_name
+#            P3: exit
+#            P3: atexit
+#        P2: child_exit
+#        P2: exit
+#        P2: atexit
+#    P1: child_exit
+#    P1: exit
+#    P1: atexit
+
+test_expect_success 'perf stream, child processes' '
+       test_when_finished "rm trace.perf actual expect" &&
+       GIT_TR2_PERF="$(pwd)/trace.perf" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&
+       perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
+       cat >expect <<-EOF &&
+               d0|main|version|||||$V
+               d0|main|start|||||_EXE_ trace2 004child test-tool trace2 004child test-tool trace2 001return 0
+               d0|main|cmd_name|||||trace2 (trace2)
+               d0|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 004child test-tool trace2 001return 0
+               d1|main|version|||||$V
+               d1|main|start|||||_EXE_ trace2 004child test-tool trace2 001return 0
+               d1|main|cmd_name|||||trace2 (trace2/trace2)
+               d1|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 001return 0
+               d2|main|version|||||$V
+               d2|main|start|||||_EXE_ trace2 001return 0
+               d2|main|cmd_name|||||trace2 (trace2/trace2/trace2)
+               d2|main|exit||_T_ABS_|||code:0
+               d2|main|atexit||_T_ABS_|||code:0
+               d1|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0
+               d1|main|exit||_T_ABS_|||code:0
+               d1|main|atexit||_T_ABS_|||code:0
+               d0|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0
+               d0|main|exit||_T_ABS_|||code:0
+               d0|main|atexit||_T_ABS_|||code:0
+       EOF
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0211/scrub_perf.perl b/t/t0211/scrub_perf.perl
new file mode 100644 (file)
index 0000000..351af78
--- /dev/null
@@ -0,0 +1,76 @@
+#!/usr/bin/perl
+#
+# Scrub the variable fields from the perf trace2 output to
+# make testing easier.
+
+use strict;
+use warnings;
+
+my $qpath = '\'[^\']*\'|[^ ]*';
+
+my $col_depth=0;
+my $col_thread=1;
+my $col_event=2;
+my $col_repo=3;
+my $col_t_abs=4;
+my $col_t_rel=5;
+my $col_category=6;
+my $col_rest=7;
+
+# This code assumes that the trace2 data was written with bare
+# turned on (which omits the "<clock> <file>:<line> | <parents>"
+# prefix.
+
+while (<>) {
+    my @tokens = split /\|/;
+
+    foreach my $col (@tokens) { $col =~ s/^\s+|\s+$//g; }
+
+    if ($tokens[$col_event] =~ m/^start/) {
+       # The 'start' message lists the contents of argv in $col_rest.
+       # On some platforms (Windows), argv[0] is *sometimes* a canonical
+       # absolute path to the EXE rather than the value passed in the
+       # shell script.  Replace it with a placeholder to simplify our
+       # HEREDOC in the test script.
+       my $argv0;
+       my $argvRest;
+       $tokens[$col_rest] =~ s/^($qpath)\W*(.*)/_EXE_ $2/;
+    }
+    elsif ($tokens[$col_event] =~ m/cmd_path/) {
+       # Likewise, the 'cmd_path' message breaks out argv[0].
+       #
+       # This line is only emitted when RUNTIME_PREFIX is defined,
+       # so just omit it for testing purposes.
+       # $tokens[$col_rest] = "_EXE_";
+       goto SKIP_LINE;
+    }
+    elsif ($tokens[$col_event] =~ m/child_exit/) {
+       $tokens[$col_rest] =~ s/ pid:\d* / pid:_PID_ /;
+    }
+    elsif ($tokens[$col_event] =~ m/data/) {
+       if ($tokens[$col_category] =~ m/process/) {
+           # 'data' and 'data_json' events containing 'process'
+           # category data are assumed to be platform-specific
+           # and highly variable.  Just omit them.
+           goto SKIP_LINE;
+       }
+    }
+
+    # t_abs and t_rel are either blank or a float.  Replace the float
+    # with a constant for matching the HEREDOC in the test script.
+    if ($tokens[$col_t_abs] =~ m/\d/) {
+       $tokens[$col_t_abs] = "_T_ABS_";
+    }
+    if ($tokens[$col_t_rel] =~ m/\d/) {
+       $tokens[$col_t_rel] = "_T_REL_";
+    }
+
+    my $out;
+
+    $out = join('|', @tokens);
+    print "$out\n";
+
+  SKIP_LINE:
+}
+
+
diff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh
new file mode 100755 (executable)
index 0000000..028b6c5
--- /dev/null
@@ -0,0 +1,236 @@
+#!/bin/sh
+
+test_description='test trace2 facility'
+. ./test-lib.sh
+
+perl -MJSON::PP -e 0 >/dev/null 2>&1 && test_set_prereq JSON_PP
+
+# Add t/helper directory to PATH so that we can use a relative
+# path to run nested instances of test-tool.exe (see 004child).
+# This helps with HEREDOC comparisons later.
+TTDIR="$GIT_BUILD_DIR/t/helper/" && export TTDIR
+PATH="$TTDIR:$PATH" && export PATH
+
+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe
+# Warning: to do the actual diff/comparison, so the HEREDOCs here
+# Warning: only cover our actual calls to test-tool and/or git.
+# Warning: So you may see extra lines in artifact files when
+# Warning: interactively debugging.
+
+# Turn off any inherited trace2 settings for this test.
+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT
+unset GIT_TR2_BARE
+unset GIT_TR2_CONFIG_PARAMS
+
+V=$(git version | sed -e 's/^git version //') && export V
+
+# There are multiple trace2 targets: normal, perf, and event.
+# Trace2 events will/can be written to each active target (subject
+# to whatever filtering that target decides to do).
+# Test each target independently.
+#
+# Defer setting GIT_TR2_PERF until the actual command we want to
+# test because hidden git and test-tool commands in the test
+# harness can contaminate our output.
+
+# We don't bother repeating the 001return and 002exit tests, since they
+# have coverage in the normal and perf targets.
+
+# Verb 003error
+#
+# To the above, add multiple 'error <msg>' events
+
+test_expect_success JSON_PP 'event stream, error event' '
+       test_when_finished "rm trace.event actual expect" &&
+       GIT_TR2_EVENT="$(pwd)/trace.event" test-tool trace2 003error "hello world" "this is a test" &&
+       perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual &&
+       sed -e "s/^|//" >expect <<-EOF &&
+       |VAR1 = {
+       |  "_SID0_":{
+       |    "argv":[
+       |      "_EXE_",
+       |      "trace2",
+       |      "003error",
+       |      "hello world",
+       |      "this is a test"
+       |    ],
+       |    "errors":[
+       |      "%s",
+       |      "%s"
+       |    ],
+       |    "exit_code":0,
+       |    "hierarchy":"trace2",
+       |    "name":"trace2",
+       |    "version":"$V"
+       |  }
+       |};
+       EOF
+       test_cmp expect actual
+'
+
+# Verb 004child
+#
+# Test nested spawning of child processes.
+#
+# Conceptually, this looks like:
+#    P1: TT trace2 004child
+#    P2: |--- TT trace2 004child
+#    P3:      |--- TT trace2 001return 0
+
+test_expect_success JSON_PP 'event stream, return code 0' '
+       test_when_finished "rm trace.event actual expect" &&
+       GIT_TR2_EVENT="$(pwd)/trace.event" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&
+       perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual &&
+       sed -e "s/^|//" >expect <<-EOF &&
+       |VAR1 = {
+       |  "_SID0_":{
+       |    "argv":[
+       |      "_EXE_",
+       |      "trace2",
+       |      "004child",
+       |      "test-tool",
+       |      "trace2",
+       |      "004child",
+       |      "test-tool",
+       |      "trace2",
+       |      "001return",
+       |      "0"
+       |    ],
+       |    "child":{
+       |      "0":{
+       |        "child_argv":[
+       |          "_EXE_",
+       |          "trace2",
+       |          "004child",
+       |          "test-tool",
+       |          "trace2",
+       |          "001return",
+       |          "0"
+       |        ],
+       |        "child_class":"?",
+       |        "child_code":0,
+       |        "use_shell":0
+       |      }
+       |    },
+       |    "exit_code":0,
+       |    "hierarchy":"trace2",
+       |    "name":"trace2",
+       |    "version":"$V"
+       |  },
+       |  "_SID0_/_SID1_":{
+       |    "argv":[
+       |      "_EXE_",
+       |      "trace2",
+       |      "004child",
+       |      "test-tool",
+       |      "trace2",
+       |      "001return",
+       |      "0"
+       |    ],
+       |    "child":{
+       |      "0":{
+       |        "child_argv":[
+       |          "_EXE_",
+       |          "trace2",
+       |          "001return",
+       |          "0"
+       |        ],
+       |        "child_class":"?",
+       |        "child_code":0,
+       |        "use_shell":0
+       |      }
+       |    },
+       |    "exit_code":0,
+       |    "hierarchy":"trace2/trace2",
+       |    "name":"trace2",
+       |    "version":"$V"
+       |  },
+       |  "_SID0_/_SID1_/_SID2_":{
+       |    "argv":[
+       |      "_EXE_",
+       |      "trace2",
+       |      "001return",
+       |      "0"
+       |    ],
+       |    "exit_code":0,
+       |    "hierarchy":"trace2/trace2/trace2",
+       |    "name":"trace2",
+       |    "version":"$V"
+       |  }
+       |};
+       EOF
+       test_cmp expect actual
+'
+
+# Test listing of all "interesting" config settings.
+
+test_expect_success JSON_PP 'event stream, list config' '
+       test_when_finished "rm trace.event actual expect" &&
+       git config --local t0212.abc 1 &&
+       git config --local t0212.def "hello world" &&
+       GIT_TR2_EVENT="$(pwd)/trace.event" GIT_TR2_CONFIG_PARAMS="t0212.*" test-tool trace2 001return 0 &&
+       perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual &&
+       sed -e "s/^|//" >expect <<-EOF &&
+       |VAR1 = {
+       |  "_SID0_":{
+       |    "argv":[
+       |      "_EXE_",
+       |      "trace2",
+       |      "001return",
+       |      "0"
+       |    ],
+       |    "exit_code":0,
+       |    "hierarchy":"trace2",
+       |    "name":"trace2",
+       |    "params":[
+       |      {
+       |        "param":"t0212.abc",
+       |        "value":"1"
+       |      },
+       |      {
+       |        "param":"t0212.def",
+       |        "value":"hello world"
+       |      }
+       |    ],
+       |    "version":"$V"
+       |  }
+       |};
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success JSON_PP 'basic trace2_data' '
+       test_when_finished "rm trace.event actual expect" &&
+       GIT_TR2_EVENT="$(pwd)/trace.event" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&
+       perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual &&
+       sed -e "s/^|//" >expect <<-EOF &&
+       |VAR1 = {
+       |  "_SID0_":{
+       |    "argv":[
+       |      "_EXE_",
+       |      "trace2",
+       |      "006data",
+       |      "test_category",
+       |      "k1",
+       |      "v1",
+       |      "test_category",
+       |      "k2",
+       |      "v2"
+       |    ],
+       |    "data":{
+       |      "test_category":{
+       |        "k1":"v1",
+       |        "k2":"v2"
+       |      }
+       |    },
+       |    "exit_code":0,
+       |    "hierarchy":"trace2",
+       |    "name":"trace2",
+       |    "version":"$V"
+       |  }
+       |};
+       EOF
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl
new file mode 100644 (file)
index 0000000..a2776ba
--- /dev/null
@@ -0,0 +1,251 @@
+#!/usr/bin/perl
+#
+# Parse event stream and convert individual events into a summary
+# record for the process.
+#
+# Git.exe generates one or more "event" records for each API method,
+# such as "start <argv>" and "exit <code>", during the life of the git
+# process.  Additionally, the input may contain interleaved events
+# from multiple concurrent git processes and/or multiple threads from
+# within a git process.
+#
+# Accumulate events for each process (based on its unique SID) in a
+# dictionary and emit process summary records.
+#
+# Convert some of the variable fields (such as elapsed time) into
+# placeholders (or omit them) to make HEREDOC comparisons easier in
+# the test scripts.
+#
+# We may also omit fields not (currently) useful for testing purposes.
+
+use strict;
+use warnings;
+use JSON::PP;
+use Data::Dumper;
+use Getopt::Long;
+
+# The version of the trace2 event target format that we understand.
+# This is reported in the 'version' event in the 'evt' field.
+# It comes from the GIT_TR2_EVENT_VERSION macro in trace2/tr2_tgt_event.c
+my $evt_version = '1';
+
+my $show_children = 1;
+my $show_exec     = 1;
+my $show_threads  = 1;
+
+# A hack to generate test HEREDOC data for pasting into the test script.
+# Usage:
+#    cd "t/trash directory.t0212-trace2-event"
+#    $TT trace ... >trace.event
+#    VV=$(../../git.exe version | sed -e 's/^git version //')
+#    perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc
+# Then paste heredoc into your new test.
+
+my $gen_heredoc = 0;
+my $gen_version = '';
+
+GetOptions("children!" => \$show_children,
+          "exec!"     => \$show_exec,
+          "threads!"  => \$show_threads,
+          "HEREDOC!"  => \$gen_heredoc,
+          "VERSION=s" => \$gen_version    )
+    or die("Error in command line arguments\n");
+
+
+# SIDs contains timestamps and PIDs of the process and its parents.
+# This makes it difficult to match up in a HEREDOC in the test script.
+# Build a map from actual SIDs to predictable constant values and yet
+# keep the parent/child relationships.  For example:
+# {..., "sid":"1539706952458276-8652", ...}
+# {..., "sid":"1539706952458276-8652/1539706952649493-15452", ...}
+# becomes:
+# {..., "sid":"_SID1_", ...}
+# {..., "sid":"_SID1_/_SID2_", ...}
+my $sid_map;
+my $sid_count = 0;
+
+my $processes;
+
+while (<>) {
+    my $line = decode_json( $_ );
+
+    my $sid = "";
+    my $sid_sep = "";
+
+    my $raw_sid = $line->{'sid'};
+    my @raw_sid_parts = split /\//, $raw_sid;
+    foreach my $raw_sid_k (@raw_sid_parts) {
+       if (!exists $sid_map->{$raw_sid_k}) {
+           $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_';
+           $sid_count++;
+       }
+       $sid = $sid . $sid_sep . $sid_map->{$raw_sid_k};
+       $sid_sep = '/';
+    }
+    
+    my $event = $line->{'event'};
+
+    if ($event eq 'version') {
+       $processes->{$sid}->{'version'} = $line->{'exe'};
+       if ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) {
+           # If we are generating data FOR the test script, replace
+           # the reported git.exe version with a reference to an
+           # environment variable.  When our output is pasted into
+           # the test script, it will then be expanded in future
+           # test runs to the THEN current version of git.exe.
+           # We assume that the test script uses env var $V.
+           $processes->{$sid}->{'version'} = "\$V";
+       }
+    }
+
+    elsif ($event eq 'start') {
+       $processes->{$sid}->{'argv'} = $line->{'argv'};
+       $processes->{$sid}->{'argv'}[0] = "_EXE_";
+    }
+
+    elsif ($event eq 'exit') {
+       $processes->{$sid}->{'exit_code'} = $line->{'code'};
+    }
+
+    elsif ($event eq 'atexit') {
+       $processes->{$sid}->{'exit_code'} = $line->{'code'};
+    }
+
+    elsif ($event eq 'error') {
+       # For HEREDOC purposes, use the error message format string if
+       # available, rather than the formatted message (which probably
+       # has an absolute pathname).
+       if (exists $line->{'fmt'}) {
+           push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} );
+       }
+       elsif (exists $line->{'msg'}) {
+           push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} );
+       }
+    }
+
+    elsif ($event eq 'cmd_path') {
+       ## $processes->{$sid}->{'path'} = $line->{'path'};
+       #
+       # Like in the 'start' event, we need to replace the value of
+       # argv[0] with a token for HEREDOC purposes.  However, the
+       # event is only emitted when RUNTIME_PREFIX is defined, so
+       # just omit it for testing purposes.
+       # $processes->{$sid}->{'path'} = "_EXE_";
+    }
+    
+    elsif ($event eq 'cmd_name') {
+       $processes->{$sid}->{'name'} = $line->{'name'};
+       $processes->{$sid}->{'hierarchy'} = $line->{'hierarchy'};
+    }
+
+    elsif ($event eq 'alias') {
+       $processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'};
+       $processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'};
+    }
+
+    elsif ($event eq 'def_param') {
+       my $kv;
+       $kv->{'param'} = $line->{'param'};
+       $kv->{'value'} = $line->{'value'};
+       push( @{$processes->{$sid}->{'params'}}, $kv );
+    }
+
+    elsif ($event eq 'child_start') {
+       if ($show_children == 1) {
+           $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'};
+           $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'};
+           $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = "_EXE_";
+           $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0;
+       }
+    }
+
+    elsif ($event eq 'child_exit') {
+       if ($show_children == 1) {
+           $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'};
+       }
+    }
+
+    # TODO decide what information we want to test from thread events.
+
+    elsif ($event eq 'thread_start') {
+       if ($show_threads == 1) {
+       }
+    }
+
+    elsif ($event eq 'thread_exit') {
+       if ($show_threads == 1) {
+       }
+    }
+
+    # TODO decide what information we want to test from exec events.
+
+    elsif ($event eq 'exec') {
+       if ($show_exec == 1) {
+       }
+    }
+
+    elsif ($event eq 'exec_result') {
+       if ($show_exec == 1) {
+       }
+    }
+
+    elsif ($event eq 'def_param') {
+       # Accumulate parameter key/value pairs by key rather than in an array
+       # so that we get overwrite (last one wins) effects.
+       $processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'};
+    }
+
+    elsif ($event eq 'def_repo') {
+       # $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'};
+       $processes->{$sid}->{'repos'}->{$line->{'repo'}} = "_WORKTREE_";
+    }
+
+    # A series of potentially nested and threaded region and data events
+    # is fundamentally incompatibile with the type of summary record we
+    # are building in this script.  Since they are intended for
+    # perf-trace-like analysis rather than a result summary, we ignore
+    # most of them here.
+
+    # elsif ($event eq 'region_enter') {
+    # }
+    # elsif ($event eq 'region_leave') {
+    # }
+
+    elsif ($event eq 'data') {
+       my $cat = $line->{'category'};
+       if ($cat eq 'test_category') {
+           
+           my $key = $line->{'key'};
+           my $value = $line->{'value'};
+           $processes->{$sid}->{'data'}->{$cat}->{$key} = $value;
+       }
+    }
+
+    # This trace2 target does not emit 'printf' events.
+    #
+    # elsif ($event eq 'printf') {
+    # }
+}
+
+# Dump the resulting hash into something that we can compare against
+# in the test script.  These options make Dumper output look a little
+# bit like JSON.  Also convert variable references of the form "$VAR*"
+# so that the matching HEREDOC doesn't need to escape it.
+
+$Data::Dumper::Sortkeys = 1;
+$Data::Dumper::Indent = 1;
+$Data::Dumper::Purity = 1;
+$Data::Dumper::Pair = ':';
+
+my $out = Dumper($processes);
+$out =~ s/'/"/g;
+$out =~ s/\$VAR/VAR/g;
+
+# Finally, if we're running this script to generate (manually confirmed)
+# data to add to the test script, guard the indentation.
+
+if ($gen_heredoc == 1) {
+    $out =~ s/^/\t\|/gms;
+}
+
+print $out;
index 4feb65157d87e803a6cd2caf6cec2a945f54b085..bc89371f53436dbf3427f231f560ef1b1f8683f6 100755 (executable)
@@ -127,4 +127,14 @@ test_expect_success 'fetch into corrupted repo with index-pack' '
        )
 '
 
+test_expect_success 'internal tree objects are not "missing"' '
+       git init missing-empty &&
+       (
+               cd missing-empty &&
+               empty_tree=$(git hash-object -t tree /dev/null) &&
+               commit=$(echo foo | git commit-tree $empty_tree) &&
+               git rev-list --objects $commit
+       )
+'
+
 test_done
index b664e51250ae0fcd6b1520a2fa424f34aa6d0479..bb2c7572a3849821f0f6b26f83c1130bb572e04b 100755 (executable)
@@ -76,4 +76,39 @@ test_expect_success 'reflog of worktrees/xx/HEAD' '
        test_cmp expected actual.wt2
 '
 
+test_expect_success 'for-each-ref from main repo' '
+       mkdir fer1 &&
+       git -C fer1 init repo &&
+       test_commit -C fer1/repo initial &&
+       git -C fer1/repo worktree add ../second &&
+       git -C fer1/repo update-ref refs/bisect/main HEAD &&
+       git -C fer1/repo update-ref refs/rewritten/main HEAD &&
+       git -C fer1/repo update-ref refs/worktree/main HEAD &&
+       git -C fer1/repo for-each-ref --format="%(refname)" | grep main >actual &&
+       cat >expected <<-\EOF &&
+       refs/bisect/main
+       refs/rewritten/main
+       refs/worktree/main
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'for-each-ref from linked repo' '
+       mkdir fer2 &&
+       git -C fer2 init repo &&
+       test_commit -C fer2/repo initial &&
+       git -C fer2/repo worktree add ../second &&
+       git -C fer2/second update-ref refs/bisect/second HEAD &&
+       git -C fer2/second update-ref refs/rewritten/second HEAD &&
+       git -C fer2/second update-ref refs/worktree/second HEAD &&
+       git -C fer2/second for-each-ref --format="%(refname)" | grep second >actual &&
+       cat >expected <<-\EOF &&
+       refs/bisect/second
+       refs/heads/second
+       refs/rewritten/second
+       refs/worktree/second
+       EOF
+       test_cmp expected actual
+'
+
 test_done
index c61f97214137f5fcf5a9d6b7dd98ea8162e9916d..49f08d5b9c078412af8ee6343519094521c86c24 100755 (executable)
@@ -740,7 +740,7 @@ test_expect_success 'fsck detects truncated loose object' '
 # for each of type, we have one version which is referenced by another object
 # (and so while unreachable, not dangling), and another variant which really is
 # dangling.
-test_expect_success 'fsck notices dangling objects' '
+test_expect_success 'create dangling-object repository' '
        git init dangling &&
        (
                cd dangling &&
@@ -751,12 +751,17 @@ test_expect_success 'fsck notices dangling objects' '
                commit=$(git commit-tree $tree) &&
                dcommit=$(git commit-tree -p $commit $tree) &&
 
-               cat >expect <<-EOF &&
+               cat >expect <<-EOF
                dangling blob $dblob
                dangling commit $dcommit
                dangling tree $dtree
                EOF
+       )
+'
 
+test_expect_success 'fsck notices dangling objects' '
+       (
+               cd dangling &&
                git fsck >actual &&
                # the output order is non-deterministic, as it comes from a hash
                sort <actual >actual.sorted &&
@@ -764,6 +769,16 @@ test_expect_success 'fsck notices dangling objects' '
        )
 '
 
+test_expect_success 'fsck --connectivity-only notices dangling objects' '
+       (
+               cd dangling &&
+               git fsck --connectivity-only >actual &&
+               # the output order is non-deterministic, as it comes from a hash
+               sort <actual >actual.sorted &&
+               test_i18ncmp expect actual.sorted
+       )
+'
+
 test_expect_success 'fsck $name notices bogus $name' '
        test_must_fail git fsck bogus &&
        test_must_fail git fsck $ZERO_OID
index 4667e1a1909a01e638b13b7264b9041805a438b6..4f2f84f309cfdfed82f1ec60ed036e22885a69aa 100755 (executable)
@@ -472,4 +472,22 @@ test_expect_success 'writing split index with null sha1 does not write cache tre
        test_line_count = 0 cache-tree.out
 '
 
+test_expect_success 'do not refresh null base index' '
+       test_create_repo merge &&
+       (
+               cd merge &&
+               test_commit initial &&
+               git checkout -b side-branch &&
+               test_commit extra &&
+               git checkout master &&
+               git update-index --split-index &&
+               test_commit more &&
+               # must not write a new shareindex, or we wont catch the problem
+               git -c splitIndex.maxPercentChange=100 merge --no-edit side-branch 2>err &&
+               # i.e. do not expect warnings like
+               # could not freshen shared index .../shareindex.00000...
+               test_must_be_empty err
+       )
+'
+
 test_done
diff --git a/t/t2025-checkout-no-overlay.sh b/t/t2025-checkout-no-overlay.sh
new file mode 100755 (executable)
index 0000000..76330cb
--- /dev/null
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+test_description='checkout --no-overlay <tree-ish> -- <pathspec>'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       git commit --allow-empty -m "initial"
+'
+
+test_expect_success 'checkout --no-overlay deletes files not in <tree-ish>' '
+       >file &&
+       mkdir dir &&
+       >dir/file1 &&
+       git add file dir/file1 &&
+       git checkout --no-overlay HEAD -- file &&
+       test_path_is_missing file &&
+       test_path_is_file dir/file1
+'
+
+test_expect_success 'checkout --no-overlay removing last file from directory' '
+       git checkout --no-overlay HEAD -- dir/file1 &&
+       test_path_is_missing dir
+'
+
+test_expect_success 'checkout -p --overlay is disallowed' '
+       test_must_fail git checkout -p --overlay HEAD 2>actual &&
+       test_i18ngrep "fatal: -p and --overlay are mutually exclusive" actual
+'
+
+test_expect_success '--no-overlay --theirs with D/F conflict deletes file' '
+       test_commit file1 file1 &&
+       test_commit file2 file2 &&
+       git rm --cached file1 &&
+       echo 1234 >file1 &&
+       F1=$(git rev-parse HEAD:file1) &&
+       F2=$(git rev-parse HEAD:file2) &&
+       {
+               echo "100644 $F1 1      file1" &&
+               echo "100644 $F2 2      file1"
+       } | git update-index --index-info &&
+       test_path_is_file file1 &&
+       git checkout --theirs --no-overlay -- file1 &&
+       test_path_is_missing file1
+'
+
+test_done
index ee6787614c80edb9b5b24065277dd413350a6fc6..be5514893030313bbe08b9041b9e3d85f7fbde2b 100755 (executable)
@@ -100,6 +100,50 @@ test_expect_success 'git branch -v pattern does not show branch summaries' '
        test_must_fail git branch -v branch*
 '
 
+test_expect_success 'git branch `--show-current` shows current branch' '
+       cat >expect <<-\EOF &&
+       branch-two
+       EOF
+       git checkout branch-two &&
+       git branch --show-current >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git branch `--show-current` is silent when detached HEAD' '
+       git checkout HEAD^0 &&
+       git branch --show-current >actual &&
+       test_must_be_empty actual
+'
+
+test_expect_success 'git branch `--show-current` works properly when tag exists' '
+       cat >expect <<-\EOF &&
+       branch-and-tag-name
+       EOF
+       test_when_finished "
+               git checkout branch-one
+               git branch -D branch-and-tag-name
+       " &&
+       git checkout -b branch-and-tag-name &&
+       test_when_finished "git tag -d branch-and-tag-name" &&
+       git tag branch-and-tag-name &&
+       git branch --show-current >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git branch `--show-current` works properly with worktrees' '
+       cat >expect <<-\EOF &&
+       branch-one
+       branch-two
+       EOF
+       git checkout branch-one &&
+       git worktree add worktree branch-two &&
+       {
+               git branch --show-current &&
+               git -C worktree branch --show-current
+       } >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'git branch shows detached HEAD properly' '
        cat >expect <<EOF &&
 * (HEAD detached at $(git rev-parse --short HEAD^0))
index 3e73f7584ce7d3a23e7e75d0f71bd595f5f822a3..460d0523be6f35e4477a010edd365c1f8fbd431c 100755 (executable)
@@ -59,6 +59,14 @@ test_expect_success 'rebase against master' '
        git rebase master
 '
 
+test_expect_success 'rebase sets ORIG_HEAD to pre-rebase state' '
+       git checkout -b orig-head topic &&
+       pre="$(git rev-parse --verify HEAD)" &&
+       git rebase master &&
+       test_cmp_rev "$pre" ORIG_HEAD &&
+       ! test_cmp_rev "$pre" HEAD
+'
+
 test_expect_success 'rebase, with <onto> and <upstream> specified as :/quuxery' '
        test_when_finished "git branch -D torebase" &&
        git checkout -b torebase my-topic-branch^ &&
index 04e5d42bd3b82a7bf683659f6fdd8e20813df770..85ae7dc1e4618275413a8b498f43d43f3e0ead92 100755 (executable)
@@ -8,91 +8,92 @@ test_description='Test of the various options to git rm.'
 . ./test-lib.sh
 
 # Setup some files to be removed, some with funny characters
-test_expect_success \
-    'Initialize test directory' \
-    "touch -- foo bar baz 'space embedded' -q &&
-     git add -- foo bar baz 'space embedded' -q &&
-     git commit -m 'add normal files'"
+test_expect_success 'Initialize test directory' '
+       touch -- foo bar baz "space embedded" -q &&
+       git add -- foo bar baz "space embedded" -q &&
+       git commit -m "add normal files"
+'
 
-if test_have_prereq !FUNNYNAMES; then
+if test_have_prereq !FUNNYNAMES
+then
        say 'Your filesystem does not allow tabs in filenames.'
 fi
 
-test_expect_success FUNNYNAMES 'add files with funny names' "
-     touch -- 'tab     embedded' 'newline
-embedded' &&
-     git add -- 'tab   embedded' 'newline
-embedded' &&
-     git commit -m 'add files with tabs and newlines'
-"
-
-test_expect_success \
-    'Pre-check that foo exists and is in index before git rm foo' \
-    '[ -f foo ] && git ls-files --error-unmatch foo'
-
-test_expect_success \
-    'Test that git rm foo succeeds' \
-    'git rm --cached foo'
-
-test_expect_success \
-    'Test that git rm --cached foo succeeds if the index matches the file' \
-    'echo content >foo &&
-     git add foo &&
-     git rm --cached foo'
-
-test_expect_success \
-    'Test that git rm --cached foo succeeds if the index matches the file' \
-    'echo content >foo &&
-     git add foo &&
-     git commit -m foo &&
-     echo "other content" >foo &&
-     git rm --cached foo'
-
-test_expect_success \
-    'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' '
-     echo content >foo &&
-     git add foo &&
-     git commit -m foo --allow-empty &&
-     echo "other content" >foo &&
-     git add foo &&
-     echo "yet another content" >foo &&
-     test_must_fail git rm --cached foo
-'
-
-test_expect_success \
-    'Test that git rm --cached -f foo works in case where --cached only did not' \
-    'echo content >foo &&
-     git add foo &&
-     git commit -m foo --allow-empty &&
-     echo "other content" >foo &&
-     git add foo &&
-     echo "yet another content" >foo &&
-     git rm --cached -f foo'
-
-test_expect_success \
-    'Post-check that foo exists but is not in index after git rm foo' \
-    '[ -f foo ] && test_must_fail git ls-files --error-unmatch foo'
-
-test_expect_success \
-    'Pre-check that bar exists and is in index before "git rm bar"' \
-    '[ -f bar ] && git ls-files --error-unmatch bar'
-
-test_expect_success \
-    'Test that "git rm bar" succeeds' \
-    'git rm bar'
-
-test_expect_success \
-    'Post-check that bar does not exist and is not in index after "git rm -f bar"' \
-    '! [ -f bar ] && test_must_fail git ls-files --error-unmatch bar'
-
-test_expect_success \
-    'Test that "git rm -- -q" succeeds (remove a file that looks like an option)' \
-    'git rm -- -q'
-
-test_expect_success FUNNYNAMES \
-    "Test that \"git rm -f\" succeeds with embedded space, tab, or newline characters." \
-    "git rm -f 'space embedded' 'tab   embedded' 'newline
-embedded'"
+test_expect_success FUNNYNAMES 'add files with funny names' '
+       touch -- "tab   embedded" "newline${LF}embedded" &&
+       git add -- "tab embedded" "newline${LF}embedded" &&
+       git commit -m "add files with tabs and newlines"
+'
+
+test_expect_success 'Pre-check that foo exists and is in index before git rm foo' '
+       test_path_is_file foo &&
+       git ls-files --error-unmatch foo
+'
+
+test_expect_success 'Test that git rm foo succeeds' '
+       git rm --cached foo
+'
+
+test_expect_success 'Test that git rm --cached foo succeeds if the index matches the file' '
+       echo content >foo &&
+       git add foo &&
+       git rm --cached foo
+'
+
+test_expect_success 'Test that git rm --cached foo succeeds if the index matches the file' '
+       echo content >foo &&
+       git add foo &&
+       git commit -m foo &&
+       echo "other content" >foo &&
+       git rm --cached foo
+'
+
+test_expect_success 'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' '
+       echo content >foo &&
+       git add foo &&
+       git commit -m foo --allow-empty &&
+       echo "other content" >foo &&
+       git add foo &&
+       echo "yet another content" >foo &&
+       test_must_fail git rm --cached foo
+'
+
+test_expect_success 'Test that git rm --cached -f foo works in case where --cached only did not' '
+       echo content >foo &&
+       git add foo &&
+       git commit -m foo --allow-empty &&
+       echo "other content" >foo &&
+       git add foo &&
+       echo "yet another content" >foo &&
+       git rm --cached -f foo
+'
+
+test_expect_success 'Post-check that foo exists but is not in index after git rm foo' '
+       test_path_is_file foo &&
+       test_must_fail git ls-files --error-unmatch foo
+'
+
+test_expect_success 'Pre-check that bar exists and is in index before "git rm bar"' '
+       test_path_is_file bar &&
+       git ls-files --error-unmatch bar
+'
+
+test_expect_success 'Test that "git rm bar" succeeds' '
+       git rm bar
+'
+
+test_expect_success 'Post-check that bar does not exist and is not in index after "git rm -f bar"' '
+       test_path_is_missing bar &&
+       test_must_fail git ls-files --error-unmatch bar
+'
+
+test_expect_success 'Test that "git rm -- -q" succeeds (remove a file that looks like an option)' '
+       git rm -- -q
+'
+
+test_expect_success FUNNYNAMES 'Test that "git rm -f" succeeds with embedded space, tab, or newline characters.' '
+       git rm -f "space embedded" "tab embedded" "newline${LF}embedded"
+'
 
 test_expect_success SANITY 'Test that "git rm -f" fails if its rm fails' '
        test_when_finished "chmod 775 ." &&
@@ -100,9 +101,9 @@ test_expect_success SANITY 'Test that "git rm -f" fails if its rm fails' '
        test_must_fail git rm -f baz
 '
 
-test_expect_success \
-    'When the rm in "git rm -f" fails, it should not remove the file from the index' \
-    'git ls-files --error-unmatch baz'
+test_expect_success 'When the rm in "git rm -f" fails, it should not remove the file from the index' '
+       git ls-files --error-unmatch baz
+'
 
 test_expect_success 'Remove nonexistent file with --ignore-unmatch' '
        git rm --ignore-unmatch nonexistent
@@ -137,15 +138,15 @@ test_expect_success 'Re-add foo and baz' '
 test_expect_success 'Modify foo -- rm should refuse' '
        echo >>foo &&
        test_must_fail git rm foo baz &&
-       test -f foo &&
-       test -f baz &&
+       test_path_is_file foo &&
+       test_path_is_file baz &&
        git ls-files --error-unmatch foo baz
 '
 
 test_expect_success 'Modified foo -- rm -f should work' '
        git rm -f foo baz &&
-       test ! -f foo &&
-       test ! -f baz &&
+       test_path_is_missing foo &&
+       test_path_is_missing baz &&
        test_must_fail git ls-files --error-unmatch foo &&
        test_must_fail git ls-files --error-unmatch bar
 '
@@ -159,15 +160,15 @@ test_expect_success 'Re-add foo and baz for HEAD tests' '
 
 test_expect_success 'foo is different in index from HEAD -- rm should refuse' '
        test_must_fail git rm foo baz &&
-       test -f foo &&
-       test -f baz &&
+       test_path_is_file foo &&
+       test_path_is_file baz &&
        git ls-files --error-unmatch foo baz
 '
 
 test_expect_success 'but with -f it should work.' '
        git rm -f foo baz &&
-       test ! -f foo &&
-       test ! -f baz &&
+       test_path_is_missing foo &&
+       test_path_is_missing baz &&
        test_must_fail git ls-files --error-unmatch foo &&
        test_must_fail git ls-files --error-unmatch baz
 '
@@ -194,21 +195,21 @@ test_expect_success 'Recursive test setup' '
 
 test_expect_success 'Recursive without -r fails' '
        test_must_fail git rm frotz &&
-       test -d frotz &&
-       test -f frotz/nitfol
+       test_path_is_dir frotz &&
+       test_path_is_file frotz/nitfol
 '
 
 test_expect_success 'Recursive with -r but dirty' '
        echo qfwfq >>frotz/nitfol &&
        test_must_fail git rm -r frotz &&
-       test -d frotz &&
-       test -f frotz/nitfol
+       test_path_is_dir frotz &&
+       test_path_is_file frotz/nitfol
 '
 
 test_expect_success 'Recursive with -r -f' '
        git rm -f -r frotz &&
-       ! test -f frotz/nitfol &&
-       ! test -d frotz
+       test_path_is_missing frotz/nitfol &&
+       test_path_is_missing frotz
 '
 
 test_expect_success 'Remove nonexistent file returns nonzero exit status' '
@@ -217,23 +218,25 @@ test_expect_success 'Remove nonexistent file returns nonzero exit status' '
 
 test_expect_success 'Call "rm" from outside the work tree' '
        mkdir repo &&
-       (cd repo &&
-        git init &&
-        echo something >somefile &&
-        git add somefile &&
-        git commit -m "add a file" &&
-        (cd .. &&
-         git --git-dir=repo/.git --work-tree=repo rm somefile) &&
-       test_must_fail git ls-files --error-unmatch somefile)
+       (
+               cd repo &&
+               git init &&
+               echo something >somefile &&
+               git add somefile &&
+               git commit -m "add a file" &&
+               (
+                       cd .. &&
+                       git --git-dir=repo/.git --work-tree=repo rm somefile
+               ) &&
+               test_must_fail git ls-files --error-unmatch somefile
+       )
 '
 
 test_expect_success 'refresh index before checking if it is up-to-date' '
-
        git reset --hard &&
        test-tool chmtime -86400 frotz/nitfol &&
        git rm frotz/nitfol &&
-       test ! -f frotz/nitfol
-
+       test_path_is_missing frotz/nitfol
 '
 
 test_expect_success 'choking "git rm" should not let it die with cruft' '
@@ -242,8 +245,8 @@ test_expect_success 'choking "git rm" should not let it die with cruft' '
        i=0 &&
        while test $i -lt 12000
        do
-           echo "100644 1234567890123456789012345678901234567890 0     some-file-$i"
-           i=$(( $i + 1 ))
+               echo "100644 1234567890123456789012345678901234567890 0 some-file-$i"
+               i=$(( $i + 1 ))
        done | git update-index --index-info &&
        git rm -n "some-file-*" | : &&
        test_path_is_missing .git/index.lock
@@ -254,7 +257,7 @@ test_expect_success 'rm removes subdirectories recursively' '
        echo content >dir/subdir/subsubdir/file &&
        git add dir/subdir/subsubdir/file &&
        git rm -f dir/subdir/subsubdir/file &&
-       ! test -d dir
+       test_path_is_missing dir
 '
 
 cat >expect <<EOF
@@ -292,7 +295,7 @@ test_expect_success 'rm removes empty submodules from work tree' '
        git add .gitmodules &&
        git commit -m "add submodule" &&
        git rm submod &&
-       test ! -e submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual &&
        test_must_fail git config -f .gitmodules submodule.sub.url &&
@@ -314,7 +317,7 @@ test_expect_success 'rm removes work tree of unmodified submodules' '
        git reset --hard &&
        git submodule update &&
        git rm submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual &&
        test_must_fail git config -f .gitmodules submodule.sub.url &&
@@ -325,7 +328,7 @@ test_expect_success 'rm removes a submodule with a trailing /' '
        git reset --hard &&
        git submodule update &&
        git rm submod/ &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual
 '
@@ -343,12 +346,12 @@ test_expect_success 'rm of a populated submodule with different HEAD fails unles
        git submodule update &&
        git -C submod checkout HEAD^ &&
        test_must_fail git rm submod &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.modified actual &&
        git rm -f submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual &&
        test_must_fail git config -f .gitmodules submodule.sub.url &&
@@ -359,8 +362,8 @@ test_expect_success 'rm --cached leaves work tree of populated submodules and .g
        git reset --hard &&
        git submodule update &&
        git rm --cached submod &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git status -s -uno >actual &&
        test_cmp expect.cached actual &&
        git config -f .gitmodules submodule.sub.url &&
@@ -371,7 +374,7 @@ test_expect_success 'rm --dry-run does not touch the submodule or .gitmodules' '
        git reset --hard &&
        git submodule update &&
        git rm -n submod &&
-       test -f submod/.git &&
+       test_path_is_file submod/.git &&
        git diff-index --exit-code HEAD
 '
 
@@ -381,8 +384,8 @@ test_expect_success 'rm does not complain when no .gitmodules file is found' '
        git rm .gitmodules &&
        git rm submod >actual 2>actual.err &&
        test_must_be_empty actual.err &&
-       ! test -d submod &&
-       ! test -f submod/.git &&
+       test_path_is_missing submod &&
+       test_path_is_missing submod/.git &&
        git status -s -uno >actual &&
        test_cmp expect.both_deleted actual
 '
@@ -392,15 +395,15 @@ test_expect_success 'rm will error out on a modified .gitmodules file unless sta
        git submodule update &&
        git config -f .gitmodules foo.bar true &&
        test_must_fail git rm submod >actual 2>actual.err &&
-       test -s actual.err &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_file_not_empty actual.err &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git diff-files --quiet -- submod &&
        git add .gitmodules &&
        git rm submod >actual 2>actual.err &&
        test_must_be_empty actual.err &&
-       ! test -d submod &&
-       ! test -f submod/.git &&
+       test_path_is_missing submod &&
+       test_path_is_missing submod/.git &&
        git status -s -uno >actual &&
        test_cmp expect actual
 '
@@ -413,8 +416,8 @@ test_expect_success 'rm issues a warning when section is not found in .gitmodule
        echo "warning: Could not find section in .gitmodules where path=submod" >expect.err &&
        git rm submod >actual 2>actual.err &&
        test_i18ncmp expect.err actual.err &&
-       ! test -d submod &&
-       ! test -f submod/.git &&
+       test_path_is_missing submod &&
+       test_path_is_missing submod/.git &&
        git status -s -uno >actual &&
        test_cmp expect actual
 '
@@ -424,12 +427,12 @@ test_expect_success 'rm of a populated submodule with modifications fails unless
        git submodule update &&
        echo X >submod/empty &&
        test_must_fail git rm submod &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.modified_inside actual &&
        git rm -f submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual
 '
@@ -439,12 +442,12 @@ test_expect_success 'rm of a populated submodule with untracked files fails unle
        git submodule update &&
        echo X >submod/untracked &&
        test_must_fail git rm submod &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.modified_untracked actual &&
        git rm -f submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual
 '
@@ -481,7 +484,7 @@ test_expect_success 'rm removes work tree of unmodified conflicted submodule' '
        git submodule update &&
        test_must_fail git merge conflict2 &&
        git rm submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual
 '
@@ -493,12 +496,12 @@ test_expect_success 'rm of a conflicted populated submodule with different HEAD
        git -C submod checkout HEAD^ &&
        test_must_fail git merge conflict2 &&
        test_must_fail git rm submod &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.conflict actual &&
        git rm -f submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual &&
        test_must_fail git config -f .gitmodules submodule.sub.url &&
@@ -512,12 +515,12 @@ test_expect_success 'rm of a conflicted populated submodule with modifications f
        echo X >submod/empty &&
        test_must_fail git merge conflict2 &&
        test_must_fail git rm submod &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.conflict actual &&
        git rm -f submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual &&
        test_must_fail git config -f .gitmodules submodule.sub.url &&
@@ -531,12 +534,12 @@ test_expect_success 'rm of a conflicted populated submodule with untracked files
        echo X >submod/untracked &&
        test_must_fail git merge conflict2 &&
        test_must_fail git rm submod &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.conflict actual &&
        git rm -f submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual
 '
@@ -545,20 +548,21 @@ test_expect_success 'rm of a conflicted populated submodule with a .git director
        git checkout conflict1 &&
        git reset --hard &&
        git submodule update &&
-       (cd submod &&
+       (
+               cd submod &&
                rm .git &&
                cp -R ../.git/modules/sub .git &&
                GIT_WORK_TREE=. git config --unset core.worktree
        ) &&
        test_must_fail git merge conflict2 &&
        test_must_fail git rm submod &&
-       test -d submod &&
-       test -d submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_dir submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.conflict actual &&
        test_must_fail git rm -f submod &&
-       test -d submod &&
-       test -d submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_dir submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.conflict actual &&
        git merge --abort &&
@@ -570,7 +574,7 @@ test_expect_success 'rm of a conflicted unpopulated submodule succeeds' '
        git reset --hard &&
        test_must_fail git merge conflict2 &&
        git rm submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual
 '
@@ -579,17 +583,18 @@ test_expect_success 'rm of a populated submodule with a .git directory migrates
        git checkout -f master &&
        git reset --hard &&
        git submodule update &&
-       (cd submod &&
+       (
+               cd submod &&
                rm .git &&
                cp -R ../.git/modules/sub .git &&
                GIT_WORK_TREE=. git config --unset core.worktree &&
                rm -r ../.git/modules/sub
        ) &&
        git rm submod 2>output.err &&
-       ! test -d submod &&
-       ! test -d submod/.git &&
+       test_path_is_missing submod &&
+       test_path_is_missing submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
-       test -s actual &&
+       test_file_not_empty actual &&
        test_i18ngrep Migrating output.err
 '
 
@@ -600,7 +605,8 @@ EOF
 test_expect_success 'setup subsubmodule' '
        git reset --hard &&
        git submodule update &&
-       (cd submod &&
+       (
+               cd submod &&
                git update-index --add --cacheinfo 160000 $(git rev-parse HEAD) subsubmod &&
                git config -f .gitmodules submodule.sub.url ../. &&
                git config -f .gitmodules submodule.sub.path subsubmod &&
@@ -614,7 +620,7 @@ test_expect_success 'setup subsubmodule' '
 
 test_expect_success 'rm recursively removes work tree of unmodified submodules' '
        git rm submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual
 '
@@ -624,12 +630,12 @@ test_expect_success 'rm of a populated nested submodule with different nested HE
        git submodule update --recursive &&
        git -C submod/subsubmod checkout HEAD^ &&
        test_must_fail git rm submod &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.modified_inside actual &&
        git rm -f submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual
 '
@@ -639,12 +645,12 @@ test_expect_success 'rm of a populated nested submodule with nested modification
        git submodule update --recursive &&
        echo X >submod/subsubmod/empty &&
        test_must_fail git rm submod &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.modified_inside actual &&
        git rm -f submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual
 '
@@ -654,12 +660,12 @@ test_expect_success 'rm of a populated nested submodule with nested untracked fi
        git submodule update --recursive &&
        echo X >submod/subsubmod/untracked &&
        test_must_fail git rm submod &&
-       test -d submod &&
-       test -f submod/.git &&
+       test_path_is_dir submod &&
+       test_path_is_file submod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect.modified_untracked actual &&
        git rm -f submod &&
-       test ! -d submod &&
+       test_path_is_missing submod &&
        git status -s -uno --ignore-submodules=none >actual &&
        test_cmp expect actual
 '
@@ -667,16 +673,17 @@ test_expect_success 'rm of a populated nested submodule with nested untracked fi
 test_expect_success "rm absorbs submodule's nested .git directory" '
        git reset --hard &&
        git submodule update --recursive &&
-       (cd submod/subsubmod &&
+       (
+               cd submod/subsubmod &&
                rm .git &&
                mv ../../.git/modules/sub/modules/sub .git &&
                GIT_WORK_TREE=. git config --unset core.worktree
        ) &&
        git rm submod 2>output.err &&
-       ! test -d submod &&
-       ! test -d submod/subsubmod/.git &&
+       test_path_is_missing submod &&
+       test_path_is_missing submod/subsubmod/.git &&
        git status -s -uno --ignore-submodules=none >actual &&
-       test -s actual &&
+       test_file_not_empty actual &&
        test_i18ngrep Migrating output.err
 '
 
index e2824d343783438f9a3ae78884df7c8b8c63670d..07b49f6d6d9ef93476af2af10fe694d2f971b59d 100755 (executable)
@@ -435,4 +435,92 @@ test_expect_success 'combine diff gets tree sorting right' '
        test_cmp expect actual
 '
 
+test_expect_success 'setup for --combined-all-paths' '
+       git branch side1c &&
+       git branch side2c &&
+       git checkout side1c &&
+       test_seq 1 10 >filename-side1c &&
+       git add filename-side1c &&
+       git commit -m with &&
+       git checkout side2c &&
+       test_seq 1 9 >filename-side2c &&
+       echo ten >>filename-side2c &&
+       git add filename-side2c &&
+       git commit -m iam &&
+       git checkout -b mergery side1c &&
+       git merge --no-commit side2c &&
+       git rm filename-side1c &&
+       echo eleven >>filename-side2c &&
+       git mv filename-side2c filename-merged &&
+       git add filename-merged &&
+       git commit
+'
+
+test_expect_success '--combined-all-paths and --raw' '
+       cat <<-\EOF >expect &&
+       ::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR    filename-side1c filename-side2c filename-merged
+       EOF
+       git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp &&
+       sed 1d <actual.tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--combined-all-paths and --cc' '
+       cat <<-\EOF >expect &&
+       --- a/filename-side1c
+       --- a/filename-side2c
+       +++ b/filename-merged
+       EOF
+       git diff-tree --cc -M --combined-all-paths HEAD >actual.tmp &&
+       grep ^[-+][-+][-+] <actual.tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success FUNNYNAMES 'setup for --combined-all-paths with funny names' '
+       git branch side1d &&
+       git branch side2d &&
+       git checkout side1d &&
+       test_seq 1 10 >$(printf "file\twith\ttabs") &&
+       git add file* &&
+       git commit -m with &&
+       git checkout side2d &&
+       test_seq 1 9 >$(printf "i\tam\ttabbed") &&
+       echo ten >>$(printf "i\tam\ttabbed") &&
+       git add *tabbed &&
+       git commit -m iam &&
+       git checkout -b funny-names-mergery side1d &&
+       git merge --no-commit side2d &&
+       git rm *tabs &&
+       echo eleven >>$(printf "i\tam\ttabbed") &&
+       git mv "$(printf "i\tam\ttabbed")" "$(printf "fickle\tnaming")" &&
+       git add fickle* &&
+       git commit
+'
+
+test_expect_success FUNNYNAMES '--combined-all-paths and --raw and funny names' '
+       cat <<-\EOF >expect &&
+       ::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR    "file\twith\ttabs"      "i\tam\ttabbed" "fickle\tnaming"
+       EOF
+       git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp &&
+       sed 1d <actual.tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success FUNNYNAMES '--combined-all-paths and --raw -and -z and funny names' '
+       printf "aaf8087c3cbd4db8e185a2d074cf27c53cfb75d7\0::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR\0file\twith\ttabs\0i\tam\ttabbed\0fickle\tnaming\0" >expect &&
+       git diff-tree -c -M --raw --combined-all-paths -z HEAD >actual &&
+       test_cmp -a expect actual
+'
+
+test_expect_success FUNNYNAMES '--combined-all-paths and --cc and funny names' '
+       cat <<-\EOF >expect &&
+       --- "a/file\twith\ttabs"
+       --- "a/i\tam\ttabbed"
+       +++ "b/fickle\tnaming"
+       EOF
+       git diff-tree --cc -M --combined-all-paths HEAD >actual.tmp &&
+       grep ^[-+][-+][-+] <actual.tmp >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 6e0dd6f9e5c482631147bb4770498dc4e7a8165f..4331b3118a07f900305fa783914091f73653bb45 100755 (executable)
@@ -137,4 +137,12 @@ test_expect_success 'diff --no-index from repo subdir with absolute paths' '
        test_cmp expect actual
 '
 
+test_expect_success 'diff --no-index allows external diff' '
+       test_expect_code 1 \
+               env GIT_EXTERNAL_DIFF="echo external ;:" \
+               git diff --no-index non/git/a non/git/b >actual &&
+       echo external >expect &&
+       test_cmp expect actual
+'
+
 test_done
index 55b577d919b5d82dd8c8bc81a6a4bc657b190f41..3f7f750cc8d7442458323f9ec77a371db3818320 100755 (executable)
@@ -77,14 +77,12 @@ test_expect_success 'setup: messages' '
 
        printf "Subject: " >subject-prefix &&
 
-       cat - subject-prefix msg-without-scissors-line >msg-with-scissors-line <<-\EOF &&
+       cat - subject-prefix msg-without-scissors-line >msg-with-scissors-line <<-\EOF
        This line should not be included in the commit message with --scissors enabled.
 
         - - >8 - - remove everything above this line - - >8 - -
 
        EOF
-
-       signoff="Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
 '
 
 test_expect_success setup '
index 7df8c3d4ec04ef86e12277e5debfeb8f8f825521..f42a69faa2fde46d958577d10c50d2a6379ebf60 100755 (executable)
@@ -578,6 +578,24 @@ test_expect_success '%(trailers:only) shows only "key: value" trailers' '
        test_cmp expect actual
 '
 
+test_expect_success '%(trailers:only=yes) shows only "key: value" trailers' '
+       git log --no-walk --pretty=format:"%(trailers:only=yes)" >actual &&
+       grep -v patch.description <trailers >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(trailers:only=no) shows all trailers' '
+       git log --no-walk --pretty=format:"%(trailers:only=no)" >actual &&
+       cat trailers >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(trailers:only=no,only=true) shows only "key: value" trailers' '
+       git log --no-walk --pretty=format:"%(trailers:only=yes)" >actual &&
+       grep -v patch.description <trailers >expect &&
+       test_cmp expect actual
+'
+
 test_expect_success '%(trailers:unfold) unfolds trailers' '
        git log --no-walk --pretty="%(trailers:unfold)" >actual &&
        {
@@ -598,6 +616,105 @@ test_expect_success ':only and :unfold work together' '
        test_cmp expect actual
 '
 
+test_expect_success 'pretty format %(trailers:key=foo) shows that trailer' '
+       git log --no-walk --pretty="format:%(trailers:key=Acked-by)" >actual &&
+       echo "Acked-by: A U Thor <author@example.com>" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty format %(trailers:key=foo) is case insensitive' '
+       git log --no-walk --pretty="format:%(trailers:key=AcKed-bY)" >actual &&
+       echo "Acked-by: A U Thor <author@example.com>" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty format %(trailers:key=foo:) trailing colon also works' '
+       git log --no-walk --pretty="format:%(trailers:key=Acked-by:)" >actual &&
+       echo "Acked-by: A U Thor <author@example.com>" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty format %(trailers:key=foo) multiple keys' '
+       git log --no-walk --pretty="format:%(trailers:key=Acked-by:,key=Signed-off-By)" >actual &&
+       grep -v patch.description <trailers >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(trailers:key=nonexistant) becomes empty' '
+       git log --no-walk --pretty="x%(trailers:key=Nacked-by)x" >actual &&
+       echo "xx" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(trailers:key=foo) handles multiple lines even if folded' '
+       git log --no-walk --pretty="format:%(trailers:key=Signed-Off-by)" >actual &&
+       grep -v patch.description <trailers | grep -v Acked-by >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(trailers:key=foo,unfold) properly unfolds' '
+       git log --no-walk --pretty="format:%(trailers:key=Signed-Off-by,unfold)" >actual &&
+       unfold <trailers | grep Signed-off-by >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty format %(trailers:key=foo,only=no) also includes nontrailer lines' '
+       git log --no-walk --pretty="format:%(trailers:key=Acked-by,only=no)" >actual &&
+       {
+               echo "Acked-by: A U Thor <author@example.com>" &&
+               grep patch.description <trailers
+       } >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(trailers:key) without value is error' '
+       git log --no-walk --pretty="tformat:%(trailers:key)" >actual &&
+       echo "%(trailers:key)" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(trailers:key=foo,valueonly) shows only value' '
+       git log --no-walk --pretty="format:%(trailers:key=Acked-by,valueonly)" >actual &&
+       echo "A U Thor <author@example.com>" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty format %(trailers:separator) changes separator' '
+       git log --no-walk --pretty=format:"X%(trailers:separator=%x00,unfold)X" >actual &&
+       printf "XSigned-off-by: A U Thor <author@example.com>\0Acked-by: A U Thor <author@example.com>\0[ v2 updated patch description ]\0Signed-off-by: A U Thor <author@example.com>X" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty format %(trailers) combining separator/key/valueonly' '
+       git commit --allow-empty -F - <<-\EOF &&
+       Important fix
+
+       The fix is explained here
+
+       Closes: #1234
+       EOF
+
+       git commit --allow-empty -F - <<-\EOF &&
+       Another fix
+
+       The fix is explained here
+
+       Closes: #567
+       Closes: #890
+       EOF
+
+       git commit --allow-empty -F - <<-\EOF &&
+       Does not close any tickets
+       EOF
+
+       git log --pretty="%s% (trailers:separator=%x2c%x20,key=Closes,valueonly)" HEAD~3.. >actual &&
+       test_write_lines \
+               "Does not close any tickets" \
+               "Another fix #567, #890" \
+               "Important fix #1234" >expect &&
+       test_cmp expect actual
+'
+
 test_expect_success 'trailer parsing not fooled by --- line' '
        git commit --allow-empty -F - <<-\EOF &&
        this is the subject
index bd5fe4d1483005175d9c2211dee1901c2b132573..1db7bd0f59b46768d6559c37e026cde41821763a 100755 (executable)
@@ -115,4 +115,21 @@ test_expect_success 'range_set_union' '
        git log $(for x in $(test_seq 200); do echo -L $((2*x)),+1:c.c; done)
 '
 
+test_expect_success '-s shows only line-log commits' '
+       git log --format="commit %s" -L1,24:b.c >expect.raw &&
+       grep ^commit expect.raw >expect &&
+       git log --format="commit %s" -L1,24:b.c -s >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '-p shows the default patch output' '
+       git log -L1,24:b.c >expect &&
+       git log -L1,24:b.c -p >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--raw is forbidden' '
+       test_must_fail git log -L1,24:b.c --raw
+'
+
 test_done
index 270da21ac3e2931a42683392536500c4bb1fb5c3..1eeb828a15146e9e5040c64b20a31329853f12e7 100755 (executable)
@@ -118,10 +118,10 @@ test_expect_success 'prune: do not prune detached HEAD with no reflog' '
 
 test_expect_success 'prune: prune former HEAD after checking out branch' '
 
-       head_sha1=$(git rev-parse HEAD) &&
+       head_oid=$(git rev-parse HEAD) &&
        git checkout --quiet master &&
        git prune -v >prune_actual &&
-       grep "$head_sha1" prune_actual
+       grep "$head_oid" prune_actual
 
 '
 
@@ -265,15 +265,27 @@ EOF
 '
 
 test_expect_success 'prune .git/shallow' '
-       SHA1=$(echo hi|git commit-tree HEAD^{tree}) &&
-       echo $SHA1 >.git/shallow &&
+       oid=$(echo hi|git commit-tree HEAD^{tree}) &&
+       echo $oid >.git/shallow &&
        git prune --dry-run >out &&
-       grep $SHA1 .git/shallow &&
-       grep $SHA1 out &&
+       grep $oid .git/shallow &&
+       grep $oid out &&
        git prune &&
        test_path_is_missing .git/shallow
 '
 
+test_expect_success 'prune .git/shallow when there are no loose objects' '
+       oid=$(echo hi|git commit-tree HEAD^{tree}) &&
+       echo $oid >.git/shallow &&
+       git update-ref refs/heads/shallow-tip $oid &&
+       git repack -ad &&
+       # verify assumption that all loose objects are gone
+       git count-objects | grep ^0 &&
+       git prune &&
+       echo $oid >expect &&
+       test_cmp expect .git/shallow
+'
+
 test_expect_success 'prune: handle alternate object database' '
        test_create_repo A &&
        git -C A commit --allow-empty -m "initial commit" &&
@@ -314,8 +326,8 @@ test_expect_success 'prune: handle HEAD reflog in multiple worktrees' '
                git reset --hard HEAD^
        ) &&
        git prune --expire=now &&
-       SHA1=`git hash-object expected` &&
-       git -C third-worktree show "$SHA1" >actual &&
+       oid=`git hash-object expected` &&
+       git -C third-worktree show "$oid" >actual &&
        test_cmp expected actual
 '
 
diff --git a/t/t5323-pack-redundant.sh b/t/t5323-pack-redundant.sh
new file mode 100755 (executable)
index 0000000..6b4d1ca
--- /dev/null
@@ -0,0 +1,467 @@
+#!/bin/sh
+#
+# Copyright (c) 2018 Jiang Xin
+#
+
+test_description='Test git pack-redundant
+
+In order to test git-pack-redundant, we will create a number of objects and
+packs in the repository `master.git`. The relationship between packs (P1-P8)
+and objects (T, A-R) is showed in the following chart. Objects of a pack will
+be marked with letter x, while objects of redundant packs will be marked with
+exclamation point, and redundant pack itself will be marked with asterisk.
+
+       | T A B C D E F G H I J K L M N O P Q R
+    ----+--------------------------------------
+    P1  | x x x x x x x                       x
+    P2* |     ! ! ! !   ! ! !
+    P3  |             x     x x x x x
+    P4* |                     ! ! ! !     !
+    P5  |               x x           x x
+    P6* |                             ! !   !
+    P7  |                                 x x
+    P8* |   !
+    ----+--------------------------------------
+    ALL | x x x x x x x x x x x x x x x x x x x
+
+Another repository `shared.git` has unique objects (X-Z), while other objects
+(marked with letter s) are shared through alt-odb (of `master.git`). The
+relationship between packs and objects is as follows:
+
+       | T A B C D E F G H I J K L M N O P Q R   X Y Z
+    ----+----------------------------------------------
+    Px1 |   s s s                                 x x x
+    Px2 |         s s s                           x x x
+'
+
+. ./test-lib.sh
+
+master_repo=master.git
+shared_repo=shared.git
+
+# Create commits in <repo> and assign each commit's oid to shell variables
+# given in the arguments (A, B, and C). E.g.:
+#
+#     create_commits_in <repo> A B C
+#
+# NOTE: Avoid calling this function from a subshell since variable
+# assignments will disappear when subshell exits.
+create_commits_in () {
+       repo="$1" &&
+       if ! parent=$(git -C "$repo" rev-parse HEAD^{} 2>/dev/null)
+       then
+               parent=
+       fi &&
+       T=$(git -C "$repo" write-tree) &&
+       shift &&
+       while test $# -gt 0
+       do
+               name=$1 &&
+               test_tick &&
+               if test -z "$parent"
+               then
+                       oid=$(echo $name | git -C "$repo" commit-tree $T)
+               else
+                       oid=$(echo $name | git -C "$repo" commit-tree -p $parent $T)
+               fi &&
+               eval $name=$oid &&
+               parent=$oid &&
+               shift ||
+               return 1
+       done &&
+       git -C "$repo" update-ref refs/heads/master $oid
+}
+
+# Create pack in <repo> and assign pack id to variable given in the 2nd argument
+# (<name>). Commits in the pack will be read from stdin. E.g.:
+#
+#     create_pack_in <repo> <name> <<-EOF
+#         ...
+#         EOF
+#
+# NOTE: commits from stdin should be given using heredoc, not using pipe, and
+# avoid calling this function from a subshell since variable assignments will
+# disappear when subshell exits.
+create_pack_in () {
+       repo="$1" &&
+       name="$2" &&
+       pack=$(git -C "$repo/objects/pack" pack-objects -q pack) &&
+       eval $name=$pack &&
+       eval P$pack=$name:$pack
+}
+
+format_packfiles () {
+       sed \
+               -e "s#.*/pack-\(.*\)\.idx#\1#" \
+               -e "s#.*/pack-\(.*\)\.pack#\1#" |
+       sort -u |
+       while read p
+       do
+               if test -z "$(eval echo \${P$p})"
+               then
+                       echo $p
+               else
+                       eval echo "\${P$p}"
+               fi
+       done |
+       sort
+}
+
+test_expect_success 'setup master repo' '
+       git init --bare "$master_repo" &&
+       create_commits_in "$master_repo" A B C D E F G H I J K L M N O P Q R
+'
+
+#############################################################################
+# Chart of packs and objects for this test case
+#
+#         | T A B C D E F G H I J K L M N O P Q R
+#     ----+--------------------------------------
+#     P1  | x x x x x x x                       x
+#     P2  |     x x x x   x x x
+#     P3  |             x     x x x x x
+#     ----+--------------------------------------
+#     ALL | x x x x x x x x x x x x x x         x
+#
+#############################################################################
+test_expect_success 'master: no redundant for pack 1, 2, 3' '
+       create_pack_in "$master_repo" P1 <<-EOF &&
+               $T
+               $A
+               $B
+               $C
+               $D
+               $E
+               $F
+               $R
+               EOF
+       create_pack_in "$master_repo" P2 <<-EOF &&
+               $B
+               $C
+               $D
+               $E
+               $G
+               $H
+               $I
+               EOF
+       create_pack_in "$master_repo" P3 <<-EOF &&
+               $F
+               $I
+               $J
+               $K
+               $L
+               $M
+               EOF
+       (
+               cd "$master_repo" &&
+               git pack-redundant --all >out &&
+               test_must_be_empty out
+       )
+'
+
+#############################################################################
+# Chart of packs and objects for this test case
+#
+#         | T A B C D E F G H I J K L M N O P Q R
+#     ----+--------------------------------------
+#     P1  | x x x x x x x                       x
+#     P2  |     x x x x   x x x
+#     P3* |             !     ! ! ! ! !
+#     P4  |                     x x x x     x
+#     P5  |               x x           x x
+#     ----+--------------------------------------
+#     ALL | x x x x x x x x x x x x x x x x x   x
+#
+#############################################################################
+test_expect_success 'master: one of pack-2/pack-3 is redundant' '
+       create_pack_in "$master_repo" P4 <<-EOF &&
+               $J
+               $K
+               $L
+               $M
+               $P
+               EOF
+       create_pack_in "$master_repo" P5 <<-EOF &&
+               $G
+               $H
+               $N
+               $O
+               EOF
+       (
+               cd "$master_repo" &&
+               cat >expect <<-EOF &&
+                       P3:$P3
+                       EOF
+               git pack-redundant --all >out &&
+               format_packfiles <out >actual &&
+               test_cmp expect actual
+       )
+'
+
+#############################################################################
+# Chart of packs and objects for this test case
+#
+#         | T A B C D E F G H I J K L M N O P Q R
+#     ----+--------------------------------------
+#     P1  | x x x x x x x                       x
+#     P2* |     ! ! ! !   ! ! !
+#     P3  |             x     x x x x x
+#     P4* |                     ! ! ! !     !
+#     P5  |               x x           x x
+#     P6* |                             ! !   !
+#     P7  |                                 x x
+#     ----+--------------------------------------
+#     ALL | x x x x x x x x x x x x x x x x x x x
+#
+#############################################################################
+test_expect_success 'master: pack 2, 4, and 6 are redundant' '
+       create_pack_in "$master_repo" P6 <<-EOF &&
+               $N
+               $O
+               $Q
+               EOF
+       create_pack_in "$master_repo" P7 <<-EOF &&
+               $P
+               $Q
+               EOF
+       (
+               cd "$master_repo" &&
+               cat >expect <<-EOF &&
+                       P2:$P2
+                       P4:$P4
+                       P6:$P6
+                       EOF
+               git pack-redundant --all >out &&
+               format_packfiles <out >actual &&
+               test_cmp expect actual
+       )
+'
+
+#############################################################################
+# Chart of packs and objects for this test case
+#
+#         | T A B C D E F G H I J K L M N O P Q R
+#     ----+--------------------------------------
+#     P1  | x x x x x x x                       x
+#     P2* |     ! ! ! !   ! ! !
+#     P3  |             x     x x x x x
+#     P4* |                     ! ! ! !     !
+#     P5  |               x x           x x
+#     P6* |                             ! !   !
+#     P7  |                                 x x
+#     P8* |   !
+#     ----+--------------------------------------
+#     ALL | x x x x x x x x x x x x x x x x x x x
+#
+#############################################################################
+test_expect_success 'master: pack-8 (subset of pack-1) is also redundant' '
+       create_pack_in "$master_repo" P8 <<-EOF &&
+               $A
+               EOF
+       (
+               cd "$master_repo" &&
+               cat >expect <<-EOF &&
+                       P2:$P2
+                       P4:$P4
+                       P6:$P6
+                       P8:$P8
+                       EOF
+               git pack-redundant --all >out &&
+               format_packfiles <out >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'master: clean loose objects' '
+       (
+               cd "$master_repo" &&
+               git prune-packed &&
+               find objects -type f | sed -e "/objects\/pack\//d" >out &&
+               test_must_be_empty out
+       )
+'
+
+test_expect_success 'master: remove redundant packs and pass fsck' '
+       (
+               cd "$master_repo" &&
+               git pack-redundant --all | xargs rm &&
+               git fsck &&
+               git pack-redundant --all >out &&
+               test_must_be_empty out
+       )
+'
+
+# The following test cases will execute inside `shared.git`, instead of
+# inside `master.git`.
+test_expect_success 'setup shared.git' '
+       git clone --mirror "$master_repo" "$shared_repo" &&
+       (
+               cd "$shared_repo" &&
+               printf "../../$master_repo/objects\n" >objects/info/alternates
+       )
+'
+
+test_expect_success 'shared: all packs are redundant, but no output without --alt-odb' '
+       (
+               cd "$shared_repo" &&
+               git pack-redundant --all >out &&
+               test_must_be_empty out
+       )
+'
+
+#############################################################################
+# Chart of packs and objects for this test case
+#
+#     ================ master.git ===============
+#         | T A B C D E F G H I J K L M N O P Q R  <----------+
+#     ----+--------------------------------------             |
+#     P1  | x x x x x x x                       x             |
+#     P3  |             x     x x x x x                       |
+#     P5  |               x x           x x                   |
+#     P7  |                                 x x               |
+#     ----+--------------------------------------             |
+#     ALL | x x x x x x x x x x x x x x x x x x x             |
+#                                                             |
+#                                                             |
+#     ================ shared.git ===============             |
+#         | T A B C D E F G H I J K L M N O P Q R  <objects/info/alternates>
+#     ----+--------------------------------------
+#     P1* | s s s s s s s                       s
+#     P3* |             s     s s s s s
+#     P5* |               s s           s s
+#     P7* |                                 s s
+#     ----+--------------------------------------
+#     ALL | x x x x x x x x x x x x x x x x x x x
+#
+#############################################################################
+test_expect_success 'shared: show redundant packs in stderr for verbose mode' '
+       (
+               cd "$shared_repo" &&
+               cat >expect <<-EOF &&
+                       P1:$P1
+                       P3:$P3
+                       P5:$P5
+                       P7:$P7
+                       EOF
+               git pack-redundant --all --verbose >out 2>out.err &&
+               test_must_be_empty out &&
+               grep "pack$" out.err | format_packfiles >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'shared: remove redundant packs, no packs left' '
+       (
+               cd "$shared_repo" &&
+               cat >expect <<-EOF &&
+                       fatal: Zero packs found!
+                       EOF
+               git pack-redundant --all --alt-odb | xargs rm &&
+               git fsck &&
+               test_must_fail git pack-redundant --all --alt-odb >actual 2>&1 &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'shared: create new objects and packs' '
+       create_commits_in "$shared_repo" X Y Z &&
+       create_pack_in "$shared_repo" Px1 <<-EOF &&
+               $X
+               $Y
+               $Z
+               $A
+               $B
+               $C
+               EOF
+       create_pack_in "$shared_repo" Px2 <<-EOF
+               $X
+               $Y
+               $Z
+               $D
+               $E
+               $F
+               EOF
+'
+
+test_expect_success 'shared: no redundant without --alt-odb' '
+       (
+               cd "$shared_repo" &&
+               git pack-redundant --all >out &&
+               test_must_be_empty out
+       )
+'
+
+#############################################################################
+# Chart of packs and objects for this test case
+#
+#     ================ master.git ===============
+#         | T A B C D E F G H I J K L M N O P Q R  <----------------+
+#     ----+--------------------------------------                   |
+#     P1  | x x x x x x x                       x                   |
+#     P3  |             x     x x x x x                             |
+#     P5  |               x x           x x                         |
+#     P7  |                                 x x                     |
+#     ----+--------------------------------------                   |
+#     ALL | x x x x x x x x x x x x x x x x x x x                   |
+#                                                                   |
+#                                                                   |
+#     ================ shared.git =======================           |
+#         | T A B C D E F G H I J K L M N O P Q R   X Y Z <objects/info/alternates>
+#     ----+----------------------------------------------
+#     Px1 |   s s s                                 x x x
+#     Px2*|         s s s                           ! ! !
+#     ----+----------------------------------------------
+#     ALL | s s s s s s s s s s s s s s s s s s s   x x x
+#
+#############################################################################
+test_expect_success 'shared: one pack is redundant with --alt-odb' '
+       (
+               cd "$shared_repo" &&
+               git pack-redundant --all --alt-odb >out &&
+               format_packfiles <out >actual &&
+               test_line_count = 1 actual
+       )
+'
+
+#############################################################################
+# Chart of packs and objects for this test case
+#
+#     ================ master.git ===============
+#         | T A B C D E F G H I J K L M N O P Q R  <----------------+
+#     ----+--------------------------------------                   |
+#     P1  | x x x x x x x                       x                   |
+#     P3  |             x     x x x x x                             |
+#     P5  |               x x           x x                         |
+#     P7  |                                 x x                     |
+#     ----+--------------------------------------                   |
+#     ALL | x x x x x x x x x x x x x x x x x x x                   |
+#                                                                   |
+#                                                                   |
+#     ================ shared.git =======================           |
+#         | T A B C D E F G H I J K L M N O P Q R   X Y Z <objects/info/alternates>
+#     ----+----------------------------------------------
+#     Px1*|   s s s                                 i i i
+#     Px2*|         s s s                           i i i
+#     ----+----------------------------------------------
+#     ALL | s s s s s s s s s s s s s s s s s s s   i i i
+#                                                  (ignored objects, marked with i)
+#
+#############################################################################
+test_expect_success 'shared: ignore unique objects and all two packs are redundant' '
+       (
+               cd "$shared_repo" &&
+               cat >expect <<-EOF &&
+                       Px1:$Px1
+                       Px2:$Px2
+                       EOF
+               git pack-redundant --all --alt-odb >out <<-EOF &&
+                       $X
+                       $Y
+                       $Z
+                       EOF
+               format_packfiles <out >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_done
index db4ae09f2f81ff77b9c29b99c21363e66c81f222..e112b6086c96e4cb0ad7bc10dcd685e7570d496f 100755 (executable)
@@ -542,7 +542,38 @@ test_expect_success 'clone with http:// using protocol v2' '
        # Client requested to use protocol v2
        grep "Git-Protocol: version=2" log &&
        # Server responded using protocol v2
-       grep "git< version 2" log
+       grep "git< version 2" log &&
+       # Verify that the chunked encoding sending codepath is NOT exercised
+       ! grep "Send header: Transfer-Encoding: chunked" log
+'
+
+test_expect_success 'clone big repository with http:// using protocol v2' '
+       test_when_finished "rm -f log" &&
+
+       git init "$HTTPD_DOCUMENT_ROOT_PATH/big" &&
+       # Ensure that the list of wants is greater than http.postbuffer below
+       for i in $(test_seq 1 1500)
+       do
+               # do not use here-doc, because it requires a process
+               # per loop iteration
+               echo "commit refs/heads/too-many-refs-$i" &&
+               echo "committer git <git@example.com> $i +0000" &&
+               echo "data 0" &&
+               echo "M 644 inline bla.txt" &&
+               echo "data 4" &&
+               echo "bla"
+       done | git -C "$HTTPD_DOCUMENT_ROOT_PATH/big" fast-import &&
+
+       GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git \
+               -c protocol.version=2 -c http.postbuffer=65536 \
+               clone "$HTTPD_URL/smart/big" big_child &&
+
+       # Client requested to use protocol v2
+       grep "Git-Protocol: version=2" log &&
+       # Server responded using protocol v2
+       grep "git< version 2" log &&
+       # Verify that the chunked encoding sending codepath is exercised
+       grep "Send header: Transfer-Encoding: chunked" log
 '
 
 test_expect_success 'fetch with http:// using protocol v2' '
index 55835ee4a47158e1f7aeaa9aeb393f1f3d619791..49a394bd75dde817700850187966c3d31ae03f17 100755 (executable)
@@ -681,7 +681,7 @@ test_expect_success 'bisect: --no-checkout - target in breakage' '
        check_same BROKEN_HASH6 BISECT_HEAD &&
        git bisect bad BISECT_HEAD &&
        check_same BROKEN_HASH5 BISECT_HEAD &&
-       git bisect good BISECT_HEAD &&
+       test_must_fail git bisect good BISECT_HEAD &&
        check_same BROKEN_HASH6 bisect/bad &&
        git bisect reset
 '
@@ -692,7 +692,7 @@ test_expect_success 'bisect: --no-checkout - target after breakage' '
        check_same BROKEN_HASH6 BISECT_HEAD &&
        git bisect good BISECT_HEAD &&
        check_same BROKEN_HASH8 BISECT_HEAD &&
-       git bisect good BISECT_HEAD &&
+       test_must_fail git bisect good BISECT_HEAD &&
        check_same BROKEN_HASH9 bisect/bad &&
        git bisect reset
 '
@@ -701,7 +701,7 @@ test_expect_success 'bisect: demonstrate identification of damage boundary' "
        git bisect reset &&
        git checkout broken &&
        git bisect start broken master --no-checkout &&
-       git bisect run \"\$SHELL_PATH\" -c '
+       test_must_fail git bisect run \"\$SHELL_PATH\" -c '
                GOOD=\$(git for-each-ref \"--format=%(objectname)\" refs/bisect/good-*) &&
                git rev-list --objects BISECT_HEAD --not \$GOOD >tmp.\$\$ &&
                git pack-objects --stdout >/dev/null < tmp.\$\$
index 231b8cc19d6342f54490b0c385150b7cb318494f..b2401cec3e3be01d0973dea72f8154b5824762aa 100755 (executable)
@@ -85,4 +85,78 @@ test_expect_success REBASE_P \
        test_must_fail git rebase -p master
 '
 
+test_expect_success 'author.name overrides user.name' '
+       test_config user.name user &&
+       test_config user.email user@example.com &&
+       test_config author.name author &&
+       test_commit author-name-override-user &&
+       echo author user@example.com > expected-author &&
+       echo user user@example.com > expected-committer &&
+       git log --format="%an %ae" -1 > actual-author &&
+       git log --format="%cn %ce" -1 > actual-committer &&
+       test_cmp expected-author actual-author &&
+       test_cmp expected-committer actual-committer
+'
+
+test_expect_success 'author.email overrides user.email' '
+       test_config user.name user &&
+       test_config user.email user@example.com &&
+       test_config author.email author@example.com &&
+       test_commit author-email-override-user &&
+       echo user author@example.com > expected-author &&
+       echo user user@example.com > expected-committer &&
+       git log --format="%an %ae" -1 > actual-author &&
+       git log --format="%cn %ce" -1 > actual-committer &&
+       test_cmp expected-author actual-author &&
+       test_cmp expected-committer actual-committer
+'
+
+test_expect_success 'committer.name overrides user.name' '
+       test_config user.name user &&
+       test_config user.email user@example.com &&
+       test_config committer.name committer &&
+       test_commit committer-name-override-user &&
+       echo user user@example.com > expected-author &&
+       echo committer user@example.com > expected-committer &&
+       git log --format="%an %ae" -1 > actual-author &&
+       git log --format="%cn %ce" -1 > actual-committer &&
+       test_cmp expected-author actual-author &&
+       test_cmp expected-committer actual-committer
+'
+
+test_expect_success 'committer.email overrides user.email' '
+       test_config user.name user &&
+       test_config user.email user@example.com &&
+       test_config committer.email committer@example.com &&
+       test_commit committer-email-override-user &&
+       echo user user@example.com > expected-author &&
+       echo user committer@example.com > expected-committer &&
+       git log --format="%an %ae" -1 > actual-author &&
+       git log --format="%cn %ce" -1 > actual-committer &&
+       test_cmp expected-author actual-author &&
+       test_cmp expected-committer actual-committer
+'
+
+test_expect_success 'author and committer environment variables override config settings' '
+       test_config user.name user &&
+       test_config user.email user@example.com &&
+       test_config author.name author &&
+       test_config author.email author@example.com &&
+       test_config committer.name committer &&
+       test_config committer.email committer@example.com &&
+       GIT_AUTHOR_NAME=env_author && export GIT_AUTHOR_NAME &&
+       GIT_AUTHOR_EMAIL=env_author@example.com && export GIT_AUTHOR_EMAIL &&
+       GIT_COMMITTER_NAME=env_commit && export GIT_COMMITTER_NAME &&
+       GIT_COMMITTER_EMAIL=env_commit@example.com && export GIT_COMMITTER_EMAIL &&
+       test_commit env-override-conf &&
+       echo env_author env_author@example.com > expected-author &&
+       echo env_commit env_commit@example.com > expected-committer &&
+       git log --format="%an %ae" -1 > actual-author &&
+       git log --format="%cn %ce" -1 > actual-committer &&
+       sane_unset GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
+       sane_unset GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL &&
+       test_cmp expected-author actual-author &&
+       test_cmp expected-committer actual-committer
+'
+
 test_done
index 22b9199d599284e9fcf5d7451b98db7ee99fffbe..bb9a7f4ff91120424e300f4ced821a9217b5e34e 100755 (executable)
@@ -546,7 +546,7 @@ do
 done >actual
 EOF
 
-test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
+test_expect_success SYMLINKS 'difftool --dir-diff --symlinks without unstaged changes' '
        cat >expect <<-EOF &&
        file
        $PWD/file
@@ -555,7 +555,7 @@ test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged cha
        sub/sub
        $PWD/sub/sub
        EOF
-       git difftool --dir-diff --symlink \
+       git difftool --dir-diff --symlinks \
                --extcmd "./.git/CHECK_SYMLINKS" branch HEAD &&
        test_cmp expect actual
 '
index 3a2c6326d83b760194c600e2ccde619438200508..f5e21bf970f4dc44c7fa30d54134a15cf343ffc1 100755 (executable)
@@ -1437,6 +1437,7 @@ test_expect_success 'double dash "git checkout"' '
        --guess Z
        --no-guess Z
        --no-... Z
+       --overlay Z
        EOF
 '
 
index 80402a428f7735a031658a904d4f623f38e43464..681c41ba329a90db8c668fde4640025872f1e622 100644 (file)
@@ -593,6 +593,15 @@ test_dir_is_empty () {
        fi
 }
 
+# Check if the file exists and has a size greater than zero
+test_file_not_empty () {
+       if ! test -s "$1"
+       then
+               echo "'$1' is not a non-empty file."
+               false
+       fi
+}
+
 test_path_is_missing () {
        if test -e "$1"
        then
index 8665b0a9b6186a7ad17889213f5225265c9f27d1..562c57e6858b79aff074fcb7ab8c65fa2f110bc0 100644 (file)
@@ -147,10 +147,16 @@ do
        --stress)
                stress=t ;;
        --stress=*)
+               echo "error: --stress does not accept an argument: '$opt'" >&2
+               echo "did you mean --stress-jobs=${opt#*=} or --stress-limit=${opt#*=}?" >&2
+               exit 1
+               ;;
+       --stress-jobs=*)
+               stress=t;
                stress=${opt#--*=}
                case "$stress" in
                *[!0-9]*|0*|"")
-                       echo "error: --stress=<N> requires the number of jobs to run" >&2
+                       echo "error: --stress-jobs=<N> requires the number of jobs to run" >&2
                        exit 1
                        ;;
                *)      # Good.
@@ -158,6 +164,7 @@ do
                esac
                ;;
        --stress-limit=*)
+               stress=t;
                stress_limit=${opt#--*=}
                case "$stress_limit" in
                *[!0-9]*|0*|"")
@@ -1327,7 +1334,11 @@ then
        fi
 fi
 
-# Provide an implementation of the 'yes' utility
+# Provide an implementation of the 'yes' utility; the upper bound
+# limit is there to help Windows that cannot stop this loop from
+# wasting cycles when the downstream stops reading, so do not be
+# tempted to turn it into an infinite loop. cf. 6129c930 ("test-lib:
+# limit the output of the yes utility", 2016-02-02)
 yes () {
        if test $# = 0
        then
diff --git a/trace2.c b/trace2.c
new file mode 100644 (file)
index 0000000..ccccd4e
--- /dev/null
+++ b/trace2.c
@@ -0,0 +1,761 @@
+#include "cache.h"
+#include "config.h"
+#include "json-writer.h"
+#include "quote.h"
+#include "run-command.h"
+#include "sigchain.h"
+#include "thread-utils.h"
+#include "version.h"
+#include "trace2/tr2_cfg.h"
+#include "trace2/tr2_cmd_name.h"
+#include "trace2/tr2_dst.h"
+#include "trace2/tr2_sid.h"
+#include "trace2/tr2_tgt.h"
+#include "trace2/tr2_tls.h"
+
+static int trace2_enabled;
+
+static int tr2_next_child_id; /* modify under lock */
+static int tr2_next_exec_id; /* modify under lock */
+static int tr2_next_repo_id = 1; /* modify under lock. zero is reserved */
+
+/*
+ * A table of the builtin TRACE2 targets.  Each of these may be independently
+ * enabled or disabled.  Each TRACE2 API method will try to write an event to
+ * *each* of the enabled targets.
+ */
+/* clang-format off */
+static struct tr2_tgt *tr2_tgt_builtins[] =
+{
+       &tr2_tgt_normal,
+       &tr2_tgt_perf,
+       &tr2_tgt_event,
+       NULL
+};
+/* clang-format on */
+
+/* clang-format off */
+#define for_each_builtin(j, tgt_j)                     \
+       for (j = 0, tgt_j = tr2_tgt_builtins[j];        \
+            tgt_j;                                     \
+            j++, tgt_j = tr2_tgt_builtins[j])
+/* clang-format on */
+
+/* clang-format off */
+#define for_each_wanted_builtin(j, tgt_j)            \
+       for_each_builtin(j, tgt_j)                   \
+               if (tr2_dst_trace_want(tgt_j->pdst))
+/* clang-format on */
+
+/*
+ * Force (rather than lazily) initialize any of the requested
+ * builtin TRACE2 targets at startup (and before we've seen an
+ * actual TRACE2 event call) so we can see if we need to setup
+ * the TR2 and TLS machinery.
+ *
+ * Return the number of builtin targets enabled.
+ */
+static int tr2_tgt_want_builtins(void)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       int sum = 0;
+
+       for_each_builtin (j, tgt_j)
+               if (tgt_j->pfn_init())
+                       sum++;
+
+       return sum;
+}
+
+/*
+ * Properly terminate each builtin target.  Give each target
+ * a chance to write a summary event and/or flush if necessary
+ * and then close the fd.
+ */
+static void tr2_tgt_disable_builtins(void)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+
+       for_each_builtin (j, tgt_j)
+               tgt_j->pfn_term();
+}
+
+static int tr2main_exit_code;
+
+/*
+ * Our atexit routine should run after everything has finished.
+ *
+ * Note that events generated here might not actually appear if
+ * we are writing to fd 1 or 2 and our atexit routine runs after
+ * the pager's atexit routine (since it closes them to shutdown
+ * the pipes).
+ */
+static void tr2main_atexit_handler(void)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       /*
+        * Clear any unbalanced regions so that our atexit message
+        * does not appear nested.  This improves the appearance of
+        * the trace output if someone calls die(), for example.
+        */
+       tr2tls_pop_unwind_self();
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_atexit)
+                       tgt_j->pfn_atexit(us_elapsed_absolute,
+                                         tr2main_exit_code);
+
+       tr2_tgt_disable_builtins();
+
+       tr2tls_release();
+       tr2_sid_release();
+       tr2_cmd_name_release();
+       tr2_cfg_free_patterns();
+
+       trace2_enabled = 0;
+}
+
+static void tr2main_signal_handler(int signo)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_signal)
+                       tgt_j->pfn_signal(us_elapsed_absolute, signo);
+
+       sigchain_pop(signo);
+       raise(signo);
+}
+
+void trace2_initialize_fl(const char *file, int line)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+
+       if (trace2_enabled)
+               return;
+
+       if (!tr2_tgt_want_builtins())
+               return;
+       trace2_enabled = 1;
+
+       tr2_sid_get();
+
+       atexit(tr2main_atexit_handler);
+       sigchain_push(SIGPIPE, tr2main_signal_handler);
+       tr2tls_init();
+
+       /*
+        * Emit 'version' message on each active builtin target.
+        */
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_version_fl)
+                       tgt_j->pfn_version_fl(file, line);
+}
+
+int trace2_is_enabled(void)
+{
+       return trace2_enabled;
+}
+
+void trace2_cmd_start_fl(const char *file, int line, const char **argv)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+
+       if (!trace2_enabled)
+               return;
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_start_fl)
+                       tgt_j->pfn_start_fl(file, line, argv);
+}
+
+int trace2_cmd_exit_fl(const char *file, int line, int code)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+
+       code &= 0xff;
+
+       if (!trace2_enabled)
+               return code;
+
+       tr2main_exit_code = code;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_exit_fl)
+                       tgt_j->pfn_exit_fl(file, line, us_elapsed_absolute,
+                                          code);
+
+       return code;
+}
+
+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,
+                           va_list ap)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+
+       if (!trace2_enabled)
+               return;
+
+       /*
+        * We expect each target function to treat 'ap' as constant
+        * and use va_copy (because an 'ap' can only be walked once).
+        */
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_error_va_fl)
+                       tgt_j->pfn_error_va_fl(file, line, fmt, ap);
+}
+
+void trace2_cmd_path_fl(const char *file, int line, const char *pathname)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+
+       if (!trace2_enabled)
+               return;
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_command_path_fl)
+                       tgt_j->pfn_command_path_fl(file, line, pathname);
+}
+
+void trace2_cmd_name_fl(const char *file, int line, const char *name)
+{
+       struct tr2_tgt *tgt_j;
+       const char *hierarchy;
+       int j;
+
+       if (!trace2_enabled)
+               return;
+
+       tr2_cmd_name_append_hierarchy(name);
+       hierarchy = tr2_cmd_name_get_hierarchy();
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_command_name_fl)
+                       tgt_j->pfn_command_name_fl(file, line, name, hierarchy);
+}
+
+void trace2_cmd_mode_fl(const char *file, int line, const char *mode)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+
+       if (!trace2_enabled)
+               return;
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_command_mode_fl)
+                       tgt_j->pfn_command_mode_fl(file, line, mode);
+}
+
+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,
+                        const char **argv)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+
+       if (!trace2_enabled)
+               return;
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_alias_fl)
+                       tgt_j->pfn_alias_fl(file, line, alias, argv);
+}
+
+void trace2_cmd_list_config_fl(const char *file, int line)
+{
+       if (!trace2_enabled)
+               return;
+
+       tr2_cfg_list_config_fl(file, line);
+}
+
+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,
+                             const char *value)
+{
+       if (!trace2_enabled)
+               return;
+
+       tr2_cfg_set_fl(file, line, key, value);
+}
+
+void trace2_child_start_fl(const char *file, int line,
+                          struct child_process *cmd)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+
+       if (!trace2_enabled)
+               return;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       cmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);
+       cmd->trace2_child_us_start = us_now;
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_child_start_fl)
+                       tgt_j->pfn_child_start_fl(file, line,
+                                                 us_elapsed_absolute, cmd);
+}
+
+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
+                         int child_exit_code)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+       uint64_t us_elapsed_child;
+
+       if (!trace2_enabled)
+               return;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       if (cmd->trace2_child_us_start)
+               us_elapsed_child = us_now - cmd->trace2_child_us_start;
+       else
+               us_elapsed_child = 0;
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_child_exit_fl)
+                       tgt_j->pfn_child_exit_fl(file, line,
+                                                us_elapsed_absolute,
+                                                cmd->trace2_child_id, cmd->pid,
+                                                child_exit_code,
+                                                us_elapsed_child);
+}
+
+int trace2_exec_fl(const char *file, int line, const char *exe,
+                  const char **argv)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       int exec_id;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+
+       if (!trace2_enabled)
+               return -1;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       exec_id = tr2tls_locked_increment(&tr2_next_exec_id);
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_exec_fl)
+                       tgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,
+                                          exec_id, exe, argv);
+
+       return exec_id;
+}
+
+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+
+       if (!trace2_enabled)
+               return;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_exec_result_fl)
+                       tgt_j->pfn_exec_result_fl(
+                               file, line, us_elapsed_absolute, exec_id, code);
+}
+
+void trace2_thread_start_fl(const char *file, int line, const char *thread_name)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+
+       if (!trace2_enabled)
+               return;
+
+       if (tr2tls_is_main_thread()) {
+               /*
+                * We should only be called from the new thread's thread-proc,
+                * so this is technically a bug.  But in those cases where the
+                * main thread also runs the thread-proc function (or when we
+                * are built with threading disabled), we need to allow it.
+                *
+                * Convert this call to a region-enter so the nesting looks
+                * correct.
+                */
+               trace2_region_enter_printf_fl(file, line, NULL, NULL, NULL,
+                                             "thread-proc on main: %s",
+                                             thread_name);
+               return;
+       }
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       tr2tls_create_self(thread_name);
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_thread_start_fl)
+                       tgt_j->pfn_thread_start_fl(file, line,
+                                                  us_elapsed_absolute);
+}
+
+void trace2_thread_exit_fl(const char *file, int line)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+       uint64_t us_elapsed_thread;
+
+       if (!trace2_enabled)
+               return;
+
+       if (tr2tls_is_main_thread()) {
+               /*
+                * We should only be called from the exiting thread's
+                * thread-proc, so this is technically a bug.  But in
+                * those cases where the main thread also runs the
+                * thread-proc function (or when we are built with
+                * threading disabled), we need to allow it.
+                *
+                * Convert this call to a region-leave so the nesting
+                * looks correct.
+                */
+               trace2_region_leave_printf_fl(file, line, NULL, NULL, NULL,
+                                             "thread-proc on main");
+               return;
+       }
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       /*
+        * Clear any unbalanced regions and then get the relative time
+        * for the outer-most region (which we pushed when the thread
+        * started).  This gives us the run time of the thread.
+        */
+       tr2tls_pop_unwind_self();
+       us_elapsed_thread = tr2tls_region_elasped_self(us_now);
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_thread_exit_fl)
+                       tgt_j->pfn_thread_exit_fl(file, line,
+                                                 us_elapsed_absolute,
+                                                 us_elapsed_thread);
+
+       tr2tls_unset_self();
+}
+
+void trace2_def_param_fl(const char *file, int line, const char *param,
+                        const char *value)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+
+       if (!trace2_enabled)
+               return;
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_param_fl)
+                       tgt_j->pfn_param_fl(file, line, param, value);
+}
+
+void trace2_def_repo_fl(const char *file, int line, struct repository *repo)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+
+       if (!trace2_enabled)
+               return;
+
+       if (repo->trace2_repo_id)
+               return;
+
+       repo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_repo_fl)
+                       tgt_j->pfn_repo_fl(file, line, repo);
+}
+
+void trace2_region_enter_printf_va_fl(const char *file, int line,
+                                     const char *category, const char *label,
+                                     const struct repository *repo,
+                                     const char *fmt, va_list ap)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+
+       if (!trace2_enabled)
+               return;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       /*
+        * Print the region-enter message at the current nesting
+        * (indentation) level and then push a new level.
+        *
+        * We expect each target function to treat 'ap' as constant
+        * and use va_copy.
+        */
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_region_enter_printf_va_fl)
+                       tgt_j->pfn_region_enter_printf_va_fl(
+                               file, line, us_elapsed_absolute, category,
+                               label, repo, fmt, ap);
+
+       tr2tls_push_self(us_now);
+}
+
+void trace2_region_enter_fl(const char *file, int line, const char *category,
+                           const char *label, const struct repository *repo)
+{
+       trace2_region_enter_printf_va_fl(file, line, category, label, repo,
+                                        NULL, NULL);
+}
+
+void trace2_region_enter_printf_fl(const char *file, int line,
+                                  const char *category, const char *label,
+                                  const struct repository *repo,
+                                  const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       trace2_region_enter_printf_va_fl(file, line, category, label, repo, fmt,
+                                        ap);
+       va_end(ap);
+}
+
+#ifndef HAVE_VARIADIC_MACROS
+void trace2_region_enter_printf(const char *category, const char *label,
+                               const struct repository *repo, const char *fmt,
+                               ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       trace2_region_enter_printf_va_fl(NULL, 0, category, label, repo, fmt,
+                                        ap);
+       va_end(ap);
+}
+#endif
+
+void trace2_region_leave_printf_va_fl(const char *file, int line,
+                                     const char *category, const char *label,
+                                     const struct repository *repo,
+                                     const char *fmt, va_list ap)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+       uint64_t us_elapsed_region;
+
+       if (!trace2_enabled)
+               return;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       /*
+        * Get the elapsed time in the current region before we
+        * pop it off the stack.  Pop the stack.  And then print
+        * the perf message at the new (shallower) level so that
+        * it lines up with the corresponding push/enter.
+        */
+       us_elapsed_region = tr2tls_region_elasped_self(us_now);
+
+       tr2tls_pop_self();
+
+       /*
+        * We expect each target function to treat 'ap' as constant
+        * and use va_copy.
+        */
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_region_leave_printf_va_fl)
+                       tgt_j->pfn_region_leave_printf_va_fl(
+                               file, line, us_elapsed_absolute,
+                               us_elapsed_region, category, label, repo, fmt,
+                               ap);
+}
+
+void trace2_region_leave_fl(const char *file, int line, const char *category,
+                           const char *label, const struct repository *repo)
+{
+       trace2_region_leave_printf_va_fl(file, line, category, label, repo,
+                                        NULL, NULL);
+}
+
+void trace2_region_leave_printf_fl(const char *file, int line,
+                                  const char *category, const char *label,
+                                  const struct repository *repo,
+                                  const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       trace2_region_leave_printf_va_fl(file, line, category, label, repo, fmt,
+                                        ap);
+       va_end(ap);
+}
+
+#ifndef HAVE_VARIADIC_MACROS
+void trace2_region_leave_printf(const char *category, const char *label,
+                               const struct repository *repo, const char *fmt,
+                               ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       trace2_region_leave_printf_va_fl(NULL, 0, category, label, repo, fmt,
+                                        ap);
+       va_end(ap);
+}
+#endif
+
+void trace2_data_string_fl(const char *file, int line, const char *category,
+                          const struct repository *repo, const char *key,
+                          const char *value)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+       uint64_t us_elapsed_region;
+
+       if (!trace2_enabled)
+               return;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+       us_elapsed_region = tr2tls_region_elasped_self(us_now);
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_data_fl)
+                       tgt_j->pfn_data_fl(file, line, us_elapsed_absolute,
+                                          us_elapsed_region, category, repo,
+                                          key, value);
+}
+
+void trace2_data_intmax_fl(const char *file, int line, const char *category,
+                          const struct repository *repo, const char *key,
+                          intmax_t value)
+{
+       struct strbuf buf_string = STRBUF_INIT;
+
+       if (!trace2_enabled)
+               return;
+
+       strbuf_addf(&buf_string, "%" PRIdMAX, value);
+       trace2_data_string_fl(file, line, category, repo, key, buf_string.buf);
+       strbuf_release(&buf_string);
+}
+
+void trace2_data_json_fl(const char *file, int line, const char *category,
+                        const struct repository *repo, const char *key,
+                        const struct json_writer *value)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+       uint64_t us_elapsed_region;
+
+       if (!trace2_enabled)
+               return;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+       us_elapsed_region = tr2tls_region_elasped_self(us_now);
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_data_fl)
+                       tgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,
+                                               us_elapsed_region, category,
+                                               repo, key, value);
+}
+
+void trace2_printf_va_fl(const char *file, int line, const char *fmt,
+                        va_list ap)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+
+       if (!trace2_enabled)
+               return;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       /*
+        * We expect each target function to treat 'ap' as constant
+        * and use va_copy.
+        */
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_printf_va_fl)
+                       tgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,
+                                               fmt, ap);
+}
+
+void trace2_printf_fl(const char *file, int line, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       trace2_printf_va_fl(file, line, fmt, ap);
+       va_end(ap);
+}
+
+#ifndef HAVE_VARIADIC_MACROS
+void trace2_printf(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       trace2_printf_va_fl(NULL, 0, fmt, ap);
+       va_end(ap);
+}
+#endif
diff --git a/trace2.h b/trace2.h
new file mode 100644 (file)
index 0000000..ae5020d
--- /dev/null
+++ b/trace2.h
@@ -0,0 +1,385 @@
+#ifndef TRACE2_H
+#define TRACE2_H
+
+struct child_process;
+struct repository;
+struct json_writer;
+
+/*
+ * The public TRACE2 routines are grouped into the following groups:
+ *
+ * [] trace2_initialize -- initialization.
+ * [] trace2_cmd_*      -- emit command/control messages.
+ * [] trace2_child*     -- emit child start/stop messages.
+ * [] trace2_exec*      -- emit exec start/stop messages.
+ * [] trace2_thread*    -- emit thread start/stop messages.
+ * [] trace2_def*       -- emit definition/parameter mesasges.
+ * [] trace2_region*    -- emit region nesting messages.
+ * [] trace2_data*      -- emit region/thread/repo data messages.
+ * [] trace2_printf*    -- legacy trace[1] messages.
+ */
+
+/*
+ * Initialize TRACE2 tracing facility if any of the builtin TRACE2
+ * targets are enabled in the environment.  Emits a 'version' event.
+ *
+ * Cleanup/Termination is handled automatically by a registered
+ * atexit() routine.
+ */
+void trace2_initialize_fl(const char *file, int line);
+
+#define trace2_initialize() trace2_initialize_fl(__FILE__, __LINE__)
+
+/*
+ * Return true if trace2 is enabled.
+ */
+int trace2_is_enabled(void);
+
+/*
+ * Emit a 'start' event with the original (unmodified) argv.
+ */
+void trace2_cmd_start_fl(const char *file, int line, const char **argv);
+
+#define trace2_cmd_start(argv) trace2_cmd_start_fl(__FILE__, __LINE__, (argv))
+
+/*
+ * Emit an 'exit' event.
+ *
+ * Write the exit-code that will be passed to exit() or returned
+ * from main().
+ *
+ * Use this prior to actually calling exit().
+ * See "#define exit()" in git-compat-util.h
+ */
+int trace2_cmd_exit_fl(const char *file, int line, int code);
+
+#define trace2_cmd_exit(code) (trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))
+
+/*
+ * Emit an 'error' event.
+ *
+ * Write an error message to the TRACE2 targets.
+ */
+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,
+                           va_list ap);
+
+#define trace2_cmd_error_va(fmt, ap) \
+       trace2_cmd_error_va_fl(__FILE__, __LINE__, (fmt), (ap))
+
+/*
+ * Emit a 'pathname' event with the canonical pathname of the current process
+ * This gives post-processors a simple field to identify the command without
+ * having to parse the argv.  For example, to distinguish invocations from
+ * installed versus debug executables.
+ */
+void trace2_cmd_path_fl(const char *file, int line, const char *pathname);
+
+#define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))
+
+/*
+ * Emit a 'cmd_name' event with the canonical name of the command.
+ * This gives post-processors a simple field to identify the command
+ * without having to parse the argv.
+ */
+void trace2_cmd_name_fl(const char *file, int line, const char *name);
+
+#define trace2_cmd_name(v) trace2_cmd_name_fl(__FILE__, __LINE__, (v))
+
+/*
+ * Emit a 'cmd_mode' event to further describe the command being run.
+ * For example, "checkout" can checkout a single file or can checkout a
+ * different branch.  This gives post-processors a simple field to compare
+ * equivalent commands without having to parse the argv.
+ */
+void trace2_cmd_mode_fl(const char *file, int line, const char *mode);
+
+#define trace2_cmd_mode(sv) trace2_cmd_mode_fl(__FILE__, __LINE__, (sv))
+
+/*
+ * Emit an 'alias' expansion event.
+ */
+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,
+                        const char **argv);
+
+#define trace2_cmd_alias(alias, argv) \
+       trace2_cmd_alias_fl(__FILE__, __LINE__, (alias), (argv))
+
+/*
+ * Emit one or more 'def_param' events for "interesting" configuration
+ * settings.
+ *
+ * The environment variable "GIT_TR2_CONFIG_PARAMS" can be set to a
+ * list of patterns considered important.  For example:
+ *
+ *    GIT_TR2_CONFIG_PARAMS="core.*,remote.*.url"
+ *
+ * Note: this routine does a read-only iteration on the config data
+ * (using read_early_config()), so it must not be called until enough
+ * of the process environment has been established.  This includes the
+ * location of the git and worktree directories, expansion of any "-c"
+ * and "-C" command line options, and etc.
+ */
+void trace2_cmd_list_config_fl(const char *file, int line);
+
+#define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__)
+
+/*
+ * Emit a "def_param" event for the given config key/value pair IF
+ * we consider the key to be "interesting".
+ *
+ * Use this for new/updated config settings created/updated after
+ * trace2_cmd_list_config() is called.
+ */
+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,
+                             const char *value);
+
+#define trace2_cmd_set_config(k, v) \
+       trace2_cmd_set_config_fl(__FILE__, __LINE__, (k), (v))
+
+/*
+ * Emit a 'child_start' event prior to spawning a child process.
+ *
+ * Before calling optionally set "cmd->trace2_child_class" to a string
+ * describing the type of the child process.  For example, "editor" or
+ * "pager".
+ */
+void trace2_child_start_fl(const char *file, int line,
+                          struct child_process *cmd);
+
+#define trace2_child_start(cmd) trace2_child_start_fl(__FILE__, __LINE__, (cmd))
+
+/*
+ * Emit a 'child_exit' event after the child process completes.
+ */
+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
+                         int child_exit_code);
+
+#define trace2_child_exit(cmd, code) \
+       trace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))
+
+/*
+ * Emit an 'exec' event prior to calling one of exec(), execv(),
+ * execvp(), and etc.  On Unix-derived systems, this will be the
+ * last event emitted for the current process, unless the exec
+ * fails.  On Windows, exec() behaves like 'child_start' and a
+ * waitpid(), so additional events may be emitted.
+ *
+ * Returns the "exec_id".
+ */
+int trace2_exec_fl(const char *file, int line, const char *exe,
+                  const char **argv);
+
+#define trace2_exec(exe, argv) trace2_exec_fl(__FILE__, __LINE__, (exe), (argv))
+
+/*
+ * Emit an 'exec_result' when possible.  On Unix-derived systems,
+ * this should be called after exec() returns (which only happens
+ * when there is an error starting the new process).  On Windows,
+ * this should be called after the waitpid().
+ *
+ * The "exec_id" should be the value returned from trace2_exec().
+ */
+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code);
+
+#define trace2_exec_result(id, code) \
+       trace2_exec_result_fl(__FILE__, __LINE__, (id), (code))
+
+/*
+ * Emit a 'thread_start' event.  This must be called from inside the
+ * thread-proc to set up the trace2 TLS data for the thread.
+ *
+ * Thread names should be descriptive, like "preload_index".
+ * Thread names will be decorated with an instance number automatically.
+ */
+void trace2_thread_start_fl(const char *file, int line,
+                           const char *thread_name);
+
+#define trace2_thread_start(thread_name) \
+       trace2_thread_start_fl(__FILE__, __LINE__, (thread_name))
+
+/*
+ * Emit a 'thread_exit' event.  This must be called from inside the
+ * thread-proc to report thread-specific data and cleanup TLS data
+ * for the thread.
+ */
+void trace2_thread_exit_fl(const char *file, int line);
+
+#define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)
+
+/*
+ * Emit a 'param' event.
+ *
+ * Write a "<param> = <value>" pair describing some aspect of the
+ * run such as an important configuration setting or command line
+ * option that significantly changes command behavior.
+ */
+void trace2_def_param_fl(const char *file, int line, const char *param,
+                        const char *value);
+
+#define trace2_def_param(param, value) \
+       trace2_def_param_fl(__FILE__, __LINE__, (param), (value))
+
+/*
+ * Tell trace2 about a newly instantiated repo object and assign
+ * a trace2-repo-id to be used in subsequent activity events.
+ *
+ * Emits a 'worktree' event for this repo instance.
+ */
+void trace2_def_repo_fl(const char *file, int line, struct repository *repo);
+
+#define trace2_def_repo(repo) trace2_def_repo_fl(__FILE__, __LINE__, repo)
+
+/*
+ * Emit a 'region_enter' event for <category>.<label> with optional
+ * repo-id and printf message.
+ *
+ * Enter a new nesting level on the current thread and remember the
+ * current time.  This controls the indenting of all subsequent events
+ * on this thread.
+ */
+void trace2_region_enter_fl(const char *file, int line, const char *category,
+                           const char *label, const struct repository *repo);
+
+#define trace2_region_enter(category, label, repo) \
+       trace2_region_enter_fl(__FILE__, __LINE__, (category), (label), (repo))
+
+void trace2_region_enter_printf_va_fl(const char *file, int line,
+                                     const char *category, const char *label,
+                                     const struct repository *repo,
+                                     const char *fmt, va_list ap);
+
+#define trace2_region_enter_printf_va(category, label, repo, fmt, ap)    \
+       trace2_region_enter_printf_va_fl(__FILE__, __LINE__, (category), \
+                                        (label), (repo), (fmt), (ap))
+
+void trace2_region_enter_printf_fl(const char *file, int line,
+                                  const char *category, const char *label,
+                                  const struct repository *repo,
+                                  const char *fmt, ...);
+
+#ifdef HAVE_VARIADIC_MACROS
+#define trace2_region_enter_printf(category, label, repo, ...)                 \
+       trace2_region_enter_printf_fl(__FILE__, __LINE__, (category), (label), \
+                                     (repo), __VA_ARGS__)
+#else
+/* clang-format off */
+__attribute__((format (region_enter_printf, 4, 5)))
+void trace2_region_enter_printf(const char *category, const char *label,
+                               const struct repository *repo, const char *fmt,
+                               ...);
+/* clang-format on */
+#endif
+
+/*
+ * Emit a 'region_leave' event for <category>.<label> with optional
+ * repo-id and printf message.
+ *
+ * Leave current nesting level and report the elapsed time spent
+ * in this nesting level.
+ */
+void trace2_region_leave_fl(const char *file, int line, const char *category,
+                           const char *label, const struct repository *repo);
+
+#define trace2_region_leave(category, label, repo) \
+       trace2_region_leave_fl(__FILE__, __LINE__, (category), (label), (repo))
+
+void trace2_region_leave_printf_va_fl(const char *file, int line,
+                                     const char *category, const char *label,
+                                     const struct repository *repo,
+                                     const char *fmt, va_list ap);
+
+#define trace2_region_leave_printf_va(category, label, repo, fmt, ap)    \
+       trace2_region_leave_printf_va_fl(__FILE__, __LINE__, (category), \
+                                        (label), (repo), (fmt), (ap))
+
+void trace2_region_leave_printf_fl(const char *file, int line,
+                                  const char *category, const char *label,
+                                  const struct repository *repo,
+                                  const char *fmt, ...);
+
+#ifdef HAVE_VARIADIC_MACROS
+#define trace2_region_leave_printf(category, label, repo, ...)                 \
+       trace2_region_leave_printf_fl(__FILE__, __LINE__, (category), (label), \
+                                     (repo), __VA_ARGS__)
+#else
+/* clang-format off */
+__attribute__((format (region_leave_printf, 4, 5)))
+void trace2_region_leave_printf(const char *category, const char *label,
+                               const struct repository *repo, const char *fmt,
+                               ...);
+/* clang-format on */
+#endif
+
+/*
+ * Emit a key-value pair 'data' event of the form <category>.<key> = <value>.
+ * This event implicitly contains information about thread, nesting region,
+ * and optional repo-id.
+ *
+ * On event-based TRACE2 targets, this generates a 'data' event suitable
+ * for post-processing.  On printf-based TRACE2 targets, this is converted
+ * into a fixed-format printf message.
+ */
+void trace2_data_string_fl(const char *file, int line, const char *category,
+                          const struct repository *repo, const char *key,
+                          const char *value);
+
+#define trace2_data_string(category, repo, key, value)                       \
+       trace2_data_string_fl(__FILE__, __LINE__, (category), (repo), (key), \
+                             (value))
+
+void trace2_data_intmax_fl(const char *file, int line, const char *category,
+                          const struct repository *repo, const char *key,
+                          intmax_t value);
+
+#define trace2_data_intmax(category, repo, key, value)                       \
+       trace2_data_intmax_fl(__FILE__, __LINE__, (category), (repo), (key), \
+                             (value))
+
+void trace2_data_json_fl(const char *file, int line, const char *category,
+                        const struct repository *repo, const char *key,
+                        const struct json_writer *jw);
+
+#define trace2_data_json(category, repo, key, value)                       \
+       trace2_data_json_fl(__FILE__, __LINE__, (category), (repo), (key), \
+                           (value))
+
+/*
+ * Emit a 'printf' event.
+ *
+ * Write an arbitrary formatted message to the TRACE2 targets.  These
+ * text messages should be considered as human-readable strings without
+ * any formatting guidelines.  Post-processors may choose to ignore
+ * them.
+ */
+void trace2_printf_va_fl(const char *file, int line, const char *fmt,
+                        va_list ap);
+
+#define trace2_printf_va(fmt, ap) \
+       trace2_printf_va_fl(__FILE__, __LINE__, (fmt), (ap))
+
+void trace2_printf_fl(const char *file, int line, const char *fmt, ...);
+
+#ifdef HAVE_VARIADIC_MACROS
+#define trace2_printf(...) trace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)
+#else
+/* clang-format off */
+__attribute__((format (printf, 1, 2)))
+void trace2_printf(const char *fmt, ...);
+/* clang-format on */
+#endif
+
+/*
+ * Optional platform-specific code to dump information about the
+ * current and any parent process(es).  This is intended to allow
+ * post-processors to know who spawned this git instance and anything
+ * else the platform may be able to tell us about the current process.
+ */
+#if defined(GIT_WINDOWS_NATIVE)
+void trace2_collect_process_info(void);
+#else
+#define trace2_collect_process_info() \
+       do {                          \
+       } while (0)
+#endif
+
+#endif /* TRACE2_H */
diff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c
new file mode 100644 (file)
index 0000000..b329921
--- /dev/null
@@ -0,0 +1,90 @@
+#include "cache.h"
+#include "config.h"
+#include "tr2_cfg.h"
+
+#define TR2_ENVVAR_CFG_PARAM "GIT_TR2_CONFIG_PARAMS"
+
+static struct strbuf **tr2_cfg_patterns;
+static int tr2_cfg_count_patterns;
+static int tr2_cfg_loaded;
+
+/*
+ * Parse a string containing a comma-delimited list of config keys
+ * or wildcard patterns into a list of strbufs.
+ */
+static int tr2_cfg_load_patterns(void)
+{
+       struct strbuf **s;
+       const char *envvar;
+
+       if (tr2_cfg_loaded)
+               return tr2_cfg_count_patterns;
+       tr2_cfg_loaded = 1;
+
+       envvar = getenv(TR2_ENVVAR_CFG_PARAM);
+       if (!envvar || !*envvar)
+               return tr2_cfg_count_patterns;
+
+       tr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);
+       for (s = tr2_cfg_patterns; *s; s++) {
+               struct strbuf *buf = *s;
+
+               if (buf->len && buf->buf[buf->len - 1] == ',')
+                       strbuf_setlen(buf, buf->len - 1);
+               strbuf_trim_trailing_newline(*s);
+               strbuf_trim(*s);
+       }
+
+       tr2_cfg_count_patterns = s - tr2_cfg_patterns;
+       return tr2_cfg_count_patterns;
+}
+
+void tr2_cfg_free_patterns(void)
+{
+       if (tr2_cfg_patterns)
+               strbuf_list_free(tr2_cfg_patterns);
+       tr2_cfg_count_patterns = 0;
+       tr2_cfg_loaded = 0;
+}
+
+struct tr2_cfg_data {
+       const char *file;
+       int line;
+};
+
+/*
+ * See if the given config key matches any of our patterns of interest.
+ */
+static int tr2_cfg_cb(const char *key, const char *value, void *d)
+{
+       struct strbuf **s;
+       struct tr2_cfg_data *data = (struct tr2_cfg_data *)d;
+
+       for (s = tr2_cfg_patterns; *s; s++) {
+               struct strbuf *buf = *s;
+               int wm = wildmatch(buf->buf, key, WM_CASEFOLD);
+               if (wm == WM_MATCH) {
+                       trace2_def_param_fl(data->file, data->line, key, value);
+                       return 0;
+               }
+       }
+
+       return 0;
+}
+
+void tr2_cfg_list_config_fl(const char *file, int line)
+{
+       struct tr2_cfg_data data = { file, line };
+
+       if (tr2_cfg_load_patterns() > 0)
+               read_early_config(tr2_cfg_cb, &data);
+}
+
+void tr2_cfg_set_fl(const char *file, int line, const char *key,
+                   const char *value)
+{
+       struct tr2_cfg_data data = { file, line };
+
+       if (tr2_cfg_load_patterns() > 0)
+               tr2_cfg_cb(key, value, &data);
+}
diff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h
new file mode 100644 (file)
index 0000000..d9c98f6
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef TR2_CFG_H
+#define TR2_CFG_H
+
+/*
+ * Iterate over all config settings and emit 'def_param' events for the
+ * "interesting" ones to TRACE2.
+ */
+void tr2_cfg_list_config_fl(const char *file, int line);
+
+/*
+ * Emit a "def_param" event for the given key/value pair IF we consider
+ * the key to be "interesting".
+ */
+void tr2_cfg_set_fl(const char *file, int line, const char *key,
+                   const char *value);
+
+void tr2_cfg_free_patterns(void);
+
+#endif /* TR2_CFG_H */
diff --git a/trace2/tr2_cmd_name.c b/trace2/tr2_cmd_name.c
new file mode 100644 (file)
index 0000000..e999592
--- /dev/null
@@ -0,0 +1,30 @@
+#include "cache.h"
+#include "trace2/tr2_cmd_name.h"
+
+#define TR2_ENVVAR_PARENT_NAME "GIT_TR2_PARENT_NAME"
+
+static struct strbuf tr2cmdname_hierarchy = STRBUF_INIT;
+
+void tr2_cmd_name_append_hierarchy(const char *name)
+{
+       const char *parent_name = getenv(TR2_ENVVAR_PARENT_NAME);
+
+       strbuf_reset(&tr2cmdname_hierarchy);
+       if (parent_name && *parent_name) {
+               strbuf_addstr(&tr2cmdname_hierarchy, parent_name);
+               strbuf_addch(&tr2cmdname_hierarchy, '/');
+       }
+       strbuf_addstr(&tr2cmdname_hierarchy, name);
+
+       setenv(TR2_ENVVAR_PARENT_NAME, tr2cmdname_hierarchy.buf, 1);
+}
+
+const char *tr2_cmd_name_get_hierarchy(void)
+{
+       return tr2cmdname_hierarchy.buf;
+}
+
+void tr2_cmd_name_release(void)
+{
+       strbuf_release(&tr2cmdname_hierarchy);
+}
diff --git a/trace2/tr2_cmd_name.h b/trace2/tr2_cmd_name.h
new file mode 100644 (file)
index 0000000..ab70b67
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef TR2_CMD_NAME_H
+#define TR2_CMD_NAME_H
+
+/*
+ * Append the current command name to the list being maintained
+ * in the environment.
+ *
+ * The hierarchy for a top-level git command is just the current
+ * command name.  For a child git process, the hierarchy includes the
+ * names of the parent processes.
+ *
+ * The hierarchy for the current process will be exported to the
+ * environment and inherited by child processes.
+ */
+void tr2_cmd_name_append_hierarchy(const char *name);
+
+/*
+ * Get the command name hierarchy for the current process.
+ */
+const char *tr2_cmd_name_get_hierarchy(void);
+
+void tr2_cmd_name_release(void);
+
+#endif /* TR2_CMD_NAME_H */
diff --git a/trace2/tr2_dst.c b/trace2/tr2_dst.c
new file mode 100644 (file)
index 0000000..fd490a4
--- /dev/null
@@ -0,0 +1,252 @@
+#include "cache.h"
+#include "trace2/tr2_dst.h"
+
+/*
+ * If a Trace2 target cannot be opened for writing, we should issue a
+ * warning to stderr, but this is very annoying if the target is a pipe
+ * or socket and beyond the user's control -- especially since every
+ * git command (and sub-command) will print the message.  So we silently
+ * eat these warnings and just discard the trace data.
+ *
+ * Enable the following environment variable to see these warnings.
+ */
+#define TR2_ENVVAR_DST_DEBUG "GIT_TR2_DST_DEBUG"
+
+static int tr2_dst_want_warning(void)
+{
+       static int tr2env_dst_debug = -1;
+
+       if (tr2env_dst_debug == -1) {
+               const char *env_value = getenv(TR2_ENVVAR_DST_DEBUG);
+               if (!env_value || !*env_value)
+                       tr2env_dst_debug = 0;
+               else
+                       tr2env_dst_debug = atoi(env_value) > 0;
+       }
+
+       return tr2env_dst_debug;
+}
+
+void tr2_dst_trace_disable(struct tr2_dst *dst)
+{
+       if (dst->need_close)
+               close(dst->fd);
+       dst->fd = 0;
+       dst->initialized = 1;
+       dst->need_close = 0;
+}
+
+static int tr2_dst_try_path(struct tr2_dst *dst, const char *tgt_value)
+{
+       int fd = open(tgt_value, O_WRONLY | O_APPEND | O_CREAT, 0666);
+       if (fd == -1) {
+               if (tr2_dst_want_warning())
+                       warning("trace2: could not open '%s' for '%s' tracing: %s",
+                               tgt_value, dst->env_var_name, strerror(errno));
+
+               tr2_dst_trace_disable(dst);
+               return 0;
+       }
+
+       dst->fd = fd;
+       dst->need_close = 1;
+       dst->initialized = 1;
+
+       return dst->fd;
+}
+
+#ifndef NO_UNIX_SOCKETS
+#define PREFIX_AF_UNIX "af_unix:"
+#define PREFIX_AF_UNIX_STREAM "af_unix:stream:"
+#define PREFIX_AF_UNIX_DGRAM "af_unix:dgram:"
+
+static int tr2_dst_try_uds_connect(const char *path, int sock_type, int *out_fd)
+{
+       int fd;
+       struct sockaddr_un sa;
+
+       fd = socket(AF_UNIX, sock_type, 0);
+       if (fd == -1)
+               return errno;
+
+       sa.sun_family = AF_UNIX;
+       strlcpy(sa.sun_path, path, sizeof(sa.sun_path));
+
+       if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
+               int e = errno;
+               close(fd);
+               return e;
+       }
+
+       *out_fd = fd;
+       return 0;
+}
+
+#define TR2_DST_UDS_TRY_STREAM (1 << 0)
+#define TR2_DST_UDS_TRY_DGRAM  (1 << 1)
+
+static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst,
+                                         const char *tgt_value)
+{
+       unsigned int uds_try = 0;
+       int fd;
+       int e;
+       const char *path = NULL;
+
+       /*
+        * Allow "af_unix:[<type>:]<absolute_path>"
+        *
+        * Trace2 always writes complete individual messages (without
+        * chunking), so we can talk to either DGRAM or STREAM type sockets.
+        *
+        * Allow the user to explicitly request the socket type.
+        *
+        * If they omit the socket type, try one and then the other.
+        */
+
+       if (skip_prefix(tgt_value, PREFIX_AF_UNIX_STREAM, &path))
+               uds_try |= TR2_DST_UDS_TRY_STREAM;
+
+       else if (skip_prefix(tgt_value, PREFIX_AF_UNIX_DGRAM, &path))
+               uds_try |= TR2_DST_UDS_TRY_DGRAM;
+
+       else if (skip_prefix(tgt_value, PREFIX_AF_UNIX, &path))
+               uds_try |= TR2_DST_UDS_TRY_STREAM | TR2_DST_UDS_TRY_DGRAM;
+
+       if (!path || !*path) {
+               if (tr2_dst_want_warning())
+                       warning("trace2: invalid AF_UNIX value '%s' for '%s' tracing",
+                               tgt_value, dst->env_var_name);
+
+               tr2_dst_trace_disable(dst);
+               return 0;
+       }
+
+       if (!is_absolute_path(path) ||
+           strlen(path) >= sizeof(((struct sockaddr_un *)0)->sun_path)) {
+               if (tr2_dst_want_warning())
+                       warning("trace2: invalid AF_UNIX path '%s' for '%s' tracing",
+                               path, dst->env_var_name);
+
+               tr2_dst_trace_disable(dst);
+               return 0;
+       }
+
+       if (uds_try & TR2_DST_UDS_TRY_STREAM) {
+               e = tr2_dst_try_uds_connect(path, SOCK_STREAM, &fd);
+               if (!e)
+                       goto connected;
+               if (e != EPROTOTYPE)
+                       goto error;
+       }
+       if (uds_try & TR2_DST_UDS_TRY_DGRAM) {
+               e = tr2_dst_try_uds_connect(path, SOCK_DGRAM, &fd);
+               if (!e)
+                       goto connected;
+       }
+
+error:
+       if (tr2_dst_want_warning())
+               warning("trace2: could not connect to socket '%s' for '%s' tracing: %s",
+                       path, dst->env_var_name, strerror(e));
+
+       tr2_dst_trace_disable(dst);
+       return 0;
+
+connected:
+       dst->fd = fd;
+       dst->need_close = 1;
+       dst->initialized = 1;
+
+       return dst->fd;
+}
+#endif
+
+static void tr2_dst_malformed_warning(struct tr2_dst *dst,
+                                     const char *tgt_value)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       strbuf_addf(&buf, "trace2: unknown value for '%s': '%s'",
+                   dst->env_var_name, tgt_value);
+       warning("%s", buf.buf);
+
+       strbuf_release(&buf);
+}
+
+int tr2_dst_get_trace_fd(struct tr2_dst *dst)
+{
+       const char *tgt_value;
+
+       /* don't open twice */
+       if (dst->initialized)
+               return dst->fd;
+
+       dst->initialized = 1;
+
+       tgt_value = getenv(dst->env_var_name);
+
+       if (!tgt_value || !strcmp(tgt_value, "") || !strcmp(tgt_value, "0") ||
+           !strcasecmp(tgt_value, "false")) {
+               dst->fd = 0;
+               return dst->fd;
+       }
+
+       if (!strcmp(tgt_value, "1") || !strcasecmp(tgt_value, "true")) {
+               dst->fd = STDERR_FILENO;
+               return dst->fd;
+       }
+
+       if (strlen(tgt_value) == 1 && isdigit(*tgt_value)) {
+               dst->fd = atoi(tgt_value);
+               return dst->fd;
+       }
+
+       if (is_absolute_path(tgt_value))
+               return tr2_dst_try_path(dst, tgt_value);
+
+#ifndef NO_UNIX_SOCKETS
+       if (starts_with(tgt_value, PREFIX_AF_UNIX))
+               return tr2_dst_try_unix_domain_socket(dst, tgt_value);
+#endif
+
+       /* Always warn about malformed values. */
+       tr2_dst_malformed_warning(dst, tgt_value);
+       tr2_dst_trace_disable(dst);
+       return 0;
+}
+
+int tr2_dst_trace_want(struct tr2_dst *dst)
+{
+       return !!tr2_dst_get_trace_fd(dst);
+}
+
+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)
+{
+       int fd = tr2_dst_get_trace_fd(dst);
+
+       strbuf_complete_line(buf_line); /* ensure final NL on buffer */
+
+       /*
+        * We do not use write_in_full() because we do not want
+        * a short-write to try again.  We are using O_APPEND mode
+        * files and the kernel handles the atomic seek+write. If
+        * another thread or git process is concurrently writing to
+        * this fd or file, our remainder-write may not be contiguous
+        * with our initial write of this message.  And that will
+        * confuse readers.  So just don't bother.
+        *
+        * It is assumed that TRACE2 messages are short enough that
+        * the system can write them in 1 attempt and we won't see
+        * a short-write.
+        *
+        * If we get an IO error, just close the trace dst.
+        */
+       if (write(fd, buf_line->buf, buf_line->len) >= 0)
+               return;
+
+       if (tr2_dst_want_warning())
+               warning("unable to write trace to '%s': %s", dst->env_var_name,
+                       strerror(errno));
+       tr2_dst_trace_disable(dst);
+}
diff --git a/trace2/tr2_dst.h b/trace2/tr2_dst.h
new file mode 100644 (file)
index 0000000..9a64f05
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef TR2_DST_H
+#define TR2_DST_H
+
+struct strbuf;
+
+struct tr2_dst {
+       const char *const env_var_name;
+       int fd;
+       unsigned int initialized : 1;
+       unsigned int need_close : 1;
+};
+
+/*
+ * Disable TRACE2 on the destination.  In TRACE2 a destination (DST)
+ * wraps a file descriptor; it is associated with a TARGET which
+ * defines the formatting.
+ */
+void tr2_dst_trace_disable(struct tr2_dst *dst);
+
+/*
+ * Return the file descriptor for the DST.
+ * If 0, the dst is closed or disabled.
+ */
+int tr2_dst_get_trace_fd(struct tr2_dst *dst);
+
+/*
+ * Return true if the DST is opened for writing.
+ */
+int tr2_dst_trace_want(struct tr2_dst *dst);
+
+/*
+ * Write a single line/message to the trace file.
+ */
+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line);
+
+#endif /* TR2_DST_H */
diff --git a/trace2/tr2_sid.c b/trace2/tr2_sid.c
new file mode 100644 (file)
index 0000000..984524a
--- /dev/null
@@ -0,0 +1,67 @@
+#include "cache.h"
+#include "trace2/tr2_sid.h"
+
+#define TR2_ENVVAR_PARENT_SID "GIT_TR2_PARENT_SID"
+
+static struct strbuf tr2sid_buf = STRBUF_INIT;
+static int tr2sid_nr_git_parents;
+
+/*
+ * Compute a "unique" session id (SID) for the current process.  This allows
+ * all events from this process to have a single label (much like a PID).
+ *
+ * Export this into our environment so that all child processes inherit it.
+ *
+ * If we were started by another git instance, use our parent's SID as a
+ * prefix.  (This lets us track parent/child relationships even if there
+ * is an intermediate shell process.)
+ *
+ * Additionally, count the number of nested git processes.
+ */
+static void tr2_sid_compute(void)
+{
+       uint64_t us_now;
+       const char *parent_sid;
+
+       if (tr2sid_buf.len)
+               return;
+
+       parent_sid = getenv(TR2_ENVVAR_PARENT_SID);
+       if (parent_sid && *parent_sid) {
+               const char *p;
+               for (p = parent_sid; *p; p++)
+                       if (*p == '/')
+                               tr2sid_nr_git_parents++;
+
+               strbuf_addstr(&tr2sid_buf, parent_sid);
+               strbuf_addch(&tr2sid_buf, '/');
+               tr2sid_nr_git_parents++;
+       }
+
+       us_now = getnanotime() / 1000;
+       strbuf_addf(&tr2sid_buf, "%" PRIuMAX "-%" PRIdMAX, (uintmax_t)us_now,
+                   (intmax_t)getpid());
+
+       setenv(TR2_ENVVAR_PARENT_SID, tr2sid_buf.buf, 1);
+}
+
+const char *tr2_sid_get(void)
+{
+       if (!tr2sid_buf.len)
+               tr2_sid_compute();
+
+       return tr2sid_buf.buf;
+}
+
+int tr2_sid_depth(void)
+{
+       if (!tr2sid_buf.len)
+               tr2_sid_compute();
+
+       return tr2sid_nr_git_parents;
+}
+
+void tr2_sid_release(void)
+{
+       strbuf_release(&tr2sid_buf);
+}
diff --git a/trace2/tr2_sid.h b/trace2/tr2_sid.h
new file mode 100644 (file)
index 0000000..9bef321
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef TR2_SID_H
+#define TR2_SID_H
+
+/*
+ * Get our session id. Compute if necessary.
+ */
+const char *tr2_sid_get(void);
+
+/*
+ * Get our process depth.  A top-level git process invoked from the
+ * command line will have depth=0.  A child git process will have
+ * depth=1 and so on.
+ */
+int tr2_sid_depth(void);
+
+void tr2_sid_release(void);
+
+#endif /* TR2_SID_H */
diff --git a/trace2/tr2_tbuf.c b/trace2/tr2_tbuf.c
new file mode 100644 (file)
index 0000000..0844910
--- /dev/null
@@ -0,0 +1,32 @@
+#include "cache.h"
+#include "tr2_tbuf.h"
+
+void tr2_tbuf_local_time(struct tr2_tbuf *tb)
+{
+       struct timeval tv;
+       struct tm tm;
+       time_t secs;
+
+       gettimeofday(&tv, NULL);
+       secs = tv.tv_sec;
+       localtime_r(&secs, &tm);
+
+       xsnprintf(tb->buf, sizeof(tb->buf), "%02d:%02d:%02d.%06ld", tm.tm_hour,
+                 tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
+}
+
+void tr2_tbuf_utc_time(struct tr2_tbuf *tb)
+{
+       struct timeval tv;
+       struct tm tm;
+       time_t secs;
+
+       gettimeofday(&tv, NULL);
+       secs = tv.tv_sec;
+       gmtime_r(&secs, &tm);
+
+       xsnprintf(tb->buf, sizeof(tb->buf),
+                 "%4d-%02d-%02d %02d:%02d:%02d.%06ld", tm.tm_year + 1900,
+                 tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+                 (long)tv.tv_usec);
+}
diff --git a/trace2/tr2_tbuf.h b/trace2/tr2_tbuf.h
new file mode 100644 (file)
index 0000000..9cdefa3
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef TR2_TBUF_H
+#define TR2_TBUF_H
+
+/*
+ * A simple wrapper around a fixed buffer to avoid C syntax
+ * quirks and the need to pass around an additional size_t
+ * argument.
+ */
+struct tr2_tbuf {
+       char buf[32];
+};
+
+/*
+ * Fill buffer with formatted local time string.
+ */
+void tr2_tbuf_local_time(struct tr2_tbuf *tb);
+
+/*
+ * Fill buffer with formatted UTC time string.
+ */
+void tr2_tbuf_utc_time(struct tr2_tbuf *tb);
+
+#endif /* TR2_TBUF_H */
diff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h
new file mode 100644 (file)
index 0000000..297bb8f
--- /dev/null
@@ -0,0 +1,133 @@
+#ifndef TR2_TGT_H
+#define TR2_TGT_H
+
+struct child_process;
+struct repository;
+struct json_writer;
+
+/*
+ * Function prototypes for a TRACE2 "target" vtable.
+ */
+
+typedef int(tr2_tgt_init_t)(void);
+typedef void(tr2_tgt_term_t)(void);
+
+typedef void(tr2_tgt_evt_version_fl_t)(const char *file, int line);
+
+typedef void(tr2_tgt_evt_start_fl_t)(const char *file, int line,
+                                    const char **argv);
+typedef void(tr2_tgt_evt_exit_fl_t)(const char *file, int line,
+                                   uint64_t us_elapsed_absolute, int code);
+typedef void(tr2_tgt_evt_signal_t)(uint64_t us_elapsed_absolute, int signo);
+typedef void(tr2_tgt_evt_atexit_t)(uint64_t us_elapsed_absolute, int code);
+
+typedef void(tr2_tgt_evt_error_va_fl_t)(const char *file, int line,
+                                       const char *fmt, va_list ap);
+
+typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,
+                                           const char *command_path);
+typedef void(tr2_tgt_evt_command_name_fl_t)(const char *file, int line,
+                                           const char *name,
+                                           const char *hierarchy);
+typedef void(tr2_tgt_evt_command_mode_fl_t)(const char *file, int line,
+                                           const char *mode);
+
+typedef void(tr2_tgt_evt_alias_fl_t)(const char *file, int line,
+                                    const char *alias, const char **argv);
+
+typedef void(tr2_tgt_evt_child_start_fl_t)(const char *file, int line,
+                                          uint64_t us_elapsed_absolute,
+                                          const struct child_process *cmd);
+typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,
+                                         uint64_t us_elapsed_absolute, int cid,
+                                         int pid, int code,
+                                         uint64_t us_elapsed_child);
+
+typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,
+                                           uint64_t us_elapsed_absolute);
+typedef void(tr2_tgt_evt_thread_exit_fl_t)(const char *file, int line,
+                                          uint64_t us_elapsed_absolute,
+                                          uint64_t us_elapsed_thread);
+
+typedef void(tr2_tgt_evt_exec_fl_t)(const char *file, int line,
+                                   uint64_t us_elapsed_absolute, int exec_id,
+                                   const char *exe, const char **argv);
+typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,
+                                          uint64_t us_elapsed_absolute,
+                                          int exec_id, int code);
+
+typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,
+                                    const char *param, const char *value);
+
+typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,
+                                   const struct repository *repo);
+
+typedef void(tr2_tgt_evt_region_enter_printf_va_fl_t)(
+       const char *file, int line, uint64_t us_elapsed_absolute,
+       const char *category, const char *label, const struct repository *repo,
+       const char *fmt, va_list ap);
+typedef void(tr2_tgt_evt_region_leave_printf_va_fl_t)(
+       const char *file, int line, uint64_t us_elapsed_absolute,
+       uint64_t us_elapsed_region, const char *category, const char *label,
+       const struct repository *repo, const char *fmt, va_list ap);
+
+typedef void(tr2_tgt_evt_data_fl_t)(const char *file, int line,
+                                   uint64_t us_elapsed_absolute,
+                                   uint64_t us_elapsed_region,
+                                   const char *category,
+                                   const struct repository *repo,
+                                   const char *key, const char *value);
+typedef void(tr2_tgt_evt_data_json_fl_t)(const char *file, int line,
+                                        uint64_t us_elapsed_absolute,
+                                        uint64_t us_elapsed_region,
+                                        const char *category,
+                                        const struct repository *repo,
+                                        const char *key,
+                                        const struct json_writer *value);
+
+typedef void(tr2_tgt_evt_printf_va_fl_t)(const char *file, int line,
+                                        uint64_t us_elapsed_absolute,
+                                        const char *fmt, va_list ap);
+
+/*
+ * "vtable" for a TRACE2 target.  Use NULL if a target does not want
+ * to emit that message.
+ */
+/* clang-format off */
+struct tr2_tgt {
+       struct tr2_dst                          *pdst;
+
+       tr2_tgt_init_t                          *pfn_init;
+       tr2_tgt_term_t                          *pfn_term;
+
+       tr2_tgt_evt_version_fl_t                *pfn_version_fl;
+       tr2_tgt_evt_start_fl_t                  *pfn_start_fl;
+       tr2_tgt_evt_exit_fl_t                   *pfn_exit_fl;
+       tr2_tgt_evt_signal_t                    *pfn_signal;
+       tr2_tgt_evt_atexit_t                    *pfn_atexit;
+       tr2_tgt_evt_error_va_fl_t               *pfn_error_va_fl;
+       tr2_tgt_evt_command_path_fl_t           *pfn_command_path_fl;
+       tr2_tgt_evt_command_name_fl_t           *pfn_command_name_fl;
+       tr2_tgt_evt_command_mode_fl_t           *pfn_command_mode_fl;
+       tr2_tgt_evt_alias_fl_t                  *pfn_alias_fl;
+       tr2_tgt_evt_child_start_fl_t            *pfn_child_start_fl;
+       tr2_tgt_evt_child_exit_fl_t             *pfn_child_exit_fl;
+       tr2_tgt_evt_thread_start_fl_t           *pfn_thread_start_fl;
+       tr2_tgt_evt_thread_exit_fl_t            *pfn_thread_exit_fl;
+       tr2_tgt_evt_exec_fl_t                   *pfn_exec_fl;
+       tr2_tgt_evt_exec_result_fl_t            *pfn_exec_result_fl;
+       tr2_tgt_evt_param_fl_t                  *pfn_param_fl;
+       tr2_tgt_evt_repo_fl_t                   *pfn_repo_fl;
+       tr2_tgt_evt_region_enter_printf_va_fl_t *pfn_region_enter_printf_va_fl;
+       tr2_tgt_evt_region_leave_printf_va_fl_t *pfn_region_leave_printf_va_fl;
+       tr2_tgt_evt_data_fl_t                   *pfn_data_fl;
+       tr2_tgt_evt_data_json_fl_t              *pfn_data_json_fl;
+       tr2_tgt_evt_printf_va_fl_t              *pfn_printf_va_fl;
+};
+/* clang-format on */
+
+extern struct tr2_tgt tr2_tgt_event;
+extern struct tr2_tgt tr2_tgt_normal;
+extern struct tr2_tgt tr2_tgt_perf;
+
+#endif /* TR2_TGT_H */
diff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c
new file mode 100644 (file)
index 0000000..107cb53
--- /dev/null
@@ -0,0 +1,588 @@
+#include "cache.h"
+#include "config.h"
+#include "json-writer.h"
+#include "run-command.h"
+#include "version.h"
+#include "trace2/tr2_dst.h"
+#include "trace2/tr2_tbuf.h"
+#include "trace2/tr2_sid.h"
+#include "trace2/tr2_tgt.h"
+#include "trace2/tr2_tls.h"
+
+static struct tr2_dst tr2dst_event = { "GIT_TR2_EVENT", 0, 0, 0 };
+
+/*
+ * The version number of the JSON data generated by the EVENT target
+ * in this source file.  Update this if you make a significant change
+ * to the JSON fields or message structure.  You probably do not need
+ * to update this if you just add another call to one of the existing
+ * TRACE2 API methods.
+ */
+#define TR2_EVENT_VERSION "1"
+
+/*
+ * Region nesting limit for messages written to the event target.
+ *
+ * The "region_enter" and "region_leave" messages (especially recursive
+ * messages such as those produced while diving the worktree or index)
+ * are primarily intended for the performance target during debugging.
+ *
+ * Some of the outer-most messages, however, may be of interest to the
+ * event target.  Set this environment variable to a larger integer for
+ * more detail in the event target.
+ */
+#define TR2_ENVVAR_EVENT_NESTING "GIT_TR2_EVENT_NESTING"
+static int tr2env_event_nesting_wanted = 2;
+
+/*
+ * Set this environment variable to true to omit the <time>, <file>, and
+ * <line> fields from most events.
+ */
+#define TR2_ENVVAR_EVENT_BRIEF "GIT_TR2_EVENT_BRIEF"
+static int tr2env_event_brief;
+
+static int fn_init(void)
+{
+       int want = tr2_dst_trace_want(&tr2dst_event);
+       int want_nesting;
+       int want_brief;
+       char *nesting;
+       char *brief;
+
+       if (!want)
+               return want;
+
+       nesting = getenv(TR2_ENVVAR_EVENT_NESTING);
+       if (nesting && ((want_nesting = atoi(nesting)) > 0))
+               tr2env_event_nesting_wanted = want_nesting;
+
+       brief = getenv(TR2_ENVVAR_EVENT_BRIEF);
+       if (brief && ((want_brief = atoi(brief)) > 0))
+               tr2env_event_brief = want_brief;
+
+       return want;
+}
+
+static void fn_term(void)
+{
+       tr2_dst_trace_disable(&tr2dst_event);
+}
+
+/*
+ * Append common key-value pairs to the currently open JSON object.
+ *     "event:"<event_name>"
+ *      "sid":"<sid>"
+ *   "thread":"<thread_name>"
+ *     "time":"<time>"
+ *     "file":"<filename>"
+ *     "line":<line_number>
+ *     "repo":<repo_id>
+ */
+static void event_fmt_prepare(const char *event_name, const char *file,
+                             int line, const struct repository *repo,
+                             struct json_writer *jw)
+{
+       struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
+       struct tr2_tbuf tb_now;
+
+       jw_object_string(jw, "event", event_name);
+       jw_object_string(jw, "sid", tr2_sid_get());
+       jw_object_string(jw, "thread", ctx->thread_name.buf);
+
+       /*
+        * In brief mode, only emit <time> on these 2 event types.
+        */
+       if (!tr2env_event_brief || !strcmp(event_name, "version") ||
+           !strcmp(event_name, "atexit")) {
+               tr2_tbuf_utc_time(&tb_now);
+               jw_object_string(jw, "time", tb_now.buf);
+       }
+
+       if (!tr2env_event_brief && file && *file) {
+               jw_object_string(jw, "file", file);
+               jw_object_intmax(jw, "line", line);
+       }
+
+       if (repo)
+               jw_object_intmax(jw, "repo", repo->trace2_repo_id);
+}
+
+static void fn_version_fl(const char *file, int line)
+{
+       const char *event_name = "version";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_string(&jw, "evt", TR2_EVENT_VERSION);
+       jw_object_string(&jw, "exe", git_version_string);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_start_fl(const char *file, int line, const char **argv)
+{
+       const char *event_name = "start";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_inline_begin_array(&jw, "argv");
+       jw_array_argv(&jw, argv);
+       jw_end(&jw);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,
+                      int code)
+{
+       const char *event_name = "exit";
+       struct json_writer jw = JSON_WRITER_INIT;
+       double t_abs = (double)us_elapsed_absolute / 1000000.0;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_double(&jw, "t_abs", 6, t_abs);
+       jw_object_intmax(&jw, "code", code);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_signal(uint64_t us_elapsed_absolute, int signo)
+{
+       const char *event_name = "signal";
+       struct json_writer jw = JSON_WRITER_INIT;
+       double t_abs = (double)us_elapsed_absolute / 1000000.0;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);
+       jw_object_double(&jw, "t_abs", 6, t_abs);
+       jw_object_intmax(&jw, "signo", signo);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_atexit(uint64_t us_elapsed_absolute, int code)
+{
+       const char *event_name = "atexit";
+       struct json_writer jw = JSON_WRITER_INIT;
+       double t_abs = (double)us_elapsed_absolute / 1000000.0;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);
+       jw_object_double(&jw, "t_abs", 6, t_abs);
+       jw_object_intmax(&jw, "code", code);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void maybe_add_string_va(struct json_writer *jw, const char *field_name,
+                               const char *fmt, va_list ap)
+{
+       if (fmt && *fmt && ap) {
+               va_list copy_ap;
+               struct strbuf buf = STRBUF_INIT;
+
+               va_copy(copy_ap, ap);
+               strbuf_vaddf(&buf, fmt, copy_ap);
+               va_end(copy_ap);
+
+               jw_object_string(jw, field_name, buf.buf);
+               strbuf_release(&buf);
+               return;
+       }
+
+       if (fmt && *fmt) {
+               jw_object_string(jw, field_name, fmt);
+               return;
+       }
+}
+
+static void fn_error_va_fl(const char *file, int line, const char *fmt,
+                          va_list ap)
+{
+       const char *event_name = "error";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       maybe_add_string_va(&jw, "msg", fmt, ap);
+       /*
+        * Also emit the format string as a field in case
+        * post-processors want to aggregate common error
+        * messages by type without argument fields (such
+        * as pathnames or branch names) cluttering it up.
+        */
+       if (fmt && *fmt)
+               jw_object_string(&jw, "fmt", fmt);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_command_path_fl(const char *file, int line, const char *pathname)
+{
+       const char *event_name = "cmd_path";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_string(&jw, "path", pathname);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_command_name_fl(const char *file, int line, const char *name,
+                              const char *hierarchy)
+{
+       const char *event_name = "cmd_name";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_string(&jw, "name", name);
+       if (hierarchy && *hierarchy)
+               jw_object_string(&jw, "hierarchy", hierarchy);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_command_mode_fl(const char *file, int line, const char *mode)
+{
+       const char *event_name = "cmd_mode";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_string(&jw, "name", mode);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_alias_fl(const char *file, int line, const char *alias,
+                       const char **argv)
+{
+       const char *event_name = "alias";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_string(&jw, "alias", alias);
+       jw_object_inline_begin_array(&jw, "argv");
+       jw_array_argv(&jw, argv);
+       jw_end(&jw);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_child_start_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute,
+                             const struct child_process *cmd)
+{
+       const char *event_name = "child_start";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_intmax(&jw, "child_id", cmd->trace2_child_id);
+       if (cmd->trace2_hook_name) {
+               jw_object_string(&jw, "child_class", "hook");
+               jw_object_string(&jw, "hook_name", cmd->trace2_hook_name);
+       } else {
+               const char *child_class =
+                       cmd->trace2_child_class ? cmd->trace2_child_class : "?";
+               jw_object_string(&jw, "child_class", child_class);
+       }
+       if (cmd->dir)
+               jw_object_string(&jw, "cd", cmd->dir);
+       jw_object_bool(&jw, "use_shell", cmd->use_shell);
+       jw_object_inline_begin_array(&jw, "argv");
+       if (cmd->git_cmd)
+               jw_array_string(&jw, "git");
+       jw_array_argv(&jw, cmd->argv);
+       jw_end(&jw);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_child_exit_fl(const char *file, int line,
+                            uint64_t us_elapsed_absolute, int cid, int pid,
+                            int code, uint64_t us_elapsed_child)
+{
+       const char *event_name = "child_exit";
+       struct json_writer jw = JSON_WRITER_INIT;
+       double t_rel = (double)us_elapsed_child / 1000000.0;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_intmax(&jw, "child_id", cid);
+       jw_object_intmax(&jw, "pid", pid);
+       jw_object_intmax(&jw, "code", code);
+       jw_object_double(&jw, "t_rel", 6, t_rel);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+
+       jw_release(&jw);
+}
+
+static void fn_thread_start_fl(const char *file, int line,
+                              uint64_t us_elapsed_absolute)
+{
+       const char *event_name = "thread_start";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_thread_exit_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute,
+                             uint64_t us_elapsed_thread)
+{
+       const char *event_name = "thread_exit";
+       struct json_writer jw = JSON_WRITER_INIT;
+       double t_rel = (double)us_elapsed_thread / 1000000.0;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_double(&jw, "t_rel", 6, t_rel);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,
+                      int exec_id, const char *exe, const char **argv)
+{
+       const char *event_name = "exec";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_intmax(&jw, "exec_id", exec_id);
+       if (exe)
+               jw_object_string(&jw, "exe", exe);
+       jw_object_inline_begin_array(&jw, "argv");
+       jw_array_argv(&jw, argv);
+       jw_end(&jw);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_exec_result_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute, int exec_id,
+                             int code)
+{
+       const char *event_name = "exec_result";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_intmax(&jw, "exec_id", exec_id);
+       jw_object_intmax(&jw, "code", code);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_param_fl(const char *file, int line, const char *param,
+                       const char *value)
+{
+       const char *event_name = "def_param";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_string(&jw, "param", param);
+       jw_object_string(&jw, "value", value);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_repo_fl(const char *file, int line,
+                      const struct repository *repo)
+{
+       const char *event_name = "def_repo";
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, repo, &jw);
+       jw_object_string(&jw, "worktree", repo->worktree);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
+static void fn_region_enter_printf_va_fl(const char *file, int line,
+                                        uint64_t us_elapsed_absolute,
+                                        const char *category,
+                                        const char *label,
+                                        const struct repository *repo,
+                                        const char *fmt, va_list ap)
+{
+       const char *event_name = "region_enter";
+       struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
+       if (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {
+               struct json_writer jw = JSON_WRITER_INIT;
+
+               jw_object_begin(&jw, 0);
+               event_fmt_prepare(event_name, file, line, repo, &jw);
+               jw_object_intmax(&jw, "nesting", ctx->nr_open_regions);
+               if (category)
+                       jw_object_string(&jw, "category", category);
+               if (label)
+                       jw_object_string(&jw, "label", label);
+               maybe_add_string_va(&jw, "msg", fmt, ap);
+               jw_end(&jw);
+
+               tr2_dst_write_line(&tr2dst_event, &jw.json);
+               jw_release(&jw);
+       }
+}
+
+static void fn_region_leave_printf_va_fl(
+       const char *file, int line, uint64_t us_elapsed_absolute,
+       uint64_t us_elapsed_region, const char *category, const char *label,
+       const struct repository *repo, const char *fmt, va_list ap)
+{
+       const char *event_name = "region_leave";
+       struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
+       if (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {
+               struct json_writer jw = JSON_WRITER_INIT;
+               double t_rel = (double)us_elapsed_region / 1000000.0;
+
+               jw_object_begin(&jw, 0);
+               event_fmt_prepare(event_name, file, line, repo, &jw);
+               jw_object_double(&jw, "t_rel", 6, t_rel);
+               jw_object_intmax(&jw, "nesting", ctx->nr_open_regions);
+               if (category)
+                       jw_object_string(&jw, "category", category);
+               if (label)
+                       jw_object_string(&jw, "label", label);
+               maybe_add_string_va(&jw, "msg", fmt, ap);
+               jw_end(&jw);
+
+               tr2_dst_write_line(&tr2dst_event, &jw.json);
+               jw_release(&jw);
+       }
+}
+
+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,
+                      uint64_t us_elapsed_region, const char *category,
+                      const struct repository *repo, const char *key,
+                      const char *value)
+{
+       const char *event_name = "data";
+       struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
+       if (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {
+               struct json_writer jw = JSON_WRITER_INIT;
+               double t_abs = (double)us_elapsed_absolute / 1000000.0;
+               double t_rel = (double)us_elapsed_region / 1000000.0;
+
+               jw_object_begin(&jw, 0);
+               event_fmt_prepare(event_name, file, line, repo, &jw);
+               jw_object_double(&jw, "t_abs", 6, t_abs);
+               jw_object_double(&jw, "t_rel", 6, t_rel);
+               jw_object_intmax(&jw, "nesting", ctx->nr_open_regions);
+               jw_object_string(&jw, "category", category);
+               jw_object_string(&jw, "key", key);
+               jw_object_string(&jw, "value", value);
+               jw_end(&jw);
+
+               tr2_dst_write_line(&tr2dst_event, &jw.json);
+               jw_release(&jw);
+       }
+}
+
+static void fn_data_json_fl(const char *file, int line,
+                           uint64_t us_elapsed_absolute,
+                           uint64_t us_elapsed_region, const char *category,
+                           const struct repository *repo, const char *key,
+                           const struct json_writer *value)
+{
+       const char *event_name = "data_json";
+       struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
+       if (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {
+               struct json_writer jw = JSON_WRITER_INIT;
+               double t_abs = (double)us_elapsed_absolute / 1000000.0;
+               double t_rel = (double)us_elapsed_region / 1000000.0;
+
+               jw_object_begin(&jw, 0);
+               event_fmt_prepare(event_name, file, line, repo, &jw);
+               jw_object_double(&jw, "t_abs", 6, t_abs);
+               jw_object_double(&jw, "t_rel", 6, t_rel);
+               jw_object_intmax(&jw, "nesting", ctx->nr_open_regions);
+               jw_object_string(&jw, "category", category);
+               jw_object_string(&jw, "key", key);
+               jw_object_sub_jw(&jw, "value", value);
+               jw_end(&jw);
+
+               tr2_dst_write_line(&tr2dst_event, &jw.json);
+               jw_release(&jw);
+       }
+}
+
+struct tr2_tgt tr2_tgt_event = {
+       &tr2dst_event,
+
+       fn_init,
+       fn_term,
+
+       fn_version_fl,
+       fn_start_fl,
+       fn_exit_fl,
+       fn_signal,
+       fn_atexit,
+       fn_error_va_fl,
+       fn_command_path_fl,
+       fn_command_name_fl,
+       fn_command_mode_fl,
+       fn_alias_fl,
+       fn_child_start_fl,
+       fn_child_exit_fl,
+       fn_thread_start_fl,
+       fn_thread_exit_fl,
+       fn_exec_fl,
+       fn_exec_result_fl,
+       fn_param_fl,
+       fn_repo_fl,
+       fn_region_enter_printf_va_fl,
+       fn_region_leave_printf_va_fl,
+       fn_data_fl,
+       fn_data_json_fl,
+       NULL, /* printf */
+};
diff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c
new file mode 100644 (file)
index 0000000..547183d
--- /dev/null
@@ -0,0 +1,323 @@
+#include "cache.h"
+#include "config.h"
+#include "run-command.h"
+#include "quote.h"
+#include "version.h"
+#include "trace2/tr2_dst.h"
+#include "trace2/tr2_tbuf.h"
+#include "trace2/tr2_tgt.h"
+#include "trace2/tr2_tls.h"
+
+static struct tr2_dst tr2dst_normal = { "GIT_TR2", 0, 0, 0 };
+
+/*
+ * Set this environment variable to true to omit the "<time> <file>:<line>"
+ * fields from each line written to the builtin normal target.
+ *
+ * Unit tests may want to use this to help with testing.
+ */
+#define TR2_ENVVAR_NORMAL_BRIEF "GIT_TR2_BRIEF"
+static int tr2env_normal_brief;
+
+#define TR2FMT_NORMAL_FL_WIDTH (50)
+
+static int fn_init(void)
+{
+       int want = tr2_dst_trace_want(&tr2dst_normal);
+       int want_brief;
+       char *brief;
+
+       if (!want)
+               return want;
+
+       brief = getenv(TR2_ENVVAR_NORMAL_BRIEF);
+       if (brief && *brief &&
+           ((want_brief = git_parse_maybe_bool(brief)) != -1))
+               tr2env_normal_brief = want_brief;
+
+       return want;
+}
+
+static void fn_term(void)
+{
+       tr2_dst_trace_disable(&tr2dst_normal);
+}
+
+static void normal_fmt_prepare(const char *file, int line, struct strbuf *buf)
+{
+       strbuf_setlen(buf, 0);
+
+       if (!tr2env_normal_brief) {
+               struct tr2_tbuf tb_now;
+
+               tr2_tbuf_local_time(&tb_now);
+               strbuf_addstr(buf, tb_now.buf);
+               strbuf_addch(buf, ' ');
+
+               if (file && *file)
+                       strbuf_addf(buf, "%s:%d ", file, line);
+               while (buf->len < TR2FMT_NORMAL_FL_WIDTH)
+                       strbuf_addch(buf, ' ');
+       }
+}
+
+static void normal_io_write_fl(const char *file, int line,
+                              const struct strbuf *buf_payload)
+{
+       struct strbuf buf_line = STRBUF_INIT;
+
+       normal_fmt_prepare(file, line, &buf_line);
+       strbuf_addbuf(&buf_line, buf_payload);
+       tr2_dst_write_line(&tr2dst_normal, &buf_line);
+       strbuf_release(&buf_line);
+}
+
+static void fn_version_fl(const char *file, int line)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "version %s", git_version_string);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_start_fl(const char *file, int line, const char **argv)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addstr(&buf_payload, "start ");
+       sq_quote_argv_pretty(&buf_payload, argv);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,
+                      int code)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+       double elapsed = (double)us_elapsed_absolute / 1000000.0;
+
+       strbuf_addf(&buf_payload, "exit elapsed:%.6f code:%d", elapsed, code);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_signal(uint64_t us_elapsed_absolute, int signo)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+       double elapsed = (double)us_elapsed_absolute / 1000000.0;
+
+       strbuf_addf(&buf_payload, "signal elapsed:%.6f code:%d", elapsed,
+                   signo);
+       normal_io_write_fl(__FILE__, __LINE__, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_atexit(uint64_t us_elapsed_absolute, int code)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+       double elapsed = (double)us_elapsed_absolute / 1000000.0;
+
+       strbuf_addf(&buf_payload, "atexit elapsed:%.6f code:%d", elapsed, code);
+       normal_io_write_fl(__FILE__, __LINE__, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,
+                                  va_list ap)
+{
+       if (fmt && *fmt && ap) {
+               va_list copy_ap;
+
+               va_copy(copy_ap, ap);
+               strbuf_vaddf(buf, fmt, copy_ap);
+               va_end(copy_ap);
+               return;
+       }
+
+       if (fmt && *fmt) {
+               strbuf_addstr(buf, fmt);
+               return;
+       }
+}
+
+static void fn_error_va_fl(const char *file, int line, const char *fmt,
+                          va_list ap)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addstr(&buf_payload, "error ");
+       maybe_append_string_va(&buf_payload, fmt, ap);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_command_path_fl(const char *file, int line, const char *pathname)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "cmd_path %s", pathname);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_command_name_fl(const char *file, int line, const char *name,
+                              const char *hierarchy)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "cmd_name %s", name);
+       if (hierarchy && *hierarchy)
+               strbuf_addf(&buf_payload, " (%s)", hierarchy);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_command_mode_fl(const char *file, int line, const char *mode)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "cmd_mode %s", mode);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_alias_fl(const char *file, int line, const char *alias,
+                       const char **argv)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "alias %s ->", alias);
+       sq_quote_argv_pretty(&buf_payload, argv);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_child_start_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute,
+                             const struct child_process *cmd)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "child_start[%d] ", cmd->trace2_child_id);
+
+       if (cmd->dir) {
+               strbuf_addstr(&buf_payload, " cd");
+               sq_quote_buf_pretty(&buf_payload, cmd->dir);
+               strbuf_addstr(&buf_payload, "; ");
+       }
+
+       /*
+        * TODO if (cmd->env) { Consider dumping changes to environment. }
+        * See trace_add_env() in run-command.c as used by original trace.c
+        */
+
+       if (cmd->git_cmd)
+               strbuf_addstr(&buf_payload, "git");
+       sq_quote_argv_pretty(&buf_payload, cmd->argv);
+
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_child_exit_fl(const char *file, int line,
+                            uint64_t us_elapsed_absolute, int cid, int pid,
+                            int code, uint64_t us_elapsed_child)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+       double elapsed = (double)us_elapsed_child / 1000000.0;
+
+       strbuf_addf(&buf_payload, "child_exit[%d] pid:%d code:%d elapsed:%.6f",
+                   cid, pid, code, elapsed);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,
+                      int exec_id, const char *exe, const char **argv)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "exec[%d] ", exec_id);
+       if (exe)
+               strbuf_addstr(&buf_payload, exe);
+       sq_quote_argv_pretty(&buf_payload, argv);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_exec_result_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute, int exec_id,
+                             int code)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "exec_result[%d] code:%d", exec_id, code);
+       if (code > 0)
+               strbuf_addf(&buf_payload, " err:%s", strerror(code));
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_param_fl(const char *file, int line, const char *param,
+                       const char *value)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "def_param %s=%s", param, value);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_repo_fl(const char *file, int line,
+                      const struct repository *repo)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addstr(&buf_payload, "worktree ");
+       sq_quote_buf_pretty(&buf_payload, repo->worktree);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_printf_va_fl(const char *file, int line,
+                           uint64_t us_elapsed_absolute, const char *fmt,
+                           va_list ap)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       maybe_append_string_va(&buf_payload, fmt, ap);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+struct tr2_tgt tr2_tgt_normal = {
+       &tr2dst_normal,
+
+       fn_init,
+       fn_term,
+
+       fn_version_fl,
+       fn_start_fl,
+       fn_exit_fl,
+       fn_signal,
+       fn_atexit,
+       fn_error_va_fl,
+       fn_command_path_fl,
+       fn_command_name_fl,
+       fn_command_mode_fl,
+       fn_alias_fl,
+       fn_child_start_fl,
+       fn_child_exit_fl,
+       NULL, /* thread_start */
+       NULL, /* thread_exit */
+       fn_exec_fl,
+       fn_exec_result_fl,
+       fn_param_fl,
+       fn_repo_fl,
+       NULL, /* region_enter */
+       NULL, /* region_leave */
+       NULL, /* data */
+       NULL, /* data_json */
+       fn_printf_va_fl,
+};
diff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c
new file mode 100644 (file)
index 0000000..f0746fc
--- /dev/null
@@ -0,0 +1,534 @@
+#include "cache.h"
+#include "config.h"
+#include "run-command.h"
+#include "quote.h"
+#include "version.h"
+#include "json-writer.h"
+#include "trace2/tr2_dst.h"
+#include "trace2/tr2_sid.h"
+#include "trace2/tr2_tbuf.h"
+#include "trace2/tr2_tgt.h"
+#include "trace2/tr2_tls.h"
+
+static struct tr2_dst tr2dst_perf = { "GIT_TR2_PERF", 0, 0, 0 };
+
+/*
+ * Set this environment variable to true to omit the "<time> <file>:<line>"
+ * fields from each line written to the builtin performance target.
+ *
+ * Unit tests may want to use this to help with testing.
+ */
+#define TR2_ENVVAR_PERF_BRIEF "GIT_TR2_PERF_BRIEF"
+static int tr2env_perf_brief;
+
+#define TR2FMT_PERF_FL_WIDTH (50)
+#define TR2FMT_PERF_MAX_EVENT_NAME (12)
+#define TR2FMT_PERF_REPO_WIDTH (4)
+#define TR2FMT_PERF_CATEGORY_WIDTH (10)
+
+#define TR2_DOTS_BUFFER_SIZE (100)
+#define TR2_INDENT (2)
+#define TR2_INDENT_LENGTH(ctx) (((ctx)->nr_open_regions - 1) * TR2_INDENT)
+
+static struct strbuf dots = STRBUF_INIT;
+
+static int fn_init(void)
+{
+       int want = tr2_dst_trace_want(&tr2dst_perf);
+       int want_brief;
+       char *brief;
+
+       if (!want)
+               return want;
+
+       strbuf_addchars(&dots, '.', TR2_DOTS_BUFFER_SIZE);
+
+       brief = getenv(TR2_ENVVAR_PERF_BRIEF);
+       if (brief && *brief &&
+           ((want_brief = git_parse_maybe_bool(brief)) != -1))
+               tr2env_perf_brief = want_brief;
+
+       return want;
+}
+
+static void fn_term(void)
+{
+       tr2_dst_trace_disable(&tr2dst_perf);
+
+       strbuf_release(&dots);
+}
+
+/*
+ * Format trace line prefix in human-readable classic format for
+ * the performance target:
+ *     "[<time> [<file>:<line>] <bar>] <nr_parents> <bar>
+ *         <thread_name> <bar> <event_name> <bar> [<repo>] <bar>
+ *         [<elapsed_absolute>] [<elapsed_relative>] <bar>
+ *         [<category>] <bar> [<dots>] "
+ */
+static void perf_fmt_prepare(const char *event_name,
+                            struct tr2tls_thread_ctx *ctx, const char *file,
+                            int line, const struct repository *repo,
+                            uint64_t *p_us_elapsed_absolute,
+                            uint64_t *p_us_elapsed_relative,
+                            const char *category, struct strbuf *buf)
+{
+       int len;
+
+       strbuf_setlen(buf, 0);
+
+       if (!tr2env_perf_brief) {
+               struct tr2_tbuf tb_now;
+
+               tr2_tbuf_local_time(&tb_now);
+               strbuf_addstr(buf, tb_now.buf);
+               strbuf_addch(buf, ' ');
+
+               if (file && *file)
+                       strbuf_addf(buf, "%s:%d ", file, line);
+               while (buf->len < TR2FMT_PERF_FL_WIDTH)
+                       strbuf_addch(buf, ' ');
+
+               strbuf_addstr(buf, "| ");
+       }
+
+       strbuf_addf(buf, "d%d | ", tr2_sid_depth());
+       strbuf_addf(buf, "%-*s | %-*s | ", TR2_MAX_THREAD_NAME,
+                   ctx->thread_name.buf, TR2FMT_PERF_MAX_EVENT_NAME,
+                   event_name);
+
+       len = buf->len + TR2FMT_PERF_REPO_WIDTH;
+       if (repo)
+               strbuf_addf(buf, "r%d ", repo->trace2_repo_id);
+       while (buf->len < len)
+               strbuf_addch(buf, ' ');
+       strbuf_addstr(buf, "| ");
+
+       if (p_us_elapsed_absolute)
+               strbuf_addf(buf, "%9.6f | ",
+                           ((double)(*p_us_elapsed_absolute)) / 1000000.0);
+       else
+               strbuf_addf(buf, "%9s | ", " ");
+
+       if (p_us_elapsed_relative)
+               strbuf_addf(buf, "%9.6f | ",
+                           ((double)(*p_us_elapsed_relative)) / 1000000.0);
+       else
+               strbuf_addf(buf, "%9s | ", " ");
+
+       strbuf_addf(buf, "%-*s | ", TR2FMT_PERF_CATEGORY_WIDTH,
+                   (category ? category : ""));
+
+       if (ctx->nr_open_regions > 0) {
+               int len_indent = TR2_INDENT_LENGTH(ctx);
+               while (len_indent > dots.len) {
+                       strbuf_addbuf(buf, &dots);
+                       len_indent -= dots.len;
+               }
+               strbuf_addf(buf, "%.*s", len_indent, dots.buf);
+       }
+}
+
+static void perf_io_write_fl(const char *file, int line, const char *event_name,
+                            const struct repository *repo,
+                            uint64_t *p_us_elapsed_absolute,
+                            uint64_t *p_us_elapsed_relative,
+                            const char *category,
+                            const struct strbuf *buf_payload)
+{
+       struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
+       struct strbuf buf_line = STRBUF_INIT;
+
+       perf_fmt_prepare(event_name, ctx, file, line, repo,
+                        p_us_elapsed_absolute, p_us_elapsed_relative, category,
+                        &buf_line);
+       strbuf_addbuf(&buf_line, buf_payload);
+       tr2_dst_write_line(&tr2dst_perf, &buf_line);
+       strbuf_release(&buf_line);
+}
+
+static void fn_version_fl(const char *file, int line)
+{
+       const char *event_name = "version";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addstr(&buf_payload, git_version_string);
+
+       perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
+                        &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_start_fl(const char *file, int line, const char **argv)
+{
+       const char *event_name = "start";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       sq_quote_argv_pretty(&buf_payload, argv);
+
+       perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
+                        &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,
+                      int code)
+{
+       const char *event_name = "exit";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "code:%d", code);
+
+       perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
+                        NULL, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_signal(uint64_t us_elapsed_absolute, int signo)
+{
+       const char *event_name = "signal";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "signo:%d", signo);
+
+       perf_io_write_fl(__FILE__, __LINE__, event_name, NULL,
+                        &us_elapsed_absolute, NULL, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_atexit(uint64_t us_elapsed_absolute, int code)
+{
+       const char *event_name = "atexit";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "code:%d", code);
+
+       perf_io_write_fl(__FILE__, __LINE__, event_name, NULL,
+                        &us_elapsed_absolute, NULL, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,
+                                  va_list ap)
+{
+       if (fmt && *fmt && ap) {
+               va_list copy_ap;
+
+               va_copy(copy_ap, ap);
+               strbuf_vaddf(buf, fmt, copy_ap);
+               va_end(copy_ap);
+               return;
+       }
+
+       if (fmt && *fmt) {
+               strbuf_addstr(buf, fmt);
+               return;
+       }
+}
+
+static void fn_error_va_fl(const char *file, int line, const char *fmt,
+                          va_list ap)
+{
+       const char *event_name = "error";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       maybe_append_string_va(&buf_payload, fmt, ap);
+
+       perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
+                        &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_command_path_fl(const char *file, int line, const char *pathname)
+{
+       const char *event_name = "cmd_path";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addstr(&buf_payload, pathname);
+
+       perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
+                        &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_command_name_fl(const char *file, int line, const char *name,
+                              const char *hierarchy)
+{
+       const char *event_name = "cmd_name";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addstr(&buf_payload, name);
+       if (hierarchy && *hierarchy)
+               strbuf_addf(&buf_payload, " (%s)", hierarchy);
+
+       perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
+                        &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_command_mode_fl(const char *file, int line, const char *mode)
+{
+       const char *event_name = "cmd_mode";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addstr(&buf_payload, mode);
+
+       perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
+                        &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_alias_fl(const char *file, int line, const char *alias,
+                       const char **argv)
+{
+       const char *event_name = "alias";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "alias:%s argv:", alias);
+       sq_quote_argv_pretty(&buf_payload, argv);
+
+       perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
+                        &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_child_start_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute,
+                             const struct child_process *cmd)
+{
+       const char *event_name = "child_start";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       if (cmd->trace2_hook_name) {
+               strbuf_addf(&buf_payload, "[ch%d] class:hook hook:%s",
+                           cmd->trace2_child_id, cmd->trace2_hook_name);
+       } else {
+               const char *child_class =
+                       cmd->trace2_child_class ? cmd->trace2_child_class : "?";
+               strbuf_addf(&buf_payload, "[ch%d] class:%s",
+                           cmd->trace2_child_id, child_class);
+       }
+
+       if (cmd->dir) {
+               strbuf_addstr(&buf_payload, " cd:");
+               sq_quote_buf_pretty(&buf_payload, cmd->dir);
+       }
+
+       strbuf_addstr(&buf_payload, " argv:");
+       if (cmd->git_cmd)
+               strbuf_addstr(&buf_payload, " git");
+       sq_quote_argv_pretty(&buf_payload, cmd->argv);
+
+       perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
+                        NULL, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_child_exit_fl(const char *file, int line,
+                            uint64_t us_elapsed_absolute, int cid, int pid,
+                            int code, uint64_t us_elapsed_child)
+{
+       const char *event_name = "child_exit";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "[ch%d] pid:%d code:%d", cid, pid, code);
+
+       perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
+                        &us_elapsed_child, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_thread_start_fl(const char *file, int line,
+                              uint64_t us_elapsed_absolute)
+{
+       const char *event_name = "thread_start";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
+                        NULL, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_thread_exit_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute,
+                             uint64_t us_elapsed_thread)
+{
+       const char *event_name = "thread_exit";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
+                        &us_elapsed_thread, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,
+                      int exec_id, const char *exe, const char **argv)
+{
+       const char *event_name = "exec";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "id:%d ", exec_id);
+       strbuf_addstr(&buf_payload, "argv:");
+       if (exe)
+               strbuf_addf(&buf_payload, " %s", exe);
+       sq_quote_argv_pretty(&buf_payload, argv);
+
+       perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
+                        NULL, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_exec_result_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute, int exec_id,
+                             int code)
+{
+       const char *event_name = "exec_result";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "id:%d code:%d", exec_id, code);
+       if (code > 0)
+               strbuf_addf(&buf_payload, " err:%s", strerror(code));
+
+       perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
+                        NULL, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_param_fl(const char *file, int line, const char *param,
+                       const char *value)
+{
+       const char *event_name = "def_param";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "%s:%s", param, value);
+
+       perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
+                        &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_repo_fl(const char *file, int line,
+                      const struct repository *repo)
+{
+       const char *event_name = "def_repo";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addstr(&buf_payload, "worktree:");
+       sq_quote_buf_pretty(&buf_payload, repo->worktree);
+
+       perf_io_write_fl(file, line, event_name, repo, NULL, NULL, NULL,
+                        &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_region_enter_printf_va_fl(const char *file, int line,
+                                        uint64_t us_elapsed_absolute,
+                                        const char *category,
+                                        const char *label,
+                                        const struct repository *repo,
+                                        const char *fmt, va_list ap)
+{
+       const char *event_name = "region_enter";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       if (label)
+               strbuf_addf(&buf_payload, "label:%s ", label);
+       maybe_append_string_va(&buf_payload, fmt, ap);
+
+       perf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,
+                        NULL, category, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_region_leave_printf_va_fl(
+       const char *file, int line, uint64_t us_elapsed_absolute,
+       uint64_t us_elapsed_region, const char *category, const char *label,
+       const struct repository *repo, const char *fmt, va_list ap)
+{
+       const char *event_name = "region_leave";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       if (label)
+               strbuf_addf(&buf_payload, "label:%s ", label);
+       maybe_append_string_va(&buf_payload, fmt, ap);
+
+       perf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,
+                        &us_elapsed_region, category, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,
+                      uint64_t us_elapsed_region, const char *category,
+                      const struct repository *repo, const char *key,
+                      const char *value)
+{
+       const char *event_name = "data";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "%s:%s", key, value);
+
+       perf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,
+                        &us_elapsed_region, category, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_data_json_fl(const char *file, int line,
+                           uint64_t us_elapsed_absolute,
+                           uint64_t us_elapsed_region, const char *category,
+                           const struct repository *repo, const char *key,
+                           const struct json_writer *value)
+{
+       const char *event_name = "data_json";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "%s:%s", key, value->json.buf);
+
+       perf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,
+                        &us_elapsed_region, category, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+static void fn_printf_va_fl(const char *file, int line,
+                           uint64_t us_elapsed_absolute, const char *fmt,
+                           va_list ap)
+{
+       const char *event_name = "printf";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       maybe_append_string_va(&buf_payload, fmt, ap);
+
+       perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
+                        NULL, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
+struct tr2_tgt tr2_tgt_perf = {
+       &tr2dst_perf,
+
+       fn_init,
+       fn_term,
+
+       fn_version_fl,
+       fn_start_fl,
+       fn_exit_fl,
+       fn_signal,
+       fn_atexit,
+       fn_error_va_fl,
+       fn_command_path_fl,
+       fn_command_name_fl,
+       fn_command_mode_fl,
+       fn_alias_fl,
+       fn_child_start_fl,
+       fn_child_exit_fl,
+       fn_thread_start_fl,
+       fn_thread_exit_fl,
+       fn_exec_fl,
+       fn_exec_result_fl,
+       fn_param_fl,
+       fn_repo_fl,
+       fn_region_enter_printf_va_fl,
+       fn_region_leave_printf_va_fl,
+       fn_data_fl,
+       fn_data_json_fl,
+       fn_printf_va_fl,
+};
diff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c
new file mode 100644 (file)
index 0000000..8e65b03
--- /dev/null
@@ -0,0 +1,164 @@
+#include "cache.h"
+#include "thread-utils.h"
+#include "trace2/tr2_tls.h"
+
+/*
+ * Initialize size of the thread stack for nested regions.
+ * This is used to store nested region start times.  Note that
+ * this stack is per-thread and not per-trace-key.
+ */
+#define TR2_REGION_NESTING_INITIAL_SIZE (100)
+
+static struct tr2tls_thread_ctx *tr2tls_thread_main;
+static uint64_t tr2tls_us_start_main;
+
+static pthread_mutex_t tr2tls_mutex;
+static pthread_key_t tr2tls_key;
+
+static int tr2_next_thread_id; /* modify under lock */
+
+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name)
+{
+       uint64_t us_now = getnanotime() / 1000;
+       struct tr2tls_thread_ctx *ctx = xcalloc(1, sizeof(*ctx));
+
+       /*
+        * Implicitly "tr2tls_push_self()" to capture the thread's start
+        * time in array_us_start[0].  For the main thread this gives us the
+        * application run time.
+        */
+       ctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;
+       ctx->array_us_start = (uint64_t *)xcalloc(ctx->alloc, sizeof(uint64_t));
+       ctx->array_us_start[ctx->nr_open_regions++] = us_now;
+
+       ctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);
+
+       strbuf_init(&ctx->thread_name, 0);
+       if (ctx->thread_id)
+               strbuf_addf(&ctx->thread_name, "th%02d:", ctx->thread_id);
+       strbuf_addstr(&ctx->thread_name, thread_name);
+       if (ctx->thread_name.len > TR2_MAX_THREAD_NAME)
+               strbuf_setlen(&ctx->thread_name, TR2_MAX_THREAD_NAME);
+
+       pthread_setspecific(tr2tls_key, ctx);
+
+       return ctx;
+}
+
+struct tr2tls_thread_ctx *tr2tls_get_self(void)
+{
+       struct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);
+
+       /*
+        * If the thread-proc did not call trace2_thread_start(), we won't
+        * have any TLS data associated with the current thread.  Fix it
+        * here and silently continue.
+        */
+       if (!ctx)
+               ctx = tr2tls_create_self("unknown");
+
+       return ctx;
+}
+
+int tr2tls_is_main_thread(void)
+{
+       struct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);
+
+       return ctx == tr2tls_thread_main;
+}
+
+void tr2tls_unset_self(void)
+{
+       struct tr2tls_thread_ctx *ctx;
+
+       ctx = tr2tls_get_self();
+
+       pthread_setspecific(tr2tls_key, NULL);
+
+       free(ctx->array_us_start);
+       free(ctx);
+}
+
+void tr2tls_push_self(uint64_t us_now)
+{
+       struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
+
+       ALLOC_GROW(ctx->array_us_start, ctx->nr_open_regions + 1, ctx->alloc);
+       ctx->array_us_start[ctx->nr_open_regions++] = us_now;
+}
+
+void tr2tls_pop_self(void)
+{
+       struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
+
+       if (!ctx->nr_open_regions)
+               BUG("no open regions in thread '%s'", ctx->thread_name.buf);
+
+       ctx->nr_open_regions--;
+}
+
+void tr2tls_pop_unwind_self(void)
+{
+       struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
+
+       while (ctx->nr_open_regions > 1)
+               tr2tls_pop_self();
+}
+
+uint64_t tr2tls_region_elasped_self(uint64_t us)
+{
+       struct tr2tls_thread_ctx *ctx;
+       uint64_t us_start;
+
+       ctx = tr2tls_get_self();
+       if (!ctx->nr_open_regions)
+               return 0;
+
+       us_start = ctx->array_us_start[ctx->nr_open_regions - 1];
+
+       return us - us_start;
+}
+
+uint64_t tr2tls_absolute_elapsed(uint64_t us)
+{
+       if (!tr2tls_thread_main)
+               return 0;
+
+       return us - tr2tls_us_start_main;
+}
+
+void tr2tls_init(void)
+{
+       pthread_key_create(&tr2tls_key, NULL);
+       init_recursive_mutex(&tr2tls_mutex);
+
+       tr2tls_thread_main = tr2tls_create_self("main");
+       /*
+        * Keep a copy of the absolute start time of the main thread
+        * in a fixed variable since other threads need to access it.
+        * This also eliminates the need to lock accesses to the main
+        * thread's array (because of reallocs).
+        */
+       tr2tls_us_start_main = tr2tls_thread_main->array_us_start[0];
+}
+
+void tr2tls_release(void)
+{
+       tr2tls_unset_self();
+       tr2tls_thread_main = NULL;
+
+       pthread_mutex_destroy(&tr2tls_mutex);
+       pthread_key_delete(tr2tls_key);
+}
+
+int tr2tls_locked_increment(int *p)
+{
+       int current_value;
+
+       pthread_mutex_lock(&tr2tls_mutex);
+       current_value = *p;
+       *p = current_value + 1;
+       pthread_mutex_unlock(&tr2tls_mutex);
+
+       return current_value;
+}
diff --git a/trace2/tr2_tls.h b/trace2/tr2_tls.h
new file mode 100644 (file)
index 0000000..bb80e3f
--- /dev/null
@@ -0,0 +1,97 @@
+#ifndef TR2_TLS_H
+#define TR2_TLS_H
+
+#include "strbuf.h"
+
+/*
+ * Arbitry limit for thread names for column alignment.
+ */
+#define TR2_MAX_THREAD_NAME (24)
+
+struct tr2tls_thread_ctx {
+       struct strbuf thread_name;
+       uint64_t *array_us_start;
+       int alloc;
+       int nr_open_regions; /* plays role of "nr" in ALLOC_GROW */
+       int thread_id;
+};
+
+/*
+ * Create TLS data for the current thread.  This gives us a place to
+ * put per-thread data, such as thread start time, function nesting
+ * and a per-thread label for our messages.
+ *
+ * We assume the first thread is "main".  Other threads are given
+ * non-zero thread-ids to help distinguish messages from concurrent
+ * threads.
+ *
+ * Truncate the thread name if necessary to help with column alignment
+ * in printf-style messages.
+ *
+ * In this and all following functions the term "self" refers to the
+ * current thread.
+ */
+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name);
+
+/*
+ * Get our TLS data.
+ */
+struct tr2tls_thread_ctx *tr2tls_get_self(void);
+
+/*
+ * return true if the current thread is the main thread.
+ */
+int tr2tls_is_main_thread(void);
+
+/*
+ * Free our TLS data.
+ */
+void tr2tls_unset_self(void);
+
+/*
+ * Begin a new nested region and remember the start time.
+ */
+void tr2tls_push_self(uint64_t us_now);
+
+/*
+ * End the innermost nested region.
+ */
+void tr2tls_pop_self(void);
+
+/*
+ * Pop any extra (above the first) open regions on the current
+ * thread and discard.  During a thread-exit, we should only
+ * have region[0] that was pushed in trace2_thread_start() if
+ * the thread exits normally.
+ */
+void tr2tls_pop_unwind_self(void);
+
+/*
+ * Compute the elapsed time since the innermost region in the
+ * current thread started and the given time (usually now).
+ */
+uint64_t tr2tls_region_elasped_self(uint64_t us);
+
+/*
+ * Compute the elapsed time since the main thread started
+ * and the given time (usually now).  This is assumed to
+ * be the absolute run time of the process.
+ */
+uint64_t tr2tls_absolute_elapsed(uint64_t us);
+
+/*
+ * Initialize the tr2 TLS system.
+ */
+void tr2tls_init(void);
+
+/*
+ * Free all tr2 TLS resources.
+ */
+void tr2tls_release(void);
+
+/*
+ * Protected increment of an integer.
+ */
+int tr2tls_locked_increment(int *p);
+
+#endif /* TR2_TLS_H */
index 0796f326b36bac334cbf7ca5162cb22c4b62d1e8..0c414f2fed2b5701f616eb2d88a386e5da16fda8 100644 (file)
--- a/trailer.c
+++ b/trailer.c
@@ -1129,10 +1129,11 @@ static void format_trailer_info(struct strbuf *out,
                                const struct trailer_info *info,
                                const struct process_trailer_options *opts)
 {
+       size_t origlen = out->len;
        size_t i;
 
        /* If we want the whole block untouched, we can take the fast path. */
-       if (!opts->only_trailers && !opts->unfold) {
+       if (!opts->only_trailers && !opts->unfold && !opts->filter && !opts->separator) {
                strbuf_add(out, info->trailer_start,
                           info->trailer_end - info->trailer_start);
                return;
@@ -1147,15 +1148,29 @@ static void format_trailer_info(struct strbuf *out,
                        struct strbuf val = STRBUF_INIT;
 
                        parse_trailer(&tok, &val, NULL, trailer, separator_pos);
-                       if (opts->unfold)
-                               unfold_value(&val);
-
-                       strbuf_addf(out, "%s: %s\n", tok.buf, val.buf);
+                       if (!opts->filter || opts->filter(&tok, opts->filter_data)) {
+                               if (opts->unfold)
+                                       unfold_value(&val);
+
+                               if (opts->separator && out->len != origlen)
+                                       strbuf_addbuf(out, opts->separator);
+                               if (!opts->value_only)
+                                       strbuf_addf(out, "%s: ", tok.buf);
+                               strbuf_addbuf(out, &val);
+                               if (!opts->separator)
+                                       strbuf_addch(out, '\n');
+                       }
                        strbuf_release(&tok);
                        strbuf_release(&val);
 
                } else if (!opts->only_trailers) {
+                       if (opts->separator && out->len != origlen) {
+                               strbuf_addbuf(out, opts->separator);
+                       }
                        strbuf_addstr(out, trailer);
+                       if (opts->separator) {
+                               strbuf_rtrim(out);
+                       }
                }
        }
 
index b997739649a37e8791e8448c2e7daefe8023bb71..203acf4ee1db99ea35a1d202010834330ce4af58 100644 (file)
--- a/trailer.h
+++ b/trailer.h
@@ -72,6 +72,10 @@ struct process_trailer_options {
        int only_input;
        int unfold;
        int no_divider;
+       int value_only;
+       const struct strbuf *separator;
+       int (*filter)(const struct strbuf *, void *);
+       void *filter_data;
 };
 
 #define PROCESS_TRAILER_OPTIONS_INIT {0}
index 1f52c95fd87b1a02968727bde71dcc1eddc50410..cec83bd663d0f27589bd09176e38b084270f56c4 100644 (file)
@@ -127,6 +127,8 @@ static struct child_process *get_helper(struct transport *transport)
                argv_array_pushf(&helper->env_array, "%s=%s",
                                 GIT_DIR_ENVIRONMENT, get_git_dir());
 
+       helper->trace2_child_class = helper->args.argv[0]; /* "remote-<name>" */
+
        code = start_command(helper);
        if (code < 0 && errno == ENOENT)
                die(_("unable to find remote helper for '%s'"), data->name);
index e078812897eddefcd5a55a7d474dd4a7a89434fc..d0608df5c90cab4d2e2a2071a7416aeac75004e1 100644 (file)
@@ -1062,6 +1062,7 @@ static int run_pre_push_hook(struct transport *transport,
 
        proc.argv = argv;
        proc.in = -1;
+       proc.trace2_hook_name = "pre-push";
 
        if (start_command(&proc)) {
                finish_command(&proc);
index 3563daae1aa6bceb4f7a4715ee574e1359622239..22c41a3ba80971e5edc6bbf02eb6ea59d3c41277 100644 (file)
@@ -299,25 +299,6 @@ static void load_gitmodules_file(struct index_state *index,
        }
 }
 
-/*
- * Unlink the last component and schedule the leading directories for
- * removal, such that empty directories get removed.
- */
-static void unlink_entry(const struct cache_entry *ce)
-{
-       const struct submodule *sub = submodule_from_ce(ce);
-       if (sub) {
-               /* state.force is set at the caller. */
-               submodule_move_head(ce->name, "HEAD", NULL,
-                                   SUBMODULE_MOVE_HEAD_FORCE);
-       }
-       if (!check_leading_path(ce->name, ce_namelen(ce)))
-               return;
-       if (remove_or_warn(ce->ce_mode, ce->name))
-               return;
-       schedule_dir_for_removal(ce->name, ce_namelen(ce));
-}
-
 static struct progress *get_progress(struct unpack_trees_options *o)
 {
        unsigned cnt = 0, total = 0;
@@ -410,7 +391,7 @@ static int check_updates(struct unpack_trees_options *o)
                                unlink_entry(ce);
                }
        }
-       remove_marked_cache_entries(index);
+       remove_marked_cache_entries(index, 0);
        remove_scheduled_dirs();
 
        if (should_update_submodules() && o->update && !o->dry_run)
diff --git a/usage.c b/usage.c
index cc803336bd5e6761b7fd50c33dba5aa9f734c4ba..2fdb20086bd695eaad57ef7574ce4e02655e96c1 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -22,17 +22,48 @@ void vreportf(const char *prefix, const char *err, va_list params)
 static NORETURN void usage_builtin(const char *err, va_list params)
 {
        vreportf("usage: ", err, params);
+
+       /*
+        * When we detect a usage error *before* the command dispatch in
+        * cmd_main(), we don't know what verb to report.  Force it to this
+        * to facilitate post-processing.
+        */
+       trace2_cmd_name("_usage_");
+
+       /*
+        * Currently, the (err, params) are usually just the static usage
+        * string which isn't very useful here.  Usually, the call site
+        * manually calls fprintf(stderr,...) with the actual detailed
+        * syntax error before calling usage().
+        *
+        * TODO It would be nice to update the call sites to pass both
+        * the static usage string and the detailed error message.
+        */
+
        exit(129);
 }
 
 static NORETURN void die_builtin(const char *err, va_list params)
 {
+       /*
+        * We call this trace2 function first and expect it to va_copy 'params'
+        * before using it (because an 'ap' can only be walked once).
+        */
+       trace2_cmd_error_va(err, params);
+
        vreportf("fatal: ", err, params);
+
        exit(128);
 }
 
 static void error_builtin(const char *err, va_list params)
 {
+       /*
+        * We call this trace2 function first and expect it to va_copy 'params'
+        * before using it (because an 'ap' can only be walked once).
+        */
+       trace2_cmd_error_va(err, params);
+
        vreportf("error: ", err, params);
 }
 
index d6a0ee7f730d96a7e8cc04095ddafb43c937105a..b45bfeb9d33b4ae4f372a15edd90651bf3735f92 100644 (file)
@@ -444,7 +444,7 @@ int submodule_uses_worktrees(const char *path)
        DIR *dir;
        struct dirent *d;
        int ret = 0;
-       struct repository_format format;
+       struct repository_format format = REPOSITORY_FORMAT_INIT;
 
        submodule_gitdir = git_pathdup_submodule(path, "%s", "");
        if (!submodule_gitdir)
@@ -462,8 +462,10 @@ int submodule_uses_worktrees(const char *path)
        read_repository_format(&format, sb.buf);
        if (format.version != 0) {
                strbuf_release(&sb);
+               clear_repository_format(&format);
                return 1;
        }
+       clear_repository_format(&format);
 
        /* Replace config by worktrees. */
        strbuf_setlen(&sb, sb.len - strlen("config"));
index 1f564b12d259956fa71fb0dce78c6e4c3c006897..445a36204a69cf69f2d3e87ea6d308a6b5a9ab29 100644 (file)
@@ -748,12 +748,23 @@ static int has_unmerged(struct wt_status *s)
 
 void wt_status_collect(struct wt_status *s)
 {
+       trace2_region_enter("status", "worktrees", s->repo);
        wt_status_collect_changes_worktree(s);
-       if (s->is_initial)
+       trace2_region_leave("status", "worktrees", s->repo);
+
+       if (s->is_initial) {
+               trace2_region_enter("status", "initial", s->repo);
                wt_status_collect_changes_initial(s);
-       else
+               trace2_region_leave("status", "initial", s->repo);
+       } else {
+               trace2_region_enter("status", "index", s->repo);
                wt_status_collect_changes_index(s);
+               trace2_region_leave("status", "index", s->repo);
+       }
+
+       trace2_region_enter("status", "untracked", s->repo);
        wt_status_collect_untracked(s);
+       trace2_region_leave("status", "untracked", s->repo);
 
        wt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, "HEAD"));
        if (s->state.merge_in_progress && !has_unmerged(s))
@@ -2291,6 +2302,13 @@ static void wt_porcelain_v2_print(struct wt_status *s)
 
 void wt_status_print(struct wt_status *s)
 {
+       trace2_data_intmax("status", s->repo, "count/changed", s->change.nr);
+       trace2_data_intmax("status", s->repo, "count/untracked",
+                          s->untracked.nr);
+       trace2_data_intmax("status", s->repo, "count/ignored", s->ignored.nr);
+
+       trace2_region_enter("status", "print", s->repo);
+
        switch (s->status_format) {
        case STATUS_FORMAT_SHORT:
                wt_shortstatus_print(s);
@@ -2309,6 +2327,8 @@ void wt_status_print(struct wt_status *s)
                wt_longstatus_print(s);
                break;
        }
+
+       trace2_region_leave("status", "print", s->repo);
 }
 
 /**
index 80f060d2782e227ce7637a41d05742729c48f6ac..8509f9ea223a1282a367874c3e3a3ef0c351a30f 100644 (file)
@@ -306,6 +306,10 @@ int git_xmerge_config(const char *var, const char *value, void *cb)
                        git_xmerge_style = XDL_MERGE_DIFF3;
                else if (!strcmp(value, "merge"))
                        git_xmerge_style = 0;
+               /*
+                * Please update _git_checkout() in
+                * git-completion.bash when you add new merge config
+                */
                else
                        die("unknown style '%s' given for '%s'",
                            value, var);