Merge branch 'jh/builtin-fsmonitor-part2' into jh/builtin-fsmonitor-part3

* jh/builtin-fsmonitor-part2: (150 commits)
  t7527: test status with untracked-cache and fsmonitor--daemon
  fsmonitor: force update index after large responses
  fsmonitor--daemon: use a cookie file to sync with file system
  fsmonitor--daemon: periodically truncate list of modified files
  t/perf/p7519: add fsmonitor--daemon test cases
  t/perf/p7519: speed up test on Windows
  t/perf/p7519: fix coding style
  t/helper/test-chmtime: skip directories on Windows
  t/perf: avoid copying builtin fsmonitor files into test repo
  t7527: create test for fsmonitor--daemon
  t/helper/fsmonitor-client: create IPC client to talk to FSMonitor Daemon
  help: include fsmonitor--daemon feature flag in version info
  fsmonitor--daemon: implement handle_client callback
  compat/fsmonitor/fsm-listen-darwin: implement FSEvent listener on MacOS
  compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
  compat/fsmonitor/fsm-listen-win32: implement FSMonitor backend on Windows
  fsmonitor--daemon: create token-based changed path cache
  fsmonitor--daemon: define token-ids
  fsmonitor--daemon: add pathname classification
  fsmonitor--daemon: implement 'start' command
  ...
maint
Junio C Hamano 2022-03-25 16:05:52 -07:00
commit 852e2c84f8
207 changed files with 8003 additions and 1124 deletions

1
.gitignore vendored
View File

@ -72,6 +72,7 @@
/git-format-patch
/git-fsck
/git-fsck-objects
/git-fsmonitor--daemon
/git-gc
/git-get-tar-commit-id
/git-grep

View File

@ -59,8 +59,9 @@ David Reiss <dreiss@facebook.com> <dreiss@dreiss-vmware.(none)>
David S. Miller <davem@davemloft.net>
David Turner <novalis@novalis.org> <dturner@twopensource.com>
David Turner <novalis@novalis.org> <dturner@twosigma.com>
Derrick Stolee <dstolee@microsoft.com> <stolee@gmail.com>
Derrick Stolee <dstolee@microsoft.com> Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>
Derrick Stolee <derrickstolee@github.com> <stolee@gmail.com>
Derrick Stolee <derrickstolee@github.com> Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>
Derrick Stolee <derrickstolee@github.com> <dstolee@microsoft.com>
Deskin Miller <deskinm@umich.edu>
Đoàn Trần Công Danh <congdanhqx@gmail.com> Doan Tran Cong Danh
Dirk Süsserott <newsletter@dirk.my1.cc>

View File

@ -70,8 +70,8 @@ git@sfconservancy.org, or individually:

- Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- Christian Couder <christian.couder@gmail.com>
- Jeff King <peff@peff.net>
- Junio C Hamano <gitster@pobox.com>
- Taylor Blau <me@ttaylorr.com>

All complaints will be reviewed and investigated promptly and fairly.


View File

@ -9,6 +9,10 @@ Backward compatibility warts
* "git name-rev --stdin" has been deprecated and issues a warning
when used; use "git name-rev --annotate-stdin" instead.

* "git clone --filter=... --recurse-submodules" only makes the
top-level a partial clone, while submodules are fully cloned. This
behaviour is changed to pass the same filter down to the submodules.


Note to those who build from the source

@ -25,6 +29,17 @@ UI, Workflows & Features
* "git log --remerge-diff" shows the difference from mechanical merge
result and the result that is actually recorded in a merge commit.

* "git log" and friends learned an option --exclude-first-parent-only
to propagate UNINTERESTING bit down only along the first-parent
chain, just like --first-parent option shows commits that lack the
UNINTERESTING bit only along the first-parent chain.

* The command line completion script (in contrib/) learned to
complete all Git subcommands, including the ones that are normally
hidden, when GIT_COMPLETION_SHOW_ALL_COMMANDS is used.

* "git branch" learned the "--recurse-submodules" option.


Performance, Internal Implementation, Development Support etc.

@ -47,6 +62,17 @@ Performance, Internal Implementation, Development Support etc.
all. Start the process of renaming it to "--annotate-stdin".
(merge a2585719b3 jc/name-rev-stdin later to maint).

* "git update-index", "git checkout-index", and "git clean" are
taught to work better with the sparse checkout feature.

* Use an internal call to reset_head() helper function instead of
spawning "git checkout" in "rebase", and update code paths that are
involved in the change.

* Messages "ort" merge backend prepares while dealing with conflicted
paths were unnecessarily confusing since it did not differentiate
inner merges and outer merges.


Fixes since v2.35
-----------------
@ -140,6 +166,72 @@ Fixes since v2.35
* "git diff --diff-filter=aR" is now parsed correctly.
(merge 75408ca949 js/diff-filter-negation-fix later to maint).

* When "git subtree" wants to create a merge, it used "git merge" and
let it be affected by end-user's "merge.ff" configuration, which
has been corrected.
(merge 9158a3564a tk/subtree-merge-not-ff-only later to maint).

* Unlike "git apply", "git patch-id" did not handle patches with
hunks that has only 1 line in either preimage or postimage, which
has been corrected.
(merge 757e75c81e jz/patch-id-hunk-header-parsing-fix later to maint).

* "receive-pack" checks if it will do any ref updates (various
conditions could reject a push) before received objects are taken
out of the temporary directory used for quarantine purposes, so
that a push that is known-to-fail will not leave crufts that a
future "gc" needs to clean up.
(merge 5407764069 cb/clear-quarantine-early-on-all-ref-update-errors later to maint).

* Because a deletion of ref would need to remove it from both the
loose ref store and the packed ref store, a delete-ref operation
that logically removes one ref may end up invoking ref-transaction
hook twice, which has been corrected.
(merge 2ed1b64ebd ps/avoid-unnecessary-hook-invocation-with-packed-refs later to maint).

* When there is no object to write .bitmap file for, "git
multi-pack-index" triggered an error, instead of just skipping,
which has been corrected.
(merge eb57277ba3 tb/midx-no-bitmap-for-no-objects later to maint).

* "git cmd -h" outside a repository should error out cleanly for many
commands, but instead it hit a BUG(), which has been corrected.
(merge 87ad07d735 js/short-help-outside-repo-fix later to maint).

* "working tree" and "per-worktree ref" were in glossary, but
"worktree" itself wasn't, which has been corrected.
(merge 2df5387ed0 jc/glossary-worktree later to maint).

* L10n support for a few error messages.
(merge 3d3c23b3a7 bs/forbid-i18n-of-protocol-token-in-fetch-pack later to maint).

* Test modernization.
(merge d4fe066e4b sy/t0001-use-path-is-helper later to maint).

* "git log --graph --graph" used to leak a graph structure, and there
was no way to countermand "--graph" that appear earlier on the
command line. A "--no-graph" option has been added and resource
leakage has been plugged.

* Error output given in response to an ambiguous object name has been
improved.
(merge 3a73c1dfaf ab/ambiguous-object-name later to maint).

* "git sparse-checkout" wants to work with per-worktree configuration,
but did not work well in a worktree attached to a bare repository.
(merge 3ce1138272 ds/sparse-checkout-requires-per-worktree-config later to maint).

* Setting core.untrackedCache to true failed to add the untracked
cache extension to the index.

* Workaround we have for versions of PCRE2 before their version 10.36
were in effect only for their versions newer than 10.36 by mistake,
which has been corrected.
(merge 97169fc361 rs/pcre-invalid-utf8-fix-fix later to maint).

* Document Taylor as a new member of Git PLC at SFC. Welcome.
(merge e8d56ca863 tb/coc-plc-update later to maint).

* Other code cleanup, docfix, build fix, etc.
(merge cfc5cf428b jc/find-header later to maint).
(merge 40e7cfdd46 jh/p4-fix-use-of-process-error-exception later to maint).
@ -157,3 +249,10 @@ Fixes since v2.35
(merge 45d0212a71 ll/doc-mktree-typofix later to maint).
(merge e9b272e4c1 js/no-more-legacy-stash later to maint).
(merge 6798b08e84 ab/do-not-hide-failures-in-git-dot-pm later to maint).
(merge 9325285df4 po/doc-check-ignore-markup-fix later to maint).
(merge cd26cd6c7c sy/modernize-t-lib-read-tree-m-3way later to maint).
(merge d17294a05e ab/hash-object-leakfix later to maint).
(merge b8403129d3 jd/t0015-modernize later to maint).
(merge 332acc248d ds/mailmap later to maint).
(merge 04bf052eef ab/grep-patterntype later to maint).
(merge 6ee36364eb ab/diff-free-more later to maint).

View File

@ -116,6 +116,9 @@ advice.*::
submoduleAlternateErrorStrategyDie::
Advice shown when a submodule.alternateErrorStrategy option
configured to "die" causes a fatal error.
submodulesNotUpdated::
Advice shown when a user runs a submodule command that fails
because `git submodule update --init` was not run.
addIgnoredFile::
Advice shown if a user attempts to add an ignored file to
the index.

View File

@ -6,3 +6,8 @@ clone.defaultRemoteName::
clone.rejectShallow::
Reject to clone a repository if it is a shallow one, can be overridden by
passing option `--reject-shallow` in command line. See linkgit:git-clone[1]

clone.filterSubmodules::
If a partial clone filter is provided (see `--filter` in
linkgit:git-rev-list[1]) and `--recurse-submodules` is used, also apply
the filter to submodules.

View File

@ -62,22 +62,54 @@ core.protectNTFS::
Defaults to `true` on Windows, and `false` elsewhere.

core.fsmonitor::
If set, the value of this variable is used as a command which
will identify all files that may have changed since the
requested date/time. This information is used to speed up git by
avoiding unnecessary processing of files that have not changed.
See the "fsmonitor-watchman" section of linkgit:githooks[5].
If set to true, enable the built-in file system monitor
daemon for this working directory (linkgit:git-fsmonitor--daemon[1]).
+
Like hook-based file system monitors, the built-in file system monitor
can speed up Git commands that need to refresh the Git index
(e.g. `git status`) in a working directory with many files. The
built-in monitor eliminates the need to install and maintain an
external third-party tool.
+
The built-in file system monitor is currently available only on a
limited set of supported platforms. Currently, this includes Windows
and MacOS.
+
Otherwise, this variable contains the pathname of the "fsmonitor"
hook command.
+
This hook command is used to identify all files that may have changed
since the requested date/time. This information is used to speed up
git by avoiding unnecessary scanning of files that have not changed.
+
See the "fsmonitor-watchman" section of linkgit:githooks[5].
+
Note that if you concurrently use multiple versions of Git, such
as one version on the command line and another version in an IDE
tool, that the definition of `core.fsmonitor` was extended to
allow boolean values in addition to hook pathnames. Git versions
2.35.1 and prior will not understand the boolean values and will
consider the "true" or "false" values as hook pathnames to be
invoked. Git versions 2.26 thru 2.35.1 default to hook protocol
V2 and will fall back to no fsmonitor (full scan). Git versions
prior to 2.26 default to hook protocol V1 and will silently
assume there were no changes to report (no scan), so status
commands may report incomplete results. For this reason, it is
best to upgrade all of your Git versions before using the built-in
file system monitor.

core.fsmonitorHookVersion::
Sets the version of hook that is to be used when calling fsmonitor.
There are currently versions 1 and 2. When this is not set,
version 2 will be tried first and if it fails then version 1
will be tried. Version 1 uses a timestamp as input to determine
which files have changes since that time but some monitors
like watchman have race conditions when used with a timestamp.
Version 2 uses an opaque string so that the monitor can return
something that can be used to determine what files have changed
without race conditions.
Sets the protocol version to be used when invoking the
"fsmonitor" hook.
+
There are currently versions 1 and 2. When this is not set,
version 2 will be tried first and if it fails then version 1
will be tried. Version 1 uses a timestamp as input to determine
which files have changes since that time but some monitors
like Watchman have race conditions when used with a timestamp.
Version 2 uses an opaque string so that the monitor can return
something that can be used to determine what files have changed
without race conditions.

core.trustctime::
If false, the ctime differences between the index and the

View File

@ -6,3 +6,34 @@ extensions.objectFormat::
Note that this setting should only be set by linkgit:git-init[1] or
linkgit:git-clone[1]. Trying to change it after initialization will not
work and will produce hard-to-diagnose issues.

extensions.worktreeConfig::
If enabled, then worktrees will load config settings from the
`$GIT_DIR/config.worktree` file in addition to the
`$GIT_COMMON_DIR/config` file. Note that `$GIT_COMMON_DIR` and
`$GIT_DIR` are the same for the main working tree, while other
working trees have `$GIT_DIR` equal to
`$GIT_COMMON_DIR/worktrees/<id>/`. The settings in the
`config.worktree` file will override settings from any other
config files.
+
When enabling `extensions.worktreeConfig`, you must be careful to move
certain values from the common config file to the main working tree's
`config.worktree` file, if present:
+
* `core.worktree` must be moved from `$GIT_COMMON_DIR/config` to
`$GIT_COMMON_DIR/config.worktree`.
* If `core.bare` is true, then it must be moved from `$GIT_COMMON_DIR/config`
to `$GIT_COMMON_DIR/config.worktree`.
+
It may also be beneficial to adjust the locations of `core.sparseCheckout`
and `core.sparseCheckoutCone` depending on your desire for customizable
sparse-checkout settings for each worktree. By default, the `git
sparse-checkout` builtin enables `extensions.worktreeConfig`, assigns
these config values on a per-worktree basis, and uses the
`$GIT_DIR/info/sparse-checkout` file to specify the sparsity for each
worktree independently. See linkgit:git-sparse-checkout[1] for more
details.
+
For historical reasons, `extensions.worktreeConfig` is respected
regardless of the `core.repositoryFormatVersion` setting.

View File

@ -59,18 +59,33 @@ submodule.active::

submodule.recurse::
A boolean indicating if commands should enable the `--recurse-submodules`
option by default.
Applies to all commands that support this option
(`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,
`restore` and `switch`) except `clone` and `ls-files`.
option by default. Defaults to false.
+
When set to true, it can be deactivated via the
`--no-recurse-submodules` option. Note that some Git commands
lacking this option may call some of the above commands affected by
`submodule.recurse`; for instance `git remote update` will call
`git fetch` but does not have a `--no-recurse-submodules` option.
For these commands a workaround is to temporarily change the
configuration value by using `git -c submodule.recurse=0`.
+
The following list shows the commands that accept
`--recurse-submodules` and whether they are supported by this
setting.

* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,
`reset`, `restore` and `switch` are always supported.
* `clone` and `ls-files` are not supported.
* `branch` is supported only if `submodule.propagateBranches` is
enabled

submodule.propagateBranches::
[EXPERIMENTAL] A boolean that enables branching support when
using `--recurse-submodules` or `submodule.recurse=true`.
Enabling this will allow certain commands to accept
`--recurse-submodules` and certain commands that already accept
`--recurse-submodules` will now consider branches.
Defaults to false.
When set to true, it can be deactivated via the
`--no-recurse-submodules` option. Note that some Git commands
lacking this option may call some of the above commands affected by
`submodule.recurse`; for instance `git remote update` will call
`git fetch` but does not have a `--no-recurse-submodules` option.
For these commands a workaround is to temporarily change the
configuration value by using `git -c submodule.recurse=0`.

submodule.fetchJobs::
Specifies how many submodules are fetched/cloned at the same time.

View File

@ -16,7 +16,8 @@ SYNOPSIS
[--points-at <object>] [--format=<format>]
[(-r | --remotes) | (-a | --all)]
[--list] [<pattern>...]
'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]
'git branch' [--track[=(direct|inherit)] | --no-track] [-f]
[--recurse-submodules] <branchname> [<start-point>]
'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
'git branch' --unset-upstream [<branchname>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
@ -235,6 +236,22 @@ how the `branch.<name>.remote` and `branch.<name>.merge` options are used.
Do not set up "upstream" configuration, even if the
branch.autoSetupMerge configuration variable is set.

--recurse-submodules::
THIS OPTION IS EXPERIMENTAL! Causes the current command to
recurse into submodules if `submodule.propagateBranches` is
enabled. See `submodule.propagateBranches` in
linkgit:git-config[1]. Currently, only branch creation is
supported.
+
When used in branch creation, a new branch <branchname> will be created
in the superproject and all of the submodules in the superproject's
<start-point>. In submodules, the branch will point to the submodule
commit in the superproject's <start-point> but the branch's tracking
information will be set up based on the submodule's branches and remotes
e.g. `git branch --recurse-submodules topic origin/main` will create the
submodule branch "topic" that points to the submodule commit in the
superproject's "origin/main", but tracks the submodule's "origin/main".

--set-upstream::
As this option had confusing syntax, it is no longer supported.
Please use `--track` or `--set-upstream-to` instead.

View File

@ -33,7 +33,7 @@ OPTIONS
Instead of printing the paths that are excluded, for each path
that matches an exclude pattern, print the exclude pattern
together with the path. (Matching an exclude pattern usually
means the path is excluded, but if the pattern begins with '!'
means the path is excluded, but if the pattern begins with "`!`"
then it is a negated pattern and matching it means the path is
NOT excluded.)
+
@ -77,7 +77,7 @@ If `--verbose` is specified, the output is a series of lines of the form:
<pathname> is the path of a file being queried, <pattern> is the
matching pattern, <source> is the pattern's source file, and <linenum>
is the line number of the pattern within that source. If the pattern
contained a `!` prefix or `/` suffix, it will be preserved in the
contained a "`!`" prefix or "`/`" suffix, it will be preserved in the
output. <source> will be an absolute path when referring to the file
configured by `core.excludesFile`, or relative to the repository root
when referring to `.git/info/exclude` or a per-directory exclude file.

View File

@ -12,6 +12,7 @@ SYNOPSIS
'git checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
[--stage=<number>|all]
[--temp]
[--ignore-skip-worktree-bits]
[-z] [--stdin]
[--] [<file>...]

@ -37,8 +38,9 @@ OPTIONS

-a::
--all::
checks out all files in the index. Cannot be used
together with explicit filenames.
checks out all files in the index except for those with the
skip-worktree bit set (see `--ignore-skip-worktree-bits`).
Cannot be used together with explicit filenames.

-n::
--no-create::
@ -59,6 +61,10 @@ OPTIONS
write the content to temporary files. The temporary name
associations will be written to stdout.

--ignore-skip-worktree-bits::
Check out all files, including those with the skip-worktree bit
set.

--stdin::
Instead of taking list of paths from the command line,
read list of paths from the standard input. Paths are

View File

@ -16,7 +16,7 @@ SYNOPSIS
[--depth <depth>] [--[no-]single-branch] [--no-tags]
[--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
[--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]
[--filter=<filter>] [--] <repository>
[--filter=<filter> [--also-filter-submodules]] [--] <repository>
[<directory>]

DESCRIPTION
@ -182,6 +182,11 @@ objects from the source repository into a pack in the cloned repository.
at least `<size>`. For more details on filter specifications, see
the `--filter` option in linkgit:git-rev-list[1].

--also-filter-submodules::
Also apply the partial clone filter to any submodules in the repository.
Requires `--filter` and `--recurse-submodules`. This can be turned on by
default by setting the `clone.filterSubmodules` config option.

--mirror::
Set up a mirror of the source repository. This implies `--bare`.
Compared to `--bare`, `--mirror` not only maps local branches of the

View File

@ -141,9 +141,13 @@ from all available files.
See also <<FILES>>.

--worktree::
Similar to `--local` except that `.git/config.worktree` is
Similar to `--local` except that `$GIT_DIR/config.worktree` is
read from or written to if `extensions.worktreeConfig` is
present. If not it's the same as `--local`.
enabled. If not it's the same as `--local`. Note that `$GIT_DIR`
is equal to `$GIT_COMMON_DIR` for the main working tree, but is of
the form `$GIT_DIR/worktrees/<id>/` for other working trees. See
linkgit:git-worktree[1] to learn how to enable
`extensions.worktreeConfig`.

-f <config-file>::
--file <config-file>::

View File

@ -0,0 +1,75 @@
git-fsmonitor--daemon(1)
========================

NAME
----
git-fsmonitor--daemon - A Built-in File System Monitor

SYNOPSIS
--------
[verse]
'git fsmonitor--daemon' start
'git fsmonitor--daemon' run
'git fsmonitor--daemon' stop
'git fsmonitor--daemon' status

DESCRIPTION
-----------

A daemon to watch the working directory for file and directory
changes using platform-specific file system notification facilities.

This daemon communicates directly with commands like `git status`
using the link:technical/api-simple-ipc.html[simple IPC] interface
instead of the slower linkgit:githooks[5] interface.

This daemon is built into Git so that no third-party tools are
required.

OPTIONS
-------

start::
Starts a daemon in the background.

run::
Runs a daemon in the foreground.

stop::
Stops the daemon running in the current working
directory, if present.

status::
Exits with zero status if a daemon is watching the
current working directory.

REMARKS
-------

This daemon is a long running process used to watch a single working
directory and maintain a list of the recently changed files and
directories. Performance of commands such as `git status` can be
increased if they just ask for a summary of changes to the working
directory and can avoid scanning the disk.

When `core.fsmonitor` is set to `true` (see linkgit:git-config[1])
commands, such as `git status`, will ask the daemon for changes and
automatically start it (if necessary).

For more information see the "File System Monitor" section in
linkgit:git-update-index[1].

CAVEATS
-------

The fsmonitor daemon does not currently know about submodules and does
not know to filter out file system events that happen within a
submodule. If fsmonitor daemon is watching a super repo and a file is
modified within the working directory of a submodule, it will report
the change (as happening against the super repo). However, the client
will properly ignore these extra events, so performance may be affected
but it will not cause an incorrect result.

GIT
---
Part of the linkgit:git[1] suite

View File

@ -31,13 +31,21 @@ COMMANDS
Describe the patterns in the sparse-checkout file.

'set'::
Enable the necessary config settings
(extensions.worktreeConfig, core.sparseCheckout,
core.sparseCheckoutCone) if they are not already enabled, and
write a set of patterns to the sparse-checkout file from the
Enable the necessary sparse-checkout config settings
(`core.sparseCheckout`, `core.sparseCheckoutCone`, and
`index.sparse`) if they are not already set to the desired values,
and write a set of patterns to the sparse-checkout file from the
list of arguments following the 'set' subcommand. Update the
working directory to match the new patterns.
+
To ensure that adjusting the sparse-checkout settings within a worktree
does not alter the sparse-checkout settings in other worktrees, the 'set'
subcommand will upgrade your repository config to use worktree-specific
config if not already present. The sparsity defined by the arguments to
the 'set' subcommand are stored in the worktree-specific sparse-checkout
file. See linkgit:git-worktree[1] and the documentation of
`extensions.worktreeConfig` in linkgit:git-config[1] for more details.
+
When the `--stdin` option is provided, the patterns are read from
standard in as a newline-delimited list instead of from the arguments.
+

View File

@ -133,7 +133,7 @@ If you really want to remove a submodule from the repository and commit
that use linkgit:git-rm[1] instead. See linkgit:gitsubmodules[7] for removal
options.

update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--] [<path>...]::
update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter <filter spec>] [--] [<path>...]::
+
--
Update the registered submodules to match what the superproject
@ -177,6 +177,10 @@ submodule with the `--init` option.

If `--recursive` is specified, this command will recurse into the
registered submodules, and update any nested submodules within.

If `--filter <filter spec>` is specified, the given partial clone filter will be
applied to the submodule. See linkgit:git-rev-list[1] for details on filter
specifications.
--
set-branch (-b|--branch) <branch> [--] <path>::
set-branch (-d|--default) [--] <path>::

View File

@ -498,7 +498,9 @@ FILE SYSTEM MONITOR
This feature is intended to speed up git operations for repos that have
large working directories.

It enables git to work together with a file system monitor (see the
It enables git to work together with a file system monitor (see
linkgit:git-fsmonitor--daemon[1]
and the
"fsmonitor-watchman" section of linkgit:githooks[5]) that can
inform it as to what files have been modified. This enables git to avoid
having to lstat() every file to find modified files.
@ -509,8 +511,8 @@ looking for new files.

If you want to enable (or disable) this feature, it is easier to use
the `core.fsmonitor` configuration variable (see
linkgit:git-config[1]) than using the `--fsmonitor` option to
`git update-index` in each repository, especially if you want to do so
linkgit:git-config[1]) than using the `--fsmonitor` option to `git
update-index` in each repository, especially if you want to do so
across all repositories you use, because you can set the configuration
variable in your `$HOME/.gitconfig` just once and have it affect all
repositories you touch.

View File

@ -286,8 +286,8 @@ CONFIGURATION FILE
------------------
By default, the repository `config` file is shared across all working
trees. If the config variables `core.bare` or `core.worktree` are
already present in the config file, they will be applied to the main
working trees only.
present in the common config file and `extensions.worktreeConfig` is
disabled, then they will be applied to the main working tree only.

In order to have configuration specific to working trees, you can turn
on the `worktreeConfig` extension, e.g.:
@ -307,11 +307,16 @@ them to the `config.worktree` of the main working tree. You may also
take this opportunity to review and move other configuration that you
do not want to share to all working trees:

- `core.worktree` and `core.bare` should never be shared
- `core.worktree` should never be shared.

- `core.bare` should not be shared if the value is `core.bare=true`.

- `core.sparseCheckout` is recommended per working tree, unless you
are sure you always use sparse checkout for all working trees.

See the documentation of `extensions.worktreeConfig` in
linkgit:git-config[1] for more details.

DETAILS
-------
Each linked working tree has a private sub-directory in the repository's

View File

@ -161,11 +161,12 @@ unspecified.

This attribute sets a specific line-ending style to be used in the
working directory. This attribute has effect only if the `text`
attribute is set or unspecified, or if it is set to `auto` and the file
is detected as text. Note that setting this attribute on paths which
are in the index with CRLF line endings may make the paths to be
considered dirty. Adding the path to the index again will normalize the
line endings in the index.
attribute is set or unspecified, or if it is set to `auto`, the file is
detected as text, and it is stored with LF endings in the index. Note
that setting this attribute on paths which are in the index with CRLF
line endings may make the paths to be considered dirty unless
`text=auto` is set. Adding the path to the index again will normalize
the line endings in the index.

Set to string value "crlf"::


View File

@ -312,7 +312,7 @@ Pathspecs are used on the command line of "git ls-files", "git
ls-tree", "git add", "git grep", "git diff", "git checkout",
and many other commands to
limit the scope of operations to some subset of the tree or
worktree. See the documentation of each command for whether
working tree. See the documentation of each command for whether
paths are relative to the current directory or toplevel. The
pathspec syntax is as follows:
+
@ -446,7 +446,7 @@ exclude;;
interface than the <<def_plumbing,plumbing>>.

[[def_per_worktree_ref]]per-worktree ref::
Refs that are per-<<def_working_tree,worktree>>, rather than
Refs that are per-<<def_worktree,worktree>>, rather than
global. This is presently only <<def_HEAD,HEAD>> and any refs
that start with `refs/bisect/`, but might later include other
unusual refs.
@ -669,3 +669,12 @@ The most notable example is `HEAD`.
The tree of actual checked out files. The working tree normally
contains the contents of the <<def_HEAD,HEAD>> commit's tree,
plus any local changes that you have made but not yet committed.

[[def_worktree]]worktree::
A repository can have zero (i.e. bare repository) or one or
more worktrees attached to it. One "worktree" consists of a
"working tree" and repository metadata, most of which are
shared among other worktrees of a single repository, and
some of which are maintained separately per worktree
(e.g. the index, HEAD and pseudorefs like MERGE_HEAD,
per-worktree refs and per-worktree configuration file).

View File

@ -122,19 +122,27 @@ again. Equivalent forms are `--min-parents=0` (any commit has 0 or more
parents) and `--max-parents=-1` (negative numbers denote no upper limit).

--first-parent::
Follow only the first parent commit upon seeing a merge
commit. This option can give a better overview when
viewing the evolution of a particular topic branch,
because merges into a topic branch tend to be only about
adjusting to updated upstream from time to time, and
this option allows you to ignore the individual commits
brought in to your history by such a merge.
When finding commits to include, follow only the first
parent commit upon seeing a merge commit. This option
can give a better overview when viewing the evolution of
a particular topic branch, because merges into a topic
branch tend to be only about adjusting to updated upstream
from time to time, and this option allows you to ignore
the individual commits brought in to your history by such
a merge.
ifdef::git-log[]
+
This option also changes default diff format for merge commits
to `first-parent`, see `--diff-merges=first-parent` for details.
endif::git-log[]

--exclude-first-parent-only::
When finding commits to exclude (with a '{caret}'), follow only
the first parent commit upon seeing a merge commit.
This can be used to find the set of changes in a topic branch
from the point where it diverged from the remote branch, given
that arbitrary merges can be valid topic branch changes.

--not::
Reverses the meaning of the '{caret}' prefix (or lack thereof)
for all following revision specifiers, up to the next `--not`.

View File

@ -470,6 +470,11 @@ all::
# directory, and the JSON compilation database 'compile_commands.json' will be
# created at the root of the repository.
#
# If your platform supports a built-in fsmonitor backend, set
# FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding
# `compat/fsmonitor/fsm-listen-<name>.c` that implements the
# `fsm_listen__*()` routines.
#
# 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). You can still set
@ -711,6 +716,7 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o
TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
TEST_BUILTINS_OBJS += test-example-decorate.o
TEST_BUILTINS_OBJS += test-fast-rebase.o
TEST_BUILTINS_OBJS += test-fsmonitor-client.o
TEST_BUILTINS_OBJS += test-genrandom.o
TEST_BUILTINS_OBJS += test-genzeros.o
TEST_BUILTINS_OBJS += test-getcwd.o
@ -907,6 +913,8 @@ LIB_OBJS += fetch-pack.o
LIB_OBJS += fmt-merge-msg.o
LIB_OBJS += fsck.o
LIB_OBJS += fsmonitor.o
LIB_OBJS += fsmonitor-ipc.o
LIB_OBJS += fsmonitor-settings.o
LIB_OBJS += gettext.o
LIB_OBJS += gpg-interface.o
LIB_OBJS += graph.o
@ -1112,6 +1120,7 @@ BUILTIN_OBJS += builtin/fmt-merge-msg.o
BUILTIN_OBJS += builtin/for-each-ref.o
BUILTIN_OBJS += builtin/for-each-repo.o
BUILTIN_OBJS += builtin/fsck.o
BUILTIN_OBJS += builtin/fsmonitor--daemon.o
BUILTIN_OBJS += builtin/gc.o
BUILTIN_OBJS += builtin/get-tar-commit-id.o
BUILTIN_OBJS += builtin/grep.o
@ -1965,6 +1974,11 @@ ifdef NEED_ACCESS_ROOT_HANDLER
COMPAT_OBJS += compat/access.o
endif

ifdef FSMONITOR_DAEMON_BACKEND
COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o
endif

ifeq ($(TCLTK_PATH),)
NO_TCLTK = NoThanks
endif
@ -2884,6 +2898,9 @@ GIT-BUILD-OPTIONS: FORCE
@echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
@echo SANITIZE_LEAK=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_LEAK)))'\' >>$@+
@echo X=\'$(X)\' >>$@+
ifdef FSMONITOR_DAEMON_BACKEND
@echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+
endif
ifdef TEST_OUTPUT_DIRECTORY
@echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+
endif

View File

@ -797,14 +797,14 @@ static int run_revert(struct add_i_state *s, const struct pathspec *ps,
diffopt.flags.override_submodule_config = 1;
diffopt.repo = s->r;

if (do_diff_cache(&oid, &diffopt))
if (do_diff_cache(&oid, &diffopt)) {
diff_free(&diffopt);
res = -1;
else {
} else {
diffcore_std(&diffopt);
diff_flush(&diffopt);
}
free(paths);
clear_pathspec(&diffopt.pathspec);

if (!res && write_locked_index(s->r->index, &index_lock,
COMMIT_LOCK) < 0)

View File

@ -70,6 +70,7 @@ static struct {
[ADVICE_STATUS_HINTS] = { "statusHints", 1 },
[ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 },
[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 },
[ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated", 1 },
[ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 },
[ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 },
};

View File

@ -44,6 +44,7 @@ struct string_list;
ADVICE_STATUS_HINTS,
ADVICE_STATUS_U_OPTION,
ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
ADVICE_SUBMODULES_NOT_UPDATED,
ADVICE_UPDATE_SPARSE_PATH,
ADVICE_WAITING_FOR_EDITOR,
ADVICE_SKIPPED_CHERRY_PICKS,

View File

@ -9,6 +9,7 @@
#include "object-store.h"
#include "userdiff.h"
#include "xdiff-interface.h"
#include "date.h"

static int zip_date;
static int zip_time;

View File

@ -12,7 +12,7 @@

static char const * const archive_usage[] = {
N_("git archive [<options>] <tree-ish> [<path>...]"),
N_("git archive --list"),
"git archive --list",
N_("git archive --remote <repo> [--exec <cmd>] [<options>] <tree-ish> [<path>...]"),
N_("git archive --remote <repo> [--exec <cmd>] --list"),
NULL

View File

@ -1403,7 +1403,6 @@ static struct blame_origin *find_origin(struct repository *r,
}
}
diff_flush(&diff_opts);
clear_pathspec(&diff_opts.pathspec);
return porigin;
}

@ -1447,7 +1446,6 @@ static struct blame_origin *find_rename(struct repository *r,
}
}
diff_flush(&diff_opts);
clear_pathspec(&diff_opts.pathspec);
return porigin;
}

@ -2328,7 +2326,6 @@ static void find_copy_in_parent(struct blame_scoreboard *sb,
} while (unblamed);
target->suspects = reverse_blame(leftover, NULL);
diff_flush(&diff_opts);
clear_pathspec(&diff_opts.pathspec);
}

/*
@ -2615,7 +2612,7 @@ void assign_blame(struct blame_scoreboard *sb, int opt)
else {
commit->object.flags |= UNINTERESTING;
if (commit->object.parsed)
mark_parents_uninteresting(commit);
mark_parents_uninteresting(sb->revs, commit);
}
/* treat root commit as boundary */
if (!commit->parents && !sb->show_root)

277
branch.c
View File

@ -8,6 +8,8 @@
#include "sequencer.h"
#include "commit.h"
#include "worktree.h"
#include "submodule-config.h"
#include "run-command.h"

struct tracking {
struct refspec_item spec;
@ -218,9 +220,11 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
}

/*
* This is called when new_ref is branched off of orig_ref, and tries
* to infer the settings for branch.<new_ref>.{remote,merge} from the
* config.
* Used internally to set the branch.<new_ref>.{remote,merge} config
* settings so that branch 'new_ref' tracks 'orig_ref'. Unlike
* dwim_and_setup_tracking(), this does not do DWIM, i.e. "origin/main"
* will not be expanded to "refs/remotes/origin/main", so it is not safe
* for 'orig_ref' to be raw user input.
*/
static void setup_tracking(const char *new_ref, const char *orig_ref,
enum branch_track track, int quiet)
@ -235,7 +239,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
if (track != BRANCH_TRACK_INHERIT)
for_each_remote(find_tracked_branch, &tracking);
else if (inherit_tracking(&tracking, orig_ref))
return;
goto cleanup;

if (!tracking.matches)
switch (track) {
@ -245,7 +249,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
case BRANCH_TRACK_INHERIT:
break;
default:
return;
goto cleanup;
}

if (tracking.matches > 1)
@ -258,7 +262,8 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
tracking.remote, tracking.srcs) < 0)
exit(-1);

string_list_clear(tracking.srcs, 0);
cleanup:
string_list_clear(&tracking_srcs, 0);
}

int read_branch_desc(struct strbuf *buf, const char *branch_name)
@ -346,31 +351,37 @@ N_("\n"
"will track its remote counterpart, you may want to use\n"
"\"git push -u\" to set the upstream config as you push.");

void create_branch(struct repository *r,
const char *name, const char *start_name,
int force, int clobber_head_ok, int reflog,
int quiet, enum branch_track track)
/**
* DWIMs a user-provided ref to determine the starting point for a
* branch and validates it, where:
*
* - r is the repository to validate the branch for
*
* - start_name is the ref that we would like to test. This is
* expanded with DWIM and assigned to out_real_ref.
*
* - track is the tracking mode of the new branch. If tracking is
* explicitly requested, start_name must be a branch (because
* otherwise start_name cannot be tracked)
*
* - out_oid is an out parameter containing the object_id of start_name
*
* - out_real_ref is an out parameter containing the full, 'real' form
* of start_name e.g. refs/heads/main instead of main
*
*/
static void dwim_branch_start(struct repository *r, const char *start_name,
enum branch_track track, char **out_real_ref,
struct object_id *out_oid)
{
struct commit *commit;
struct object_id oid;
char *real_ref;
struct strbuf ref = STRBUF_INIT;
int forcing = 0;
int dont_change_ref = 0;
int explicit_tracking = 0;

if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
explicit_tracking = 1;

if ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)
? validate_branchname(name, &ref)
: validate_new_branchname(name, &ref, force)) {
if (!force)
dont_change_ref = 1;
else
forcing = 1;
}

real_ref = NULL;
if (get_oid_mb(start_name, &oid)) {
if (explicit_tracking) {
@ -407,40 +418,218 @@ void create_branch(struct repository *r,

if ((commit = lookup_commit_reference(r, &oid)) == NULL)
die(_("not a valid branch point: '%s'"), start_name);
oidcpy(&oid, &commit->object.oid);
if (out_real_ref) {
*out_real_ref = real_ref;
real_ref = NULL;
}
if (out_oid)
oidcpy(out_oid, &commit->object.oid);

FREE_AND_NULL(real_ref);
}

void create_branch(struct repository *r,
const char *name, const char *start_name,
int force, int clobber_head_ok, int reflog,
int quiet, enum branch_track track, int dry_run)
{
struct object_id oid;
char *real_ref;
struct strbuf ref = STRBUF_INIT;
int forcing = 0;
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
char *msg;

if (track == BRANCH_TRACK_OVERRIDE)
BUG("'track' cannot be BRANCH_TRACK_OVERRIDE. Did you mean to call dwim_and_setup_tracking()?");
if (clobber_head_ok && !force)
BUG("'clobber_head_ok' can only be used with 'force'");

if (clobber_head_ok ?
validate_branchname(name, &ref) :
validate_new_branchname(name, &ref, force)) {
forcing = 1;
}

dwim_branch_start(r, start_name, track, &real_ref, &oid);
if (dry_run)
goto cleanup;

if (reflog)
log_all_ref_updates = LOG_REFS_NORMAL;

if (!dont_change_ref) {
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
char *msg;

if (forcing)
msg = xstrfmt("branch: Reset to %s", start_name);
else
msg = xstrfmt("branch: Created from %s", start_name);

transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
&oid, forcing ? NULL : null_oid(),
0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
strbuf_release(&err);
free(msg);
}
if (forcing)
msg = xstrfmt("branch: Reset to %s", start_name);
else
msg = xstrfmt("branch: Created from %s", start_name);
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
&oid, forcing ? NULL : null_oid(),
0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
strbuf_release(&err);
free(msg);

if (real_ref && track)
setup_tracking(ref.buf + 11, real_ref, track, quiet);

cleanup:
strbuf_release(&ref);
free(real_ref);
}

void dwim_and_setup_tracking(struct repository *r, const char *new_ref,
const char *orig_ref, enum branch_track track,
int quiet)
{
char *real_orig_ref;
dwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL);
setup_tracking(new_ref, real_orig_ref, track, quiet);
}

/**
* Creates a branch in a submodule by calling
* create_branches_recursively() in a child process. The child process
* is necessary because install_branch_config_multiple_remotes() (which
* is called by setup_tracking()) does not support writing configs to
* submodules.
*/
static int submodule_create_branch(struct repository *r,
const struct submodule *submodule,
const char *name, const char *start_oid,
const char *tracking_name, int force,
int reflog, int quiet,
enum branch_track track, int dry_run)
{
int ret = 0;
struct child_process child = CHILD_PROCESS_INIT;
struct strbuf child_err = STRBUF_INIT;
struct strbuf out_buf = STRBUF_INIT;
char *out_prefix = xstrfmt("submodule '%s': ", submodule->name);
child.git_cmd = 1;
child.err = -1;
child.stdout_to_stderr = 1;

prepare_other_repo_env(&child.env_array, r->gitdir);
/*
* submodule_create_branch() is indirectly invoked by "git
* branch", but we cannot invoke "git branch" in the child
* process. "git branch" accepts a branch name and start point,
* where the start point is assumed to provide both the OID
* (start_oid) and the branch to use for tracking
* (tracking_name). But when recursing through submodules,
* start_oid and tracking name need to be specified separately
* (see create_branches_recursively()).
*/
strvec_pushl(&child.args, "submodule--helper", "create-branch", NULL);
if (dry_run)
strvec_push(&child.args, "--dry-run");
if (force)
strvec_push(&child.args, "--force");
if (quiet)
strvec_push(&child.args, "--quiet");
if (reflog)
strvec_push(&child.args, "--create-reflog");
if (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)
strvec_push(&child.args, "--track");

strvec_pushl(&child.args, name, start_oid, tracking_name, NULL);

if ((ret = start_command(&child)))
return ret;
ret = finish_command(&child);
strbuf_read(&child_err, child.err, 0);
strbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);

if (ret)
fprintf(stderr, "%s", out_buf.buf);
else
printf("%s", out_buf.buf);

strbuf_release(&child_err);
strbuf_release(&out_buf);
return ret;
}

void create_branches_recursively(struct repository *r, const char *name,
const char *start_commitish,
const char *tracking_name, int force,
int reflog, int quiet, enum branch_track track,
int dry_run)
{
int i = 0;
char *branch_point = NULL;
struct object_id super_oid;
struct submodule_entry_list submodule_entry_list;

/* Perform dwim on start_commitish to get super_oid and branch_point. */
dwim_branch_start(r, start_commitish, BRANCH_TRACK_NEVER,
&branch_point, &super_oid);

/*
* If we were not given an explicit name to track, then assume we are at
* the top level and, just like the non-recursive case, the tracking
* name is the branch point.
*/
if (!tracking_name)
tracking_name = branch_point;

submodules_of_tree(r, &super_oid, &submodule_entry_list);
/*
* Before creating any branches, first check that the branch can
* be created in every submodule.
*/
for (i = 0; i < submodule_entry_list.entry_nr; i++) {
if (submodule_entry_list.entries[i].repo == NULL) {
if (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))
advise(_("You may try updating the submodules using 'git checkout %s && git submodule update --init'"),
start_commitish);
die(_("submodule '%s': unable to find submodule"),
submodule_entry_list.entries[i].submodule->name);
}

if (submodule_create_branch(
submodule_entry_list.entries[i].repo,
submodule_entry_list.entries[i].submodule, name,
oid_to_hex(&submodule_entry_list.entries[i]
.name_entry->oid),
tracking_name, force, reflog, quiet, track, 1))
die(_("submodule '%s': cannot create branch '%s'"),
submodule_entry_list.entries[i].submodule->name,
name);
}

create_branch(the_repository, name, start_commitish, force, 0, reflog, quiet,
BRANCH_TRACK_NEVER, dry_run);
if (dry_run)
return;
/*
* NEEDSWORK If tracking was set up in the superproject but not the
* submodule, users might expect "git branch --recurse-submodules" to
* fail or give a warning, but this is not yet implemented because it is
* tedious to determine whether or not tracking was set up in the
* superproject.
*/
setup_tracking(name, tracking_name, track, quiet);

for (i = 0; i < submodule_entry_list.entry_nr; i++) {
if (submodule_create_branch(
submodule_entry_list.entries[i].repo,
submodule_entry_list.entries[i].submodule, name,
oid_to_hex(&submodule_entry_list.entries[i]
.name_entry->oid),
tracking_name, force, reflog, quiet, track, 0))
die(_("submodule '%s': cannot create branch '%s'"),
submodule_entry_list.entries[i].submodule->name,
name);
repo_clear(submodule_entry_list.entries[i].repo);
}
}

void remove_merge_branch_state(struct repository *r)
{
unlink(git_path_merge_head(r));

View File

@ -18,6 +18,28 @@ extern enum branch_track git_branch_track;

/* Functions for acting on the information about branches. */

/**
* Sets branch.<new_ref>.{remote,merge} config settings such that
* new_ref tracks orig_ref according to the specified tracking mode.
*
* - new_ref is the name of the branch that we are setting tracking
* for.
*
* - orig_ref is the name of the ref that is 'upstream' of new_ref.
* orig_ref will be expanded with DWIM so that the config settings
* are in the correct format e.g. "refs/remotes/origin/main" instead
* of "origin/main".
*
* - track is the tracking mode e.g. BRANCH_TRACK_REMOTE causes
* new_ref to track orig_ref directly, whereas BRANCH_TRACK_INHERIT
* causes new_ref to track whatever orig_ref tracks.
*
* - quiet suppresses tracking information.
*/
void dwim_and_setup_tracking(struct repository *r, const char *new_ref,
const char *orig_ref, enum branch_track track,
int quiet);

/*
* Creates a new branch, where:
*
@ -30,8 +52,8 @@ extern enum branch_track git_branch_track;
*
* - force enables overwriting an existing (non-head) branch
*
* - clobber_head_ok allows the currently checked out (hence existing)
* branch to be overwritten; without 'force', it has no effect.
* - clobber_head_ok, when enabled with 'force', allows the currently
* checked out (head) branch to be overwritten
*
* - reflog creates a reflog for the branch
*
@ -40,12 +62,44 @@ extern enum branch_track git_branch_track;
* - track causes the new branch to be configured to merge the remote branch
* that start_name is a tracking branch for (if any).
*
* - dry_run causes the branch to be validated but not created.
*
*/
void create_branch(struct repository *r,
const char *name, const char *start_name,
int force, int clobber_head_ok,
int reflog, int quiet, enum branch_track track);
int reflog, int quiet, enum branch_track track,
int dry_run);

/*
* Creates a new branch in a repository and its submodules (and its
* submodules, recursively). The parameters are mostly analogous to
* those of create_branch() except for start_name, which is represented
* by two different parameters:
*
* - start_commitish is the commit-ish, in repository r, that determines
* which commits the branches will point to. The superproject branch
* will point to the commit of start_commitish and the submodule
* branches will point to the gitlink commit oids in start_commitish's
* tree.
*
* - tracking_name is the name of the ref, in repository r, that will be
* used to set up tracking information. This value is propagated to
* all submodules, which will evaluate the ref using their own ref
* stores. If NULL, this defaults to start_commitish.
*
* When this function is called on the superproject, start_commitish
* can be any user-provided ref and tracking_name can be NULL (similar
* to create_branches()). But when recursing through submodules,
* start_commitish is the plain gitlink commit oid. Since the oid cannot
* be used for tracking information, tracking_name is propagated and
* used for tracking instead.
*/
void create_branches_recursively(struct repository *r, const char *name,
const char *start_commitish,
const char *tracking_name, int force,
int reflog, int quiet, enum branch_track track,
int dry_run);
/*
* Check if 'name' can be a valid name for a branch; die otherwise.
* Return 1 if the named branch already exists; return 0 otherwise.

View File

@ -159,6 +159,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
int cmd_for_each_repo(int argc, const char **argv, const char *prefix);
int cmd_format_patch(int argc, const char **argv, const char *prefix);
int cmd_fsck(int argc, const char **argv, const char *prefix);
int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix);
int cmd_gc(int argc, const char **argv, const char *prefix);
int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
int cmd_grep(int argc, const char **argv, const char *prefix);

View File

@ -34,6 +34,7 @@
#include "string-list.h"
#include "packfile.h"
#include "repository.h"
#include "pretty.h"

/**
* Returns the length of the first line of msg.
@ -199,7 +200,7 @@ static int am_option_parse_empty(const struct option *opt,
else if (!strcmp(arg, "keep"))
*opt_value = KEEP_EMPTY_COMMIT;
else
return error(_("Invalid value for --empty: %s"), arg);
return error(_("invalid value for '%s': '%s'"), "--empty", arg);

return 0;
}
@ -2239,7 +2240,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
* when you add new options
*/
else
return error(_("Invalid value for --patch-format: %s"), arg);
return error(_("invalid value for '%s': '%s'"),
"--patch-format", arg);
return 0;
}

@ -2282,7 +2284,8 @@ static int parse_opt_show_current_patch(const struct option *opt, const char *ar
break;
}
if (new_value >= ARRAY_SIZE(valid_modes))
return error(_("Invalid value for --show-current-patch: %s"), arg);
return error(_("invalid value for '%s': '%s'"),
"--show-current-patch", arg);
}

if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode)

View File

@ -22,15 +22,15 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")

static const char * const git_bisect_helper_usage[] = {
N_("git bisect--helper --bisect-reset [<commit>]"),
N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
"git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]",
N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]"
" [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"),
N_("git bisect--helper --bisect-next"),
"git bisect--helper --bisect-next",
N_("git bisect--helper --bisect-state (bad|new) [<rev>]"),
N_("git bisect--helper --bisect-state (good|old) [<rev>...]"),
N_("git bisect--helper --bisect-replay <filename>"),
N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"),
N_("git bisect--helper --bisect-visualize"),
"git bisect--helper --bisect-visualize",
N_("git bisect--helper --bisect-run <cmd>..."),
NULL
};

View File

@ -721,8 +721,8 @@ static int git_blame_config(const char *var, const char *value, void *cb)
}
if (!strcmp(var, "color.blame.repeatedlines")) {
if (color_parse_mem(value, strlen(value), repeated_meta_color))
warning(_("invalid color '%s' in color.blame.repeatedLines"),
value);
warning(_("invalid value for '%s': '%s'"),
"color.blame.repeatedLines", value);
return 0;
}
if (!strcmp(var, "color.blame.highlightrecent")) {
@ -739,7 +739,8 @@ static int git_blame_config(const char *var, const char *value, void *cb)
coloring_mode &= ~(OUTPUT_COLOR_LINE |
OUTPUT_SHOW_AGE_WITH_COLOR);
} else {
warning(_("invalid value for blame.coloring"));
warning(_("invalid value for '%s': '%s'"),
"blame.coloring", value);
return 0;
}
}
@ -934,6 +935,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
}
parse_done:
revision_opts_finish(&revs);
no_whole_file_rename = !revs.diffopt.flags.follow_renames;
xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC;
revs.diffopt.flags.follow_renames = 0;

View File

@ -27,7 +27,8 @@

static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
N_("git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]"),
N_("git branch [<options>] [-l] [<pattern>...]"),
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = {

static const char *head;
static struct object_id head_oid;
static int recurse_submodules = 0;
static int submodule_propagate_branches = 0;

static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
@ -99,6 +102,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)
return config_error_nonbool(var);
return color_parse(value, branch_colors[slot]);
}
if (!strcmp(var, "submodule.recurse")) {
recurse_submodules = git_config_bool(var, value);
return 0;
}
if (!strcasecmp(var, "submodule.propagateBranches")) {
submodule_propagate_branches = git_config_bool(var, value);
return 0;
}

return git_color_default_config(var, value, cb);
}

@ -621,14 +633,16 @@ 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;
/* possible actions */
int delete = 0, rename = 0, copy = 0, list = 0,
unset_upstream = 0, show_current = 0, edit_description = 0;
const char *new_upstream = NULL;
int noncreate_actions = 0;
/* possible options */
int reflog = 0, quiet = 0, icase = 0, force = 0,
recurse_submodules_explicit = 0;
enum branch_track track;
struct ref_filter filter;
int icase = 0;
static struct ref_sorting *sorting;
struct string_list sorting_options = STRING_LIST_INIT_DUP;
struct ref_format format = REF_FORMAT_INIT;
@ -677,6 +691,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"),
N_("print only branches of the object"), parse_opt_object_name),
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_BOOL(0, "recurse-submodules", &recurse_submodules_explicit, N_("recurse through submodules")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_END(),
};
@ -713,10 +728,23 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
filter.reachable_from || filter.unreachable_from || filter.points_at.nr)
list = 1;

if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +
list + edit_description + unset_upstream > 1)
noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
!!show_current + !!list + !!edit_description +
!!unset_upstream;
if (noncreate_actions > 1)
usage_with_options(builtin_branch_usage, options);

if (recurse_submodules_explicit) {
if (!submodule_propagate_branches)
die(_("branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled"));
if (noncreate_actions)
die(_("--recurse-submodules can only be used to create branches"));
}

recurse_submodules =
(recurse_submodules || recurse_submodules_explicit) &&
submodule_propagate_branches;

if (filter.abbrev == -1)
filter.abbrev = DEFAULT_ABBREV;
filter.ignore_case = icase;
@ -828,12 +856,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (!ref_exists(branch->refname))
die(_("branch '%s' does not exist"), branch->name);

/*
* create_branch takes care of setting up the tracking
* info and making sure new_upstream is correct
*/
create_branch(the_repository, branch->name, new_upstream,
0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);
dwim_and_setup_tracking(the_repository, branch->name,
new_upstream, BRANCH_TRACK_OVERRIDE,
quiet);
} else if (unset_upstream) {
struct branch *branch = branch_get(argv[0]);
struct strbuf buf = STRBUF_INIT;
@ -857,7 +882,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
strbuf_addf(&buf, "branch.%s.merge", branch->name);
git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_release(&buf);
} else if (argc > 0 && argc <= 2) {
} else if (!noncreate_actions && argc > 0 && argc <= 2) {
const char *branch_name = argv[0];
const char *start_name = argc == 2 ? argv[1] : head;

if (filter.kind != FILTER_REFS_BRANCHES)
die(_("The -a, and -r, options to 'git branch' do not take a branch name.\n"
"Did you mean to use: -a|-r --list <pattern>?"));
@ -865,10 +893,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (track == BRANCH_TRACK_OVERRIDE)
die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));

create_branch(the_repository,
argv[0], (argc == 2) ? argv[1] : head,
force, 0, reflog, quiet, track);

if (recurse_submodules) {
create_branches_recursively(the_repository, branch_name,
start_name, NULL, force,
reflog, quiet, track, 0);
return 0;
}
create_branch(the_repository, branch_name, start_name, force, 0,
reflog, quiet, track, 0);
} else
usage_with_options(builtin_branch_usage, options);


View File

@ -7,6 +7,7 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "dir.h"
#include "lockfile.h"
#include "quote.h"
#include "cache-tree.h"
@ -17,6 +18,7 @@
#define CHECKOUT_ALL 4
static int nul_term_line;
static int checkout_stage; /* default to checkout stage0 */
static int ignore_skip_worktree; /* default to 0 */
static int to_tempfile;
static char topath[4][TEMPORARY_FILENAME_LENGTH + 1];

@ -65,6 +67,8 @@ static int checkout_file(const char *name, const char *prefix)
int namelen = strlen(name);
int pos = cache_name_pos(name, namelen);
int has_same_name = 0;
int is_file = 0;
int is_skipped = 1;
int did_checkout = 0;
int errs = 0;

@ -78,6 +82,12 @@ static int checkout_file(const char *name, const char *prefix)
break;
has_same_name = 1;
pos++;
if (S_ISSPARSEDIR(ce->ce_mode))
break;
is_file = 1;
if (!ignore_skip_worktree && ce_skip_worktree(ce))
break;
is_skipped = 0;
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
@ -106,6 +116,11 @@ static int checkout_file(const char *name, const char *prefix)
fprintf(stderr, "git checkout-index: %s ", name);
if (!has_same_name)
fprintf(stderr, "is not in the cache");
else if (!is_file)
fprintf(stderr, "is a sparse directory");
else if (is_skipped)
fprintf(stderr, "has skip-worktree enabled; "
"use '--ignore-skip-worktree-bits' to checkout");
else if (checkout_stage)
fprintf(stderr, "does not exist at stage %d",
checkout_stage);
@ -121,10 +136,27 @@ static int checkout_all(const char *prefix, int prefix_length)
int i, errs = 0;
struct cache_entry *last_ce = NULL;

/* TODO: audit for interaction with sparse-index. */
ensure_full_index(&the_index);
for (i = 0; i < active_nr ; i++) {
struct cache_entry *ce = active_cache[i];

if (S_ISSPARSEDIR(ce->ce_mode)) {
if (!ce_skip_worktree(ce))
BUG("sparse directory '%s' does not have skip-worktree set", ce->name);

/*
* If the current entry is a sparse directory and skip-worktree
* entries are being checked out, expand the index and continue
* the loop on the current index position (now pointing to the
* first entry inside the expanded sparse directory).
*/
if (ignore_skip_worktree) {
ensure_full_index(&the_index);
ce = active_cache[i];
}
}

if (!ignore_skip_worktree && ce_skip_worktree(ce))
continue;
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
@ -185,6 +217,8 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
OPT_BOOL(0, "ignore-skip-worktree-bits", &ignore_skip_worktree,
N_("do not skip files with skip-worktree set")),
OPT__FORCE(&force, N_("force overwrite of existing files"), 0),
OPT__QUIET(&quiet,
N_("no warning for existing files and files not in index")),
@ -212,6 +246,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
prefix_length = prefix ? strlen(prefix) : 0;

prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;

if (read_cache() < 0) {
die("invalid cache");
}

View File

@ -909,7 +909,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
opts->new_branch_force ? 1 : 0,
opts->new_branch_log,
opts->quiet,
opts->track);
opts->track,
0);
free(new_branch_info->name);
free(new_branch_info->refname);
new_branch_info->name = xstrdup(opts->new_branch);
@ -1607,9 +1608,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
opts->show_progress = -1;

git_config(git_checkout_config, opts);

prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
if (the_repository->gitdir) {
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
}

opts->track = BRANCH_TRACK_UNSPECIFIED;


View File

@ -1009,6 +1009,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS;
}

prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;

if (read_cache() < 0)
die(_("index file corrupt"));


View File

@ -72,6 +72,8 @@ static int option_dissociate;
static int max_jobs = -1;
static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
static struct list_objects_filter_options filter_options;
static int option_filter_submodules = -1; /* unspecified */
static int config_filter_submodules = -1; /* unspecified */
static struct string_list server_options = STRING_LIST_INIT_NODUP;
static int option_remote_submodules;

@ -151,6 +153,8 @@ static struct option builtin_clone_options[] = {
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
TRANSPORT_FAMILY_IPV6),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_BOOL(0, "also-filter-submodules", &option_filter_submodules,
N_("apply partial clone filters to submodules")),
OPT_BOOL(0, "remote-submodules", &option_remote_submodules,
N_("any cloned submodules will use their remote-tracking branch")),
OPT_BOOL(0, "sparse", &option_sparse_checkout,
@ -651,7 +655,7 @@ static int git_sparse_checkout_init(const char *repo)
return result;
}

static int checkout(int submodule_progress)
static int checkout(int submodule_progress, int filter_submodules)
{
struct object_id oid;
char *head;
@ -730,6 +734,10 @@ static int checkout(int submodule_progress)
strvec_push(&args, "--no-fetch");
}

if (filter_submodules && filter_options.choice)
strvec_pushf(&args, "--filter=%s",
expand_list_objects_filter_spec(&filter_options));

if (option_single_branch >= 0)
strvec_push(&args, option_single_branch ?
"--single-branch" :
@ -750,6 +758,8 @@ static int git_clone_config(const char *k, const char *v, void *cb)
}
if (!strcmp(k, "clone.rejectshallow"))
config_reject_shallow = git_config_bool(k, v);
if (!strcmp(k, "clone.filtersubmodules"))
config_filter_submodules = git_config_bool(k, v);

return git_default_config(k, v, cb);
}
@ -872,6 +882,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
struct remote *remote;
int err = 0, complete_refs_before_fetch = 1;
int submodule_progress;
int filter_submodules = 0;

struct transport_ls_refs_options transport_ls_refs_options =
TRANSPORT_LS_REFS_OPTIONS_INIT;
@ -1067,6 +1078,27 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_reject_shallow != -1)
reject_shallow = option_reject_shallow;

/*
* If option_filter_submodules is specified from CLI option,
* ignore config_filter_submodules from git_clone_config.
*/
if (config_filter_submodules != -1)
filter_submodules = config_filter_submodules;
if (option_filter_submodules != -1)
filter_submodules = option_filter_submodules;

/*
* Exit if the user seems to be doing something silly with submodule
* filter flags (but not with filter configs, as those should be
* set-and-forget).
*/
if (option_filter_submodules > 0 && !filter_options.choice)
die(_("the option '%s' requires '%s'"),
"--also-filter-submodules", "--filter");
if (option_filter_submodules > 0 && !option_recurse_submodules.nr)
die(_("the option '%s' requires '%s'"),
"--also-filter-submodules", "--recurse-submodules");

/*
* apply the remote name provided by --origin only after this second
* call to git_config, to ensure it overrides all config-based values.
@ -1235,7 +1267,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}
else {
const char *branch;
char *ref;
const char *ref;
char *ref_free = NULL;

if (option_branch)
die(_("Remote branch %s not found in upstream %s"),
@ -1251,17 +1284,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
skip_prefix(transport_ls_refs_options.unborn_head_target,
"refs/heads/", &branch)) {
ref = transport_ls_refs_options.unborn_head_target;
transport_ls_refs_options.unborn_head_target = NULL;
create_symref("HEAD", ref, reflog_msg.buf);
} else {
branch = git_default_branch_name(0);
ref = xstrfmt("refs/heads/%s", branch);
ref_free = xstrfmt("refs/heads/%s", branch);
ref = ref_free;
}

if (!option_bare)
install_branch_config(0, branch, remote_name, ref);

free(ref);
free(ref_free);
}

write_refspec_config(src_ref_prefix, our_head_points_at,
@ -1300,7 +1332,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}

junk_mode = JUNK_LEAVE_REPO;
err = checkout(submodule_progress);
err = checkout(submodule_progress, filter_submodules);

free(remote_name);
strbuf_release(&reflog_msg);
@ -1313,7 +1345,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
UNLEAK(repo);
junk_mode = JUNK_LEAVE_ALL;

strvec_clear(&transport_ls_refs_options.ref_prefixes);
free(transport_ls_refs_options.unborn_head_target);
transport_ls_refs_options_release(&transport_ls_refs_options);
return err;
}

View File

@ -37,6 +37,7 @@
#include "help.h"
#include "commit-reach.h"
#include "commit-graph.h"
#include "pretty.h"

static const char * const builtin_commit_usage[] = {
N_("git commit [<options>] [--] <pathspec>..."),
@ -1242,8 +1243,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
struct commit *current_head,
struct wt_status *s)
{
int f = 0;

argc = parse_options(argc, argv, prefix, options, usage, 0);
finalize_deferred_config(s);

@ -1251,7 +1250,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
force_author = find_author_by_nickname(force_author);

if (force_author && renew_authorship)
die(_("Using both --reset-author and --author does not make sense"));
die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");

if (logfile || have_option_m || use_message)
use_editor = 0;
@ -1268,20 +1267,16 @@ static int parse_and_validate_options(int argc, const char *argv[],
die(_("You are in the middle of a rebase -- cannot amend."));
}
if (fixup_message && squash_message)
die(_("Options --squash and --fixup cannot be used together"));
if (use_message)
f++;
if (edit_message)
f++;
if (fixup_message)
f++;
if (logfile)
f++;
if (f > 1)
die(_("Only one of -c/-C/-F/--fixup can be used."));
if (have_option_m && (edit_message || use_message || logfile))
die((_("Option -m cannot be combined with -c/-C/-F.")));
if (f || have_option_m)
die(_("options '%s' and '%s' cannot be used together"), "--squash", "--fixup");
die_for_incompatible_opt4(!!use_message, "-C",
!!edit_message, "-c",
!!logfile, "-F",
!!fixup_message, "--fixup");
die_for_incompatible_opt4(have_option_m, "-m",
!!edit_message, "-c",
!!use_message, "-C",
!!logfile, "-F");
if (use_message || edit_message || logfile ||fixup_message || have_option_m)
template_file = NULL;
if (edit_message)
use_message = edit_message;
@ -1306,9 +1301,10 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (patch_interactive)
interactive = 1;

if (also + only + all + interactive > 1)
die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));

die_for_incompatible_opt4(also, "-i/--include",
only, "-o/--only",
all, "-a/--all",
interactive, "--interactive/-p/--patch");
if (fixup_message) {
/*
* We limit --fixup's suboptions to only alpha characters.

View File

@ -87,7 +87,7 @@ static int print_alternate(struct object_directory *odb, void *data)
}

static char const * const count_objects_usage[] = {
N_("git count-objects [-v] [-H | --human-readable]"),
"git count-objects [-v] [-H | --human-readable]",
NULL
};


View File

@ -732,8 +732,9 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
} else if (dir_diff)
die(_("options '%s' and '%s' cannot be used together"), "--dir-diff", "--no-index");

if (use_gui_tool + !!difftool_cmd + !!extcmd > 1)
die(_("options '%s', '%s', and '%s' cannot be used together"), "--gui", "--tool", "--extcmd");
die_for_incompatible_opt3(use_gui_tool, "--gui",
!!difftool_cmd, "--tool",
!!extcmd, "--extcmd");

if (use_gui_tool)
setenv("GIT_MERGETOOL_GUI", "true", 1);

View File

@ -26,7 +26,7 @@
#include "commit-slab.h"

static const char *fast_export_usage[] = {
N_("git fast-export [rev-list-opts]"),
N_("git fast-export [<rev-list-opts>]"),
NULL
};


View File

@ -19,6 +19,7 @@
#include "mem-pool.h"
#include "commit-reach.h"
#include "khash.h"
#include "date.h"

#define PACK_ID_BITS 16
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)

View File

@ -764,8 +764,8 @@ static void prepare_format_display(struct ref *ref_map)
else if (!strcasecmp(format, "compact"))
compact_format = 1;
else
die(_("configuration fetch.output contains invalid value %s"),
format);
die(_("invalid value for '%s': '%s'"),
"fetch.output", format);

for (rm = ref_map; rm; rm = rm->next) {
if (rm->status == REF_STATUS_REJECT_SHALLOW ||
@ -1094,12 +1094,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
struct ref *rm;
char *url;
int want_status;
int summary_width = transport_summary_width(ref_map);
int summary_width = 0;

rc = open_fetch_head(&fetch_head);
if (rc)
return -1;

if (verbosity >= 0)
summary_width = transport_summary_width(ref_map);

if (raw_url)
url = transport_anonymize_url(raw_url);
else
@ -1345,7 +1348,6 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
int url_len, i, result = 0;
struct ref *ref, *stale_refs = get_stale_heads(rs, ref_map);
char *url;
int summary_width = transport_summary_width(stale_refs);
const char *dangling_msg = dry_run
? _(" (%s will become dangling)")
: _(" (%s has become dangling)");
@ -1374,6 +1376,8 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
}

if (verbosity >= 0) {
int summary_width = transport_summary_width(stale_refs);

for (ref = stale_refs; ref; ref = ref->next) {
struct strbuf sb = STRBUF_INIT;
if (!shown_url) {
@ -1594,7 +1598,7 @@ static int do_fetch(struct transport *transport,
} else
remote_refs = NULL;

strvec_clear(&transport_ls_refs_options.ref_prefixes);
transport_ls_refs_options_release(&transport_ls_refs_options);

ref_map = get_ref_map(transport->remote, remote_refs, rs,
tags, &autotags);
@ -2017,8 +2021,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}

git_config(git_fetch_config, NULL);
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
if (the_repository->gitdir) {
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
}

argc = parse_options(argc, argv, prefix,
builtin_fetch_options, builtin_fetch_usage, 0);

1479
builtin/fsmonitor--daemon.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,8 @@
#include "object-store.h"
#include "packfile.h"

static const char *grep_prefix;

static char const * const grep_usage[] = {
N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
NULL
@ -284,7 +286,7 @@ static int wait_all(void)
static int grep_cmd_config(const char *var, const char *value, void *cb)
{
int st = grep_config(var, value, cb);
if (git_color_default_config(var, value, cb) < 0)
if (git_color_default_config(var, value, NULL) < 0)
st = -1;

if (!strcmp(var, "grep.threads")) {
@ -315,11 +317,11 @@ static void grep_source_name(struct grep_opt *opt, const char *filename,
strbuf_reset(out);

if (opt->null_following_name) {
if (opt->relative && opt->prefix_length) {
if (opt->relative && grep_prefix) {
struct strbuf rel_buf = STRBUF_INIT;
const char *rel_name =
relative_path(filename + tree_name_len,
opt->prefix, &rel_buf);
grep_prefix, &rel_buf);

if (tree_name_len)
strbuf_add(out, filename, tree_name_len);
@ -332,8 +334,8 @@ static void grep_source_name(struct grep_opt *opt, const char *filename,
return;
}

if (opt->relative && opt->prefix_length)
quote_path(filename + tree_name_len, opt->prefix, out, 0);
if (opt->relative && grep_prefix)
quote_path(filename + tree_name_len, grep_prefix, out, 0);
else
quote_c_style(filename + tree_name_len, out, NULL, 0);

@ -843,7 +845,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
int i;
int dummy;
int use_index = 1;
int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
int allow_revs;

struct option options[] = {
@ -877,16 +878,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
N_("descend at most <depth> levels"), PARSE_OPT_NONEG,
NULL, 1 },
OPT_GROUP(""),
OPT_SET_INT('E', "extended-regexp", &pattern_type_arg,
OPT_SET_INT('E', "extended-regexp", &opt.pattern_type_option,
N_("use extended POSIX regular expressions"),
GREP_PATTERN_TYPE_ERE),
OPT_SET_INT('G', "basic-regexp", &pattern_type_arg,
OPT_SET_INT('G', "basic-regexp", &opt.pattern_type_option,
N_("use basic POSIX regular expressions (default)"),
GREP_PATTERN_TYPE_BRE),
OPT_SET_INT('F', "fixed-strings", &pattern_type_arg,
OPT_SET_INT('F', "fixed-strings", &opt.pattern_type_option,
N_("interpret patterns as fixed strings"),
GREP_PATTERN_TYPE_FIXED),
OPT_SET_INT('P', "perl-regexp", &pattern_type_arg,
OPT_SET_INT('P', "perl-regexp", &opt.pattern_type_option,
N_("use Perl-compatible regular expressions"),
GREP_PATTERN_TYPE_PCRE),
OPT_GROUP(""),
@ -962,9 +963,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
PARSE_OPT_NOCOMPLETE),
OPT_END()
};
grep_prefix = prefix;

git_config(grep_cmd_config, NULL);
grep_init(&opt, the_repository, prefix);
grep_init(&opt, the_repository);
git_config(grep_cmd_config, &opt);

/*
* If there is no -- then the paths must exist in the working
@ -979,7 +981,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, grep_usage,
PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_STOP_AT_NON_OPTION);
grep_commit_pattern_type(pattern_type_arg, &opt);

if (use_index && !startup_info->have_repository) {
int fallback = 0;
@ -1167,11 +1168,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (!show_in_pager && !opt.status_only)
setup_pager();

if (!use_index && (untracked || cached))
die(_("--cached or --untracked cannot be used with --no-index"));

if (untracked && cached)
die(_("--untracked cannot be used with --cached"));
die_for_incompatible_opt3(!use_index, "--no-index",
untracked, "--untracked",
cached, "--cached");

if (!use_index || untracked) {
int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;

View File

@ -81,7 +81,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
{
static const char * const hash_object_usage[] = {
N_("git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>..."),
N_("git hash-object --stdin-paths"),
"git hash-object --stdin-paths",
NULL
};
const char *type = blob_type;
@ -92,6 +92,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
int nongit = 0;
unsigned flags = HASH_FORMAT_CHECK;
const char *vpath = NULL;
char *vpath_free = NULL;
const struct option hash_object_options[] = {
OPT_STRING('t', NULL, &type, N_("type"), N_("object type")),
OPT_BIT('w', NULL, &flags, N_("write the object into the object database"),
@ -114,8 +115,10 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
else
prefix = setup_git_directory_gently(&nongit);

if (vpath && prefix)
vpath = prefix_filename(prefix, vpath);
if (vpath && prefix) {
vpath_free = prefix_filename(prefix, vpath);
vpath = vpath_free;
}

git_config(git_default_config, NULL);

@ -156,5 +159,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
if (stdin_paths)
hash_stdin_paths(type, no_filters, flags, literally);

free(vpath_free);

return 0;
}

View File

@ -77,8 +77,8 @@ static struct option builtin_help_options[] = {
static const char * const builtin_help_usage[] = {
N_("git help [-a|--all] [--[no-]verbose]]\n"
" [[-i|--info] [-m|--man] [-w|--web]] [<command>]"),
N_("git help [-g|--guides]"),
N_("git help [-c|--config]"),
"git help [-g|--guides]",
"git help [-c|--config]",
NULL
};


View File

@ -533,8 +533,6 @@ static int git_log_config(const char *var, const char *value, void *cb)
return 0;
}

if (grep_config(var, value, cb) < 0)
return -1;
if (git_gpg_config(var, value, cb) < 0)
return -1;
return git_diff_ui_config(var, value, cb);
@ -549,6 +547,8 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
git_config(git_log_config, NULL);

repo_init_revisions(the_repository, &rev, prefix);
git_config(grep_config, &rev.grep_filter);

rev.diff = 1;
rev.simplify_history = 0;
memset(&opt, 0, sizeof(opt));
@ -663,6 +663,8 @@ int cmd_show(int argc, const char **argv, const char *prefix)

memset(&match_all, 0, sizeof(match_all));
repo_init_revisions(the_repository, &rev, prefix);
git_config(grep_config, &rev.grep_filter);

rev.diff = 1;
rev.always_show_header = 1;
rev.no_walk = 1;
@ -746,6 +748,8 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)

repo_init_revisions(the_repository, &rev, prefix);
init_reflog_walk(&rev.reflog_info);
git_config(grep_config, &rev.grep_filter);

rev.verbose_header = 1;
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
@ -779,6 +783,8 @@ int cmd_log(int argc, const char **argv, const char *prefix)
git_config(git_log_config, NULL);

repo_init_revisions(the_repository, &rev, prefix);
git_config(grep_config, &rev.grep_filter);

rev.always_show_header = 1;
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
@ -1861,10 +1867,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
extra_hdr.strdup_strings = 1;
extra_to.strdup_strings = 1;
extra_cc.strdup_strings = 1;

init_log_defaults();
init_display_notes(&notes_opt);
git_config(git_format_config, NULL);
repo_init_revisions(the_repository, &rev, prefix);
git_config(grep_config, &rev.grep_filter);

rev.show_notes = show_notes;
memcpy(&rev.notes_opt, &notes_opt, sizeof(notes_opt));
rev.commit_format = CMIT_FMT_EMAIL;
@ -1993,8 +2002,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (rev.show_notes)
load_display_notes(&rev.notes_opt);

if (use_stdout + rev.diffopt.close_file + !!output_directory > 1)
die(_("options '%s', '%s', and '%s' cannot be used together"), "--stdout", "--output", "--output-directory");
die_for_incompatible_opt3(use_stdout, "--stdout",
rev.diffopt.close_file, "--output",
!!output_directory, "--output-directory");

if (use_stdout) {
setup_pager();

View File

@ -155,6 +155,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)

ref_array_clear(&ref_array);
if (transport_disconnect(transport))
return 1;
status = 1;
transport_ls_refs_options_release(&transport_options);
return status;
}

View File

@ -150,7 +150,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)

git_config(git_default_config, NULL);
ls_tree_prefix = prefix;
if (prefix && *prefix)
if (prefix)
chomp_prefix = strlen(prefix);

argc = parse_options(argc, argv, prefix, ls_tree_options,

View File

@ -159,12 +159,14 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix)
if (argc < 2)
usage_with_options(merge_base_usage, options);
if (show_all)
die("--is-ancestor cannot be used with --all");
die(_("options '%s' and '%s' cannot be used together"),
"--is-ancestor", "--all");
return handle_is_ancestor(argc, argv);
}

if (cmdmode == 'r' && show_all)
die("--independent cannot be used with --all");
die(_("options '%s' and '%s' cannot be used together"),
"--independent", "--all");

if (cmdmode == 'o')
return handle_octopus(argc, argv, show_all);

View File

@ -1568,8 +1568,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)

if (autostash)
create_autostash(the_repository,
git_path_merge_autostash(the_repository),
"merge");
git_path_merge_autostash(the_repository));
if (checkout_fast_forward(the_repository,
&head_commit->object.oid,
&commit->object.oid,
@ -1640,8 +1639,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)

if (autostash)
create_autostash(the_repository,
git_path_merge_autostash(the_repository),
"merge");
git_path_merge_autostash(the_repository));

/* We are going to make a new commit. */
git_committer_info(IDENT_STRICT);

View File

@ -7,7 +7,7 @@
#include "config.h"

static char const * const builtin_mktag_usage[] = {
N_("git mktag"),
"git mktag",
NULL
};
static int option_strict = 1;

View File

@ -63,7 +63,7 @@ static void write_tree(struct object_id *oid)
}

static const char *mktree_usage[] = {
N_("git mktree [-z] [--missing] [--batch]"),
"git mktree [-z] [--missing] [--batch]",
NULL
};


View File

@ -473,7 +473,7 @@ static void show_name(const struct object *obj,
static char const * const name_rev_usage[] = {
N_("git name-rev [<options>] <commit>..."),
N_("git name-rev [<options>] --all"),
N_("git name-rev [<options>] --stdin"),
N_("git name-rev [<options>] --annotate-stdin"),
NULL
};


View File

@ -32,8 +32,8 @@ static const char * const git_notes_usage[] = {
N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
N_("git notes [--ref <notes-ref>] show [<object>]"),
N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
N_("git notes merge --commit [-v | -q]"),
N_("git notes merge --abort [-v | -q]"),
"git notes merge --commit [-v | -q]",
"git notes merge --abort [-v | -q]",
N_("git notes [--ref <notes-ref>] remove [<object>...]"),
N_("git notes [--ref <notes-ref>] prune [-n] [-v]"),
N_("git notes [--ref <notes-ref>] get-ref"),
@ -89,7 +89,7 @@ static const char * const git_notes_prune_usage[] = {
};

static const char * const git_notes_get_ref_usage[] = {
N_("git notes get-ref"),
"git notes get-ref",
NULL
};


View File

@ -3504,7 +3504,7 @@ static int option_parse_missing_action(const struct option *opt,
return 0;
}

die(_("invalid value for --missing"));
die(_("invalid value for '%s': '%s'"), "--missing", arg);
return 0;
}

@ -3976,9 +3976,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
read_replace_refs = 0;

sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1);
prepare_repo_settings(the_repository);
if (sparse < 0)
sparse = the_repository->settings.pack_use_sparse;
if (the_repository->gitdir) {
prepare_repo_settings(the_repository);
if (sparse < 0)
sparse = the_repository->settings.pack_use_sparse;
}

reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);

View File

@ -32,8 +32,12 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
n = strspn(q, digits);
if (q[n] == ',') {
q += n + 1;
*p_before = atoi(q);
n = strspn(q, digits);
} else {
*p_before = 1;
}

if (n == 0 || q[n] != ' ' || q[n+1] != '+')
return 0;

@ -41,13 +45,14 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
n = strspn(r, digits);
if (r[n] == ',') {
r += n + 1;
*p_after = atoi(r);
n = strspn(r, digits);
} else {
*p_after = 1;
}
if (n == 0)
return 0;

*p_before = atoi(q);
*p_after = atoi(r);
return 1;
}


View File

@ -3,7 +3,7 @@
#include "prune-packed.h"

static const char * const prune_packed_usage[] = {
N_("git prune-packed [-n | --dry-run] [-q | --quiet]"),
"git prune-packed [-n | --dry-run] [-q | --quiet]",
NULL
};


View File

@ -42,9 +42,9 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,
return v;

if (fatal)
die(_("Invalid value for %s: %s"), key, value);
die(_("invalid value for '%s': '%s'"), key, value);
else
error(_("Invalid value for %s: %s"), key, value);
error(_("invalid value for '%s': '%s'"), key, value);

return REBASE_INVALID;
}
@ -318,7 +318,7 @@ static const char *config_get_ff(void)
if (!strcmp(value, "only"))
return "--ff-only";

die(_("Invalid value for pull.ff: %s"), value);
die(_("invalid value for '%s': '%s'"), "pull.ff", value);
}

/**
@ -994,8 +994,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
set_reflog_message(argc, argv);

git_config(git_pull_config, NULL);
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
if (the_repository->gitdir) {
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
}

argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);


View File

@ -486,7 +486,7 @@ static int git_push_config(const char *k, const char *v, void *cb)
if (value && !strcasecmp(value, "if-asked"))
set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED);
else
return error("Invalid value for '%s'", k);
return error(_("invalid value for '%s'"), k);
}
}
} else if (!strcmp(k, "push.recursesubmodules")) {

View File

@ -37,7 +37,7 @@ static char const * const builtin_rebase_usage[] = {
"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
"--root [<branch>]"),
N_("git rebase --continue | --abort | --skip | --edit-todo"),
"git rebase --continue | --abort | --skip | --edit-todo",
NULL
};

@ -571,7 +571,8 @@ static int finish_rebase(struct rebase_options *opts)

static int move_to_original_branch(struct rebase_options *opts)
{
struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
struct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
struct reset_head_opts ropts = { 0 };
int ret;

if (!opts->head_name)
@ -580,16 +581,17 @@ static int move_to_original_branch(struct rebase_options *opts)
if (!opts->onto)
BUG("move_to_original_branch without onto");

strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s",
strbuf_addf(&branch_reflog, "rebase finished: %s onto %s",
opts->head_name, oid_to_hex(&opts->onto->object.oid));
strbuf_addf(&head_reflog, "rebase finished: returning to %s",
opts->head_name);
ret = reset_head(the_repository, NULL, "", opts->head_name,
RESET_HEAD_REFS_ONLY,
orig_head_reflog.buf, head_reflog.buf,
DEFAULT_REFLOG_ACTION);
ropts.branch = opts->head_name;
ropts.flags = RESET_HEAD_REFS_ONLY;
ropts.branch_msg = branch_reflog.buf;
ropts.head_msg = head_reflog.buf;
ret = reset_head(the_repository, &ropts);

strbuf_release(&orig_head_reflog);
strbuf_release(&branch_reflog);
strbuf_release(&head_reflog);
return ret;
}
@ -671,13 +673,15 @@ static int run_am(struct rebase_options *opts)

status = run_command(&format_patch);
if (status) {
struct reset_head_opts ropts = { 0 };
unlink(rebased_patches);
free(rebased_patches);
strvec_clear(&am.args);

reset_head(the_repository, &opts->orig_head, "checkout",
opts->head_name, 0,
"HEAD", NULL, DEFAULT_REFLOG_ACTION);
ropts.oid = &opts->orig_head;
ropts.branch = opts->head_name;
ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
reset_head(the_repository, &ropts);
error(_("\ngit encountered an error while preparing the "
"patches to replay\n"
"these revisions:\n"
@ -813,6 +817,26 @@ static int rebase_config(const char *var, const char *value, void *data)
return git_default_config(var, value, data);
}

static int checkout_up_to_date(struct rebase_options *options)
{
struct strbuf buf = STRBUF_INIT;
struct reset_head_opts ropts = { 0 };
int ret = 0;

strbuf_addf(&buf, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
options->switch_to);
ropts.oid = &options->orig_head;
ropts.branch = options->head_name;
ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
ropts.head_msg = buf.buf;
if (reset_head(the_repository, &ropts) < 0)
ret = error(_("could not switch to %s"), options->switch_to);
strbuf_release(&buf);

return ret;
}

/*
* Determines whether the commits in from..to are linear, i.e. contain
* no merge commits. This function *expects* `from` to be an ancestor of
@ -1018,6 +1042,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
int reschedule_failed_exec = -1;
int allow_preemptive_ff = 1;
int preserve_merges_selected = 0;
struct reset_head_opts ropts = { 0 };
struct option builtin_rebase_options[] = {
OPT_STRING(0, "onto", &options.onto_name,
N_("revision"),
@ -1255,9 +1280,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)

rerere_clear(the_repository, &merge_rr);
string_list_clear(&merge_rr, 1);

if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD,
NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
ropts.flags = RESET_HEAD_HARD;
if (reset_head(the_repository, &ropts) < 0)
die(_("could not discard worktree changes"));
remove_branch_state(the_repository, 0);
if (read_basic_state(&options))
@ -1274,9 +1298,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)

if (read_basic_state(&options))
exit(1);
if (reset_head(the_repository, &options.orig_head, "reset",
options.head_name, RESET_HEAD_HARD,
NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
ropts.oid = &options.orig_head;
ropts.branch = options.head_name;
ropts.flags = RESET_HEAD_HARD;
ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
if (reset_head(the_repository, &ropts) < 0)
die(_("could not move back to %s"),
oid_to_hex(&options.orig_head));
remove_branch_state(the_repository, 0);
@ -1642,10 +1668,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (repo_read_index(the_repository) < 0)
die(_("could not read index"));

if (options.autostash) {
create_autostash(the_repository, state_dir_path("autostash", &options),
DEFAULT_REFLOG_ACTION);
}
if (options.autostash)
create_autostash(the_repository,
state_dir_path("autostash", &options));


if (require_clean_work_tree(the_repository, "rebase",
_("Please commit or stash them."), 1, 1)) {
@ -1674,21 +1700,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (!(options.flags & REBASE_FORCE)) {
/* Lazily switch to the target branch if needed... */
if (options.switch_to) {
strbuf_reset(&buf);
strbuf_addf(&buf, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
options.switch_to);
if (reset_head(the_repository,
&options.orig_head, "checkout",
options.head_name,
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
NULL, buf.buf,
DEFAULT_REFLOG_ACTION) < 0) {
ret = error(_("could not switch to "
"%s"),
options.switch_to);
ret = checkout_up_to_date(&options);
if (ret)
goto cleanup;
}
}

if (!(options.flags & REBASE_NO_QUIET))
@ -1755,10 +1769,13 @@ 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(the_repository, &options.onto->object.oid, "checkout", NULL,
RESET_HEAD_DETACH | RESET_ORIG_HEAD |
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
NULL, msg.buf, DEFAULT_REFLOG_ACTION))
ropts.oid = &options.onto->object.oid;
ropts.orig_head = &options.orig_head,
ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
ropts.head_msg = msg.buf;
ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
if (reset_head(the_repository, &ropts))
die(_("Could not detach HEAD"));
strbuf_release(&msg);

@ -1773,9 +1790,11 @@ 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(the_repository, NULL, "Fast-forwarded", options.head_name,
RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
DEFAULT_REFLOG_ACTION);
memset(&ropts, 0, sizeof(ropts));
ropts.branch = options.head_name;
ropts.flags = RESET_HEAD_REFS_ONLY;
ropts.head_msg = msg.buf;
reset_head(the_repository, &ropts);
strbuf_release(&msg);
ret = finish_rebase(&options);
goto cleanup;

View File

@ -1961,6 +1961,15 @@ static void execute_commands(struct command *commands,
return;
}

/*
* If there is no command ready to run, should return directly to destroy
* temporary data in the quarantine area.
*/
for (cmd = commands; cmd && cmd->error_string; cmd = cmd->next)
; /* nothing */
if (!cmd)
return;

/*
* Now we'll start writing out refs, which means the objects need
* to be in their final positions so that other processes can see them.

View File

@ -818,7 +818,7 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
*/

static const char reflog_usage[] =
N_("git reflog [ show | expire | delete | exists ]");
"git reflog [ show | expire | delete | exists ]";

int cmd_reflog(int argc, const char **argv, const char *prefix)
{

View File

@ -14,7 +14,7 @@
#include "commit-reach.h"

static const char * const builtin_remote_usage[] = {
N_("git remote [-v | --verbose]"),
"git remote [-v | --verbose]",
N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags | --no-tags] [--mirror=<fetch|push>] <name> <url>"),
N_("git remote rename <old> <new>"),
N_("git remote remove <name>"),

View File

@ -22,7 +22,7 @@ static const char * const git_replace_usage[] = {
N_("git replace [-f] <object> <replacement>"),
N_("git replace [-f] --edit <object>"),
N_("git replace [-f] --graft <commit> [<parent>...]"),
N_("git replace [-f] --convert-graft-file"),
"git replace [-f] --convert-graft-file",
N_("git replace -d <object>..."),
N_("git replace [--format=<format>] [-l [<pattern>]]"),
NULL

View File

@ -204,10 +204,16 @@ static int pathspec_needs_expanded_index(const struct pathspec *pathspec)
/*
* Special case: if the pattern is a path inside the cone
* followed by only wildcards, the pattern cannot match
* partial sparse directories, so we don't expand the index.
* partial sparse directories, so we know we don't need to
* expand the index.
*
* Examples:
* - in-cone/foo***: doesn't need expanded index
* - not-in-cone/bar*: may need expanded index
* - **.c: may need expanded index
*/
if (path_in_cone_mode_sparse_checkout(item.original, &the_index) &&
strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len)
if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len &&
path_in_cone_mode_sparse_checkout(item.original, &the_index))
continue;

for (pos = 0; pos < active_nr; pos++) {
@ -274,7 +280,6 @@ static int read_from_tree(const struct pathspec *pathspec,
return 1;
diffcore_std(&opt);
diff_flush(&opt);
clear_pathspec(&opt.pathspec);

return 0;
}

View File

@ -20,7 +20,7 @@
#include "packfile.h"

static const char rev_list_usage[] =
"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
"git rev-list [<options>] <commit-id>... [-- <path>...]\n"
" limiting output:\n"
" --max-count=<n>\n"
" --max-age=<epoch>\n"

View File

@ -145,7 +145,7 @@ static int send_pack_config(const char *k, const char *v, void *cb)
if (value && !strcasecmp(value, "if-asked"))
args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED;
else
return error("Invalid value for '%s'", k);
return error(_("invalid value for '%s'"), k);
}
}
}

View File

@ -388,6 +388,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
parse_revision_opt(&rev, &ctx, options, shortlog_usage);
}
parse_done:
revision_opts_finish(&rev);
argc = parse_options_end(&ctx);

if (nongit && argc > 1) {

View File

@ -8,6 +8,7 @@
#include "parse-options.h"
#include "dir.h"
#include "commit-slab.h"
#include "date.h"

static const char* show_branch_usage[] = {
N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n"

View File

@ -15,6 +15,7 @@
#include "wt-status.h"
#include "quote.h"
#include "sparse-index.h"
#include "worktree.h"

static const char *empty_base = "";

@ -43,7 +44,7 @@ static void write_patterns_to_file(FILE *fp, struct pattern_list *pl)
}

static char const * const builtin_sparse_checkout_list_usage[] = {
N_("git sparse-checkout list"),
"git sparse-checkout list",
NULL
};

@ -361,26 +362,23 @@ enum sparse_checkout_mode {

static int set_config(enum sparse_checkout_mode mode)
{
const char *config_path;

if (upgrade_repository_format(1) < 0)
die(_("unable to upgrade repository format to enable worktreeConfig"));
if (git_config_set_gently("extensions.worktreeConfig", "true")) {
error(_("failed to set extensions.worktreeConfig setting"));
/* Update to use worktree config, if not already. */
if (init_worktree_config(the_repository)) {
error(_("failed to initialize worktree config"));
return 1;
}

config_path = git_path("config.worktree");
git_config_set_in_file_gently(config_path,
"core.sparseCheckout",
mode ? "true" : NULL);

git_config_set_in_file_gently(config_path,
"core.sparseCheckoutCone",
mode == MODE_CONE_PATTERNS ? "true" : NULL);
if (repo_config_set_worktree_gently(the_repository,
"core.sparseCheckout",
mode ? "true" : "false") ||
repo_config_set_worktree_gently(the_repository,
"core.sparseCheckoutCone",
mode == MODE_CONE_PATTERNS ?
"true" : "false"))
return 1;

if (mode == MODE_NO_PATTERNS)
set_sparse_index_config(the_repository, 0);
return set_sparse_index_config(the_repository, 0);

return 0;
}
@ -421,7 +419,7 @@ static int update_modes(int *cone_mode, int *sparse_index)
}

static char const * const builtin_sparse_checkout_init_usage[] = {
N_("git sparse-checkout init [--cone] [--[no-]sparse-index]"),
"git sparse-checkout init [--cone] [--[no-]sparse-index]",
NULL
};

@ -767,7 +765,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
}

static char const * const builtin_sparse_checkout_reapply_usage[] = {
N_("git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]"),
"git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]",
NULL
};

@ -805,7 +803,7 @@ static int sparse_checkout_reapply(int argc, const char **argv)
}

static char const * const builtin_sparse_checkout_disable_usage[] = {
N_("git sparse-checkout disable"),
"git sparse-checkout disable",
NULL
};


View File

@ -15,8 +15,8 @@ static void comment_lines(struct strbuf *buf)
}

static const char * const stripspace_usage[] = {
N_("git stripspace [-s | --strip-comments]"),
N_("git stripspace [-c | --comment-lines]"),
"git stripspace [-s | --strip-comments]",
"git stripspace [-c | --comment-lines]",
NULL
};


View File

@ -20,6 +20,8 @@
#include "diff.h"
#include "object-store.h"
#include "advice.h"
#include "branch.h"
#include "list-objects-filter-options.h"

#define OPT_QUIET (1 << 0)
#define OPT_CACHED (1 << 1)
@ -1630,6 +1632,7 @@ struct module_clone_data {
const char *name;
const char *url;
const char *depth;
struct list_objects_filter_options *filter_options;
struct string_list reference;
unsigned int quiet: 1;
unsigned int progress: 1;
@ -1796,6 +1799,10 @@ static int clone_submodule(struct module_clone_data *clone_data)
strvec_push(&cp.args, "--dissociate");
if (sm_gitdir && *sm_gitdir)
strvec_pushl(&cp.args, "--separate-git-dir", sm_gitdir, NULL);
if (clone_data->filter_options && clone_data->filter_options->choice)
strvec_pushf(&cp.args, "--filter=%s",
expand_list_objects_filter_spec(
clone_data->filter_options));
if (clone_data->single_branch >= 0)
strvec_push(&cp.args, clone_data->single_branch ?
"--single-branch" :
@ -1852,6 +1859,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
{
int dissociate = 0, quiet = 0, progress = 0, require_init = 0;
struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
struct list_objects_filter_options filter_options;

struct option module_clone_options[] = {
OPT_STRING(0, "prefix", &clone_data.prefix,
@ -1881,17 +1889,19 @@ static int module_clone(int argc, const char **argv, const char *prefix)
N_("disallow cloning into non-empty directory")),
OPT_BOOL(0, "single-branch", &clone_data.single_branch,
N_("clone only one branch, HEAD or --branch")),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_END()
};

const char *const git_submodule_helper_usage[] = {
N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
"[--reference <repository>] [--name <name>] [--depth <depth>] "
"[--single-branch] "
"[--single-branch] [--filter <filter-spec>]"
"--url <url> --path <path>"),
NULL
};

memset(&filter_options, 0, sizeof(filter_options));
argc = parse_options(argc, argv, prefix, module_clone_options,
git_submodule_helper_usage, 0);

@ -1899,12 +1909,14 @@ static int module_clone(int argc, const char **argv, const char *prefix)
clone_data.quiet = !!quiet;
clone_data.progress = !!progress;
clone_data.require_init = !!require_init;
clone_data.filter_options = &filter_options;

if (argc || !clone_data.url || !clone_data.path || !*(clone_data.path))
usage_with_options(git_submodule_helper_usage,
module_clone_options);

clone_submodule(&clone_data);
list_objects_filter_release(&filter_options);
return 0;
}

@ -1994,6 +2006,7 @@ struct submodule_update_clone {
const char *recursive_prefix;
const char *prefix;
int single_branch;
struct list_objects_filter_options *filter_options;

/* to be consumed by git-submodule.sh */
struct update_clone_data *update_clone;
@ -2154,6 +2167,9 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
strvec_pushl(&child->args, "--prefix", suc->prefix, NULL);
if (suc->recommend_shallow && sub->recommend_shallow == 1)
strvec_push(&child->args, "--depth=1");
if (suc->filter_options && suc->filter_options->choice)
strvec_pushf(&child->args, "--filter=%s",
expand_list_objects_filter_spec(suc->filter_options));
if (suc->require_init)
strvec_push(&child->args, "--require-init");
strvec_pushl(&child->args, "--path", sub->path, NULL);
@ -2498,6 +2514,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
const char *update = NULL;
struct pathspec pathspec;
struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
struct list_objects_filter_options filter_options;
int ret;

struct option module_update_clone_options[] = {
OPT_STRING(0, "prefix", &prefix,
@ -2528,6 +2546,7 @@ static int update_clone(int argc, const char **argv, const char *prefix)
N_("disallow cloning into non-empty directory")),
OPT_BOOL(0, "single-branch", &suc.single_branch,
N_("clone only one branch, HEAD or --branch")),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_END()
};

@ -2540,20 +2559,26 @@ static int update_clone(int argc, const char **argv, const char *prefix)
update_clone_config_from_gitmodules(&suc.max_jobs);
git_config(git_update_clone_config, &suc.max_jobs);

memset(&filter_options, 0, sizeof(filter_options));
argc = parse_options(argc, argv, prefix, module_update_clone_options,
git_submodule_helper_usage, 0);
suc.filter_options = &filter_options;

if (update)
if (parse_submodule_update_strategy(update, &suc.update) < 0)
die(_("bad value for update parameter"));

if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0) {
list_objects_filter_release(&filter_options);
return 1;
}

if (pathspec.nr)
suc.warn_if_uninitialized = 1;

return update_submodules(&suc);
ret = update_submodules(&suc);
list_objects_filter_release(&filter_options);
return ret;
}

static int run_update_procedure(int argc, const char **argv, const char *prefix)
@ -2883,7 +2908,7 @@ static int module_config(int argc, const char **argv, const char *prefix)
const char *const git_submodule_helper_usage[] = {
N_("git submodule--helper config <name> [<value>]"),
N_("git submodule--helper config --unset <name>"),
N_("git submodule--helper config --check-writeable"),
"git submodule--helper config --check-writeable",
NULL
};

@ -2984,6 +3009,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
return !!ret;
}

static int module_create_branch(int argc, const char **argv, const char *prefix)
{
enum branch_track track;
int quiet = 0, force = 0, reflog = 0, dry_run = 0;

struct option options[] = {
OPT__QUIET(&quiet, N_("print only error messages")),
OPT__FORCE(&force, N_("force creation"), 0),
OPT_BOOL(0, "create-reflog", &reflog,
N_("create the branch's reflog")),
OPT_SET_INT('t', "track", &track,
N_("set up tracking mode (see git-pull(1))"),
BRANCH_TRACK_EXPLICIT),
OPT__DRY_RUN(&dry_run,
N_("show whether the branch would be created")),
OPT_END()
};
const char *const usage[] = {
N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>"),
NULL
};

git_config(git_default_config, NULL);
track = git_branch_track;
argc = parse_options(argc, argv, prefix, options, usage, 0);

if (argc != 3)
usage_with_options(usage, options);

if (!quiet && !dry_run)
printf_ln(_("creating branch '%s'"), argv[0]);

create_branches_recursively(the_repository, argv[0], argv[1], argv[2],
force, reflog, quiet, track, dry_run);
return 0;
}
struct add_data {
const char *prefix;
const char *branch;
@ -3390,6 +3451,7 @@ static struct cmd_struct commands[] = {
{"config", module_config, 0},
{"set-url", module_set_url, 0},
{"set-branch", module_set_branch, 0},
{"create-branch", module_create_branch, 0},
};

int cmd_submodule__helper(int argc, const char **argv, const char *prefix)

View File

@ -20,6 +20,7 @@
#include "oid-array.h"
#include "column.h"
#include "ref-filter.h"
#include "date.h"

static const char * const git_tag_usage[] = {
N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n"

View File

@ -606,7 +606,7 @@ static struct cache_entry *read_one_ent(const char *which,
error("%s: not in %s branch.", path, which);
return NULL;
}
if (mode == S_IFDIR) {
if (!the_index.sparse_index && mode == S_IFDIR) {
if (which)
error("%s: not a blob in %s branch.", path, which);
return NULL;
@ -743,8 +743,6 @@ static int do_reupdate(int ac, const char **av,
*/
has_head = 0;
redo:
/* TODO: audit for interaction with sparse-index. */
ensure_full_index(&the_index);
for (pos = 0; pos < active_nr; pos++) {
const struct cache_entry *ce = active_cache[pos];
struct cache_entry *old = NULL;
@ -761,6 +759,16 @@ static int do_reupdate(int ac, const char **av,
discard_cache_entry(old);
continue; /* unchanged */
}

/* At this point, we know the contents of the sparse directory are
* modified with respect to HEAD, so we expand the index and restart
* to process each path individually
*/
if (S_ISSPARSEDIR(ce->ce_mode)) {
ensure_full_index(&the_index);
goto redo;
}

/* Be careful. The working tree may not have the
* path anymore, in which case, under 'allow_remove',
* or worse yet 'allow_replace', active_nr may decrease.
@ -1088,6 +1096,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)

git_config(git_default_config, NULL);

prepare_repo_settings(r);
the_repository->settings.command_requires_full_index = 0;

/* we will diagnose later if it turns out that we need to update it */
newfd = hold_locked_index(&lock_file, 0);
if (newfd < 0)
@ -1225,14 +1236,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
}

if (fsmonitor > 0) {
if (git_config_get_fsmonitor() == 0)
enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);
if (fsm_mode == FSMONITOR_MODE_DISABLED) {
warning(_("core.fsmonitor is unset; "
"set it if you really want to "
"enable fsmonitor"));
}
add_fsmonitor(&the_index);
report(_("fsmonitor enabled"));
} else if (!fsmonitor) {
if (git_config_get_fsmonitor() == 1)
enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);
if (fsm_mode > FSMONITOR_MODE_DISABLED)
warning(_("core.fsmonitor is set; "
"remove it if you really want to "
"disable fsmonitor"));

View File

@ -4,7 +4,7 @@
#include "parse-options.h"

static const char * const update_server_info_usage[] = {
N_("git update-server-info [--force]"),
"git update-server-info [--force]",
NULL
};


View File

@ -335,6 +335,69 @@ static int add_worktree(const char *path, const char *refname,
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../..");

/*
* If the current worktree has sparse-checkout enabled, then copy
* the sparse-checkout patterns from the current worktree.
*/
if (core_apply_sparse_checkout) {
char *from_file = git_pathdup("info/sparse-checkout");
char *to_file = xstrfmt("%s/info/sparse-checkout",
sb_repo.buf);

if (file_exists(from_file)) {
if (safe_create_leading_directories(to_file) ||
copy_file(to_file, from_file, 0666))
error(_("failed to copy '%s' to '%s'; sparse-checkout may not work correctly"),
from_file, to_file);
}

free(from_file);
free(to_file);
}

/*
* If we are using worktree config, then copy all current config
* values from the current worktree into the new one, that way the
* new worktree behaves the same as this one.
*/
if (repository_format_worktree_config) {
char *from_file = git_pathdup("config.worktree");
char *to_file = xstrfmt("%s/config.worktree",
sb_repo.buf);

if (file_exists(from_file)) {
struct config_set cs = { { 0 } };
const char *core_worktree;
int bare;

if (safe_create_leading_directories(to_file) ||
copy_file(to_file, from_file, 0666)) {
error(_("failed to copy worktree config from '%s' to '%s'"),
from_file, to_file);
goto worktree_copy_cleanup;
}

git_configset_init(&cs);
git_configset_add_file(&cs, from_file);

if (!git_configset_get_bool(&cs, "core.bare", &bare) &&
bare &&
git_config_set_multivar_in_file_gently(
to_file, "core.bare", NULL, "true", 0))
error(_("failed to unset 'core.bare' in '%s'"), to_file);
if (!git_configset_get_value(&cs, "core.worktree", &core_worktree) &&
git_config_set_in_file_gently(to_file,
"core.worktree", NULL))
error(_("failed to unset 'core.worktree' in '%s'"), to_file);

git_configset_clear(&cs);
}

worktree_copy_cleanup:
free(from_file);
free(to_file);
}

strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
cp.git_cmd = 1;

51
cache.h
View File

@ -999,7 +999,6 @@ extern int core_preload_index;
extern int precomposed_unicode;
extern int protect_hfs;
extern int protect_ntfs;
extern const char *core_fsmonitor;

extern int core_apply_sparse_checkout;
extern int core_sparse_checkout_cone;
@ -1558,48 +1557,6 @@ struct object *repo_peel_to_type(struct repository *r,
#define peel_to_type(name, namelen, obj, type) \
repo_peel_to_type(the_repository, name, namelen, obj, type)

enum date_mode_type {
DATE_NORMAL = 0,
DATE_HUMAN,
DATE_RELATIVE,
DATE_SHORT,
DATE_ISO8601,
DATE_ISO8601_STRICT,
DATE_RFC2822,
DATE_STRFTIME,
DATE_RAW,
DATE_UNIX
};

struct date_mode {
enum date_mode_type type;
const char *strftime_fmt;
int local;
};

/*
* Convenience helper for passing a constant type, like:
*
* show_date(t, tz, DATE_MODE(NORMAL));
*/
#define DATE_MODE(t) date_mode_from_type(DATE_##t)
struct date_mode *date_mode_from_type(enum date_mode_type type);

const char *show_date(timestamp_t time, int timezone, const struct date_mode *mode);
void show_date_relative(timestamp_t time, struct strbuf *timebuf);
void show_date_human(timestamp_t time, int tz, const struct timeval *now,
struct strbuf *timebuf);
int parse_date(const char *date, struct strbuf *out);
int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset);
int parse_expiry_date(const char *date, timestamp_t *timestamp);
void datestamp(struct strbuf *out);
#define approxidate(s) approxidate_careful((s), NULL)
timestamp_t approxidate_careful(const char *, int *);
timestamp_t approxidate_relative(const char *date);
void parse_date_format(const char *format, struct date_mode *mode);
int date_overflows(timestamp_t date);
time_t tm_to_time_t(const struct tm *tm);

#define IDENT_STRICT 1
#define IDENT_NO_DATE 2
#define IDENT_NO_NAME 4
@ -1645,14 +1602,6 @@ struct ident_split {
*/
int split_ident_line(struct ident_split *, const char *, int);

/*
* Like show_date, but pull the timestamp and tz parameters from
* the ident_split. It will also sanity-check the values and produce
* a well-known sentinel date if they appear bogus.
*/
const char *show_ident_date(const struct ident_split *id,
const struct date_mode *mode);

/*
* Compare split idents for equality or strict ordering. Note that we
* compare only the ident part of the line, ignoring any timestamp.

View File

@ -0,0 +1,92 @@
#ifndef FSM_DARWIN_GCC_H
#define FSM_DARWIN_GCC_H

#ifndef __clang__
/*
* It is possible to #include CoreFoundation/CoreFoundation.h when compiling
* with clang, but not with GCC as of time of writing.
*
* See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93082 for details.
*/
typedef unsigned int FSEventStreamCreateFlags;
#define kFSEventStreamEventFlagNone 0x00000000
#define kFSEventStreamEventFlagMustScanSubDirs 0x00000001
#define kFSEventStreamEventFlagUserDropped 0x00000002
#define kFSEventStreamEventFlagKernelDropped 0x00000004
#define kFSEventStreamEventFlagEventIdsWrapped 0x00000008
#define kFSEventStreamEventFlagHistoryDone 0x00000010
#define kFSEventStreamEventFlagRootChanged 0x00000020
#define kFSEventStreamEventFlagMount 0x00000040
#define kFSEventStreamEventFlagUnmount 0x00000080
#define kFSEventStreamEventFlagItemCreated 0x00000100
#define kFSEventStreamEventFlagItemRemoved 0x00000200
#define kFSEventStreamEventFlagItemInodeMetaMod 0x00000400
#define kFSEventStreamEventFlagItemRenamed 0x00000800
#define kFSEventStreamEventFlagItemModified 0x00001000
#define kFSEventStreamEventFlagItemFinderInfoMod 0x00002000
#define kFSEventStreamEventFlagItemChangeOwner 0x00004000
#define kFSEventStreamEventFlagItemXattrMod 0x00008000
#define kFSEventStreamEventFlagItemIsFile 0x00010000
#define kFSEventStreamEventFlagItemIsDir 0x00020000
#define kFSEventStreamEventFlagItemIsSymlink 0x00040000
#define kFSEventStreamEventFlagOwnEvent 0x00080000
#define kFSEventStreamEventFlagItemIsHardlink 0x00100000
#define kFSEventStreamEventFlagItemIsLastHardlink 0x00200000
#define kFSEventStreamEventFlagItemCloned 0x00400000

typedef struct __FSEventStream *FSEventStreamRef;
typedef const FSEventStreamRef ConstFSEventStreamRef;

typedef unsigned int CFStringEncoding;
#define kCFStringEncodingUTF8 0x08000100

typedef const struct __CFString *CFStringRef;
typedef const struct __CFArray *CFArrayRef;
typedef const struct __CFRunLoop *CFRunLoopRef;

struct FSEventStreamContext {
long long version;
void *cb_data, *retain, *release, *copy_description;
};

typedef struct FSEventStreamContext FSEventStreamContext;
typedef unsigned int FSEventStreamEventFlags;
#define kFSEventStreamCreateFlagNoDefer 0x02
#define kFSEventStreamCreateFlagWatchRoot 0x04
#define kFSEventStreamCreateFlagFileEvents 0x10

typedef unsigned long long FSEventStreamEventId;
#define kFSEventStreamEventIdSinceNow 0xFFFFFFFFFFFFFFFFULL

typedef void (*FSEventStreamCallback)(ConstFSEventStreamRef streamRef,
void *context,
__SIZE_TYPE__ num_of_events,
void *event_paths,
const FSEventStreamEventFlags event_flags[],
const FSEventStreamEventId event_ids[]);
typedef double CFTimeInterval;
FSEventStreamRef FSEventStreamCreate(void *allocator,
FSEventStreamCallback callback,
FSEventStreamContext *context,
CFArrayRef paths_to_watch,
FSEventStreamEventId since_when,
CFTimeInterval latency,
FSEventStreamCreateFlags flags);
CFStringRef CFStringCreateWithCString(void *allocator, const char *string,
CFStringEncoding encoding);
CFArrayRef CFArrayCreate(void *allocator, const void **items, long long count,
void *callbacks);
void CFRunLoopRun(void);
void CFRunLoopStop(CFRunLoopRef run_loop);
CFRunLoopRef CFRunLoopGetCurrent(void);
extern CFStringRef kCFRunLoopDefaultMode;
void FSEventStreamScheduleWithRunLoop(FSEventStreamRef stream,
CFRunLoopRef run_loop,
CFStringRef run_loop_mode);
unsigned char FSEventStreamStart(FSEventStreamRef stream);
void FSEventStreamStop(FSEventStreamRef stream);
void FSEventStreamInvalidate(FSEventStreamRef stream);
void FSEventStreamRelease(FSEventStreamRef stream);

#endif /* !clang */
#endif /* FSM_DARWIN_GCC_H */

View File

@ -0,0 +1,427 @@
#ifndef __clang__
#include "fsm-darwin-gcc.h"
#else
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>

#ifndef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER
/*
* This enum value was added in 10.13 to:
*
* /Applications/Xcode.app/Contents/Developer/Platforms/ \
* MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/ \
* Library/Frameworks/CoreServices.framework/Frameworks/ \
* FSEvents.framework/Versions/Current/Headers/FSEvents.h
*
* If we're compiling against an older SDK, this symbol won't be
* present. Silently define it here so that we don't have to ifdef
* the logging or masking below. This should be harmless since older
* versions of macOS won't ever emit this FS event anyway.
*/
#define kFSEventStreamEventFlagItemCloned 0x00400000
#endif
#endif

#include "cache.h"
#include "fsmonitor.h"
#include "fsm-listen.h"
#include "fsmonitor--daemon.h"

struct fsmonitor_daemon_backend_data
{
CFStringRef cfsr_worktree_path;
CFStringRef cfsr_gitdir_path;

CFArrayRef cfar_paths_to_watch;
int nr_paths_watching;

FSEventStreamRef stream;

CFRunLoopRef rl;

enum shutdown_style {
SHUTDOWN_EVENT = 0,
FORCE_SHUTDOWN,
FORCE_ERROR_STOP,
} shutdown_style;

unsigned int stream_scheduled:1;
unsigned int stream_started:1;
};

static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)
{
struct strbuf msg = STRBUF_INIT;

if (flag & kFSEventStreamEventFlagMustScanSubDirs)
strbuf_addstr(&msg, "MustScanSubDirs|");
if (flag & kFSEventStreamEventFlagUserDropped)
strbuf_addstr(&msg, "UserDropped|");
if (flag & kFSEventStreamEventFlagKernelDropped)
strbuf_addstr(&msg, "KernelDropped|");
if (flag & kFSEventStreamEventFlagEventIdsWrapped)
strbuf_addstr(&msg, "EventIdsWrapped|");
if (flag & kFSEventStreamEventFlagHistoryDone)
strbuf_addstr(&msg, "HistoryDone|");
if (flag & kFSEventStreamEventFlagRootChanged)
strbuf_addstr(&msg, "RootChanged|");
if (flag & kFSEventStreamEventFlagMount)
strbuf_addstr(&msg, "Mount|");
if (flag & kFSEventStreamEventFlagUnmount)
strbuf_addstr(&msg, "Unmount|");
if (flag & kFSEventStreamEventFlagItemChangeOwner)
strbuf_addstr(&msg, "ItemChangeOwner|");
if (flag & kFSEventStreamEventFlagItemCreated)
strbuf_addstr(&msg, "ItemCreated|");
if (flag & kFSEventStreamEventFlagItemFinderInfoMod)
strbuf_addstr(&msg, "ItemFinderInfoMod|");
if (flag & kFSEventStreamEventFlagItemInodeMetaMod)
strbuf_addstr(&msg, "ItemInodeMetaMod|");
if (flag & kFSEventStreamEventFlagItemIsDir)
strbuf_addstr(&msg, "ItemIsDir|");
if (flag & kFSEventStreamEventFlagItemIsFile)
strbuf_addstr(&msg, "ItemIsFile|");
if (flag & kFSEventStreamEventFlagItemIsHardlink)
strbuf_addstr(&msg, "ItemIsHardlink|");
if (flag & kFSEventStreamEventFlagItemIsLastHardlink)
strbuf_addstr(&msg, "ItemIsLastHardlink|");
if (flag & kFSEventStreamEventFlagItemIsSymlink)
strbuf_addstr(&msg, "ItemIsSymlink|");
if (flag & kFSEventStreamEventFlagItemModified)
strbuf_addstr(&msg, "ItemModified|");
if (flag & kFSEventStreamEventFlagItemRemoved)
strbuf_addstr(&msg, "ItemRemoved|");
if (flag & kFSEventStreamEventFlagItemRenamed)
strbuf_addstr(&msg, "ItemRenamed|");
if (flag & kFSEventStreamEventFlagItemXattrMod)
strbuf_addstr(&msg, "ItemXattrMod|");
if (flag & kFSEventStreamEventFlagOwnEvent)
strbuf_addstr(&msg, "OwnEvent|");
if (flag & kFSEventStreamEventFlagItemCloned)
strbuf_addstr(&msg, "ItemCloned|");

trace_printf_key(&trace_fsmonitor, "fsevent: '%s', flags=%u %s",
path, flag, msg.buf);

strbuf_release(&msg);
}

static int ef_is_root_delete(const FSEventStreamEventFlags ef)
{
return (ef & kFSEventStreamEventFlagItemIsDir &&
ef & kFSEventStreamEventFlagItemRemoved);
}

static int ef_is_root_renamed(const FSEventStreamEventFlags ef)
{
return (ef & kFSEventStreamEventFlagItemIsDir &&
ef & kFSEventStreamEventFlagItemRenamed);
}

static int ef_is_dropped(const FSEventStreamEventFlags ef)
{
return (ef & kFSEventStreamEventFlagMustScanSubDirs ||
ef & kFSEventStreamEventFlagKernelDropped ||
ef & kFSEventStreamEventFlagUserDropped);
}

static void fsevent_callback(ConstFSEventStreamRef streamRef,
void *ctx,
size_t num_of_events,
void *event_paths,
const FSEventStreamEventFlags event_flags[],
const FSEventStreamEventId event_ids[])
{
struct fsmonitor_daemon_state *state = ctx;
struct fsmonitor_daemon_backend_data *data = state->backend_data;
char **paths = (char **)event_paths;
struct fsmonitor_batch *batch = NULL;
struct string_list cookie_list = STRING_LIST_INIT_DUP;
const char *path_k;
const char *slash;
int k;
struct strbuf tmp = STRBUF_INIT;

/*
* Build a list of all filesystem changes into a private/local
* list and without holding any locks.
*/
for (k = 0; k < num_of_events; k++) {
/*
* On Mac, we receive an array of absolute paths.
*/
path_k = paths[k];

/*
* If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.
* Please don't log them to Trace2.
*
* trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k);
*/

/*
* If event[k] is marked as dropped, we assume that we have
* lost sync with the filesystem and should flush our cached
* data. We need to:
*
* [1] Abort/wake any client threads waiting for a cookie and
* flush the cached state data (the current token), and
* create a new token.
*
* [2] Discard the batch that we were locally building (since
* they are conceptually relative to the just flushed
* token).
*/
if (ef_is_dropped(event_flags[k])) {
if (trace_pass_fl(&trace_fsmonitor))
log_flags_set(path_k, event_flags[k]);

fsmonitor_force_resync(state);
fsmonitor_batch__free_list(batch);
string_list_clear(&cookie_list, 0);

/*
* We assume that any events that we received
* in this callback after this dropped event
* may still be valid, so we continue rather
* than break. (And just in case there is a
* delete of ".git" hiding in there.)
*/
continue;
}

switch (fsmonitor_classify_path_absolute(state, path_k)) {

case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
/* special case cookie files within .git or gitdir */

/* Use just the filename of the cookie file. */
slash = find_last_dir_sep(path_k);
string_list_append(&cookie_list,
slash ? slash + 1 : path_k);
break;

case IS_INSIDE_DOT_GIT:
case IS_INSIDE_GITDIR:
/* ignore all other paths inside of .git or gitdir */
break;

case IS_DOT_GIT:
case IS_GITDIR:
/*
* If .git directory is deleted or renamed away,
* we have to quit.
*/
if (ef_is_root_delete(event_flags[k])) {
trace_printf_key(&trace_fsmonitor,
"event: gitdir removed");
goto force_shutdown;
}
if (ef_is_root_renamed(event_flags[k])) {
trace_printf_key(&trace_fsmonitor,
"event: gitdir renamed");
goto force_shutdown;
}
break;

case IS_WORKDIR_PATH:
/* try to queue normal pathnames */

if (trace_pass_fl(&trace_fsmonitor))
log_flags_set(path_k, event_flags[k]);

/*
* Because of the implicit "binning" (the
* kernel calls us at a given frequency) and
* de-duping (the kernel is free to combine
* multiple events for a given pathname), an
* individual fsevent could be marked as both
* a file and directory. Add it to the queue
* with both spellings so that the client will
* know how much to invalidate/refresh.
*/

if (event_flags[k] & kFSEventStreamEventFlagItemIsFile) {
const char *rel = path_k +
state->path_worktree_watch.len + 1;

if (!batch)
batch = fsmonitor_batch__new();
fsmonitor_batch__add_path(batch, rel);
}

if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) {
const char *rel = path_k +
state->path_worktree_watch.len + 1;

strbuf_reset(&tmp);
strbuf_addstr(&tmp, rel);
strbuf_addch(&tmp, '/');

if (!batch)
batch = fsmonitor_batch__new();
fsmonitor_batch__add_path(batch, tmp.buf);
}

break;

case IS_OUTSIDE_CONE:
default:
trace_printf_key(&trace_fsmonitor,
"ignoring '%s'", path_k);
break;
}
}

fsmonitor_publish(state, batch, &cookie_list);
string_list_clear(&cookie_list, 0);
strbuf_release(&tmp);
return;

force_shutdown:
fsmonitor_batch__free_list(batch);
string_list_clear(&cookie_list, 0);

data->shutdown_style = FORCE_SHUTDOWN;
CFRunLoopStop(data->rl);
strbuf_release(&tmp);
return;
}

/*
* In the call to `FSEventStreamCreate()` to setup our watch, the
* `latency` argument determines the frequency of calls to our callback
* with new FS events. Too slow and events get dropped; too fast and
* we burn CPU unnecessarily. Since it is rather obscure, I don't
* think this needs to be a config setting. I've done extensive
* testing on my systems and chosen the value below. It gives good
* results and I've not seen any dropped events.
*
* With a latency of 0.1, I was seeing lots of dropped events during
* the "touch 100000" files test within t/perf/p7519, but with a
* latency of 0.001 I did not see any dropped events. So I'm going
* to assume that this is the "correct" value.
*
* https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
*/

int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
{
FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagNoDefer |
kFSEventStreamCreateFlagWatchRoot |
kFSEventStreamCreateFlagFileEvents;
FSEventStreamContext ctx = {
0,
state,
NULL,
NULL,
NULL
};
struct fsmonitor_daemon_backend_data *data;
const void *dir_array[2];

CALLOC_ARRAY(data, 1);
state->backend_data = data;

data->cfsr_worktree_path = CFStringCreateWithCString(
NULL, state->path_worktree_watch.buf, kCFStringEncodingUTF8);
dir_array[data->nr_paths_watching++] = data->cfsr_worktree_path;

if (state->nr_paths_watching > 1) {
data->cfsr_gitdir_path = CFStringCreateWithCString(
NULL, state->path_gitdir_watch.buf,
kCFStringEncodingUTF8);
dir_array[data->nr_paths_watching++] = data->cfsr_gitdir_path;
}

data->cfar_paths_to_watch = CFArrayCreate(NULL, dir_array,
data->nr_paths_watching,
NULL);
data->stream = FSEventStreamCreate(NULL, fsevent_callback, &ctx,
data->cfar_paths_to_watch,
kFSEventStreamEventIdSinceNow,
0.001, flags);
if (data->stream == NULL)
goto failed;

/*
* `data->rl` needs to be set inside the listener thread.
*/

return 0;

failed:
error(_("Unable to create FSEventStream."));

FREE_AND_NULL(state->backend_data);
return -1;
}

void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
{
struct fsmonitor_daemon_backend_data *data;

if (!state || !state->backend_data)
return;

data = state->backend_data;

if (data->stream) {
if (data->stream_started)
FSEventStreamStop(data->stream);
if (data->stream_scheduled)
FSEventStreamInvalidate(data->stream);
FSEventStreamRelease(data->stream);
}

FREE_AND_NULL(state->backend_data);
}

void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
{
struct fsmonitor_daemon_backend_data *data;

data = state->backend_data;
data->shutdown_style = SHUTDOWN_EVENT;

CFRunLoopStop(data->rl);
}

void fsm_listen__loop(struct fsmonitor_daemon_state *state)
{
struct fsmonitor_daemon_backend_data *data;

data = state->backend_data;

data->rl = CFRunLoopGetCurrent();

FSEventStreamScheduleWithRunLoop(data->stream, data->rl, kCFRunLoopDefaultMode);
data->stream_scheduled = 1;

if (!FSEventStreamStart(data->stream)) {
error(_("Failed to start the FSEventStream"));
goto force_error_stop_without_loop;
}
data->stream_started = 1;

CFRunLoopRun();

switch (data->shutdown_style) {
case FORCE_ERROR_STOP:
state->error_code = -1;
/* fall thru */
case FORCE_SHUTDOWN:
ipc_server_stop_async(state->ipc_server_data);
/* fall thru */
case SHUTDOWN_EVENT:
default:
break;
}
return;

force_error_stop_without_loop:
state->error_code = -1;
ipc_server_stop_async(state->ipc_server_data);
return;
}

View File

@ -0,0 +1,586 @@
#include "cache.h"
#include "config.h"
#include "fsmonitor.h"
#include "fsm-listen.h"
#include "fsmonitor--daemon.h"

/*
* The documentation of ReadDirectoryChangesW() states that the maximum
* buffer size is 64K when the monitored directory is remote.
*
* Larger buffers may be used when the monitored directory is local and
* will help us receive events faster from the kernel and avoid dropped
* events.
*
* So we try to use a very large buffer and silently fallback to 64K if
* we get an error.
*/
#define MAX_RDCW_BUF_FALLBACK (65536)
#define MAX_RDCW_BUF (65536 * 8)

struct one_watch
{
char buffer[MAX_RDCW_BUF];
DWORD buf_len;
DWORD count;

struct strbuf path;
HANDLE hDir;
HANDLE hEvent;
OVERLAPPED overlapped;

/*
* Is there an active ReadDirectoryChangesW() call pending. If so, we
* need to later call GetOverlappedResult() and possibly CancelIoEx().
*/
BOOL is_active;
};

struct fsmonitor_daemon_backend_data
{
struct one_watch *watch_worktree;
struct one_watch *watch_gitdir;

HANDLE hEventShutdown;

HANDLE hListener[3]; /* we don't own these handles */
#define LISTENER_SHUTDOWN 0
#define LISTENER_HAVE_DATA_WORKTREE 1
#define LISTENER_HAVE_DATA_GITDIR 2
int nr_listener_handles;
};

/*
* Convert the WCHAR path from the notification into UTF8 and
* then normalize it.
*/
static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info,
struct strbuf *normalized_path)
{
int reserve;
int len = 0;

strbuf_reset(normalized_path);
if (!info->FileNameLength)
goto normalize;

/*
* Pre-reserve enough space in the UTF8 buffer for
* each Unicode WCHAR character to be mapped into a
* sequence of 2 UTF8 characters. That should let us
* avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time.
*/
reserve = info->FileNameLength + 1;
strbuf_grow(normalized_path, reserve);

for (;;) {
len = WideCharToMultiByte(CP_UTF8, 0, info->FileName,
info->FileNameLength / sizeof(WCHAR),
normalized_path->buf,
strbuf_avail(normalized_path) - 1,
NULL, NULL);
if (len > 0)
goto normalize;
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
error(_("[GLE %ld] could not convert path to UTF-8: '%.*ls'"),
GetLastError(),
(int)(info->FileNameLength / sizeof(WCHAR)),
info->FileName);
return -1;
}

strbuf_grow(normalized_path,
strbuf_avail(normalized_path) + reserve);
}

normalize:
strbuf_setlen(normalized_path, len);
return strbuf_normalize_path(normalized_path);
}

void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
{
SetEvent(state->backend_data->hListener[LISTENER_SHUTDOWN]);
}

static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
const char *path)
{
struct one_watch *watch = NULL;
DWORD desired_access = FILE_LIST_DIRECTORY;
DWORD share_mode =
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
HANDLE hDir;
wchar_t wpath[MAX_PATH];

if (xutftowcs_path(wpath, path) < 0) {
error(_("could not convert to wide characters: '%s'"), path);
return NULL;
}

hDir = CreateFileW(wpath,
desired_access, share_mode, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
if (hDir == INVALID_HANDLE_VALUE) {
error(_("[GLE %ld] could not watch '%s'"),
GetLastError(), path);
return NULL;
}

CALLOC_ARRAY(watch, 1);

watch->buf_len = sizeof(watch->buffer); /* assume full MAX_RDCW_BUF */

strbuf_init(&watch->path, 0);
strbuf_addstr(&watch->path, path);

watch->hDir = hDir;
watch->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

return watch;
}

static void destroy_watch(struct one_watch *watch)
{
if (!watch)
return;

strbuf_release(&watch->path);
if (watch->hDir != INVALID_HANDLE_VALUE)
CloseHandle(watch->hDir);
if (watch->hEvent != INVALID_HANDLE_VALUE)
CloseHandle(watch->hEvent);

free(watch);
}

static int start_rdcw_watch(struct fsmonitor_daemon_backend_data *data,
struct one_watch *watch)
{
DWORD dwNotifyFilter =
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_CREATION;

ResetEvent(watch->hEvent);

memset(&watch->overlapped, 0, sizeof(watch->overlapped));
watch->overlapped.hEvent = watch->hEvent;

/*
* Queue an async call using Overlapped IO. This returns immediately.
* Our event handle will be signalled when the real result is available.
*
* The return value here just means that we successfully queued it.
* We won't know if the Read...() actually produces data until later.
*/
watch->is_active = ReadDirectoryChangesW(
watch->hDir, watch->buffer, watch->buf_len, TRUE,
dwNotifyFilter, &watch->count, &watch->overlapped, NULL);

if (watch->is_active)
return 0;

error(_("ReadDirectoryChangedW failed on '%s' [GLE %ld]"),
watch->path.buf, GetLastError());
return -1;
}

static int recv_rdcw_watch(struct one_watch *watch)
{
DWORD gle;

watch->is_active = FALSE;

/*
* The overlapped result is ready. If the Read...() was successful
* we finally receive the actual result into our buffer.
*/
if (GetOverlappedResult(watch->hDir, &watch->overlapped, &watch->count,
TRUE))
return 0;

gle = GetLastError();
if (gle == ERROR_INVALID_PARAMETER &&
/*
* The kernel throws an invalid parameter error when our
* buffer is too big and we are pointed at a remote
* directory (and possibly for other reasons). Quietly
* set it down and try again.
*
* See note about MAX_RDCW_BUF at the top.
*/
watch->buf_len > MAX_RDCW_BUF_FALLBACK) {
watch->buf_len = MAX_RDCW_BUF_FALLBACK;
return -2;
}

/*
* NEEDSWORK: If an external <gitdir> is deleted, the above
* returns an error. I'm not sure that there's anything that
* we can do here other than failing -- the <worktree>/.git
* link file would be broken anyway. We might try to check
* for that and return a better error message, but I'm not
* sure it is worth it.
*/

error(_("GetOverlappedResult failed on '%s' [GLE %ld]"),
watch->path.buf, gle);
return -1;
}

static void cancel_rdcw_watch(struct one_watch *watch)
{
DWORD count;

if (!watch || !watch->is_active)
return;

/*
* The calls to ReadDirectoryChangesW() and GetOverlappedResult()
* form a "pair" (my term) where we queue an IO and promise to
* hang around and wait for the kernel to give us the result.
*
* If for some reason after we queue the IO, we have to quit
* or otherwise not stick around for the second half, we must
* tell the kernel to abort the IO. This prevents the kernel
* from writing to our buffer and/or signalling our event
* after we free them.
*
* (Ask me how much fun it was to track that one down).
*/
CancelIoEx(watch->hDir, &watch->overlapped);
GetOverlappedResult(watch->hDir, &watch->overlapped, &count, TRUE);
watch->is_active = FALSE;
}

/*
* Process filesystem events that happen anywhere (recursively) under the
* <worktree> root directory. For a normal working directory, this includes
* both version controlled files and the contents of the .git/ directory.
*
* If <worktree>/.git is a file, then we only see events for the file
* itself.
*/
static int process_worktree_events(struct fsmonitor_daemon_state *state)
{
struct fsmonitor_daemon_backend_data *data = state->backend_data;
struct one_watch *watch = data->watch_worktree;
struct strbuf path = STRBUF_INIT;
struct string_list cookie_list = STRING_LIST_INIT_DUP;
struct fsmonitor_batch *batch = NULL;
const char *p = watch->buffer;

/*
* If the kernel gets more events than will fit in the kernel
* buffer associated with our RDCW handle, it drops them and
* returns a count of zero.
*
* Yes, the call returns WITHOUT error and with length zero.
* This is the documented behavior. (My testing has confirmed
* that it also sets the last error to ERROR_NOTIFY_ENUM_DIR,
* but we do not rely on that since the function did not
* return an error and it is not documented.)
*
* (The "overflow" case is not ambiguous with the "no data" case
* because we did an INFINITE wait.)
*
* This means we have a gap in coverage. Tell the daemon layer
* to resync.
*/
if (!watch->count) {
trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel",
"overflow");
fsmonitor_force_resync(state);
return LISTENER_HAVE_DATA_WORKTREE;
}

/*
* On Windows, `info` contains an "array" of paths that are
* relative to the root of whichever directory handle received
* the event.
*/
for (;;) {
FILE_NOTIFY_INFORMATION *info = (void *)p;
const char *slash;
enum fsmonitor_path_type t;

strbuf_reset(&path);
if (normalize_path_in_utf8(info, &path) == -1)
goto skip_this_path;

t = fsmonitor_classify_path_workdir_relative(path.buf);

switch (t) {
case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
/* special case cookie files within .git */

/* Use just the filename of the cookie file. */
slash = find_last_dir_sep(path.buf);
string_list_append(&cookie_list,
slash ? slash + 1 : path.buf);
break;

case IS_INSIDE_DOT_GIT:
/* ignore everything inside of "<worktree>/.git/" */
break;

case IS_DOT_GIT:
/* "<worktree>/.git" was deleted (or renamed away) */
if ((info->Action == FILE_ACTION_REMOVED) ||
(info->Action == FILE_ACTION_RENAMED_OLD_NAME)) {
trace2_data_string("fsmonitor", NULL,
"fsm-listen/dotgit",
"removed");
goto force_shutdown;
}
break;

case IS_WORKDIR_PATH:
/* queue normal pathname */
if (!batch)
batch = fsmonitor_batch__new();
fsmonitor_batch__add_path(batch, path.buf);
break;

case IS_GITDIR:
case IS_INSIDE_GITDIR:
case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
default:
BUG("unexpected path classification '%d' for '%s'",
t, path.buf);
}

skip_this_path:
if (!info->NextEntryOffset)
break;
p += info->NextEntryOffset;
}

fsmonitor_publish(state, batch, &cookie_list);
batch = NULL;
string_list_clear(&cookie_list, 0);
strbuf_release(&path);
return LISTENER_HAVE_DATA_WORKTREE;

force_shutdown:
fsmonitor_batch__free_list(batch);
string_list_clear(&cookie_list, 0);
strbuf_release(&path);
return LISTENER_SHUTDOWN;
}

/*
* Process filesystem events that happened anywhere (recursively) under the
* external <gitdir> (such as non-primary worktrees or submodules).
* We only care about cookie files that our client threads created here.
*
* Note that we DO NOT get filesystem events on the external <gitdir>
* itself (it is not inside something that we are watching). In particular,
* we do not get an event if the external <gitdir> is deleted.
*/
static int process_gitdir_events(struct fsmonitor_daemon_state *state)
{
struct fsmonitor_daemon_backend_data *data = state->backend_data;
struct one_watch *watch = data->watch_gitdir;
struct strbuf path = STRBUF_INIT;
struct string_list cookie_list = STRING_LIST_INIT_DUP;
const char *p = watch->buffer;

if (!watch->count) {
trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel",
"overflow");
fsmonitor_force_resync(state);
return LISTENER_HAVE_DATA_GITDIR;
}

for (;;) {
FILE_NOTIFY_INFORMATION *info = (void *)p;
const char *slash;
enum fsmonitor_path_type t;

strbuf_reset(&path);
if (normalize_path_in_utf8(info, &path) == -1)
goto skip_this_path;

t = fsmonitor_classify_path_gitdir_relative(path.buf);

switch (t) {
case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
/* special case cookie files within gitdir */

/* Use just the filename of the cookie file. */
slash = find_last_dir_sep(path.buf);
string_list_append(&cookie_list,
slash ? slash + 1 : path.buf);
break;

case IS_INSIDE_GITDIR:
goto skip_this_path;

default:
BUG("unexpected path classification '%d' for '%s'",
t, path.buf);
}

skip_this_path:
if (!info->NextEntryOffset)
break;
p += info->NextEntryOffset;
}

fsmonitor_publish(state, NULL, &cookie_list);
string_list_clear(&cookie_list, 0);
strbuf_release(&path);
return LISTENER_HAVE_DATA_GITDIR;
}

void fsm_listen__loop(struct fsmonitor_daemon_state *state)
{
struct fsmonitor_daemon_backend_data *data = state->backend_data;
DWORD dwWait;
int result;

state->error_code = 0;

if (start_rdcw_watch(data, data->watch_worktree) == -1)
goto force_error_stop;

if (data->watch_gitdir &&
start_rdcw_watch(data, data->watch_gitdir) == -1)
goto force_error_stop;

for (;;) {
dwWait = WaitForMultipleObjects(data->nr_listener_handles,
data->hListener,
FALSE, INFINITE);

if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_WORKTREE) {
result = recv_rdcw_watch(data->watch_worktree);
if (result == -1) {
/* hard error */
goto force_error_stop;
}
if (result == -2) {
/* retryable error */
if (start_rdcw_watch(data, data->watch_worktree) == -1)
goto force_error_stop;
continue;
}

/* have data */
if (process_worktree_events(state) == LISTENER_SHUTDOWN)
goto force_shutdown;
if (start_rdcw_watch(data, data->watch_worktree) == -1)
goto force_error_stop;
continue;
}

if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_GITDIR) {
result = recv_rdcw_watch(data->watch_gitdir);
if (result == -1) {
/* hard error */
goto force_error_stop;
}
if (result == -2) {
/* retryable error */
if (start_rdcw_watch(data, data->watch_gitdir) == -1)
goto force_error_stop;
continue;
}

/* have data */
if (process_gitdir_events(state) == LISTENER_SHUTDOWN)
goto force_shutdown;
if (start_rdcw_watch(data, data->watch_gitdir) == -1)
goto force_error_stop;
continue;
}

if (dwWait == WAIT_OBJECT_0 + LISTENER_SHUTDOWN)
goto clean_shutdown;

error(_("could not read directory changes [GLE %ld]"),
GetLastError());
goto force_error_stop;
}

force_error_stop:
state->error_code = -1;

force_shutdown:
/*
* Tell the IPC thead pool to stop (which completes the await
* in the main thread (which will also signal this thread (if
* we are still alive))).
*/
ipc_server_stop_async(state->ipc_server_data);

clean_shutdown:
cancel_rdcw_watch(data->watch_worktree);
cancel_rdcw_watch(data->watch_gitdir);
}

int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
{
struct fsmonitor_daemon_backend_data *data;

CALLOC_ARRAY(data, 1);

data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);

data->watch_worktree = create_watch(state,
state->path_worktree_watch.buf);
if (!data->watch_worktree)
goto failed;

if (state->nr_paths_watching > 1) {
data->watch_gitdir = create_watch(state,
state->path_gitdir_watch.buf);
if (!data->watch_gitdir)
goto failed;
}

data->hListener[LISTENER_SHUTDOWN] = data->hEventShutdown;
data->nr_listener_handles++;

data->hListener[LISTENER_HAVE_DATA_WORKTREE] =
data->watch_worktree->hEvent;
data->nr_listener_handles++;

if (data->watch_gitdir) {
data->hListener[LISTENER_HAVE_DATA_GITDIR] =
data->watch_gitdir->hEvent;
data->nr_listener_handles++;
}

state->backend_data = data;
return 0;

failed:
CloseHandle(data->hEventShutdown);
destroy_watch(data->watch_worktree);
destroy_watch(data->watch_gitdir);

return -1;
}

void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
{
struct fsmonitor_daemon_backend_data *data;

if (!state || !state->backend_data)
return;

data = state->backend_data;

CloseHandle(data->hEventShutdown);
destroy_watch(data->watch_worktree);
destroy_watch(data->watch_gitdir);

FREE_AND_NULL(state->backend_data);
}

View File

@ -0,0 +1,49 @@
#ifndef FSM_LISTEN_H
#define FSM_LISTEN_H

/* This needs to be implemented by each backend */

#ifdef HAVE_FSMONITOR_DAEMON_BACKEND

struct fsmonitor_daemon_state;

/*
* Initialize platform-specific data for the fsmonitor listener thread.
* This will be called from the main thread PRIOR to staring the
* fsmonitor_fs_listener thread.
*
* Returns 0 if successful.
* Returns -1 otherwise.
*/
int fsm_listen__ctor(struct fsmonitor_daemon_state *state);

/*
* Cleanup platform-specific data for the fsmonitor listener thread.
* This will be called from the main thread AFTER joining the listener.
*/
void fsm_listen__dtor(struct fsmonitor_daemon_state *state);

/*
* The main body of the platform-specific event loop to watch for
* filesystem events. This will run in the fsmonitor_fs_listen thread.
*
* It should call `ipc_server_stop_async()` if the listener thread
* prematurely terminates (because of a filesystem error or if it
* detects that the .git directory has been deleted). (It should NOT
* do so if the listener thread receives a normal shutdown signal from
* the IPC layer.)
*
* It should set `state->error_code` to -1 if the daemon should exit
* with an error.
*/
void fsm_listen__loop(struct fsmonitor_daemon_state *state);

/*
* Gently request that the fsmonitor listener thread shutdown.
* It does not wait for it to stop. The caller should do a JOIN
* to wait for it.
*/
void fsm_listen__stop_async(struct fsmonitor_daemon_state *state);

#endif /* HAVE_FSMONITOR_DAEMON_BACKEND */
#endif /* FSM_LISTEN_H */

View File

@ -6,6 +6,7 @@
*
*/
#include "cache.h"
#include "date.h"
#include "branch.h"
#include "config.h"
#include "environment.h"
@ -21,6 +22,7 @@
#include "dir.h"
#include "color.h"
#include "refs.h"
#include "worktree.h"

struct config_source {
struct config_source *prev;
@ -2294,8 +2296,8 @@ int git_configset_get_string(struct config_set *cs, const char *key, char **dest
return 1;
}

int git_configset_get_string_tmp(struct config_set *cs, const char *key,
const char **dest)
static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
const char **dest)
{
const char *value;
if (!git_configset_get_value(cs, key, &value)) {
@ -2624,20 +2626,6 @@ int git_config_get_max_percent_split_change(void)
return -1; /* default value */
}

int git_config_get_fsmonitor(void)
{
if (git_config_get_pathname("core.fsmonitor", &core_fsmonitor))
core_fsmonitor = getenv("GIT_TEST_FSMONITOR");

if (core_fsmonitor && !*core_fsmonitor)
core_fsmonitor = NULL;

if (core_fsmonitor)
return 1;

return 0;
}

int git_config_get_index_threads(int *dest)
{
int is_bool, val;
@ -3000,6 +2988,20 @@ int git_config_set_gently(const char *key, const char *value)
return git_config_set_multivar_gently(key, value, NULL, 0);
}

int repo_config_set_worktree_gently(struct repository *r,
const char *key, const char *value)
{
/* Only use worktree-specific config if it is is already enabled. */
if (repository_format_worktree_config) {
char *file = repo_git_path(r, "config.worktree");
int ret = git_config_set_multivar_in_file_gently(
file, key, value, NULL, 0);
free(file);
return ret;
}
return repo_config_set_multivar_gently(r, key, value, NULL, 0);
}

void git_config_set(const char *key, const char *value)
{
git_config_set_multivar(key, value, NULL, 0);
@ -3297,14 +3299,28 @@ void git_config_set_multivar_in_file(const char *config_filename,
int git_config_set_multivar_gently(const char *key, const char *value,
const char *value_pattern, unsigned flags)
{
return git_config_set_multivar_in_file_gently(NULL, key, value, value_pattern,
flags);
return repo_config_set_multivar_gently(the_repository, key, value,
value_pattern, flags);
}

int repo_config_set_multivar_gently(struct repository *r, const char *key,
const char *value,
const char *value_pattern, unsigned flags)
{
char *file = repo_git_path(r, "config");
int res = git_config_set_multivar_in_file_gently(file,
key, value,
value_pattern,
flags);
free(file);
return res;
}

void git_config_set_multivar(const char *key, const char *value,
const char *value_pattern, unsigned flags)
{
git_config_set_multivar_in_file(NULL, key, value, value_pattern,
git_config_set_multivar_in_file(git_path("config"),
key, value, value_pattern,
flags);
}


View File

@ -266,6 +266,13 @@ void git_config_set_in_file(const char *, const char *, const char *);

int git_config_set_gently(const char *, const char *);

/**
* Write a config value that should apply to the current worktree. If
* extensions.worktreeConfig is enabled, then the write will happen in the
* current worktree's config. Otherwise, write to the common config file.
*/
int repo_config_set_worktree_gently(struct repository *, const char *, const char *);

/**
* write config values to `.git/config`, takes a key/value pair as parameter.
*/
@ -294,6 +301,7 @@ int git_config_parse_key(const char *, char **, size_t *);

int git_config_set_multivar_gently(const char *, const char *, const char *, unsigned);
void git_config_set_multivar(const char *, const char *, const char *, unsigned);
int repo_config_set_multivar_gently(struct repository *, const char *, const char *, const char *, unsigned);
int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, unsigned);

/**
@ -466,7 +474,6 @@ void git_configset_clear(struct config_set *cs);
int git_configset_get_value(struct config_set *cs, const char *key, const char **dest);

int git_configset_get_string(struct config_set *cs, const char *key, char **dest);
int git_configset_get_string_tmp(struct config_set *cs, const char *key, const char **dest);
int git_configset_get_int(struct config_set *cs, const char *key, int *dest);
int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest);
int git_configset_get_bool(struct config_set *cs, const char *key, int *dest);
@ -590,7 +597,6 @@ int git_config_get_pathname(const char *key, const char **dest);
int git_config_get_index_threads(int *dest);
int git_config_get_split_index(void);
int git_config_get_max_percent_split_change(void);
int git_config_get_fsmonitor(void);

/* This dies if the configured or default date is in the future */
int git_config_get_expiry(const char *key, const char **output);

View File

@ -157,6 +157,16 @@ ifeq ($(uname_S),Darwin)
MSGFMT = /usr/local/opt/gettext/bin/msgfmt
endif
endif

# The builtin FSMonitor on MacOS builds upon Simple-IPC. Both require
# Unix domain sockets and PThreads.
ifndef NO_PTHREADS
ifndef NO_UNIX_SOCKETS
FSMONITOR_DAEMON_BACKEND = darwin
endif
endif

BASIC_LDFLAGS += -framework CoreServices
endif
ifeq ($(uname_S),SunOS)
NEEDS_SOCKET = YesPlease
@ -435,6 +445,11 @@ ifeq ($(uname_S),Windows)
# so we don't need this:
#
# SNPRINTF_RETURNS_BOGUS = YesPlease

# The builtin FSMonitor requires Named Pipes and Threads on Windows.
# These are always available, so we do not have to conditionally
# support it.
FSMONITOR_DAEMON_BACKEND = win32
NO_SVN_TESTS = YesPlease
RUNTIME_PREFIX = YesPlease
HAVE_WPGMPTR = YesWeDo
@ -619,6 +634,11 @@ ifeq ($(uname_S),MINGW)
NO_STRTOUMAX = YesPlease
NO_MKDTEMP = YesPlease
NO_SVN_TESTS = YesPlease

# The builtin FSMonitor requires Named Pipes and Threads on Windows.
# These are always available, so we do not have to conditionally
# support it.
FSMONITOR_DAEMON_BACKEND = win32
RUNTIME_PREFIX = YesPlease
HAVE_WPGMPTR = YesWeDo
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease

View File

@ -379,7 +379,7 @@ struct ref **get_remote_heads(struct packet_reader *reader,

/* Returns 1 when a valid ref has been added to `list`, 0 otherwise */
static int process_ref_v2(struct packet_reader *reader, struct ref ***list,
char **unborn_head_target)
const char **unborn_head_target)
{
int ret = 1;
int i = 0;
@ -483,7 +483,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
const char *hash_name;
struct strvec *ref_prefixes = transport_options ?
&transport_options->ref_prefixes : NULL;
char **unborn_head_target = transport_options ?
const char **unborn_head_target = transport_options ?
&transport_options->unborn_head_target : NULL;
*list = NULL;


View File

@ -285,6 +285,16 @@ else()
endif()
endif()

if(SUPPORTS_SIMPLE_IPC)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)
endif()
endif()

set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})

#header checks

View File

@ -49,6 +49,11 @@
# and git-switch completion (e.g., completing "foo" when "origin/foo"
# exists).
#
# GIT_COMPLETION_SHOW_ALL_COMMANDS
#
# When set to "1" suggest all commands, including plumbing commands
# which are hidden by default (e.g. "cat-file" on "git ca<TAB>").
#
# GIT_COMPLETION_SHOW_ALL
#
# When set to "1" suggest all options, including options which are
@ -3483,7 +3488,13 @@ __git_main ()
then
__gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST"
else
__gitcomp "$(__git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)"
local list_cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config

if test "${GIT_COMPLETION_SHOW_ALL_COMMANDS-}" = "1"
then
list_cmds=builtins,$list_cmds
fi
__gitcomp "$(__git --list-cmds=$list_cmds)"
fi
;;
esac

View File

@ -808,6 +808,25 @@ int cmd_main(int argc, const char **argv)
struct strbuf scalar_usage = STRBUF_INIT;
int i;

while (argc > 1 && *argv[1] == '-') {
if (!strcmp(argv[1], "-C")) {
if (argc < 3)
die(_("-C requires a <directory>"));
if (chdir(argv[2]) < 0)
die_errno(_("could not change to '%s'"),
argv[2]);
argc -= 2;
argv += 2;
} else if (!strcmp(argv[1], "-c")) {
if (argc < 3)
die(_("-c requires a <key>=<value> argument"));
git_config_push_parameter(argv[2]);
argc -= 2;
argv += 2;
} else
break;
}

if (argc > 1) {
argv++;
argc--;
@ -818,7 +837,8 @@ int cmd_main(int argc, const char **argv)
}

strbuf_addstr(&scalar_usage,
N_("scalar <command> [<options>]\n\nCommands:\n"));
N_("scalar [-C <directory>] [-c <key>=<value>] "
"<command> [<options>]\n\nCommands:\n"));
for (i = 0; builtins[i].name; i++)
strbuf_addf(&scalar_usage, "\t%s\n", builtins[i].name);


View File

@ -36,6 +36,16 @@ The `scalar` command implements various subcommands, and different options
depending on the subcommand. With the exception of `clone`, `list` and
`reconfigure --all`, all subcommands expect to be run in an enlistment.

The following options can be specified _before_ the subcommand:

-C <directory>::
Before running the subcommand, change the working directory. This
option imitates the same option of linkgit:git[1].

-c <key>=<value>::
For the duration of running the specified subcommand, configure this
setting. This option imitates the same option of linkgit:git[1].

COMMANDS
--------


View File

@ -85,4 +85,12 @@ test_expect_success 'scalar delete with enlistment' '
test_path_is_missing cloned
'

test_expect_success 'scalar supports -c/-C' '
test_when_finished "scalar delete sub" &&
git init sub &&
scalar -C sub -c status.aheadBehind=bogus register &&
test -z "$(git -C sub config --local status.aheadBehind)" &&
test true = "$(git -C sub config core.preloadIndex)"
'

test_done

View File

@ -975,10 +975,10 @@ cmd_merge () {

if test -n "$arg_addmerge_message"
then
git merge -Xsubtree="$arg_prefix" \
git merge --no-ff -Xsubtree="$arg_prefix" \
--message="$arg_addmerge_message" "$rev"
else
git merge -Xsubtree="$arg_prefix" $rev
git merge --no-ff -Xsubtree="$arg_prefix" $rev
fi
}


9
date.c
View File

@ -5,6 +5,7 @@
*/

#include "cache.h"
#include "date.h"

/*
* This is like mktime, but without normalization of tm_wday and tm_yday.
@ -205,11 +206,10 @@ void show_date_relative(timestamp_t time, struct strbuf *timebuf)

struct date_mode *date_mode_from_type(enum date_mode_type type)
{
static struct date_mode mode;
static struct date_mode mode = DATE_MODE_INIT;
if (type == DATE_STRFTIME)
BUG("cannot create anonymous strftime date_mode struct");
mode.type = type;
mode.local = 0;
return &mode;
}

@ -993,6 +993,11 @@ void parse_date_format(const char *format, struct date_mode *mode)
die("unknown date format %s", format);
}

void date_mode_release(struct date_mode *mode)
{
free((char *)mode->strftime_fmt);
}

void datestamp(struct strbuf *out)
{
time_t now;

74
date.h Normal file
View File

@ -0,0 +1,74 @@
#ifndef DATE_H
#define DATE_H

/**
* The date mode type. This has DATE_NORMAL at an explicit "= 0" to
* accommodate a memset([...], 0, [...]) initialization when "struct
* date_mode" is used as an embedded struct member, as in the case of
* e.g. "struct pretty_print_context" and "struct rev_info".
*/
enum date_mode_type {
DATE_NORMAL = 0,
DATE_HUMAN,
DATE_RELATIVE,
DATE_SHORT,
DATE_ISO8601,
DATE_ISO8601_STRICT,
DATE_RFC2822,
DATE_STRFTIME,
DATE_RAW,
DATE_UNIX
};

struct date_mode {
enum date_mode_type type;
const char *strftime_fmt;
int local;
};

#define DATE_MODE_INIT { \
.type = DATE_NORMAL, \
}

/**
* Convenience helper for passing a constant type, like:
*
* show_date(t, tz, DATE_MODE(NORMAL));
*/
#define DATE_MODE(t) date_mode_from_type(DATE_##t)
struct date_mode *date_mode_from_type(enum date_mode_type type);

/**
* Format <'time', 'timezone'> into static memory according to 'mode'
* and return it. The mode is an initialized "struct date_mode"
* (usually from the DATE_MODE() macro).
*/
const char *show_date(timestamp_t time, int timezone, const struct date_mode *mode);

/**
* Parse a date format for later use with show_date().
*
* When the "date_mode_type" is DATE_STRFTIME the "strftime_fmt"
* member of "struct date_mode" will be a malloc()'d format string to
* be used with strbuf_addftime(), in which case you'll need to call
* date_mode_release() later.
*/
void parse_date_format(const char *format, struct date_mode *mode);

/**
* Release a "struct date_mode", currently only required if
* parse_date_format() has parsed a "DATE_STRFTIME" format.
*/
void date_mode_release(struct date_mode *mode);

void show_date_relative(timestamp_t time, struct strbuf *timebuf);
int parse_date(const char *date, struct strbuf *out);
int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset);
int parse_expiry_date(const char *date, timestamp_t *timestamp);
void datestamp(struct strbuf *out);
#define approxidate(s) approxidate_careful((s), NULL)
timestamp_t approxidate_careful(const char *, int *);
timestamp_t approxidate_relative(const char *date);
int date_overflows(timestamp_t date);
time_t tm_to_time_t(const struct tm *tm);
#endif

View File

@ -78,7 +78,7 @@ static void set_diff_merges(struct rev_info *revs, const char *optarg)
diff_merges_setup_func_t func = func_by_opt(optarg);

if (!func)
die(_("unknown value for --diff-merges: %s"), optarg);
die(_("invalid value for '%s': '%s'"), "--diff-merges", optarg);

func(revs);


2
diff.c
View File

@ -6452,6 +6452,8 @@ void diff_free(struct diff_options *options)

diff_free_file(options);
diff_free_ignore_regex(options);
clear_pathspec(&options->pathspec);
FREE_AND_NULL(options->parseopts);
}

void diff_flush(struct diff_options *options)

Some files were not shown because too many files have changed in this diff Show More