# The settings for C (*.c and *.h) files are mirrored in .clang-format. Keep
# them in sync.
-[*.{c,h,sh,perl,pl,pm,txt}]
+[{*.{c,h,sh,perl,pl,pm,txt},config.mak.*,Makefile}]
indent_style = tab
tab_width = 8
- Prefer succinctness and matter-of-factly describing functionality
in the abstract. E.g.
- --short:: Emit output in the short-format.
+ `--short`:: Emit output in the short-format.
and avoid something like these overly verbose alternatives:
- --short:: Use this to emit output in the short-format.
- --short:: You can use this to get output in the short-format.
- --short:: A user who prefers shorter output could....
- --short:: Should a person and/or program want shorter output, he
- she/they/it can...
+ `--short`:: Use this to emit output in the short-format.
+ `--short`:: You can use this to get output in the short-format.
+ `--short`:: A user who prefers shorter output could....
+ `--short`:: Should a person and/or program want shorter output, he
+ she/they/it can...
This practice often eliminates the need to involve human actors in
your description, but it is a good practice regardless of the
addressing the hypothetical user, and possibly "we" when
discussing how the program might react to the user. E.g.
- You can use this option instead of --xyz, but we might remove
+ You can use this option instead of `--xyz`, but we might remove
support for it in future versions.
while keeping in mind that you can probably be less verbose, e.g.
- Use this instead of --xyz. This option might be removed in future
+ Use this instead of `--xyz`. This option might be removed in future
versions.
- If you still need to refer to an example person that is
The same general rule as for code applies -- imitate the existing
conventions.
- A few commented examples follow to provide reference when writing or
- modifying command usage strings and synopsis sections in the manual
- pages:
- Placeholders are spelled in lowercase and enclosed in angle brackets:
- <file>
- --sort=<key>
- --abbrev[=<n>]
+Markup:
+
+ Literal parts (e.g. use of command-line options, command names,
+ branch names, URLs, pathnames (files and directories), configuration and
+ environment variables) must be typeset as verbatim (i.e. wrapped with
+ backticks):
+ `--pretty=oneline`
+ `git rev-list`
+ `remote.pushDefault`
+ `http://git.example.com`
+ `.git/config`
+ `GIT_DIR`
+ `HEAD`
+ `umask`(2)
+
+ An environment variable must be prefixed with "$" only when referring to its
+ value and not when referring to the variable itself, in this case there is
+ nothing to add except the backticks:
+ `GIT_DIR` is specified
+ `$GIT_DIR/hooks/pre-receive`
+
+ Word phrases enclosed in `backtick characters` are rendered literally
+ and will not be further expanded. The use of `backticks` to achieve the
+ previous rule means that literal examples should not use AsciiDoc
+ escapes.
+ Correct:
+ `--pretty=oneline`
+ Incorrect:
+ `\--pretty=oneline`
+
+ Placeholders are spelled in lowercase and enclosed in
+ angle brackets surrounded by underscores:
+ _<file>_
+ _<commit>_
If a placeholder has multiple words, they are separated by dashes:
- <new-branch-name>
- --template=<template-directory>
+ _<new-branch-name>_
+ _<template-directory>_
+
+ A placeholder is not enclosed in backticks, as it is not a literal.
+
+ When needed, use a distinctive identifier for placeholders, usually
+ made of a qualification and a type:
+ _<git-dir>_
+ _<key-id>_
+
+ When literal and placeholders are mixed, each markup is applied for
+ each sub-entity. If they are stuck, a special markup, called
+ unconstrained formatting is required.
+ Unconstrained formating for placeholders is __<like-this>__
+ Unconstrained formatting for literal formatting is ++like this++
+ `--jobs` _<n>_
+ ++--sort=++__<key>__
+ __<directory>__++/.git++
+ ++remote.++__<name>__++.mirror++
+
+ caveat: ++ unconstrained format is not verbatim and may expand
+ content. Use Asciidoc escapes inside them.
- When a placeholder is cited in text paragraph, it is enclosed in angle
- brackets to remind the reader the reference in the synopsis section.
- For better visibility, the placeholder is typeset in italics:
- The _<file>_ to be added.
+Synopsis Syntax
+
+ Syntax grammar is formatted neither as literal nor as placeholder.
+
+ A few commented examples follow to provide reference when writing or
+ modifying command usage strings and synopsis sections in the manual
+ pages:
Possibility of multiple occurrences is indicated by three dots:
- <file>...
+ _<file>_...
(One or more of <file>.)
Optional parts are enclosed in square brackets:
- [<file>...]
+ [_<file>_...]
(Zero or more of <file>.)
- --exec-path[=<path>]
+ ++--exec-path++[++=++__<path>__]
(Option with an optional argument. Note that the "=" is inside the
brackets.)
- [<patch>...]
+ [_<patch>_...]
(Zero or more of <patch>. Note that the dots are inside, not
outside the brackets.)
Multiple alternatives are indicated with vertical bars:
- [-q | --quiet]
- [--utf8 | --no-utf8]
+ [`-q` | `--quiet`]
+ [`--utf8` | `--no-utf8`]
Use spacing around "|" token(s), but not immediately after opening or
before closing a [] or () pair:
- Do: [-q | --quiet]
- Don't: [-q|--quiet]
+ Do: [`-q` | `--quiet`]
+ Don't: [`-q`|`--quiet`]
Don't use spacing around "|" tokens when they're used to separate the
alternate arguments of an option:
- Do: --track[=(direct|inherit)]
- Don't: --track[=(direct | inherit)]
+ Do: ++--track++[++=++(`direct`|`inherit`)]`
+ Don't: ++--track++[++=++(`direct` | `inherit`)]
Parentheses are used for grouping:
- [(<rev> | <range>)...]
+ [(_<rev>_ | _<range>_)...]
(Any number of either <rev> or <range>. Parens are needed to make
it clear that "..." pertains to both <rev> and <range>.)
- [(-p <parent>)...]
+ [(`-p` _<parent>_)...]
(Any number of option -p, each with one <parent> argument.)
- git remote set-head <name> (-a | -d | <branch>)
+ `git remote set-head` _<name>_ (`-a` | `-d` | _<branch>_)
(One and only one of "-a", "-d" or "<branch>" _must_ (no square
brackets) be provided.)
And a somewhat more contrived example:
- --diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]
+ `--diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]`
Here "=" is outside the brackets, because "--diff-filter=" is a
valid usage. "*" has its own pair of brackets, because it can
(optionally) be specified only when one or more of the letters is
the user would type into a shell and use 'Git' (uppercase first letter)
when talking about the version control system and its properties.
- A few commented examples follow to provide reference when writing or
- modifying paragraphs or option/command explanations that contain options
- or commands:
-
- Literal examples (e.g. use of command-line options, command names,
- branch names, URLs, pathnames (files and directories), configuration and
- environment variables) must be typeset in monospace (i.e. wrapped with
- backticks):
- `--pretty=oneline`
- `git rev-list`
- `remote.pushDefault`
- `http://git.example.com`
- `.git/config`
- `GIT_DIR`
- `HEAD`
-
- An environment variable must be prefixed with "$" only when referring to its
- value and not when referring to the variable itself, in this case there is
- nothing to add except the backticks:
- `GIT_DIR` is specified
- `$GIT_DIR/hooks/pre-receive`
-
- Word phrases enclosed in `backtick characters` are rendered literally
- and will not be further expanded. The use of `backticks` to achieve the
- previous rule means that literal examples should not use AsciiDoc
- escapes.
- Correct:
- `--pretty=oneline`
- Incorrect:
- `\--pretty=oneline`
-
-A placeholder is not enclosed in backticks, as it is not a literal.
-
If some place in the documentation needs to typeset a command usage
example with inline substitutions, it is fine to use +monospaced and
inline substituted text+ instead of `monospaced literal text`, and with
...
-static int git_walken_config(const char *var, const char *value, void *cb)
+static int git_walken_config(const char *var, const char *value,
+ const struct config_context *ctx, void *cb)
{
/*
* For now, we don't have any custom configuration, so fall back to
* the default config.
*/
- return git_default_config(var, value, cb);
+ return git_default_config(var, value, ctx, cb);
}
----
First some setup. Add `grep_config()` to `git_walken_config()`:
----
-static int git_walken_config(const char *var, const char *value, void *cb)
+static int git_walken_config(const char *var, const char *value,
+ const struct config_context *ctx, void *cb)
{
- grep_config(var, value, cb);
- return git_default_config(var, value, cb);
+ grep_config(var, value, ctx, cb);
+ return git_default_config(var, value, ctx, cb);
}
----
We can base our work on an example. `git pack-objects` prepares all kinds of
objects for packing into a bitmap or packfile. The work we are interested in
-resides in `builtins/pack-objects.c:get_object_list()`; examination of that
+resides in `builtin/pack-objects.c:get_object_list()`; examination of that
function shows that the all-object walk is being performed by
`traverse_commit_list()` or `traverse_commit_list_filtered()`. Those two
functions reside in `list-objects.c`; examining the source shows that, despite
} else {
trace_printf(
_("Filtered object walk with filterspec 'tree:1'.\n"));
- CALLOC_ARRAY(rev->filter, 1);
- parse_list_objects_filter(rev->filter, "tree:1");
+
+ parse_list_objects_filter(&rev->filter, "tree:1");
}
traverse_commit_list(rev, walken_show_commit,
walken_show_object, NULL);
=== Counting Omitted Objects
We also have the capability to enumerate all objects which were omitted by a
-filter, like with `git log --filter=<spec> --filter-print-omitted`. Asking
-`traverse_commit_list_filtered()` to populate the `omitted` list means that our
-object walk does not perform any better than an unfiltered object walk; all
-reachable objects are walked in order to populate the list.
+filter, like with `git log --filter=<spec> --filter-print-omitted`. To do this,
+change `traverse_commit_list()` to `traverse_commit_list_filtered()`, which is
+able to populate an `omitted` list. Asking for this list of filtered objects
+may cause performance degradations, however, because in this case, despite
+filtering objects, the possibly much larger set of all reachable objects must
+be processed in order to populate that list.
First, add the `struct oidset` and related items we will use to iterate it:
...
----
-Modify the call to `traverse_commit_list_filtered()` to include your `omitted`
-object:
+Replace the call to `traverse_commit_list()` with
+`traverse_commit_list_filtered()` and pass a pointer to the `omitted` oidset
+defined and initialized above:
----
...
With only that change, run again (but save yourself some scrollback):
----
-$ GIT_TRACE=1 ./bin-wrappers/git walken | head -n 10
+$ GIT_TRACE=1 ./bin-wrappers/git walken 2>&1 | head -n 10
----
Take a look at the top commit with `git show` and the object ID you printed; it
----
$ make
-$ GIT_TRACE=1 ./bin-wrappers git walken | tail -n 10
+$ GIT_TRACE=1 ./bin-wrappers/git walken 2>&1 | tail -n 10
----
The last commit object given should have the same OID as the one we saw at the
* Hints that suggest what to do after resolving conflicts can now be
squelched by disabling advice.mergeConflict.
+ * Allow git-cherry-pick(1) to automatically drop redundant commits via
+ a new `--empty` option, similar to the `--empty` options for
+ git-rebase(1) and git-am(1). Includes a soft deprecation of
+ `--keep-redundant-commits` as well as some related docs changes and
+ sequencer code cleanup.
+
+ * "git config" learned "--comment=<message>" option to leave a
+ comment immediately after the "variable = value" on the same line
+ in the configuration file.
+
+ * core.commentChar used to be limited to a single byte, but has been
+ updated to allow an arbitrary multi-byte sequence.
+
+ * "git add -p" and other "interactive hunk selection" UI has learned to
+ skip showing the hunk immediately after it has already been shown, and
+ an additional action to explicitly ask to reshow the current hunk.
+
+ * "git pack-refs" learned the "--auto" option, which is a useful
+ addition to be triggered from "git gc --auto".
Performance, Internal Implementation, Development Support etc.
outside them, and document the best practice to help future
developers.
+ * Introduce an experimental protocol for contributors to propose the
+ topic description to be used in the "What's cooking" report, the
+ merge commit message for the topic, and in the release notes and
+ document it in the SubmittingPatches document.
+
+ * The t/README file now gives a hint on running individual tests in
+ the "t/" directory with "make t<num>-*.sh t<num>-*.sh".
+ (merge 8d383806fc pb/test-scripts-are-build-targets later to maint).
+
+ * The "hint:" messages given by the advice mechanism, when given a
+ message with a blank line, left a line with trailing whitespace,
+ which has been cleansed.
+
+ * Documentation rules has been explicitly described how to mark-up
+ literal parts and a few manual pages have been updated as examples.
+
+ * The .editorconfig file has been taught that a Makefile uses HT
+ indentation.
+
Fixes since v2.44
-----------------
files left, which has been corrected.
(merge 0068aa7946 ps/reftable-unit-test-nfs-workaround later to maint).
+ * The implementation and documentation of "object-format" option
+ exchange between the Git itself and its remote helpers did not
+ quite match, which has been corrected.
+
+ * The "--pretty=<shortHand>" option of the commands in the "git log"
+ family, defined as "[pretty] shortHand = <expansion>" should have
+ been looked up case insensitively, but was not, which has been
+ corrected.
+ (merge f999d5188b bl/pretty-shorthand-config-fix later to maint).
+
+ * "git apply" failed to extract the filename the patch applied to,
+ when the change was about an empty file created in or deleted from
+ a directory whose name ends with a SP, which has been corrected.
+ (merge 776ffd1a30 jc/apply-parse-diff-git-header-names-fix later to maint).
+
+ * Update a more recent tutorial doc.
+ (merge 95ab557b4b dg/myfirstobjectwalk-updates later to maint).
+
+ * The test script had an incomplete and ineffective attempt to avoid
+ clobbering the testing user's real crontab (and its equivalents),
+ which has been completed.
+ (merge 73cb87773b es/test-cron-safety later to maint).
+
+ * Use advice_if_enabled() API to rewrite a simple pattern to
+ call advise() after checking advice_enabled().
+ (merge 6412d01527 rj/use-adv-if-enabled later to maint).
+
+ * Another "set -u" fix for the bash prompt (in contrib/) script.
+ (merge d7805bc743 vs/complete-with-set-u-fix later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge f0e578c69c rs/use-xstrncmpz later to maint).
(merge 83e6eb7d7a ba/credential-test-clean-fix later to maint).
(merge ad538c61da jc/index-pack-fsck-levels later to maint).
(merge 67471bc704 ja/doc-formatting-fix later to maint).
(merge 86f9ce7dd6 bl/doc-config-fixes later to maint).
+ (merge 0d527842b7 az/grep-group-error-message-update later to maint).
+ (merge 7c43bdf07b rs/strbuf-expand-bad-format later to maint).
+ (merge 8b68b48d5c ds/typofix-core-config-doc later to maint).
Git-notes and inserted automatically following the three-dash
line via `git format-patch --notes`.
+[[the-topic-summary]]
+*This is EXPERIMENTAL*.
+
+When sending a topic, you can propose a one-paragraph summary that
+should appear in the "What's cooking" report when it is picked up to
+explain the topic. If you choose to do so, please write a 2-5 line
+paragraph that will fit well in our release notes (see many bulleted
+entries in the Documentation/RelNotes/* files for examples), and make
+it the first paragraph of the cover letter. For a single-patch
+series, use the space between the three-dash line and the diffstat, as
+described earlier.
+
[[attachment]]
Do not attach the patch as a MIME attachment, compressed or not.
Do not let your e-mail client send quoted-printable. Do not let
-clone.defaultRemoteName::
+`clone.defaultRemoteName`::
The name of the remote to create when cloning a repository. Defaults to
- `origin`, and can be overridden by passing the `--origin` command-line
+ `origin`.
+ifdef::git-clone[]
+ It can be overridden by passing the `--origin` command-line
+ option.
+endif::[]
+ifndef::git-clone[]
+ It can be overridden by passing the `--origin` command-line
option to linkgit:git-clone[1].
+endif::[]
-clone.rejectShallow::
+`clone.rejectShallow`::
Reject cloning a repository if it is a shallow one; this can be overridden by
- passing the `--reject-shallow` option on the command line. See linkgit:git-clone[1]
+ passing the `--reject-shallow` option on the command line.
+ifndef::git-clone[]
+ See linkgit:git-clone[1].
+endif::[]
-clone.filterSubmodules::
+`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.
`GIT_EDITOR` is not set. See linkgit:git-var[1].
core.commentChar::
+core.commentString::
Commands such as `commit` and `tag` that let you edit
- messages consider a line that begins with this ASCII character
+ messages consider a line that begins with this character
commented, and removes them after the editor returns
(default '#').
+
If set to "auto", `git-commit` would select a character that is not
the beginning character of any line in existing commit messages.
++
+Note that these two variables are aliases of each other, and in modern
+versions of Git you are free to use a string (e.g., `//` or `⁑⁕⁑`) with
+`commentChar`. Versions of Git prior to v2.45.0 will ignore
+`commentString` but will reject a value of `commentChar` that consists
+of more than a single ASCII byte. If you plan to use your config with
+older and newer versions of Git, you may want to specify both:
++
+ [core]
+ # single character for older versions
+ commentChar = "#"
+ # string for newer versions (which will override commentChar
+ # because it comes later in the file)
+ commentString = "//"
core.filesRefLockTimeout::
The length of time, in milliseconds, to retry when trying to
will not overwrite existing objects.
+
On some file system/operating system combinations, this is unreliable.
-Set this config setting to 'rename' there; However, This will remove the
+Set this config setting to 'rename' there; however, this will remove the
check that makes sure that existing object files will not get overwritten.
core.notesRef::
If set to true, enable `--full-name` option by default.
grep.fallbackToNoIndex::
- If set to true, fall back to git grep --no-index if git grep
+ If set to true, fall back to `git grep --no-index` if `git grep`
is executed outside of a git repository. Defaults to false.
:see-git-init: (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
endif::[]
-init.templateDir::
+`init.templateDir`::
Specify the directory from which templates will be copied. {see-git-init}
-init.defaultBranch::
+`init.defaultBranch`::
Allows overriding the default branch name e.g. when initializing
a new repository.
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
+ p - print the current hunk
? - print help
+
After deciding the fate for all hunks, if there is any hunk
--quoted-cr=<action>::
This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
---empty=(stop|drop|keep)::
- By default, or when the option is set to 'stop', the command
- errors out on an input e-mail message lacking a patch
- and stops in the middle of the current am session. When this
- option is set to 'drop', skip such an e-mail message instead.
- When this option is set to 'keep', create an empty commit,
- recording the contents of the e-mail message as its log.
+--empty=(drop|keep|stop)::
+ How to handle an e-mail message lacking a patch:
++
+--
+`drop`;;
+ The e-mail message will be skipped.
+`keep`;;
+ An empty commit will be created, with the contents of the e-mail
+ message as its log.
+`stop`;;
+ The command will fail, stopping in the middle of the current `am`
+ session. This is the default behavior.
+--
-m::
--message-id::
even without this option. Note also, that use of this option only
keeps commits that were initially empty (i.e. the commit recorded the
same tree as its parent). Commits which are made empty due to a
- previous commit are dropped. To force the inclusion of those commits
- use `--keep-redundant-commits`.
+ previous commit will cause the cherry-pick to fail. To force the
+ inclusion of those commits, use `--empty=keep`.
--allow-empty-message::
By default, cherry-picking a commit with an empty message will fail.
This option overrides that behavior, allowing commits with empty
messages to be cherry picked.
+--empty=(drop|keep|stop)::
+ How to handle commits being cherry-picked that are redundant with
+ changes already in the current history.
++
+--
+`drop`;;
+ The commit will be dropped.
+`keep`;;
+ The commit will be kept. Implies `--allow-empty`.
+`stop`;;
+ The cherry-pick will stop when the commit is applied, allowing
+ you to examine the commit. This is the default behavior.
+--
++
+Note that `--empty=drop` and `--empty=stop` only specify how to handle a
+commit that was not initially empty, but rather became empty due to a previous
+commit. Commits that were initially empty will still cause the cherry-pick to
+fail unless one of `--empty=keep` or `--allow-empty` are specified.
++
+
--keep-redundant-commits::
- If a commit being cherry picked duplicates a commit already in the
- current history, it will become empty. By default these
- redundant commits cause `cherry-pick` to stop so the user can
- examine the commit. This option overrides that behavior and
- creates an empty commit object. Implies `--allow-empty`.
+ Deprecated synonym for `--empty=keep`.
--strategy=<strategy>::
Use the given merge strategy. Should only be used once.
SYNOPSIS
--------
[verse]
-'git clone' [--template=<template-directory>]
- [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
- [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
- [--dissociate] [--separate-git-dir <git-dir>]
- [--depth <depth>] [--[no-]single-branch] [--no-tags]
- [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
- [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]
- [--filter=<filter> [--also-filter-submodules]] [--] <repository>
- [<directory>]
+`git clone` [++--template=++__<template-directory>__]
+ [`-l`] [`-s`] [`--no-hardlinks`] [`-q`] [`-n`] [`--bare`] [`--mirror`]
+ [`-o` _<name>_] [`-b` _<name>_] [`-u` _<upload-pack>_] [`--reference` _<repository>_]
+ [`--dissociate`] [`--separate-git-dir` _<git-dir>_]
+ [`--depth` _<depth>_] [`--`[`no-`]`single-branch`] [`--no-tags`]
+ [++--recurse-submodules++[++=++__<pathspec>__]] [`--`[`no-`]`shallow-submodules`]
+ [`--`[`no-`]`remote-submodules`] [`--jobs` _<n>_] [`--sparse`] [`--`[`no-`]`reject-shallow`]
+ [++--filter=++__<filter-spec>__] [`--also-filter-submodules`]] [`--`] _<repository>_
+ [_<directory>_]
DESCRIPTION
-----------
After the clone, a plain `git fetch` without arguments will update
all the remote-tracking branches, and a `git pull` without
arguments will in addition merge the remote master branch into the
-current master branch, if any (this is untrue when "--single-branch"
+current master branch, if any (this is untrue when `--single-branch`
is given; see below).
This default configuration is achieved by creating references to
OPTIONS
-------
--l::
---local::
+`-l`::
+`--local`::
When the repository to clone from is on a local machine,
this flag bypasses the normal "Git aware" transport
mechanism and clones the repository by making a copy of
- HEAD and everything under objects and refs directories.
+ `HEAD` and everything under objects and refs directories.
The files under `.git/objects/` directory are hardlinked
to save space when possible.
+
source repository, similar to running `cp -r src dst` while modifying
`src`.
---no-hardlinks::
+`--no-hardlinks`::
Force the cloning process from a repository on a local
filesystem to copy the files under the `.git/objects`
directory instead of using hardlinks. This may be desirable
if you are trying to make a back-up of your repository.
--s::
---shared::
+`-s`::
+`--shared`::
When the repository to clone is on the local machine,
instead of using hard links, automatically setup
`.git/objects/info/alternates` to share the objects
its source repository, you can simply run `git repack -a` to copy all
objects from the source repository into a pack in the cloned repository.
---reference[-if-able] <repository>::
+`--reference`[`-if-able`] _<repository>_::
If the reference _<repository>_ is on the local machine,
automatically setup `.git/objects/info/alternates` to
obtain objects from the reference _<repository>_. Using
*NOTE*: see the NOTE for the `--shared` option, and also the
`--dissociate` option.
---dissociate::
+`--dissociate`::
Borrow the objects from reference repositories specified
with the `--reference` options only to reduce network
transfer, and stop borrowing from them after a clone is made
same repository, and this option can be used to stop the
borrowing.
--q::
---quiet::
+`-q`::
+`--quiet`::
Operate quietly. Progress is not reported to the standard
error stream.
--v::
---verbose::
+`-v`::
+`--verbose`::
Run verbosely. Does not affect the reporting of progress status
to the standard error stream.
---progress::
+`--progress`::
Progress status is reported on the standard error stream
by default when it is attached to a terminal, unless `--quiet`
is specified. This flag forces progress status even if the
standard error stream is not directed to a terminal.
---server-option=<option>::
+++--server-option=++__<option>__::
Transmit the given string to the server when communicating using
protocol version 2. The given string must not contain a NUL or LF
character. The server's handling of server options, including
unknown ones, is server-specific.
- When multiple `--server-option=<option>` are given, they are all
+ When multiple ++--server-option=++__<option>__ are given, they are all
sent to the other side in the order listed on the command line.
--n::
---no-checkout::
+`-n`::
+`--no-checkout`::
No checkout of HEAD is performed after the clone is complete.
---[no-]reject-shallow::
+`--`[`no-`]`reject-shallow`::
Fail if the source repository is a shallow repository.
The `clone.rejectShallow` configuration variable can be used to
specify the default.
---bare::
+`--bare`::
Make a 'bare' Git repository. That is, instead of
creating _<directory>_ and placing the administrative
- files in `<directory>/.git`, make the _<directory>_
+ files in _<directory>_`/.git`, make the _<directory>_
itself the `$GIT_DIR`. This obviously implies the `--no-checkout`
because there is nowhere to check out the working tree.
Also the branch heads at the remote are copied directly
used, neither remote-tracking branches nor the related
configuration variables are created.
---sparse::
+`--sparse`::
Employ a sparse-checkout, with only files in the toplevel
directory initially being present. The
linkgit:git-sparse-checkout[1] command can be used to grow the
working directory as needed.
---filter=<filter-spec>::
+++--filter=++__<filter-spec>__::
Use the partial clone feature and request that the server sends
a subset of reachable objects according to a given object filter.
When using `--filter`, the supplied _<filter-spec>_ is used for
the partial clone filter. For example, `--filter=blob:none` will
filter out all blobs (file contents) until needed by Git. Also,
- `--filter=blob:limit=<size>` will filter out all blobs of size
+ ++--filter=blob:limit=++__<size>__ will filter out all blobs of size
at least _<size>_. For more details on filter specifications, see
the `--filter` option in linkgit:git-rev-list[1].
---also-filter-submodules::
+`--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::
+`--mirror`::
Set up a mirror of the source repository. This implies `--bare`.
Compared to `--bare`, `--mirror` not only maps local branches of the
source to local branches of the target, it maps all refs (including
that all these refs are overwritten by a `git remote update` in the
target repository.
--o <name>::
---origin <name>::
+`-o` _<name>_::
+`--origin` _<name>_::
Instead of using the remote name `origin` to keep track of the upstream
repository, use _<name>_. Overrides `clone.defaultRemoteName` from the
config.
--b <name>::
---branch <name>::
+`-b` _<name>_::
+`--branch` _<name>_::
Instead of pointing the newly created HEAD to the branch pointed
to by the cloned repository's HEAD, point to _<name>_ branch
instead. In a non-bare repository, this is the branch that will
`--branch` can also take tags and detaches the HEAD at that commit
in the resulting repository.
--u <upload-pack>::
---upload-pack <upload-pack>::
+`-u` _<upload-pack>_::
+`--upload-pack` _<upload-pack>_::
When given, and the repository to clone from is accessed
via ssh, this specifies a non-default path for the command
run on the other end.
---template=<template-directory>::
+++--template=++__<template-directory>__::
Specify the directory from which templates will be used;
(See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
--c <key>=<value>::
---config <key>=<value>::
+`-c` __<key>__++=++__<value>__::
+`--config` __<key>__++=++__<value>__::
Set a configuration variable in the newly-created repository;
this takes effect immediately after the repository is
initialized, but before the remote history is fetched or any
Due to limitations of the current implementation, some configuration
variables do not take effect until after the initial fetch and checkout.
Configuration variables known to not take effect are:
-`remote.<name>.mirror` and `remote.<name>.tagOpt`. Use the
+++remote.++__<name>__++.mirror++ and ++remote.++__<name>__++.tagOpt++. Use the
corresponding `--mirror` and `--no-tags` options instead.
---depth <depth>::
+`--depth` _<depth>_::
Create a 'shallow' clone with a history truncated to the
specified number of commits. Implies `--single-branch` unless
`--no-single-branch` is given to fetch the histories near the
tips of all branches. If you want to clone submodules shallowly,
also pass `--shallow-submodules`.
---shallow-since=<date>::
+++--shallow-since=++__<date>__::
Create a shallow clone with a history after the specified time.
---shallow-exclude=<revision>::
+++--shallow-exclude=++__<revision>__::
Create a shallow clone with a history, excluding commits
reachable from a specified remote branch or tag. This option
can be specified multiple times.
---[no-]single-branch::
+`--`[`no-`]`single-branch`::
Clone only the history leading to the tip of a single branch,
either specified by the `--branch` option or the primary
branch remote's `HEAD` points at.
branch when `--single-branch` clone was made, no remote-tracking
branch is created.
---no-tags::
+`--no-tags`::
Don't clone any tags, and set
`remote.<remote>.tagOpt=--no-tags` in the config, ensuring
that future `git pull` and `git fetch` operations won't follow
branch. This is useful e.g. to maintain minimal clones of the default
branch of some repository for search indexing.
---recurse-submodules[=<pathspec>]::
+`--recurse-submodules`[`=`{empty}__<pathspec>__]::
After the clone is created, initialize and clone submodules
within based on the provided _<pathspec>_. If no _=<pathspec>_ is
provided, all submodules are initialized and cloned.
not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`,
or `--mirror` is given)
---[no-]shallow-submodules::
+`--`[`no-`]`shallow-submodules`::
All submodules which are cloned will be shallow with a depth of 1.
---[no-]remote-submodules::
+`--`[`no-`]`remote-submodules`::
All submodules which are cloned will use the status of the submodule's
remote-tracking branch to update the submodule, rather than the
superproject's recorded SHA-1. Equivalent to passing `--remote` to
`git submodule update`.
---separate-git-dir=<git-dir>::
+`--separate-git-dir=`{empty}__<git-dir>__::
Instead of placing the cloned repository where it is supposed
to be, place the cloned repository at the specified directory,
then make a filesystem-agnostic Git symbolic link to there.
The result is Git repository can be separated from working
tree.
---ref-format=<ref-format>::
+`--ref-format=`{empty}__<ref-format>__::
Specify the given ref storage format for the repository. The valid values are:
+
include::ref-storage-format.txt[]
--j <n>::
---jobs <n>::
+`-j` _<n>_::
+`--jobs` _<n>_::
The number of submodules fetched at the same time.
Defaults to the `submodule.fetchJobs` option.
-<repository>::
+_<repository>_::
The (possibly remote) _<repository>_ to clone from. See the
<<URLS,GIT URLS>> section below for more information on specifying
repositories.
-<directory>::
+_<directory>_::
The name of a new directory to clone into. The "humanish"
part of the source repository is used if no _<directory>_ is
explicitly given (`repo` for `/path/to/repo.git` and `foo`
for `host.xz:foo/.git`). Cloning into an existing directory
is only allowed if the directory is empty.
---bundle-uri=<uri>::
+`--bundle-uri=`{empty}__<uri>__::
Before fetching from the remote, fetch a bundle from the given
_<uri>_ and unbundle the data into the local repository. The refs
in the bundle will be stored under the hidden `refs/bundle/*`
SYNOPSIS
--------
[verse]
-'git config' [<file-option>] [--type=<type>] [--fixed-value] [--show-origin] [--show-scope] [-z|--null] <name> [<value> [<value-pattern>]]
-'git config' [<file-option>] [--type=<type>] --add <name> <value>
-'git config' [<file-option>] [--type=<type>] [--fixed-value] --replace-all <name> <value> [<value-pattern>]
+'git config' [<file-option>] [--type=<type>] [--comment=<message>] [--fixed-value] [--show-origin] [--show-scope] [-z|--null] <name> [<value> [<value-pattern>]]
+'git config' [<file-option>] [--type=<type>] [--comment=<message>] --add <name> <value>
+'git config' [<file-option>] [--type=<type>] [--comment=<message>] [--fixed-value] --replace-all <name> <value> [<value-pattern>]
'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] --get <name> [<value-pattern>]
'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] --get-all <name> [<value-pattern>]
'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] [--name-only] --get-regexp <name-regex> [<value-pattern>]
values. This is the same as providing '^$' as the `value-pattern`
in `--replace-all`.
+--comment <message>::
+ Append a comment at the end of new or modified lines.
+
+ If _<message>_ begins with one or more whitespaces followed
+ by "#", it is used as-is. If it begins with "#", a space is
+ prepended before it is used. Otherwise, a string " # " (a
+ space followed by a hash followed by a space) is prepended
+ to it. And the resulting string is placed immediately after
+ the value defined for the variable. The _<message>_ must
+ not contain linefeed characters (no multi-line comments are
+ permitted).
+
--get::
Get the value for a given key (optionally filtered by a regex
matching the value). Returns error code 1 if the key was not
[-f <file>] [-e] <pattern>
[--and|--or|--not|(|)|-e <pattern>...]
[--recurse-submodules] [--parent-basename <basename>]
- [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
+ [ [--[no-]exclude-standard] [--cached | --untracked | --no-index] | <tree>...]
[--] [<pathspec>...]
DESCRIPTION
Instead of searching tracked files in the working tree, search
blobs registered in the index file.
---no-index::
- Search files in the current directory that is not managed by Git.
-
--untracked::
In addition to searching in the tracked files in the working
tree, search also in untracked files.
+--no-index::
+ Search files in the current directory that is not managed by Git,
+ or by ignoring that the current directory is managed by Git. This
+ is rather similar to running the regular `grep(1)` utility with its
+ `-r` option specified, but with some additional benefits, such as
+ using pathspec patterns to limit paths; see the 'pathspec' entry
+ in linkgit:gitglossary[7] for more information.
++
+This option cannot be used together with `--cached` or `--untracked`.
+See also `grep.fallbackToNoIndex` in 'CONFIGURATION' below.
+
--no-exclude-standard::
Also search in ignored files by not honoring the `.gitignore`
mechanism. Only useful with `--untracked`.
--recurse-submodules::
Recursively search in each submodule that is active and
checked out in the repository. When used in combination with the
- <tree> option the prefix of all submodule output will be the name of
- the parent project's <tree> object. This option has no effect
- if `--no-index` is given.
+ _<tree>_ option the prefix of all submodule output will be the name of
+ the parent project's _<tree>_ object. This option cannot be used together
+ with `--untracked`, and it has no effect if `--no-index` is specified.
-a::
--text::
Use \0 as the delimiter for pathnames in the output, and print
them verbatim. Without this option, pathnames with "unusual"
characters are quoted as explained for the configuration
- variable core.quotePath (see linkgit:git-config[1]).
+ variable `core.quotePath` (see linkgit:git-config[1]).
-o::
--only-matching::
a non-zero status.
--threads <num>::
- Number of grep worker threads to use.
- See `grep.threads` in 'CONFIGURATION' for more information.
+ Number of `grep` worker threads to use. See 'NOTES ON THREADS'
+ and `grep.threads` in 'CONFIGURATION' for more information.
-f <file>::
Read patterns from <file>, one per line.
NOTES ON THREADS
----------------
-The `--threads` option (and the grep.threads configuration) will be ignored when
+The `--threads` option (and the `grep.threads` configuration) will be ignored when
`--open-files-in-pager` is used, forcing a single-threaded execution.
When grepping the object store (with `--cached` or giving tree objects), running
-with multiple threads might perform slower than single threaded if `--textconv`
-is given and there are too many text conversions. So if you experience low
-performance in this case, it might be desirable to use `--threads=1`.
+with multiple threads might perform slower than single-threaded if `--textconv`
+is given and there are too many text conversions. Thus, if low performance is
+experienced in this case, it might be desirable to use `--threads=1`.
CONFIGURATION
-------------
SYNOPSIS
--------
[verse]
-'git init' [-q | --quiet] [--bare] [--template=<template-directory>]
- [--separate-git-dir <git-dir>] [--object-format=<format>]
- [--ref-format=<format>]
- [-b <branch-name> | --initial-branch=<branch-name>]
- [--shared[=<permissions>]] [<directory>]
+`git init` [`-q` | `--quiet`] [`--bare`] [++--template=++__<template-directory>__]
+ [`--separate-git-dir` _<git-dir>_] [++--object-format=++__<format>__]
+ [++--ref-format=++__<format>__]
+ [`-b` _<branch-name>_ | ++--initial-branch=++__<branch-name>__]
+ [++--shared++[++=++__<permissions>__]] [_<directory>_]
DESCRIPTION
OPTIONS
-------
--q::
---quiet::
+`-q`::
+`--quiet`::
Only print error and warning messages; all other output will be suppressed.
---bare::
+`--bare`::
Create a bare repository. If `GIT_DIR` environment is not set, it is set to the
current working directory.
---object-format=<format>::
+++--object-format=++__<format>__::
Specify the given object _<format>_ (hash algorithm) for the repository. The valid
values are `sha1` and (if enabled) `sha256`. `sha1` is the default.
+
include::object-format-disclaimer.txt[]
---ref-format=<format>::
+++--ref-format=++__<format>__::
Specify the given ref storage _<format>_ for the repository. The valid values are:
+
include::ref-storage-format.txt[]
---template=<template-directory>::
+++--template=++__<template-directory>__::
Specify the directory from which templates will be used. (See the "TEMPLATE
DIRECTORY" section below.)
---separate-git-dir=<git-dir>::
+++--separate-git-dir=++__<git-dir>__::
Instead of initializing the repository as a directory to either `$GIT_DIR` or
`./.git/`, create a text file there containing the path to the actual
+
If this is a reinitialization, the repository will be moved to the specified path.
--b <branch-name>::
---initial-branch=<branch-name>::
+`-b` _<branch-name>_::
+++--initial-branch=++__<branch-name>__::
Use _<branch-name>_ for the initial branch in the newly created
repository. If not specified, fall back to the default name (currently
`master`, but this is subject to change in the future; the name can be
customized via the `init.defaultBranch` configuration variable).
---shared[=(false|true|umask|group|all|world|everybody|<perm>)]::
+++--shared++[++=++(`false`|`true`|`umask`|`group`|`all`|`world`|`everybody`|_<perm>_)]::
Specify that the Git repository is to be shared amongst several users. This
allows users belonging to the same group to push into that
repository. When specified, the config variable `core.sharedRepository` is
set so that files and directories under `$GIT_DIR` are created with the
requested permissions. When not specified, Git will use permissions reported
-by `umask(2)`.
+by `umask`(2).
+
The option can have the following values, defaulting to `group` if no value
is given:
+
--
-umask::
-false::
+`umask`::
+`false`::
-Use permissions reported by umask(2). The default, when `--shared` is not
+Use permissions reported by `umask`(2). The default, when `--shared` is not
specified.
-group::
-true::
+`group`::
+`true`::
-Make the repository group-writable, (and g+sx, since the git group may not be
+Make the repository group-writable, (and `g+sx`, since the git group may not be
the primary group of all users). This is used to loosen the permissions of an
-otherwise safe umask(2) value. Note that the umask still applies to the other
+otherwise safe `umask`(2) value. Note that the umask still applies to the other
permission bits (e.g. if umask is `0022`, using `group` will not remove read
privileges from other (non-group) users). See `0xxx` for how to exactly specify
the repository permissions.
-all::
-world::
-everybody::
+`all`::
+`world`::
+`everybody`::
Same as `group`, but make the repository readable by all users.
-<perm>::
+_<perm>_::
_<perm>_ is a 3-digit octal number prefixed with `0` and each file
-will have mode _<perm>_. _<perm>_ will override users'`umask(2)`
+will have mode _<perm>_. _<perm>_ will override users' `umask`(2)
value (and not only loosen permissions as `group` and `all`
do). `0640` will create a repository which is group-readable, but
not group-writable or accessible to others. `0660` will create a repo
SYNOPSIS
--------
[verse]
-'git pack-refs' [--all] [--no-prune] [--include <pattern>] [--exclude <pattern>]
+'git pack-refs' [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]
DESCRIPTION
-----------
The command usually removes loose refs under `$GIT_DIR/refs`
hierarchy after packing them. This option tells it not to.
+--auto::
+
+Pack refs as needed depending on the current state of the ref database. The
+behavior depends on the ref format used by the repository and may change in the
+future.
++
+ - "files": No special handling for `--auto` has been implemented.
++
+ - "reftable": Tables are compacted such that they form a geometric
+ sequence. For two tables N and N+1, where N+1 is newer, this
+ maintains the property that N is at least twice as big as N+1. Only
+ tables that violate this property are compacted.
+
--include <pattern>::
Pack refs based on a `glob(7)` pattern. Repetitions of this option
+
See also INCOMPATIBLE OPTIONS below.
---empty=(drop|keep|ask)::
+--empty=(drop|keep|stop)::
How to handle commits that are not empty to start and are not
clean cherry-picks of any upstream commit, but which become
empty after rebasing (because they contain a subset of already
- upstream changes). With drop (the default), commits that
- become empty are dropped. With keep, such commits are kept.
- With ask (implied by `--interactive`), the rebase will halt when
- an empty commit is applied allowing you to choose whether to
- drop it, edit files more, or just commit the empty changes.
- Other options, like `--exec`, will use the default of drop unless
- `-i`/`--interactive` is explicitly specified.
+ upstream changes):
++
+--
+`drop`;;
+ The commit will be dropped. This is the default behavior.
+`keep`;;
+ The commit will be kept. This option is implied when `--exec` is
+ specified unless `-i`/`--interactive` is also specified.
+`stop`;;
+`ask`;;
+ The rebase will halt when the commit is applied, allowing you to
+ choose whether to drop it, edit files more, or just commit the empty
+ changes. This option is implied when `-i`/`--interactive` is
+ specified. `ask` is a deprecated synonym of `stop`.
+--
+
Note that commits which start empty are kept (unless `--no-keep-empty`
is specified), and commits which are clean cherry-picks (as determined
Similar to the apply backend, by default the merge backend drops
commits that become empty unless `-i`/`--interactive` is specified (in
which case it stops and asks the user what to do). The merge backend
-also has an `--empty=(drop|keep|ask)` option for changing the behavior
+also has an `--empty=(drop|keep|stop)` option for changing the behavior
of handling commits that become empty.
Directory rename detection
SYNOPSIS
--------
[verse]
-'git update-ref' [-m <reason>] [--no-deref] (-d <ref> [<oldvalue>] | [--create-reflog] <ref> <newvalue> [<oldvalue>] | --stdin [-z])
+'git update-ref' [-m <reason>] [--no-deref] (-d <ref> [<old-oid>] | [--create-reflog] <ref> <new-oid> [<old-oid>] | --stdin [-z])
DESCRIPTION
-----------
-Given two arguments, stores the <newvalue> in the <ref>, possibly
+Given two arguments, stores the <new-oid> in the <ref>, possibly
dereferencing the symbolic refs. E.g. `git update-ref HEAD
-<newvalue>` updates the current branch head to the new object.
+<new-oid>` updates the current branch head to the new object.
-Given three arguments, stores the <newvalue> in the <ref>,
+Given three arguments, stores the <new-oid> in the <ref>,
possibly dereferencing the symbolic refs, after verifying that
-the current value of the <ref> matches <oldvalue>.
-E.g. `git update-ref refs/heads/master <newvalue> <oldvalue>`
-updates the master branch head to <newvalue> only if its current
-value is <oldvalue>. You can specify 40 "0" or an empty string
-as <oldvalue> to make sure that the ref you are creating does
+the current value of the <ref> matches <old-oid>.
+E.g. `git update-ref refs/heads/master <new-oid> <old-oid>`
+updates the master branch head to <new-oid> only if its current
+value is <old-oid>. You can specify 40 "0" or an empty string
+as <old-oid> to make sure that the ref you are creating does
not exist.
It also allows a "ref" file to be a symbolic pointer to another
archive by creating a symlink tree).
With `-d` flag, it deletes the named <ref> after verifying it
-still contains <oldvalue>.
+still contains <old-oid>.
With `--stdin`, update-ref reads instructions from standard input and
performs all modifications together. Specify commands of the form:
- update SP <ref> SP <newvalue> [SP <oldvalue>] LF
- create SP <ref> SP <newvalue> LF
- delete SP <ref> [SP <oldvalue>] LF
- verify SP <ref> [SP <oldvalue>] LF
+ update SP <ref> SP <new-oid> [SP <old-oid>] LF
+ create SP <ref> SP <new-oid> LF
+ delete SP <ref> [SP <old-oid>] LF
+ verify SP <ref> [SP <old-oid>] LF
option SP <opt> LF
start LF
prepare LF
Alternatively, use `-z` to specify in NUL-terminated format, without
quoting:
- update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL
- create SP <ref> NUL <newvalue> NUL
- delete SP <ref> NUL [<oldvalue>] NUL
- verify SP <ref> NUL [<oldvalue>] NUL
+ update SP <ref> NUL <new-oid> NUL [<old-oid>] NUL
+ create SP <ref> NUL <new-oid> NUL
+ delete SP <ref> NUL [<old-oid>] NUL
+ verify SP <ref> NUL [<old-oid>] NUL
option SP <opt> NUL
start NUL
prepare NUL
repeated <ref> produce an error. Command meanings are:
update::
- Set <ref> to <newvalue> after verifying <oldvalue>, if given.
- Specify a zero <newvalue> to ensure the ref does not exist
- after the update and/or a zero <oldvalue> to make sure the
+ Set <ref> to <new-oid> after verifying <old-oid>, if given.
+ Specify a zero <new-oid> to ensure the ref does not exist
+ after the update and/or a zero <old-oid> to make sure the
ref does not exist before the update.
create::
- Create <ref> with <newvalue> after verifying it does not
- exist. The given <newvalue> may not be zero.
+ Create <ref> with <new-oid> after verifying it does not
+ exist. The given <new-oid> may not be zero.
delete::
- Delete <ref> after verifying it exists with <oldvalue>, if
- given. If given, <oldvalue> may not be zero.
+ Delete <ref> after verifying it exists with <old-oid>, if
+ given. If given, <old-oid> may not be zero.
verify::
- Verify <ref> against <oldvalue> but do not change it. If
- <oldvalue> is zero or missing, the ref must not exist.
+ Verify <ref> against <old-oid> but do not change it. If
+ <old-oid> is zero or missing, the ref must not exist.
option::
Modify the behavior of the next command naming a <ref>.
Abort the transaction, releasing all locks if the transaction is in
prepared state.
-If all <ref>s can be locked with matching <oldvalue>s
+If all <ref>s can be locked with matching <old-oid>s
simultaneously, all modifications are performed. Otherwise, no
modifications are performed. Note that while each individual
<ref> is updated or deleted atomically, a concurrent reader may
Where "oldsha1" is the 40 character hexadecimal value previously
stored in <ref>, "newsha1" is the 40 character hexadecimal value of
-<newvalue> and "committer" is the committer's name, email address
+<new-oid> and "committer" is the committer's name, email address
and date in the standard Git committer ident format.
Optionally with -m:
arguments, but for each ref to be updated it receives on standard
input a line of the format:
- <old-value> SP <new-value> SP <ref-name> LF
+ <old-oid> SP <new-oid> SP <ref-name> LF
-where `<old-value>` is the old object name stored in the ref,
-`<new-value>` is the new object name to be stored in the ref and
+where `<old-oid>` is the old object name stored in the ref,
+`<new-oid>` is the new object name to be stored in the ref and
`<ref-name>` is the full name of the ref.
-When creating a new ref, `<old-value>` is the all-zeroes object name.
+When creating a new ref, `<old-oid>` is the all-zeroes object name.
If the hook exits with non-zero status, none of the refs will be
updated. If the hook exits with zero, updating of individual refs can
For each reference update that was added to the transaction, the hook
receives on standard input a line of the format:
- <old-value> SP <new-value> SP <ref-name> LF
+ <old-oid> SP <new-oid> SP <ref-name> LF
-where `<old-value>` is the old object name passed into the reference
-transaction, `<new-value>` is the new object name to be stored in the
+where `<old-oid>` is the old object name passed into the reference
+transaction, `<new-oid>` is the new object name to be stored in the
ref and `<ref-name>` is the full name of the ref. When force updating
the reference regardless of its current value or when the reference is
-to be created anew, `<old-value>` is the all-zeroes object name. To
+to be created anew, `<old-oid>` is the all-zeroes object name. To
distinguish these cases, you can inspect the current value of
`<ref-name>` via `git rev-parse`.
transaction. If successful, all refs will be updated, or none will. If the
remote side does not support this capability, the push will fail.
-'option object-format' {'true'|algorithm}::
- If 'true', indicate that the caller wants hash algorithm information
+'option object-format true'::
+ Indicate that the caller wants hash algorithm information
to be passed back from the remote. This mode is used when fetching
refs.
-+
-If set to an algorithm, indicate that the caller wants to interact with
-the remote side using that algorithm.
SEE ALSO
--------
The following syntaxes may be used with them:
-- ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/path/to/repo.git/
-- git://host.xz{startsb}:port{endsb}/path/to/repo.git/
-- http{startsb}s{endsb}://host.xz{startsb}:port{endsb}/path/to/repo.git/
-- ftp{startsb}s{endsb}://host.xz{startsb}:port{endsb}/path/to/repo.git/
+- ++ssh://++{startsb}__<user>__++@++{endsb}__<host>__{startsb}++:++__<port>__{endsb}++/++__<path-to-git-repo>__
+- ++git://++__<host>__{startsb}:__<port>__{endsb}++/++__<path-to-git-repo>__
+- ++http++{startsb}++s++{endsb}++://++__<host>__{startsb}++:++__<port>__{endsb}++/++__<path-to-git-repo>__
+- ++ftp++{startsb}++s++{endsb}++://++__<host>__{startsb}++:++__<port>__{endsb}++/++__<path-to-git-repo>__
An alternative scp-like syntax may also be used with the ssh protocol:
-- {startsb}user@{endsb}host.xz:path/to/repo.git/
+- {startsb}__<user>__++@++{endsb}__<host>__++:/++__<path-to-git-repo>__
This syntax is only recognized if there are no slashes before the
first colon. This helps differentiate a local path that contains a
absolute path or `./foo:bar` to avoid being misinterpreted as an ssh
url.
-The ssh and git protocols additionally support ~username expansion:
+The ssh and git protocols additionally support ++~++__<username>__ expansion:
-- ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/
-- git://host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/
-- {startsb}user@{endsb}host.xz:/~{startsb}user{endsb}/path/to/repo.git/
+- ++ssh://++{startsb}__<user>__++@++{endsb}__<host>__{startsb}++:++__<port>__{endsb}++/~++__<user>__++/++__<path-to-git-repo>__
+- ++git://++__<host>__{startsb}++:++__<port>__{endsb}++/~++__<user>__++/++__<path-to-git-repo>__
+- {startsb}__<user>__++@++{endsb}__<host>__++:~++__<user>__++/++__<path-to-git-repo>__
For local repositories, also supported by Git natively, the following
syntaxes may be used:
-- /path/to/repo.git/
-- \file:///path/to/repo.git/
+- `/path/to/repo.git/`
+- ++file:///path/to/repo.git/++
ifndef::git-clone[]
These two syntaxes are mostly equivalent, except when cloning, when
accept a suitable bundle file. See linkgit:git-bundle[1].
When Git doesn't know how to handle a certain transport protocol, it
-attempts to use the `remote-<transport>` remote helper, if one
+attempts to use the `remote-`{empty}__<transport>__ remote helper, if one
exists. To explicitly request a remote helper, the following syntax
may be used:
-- _<transport>_::_<address>_
+- _<transport>_::__<address>__
where _<address>_ may be a path, a server and path, or an arbitrary
URL-like string recognized by the specific remote helper being
use will be rewritten into URLs that work), you can create a
configuration section of the form:
-------------
- [url "<actual-url-base>"]
- insteadOf = <other-url-base>
-------------
+[verse]
+--
+ [url "__<actual-url-base>__"]
+ insteadOf = _<other-url-base>_
+--
For example, with this:
If you want to rewrite URLs for push only, you can create a
configuration section of the form:
-------------
- [url "<actual-url-base>"]
- pushInsteadOf = <other-url-base>
-------------
+[verse]
+--
+ [url "__<actual-url-base>__"]
+ pushInsteadOf = _<other-url-base>_
+--
For example, with this:
size_t i;
strbuf_reset(&s->buf);
- strbuf_commented_addf(&s->buf, comment_line_char,
+ strbuf_commented_addf(&s->buf, comment_line_str,
_("Manual hunk edit mode -- see bottom for "
"a quick guide.\n"));
render_hunk(s, hunk, 0, 0, &s->buf);
- strbuf_commented_addf(&s->buf, comment_line_char,
+ strbuf_commented_addf(&s->buf, comment_line_str,
_("---\n"
"To remove '%c' lines, make them ' ' lines "
"(context).\n"
"To remove '%c' lines, delete them.\n"
- "Lines starting with %c will be removed.\n"),
+ "Lines starting with %s will be removed.\n"),
s->mode->is_reverse ? '+' : '-',
s->mode->is_reverse ? '-' : '+',
- comment_line_char);
- strbuf_commented_addf(&s->buf, comment_line_char, "%s",
+ comment_line_str);
+ strbuf_commented_addf(&s->buf, comment_line_str, "%s",
_(s->mode->edit_hunk_hint));
/*
* TRANSLATORS: 'it' refers to the patch mentioned in the previous
* messages.
*/
- strbuf_commented_addf(&s->buf, comment_line_char,
+ strbuf_commented_addf(&s->buf, comment_line_str,
_("If it does not apply cleanly, you will be "
"given an opportunity to\n"
"edit again. If all lines of the hunk are "
for (i = 0; i < s->buf.len; ) {
size_t next = find_next_line(&s->buf, i);
- if (s->buf.buf[i] != comment_line_char)
+ if (!starts_with(s->buf.buf + i, comment_line_str))
strbuf_add(&s->plain, s->buf.buf + i, next - i);
i = next;
}
"/ - search for a hunk matching the given regex\n"
"s - split the current hunk into smaller hunks\n"
"e - manually edit the current hunk\n"
+ "p - print the current hunk\n"
"? - print help\n");
static int patch_update_file(struct add_p_state *s,
struct file_diff *file_diff)
{
size_t hunk_index = 0;
- ssize_t i, undecided_previous, undecided_next;
+ ssize_t i, undecided_previous, undecided_next, rendered_hunk_index = -1;
struct hunk *hunk;
char ch;
struct child_process cp = CHILD_PROCESS_INIT;
strbuf_reset(&s->buf);
if (file_diff->hunk_nr) {
- render_hunk(s, hunk, 0, colored, &s->buf);
- fputs(s->buf.buf, stdout);
+ if (rendered_hunk_index != hunk_index) {
+ render_hunk(s, hunk, 0, colored, &s->buf);
+ fputs(s->buf.buf, stdout);
+ rendered_hunk_index = hunk_index;
+ }
strbuf_reset(&s->buf);
if (undecided_previous >= 0) {
permitted |= ALLOW_EDIT;
strbuf_addstr(&s->buf, ",e");
}
+ strbuf_addstr(&s->buf, ",p");
}
if (file_diff->deleted)
prompt_mode_type = PROMPT_DELETION;
hunk_index = i;
} else if (s->answer.buf[0] == 's') {
size_t splittable_into = hunk->splittable_into;
- if (!(permitted & ALLOW_SPLIT))
+ if (!(permitted & ALLOW_SPLIT)) {
err(s, _("Sorry, cannot split this hunk"));
- else if (!split_hunk(s, file_diff,
- hunk - file_diff->hunk))
+ } else if (!split_hunk(s, file_diff,
+ hunk - file_diff->hunk)) {
color_fprintf_ln(stdout, s->s.header_color,
_("Split into %d hunks."),
(int)splittable_into);
+ rendered_hunk_index = -1;
+ }
} else if (s->answer.buf[0] == 'e') {
if (!(permitted & ALLOW_EDIT))
err(s, _("Sorry, cannot edit this hunk"));
hunk->use = USE_HUNK;
goto soft_increment;
}
+ } else if (s->answer.buf[0] == 'p') {
+ rendered_hunk_index = -1;
} else {
const char *p = _(help_patch_remainder), *eol = p;
for (cp = buf.buf; *cp; cp = np) {
np = strchrnul(cp, '\n');
- fprintf(stderr, _("%shint: %.*s%s\n"),
+ fprintf(stderr, _("%shint:%s%.*s%s\n"),
advise_get_color(ADVICE_COLOR_HINT),
+ (np == cp) ? "" : " ",
(int)(np - cp), cp,
advise_get_color(ADVICE_COLOR_RESET));
if (*np)
return NULL; /* no postimage name */
second = skip_tree_prefix(p_value, name + len + 1,
line_len - (len + 1));
+ /*
+ * If we are at the SP at the end of a directory,
+ * skip_tree_prefix() may return NULL as that makes
+ * it appears as if we have an absolute path.
+ * Keep going to find another SP.
+ */
if (!second)
- return NULL;
+ continue;
+
/*
* Does len bytes starting at "name" and "second"
* (that are separated by one HT or SP we just
strbuf_strip_suffix(&name, "/");
warning(_("adding embedded git repository: %s"), name.buf);
- if (!adviced_on_embedded_repo &&
- advice_enabled(ADVICE_ADD_EMBEDDED_REPO)) {
- advise(embedded_advice, name.buf, name.buf);
+ if (!adviced_on_embedded_repo) {
+ advise_if_enabled(ADVICE_ADD_EMBEDDED_REPO,
+ embedded_advice, name.buf, name.buf);
adviced_on_embedded_repo = 1;
}
fprintf(stderr, _(ignore_error));
for (i = 0; i < dir->ignored_nr; i++)
fprintf(stderr, "%s\n", dir->ignored[i]->name);
- if (advice_enabled(ADVICE_ADD_IGNORED_FILE))
- advise(_("Use -f if you really want to add them.\n"
- "Turn this message off by running\n"
- "\"git config advice.addIgnoredFile false\""));
+ advise_if_enabled(ADVICE_ADD_IGNORED_FILE,
+ _("Use -f if you really want to add them."));
exit_status = 1;
}
if (require_pathspec && pathspec.nr == 0) {
fprintf(stderr, _("Nothing specified, nothing added.\n"));
- if (advice_enabled(ADVICE_ADD_EMPTY_PATHSPEC))
- advise( _("Maybe you wanted to say 'git add .'?\n"
- "Turn this message off by running\n"
- "\"git config advice.addEmptyPathspec false\""));
+ advise_if_enabled(ADVICE_ADD_EMPTY_PATHSPEC,
+ _("Maybe you wanted to say 'git add .'?"));
return 0;
}
strbuf_addstr(&msg, "\n\n");
strbuf_addbuf(&msg, &mi.log_message);
- strbuf_stripspace(&msg, '\0');
+ strbuf_stripspace(&msg, NULL);
assert(!state->author_name);
state->author_name = strbuf_detach(&author_name, NULL);
exists = !read_branch_desc(&buf, branch_name);
if (!buf.len || buf.buf[buf.len-1] != '\n')
strbuf_addch(&buf, '\n');
- strbuf_commented_addf(&buf, comment_line_char,
+ strbuf_commented_addf(&buf, comment_line_str,
_("Please edit the description for the branch\n"
" %s\n"
- "Lines starting with '%c' will be stripped.\n"),
- branch_name, comment_line_char);
+ "Lines starting with '%s' will be stripped.\n"),
+ branch_name, comment_line_str);
write_file_buf(edit_description(), buf.buf, buf.len);
strbuf_reset(&buf);
if (launch_editor(edit_description(), &buf, NULL)) {
strbuf_release(&buf);
return -1;
}
- strbuf_stripspace(&buf, comment_line_char);
+ strbuf_stripspace(&buf, comment_line_str);
strbuf_addf(&name, "branch.%s.description", branch_name);
if (buf.len || exists)
return alen == slen && !memcmp(atom, s, alen);
}
-static void expand_atom(struct strbuf *sb, const char *atom, int len,
- struct expand_data *data)
+static int expand_atom(struct strbuf *sb, const char *atom, int len,
+ struct expand_data *data)
{
if (is_atom("objectname", atom, len)) {
if (!data->mark_query)
strbuf_addstr(sb,
oid_to_hex(&data->delta_base_oid));
} else
- die("unknown format element: %.*s", len, atom);
+ return 0;
+ return 1;
}
static void expand_format(struct strbuf *sb, const char *start,
if (skip_prefix(start, "%", &start) || *start != '(')
strbuf_addch(sb, '%');
- else if (!(end = strchr(start + 1, ')')))
- die("format element '%s' does not end in ')'", start);
- else {
- expand_atom(sb, start + 1, end - start - 1, data);
+ else if ((end = strchr(start + 1, ')')) &&
+ expand_atom(sb, start + 1, end - start - 1, data))
start = end + 1;
- }
+ else
+ strbuf_expand_bad_format(start, "cat-file");
}
}
remove_branch_state(the_repository, !opts->quiet);
strbuf_release(&msg);
if (!opts->quiet &&
- (new_branch_info->path || (!opts->force_detach && !strcmp(new_branch_info->name, "HEAD"))))
+ !opts->force_detach &&
+ (new_branch_info->path || !strcmp(new_branch_info->name, "HEAD")))
report_tracking(new_branch_info);
}
char *candidate;
const char *p;
- comment_line_char = candidates[0];
- if (!memchr(sb->buf, comment_line_char, sb->len))
+ if (!memchr(sb->buf, candidates[0], sb->len)) {
+ comment_line_str = xstrfmt("%c", candidates[0]);
return;
+ }
p = sb->buf;
candidate = strchr(candidates, *p);
if (!*p)
die(_("unable to select a comment character that is not used\n"
"in the current commit message"));
- comment_line_char = *p;
+ comment_line_str = xstrfmt("%c", *p);
}
static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,
s->hints = 0;
if (clean_message_contents)
- strbuf_stripspace(&sb, '\0');
+ strbuf_stripspace(&sb, NULL);
if (signoff)
append_signoff(&sb, ignored_log_message_bytes(sb.buf, sb.len), 0);
struct ident_split ci, ai;
const char *hint_cleanup_all = allow_empty_message ?
_("Please enter the commit message for your changes."
- " Lines starting\nwith '%c' will be ignored.\n") :
+ " Lines starting\nwith '%s' will be ignored.\n") :
_("Please enter the commit message for your changes."
- " Lines starting\nwith '%c' will be ignored, and an empty"
+ " Lines starting\nwith '%s' will be ignored, and an empty"
" message aborts the commit.\n");
const char *hint_cleanup_space = allow_empty_message ?
_("Please enter the commit message for your changes."
" Lines starting\n"
- "with '%c' will be kept; you may remove them"
+ "with '%s' will be kept; you may remove them"
" yourself if you want to.\n") :
_("Please enter the commit message for your changes."
" Lines starting\n"
- "with '%c' will be kept; you may remove them"
+ "with '%s' will be kept; you may remove them"
" yourself if you want to.\n"
"An empty message aborts the commit.\n");
if (whence != FROM_COMMIT) {
fprintf(s->fp, "\n");
if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL)
- status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_all, comment_line_char);
+ status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_all, comment_line_str);
else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
if (whence == FROM_COMMIT)
wt_status_add_cut_line(s);
} else /* COMMIT_MSG_CLEANUP_SPACE, that is. */
- status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_space, comment_line_char);
+ status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_space, comment_line_str);
/*
* These should never fail because they come from our own
static int show_origin;
static int show_scope;
static int fixed_value;
+static const char *comment;
#define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1)
OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")),
OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")),
OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")),
+ OPT_STRING(0, "comment", &comment, N_("value"), N_("human-readable comment string (# will be prepended as needed)")),
OPT_END(),
};
usage_builtin_config();
}
+ if (comment &&
+ !(actions & (ACTION_ADD|ACTION_SET|ACTION_SET_ALL|ACTION_REPLACE_ALL))) {
+ error(_("--comment is only applicable to add/set/replace operations"));
+ usage_builtin_config();
+ }
+
/* check usage of --fixed-value */
if (fixed_value) {
int allowed_usage = 0;
flags |= CONFIG_FLAGS_FIXED_VALUE;
}
+ comment = git_config_prepare_comment_string(comment);
+
if (actions & PAGING_ACTIONS)
setup_auto_pager("config", 1);
check_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1], &default_kvi);
- ret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);
+ ret = git_config_set_in_file_gently(given_config_source.file, argv[0], comment, value);
if (ret == CONFIG_NOTHING_SET)
error(_("cannot overwrite multiple values with a single value\n"
" Use a regexp, --add or --replace-all to change %s."), argv[0]);
value = normalize_value(argv[0], argv[1], &default_kvi);
ret = git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value, argv[2],
- flags);
+ comment, flags);
}
else if (actions == ACTION_ADD) {
check_write();
ret = git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value,
CONFIG_REGEX_NONE,
- flags);
+ comment, flags);
}
else if (actions == ACTION_REPLACE_ALL) {
check_write();
value = normalize_value(argv[0], argv[1], &default_kvi);
ret = git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value, argv[2],
- flags | CONFIG_FLAGS_MULTI_REPLACE);
+ comment, flags | CONFIG_FLAGS_MULTI_REPLACE);
}
else if (actions == ACTION_GET) {
check_argc(argc, 1, 2);
if (argc == 2)
return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], NULL, argv[1],
- flags);
+ NULL, flags);
else
return git_config_set_in_file_gently(given_config_source.file,
- argv[0], NULL);
+ argv[0], NULL, NULL);
}
else if (actions == ACTION_UNSET_ALL) {
check_write();
check_argc(argc, 1, 2);
return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], NULL, argv[1],
- flags | CONFIG_FLAGS_MULTI_REPLACE);
+ NULL, flags | CONFIG_FLAGS_MULTI_REPLACE);
}
else if (actions == ACTION_RENAME_SECTION) {
check_write();
git_config(git_default_config, NULL);
}
-struct maintenance_run_opts;
+enum schedule_priority {
+ SCHEDULE_NONE = 0,
+ SCHEDULE_WEEKLY = 1,
+ SCHEDULE_DAILY = 2,
+ SCHEDULE_HOURLY = 3,
+};
+
+static enum schedule_priority parse_schedule(const char *value)
+{
+ if (!value)
+ return SCHEDULE_NONE;
+ if (!strcasecmp(value, "hourly"))
+ return SCHEDULE_HOURLY;
+ if (!strcasecmp(value, "daily"))
+ return SCHEDULE_DAILY;
+ if (!strcasecmp(value, "weekly"))
+ return SCHEDULE_WEEKLY;
+ return SCHEDULE_NONE;
+}
+
+struct maintenance_run_opts {
+ int auto_flag;
+ int quiet;
+ enum schedule_priority schedule;
+};
+
+static int pack_refs_condition(void)
+{
+ /*
+ * The auto-repacking logic for refs is handled by the ref backends and
+ * exposed via `git pack-refs --auto`. We thus always return truish
+ * here and let the backend decide for us.
+ */
+ return 1;
+}
+
static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts)
{
struct child_process cmd = CHILD_PROCESS_INIT;
cmd.git_cmd = 1;
strvec_pushl(&cmd.args, "pack-refs", "--all", "--prune", NULL);
+ if (opts->auto_flag)
+ strvec_push(&cmd.args, "--auto");
+
return run_command(&cmd);
}
return ret;
}
-static void gc_before_repack(void)
+static void gc_before_repack(struct maintenance_run_opts *opts)
{
/*
* We may be called twice, as both the pre- and
if (done++)
return;
- if (pack_refs && maintenance_task_pack_refs(NULL))
+ if (pack_refs && maintenance_task_pack_refs(opts))
die(FAILED_RUN, "pack-refs");
if (prune_reflogs) {
int cmd_gc(int argc, const char **argv, const char *prefix)
{
int aggressive = 0;
- int auto_gc = 0;
int quiet = 0;
int force = 0;
const char *name;
int keep_largest_pack = -1;
timestamp_t dummy;
struct child_process rerere_cmd = CHILD_PROCESS_INIT;
+ struct maintenance_run_opts opts = {0};
struct option builtin_gc_options[] = {
OPT__QUIET(&quiet, N_("suppress progress reporting")),
OPT_MAGNITUDE(0, "max-cruft-size", &max_cruft_size,
N_("with --cruft, limit the size of new cruft packs")),
OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
- OPT_BOOL_F(0, "auto", &auto_gc, N_("enable auto-gc mode"),
+ OPT_BOOL_F(0, "auto", &opts.auto_flag, N_("enable auto-gc mode"),
PARSE_OPT_NOCOMPLETE),
OPT_BOOL_F(0, "force", &force,
N_("force running gc even if there may be another gc running"),
if (quiet)
strvec_push(&repack, "-q");
- if (auto_gc) {
+ if (opts.auto_flag) {
/*
* Auto-gc should be least intrusive as possible.
*/
if (lock_repo_for_gc(force, &pid))
return 0;
- gc_before_repack(); /* dies on failure */
+ gc_before_repack(&opts); /* dies on failure */
delete_tempfile(&pidfile);
/*
name = lock_repo_for_gc(force, &pid);
if (name) {
- if (auto_gc)
+ if (opts.auto_flag)
return 0; /* be quiet on --auto */
die(_("gc is already running on machine '%s' pid %"PRIuMAX" (use --force if not)"),
name, (uintmax_t)pid);
atexit(process_log_file_at_exit);
}
- gc_before_repack();
+ gc_before_repack(&opts);
if (!repository_format_precious_objects) {
struct child_process repack_cmd = CHILD_PROCESS_INIT;
!quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
NULL);
- if (auto_gc && too_many_loose_objects())
+ if (opts.auto_flag && too_many_loose_objects())
warning(_("There are too many unreachable loose objects; "
"run 'git prune' to remove them."));
NULL
};
-enum schedule_priority {
- SCHEDULE_NONE = 0,
- SCHEDULE_WEEKLY = 1,
- SCHEDULE_DAILY = 2,
- SCHEDULE_HOURLY = 3,
-};
-
-static enum schedule_priority parse_schedule(const char *value)
-{
- if (!value)
- return SCHEDULE_NONE;
- if (!strcasecmp(value, "hourly"))
- return SCHEDULE_HOURLY;
- if (!strcasecmp(value, "daily"))
- return SCHEDULE_DAILY;
- if (!strcasecmp(value, "weekly"))
- return SCHEDULE_WEEKLY;
- return SCHEDULE_NONE;
-}
-
static int maintenance_opt_schedule(const struct option *opt, const char *arg,
int unset)
{
return 0;
}
-struct maintenance_run_opts {
- int auto_flag;
- int quiet;
- enum schedule_priority schedule;
-};
-
/* Remember to update object flag allocation in object.h */
#define SEEN (1u<<0)
[TASK_PACK_REFS] = {
"pack-refs",
maintenance_task_pack_refs,
- NULL,
+ pack_refs_condition,
},
};
die(_("$HOME not set"));
rc = git_config_set_multivar_in_file_gently(
config_file, "maintenance.repo", maintpath,
- CONFIG_REGEX_NONE, 0);
+ CONFIG_REGEX_NONE, NULL, 0);
free(global_config_file);
if (rc)
if (!config_file)
die(_("$HOME not set"));
rc = git_config_set_multivar_in_file_gently(
- config_file, key, NULL, maintpath,
+ config_file, key, NULL, maintpath, NULL,
CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE);
free(global_config_file);
struct strbuf sb = STRBUF_INIT;
while (strbuf_expand_step(&sb, &format)) {
- const char *end;
size_t len;
struct stat st;
strbuf_addch(&sb, '%');
else if ((len = strbuf_expand_literal(&sb, format)))
format += len;
- else if (*format != '(')
- die(_("bad ls-files format: element '%s' "
- "does not start with '('"), format);
- else if (!(end = strchr(format + 1, ')')))
- die(_("bad ls-files format: element '%s' "
- "does not end in ')'"), format);
else if (skip_prefix(format, "(objectmode)", &format))
strbuf_addf(&sb, "%06o", ce->ce_mode);
else if (skip_prefix(format, "(objectname)", &format))
else if (skip_prefix(format, "(path)", &format))
write_name_to_buf(&sb, fullname);
else
- die(_("bad ls-files format: %%%.*s"),
- (int)(end - format + 1), format);
+ strbuf_expand_bad_format(format, "ls-files");
}
strbuf_addch(&sb, line_terminator);
fwrite(sb.buf, sb.len, 1, stdout);
return 0;
while (strbuf_expand_step(&sb, &format)) {
- const char *end;
size_t len;
if (skip_prefix(format, "%", &format))
strbuf_addch(&sb, '%');
else if ((len = strbuf_expand_literal(&sb, format)))
format += len;
- else if (*format != '(')
- die(_("bad ls-tree format: element '%s' "
- "does not start with '('"), format);
- else if (!(end = strchr(format + 1, ')')))
- die(_("bad ls-tree format: element '%s' "
- "does not end in ')'"), format);
else if (skip_prefix(format, "(objectmode)", &format))
strbuf_addf(&sb, "%06o", mode);
else if (skip_prefix(format, "(objecttype)", &format))
strbuf_setlen(base, baselen);
strbuf_release(&sbuf);
} else
- die(_("bad ls-tree format: %%%.*s"),
- (int)(end - format + 1), format);
+ strbuf_expand_bad_format(format, "ls-tree");
}
strbuf_addch(&sb, options->null_termination ? '\0' : '\n');
fwrite(sb.buf, sb.len, 1, stdout);
PARSE_OPT_NONEG),
OPT_STRING(0, "merge-base",
&merge_base,
- N_("commit"),
+ N_("tree-ish"),
N_("specify a merge-base for the merge")),
OPT_STRVEC('X', "strategy-option", &xopts, N_("option=value"),
N_("option for selected merge strategy")),
N_("An empty message aborts the commit.\n");
static const char no_scissors_editor_comment[] =
-N_("Lines starting with '%c' will be ignored, and an empty message aborts\n"
+N_("Lines starting with '%s' will be ignored, and an empty message aborts\n"
"the commit.\n");
static void write_merge_heads(struct commit_list *);
strbuf_addch(&msg, '\n');
if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
wt_status_append_cut_line(&msg);
- strbuf_commented_addf(&msg, comment_line_char, "\n");
+ strbuf_commented_addf(&msg, comment_line_str, "\n");
}
- strbuf_commented_addf(&msg, comment_line_char,
+ strbuf_commented_addf(&msg, comment_line_str,
_(merge_editor_comment));
if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
- strbuf_commented_addf(&msg, comment_line_char,
+ strbuf_commented_addf(&msg, comment_line_str,
_(scissors_editor_comment));
else
- strbuf_commented_addf(&msg, comment_line_char,
- _(no_scissors_editor_comment), comment_line_char);
+ strbuf_commented_addf(&msg, comment_line_str,
+ _(no_scissors_editor_comment), comment_line_str);
}
if (signoff)
append_signoff(&msg, ignored_log_message_bytes(msg.buf, msg.len), 0);
if (strbuf_read(&buf, show.out, 0) < 0)
die_errno(_("could not read 'show' output"));
- strbuf_add_commented_lines(&cbuf, buf.buf, buf.len, comment_line_char);
+ strbuf_add_commented_lines(&cbuf, buf.buf, buf.len, comment_line_str);
write_or_die(fd, cbuf.buf, cbuf.len);
strbuf_release(&cbuf);
copy_obj_to_fd(fd, old_note);
strbuf_addch(&buf, '\n');
- strbuf_add_commented_lines(&buf, "\n", strlen("\n"), comment_line_char);
+ strbuf_add_commented_lines(&buf, "\n", strlen("\n"), comment_line_str);
strbuf_add_commented_lines(&buf, _(note_template), strlen(_(note_template)),
- comment_line_char);
- strbuf_add_commented_lines(&buf, "\n", strlen("\n"), comment_line_char);
+ comment_line_str);
+ strbuf_add_commented_lines(&buf, "\n", strlen("\n"), comment_line_str);
write_or_die(fd, buf.buf, buf.len);
write_commented_object(fd, object);
die(_("please supply the note contents using either -m or -F option"));
}
if (d->stripspace)
- strbuf_stripspace(&d->buf, comment_line_char);
+ strbuf_stripspace(&d->buf, comment_line_str);
}
}
if ((d->stripspace == UNSPECIFIED &&
d->messages[i]->stripspace == STRIPSPACE) ||
d->stripspace == STRIPSPACE)
- strbuf_stripspace(&d->buf, 0);
+ strbuf_stripspace(&d->buf, NULL);
strbuf_reset(&msg);
}
strbuf_release(&msg);
#include "revision.h"
static char const * const pack_refs_usage[] = {
- N_("git pack-refs [--all] [--no-prune] [--include <pattern>] [--exclude <pattern>]"),
+ N_("git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]"),
NULL
};
int cmd_pack_refs(int argc, const char **argv, const char *prefix)
{
- unsigned int flags = PACK_REFS_PRUNE;
- static struct ref_exclusions excludes = REF_EXCLUSIONS_INIT;
- static struct string_list included_refs = STRING_LIST_INIT_NODUP;
- struct pack_refs_opts pack_refs_opts = { .exclusions = &excludes,
- .includes = &included_refs,
- .flags = flags };
- static struct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;
+ struct ref_exclusions excludes = REF_EXCLUSIONS_INIT;
+ struct string_list included_refs = STRING_LIST_INIT_NODUP;
+ struct pack_refs_opts pack_refs_opts = {
+ .exclusions = &excludes,
+ .includes = &included_refs,
+ .flags = PACK_REFS_PRUNE,
+ };
+ struct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;
struct string_list_item *item;
+ int pack_all = 0;
+ int ret;
struct option opts[] = {
- OPT_BIT(0, "all", &pack_refs_opts.flags, N_("pack everything"), PACK_REFS_ALL),
+ OPT_BOOL(0, "all", &pack_all, N_("pack everything")),
OPT_BIT(0, "prune", &pack_refs_opts.flags, N_("prune loose refs (default)"), PACK_REFS_PRUNE),
+ OPT_BIT(0, "auto", &pack_refs_opts.flags, N_("auto-pack refs as needed"), PACK_REFS_AUTO),
OPT_STRING_LIST(0, "include", pack_refs_opts.includes, N_("pattern"),
N_("references to include")),
OPT_STRING_LIST(0, "exclude", &option_excluded_refs, N_("pattern"),
for_each_string_list_item(item, &option_excluded_refs)
add_ref_exclusion(pack_refs_opts.exclusions, item->string);
- if (pack_refs_opts.flags & PACK_REFS_ALL)
+ if (pack_all)
string_list_append(pack_refs_opts.includes, "*");
if (!pack_refs_opts.includes->nr)
string_list_append(pack_refs_opts.includes, "refs/tags/*");
- return refs_pack_refs(get_main_ref_store(the_repository), &pack_refs_opts);
+ ret = refs_pack_refs(get_main_ref_store(the_repository), &pack_refs_opts);
+
+ clear_ref_exclusions(&excludes);
+ string_list_clear(&included_refs, 0);
+ string_list_clear(&option_excluded_refs, 0);
+ return ret;
}
EMPTY_UNSPECIFIED = -1,
EMPTY_DROP,
EMPTY_KEEP,
- EMPTY_ASK
+ EMPTY_STOP
};
enum action {
if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
return error_errno(_("could not read '%s'."), todo_file);
- strbuf_stripspace(&todo_list.buf, comment_line_char);
+ strbuf_stripspace(&todo_list.buf, comment_line_str);
res = edit_todo_list(the_repository, &todo_list, &new_todo, NULL, NULL, flags);
if (!res && todo_list_write_to_file(the_repository, &new_todo, todo_file,
NULL, NULL, -1, flags & ~(TODO_LIST_SHORTEN_IDS)))
return EMPTY_DROP;
else if (!strcasecmp(value, "keep"))
return EMPTY_KEEP;
- else if (!strcasecmp(value, "ask"))
- return EMPTY_ASK;
+ else if (!strcasecmp(value, "stop"))
+ return EMPTY_STOP;
+ else if (!strcasecmp(value, "ask")) {
+ warning(_("--empty=ask is deprecated; use '--empty=stop' instead."));
+ return EMPTY_STOP;
+ }
- die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
+ die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"stop\"."), value);
}
static int parse_opt_keep_empty(const struct option *opt, const char *arg,
"instead of ignoring them"),
1, PARSE_OPT_HIDDEN),
OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
- OPT_CALLBACK_F(0, "empty", &options, "(drop|keep|ask)",
+ OPT_CALLBACK_F(0, "empty", &options, "(drop|keep|stop)",
N_("how to handle commits that become empty"),
PARSE_OPT_NONEG, parse_opt_empty),
OPT_CALLBACK_F('k', "keep-empty", &options, NULL,
if (options.empty == EMPTY_UNSPECIFIED) {
if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
- options.empty = EMPTY_ASK;
+ options.empty = EMPTY_STOP;
else if (options.exec.nr > 0)
options.empty = EMPTY_KEEP;
else
return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage;
}
+enum empty_action {
+ EMPTY_COMMIT_UNSPECIFIED = -1,
+ STOP_ON_EMPTY_COMMIT, /* output errors and stop in the middle of a cherry-pick */
+ DROP_EMPTY_COMMIT, /* skip with a notice message */
+ KEEP_EMPTY_COMMIT, /* keep recording as empty commits */
+};
+
+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
+{
+ int *opt_value = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+
+ if (!strcmp(arg, "stop"))
+ *opt_value = STOP_ON_EMPTY_COMMIT;
+ else if (!strcmp(arg, "drop"))
+ *opt_value = DROP_EMPTY_COMMIT;
+ else if (!strcmp(arg, "keep"))
+ *opt_value = KEEP_EMPTY_COMMIT;
+ else
+ return error(_("invalid value for '%s': '%s'"), "--empty", arg);
+
+ return 0;
+}
+
static int option_parse_m(const struct option *opt,
const char *arg, int unset)
{
const char * const * usage_str = revert_or_cherry_pick_usage(opts);
const char *me = action_name(opts);
const char *cleanup_arg = NULL;
+ enum empty_action empty_opt = EMPTY_COMMIT_UNSPECIFIED;
int cmd = 0;
struct option base_options[] = {
OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'),
OPT_BOOL(0, "ff", &opts->allow_ff, N_("allow fast-forward")),
OPT_BOOL(0, "allow-empty", &opts->allow_empty, N_("preserve initially empty commits")),
OPT_BOOL(0, "allow-empty-message", &opts->allow_empty_message, N_("allow commits with empty messages")),
- OPT_BOOL(0, "keep-redundant-commits", &opts->keep_redundant_commits, N_("keep redundant, empty commits")),
+ OPT_BOOL(0, "keep-redundant-commits", &opts->keep_redundant_commits, N_("deprecated: use --empty=keep instead")),
+ OPT_CALLBACK_F(0, "empty", &empty_opt, "(stop|drop|keep)",
+ N_("how to handle commits that become empty"),
+ PARSE_OPT_NONEG, parse_opt_empty),
OPT_END(),
};
options = parse_options_concat(options, cp_extra);
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
+ if (opts->action == REPLAY_PICK) {
+ opts->drop_redundant_commits = (empty_opt == DROP_EMPTY_COMMIT);
+ opts->keep_redundant_commits = opts->keep_redundant_commits || (empty_opt == KEEP_EMPTY_COMMIT);
+ }
+
/* implies allow_empty */
if (opts->keep_redundant_commits)
opts->allow_empty = 1;
"--ff", opts->allow_ff,
"--rerere-autoupdate", opts->allow_rerere_auto == RERERE_AUTOUPDATE,
"--no-rerere-autoupdate", opts->allow_rerere_auto == RERERE_NOAUTOUPDATE,
+ "--keep-redundant-commits", opts->keep_redundant_commits,
+ "--empty", empty_opt != EMPTY_COMMIT_UNSPECIFIED,
NULL);
}
size_t len;
msg = strbuf_detach(buf, &len);
- strbuf_add_commented_lines(buf, msg, len, comment_line_char);
+ strbuf_add_commented_lines(buf, msg, len, comment_line_str);
free(msg);
}
if (mode == STRIP_DEFAULT || mode == STRIP_COMMENTS)
strbuf_stripspace(&buf,
- mode == STRIP_COMMENTS ? comment_line_char : '\0');
+ mode == STRIP_COMMENTS ? comment_line_str : NULL);
else
comment_lines(&buf);
submodule_to_gitdir(&sb, path);
strbuf_addstr(&sb, "/config");
- if (git_config_set_in_file_gently(sb.buf, remote_key, sub_origin_url))
+ if (git_config_set_in_file_gently(sb.buf, remote_key, NULL, sub_origin_url))
die(_("failed to update remote for submodule '%s'"),
path);
static const char tag_template[] =
N_("\nWrite a message for tag:\n %s\n"
- "Lines starting with '%c' will be ignored.\n");
+ "Lines starting with '%s' will be ignored.\n");
static const char tag_template_nocleanup[] =
N_("\nWrite a message for tag:\n %s\n"
- "Lines starting with '%c' will be kept; you may remove them"
+ "Lines starting with '%s' will be kept; you may remove them"
" yourself if you want to.\n");
static int git_tag_config(const char *var, const char *value,
struct strbuf buf = STRBUF_INIT;
strbuf_addch(&buf, '\n');
if (opt->cleanup_mode == CLEANUP_ALL)
- strbuf_commented_addf(&buf, comment_line_char,
- _(tag_template), tag, comment_line_char);
+ strbuf_commented_addf(&buf, comment_line_str,
+ _(tag_template), tag, comment_line_str);
else
- strbuf_commented_addf(&buf, comment_line_char,
- _(tag_template_nocleanup), tag, comment_line_char);
+ strbuf_commented_addf(&buf, comment_line_str,
+ _(tag_template_nocleanup), tag, comment_line_str);
write_or_die(fd, buf.buf, buf.len);
strbuf_release(&buf);
}
if (opt->cleanup_mode != CLEANUP_NONE)
strbuf_stripspace(buf,
- opt->cleanup_mode == CLEANUP_ALL ? comment_line_char : '\0');
+ opt->cleanup_mode == CLEANUP_ALL ? comment_line_str : NULL);
if (!opt->message_given && !buf->len)
die(_("no tag message?"));
#include "repository.h"
static const char * const git_update_ref_usage[] = {
- N_("git update-ref [<options>] -d <refname> [<old-val>]"),
- N_("git update-ref [<options>] <refname> <new-val> [<old-val>]"),
+ N_("git update-ref [<options>] -d <refname> [<old-oid>]"),
+ N_("git update-ref [<options>] <refname> <new-oid> [<old-oid>]"),
N_("git update-ref [<options>] --stdin [-z]"),
NULL
};
}
/*
- * The value being parsed is <oldvalue> (as opposed to <newvalue>; the
+ * The value being parsed is <old-oid> (as opposed to <new-oid>; the
* difference affects which error messages are generated):
*/
#define PARSE_SHA1_OLD 0x01
/*
* For backwards compatibility, accept an empty string for update's
- * <newvalue> in binary mode to be equivalent to specifying zeros.
+ * <new-oid> in binary mode to be equivalent to specifying zeros.
*/
#define PARSE_SHA1_ALLOW_EMPTY 0x02
goto invalid;
} else if (flags & PARSE_SHA1_ALLOW_EMPTY) {
/* With -z, treat an empty value as all zeros: */
- warning("%s %s: missing <newvalue>, treating as zero",
+ warning("%s %s: missing <new-oid>, treating as zero",
command, refname);
oidclr(oid);
} else {
invalid:
die(flags & PARSE_SHA1_OLD ?
- "%s %s: invalid <oldvalue>: %s" :
- "%s %s: invalid <newvalue>: %s",
+ "%s %s: invalid <old-oid>: %s" :
+ "%s %s: invalid <new-oid>: %s",
command, refname, arg.buf);
eof:
die(flags & PARSE_SHA1_OLD ?
- "%s %s: unexpected end of input when reading <oldvalue>" :
- "%s %s: unexpected end of input when reading <newvalue>",
+ "%s %s: unexpected end of input when reading <old-oid>" :
+ "%s %s: unexpected end of input when reading <new-oid>",
command, refname);
}
if (parse_next_oid(&next, end, &new_oid, "update", refname,
PARSE_SHA1_ALLOW_EMPTY))
- die("update %s: missing <newvalue>", refname);
+ die("update %s: missing <new-oid>", refname);
have_old = !parse_next_oid(&next, end, &old_oid, "update", refname,
PARSE_SHA1_OLD);
die("create: missing <ref>");
if (parse_next_oid(&next, end, &new_oid, "create", refname, 0))
- die("create %s: missing <newvalue>", refname);
+ die("create %s: missing <new-oid>", refname);
if (is_null_oid(&new_oid))
- die("create %s: zero <newvalue>", refname);
+ die("create %s: zero <new-oid>", refname);
if (*next != line_termination)
die("create %s: extra input: %s", refname, next);
have_old = 0;
} else {
if (is_null_oid(&old_oid))
- die("delete %s: zero <oldvalue>", refname);
+ die("delete %s: zero <old-oid>", refname);
have_old = 1;
}
if (!git_configset_get_bool(&cs, "core.bare", &bare) &&
bare &&
git_config_set_multivar_in_file_gently(
- to_file, "core.bare", NULL, "true", 0))
+ to_file, "core.bare", NULL, "true", NULL, 0))
error(_("failed to unset '%s' in '%s'"),
"core.bare", to_file);
if (!git_configset_get(&cs, "core.worktree") &&
git_config_set_in_file_gently(to_file,
- "core.worktree", NULL))
+ "core.worktree", NULL, NULL))
error(_("failed to unset '%s' in '%s'"),
"core.worktree", to_file);
strbuf_add_real_path(&path, get_worktree_git_dir(NULL));
strbuf_addstr(&path, "/HEAD");
strbuf_read_file(&contents, path.buf, 64);
- strbuf_stripspace(&contents, 0);
+ strbuf_stripspace(&contents, NULL);
strbuf_strip_suffix(&contents, "\n");
warning(_("HEAD points to an invalid (or orphaned) reference.\n"
else
next_line++;
- if (buf[bol] == comment_line_char || buf[bol] == '\n') {
+ if (starts_with_mem(buf + bol, cutoff - bol, comment_line_str) ||
+ buf[bol] == '\n') {
/* is this the first of the run of comments? */
if (!boc)
boc = bol;
if (!strcmp(var, "core.editor"))
return git_config_string(&editor_program, var, value);
- if (!strcmp(var, "core.commentchar")) {
+ if (!strcmp(var, "core.commentchar") ||
+ !strcmp(var, "core.commentstring")) {
if (!value)
return config_error_nonbool(var);
else if (!strcasecmp(value, "auto"))
auto_comment_line_char = 1;
- else if (value[0] && !value[1]) {
- comment_line_char = value[0];
+ else if (value[0]) {
+ if (strchr(value, '\n'))
+ return error(_("%s cannot contain newline"), var);
+ comment_line_str = xstrdup(value);
auto_comment_line_char = 0;
} else
- return error(_("core.commentChar should only be one ASCII character"));
+ return error(_("%s must have at least one character"), var);
return 0;
}
}
static ssize_t write_pair(int fd, const char *key, const char *value,
+ const char *comment,
const struct config_store_data *store)
{
int i;
strbuf_addch(&sb, value[i]);
break;
}
- strbuf_addf(&sb, "%s\n", quote);
+
+ if (comment)
+ strbuf_addf(&sb, "%s%s\n", quote, comment);
+ else
+ strbuf_addf(&sb, "%s\n", quote);
ret = write_in_full(fd, sb.buf, sb.len);
strbuf_release(&sb);
}
int git_config_set_in_file_gently(const char *config_filename,
- const char *key, const char *value)
+ const char *key, const char *comment, const char *value)
{
- return git_config_set_multivar_in_file_gently(config_filename, key, value, NULL, 0);
+ return git_config_set_multivar_in_file_gently(config_filename, key, value, NULL, comment, 0);
}
void git_config_set_in_file(const char *config_filename,
if (r->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);
+ file, key, value, NULL, NULL, 0);
free(file);
return ret;
}
trace2_cmd_set_config(key, value);
}
+/*
+ * The ownership rule is that the caller will own the string
+ * if it receives a piece of memory different from what it passed
+ * as the parameter.
+ */
+const char *git_config_prepare_comment_string(const char *comment)
+{
+ size_t leading_blanks;
+
+ if (!comment)
+ return NULL;
+
+ if (strchr(comment, '\n'))
+ die(_("no multi-line comment allowed: '%s'"), comment);
+
+ /*
+ * If it begins with one or more leading whitespace characters
+ * followed by '#", the comment string is used as-is.
+ *
+ * If it begins with '#', a SP is inserted between the comment
+ * and the value the comment is about.
+ *
+ * Otherwise, the value is followed by a SP followed by '#'
+ * followed by SP and then the comment string comes.
+ */
+
+ leading_blanks = strspn(comment, " \t");
+ if (leading_blanks && comment[leading_blanks] == '#')
+ ; /* use it as-is */
+ else if (comment[0] == '#')
+ comment = xstrfmt(" %s", comment);
+ else
+ comment = xstrfmt(" # %s", comment);
+
+ return comment;
+}
+
+static void validate_comment_string(const char *comment)
+{
+ size_t leading_blanks;
+
+ if (!comment)
+ return;
+ /*
+ * The front-end must have massaged the comment string
+ * properly before calling us.
+ */
+ if (strchr(comment, '\n'))
+ BUG("multi-line comments are not permitted: '%s'", comment);
+
+ leading_blanks = strspn(comment, " \t");
+ if (!leading_blanks || comment[leading_blanks] != '#')
+ BUG("comment must begin with one or more SP followed by '#': '%s'",
+ comment);
+}
+
/*
* If value==NULL, unset in (remove from) config,
* if value_pattern!=NULL, disregard key/value pairs where value does not match.
int git_config_set_multivar_in_file_gently(const char *config_filename,
const char *key, const char *value,
const char *value_pattern,
+ const char *comment,
unsigned flags)
{
int fd = -1, in_fd = -1;
size_t contents_sz;
struct config_store_data store = CONFIG_STORE_INIT;
+ validate_comment_string(comment);
+
/* parse-key returns negative; flip the sign to feed exit(3) */
ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
if (ret)
free(store.key);
store.key = xstrdup(key);
if (write_section(fd, key, &store) < 0 ||
- write_pair(fd, key, value, &store) < 0)
+ write_pair(fd, key, value, comment, &store) < 0)
goto write_err_out;
} else {
struct stat st;
if (write_section(fd, key, &store) < 0)
goto write_err_out;
}
- if (write_pair(fd, key, value, &store) < 0)
+ if (write_pair(fd, key, value, comment, &store) < 0)
goto write_err_out;
}
const char *value_pattern, unsigned flags)
{
if (!git_config_set_multivar_in_file_gently(config_filename, key, value,
- value_pattern, flags))
+ value_pattern, NULL, flags))
return;
if (value)
die(_("could not set '%s' to '%s'"), key, value);
int res = git_config_set_multivar_in_file_gently(file,
key, value,
value_pattern,
- flags);
+ NULL, flags);
free(file);
return res;
}
int git_config_expiry_date(timestamp_t *, const char *, const char *);
int git_config_color(char *, const char *, const char *);
-int git_config_set_in_file_gently(const char *, const char *, const char *);
+int git_config_set_in_file_gently(const char *, const char *, const char *, const char *);
/**
* write config values to a specific config file, takes a key/value pair as
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);
+int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, const char *, unsigned);
+
+const char *git_config_prepare_comment_string(const char *);
/**
* takes four parameters:
# parse configuration values
local option
- for option in ${GIT_PS1_SHOWUPSTREAM}; do
+ for option in ${GIT_PS1_SHOWUPSTREAM-}; do
case "$option" in
git|svn) upstream_type="$option" ;;
verbose) verbose=1 ;;
fi
local conflict="" # state indicator for unresolved conflicts
- if [[ "${GIT_PS1_SHOWCONFLICTSTATE}" == "yes" ]] &&
+ if [[ "${GIT_PS1_SHOWCONFLICTSTATE-}" == "yes" ]] &&
[[ $(git ls-files --unmerged 2>/dev/null) ]]; then
conflict="|CONFLICT"
fi
* The character that begins a commented line in user-editable file
* that is subject to stripspace.
*/
-char comment_line_char = '#';
+const char *comment_line_str = "#";
int auto_comment_line_char;
/* Parallel index stat data preload? */
* The character that begins a commented line in user-editable file
* that is subject to stripspace.
*/
-extern char comment_line_char;
+extern const char *comment_line_str;
extern int auto_comment_line_char;
/*
skip_prefix(me, them->items->string, &me) &&
starts_with(me, " <")))
return;
- strbuf_addf(out, "\n%c %s ", comment_line_char, label);
+ strbuf_addf(out, "\n%s %s ", comment_line_str, label);
add_people_count(out, them);
}
if (sig->len) {
strbuf_addch(tagbuf, '\n');
strbuf_add_commented_lines(tagbuf, sig->buf, sig->len,
- comment_line_char);
+ comment_line_str);
}
}
strbuf_add_commented_lines(&tagline,
origins.items[first_tag].string,
strlen(origins.items[first_tag].string),
- comment_line_char);
+ comment_line_str);
strbuf_insert(&tagbuf, 0, tagline.buf,
tagline.len);
strbuf_release(&tagline);
strbuf_add_commented_lines(&tagbuf,
origins.items[i].string,
strlen(origins.items[i].string),
- comment_line_char);
+ comment_line_str);
fmt_tag_signature(&tagbuf, &sig, buf, len);
}
strbuf_release(&payload);
}
}
- strbuf_stripspace(&ssh_keygen_out, '\0');
- strbuf_stripspace(&ssh_keygen_err, '\0');
+ strbuf_stripspace(&ssh_keygen_out, NULL);
+ strbuf_stripspace(&ssh_keygen_err, NULL);
/* Add stderr outputs to show the user actual ssh-keygen errors */
strbuf_add(&ssh_keygen_out, ssh_principals_err.buf, ssh_principals_err.len);
strbuf_add(&ssh_keygen_out, ssh_keygen_err.buf, ssh_keygen_err.len);
*list = p->next;
x = compile_pattern_or(list);
if (!*list || (*list)->token != GREP_CLOSE_PAREN)
- die("unmatched parenthesis");
+ die("unmatched ( for expression group");
*list = (*list)->next;
return x;
default:
if (p)
opt->pattern_expression = compile_pattern_expr(&p);
if (p)
- die("incomplete pattern expression: %s", p->pattern);
+ die("incomplete pattern expression group: %s", p->pattern);
if (opt->no_body_match && opt->pattern_expression)
opt->pattern_expression = grep_not_expr(opt->pattern_expression);
static char *next_arg(char **);
-__attribute__((format (printf, 3, 4)))
-static int nfsnprintf(char *buf, int blen, const char *fmt, ...);
-
static int nfvasprintf(char **strp, const char *fmt, va_list ap)
{
int len;
return ret;
}
-__attribute__((format (printf, 3, 4)))
-static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
-{
- int ret;
- va_list va;
-
- va_start(va, fmt);
- if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
- BUG("buffer too small. Please report a bug.");
- va_end(va);
- return ret;
-}
-
static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx,
struct imap_cmd_cb *cb,
const char *fmt, va_list ap)
get_cmd_result(ctx, NULL);
if (!cmd->cb.data)
- bufl = nfsnprintf(buf, sizeof(buf), "%d %s\r\n", cmd->tag, cmd->cmd);
+ bufl = xsnprintf(buf, sizeof(buf), "%d %s\r\n", cmd->tag, cmd->cmd);
else
- bufl = nfsnprintf(buf, sizeof(buf), "%d %s{%d%s}\r\n",
- cmd->tag, cmd->cmd, cmd->cb.dlen,
- CAP(LITERALPLUS) ? "+" : "");
+ bufl = xsnprintf(buf, sizeof(buf), "%d %s{%d%s}\r\n",
+ cmd->tag, cmd->cmd, cmd->cb.dlen,
+ CAP(LITERALPLUS) ? "+" : "");
if (0 < verbosity) {
if (imap->num_in_progress)
size_t available = block ? block->end - block->next_free : 0;
va_list cp;
int len, len2;
+ size_t size;
char *ret;
va_copy(cp, ap);
if (len < 0)
BUG("your vsnprintf is broken (returned %d)", len);
- ret = mem_pool_alloc(pool, len + 1); /* 1 for NUL */
+ size = st_add(len, 1); /* 1 for NUL */
+ ret = mem_pool_alloc(pool, size);
/* Shortcut; relies on mem_pool_alloc() not touching buffer contents. */
if (ret == next_free)
return ret;
- len2 = vsnprintf(ret, len + 1, fmt, ap);
+ len2 = vsnprintf(ret, size, fmt, ap);
if (len2 != len)
BUG("your vsnprintf is broken (returns inconsistent lengths)");
return ret;
for (i = 0; i < commit_formats_len; i++) {
size_t match_len;
- if (!starts_with(commit_formats[i].name, sought))
+ if (!istarts_with(commit_formats[i].name, sought))
continue;
match_len = strlen(commit_formats[i].name);
if (!edit_todo) {
strbuf_addch(buf, '\n');
- strbuf_commented_addf(buf, comment_line_char,
+ strbuf_commented_addf(buf, comment_line_str,
Q_("Rebase %s onto %s (%d command)",
"Rebase %s onto %s (%d commands)",
command_count),
shortrevisions, shortonto, command_count);
}
- strbuf_add_commented_lines(buf, msg, strlen(msg), comment_line_char);
+ strbuf_add_commented_lines(buf, msg, strlen(msg), comment_line_str);
if (get_missing_commit_check_level() == MISSING_COMMIT_CHECK_ERROR)
msg = _("\nDo not remove any line. Use 'drop' "
msg = _("\nIf you remove a line here "
"THAT COMMIT WILL BE LOST.\n");
- strbuf_add_commented_lines(buf, msg, strlen(msg), comment_line_char);
+ strbuf_add_commented_lines(buf, msg, strlen(msg), comment_line_str);
if (edit_todo)
msg = _("\nYou are editing the todo file "
msg = _("\nHowever, if you remove everything, "
"the rebase will be aborted.\n\n");
- strbuf_add_commented_lines(buf, msg, strlen(msg), comment_line_char);
+ strbuf_add_commented_lines(buf, msg, strlen(msg), comment_line_str);
}
int edit_todo_list(struct repository *r, struct todo_list *todo_list,
if (launch_sequence_editor(todo_file, &new_todo->buf, NULL))
return -2;
- strbuf_stripspace(&new_todo->buf, comment_line_char);
+ strbuf_stripspace(&new_todo->buf, comment_line_str);
if (initial && new_todo->buf.len == 0)
return -3;
#define RESOLVE_REF_NO_RECURSE 0x02
#define RESOLVE_REF_ALLOW_BAD_NAME 0x04
-struct pack_refs_opts {
- unsigned int flags;
- struct ref_exclusions *exclusions;
- struct string_list *includes;
-};
-
const char *refs_resolve_ref_unsafe(struct ref_store *refs,
const char *refname,
int resolve_flags,
/*
* Flags for controlling behaviour of pack_refs()
* PACK_REFS_PRUNE: Prune loose refs after packing
- * PACK_REFS_ALL: Pack _all_ refs, not just tags and already packed refs
+ * PACK_REFS_AUTO: Pack refs on a best effort basis. The heuristics and end
+ * result are decided by the ref backend. Backends may ignore
+ * this flag and fall back to a normal repack.
*/
-#define PACK_REFS_PRUNE 0x0001
-#define PACK_REFS_ALL 0x0002
+#define PACK_REFS_PRUNE (1 << 0)
+#define PACK_REFS_AUTO (1 << 1)
+
+struct pack_refs_opts {
+ unsigned int flags;
+ struct ref_exclusions *exclusions;
+ struct string_list *includes;
+};
/*
* Write a packed-refs file for the current repository.
if (!stack)
stack = refs->main_stack;
- ret = reftable_stack_compact_all(stack, NULL);
- if (ret)
+ if (opts->flags & PACK_REFS_AUTO)
+ ret = reftable_stack_auto_compact(stack);
+ else
+ ret = reftable_stack_compact_all(stack, NULL);
+ if (ret < 0) {
+ ret = error(_("unable to compact stack: %s"),
+ reftable_error_str(ret));
goto out;
+ }
+
ret = reftable_stack_clean(stack);
if (ret)
goto out;
out[1] = (uint8_t)(i & 0xff);
}
-int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args)
+size_t binsearch(size_t sz, int (*f)(size_t k, void *args), void *args)
{
size_t lo = 0;
size_t hi = sz;
*/
while (hi - lo > 1) {
size_t mid = lo + (hi - lo) / 2;
+ int ret = f(mid, args);
+ if (ret < 0)
+ return sz;
- if (f(mid, args))
+ if (ret > 0)
hi = mid;
else
lo = mid;
void put_be16(uint8_t *out, uint16_t i);
/*
- * find smallest index i in [0, sz) at which f(i) is true, assuming
- * that f is ascending. Return sz if f(i) is false for all indices.
+ * find smallest index i in [0, sz) at which `f(i) > 0`, assuming that f is
+ * ascending. Return sz if `f(i) == 0` for all indices. The search is aborted
+ * and `sz` is returned in case `f(i) < 0`.
*
* Contrary to bsearch(3), this returns something useful if the argument is not
* found.
*/
-int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args);
+size_t binsearch(size_t sz, int (*f)(size_t k, void *args), void *args);
/*
* Frees a NULL terminated array of malloced strings. The array itself is also
#include "test_framework.h"
#include "reftable-tests.h"
-struct binsearch_args {
- int key;
- int *arr;
+struct integer_needle_lesseq_args {
+ int needle;
+ int *haystack;
};
-static int binsearch_func(size_t i, void *void_args)
+static int integer_needle_lesseq(size_t i, void *_args)
{
- struct binsearch_args *args = void_args;
-
- return args->key < args->arr[i];
+ struct integer_needle_lesseq_args *args = _args;
+ return args->needle <= args->haystack[i];
}
static void test_binsearch(void)
{
- int arr[] = { 2, 4, 6, 8, 10 };
- size_t sz = ARRAY_SIZE(arr);
- struct binsearch_args args = {
- .arr = arr,
+ int haystack[] = { 2, 4, 6, 8, 10 };
+ struct {
+ int needle;
+ size_t expected_idx;
+ } testcases[] = {
+ {-9000, 0},
+ {-1, 0},
+ {0, 0},
+ {2, 0},
+ {3, 1},
+ {4, 1},
+ {7, 3},
+ {9, 4},
+ {10, 4},
+ {11, 5},
+ {9000, 5},
};
+ size_t i = 0;
- int i = 0;
- for (i = 1; i < 11; i++) {
- int res;
- args.key = i;
- res = binsearch(sz, &binsearch_func, &args);
+ for (i = 0; i < ARRAY_SIZE(testcases); i++) {
+ struct integer_needle_lesseq_args args = {
+ .haystack = haystack,
+ .needle = testcases[i].needle,
+ };
+ size_t idx;
- if (res < sz) {
- EXPECT(args.key < arr[res]);
- if (res > 0) {
- EXPECT(args.key >= arr[res - 1]);
- }
- } else {
- EXPECT(args.key == 10 || args.key == 11);
- }
+ idx = binsearch(ARRAY_SIZE(haystack), &integer_needle_lesseq, &args);
+ EXPECT(idx == testcases[i].expected_idx);
}
}
it->next_off = br->header_off + 4;
}
-struct restart_find_args {
+struct restart_needle_less_args {
int error;
- struct strbuf key;
- struct block_reader *r;
+ struct strbuf needle;
+ struct block_reader *reader;
};
-static int restart_key_less(size_t idx, void *args)
+static int restart_needle_less(size_t idx, void *_args)
{
- struct restart_find_args *a = args;
- uint32_t off = block_reader_restart_offset(a->r, idx);
+ struct restart_needle_less_args *args = _args;
+ uint32_t off = block_reader_restart_offset(args->reader, idx);
struct string_view in = {
- .buf = a->r->block.data + off,
- .len = a->r->block_len - off,
+ .buf = args->reader->block.data + off,
+ .len = args->reader->block_len - off,
};
+ uint64_t prefix_len, suffix_len;
+ uint8_t extra;
+ int n;
+
+ /*
+ * Records at restart points are stored without prefix compression, so
+ * there is no need to fully decode the record key here. This removes
+ * the need for allocating memory.
+ */
+ n = reftable_decode_keylen(in, &prefix_len, &suffix_len, &extra);
+ if (n < 0 || prefix_len) {
+ args->error = 1;
+ return -1;
+ }
- /* the restart key is verbatim in the block, so this could avoid the
- alloc for decoding the key */
- struct strbuf rkey = STRBUF_INIT;
- uint8_t unused_extra;
- int n = reftable_decode_key(&rkey, &unused_extra, in);
- int result;
- if (n < 0) {
- a->error = 1;
+ string_view_consume(&in, n);
+ if (suffix_len > in.len) {
+ args->error = 1;
return -1;
}
- result = strbuf_cmp(&a->key, &rkey);
- strbuf_release(&rkey);
- return result < 0;
+ n = memcmp(args->needle.buf, in.buf,
+ args->needle.len < suffix_len ? args->needle.len : suffix_len);
+ if (n)
+ return n < 0;
+ return args->needle.len < suffix_len;
}
void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
int block_reader_seek(struct block_reader *br, struct block_iter *it,
struct strbuf *want)
{
- struct restart_find_args args = {
- .key = *want,
- .r = br,
+ struct restart_needle_less_args args = {
+ .needle = *want,
+ .reader = br,
};
struct block_iter next = BLOCK_ITER_INIT;
struct reftable_record rec;
- int err = 0, i;
-
+ int err = 0;
+ size_t i;
+
+ /*
+ * Perform a binary search over the block's restart points, which
+ * avoids doing a linear scan over the whole block. Like this, we
+ * identify the section of the block that should contain our key.
+ *
+ * Note that we explicitly search for the first restart point _greater_
+ * than the sought-after record, not _greater or equal_ to it. In case
+ * the sought-after record is located directly at the restart point we
+ * would otherwise start doing the linear search at the preceding
+ * restart point. While that works alright, we would end up scanning
+ * too many record.
+ */
+ i = binsearch(br->restart_count, &restart_needle_less, &args);
if (args.error) {
err = REFTABLE_FORMAT_ERROR;
goto done;
}
- i = binsearch(br->restart_count, &restart_key_less, &args);
+ /*
+ * Now there are multiple cases:
+ *
+ * - `i == 0`: The wanted record is smaller than the record found at
+ * the first restart point. As the first restart point is the first
+ * record in the block, our wanted record cannot be located in this
+ * block at all. We still need to position the iterator so that the
+ * next call to `block_iter_next()` will yield an end-of-iterator
+ * signal.
+ *
+ * - `i == restart_count`: The wanted record was not found at any of
+ * the restart points. As there is no restart point at the end of
+ * the section the record may thus be contained in the last block.
+ *
+ * - `i > 0`: The wanted record must be contained in the section
+ * before the found restart point. We thus do a linear search
+ * starting from the preceding restart point.
+ */
if (i > 0)
it->next_off = block_reader_restart_offset(br, i - 1);
else
reftable_record_init(&rec, block_reader_type(br));
- /* We're looking for the last entry less/equal than the wanted key, so
- we have to go one entry too far and then back up.
- */
+ /*
+ * We're looking for the last entry less than the wanted key so that
+ * the next call to `block_reader_next()` would yield the wanted
+ * record. We thus don't want to position our reader at the sought
+ * after record, but one before. To do so, we have to go one entry too
+ * far and then back up.
+ */
while (1) {
block_iter_copy_from(&next, it);
err = block_iter_next(&next, &rec);
if (err < 0)
goto done;
-
- reftable_record_key(&rec, &it->last_key);
- if (err > 0 || strbuf_cmp(&it->last_key, want) >= 0) {
+ if (err > 0) {
err = 0;
goto done;
}
+ /*
+ * Check whether the current key is greater or equal to the
+ * sought-after key. In case it is greater we know that the
+ * record does not exist in the block and can thus abort early.
+ * In case it is equal to the sought-after key we have found
+ * the desired record.
+ */
+ reftable_record_key(&rec, &it->last_key);
+ if (strbuf_cmp(&it->last_key, want) >= 0)
+ goto done;
+
block_iter_copy_from(it, &next);
}
case REFTABLE_NOT_EXIST_ERROR:
return "file does not exist";
case REFTABLE_LOCK_ERROR:
- return "data is outdated";
+ return "data is locked";
case REFTABLE_API_ERROR:
return "misuse of the reftable API";
case REFTABLE_ZLIB_ERROR:
return "invalid refname";
case REFTABLE_ENTRY_TOO_BIG_ERROR:
return "entry too large";
+ case REFTABLE_OUTDATED_ERROR:
+ return "data concurrently modified";
case -1:
return "general error";
default:
return start.len - dest.len;
}
-int reftable_decode_key(struct strbuf *last_key, uint8_t *extra,
- struct string_view in)
+int reftable_decode_keylen(struct string_view in,
+ uint64_t *prefix_len,
+ uint64_t *suffix_len,
+ uint8_t *extra)
{
- int start_len = in.len;
- uint64_t prefix_len = 0;
- uint64_t suffix_len = 0;
+ size_t start_len = in.len;
int n;
- n = get_var_int(&prefix_len, &in);
+ n = get_var_int(prefix_len, &in);
if (n < 0)
return -1;
string_view_consume(&in, n);
- n = get_var_int(&suffix_len, &in);
+ n = get_var_int(suffix_len, &in);
if (n <= 0)
return -1;
string_view_consume(&in, n);
- *extra = (uint8_t)(suffix_len & 0x7);
- suffix_len >>= 3;
+ *extra = (uint8_t)(*suffix_len & 0x7);
+ *suffix_len >>= 3;
+
+ return start_len - in.len;
+}
+
+int reftable_decode_key(struct strbuf *last_key, uint8_t *extra,
+ struct string_view in)
+{
+ int start_len = in.len;
+ uint64_t prefix_len = 0;
+ uint64_t suffix_len = 0;
+ int n;
+
+ n = reftable_decode_keylen(in, &prefix_len, &suffix_len, extra);
+ if (n < 0)
+ return -1;
+ string_view_consume(&in, n);
if (in.len < suffix_len ||
prefix_len > last_key->len)
struct strbuf prev_key, struct strbuf key,
uint8_t extra);
+/* Decode a record's key lengths. */
+int reftable_decode_keylen(struct string_view in,
+ uint64_t *prefix_len,
+ uint64_t *suffix_len,
+ uint8_t *extra);
+
/*
* Decode into `last_key` and `extra` from `in`. `last_key` is expected to
* contain the decoded key of the preceding record, if any.
#include "refname.h"
#include "reftable-iterator.h"
-struct find_arg {
- char **names;
- const char *want;
+struct refname_needle_lesseq_args {
+ char **haystack;
+ const char *needle;
};
-static int find_name(size_t k, void *arg)
+static int refname_needle_lesseq(size_t k, void *_args)
{
- struct find_arg *f_arg = arg;
- return strcmp(f_arg->names[k], f_arg->want) >= 0;
+ struct refname_needle_lesseq_args *args = _args;
+ return strcmp(args->needle, args->haystack[k]) <= 0;
}
static int modification_has_ref(struct modification *mod, const char *name)
int err = 0;
if (mod->add_len > 0) {
- struct find_arg arg = {
- .names = mod->add,
- .want = name,
+ struct refname_needle_lesseq_args args = {
+ .haystack = mod->add,
+ .needle = name,
};
- int idx = binsearch(mod->add_len, find_name, &arg);
- if (idx < mod->add_len && !strcmp(mod->add[idx], name)) {
+ size_t idx = binsearch(mod->add_len, refname_needle_lesseq, &args);
+ if (idx < mod->add_len && !strcmp(mod->add[idx], name))
return 0;
- }
}
if (mod->del_len > 0) {
- struct find_arg arg = {
- .names = mod->del,
- .want = name,
+ struct refname_needle_lesseq_args args = {
+ .haystack = mod->del,
+ .needle = name,
};
- int idx = binsearch(mod->del_len, find_name, &arg);
- if (idx < mod->del_len && !strcmp(mod->del[idx], name)) {
+ size_t idx = binsearch(mod->del_len, refname_needle_lesseq, &args);
+ if (idx < mod->del_len && !strcmp(mod->del[idx], name))
return 1;
- }
}
err = reftable_table_read_ref(&mod->tab, name, &ref);
int err = 0;
if (mod->add_len > 0) {
- struct find_arg arg = {
- .names = mod->add,
- .want = prefix,
+ struct refname_needle_lesseq_args args = {
+ .haystack = mod->add,
+ .needle = prefix,
};
- int idx = binsearch(mod->add_len, find_name, &arg);
+ size_t idx = binsearch(mod->add_len, refname_needle_lesseq, &args);
if (idx < mod->add_len &&
!strncmp(prefix, mod->add[idx], strlen(prefix)))
goto done;
goto done;
if (mod->del_len > 0) {
- struct find_arg arg = {
- .names = mod->del,
- .want = ref.refname,
+ struct refname_needle_lesseq_args args = {
+ .haystack = mod->del,
+ .needle = ref.refname,
};
- int idx = binsearch(mod->del_len, find_name, &arg);
+ size_t idx = binsearch(mod->del_len, refname_needle_lesseq, &args);
if (idx < mod->del_len &&
- !strcmp(ref.refname, mod->del[idx])) {
+ !strcmp(ref.refname, mod->del[idx]))
continue;
- }
}
if (strncmp(ref.refname, prefix, strlen(prefix))) {
*/
REFTABLE_NOT_EXIST_ERROR = -4,
- /* Trying to write out-of-date data. */
+ /* Trying to access locked data. */
REFTABLE_LOCK_ERROR = -5,
/* Misuse of the API:
/* Entry does not fit. This can happen when writing outsize reflog
messages. */
REFTABLE_ENTRY_TOO_BIG_ERROR = -11,
+
+ /* Trying to write out-of-date data. */
+ REFTABLE_OUTDATED_ERROR = -12,
};
/* convert the numeric error code to a string. The string should not be
{
int err = stack_try_add(st, write, arg);
if (err < 0) {
- if (err == REFTABLE_LOCK_ERROR) {
+ if (err == REFTABLE_OUTDATED_ERROR) {
/* Ignore error return, we want to propagate
- REFTABLE_LOCK_ERROR.
+ REFTABLE_OUTDATED_ERROR.
*/
reftable_stack_reload(st);
}
err = stack_uptodate(st);
if (err < 0)
goto done;
-
- if (err > 1) {
- err = REFTABLE_LOCK_ERROR;
+ if (err > 0) {
+ err = REFTABLE_OUTDATED_ERROR;
goto done;
}
if (err)
goto done;
- if (!add->stack->disable_auto_compact)
+ if (!add->stack->disable_auto_compact) {
+ /*
+ * Auto-compact the stack to keep the number of tables in
+ * control. It is possible that a concurrent writer is already
+ * trying to compact parts of the stack, which would lead to a
+ * `REFTABLE_LOCK_ERROR` because parts of the stack are locked
+ * already. This is a benign error though, so we ignore it.
+ */
err = reftable_stack_auto_compact(add->stack);
+ if (err < 0 && err != REFTABLE_LOCK_ERROR)
+ goto done;
+ err = 0;
+ }
done:
reftable_addition_close(add);
int err = reftable_stack_init_addition(&add, st);
if (err < 0)
goto done;
- if (err > 0) {
- err = REFTABLE_LOCK_ERROR;
- goto done;
- }
err = reftable_addition_add(&add, write_table, arg);
if (err < 0)
return err;
}
-/* < 0: error. 0 == OK, > 0 attempt failed; could retry. */
+/*
+ * Compact all tables in the range `[first, last)` into a single new table.
+ *
+ * This function returns `0` on success or a code `< 0` on failure. When the
+ * stack or any of the tables in the specified range are already locked then
+ * this function returns `REFTABLE_LOCK_ERROR`. This is a benign error that
+ * callers can either ignore, or they may choose to retry compaction after some
+ * amount of time.
+ */
static int stack_compact_range(struct reftable_stack *st,
size_t first, size_t last,
struct reftable_log_expiry_config *expiry)
LOCK_NO_DEREF);
if (err < 0) {
if (errno == EEXIST)
- err = 1;
+ err = REFTABLE_LOCK_ERROR;
else
err = REFTABLE_IO_ERROR;
goto done;
table_name.buf, LOCK_NO_DEREF);
if (err < 0) {
if (errno == EEXIST)
- err = 1;
+ err = REFTABLE_LOCK_ERROR;
else
err = REFTABLE_IO_ERROR;
goto done;
LOCK_NO_DEREF);
if (err < 0) {
if (errno == EEXIST)
- err = 1;
+ err = REFTABLE_LOCK_ERROR;
else
err = REFTABLE_IO_ERROR;
goto done;
struct reftable_log_expiry_config *config)
{
int err = stack_compact_range(st, first, last, config);
- if (err > 0)
+ if (err == REFTABLE_LOCK_ERROR)
st->stats.failures++;
return err;
}
EXPECT_ERR(err);
err = reftable_stack_add(st2, &write_test_ref, &ref2);
- EXPECT(err == REFTABLE_LOCK_ERROR);
+ EXPECT(err == REFTABLE_OUTDATED_ERROR);
err = reftable_stack_reload(st2);
EXPECT_ERR(err);
clear_dir(dir);
}
+static void test_reftable_stack_auto_compaction_fails_gracefully(void)
+{
+ struct reftable_ref_record ref = {
+ .refname = "refs/heads/master",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = {0x01},
+ };
+ struct reftable_write_options cfg = {0};
+ struct reftable_stack *st;
+ struct strbuf table_path = STRBUF_INIT;
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+
+ err = reftable_new_stack(&st, dir, cfg);
+ EXPECT_ERR(err);
+
+ err = reftable_stack_add(st, write_test_ref, &ref);
+ EXPECT_ERR(err);
+ EXPECT(st->merged->stack_len == 1);
+ EXPECT(st->stats.attempts == 0);
+ EXPECT(st->stats.failures == 0);
+
+ /*
+ * Lock the newly written table such that it cannot be compacted.
+ * Adding a new table to the stack should not be impacted by this, even
+ * though auto-compaction will now fail.
+ */
+ strbuf_addf(&table_path, "%s/%s.lock", dir, st->readers[0]->name);
+ write_file_buf(table_path.buf, "", 0);
+
+ ref.update_index = 2;
+ err = reftable_stack_add(st, write_test_ref, &ref);
+ EXPECT_ERR(err);
+ EXPECT(st->merged->stack_len == 2);
+ EXPECT(st->stats.attempts == 1);
+ EXPECT(st->stats.failures == 1);
+
+ reftable_stack_destroy(st);
+ strbuf_release(&table_path);
+ clear_dir(dir);
+}
+
static void test_reftable_stack_validate_refname(void)
{
struct reftable_write_options cfg = { 0 };
RUN_TEST(test_reftable_stack_tombstone);
RUN_TEST(test_reftable_stack_transaction_api);
RUN_TEST(test_reftable_stack_transaction_api_performs_auto_compaction);
+ RUN_TEST(test_reftable_stack_auto_compaction_fails_gracefully);
RUN_TEST(test_reftable_stack_update_index_check);
RUN_TEST(test_reftable_stack_uptodate);
RUN_TEST(test_reftable_stack_validate_refname);
options.filter = xstrdup(value);
return 0;
} else if (!strcmp(name, "object-format")) {
- int algo;
options.object_format = 1;
- if (strcmp(value, "true")) {
- algo = hash_algo_by_name(value);
- if (algo == GIT_HASH_UNKNOWN)
- die("unknown object format '%s'", value);
- options.hash_algo = &hash_algos[algo];
- }
+ if (strcmp(value, "true"))
+ die(_("unknown value for object-format: %s"), value);
return 0;
} else {
return 1 /* unsupported */;
if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
strbuf_addch(msgbuf, '\n');
wt_status_append_cut_line(msgbuf);
- strbuf_addch(msgbuf, comment_line_char);
+ strbuf_addstr(msgbuf, comment_line_str);
}
strbuf_addch(msgbuf, '\n');
- strbuf_commented_addf(msgbuf, comment_line_char, "Conflicts:\n");
+ strbuf_commented_addf(msgbuf, comment_line_str, "Conflicts:\n");
for (i = 0; i < istate->cache_nr;) {
const struct cache_entry *ce = istate->cache[i++];
if (ce_stage(ce)) {
- strbuf_commented_addf(msgbuf, comment_line_char,
+ strbuf_commented_addf(msgbuf, comment_line_str,
"\t%s\n", ce->name);
while (i < istate->cache_nr &&
!strcmp(ce->name, istate->cache[i]->name))
static int is_index_unchanged(struct repository *r)
{
struct object_id head_oid, *cache_tree_oid;
+ const struct object_id *head_tree_oid;
struct commit *head_commit;
struct index_state *istate = r->index;
+ const char *head_name;
+
+ if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) {
+ /* Check to see if this is an unborn branch */
+ head_name = resolve_ref_unsafe("HEAD",
+ RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+ &head_oid, NULL);
+ if (!head_name ||
+ !starts_with(head_name, "refs/heads/") ||
+ !is_null_oid(&head_oid))
+ return error(_("could not resolve HEAD commit"));
+ head_tree_oid = the_hash_algo->empty_tree;
+ } else {
+ head_commit = lookup_commit(r, &head_oid);
- if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
- return error(_("could not resolve HEAD commit"));
-
- head_commit = lookup_commit(r, &head_oid);
+ /*
+ * If head_commit is NULL, check_commit, called from
+ * lookup_commit, would have indicated that head_commit is not
+ * a commit object already. repo_parse_commit() will return failure
+ * without further complaints in such a case. Otherwise, if
+ * the commit is invalid, repo_parse_commit() will complain. So
+ * there is nothing for us to say here. Just return failure.
+ */
+ if (repo_parse_commit(r, head_commit))
+ return -1;
- /*
- * If head_commit is NULL, check_commit, called from
- * lookup_commit, would have indicated that head_commit is not
- * a commit object already. repo_parse_commit() will return failure
- * without further complaints in such a case. Otherwise, if
- * the commit is invalid, repo_parse_commit() will complain. So
- * there is nothing for us to say here. Just return failure.
- */
- if (repo_parse_commit(r, head_commit))
- return -1;
+ head_tree_oid = get_commit_tree_oid(head_commit);
+ }
if (!(cache_tree_oid = get_cache_tree_oid(istate)))
return -1;
- return oideq(cache_tree_oid, get_commit_tree_oid(head_commit));
+ return oideq(cache_tree_oid, head_tree_oid);
}
static int write_author_script(const char *message)
strbuf_setlen(msgbuf, wt_status_locate_end(msgbuf->buf, msgbuf->len));
if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
strbuf_stripspace(msgbuf,
- cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
+ cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_str : NULL);
}
/*
return 0;
strbuf_stripspace(&tmpl,
- cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
+ cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_str : NULL);
if (!skip_prefix(sb->buf, tmpl.buf, &start))
start = sb->buf;
strbuf_release(&tmpl);
if (cleanup != COMMIT_MSG_CLEANUP_NONE)
strbuf_stripspace(msg,
- cleanup == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
+ cleanup == COMMIT_MSG_CLEANUP_ALL ? comment_line_str : NULL);
if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) {
res = 1; /* run 'git commit' to display error message */
goto out;
int index_unchanged, originally_empty;
/*
- * Four cases:
- *
- * (1) we do not allow empty at all and error out.
+ * For a commit that is initially empty, allow_empty determines if it
+ * should be kept or not
*
- * (2) we allow ones that were initially empty, and
- * just drop the ones that become empty
- *
- * (3) we allow ones that were initially empty, but
- * halt for the ones that become empty;
- *
- * (4) we allow both.
+ * For a commit that becomes empty, keep_redundant_commits and
+ * drop_redundant_commits determine whether the commit should be kept or
+ * dropped. If neither is specified, halt.
*/
- if (!opts->allow_empty)
- return 0; /* let "git commit" barf as necessary */
-
index_unchanged = is_index_unchanged(r);
if (index_unchanged < 0)
return index_unchanged;
if (!index_unchanged)
return 0; /* we do not have to say --allow-empty */
- if (opts->keep_redundant_commits)
- return 1;
-
originally_empty = is_original_commit_empty(commit);
if (originally_empty < 0)
return originally_empty;
if (originally_empty)
+ return opts->allow_empty;
+ else if (opts->keep_redundant_commits)
return 1;
else if (opts->drop_redundant_commits)
return 2;
{
if (command < TODO_COMMENT)
return todo_command_info[command].str;
+ if (command == TODO_COMMENT)
+ return comment_line_str;
die(_("unknown command: %d"), command);
}
{
if (command < TODO_COMMENT)
return todo_command_info[command].c;
- return comment_line_char;
+ return 0;
}
static int is_noop(const enum todo_command command)
static void add_commented_lines(struct strbuf *buf, const void *str, size_t len)
{
const char *s = str;
- while (len > 0 && s[0] == comment_line_char) {
+ while (starts_with_mem(s, len, comment_line_str)) {
size_t count;
const char *n = memchr(s, '\n', len);
if (!n)
s += count;
len -= count;
}
- strbuf_add_commented_lines(buf, s, len, comment_line_char);
+ strbuf_add_commented_lines(buf, s, len, comment_line_str);
}
/* Does the current fixup chain contain a squash command? */
(starts_with(body, "squash!") || starts_with(body, "fixup!"))))
commented_len = commit_subject_length(body);
- strbuf_addf(buf, "\n%c ", comment_line_char);
+ strbuf_addf(buf, "\n%s ", comment_line_str);
strbuf_addf(buf, _(nth_commit_msg_fmt),
++opts->current_fixup_count + 1);
strbuf_addstr(buf, "\n\n");
- strbuf_add_commented_lines(buf, body, commented_len, comment_line_char);
+ strbuf_add_commented_lines(buf, body, commented_len, comment_line_str);
/* buf->buf may be reallocated so store an offset into the buffer */
fixup_off = buf->len;
strbuf_addstr(buf, body + commented_len);
return error(_("could not read '%s'"),
rebase_path_squash_msg());
- eol = buf.buf[0] != comment_line_char ?
+ eol = !starts_with(buf.buf, comment_line_str) ?
buf.buf : strchrnul(buf.buf, '\n');
- strbuf_addf(&header, "%c ", comment_line_char);
+ strbuf_addf(&header, "%s ", comment_line_str);
strbuf_addf(&header, _(combined_commit_msg_fmt),
opts->current_fixup_count + 2);
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
repo_unuse_commit_buffer(r, head_commit, head_message);
return error(_("cannot write '%s'"), rebase_path_fixup_msg());
}
- strbuf_addf(&buf, "%c ", comment_line_char);
+ strbuf_addf(&buf, "%s ", comment_line_str);
strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
- strbuf_addf(&buf, "\n%c ", comment_line_char);
+ strbuf_addf(&buf, "\n%s ", comment_line_str);
strbuf_addstr(&buf, is_fixup_flag(command, flag) ?
_(skip_first_commit_msg_str) :
_(first_commit_msg_str));
strbuf_addstr(&buf, "\n\n");
if (is_fixup_flag(command, flag))
strbuf_add_commented_lines(&buf, body, strlen(body),
- comment_line_char);
+ comment_line_str);
else
strbuf_addstr(&buf, body);
if (command == TODO_SQUASH || is_fixup_flag(command, flag)) {
res = append_squash_message(&buf, body, command, opts, flag);
} else if (command == TODO_FIXUP) {
- strbuf_addf(&buf, "\n%c ", comment_line_char);
+ strbuf_addf(&buf, "\n%s ", comment_line_str);
strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
++opts->current_fixup_count + 1);
strbuf_addstr(&buf, "\n\n");
strbuf_add_commented_lines(&buf, body, strlen(body),
- comment_line_char);
+ comment_line_str);
} else
return error(_("unknown command: %d"), command);
repo_unuse_commit_buffer(r, commit, message);
/* left-trim */
bol += strspn(bol, " \t");
- if (bol == eol || *bol == '\r' || *bol == comment_line_char) {
+ if (bol == eol || *bol == '\r' || starts_with_mem(bol, eol - bol, comment_line_str)) {
item->command = TODO_COMMENT;
item->commit = NULL;
item->arg_offset = bol - buf;
else if (!strcmp(key, "options.allow-empty-message"))
opts->allow_empty_message =
git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
+ else if (!strcmp(key, "options.drop-redundant-commits"))
+ opts->drop_redundant_commits =
+ git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
else if (!strcmp(key, "options.keep-redundant-commits"))
opts->keep_redundant_commits =
git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
if (opts->no_commit)
res |= git_config_set_in_file_gently(opts_file,
- "options.no-commit", "true");
+ "options.no-commit", NULL, "true");
if (opts->edit >= 0)
- res |= git_config_set_in_file_gently(opts_file, "options.edit",
+ res |= git_config_set_in_file_gently(opts_file, "options.edit", NULL,
opts->edit ? "true" : "false");
if (opts->allow_empty)
res |= git_config_set_in_file_gently(opts_file,
- "options.allow-empty", "true");
+ "options.allow-empty", NULL, "true");
if (opts->allow_empty_message)
res |= git_config_set_in_file_gently(opts_file,
- "options.allow-empty-message", "true");
+ "options.allow-empty-message", NULL, "true");
+ if (opts->drop_redundant_commits)
+ res |= git_config_set_in_file_gently(opts_file,
+ "options.drop-redundant-commits", NULL, "true");
if (opts->keep_redundant_commits)
res |= git_config_set_in_file_gently(opts_file,
- "options.keep-redundant-commits", "true");
+ "options.keep-redundant-commits", NULL, "true");
if (opts->signoff)
res |= git_config_set_in_file_gently(opts_file,
- "options.signoff", "true");
+ "options.signoff", NULL, "true");
if (opts->record_origin)
res |= git_config_set_in_file_gently(opts_file,
- "options.record-origin", "true");
+ "options.record-origin", NULL, "true");
if (opts->allow_ff)
res |= git_config_set_in_file_gently(opts_file,
- "options.allow-ff", "true");
+ "options.allow-ff", NULL, "true");
if (opts->mainline) {
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "%d", opts->mainline);
res |= git_config_set_in_file_gently(opts_file,
- "options.mainline", buf.buf);
+ "options.mainline", NULL, buf.buf);
strbuf_release(&buf);
}
if (opts->strategy)
res |= git_config_set_in_file_gently(opts_file,
- "options.strategy", opts->strategy);
+ "options.strategy", NULL, opts->strategy);
if (opts->gpg_sign)
res |= git_config_set_in_file_gently(opts_file,
- "options.gpg-sign", opts->gpg_sign);
+ "options.gpg-sign", NULL, opts->gpg_sign);
for (size_t i = 0; i < opts->xopts.nr; i++)
res |= git_config_set_multivar_in_file_gently(opts_file,
"options.strategy-option",
- opts->xopts.v[i], "^$", 0);
+ opts->xopts.v[i], "^$", NULL, 0);
if (opts->allow_rerere_auto)
res |= git_config_set_in_file_gently(opts_file,
- "options.allow-rerere-auto",
+ "options.allow-rerere-auto", NULL,
opts->allow_rerere_auto == RERERE_AUTOUPDATE ?
"true" : "false");
if (opts->explicit_cleanup)
res |= git_config_set_in_file_gently(opts_file,
- "options.default-msg-cleanup",
+ "options.default-msg-cleanup", NULL,
describe_cleanup_mode(opts->default_msg_cleanup));
return res;
}
oid_to_hex(&commit->object.oid),
oneline.buf);
if (is_empty)
- strbuf_addf(&buf, " %c empty",
- comment_line_char);
+ strbuf_addf(&buf, " %s empty",
+ comment_line_str);
FLEX_ALLOC_STR(entry, string, buf.buf);
oidcpy(&entry->entry.oid, &commit->object.oid);
entry = oidmap_get(&state.commit2label, &commit->object.oid);
if (entry)
- strbuf_addf(out, "\n%c Branch %s\n", comment_line_char, entry->string);
+ strbuf_addf(out, "\n%s Branch %s\n", comment_line_str, entry->string);
else
strbuf_addch(out, '\n');
oid_to_hex(&commit->object.oid));
pretty_print_commit(&pp, commit, out);
if (is_empty)
- strbuf_addf(out, " %c empty", comment_line_char);
+ strbuf_addf(out, " %s empty", comment_line_str);
strbuf_addch(out, '\n');
}
if (skipped_commit)
return 0;
}
+int starts_with_mem(const char *str, size_t len, const char *prefix)
+{
+ const char *end = str + len;
+ for (; ; str++, prefix++) {
+ if (!*prefix)
+ return 1;
+ else if (str == end || *str != *prefix)
+ return 0;
+ }
+}
+
int skip_to_optional_arg_default(const char *str, const char *prefix,
const char **arg, const char *def)
{
}
static void add_lines(struct strbuf *out,
- const char *prefix1,
- const char *prefix2,
- const char *buf, size_t size)
+ const char *prefix,
+ const char *buf, size_t size,
+ int space_after_prefix)
{
while (size) {
- const char *prefix;
const char *next = memchr(buf, '\n', size);
next = next ? (next + 1) : (buf + size);
- prefix = ((prefix2 && (buf[0] == '\n' || buf[0] == '\t'))
- ? prefix2 : prefix1);
strbuf_addstr(out, prefix);
+ if (space_after_prefix && buf[0] != '\n' && buf[0] != '\t')
+ strbuf_addch(out, ' ');
strbuf_add(out, buf, next - buf);
size -= next - buf;
buf = next;
}
void strbuf_add_commented_lines(struct strbuf *out, const char *buf,
- size_t size, char comment_line_char)
+ size_t size, const char *comment_prefix)
{
- static char prefix1[3];
- static char prefix2[2];
-
- if (prefix1[0] != comment_line_char) {
- xsnprintf(prefix1, sizeof(prefix1), "%c ", comment_line_char);
- xsnprintf(prefix2, sizeof(prefix2), "%c", comment_line_char);
- }
- add_lines(out, prefix1, prefix2, buf, size);
+ add_lines(out, comment_prefix, buf, size, 1);
}
-void strbuf_commented_addf(struct strbuf *sb, char comment_line_char,
+void strbuf_commented_addf(struct strbuf *sb, const char *comment_prefix,
const char *fmt, ...)
{
va_list params;
strbuf_vaddf(&buf, fmt, params);
va_end(params);
- strbuf_add_commented_lines(sb, buf.buf, buf.len, comment_line_char);
+ strbuf_add_commented_lines(sb, buf.buf, buf.len, comment_prefix);
if (incomplete_line)
sb->buf[--sb->len] = '\0';
return 0;
}
+void strbuf_expand_bad_format(const char *format, const char *command)
+{
+ const char *end;
+
+ if (*format != '(')
+ /* TRANSLATORS: The first %s is a command like "ls-tree". */
+ die(_("bad %s format: element '%s' does not start with '('"),
+ command, format);
+
+ end = strchr(format + 1, ')');
+ if (!end)
+ /* TRANSLATORS: The first %s is a command like "ls-tree". */
+ die(_("bad %s format: element '%s' does not end in ')'"),
+ command, format);
+
+ /* TRANSLATORS: %s is a command like "ls-tree". */
+ die(_("bad %s format: %%%.*s"),
+ command, (int)(end - format + 1), format);
+}
+
void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src)
{
size_t i, len = src->len;
void strbuf_add_lines(struct strbuf *out, const char *prefix,
const char *buf, size_t size)
{
- add_lines(out, prefix, NULL, buf, size);
+ add_lines(out, prefix, buf, size, 0);
}
void strbuf_addstr_xml_quoted(struct strbuf *buf, const char *s)
*
* If last line does not have a newline at the end, one is added.
*
- * Pass a non-NUL comment_line_char to skip every line starting
+ * Pass a non-NULL comment_prefix to skip every line starting
* with it.
*/
-void strbuf_stripspace(struct strbuf *sb, char comment_line_char)
+void strbuf_stripspace(struct strbuf *sb, const char *comment_prefix)
{
size_t empties = 0;
size_t i, j, len, newlen;
eol = memchr(sb->buf + i, '\n', sb->len - i);
len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
- if (comment_line_char && len &&
- sb->buf[i] == comment_line_char) {
+ if (comment_prefix && len &&
+ starts_with(sb->buf + i, comment_prefix)) {
newlen = 0;
continue;
}
*/
void strbuf_add_commented_lines(struct strbuf *out,
const char *buf, size_t size,
- char comment_line_char);
+ const char *comment_prefix);
/**
*/
int strbuf_expand_step(struct strbuf *sb, const char **formatp);
+/**
+ * Used with `strbuf_expand_step` to report unknown placeholders.
+ */
+void strbuf_expand_bad_format(const char *format, const char *command);
+
/**
* Append the contents of one strbuf to another, quoting any
* percent signs ("%") into double-percents ("%%") in the
* blank to the buffer.
*/
__attribute__((format (printf, 3, 4)))
-void strbuf_commented_addf(struct strbuf *sb, char comment_line_char, const char *fmt, ...);
+void strbuf_commented_addf(struct strbuf *sb, const char *comment_prefix, const char *fmt, ...);
__attribute__((format (printf,2,0)))
void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
int strbuf_normalize_path(struct strbuf *sb);
/**
- * Strip whitespace from a buffer. If comment_line_char is non-NUL,
+ * Strip whitespace from a buffer. If comment_prefix is non-NULL,
* then lines beginning with that character are considered comments,
* thus removed.
*/
-void strbuf_stripspace(struct strbuf *buf, char comment_line_char);
+void strbuf_stripspace(struct strbuf *buf, const char *comment_prefix);
static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix)
{
int starts_with(const char *str, const char *prefix);
int istarts_with(const char *str, const char *prefix);
+int starts_with_mem(const char *str, size_t len, const char *prefix);
/*
* If the string "str" is the same as the string in "prefix", then the "arg"
{
int ret;
- ret = git_config_set_in_file_gently(GITMODULES_FILE, key, value);
+ ret = git_config_set_in_file_gently(GITMODULES_FILE, key, NULL, value);
if (ret < 0)
/* Maybe the user already did that, don't error out here */
warning(_("Could not update .gitmodules entry %s"), key);
submodule_name_to_gitdir(&config_path, the_repository, sub->name);
strbuf_addstr(&config_path, "/config");
- if (git_config_set_in_file_gently(config_path.buf, "core.worktree", NULL))
+ if (git_config_set_in_file_gently(config_path.buf, "core.worktree", NULL, NULL))
warning(_("Could not unset core.worktree setting in submodule '%s'"),
sub->path);
ok 2 - plain with GIT_WORK_TREE
ok 3 - plain bare
+t/Makefile defines a target for each test file, such that you can also use
+shell pattern matching to run a subset of the tests:
+
+ make *checkout*
+
+will run all tests with 'checkout' in their filename.
+
Since the tests all output TAP (see https://testanything.org) they can
be run with any TAP harness. Here's an example of parallel testing
powered by a recent version of prove(1):
return argv + 1;
}
-static struct flag_definition pack_flags[] = { FLAG_DEF(PACK_REFS_PRUNE),
- FLAG_DEF(PACK_REFS_ALL),
- { NULL, 0 } };
-
-static int cmd_pack_refs(struct ref_store *refs, const char **argv)
-{
- unsigned int flags = arg_flags(*argv++, "flags", pack_flags);
- static struct ref_exclusions exclusions = REF_EXCLUSIONS_INIT;
- static struct string_list included_refs = STRING_LIST_INIT_NODUP;
- struct pack_refs_opts pack_opts = { .flags = flags,
- .exclusions = &exclusions,
- .includes = &included_refs };
-
- if (pack_opts.flags & PACK_REFS_ALL)
- string_list_append(pack_opts.includes, "*");
-
- return refs_pack_refs(refs, &pack_opts);
-}
-
static int cmd_create_symref(struct ref_store *refs, const char **argv)
{
const char *refname = notnull(*argv++, "refname");
};
static struct command commands[] = {
- { "pack-refs", cmd_pack_refs },
{ "create-symref", cmd_create_symref },
{ "delete-refs", cmd_delete_refs },
{ "rename-ref", cmd_rename_ref },
empty_tree sha1:4b825dc642cb6eb9a060e54bf8d69288fbee4904
empty_tree sha256:6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321
+
+blob17_1 sha1:263
+blob17_1 sha256:34
+
+blob17_2 sha1:410
+blob17_2 sha256:174
+
+blob17_3 sha1:523
+blob17_3 sha256:313
+
+blob17_4 sha1:790
+blob17_4 sha256:481
test -z "$(echo "; comment" | git -c core.commentchar=";" stripspace -s)"
'
+test_expect_success 'strip comments with changed comment string' '
+ test ! -z "$(echo "// comment" | git -c core.commentchar=// stripspace)" &&
+ test -z "$(echo "// comment" | git -c core.commentchar="//" stripspace -s)"
+'
+
+test_expect_success 'newline as commentchar is forbidden' '
+ test_must_fail git -c core.commentChar="$LF" stripspace -s 2>err &&
+ grep "core.commentchar cannot contain newline" err
+'
+
+test_expect_success 'empty commentchar is forbidden' '
+ test_must_fail git -c core.commentchar= stripspace -s 2>err &&
+ grep "core.commentchar must have at least one character" err
+'
+
test_expect_success '-c with single line' '
printf "# foo\n" >expect &&
printf "foo" | git stripspace -c >actual &&
-e '/^\[verse\]$/,/^$/ {
/^$/d;
/^\[verse\]$/d;
-
+ s/_//g;
+ s/++//g;
+ s/`//g;
s/{litdd}/--/g;
s/'\''\(git[ a-z-]*\)'\''/\1/g;
HEAD=$(git rev-parse --verify HEAD)
'
-test_expect_success 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' '
- N=`find .git/refs -type f | wc -l` &&
+test_expect_success 'pack-refs --prune --all' '
+ test_path_is_missing .git/packed-refs &&
+ git pack-refs --no-prune --all &&
+ test_path_is_file .git/packed-refs &&
+ N=$(find .git/refs -type f | wc -l) &&
test "$N" != 0 &&
- test-tool ref-store main pack-refs PACK_REFS_PRUNE,PACK_REFS_ALL &&
- N=`find .git/refs -type f` &&
+
+ git pack-refs --prune --all &&
+ test_path_is_file .git/packed-refs &&
+ N=$(find .git/refs -type f) &&
test -z "$N"
'
git pack-refs --include "refs/heads/pack*" --exclude "refs/heads/pack*" &&
test -f .git/refs/heads/dont_pack5'
+test_expect_success '--auto packs and prunes refs as usual' '
+ git branch auto &&
+ test_path_is_file .git/refs/heads/auto &&
+ git pack-refs --auto --all &&
+ test_path_is_missing .git/refs/heads/auto
+'
+
test_expect_success 'see if up-to-date packed refs are preserved' '
git branch q &&
git pack-refs --all --prune &&
test_cmp expect actual
'
+test_expect_success 'maintenance --auto unconditionally packs loose refs' '
+ git update-ref refs/heads/something HEAD &&
+ test_path_is_file .git/refs/heads/something &&
+ git rev-parse refs/heads/something >expect &&
+ git maintenance run --task=pack-refs --auto &&
+ test_path_is_missing .git/refs/heads/something &&
+ git rev-parse refs/heads/something >actual &&
+ test_cmp expect actual
+'
+
test_done
EOF
'
+test_expect_success 'ref transaction: fails gracefully when auto compaction fails' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+
+ test_commit A &&
+ for i in $(test_seq 10)
+ do
+ git branch branch-$i &&
+ for table in .git/reftable/*.ref
+ do
+ touch "$table.lock" || exit 1
+ done ||
+ exit 1
+ done &&
+ test_line_count = 13 .git/reftable/tables.list
+ )
+'
+
test_expect_success 'pack-refs: compacts tables' '
test_when_finished "rm -rf repo" &&
git init repo &&
test_line_count = 1 repo/.git/reftable/tables.list
'
+test_expect_success 'pack-refs: compaction raises locking errors' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ test_commit -C repo A &&
+ touch repo/.git/reftable/tables.list.lock &&
+ cat >expect <<-EOF &&
+ error: unable to compact stack: data is locked
+ EOF
+ test_must_fail git -C repo pack-refs 2>err &&
+ test_cmp expect err
+'
+
+for command in pack-refs gc "maintenance run --task=pack-refs"
+do
+test_expect_success "$command: auto compaction" '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+
+ test_commit A &&
+
+ # We need a bit of setup to ensure that git-gc(1) actually
+ # triggers, and that it does not write anything to the refdb.
+ git config gc.auto 1 &&
+ git config gc.autoDetach 0 &&
+ git config gc.reflogExpire never &&
+ git config gc.reflogExpireUnreachable never &&
+ test_oid blob17_1 | git hash-object -w --stdin &&
+
+ # The tables should have been auto-compacted, and thus auto
+ # compaction should not have to do anything.
+ ls -1 .git/reftable >tables-expect &&
+ test_line_count = 4 tables-expect &&
+ git $command --auto &&
+ ls -1 .git/reftable >tables-actual &&
+ test_cmp tables-expect tables-actual &&
+
+ test_oid blob17_2 | git hash-object -w --stdin &&
+
+ # Lock all tables write some refs. Auto-compaction will be
+ # unable to compact tables and thus fails gracefully, leaving
+ # the stack in a sub-optimal state.
+ ls .git/reftable/*.ref |
+ while read table
+ do
+ touch "$table.lock" || exit 1
+ done &&
+ git branch B &&
+ git branch C &&
+ rm .git/reftable/*.lock &&
+ test_line_count = 5 .git/reftable/tables.list &&
+
+ git $command --auto &&
+ test_line_count = 1 .git/reftable/tables.list
+ )
+'
+done
+
test_expect_success 'pack-refs: prunes stale tables' '
test_when_finished "rm -rf repo" &&
git init repo &&
cat > expect << EOF
[section]
- penguin = very blue
Movie = BadPhysics
UPPERCASE = true
- penguin = kingpin
+ penguin = gentoo # Pygoscelis papua
+ disposition = peckish # find fish
+ foo = bar #abc
+ spsp = value # and comment
+ htsp = value # and comment
[Sections]
WhatEver = Second
EOF
+test_expect_success 'append comments' '
+ git config --replace-all --comment="Pygoscelis papua" section.penguin gentoo &&
+ git config --comment="find fish" section.disposition peckish &&
+ git config --comment="#abc" section.foo bar &&
+
+ git config --comment="and comment" section.spsp value &&
+ git config --comment=" # and comment" section.htsp value &&
+
+ test_cmp expect .git/config
+'
+
+test_expect_success 'Prohibited LF in comment' '
+ test_must_fail git config --comment="a${LF}b" section.k v
+'
+
test_expect_success 'non-match result' 'test_cmp expect .git/config'
test_expect_success 'find mixed-case key by canonical name' '
test_expect_success 'stdin fails create with no new value' '
echo "create $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: create $a: missing <newvalue>" err
+ grep "fatal: create $a: missing <new-oid>" err
'
test_expect_success 'stdin fails create with too many arguments' '
test_expect_success 'stdin fails update with no new value' '
echo "update $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: update $a: missing <newvalue>" err
+ grep "fatal: update $a: missing <new-oid>" err
'
test_expect_success 'stdin fails update with too many arguments' '
test_expect_success 'stdin update ref fails with bad old value' '
echo "update $c $m does-not-exist" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
+ grep "fatal: update $c: invalid <old-oid>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin create ref fails with bad new value' '
echo "create $c does-not-exist" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
+ grep "fatal: create $c: invalid <new-oid>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin create ref fails with zero new value' '
echo "create $c " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: create $c: zero <newvalue>" err &&
+ grep "fatal: create $c: zero <new-oid>" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin delete ref fails with zero old value' '
echo "delete $a " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: delete $a: zero <oldvalue>" err &&
+ grep "fatal: delete $a: zero <old-oid>" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
test_expect_success 'stdin -z fails create with no new value' '
printf $F "create $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: create $a: unexpected end of input when reading <newvalue>" err
+ grep "fatal: create $a: unexpected end of input when reading <new-oid>" err
'
test_expect_success 'stdin -z fails create with too many arguments' '
test_expect_success 'stdin -z fails update with too few args' '
printf $F "update $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
+ grep "fatal: update $a: unexpected end of input when reading <old-oid>" err
'
test_expect_success 'stdin -z emits warning with empty new value' '
git update-ref $a $m &&
printf $F "update $a" "" "" >stdin &&
git update-ref -z --stdin <stdin 2>err &&
- grep "warning: update $a: missing <newvalue>, treating as zero" err &&
+ grep "warning: update $a: missing <new-oid>, treating as zero" err &&
test_must_fail git rev-parse --verify -q $a
'
test_expect_success 'stdin -z fails update with no new value' '
printf $F "update $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: update $a: unexpected end of input when reading <newvalue>" err
+ grep "fatal: update $a: unexpected end of input when reading <new-oid>" err
'
test_expect_success 'stdin -z fails update with no old value' '
printf $F "update $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
+ grep "fatal: update $a: unexpected end of input when reading <old-oid>" err
'
test_expect_success 'stdin -z fails update with too many arguments' '
test_expect_success 'stdin -z fails delete with no old value' '
printf $F "delete $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: delete $a: unexpected end of input when reading <oldvalue>" err
+ grep "fatal: delete $a: unexpected end of input when reading <old-oid>" err
'
test_expect_success 'stdin -z fails delete with too many arguments' '
test_expect_success 'stdin -z fails verify with no old value' '
printf $F "verify $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: verify $a: unexpected end of input when reading <oldvalue>" err
+ grep "fatal: verify $a: unexpected end of input when reading <old-oid>" err
'
test_expect_success 'stdin -z fails option with unknown name' '
test_expect_success 'stdin -z update ref fails with bad old value' '
printf $F "update $c" "$m" "does-not-exist" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
+ grep "fatal: update $c: invalid <old-oid>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
git update-ref -d "$c" &&
printf $F "create $c" "does-not-exist" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
+ grep "fatal: create $c: invalid <new-oid>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin -z create ref fails with empty new value' '
printf $F "create $c" "" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: create $c: missing <newvalue>" err &&
+ grep "fatal: create $c: missing <new-oid>" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin -z delete ref fails with zero old value' '
printf $F "delete $a" "$Z" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: delete $a: zero <oldvalue>" err &&
+ grep "fatal: delete $a: zero <old-oid>" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
git config branch.child.merge refs/heads/main &&
git checkout child^ &&
git checkout child >stdout &&
- test_cmp expect stdout
+ test_cmp expect stdout &&
+
+ git checkout --detach child >stdout &&
+ test_grep ! "can be fast-forwarded\." stdout
'
test_expect_success 'no advice given for explicit detached head state' '
sane_unset GIT_TEST_SPLIT_INDEX
test_set_index_version () {
- GIT_INDEX_VERSION="$1"
- export GIT_INDEX_VERSION
+ GIT_INDEX_VERSION="$1"
+ export GIT_INDEX_VERSION
}
test_set_index_version 3
-cat >expect.full <<EOF
-H 1
-H 2
-H sub/1
-H sub/2
-EOF
+test_expect_success 'setup' '
+ cat >expect.full <<-\EOF &&
+ H 1
+ H 2
+ H sub/1
+ H sub/2
+ EOF
-cat >expect.skip <<EOF
-S 1
-H 2
-S sub/1
-H sub/2
-EOF
+ cat >expect.skip <<-\EOF &&
+ S 1
+ H 2
+ S sub/1
+ H sub/2
+ EOF
-test_expect_success 'setup' '
mkdir sub &&
touch ./1 ./2 sub/1 sub/2 &&
git add 1 2 sub/1 sub/2 &&
hint: tracking ref '\''refs/heads/main'\'':
hint: ambi1
hint: ambi2
- hint: ''
+ hint:
hint: This is typically a configuration error.
- hint: ''
+ hint:
hint: To support setting up tracking branches, ensure that
hint: different remotes'\'' fetch refspecs map into different
hint: tracking namespaces.
test_cmp expect actual
'
+test_expect_success 'rebase --merge --empty=stop' '
+ git checkout -B testing localmods &&
+ test_must_fail git rebase --merge --empty=stop upstream &&
+
+ git rebase --skip &&
+
+ test_write_lines D C B A >expect &&
+ git log --format=%s >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'rebase --merge --empty=ask' '
git checkout -B testing localmods &&
test_must_fail git rebase --merge --empty=ask upstream &&
test_cmp expect actual
'
-test_expect_success 'rebase --interactive --empty=ask' '
+test_expect_success 'rebase --interactive --empty=stop' '
git checkout -B testing localmods &&
- test_must_fail git rebase --interactive --empty=ask upstream &&
+ test_must_fail git rebase --interactive --empty=stop upstream &&
git rebase --skip &&
test_cmp expect actual
'
-test_expect_success 'rebase --interactive uses default of --empty=ask' '
+test_expect_success 'rebase --interactive uses default of --empty=stop' '
git checkout -B testing localmods &&
test_must_fail git rebase --interactive upstream &&
test_path_is_missing .git/MERGE_MSG
'
+test_expect_success 'rebase --exec --empty=drop' '
+ git checkout -B testing localmods &&
+ git rebase --exec "true" --empty=drop upstream &&
+
+ test_write_lines D C B A >expect &&
+ git log --format=%s >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rebase --exec --empty=keep' '
+ git checkout -B testing localmods &&
+ git rebase --exec "true" --empty=keep upstream &&
+
+ test_write_lines D C2 C B A >expect &&
+ git log --format=%s >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rebase --exec uses default of --empty=keep' '
+ git checkout -B testing localmods &&
+ git rebase --exec "true" upstream &&
+
+ test_write_lines D C2 C B A >expect &&
+ git log --format=%s >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rebase --exec --empty=stop' '
+ git checkout -B testing localmods &&
+ test_must_fail git rebase --exec "true" --empty=stop upstream &&
+
+ git rebase --skip &&
+
+ test_write_lines D C B A >expect &&
+ git log --format=%s >actual &&
+ test_cmp expect actual
+'
+
test_done
'
test_expect_success 'cherry-pick on unborn branch' '
- git checkout --orphan unborn &&
+ git switch --orphan unborn &&
git rm --cached -r . &&
- rm -rf * &&
git cherry-pick initial &&
- git diff --quiet initial &&
+ git diff --exit-code initial &&
+ test_cmp_rev ! initial HEAD
+'
+
+test_expect_success 'cherry-pick on unborn branch with --allow-empty' '
+ git checkout --detach &&
+ git branch -D unborn &&
+ git switch --orphan unborn &&
+ git cherry-pick initial --allow-empty &&
+ git diff --exit-code initial &&
test_cmp_rev ! initial HEAD
'
git commit -m "add file2 on the side"
'
-test_expect_success 'cherry-pick a no-op without --keep-redundant' '
+test_expect_success 'cherry-pick a no-op with neither --keep-redundant nor --empty' '
git reset --hard &&
git checkout fork^0 &&
test_must_fail git cherry-pick main
test_cmp expect actual
'
+test_expect_success '--keep-redundant-commits is incompatible with operations' '
+ test_must_fail git cherry-pick HEAD 2>output &&
+ test_grep "The previous cherry-pick is now empty" output &&
+ test_must_fail git cherry-pick --keep-redundant-commits --continue 2>output &&
+ test_grep "fatal: cherry-pick: --keep-redundant-commits cannot be used with --continue" output &&
+ test_must_fail git cherry-pick --keep-redundant-commits --skip 2>output &&
+ test_grep "fatal: cherry-pick: --keep-redundant-commits cannot be used with --skip" output &&
+ test_must_fail git cherry-pick --keep-redundant-commits --abort 2>output &&
+ test_grep "fatal: cherry-pick: --keep-redundant-commits cannot be used with --abort" output &&
+ test_must_fail git cherry-pick --keep-redundant-commits --quit 2>output &&
+ test_grep "fatal: cherry-pick: --keep-redundant-commits cannot be used with --quit" output &&
+ git cherry-pick --abort
+'
+
+test_expect_success '--empty is incompatible with operations' '
+ test_must_fail git cherry-pick HEAD 2>output &&
+ test_grep "The previous cherry-pick is now empty" output &&
+ test_must_fail git cherry-pick --empty=stop --continue 2>output &&
+ test_grep "fatal: cherry-pick: --empty cannot be used with --continue" output &&
+ test_must_fail git cherry-pick --empty=stop --skip 2>output &&
+ test_grep "fatal: cherry-pick: --empty cannot be used with --skip" output &&
+ test_must_fail git cherry-pick --empty=stop --abort 2>output &&
+ test_grep "fatal: cherry-pick: --empty cannot be used with --abort" output &&
+ test_must_fail git cherry-pick --empty=stop --quit 2>output &&
+ test_grep "fatal: cherry-pick: --empty cannot be used with --quit" output &&
+ git cherry-pick --abort
+'
+
+test_expect_success 'cherry-pick a no-op with --empty=stop' '
+ git reset --hard &&
+ git checkout fork^0 &&
+ test_must_fail git cherry-pick --empty=stop main 2>output &&
+ test_grep "The previous cherry-pick is now empty" output
+'
+
+test_expect_success 'cherry-pick a no-op with --empty=drop' '
+ git reset --hard &&
+ git checkout fork^0 &&
+ git cherry-pick --empty=drop main &&
+ test_commit_message HEAD -m "add file2 on the side"
+'
+
+test_expect_success 'cherry-pick a no-op with --empty=keep' '
+ git reset --hard &&
+ git checkout fork^0 &&
+ git cherry-pick --empty=keep main &&
+ test_commit_message HEAD -m "add file2 on main"
+'
+
test_done
test_cmp expect actual
'
+test_expect_success 'cherry-pick persists --empty=stop correctly' '
+ pristine_detach yetanotherpick &&
+ # Picking `anotherpick` forces a conflict so that we stop. That
+ # commit is then skipped, after which we pick `yetanotherpick`
+ # while already on `yetanotherpick` to cause an empty commit
+ test_must_fail git cherry-pick --empty=stop anotherpick yetanotherpick &&
+ test_must_fail git cherry-pick --skip 2>msg &&
+ test_grep "The previous cherry-pick is now empty" msg &&
+ rm msg &&
+ git cherry-pick --abort
+'
+
+test_expect_success 'cherry-pick persists --empty=drop correctly' '
+ pristine_detach yetanotherpick &&
+ # Picking `anotherpick` forces a conflict so that we stop. That
+ # commit is then skipped, after which we pick `yetanotherpick`
+ # while already on `yetanotherpick` to cause an empty commit
+ test_must_fail git cherry-pick --empty=drop anotherpick yetanotherpick &&
+ git cherry-pick --skip &&
+ test_cmp_rev yetanotherpick HEAD
+'
+
+test_expect_success 'cherry-pick persists --empty=keep correctly' '
+ pristine_detach yetanotherpick &&
+ # Picking `anotherpick` forces a conflict so that we stop. That
+ # commit is then skipped, after which we pick `yetanotherpick`
+ # while already on `yetanotherpick` to cause an empty commit
+ test_must_fail git cherry-pick --empty=keep anotherpick yetanotherpick &&
+ git cherry-pick --skip &&
+ test_cmp_rev yetanotherpick HEAD^
+'
+
test_expect_success 'revert persists opts correctly' '
pristine_detach initial &&
# to make sure that the session to revert a sequence
touch foo && git add foo
'
+test_expect_success 'Test with no pathspecs' '
+ cat >expect <<-EOF &&
+ Nothing specified, nothing added.
+ hint: Maybe you wanted to say ${SQ}git add .${SQ}?
+ hint: Disable this message with "git config advice.addEmptyPathspec false"
+ EOF
+ git add 2>actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'Post-check that foo is in the index' '
git ls-files foo | grep foo
'
)
'
+test_expect_success '"git add" a embedded repository' '
+ rm -fr outer && git init outer &&
+ (
+ cd outer &&
+ for i in 1 2
+ do
+ name=inner$i &&
+ git init $name &&
+ git -C $name commit --allow-empty -m $name ||
+ return 1
+ done &&
+ git add . 2>actual &&
+ cat >expect <<-EOF &&
+ warning: adding embedded git repository: inner1
+ hint: You${SQ}ve added another git repository inside your current repository.
+ hint: Clones of the outer repository will not contain the contents of
+ hint: the embedded repository and will not know how to obtain it.
+ hint: If you meant to add a submodule, use:
+ hint:
+ hint: git submodule add <url> inner1
+ hint:
+ hint: If you added this path by mistake, you can remove it from the
+ hint: index with:
+ hint:
+ hint: git rm --cached inner1
+ hint:
+ hint: See "git help submodule" for more information.
+ hint: Disable this message with "git config advice.addEmbeddedRepo false"
+ warning: adding embedded git repository: inner2
+ EOF
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'error on a repository with no commits' '
rm -fr empty &&
git init empty &&
The following paths are ignored by one of your .gitignore files:
ignored-file
hint: Use -f if you really want to add them.
-hint: Turn this message off by running
-hint: "git config advice.addIgnoredFile false"
+hint: Disable this message with "git config advice.addIgnoredFile false"
EOF
cat >expect.out <<\EOF
add 'track-this'
git -c core.filemode=true add -p >actual &&
sed -n "s/^\(([0-9/]*) Stage .*?\).*/\1/p" actual >actual.filtered &&
cat >expect <<-\EOF &&
- (1/1) Stage deletion [y,n,q,a,d,?]?
- (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,?]?
- (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]?
+ (1/1) Stage deletion [y,n,q,a,d,p,?]?
+ (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,p,?]?
+ (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]?
EOF
test_cmp expect actual.filtered
'
test_expect_success 'goto hunk' '
test_when_finished "git reset" &&
tr _ " " >expect <<-EOF &&
- (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? + 1: -1,2 +1,3 +15
+ (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? + 1: -1,2 +1,3 +15
_ 2: -2,4 +3,8 +21
go to which hunk? @@ -1,2 +1,3 @@
_10
+15
_20
- (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
+ (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_
EOF
test_write_lines s y g 1 | git add -p >actual &&
tail -n 7 <actual >actual.trimmed &&
test_expect_success 'navigate to hunk via regex' '
test_when_finished "git reset" &&
tr _ " " >expect <<-EOF &&
- (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? @@ -1,2 +1,3 @@
+ (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? @@ -1,2 +1,3 @@
_10
+15
_20
- (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
+ (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_
EOF
test_write_lines s y /1,2 | git add -p >actual &&
tail -n 5 <actual >actual.trimmed &&
<BLUE>+<RESET><BLUE>new<RESET>
<CYAN> more-context<RESET>
<BLUE>+<RESET><BLUE>another-one<RESET>
- <YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? <RESET><BOLD>Split into 2 hunks.<RESET>
+ <YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,?]? <RESET><BOLD>Split into 2 hunks.<RESET>
<MAGENTA>@@ -1,3 +1,3 @@<RESET>
<CYAN> context<RESET>
<BOLD>-old<RESET>
<BLUE>+<RESET><BLUE>new<RESET>
<CYAN> more-context<RESET>
- <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>
+ <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>
<CYAN> more-context<RESET>
<BLUE>+<RESET><BLUE>another-one<RESET>
- <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>
+ <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>
<CYAN> context<RESET>
<BOLD>-old<RESET>
<BLUE>+new<RESET>
<CYAN> more-context<RESET>
- <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? <RESET>
+ <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET>
EOF
test_cmp expect actual
'
git diff --exit-code
'
+test_expect_success !MINGW 'apply with no-contents and a funny pathname' '
+ test_when_finished "rm -fr \"funny \"; git reset --hard" &&
+
+ mkdir "funny " &&
+ >"funny /empty" &&
+ git add "funny /empty" &&
+ git diff HEAD -- "funny /" >sample.patch &&
+ git diff -R HEAD -- "funny /" >elpmas.patch &&
+
+ git reset --hard &&
+
+ git apply --stat --check --apply sample.patch &&
+ test_must_be_empty "funny /empty" &&
+
+ git apply --stat --check --apply elpmas.patch &&
+ test_path_is_missing "funny /empty" &&
+
+ git apply -R --stat --check --apply elpmas.patch &&
+ test_must_be_empty "funny /empty" &&
+
+ git apply -R --stat --check --apply sample.patch &&
+ test_path_is_missing "funny /empty"
+'
+
test_done
>bar &&
git add foo &&
test_tick &&
- git config i18n.commitEncoding $test_encoding &&
+ test_config i18n.commitEncoding $test_encoding &&
commit_msg $test_encoding | git commit -F - &&
git add bar &&
test_tick &&
- git commit -m "add bar" &&
- git config --unset i18n.commitEncoding
+ git commit -m "add bar"
'
test_expect_success 'alias builtin format' '
git log --pretty=oneline >expected &&
- git config pretty.test-alias oneline &&
+ test_config pretty.test-alias oneline &&
git log --pretty=test-alias >actual &&
test_cmp expected actual
'
test_expect_success 'alias masking builtin format' '
git log --pretty=oneline >expected &&
- git config pretty.oneline "%H" &&
+ test_config pretty.oneline "%H" &&
git log --pretty=oneline >actual &&
test_cmp expected actual
'
test_expect_success 'alias user-defined format' '
git log --pretty="format:%h" >expected &&
- git config pretty.test-alias "format:%h" &&
+ test_config pretty.test-alias "format:%h" &&
git log --pretty=test-alias >actual &&
test_cmp expected actual
'
+test_expect_success 'alias user-defined format is matched case-insensitively' '
+ git log --pretty="format:%h" >expected &&
+ test_config pretty.testone "format:%h" &&
+ test_config pretty.testtwo testOne &&
+ git log --pretty=testTwo >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'alias user-defined tformat with %s (ISO8859-1 encoding)' '
- git config i18n.logOutputEncoding $test_encoding &&
+ test_config i18n.logOutputEncoding $test_encoding &&
git log --oneline >expected-s &&
git log --pretty="tformat:%h %s" >actual-s &&
- git config --unset i18n.logOutputEncoding &&
test_cmp expected-s actual-s
'
test_expect_success 'alias user-defined tformat' '
git log --pretty="tformat:%h" >expected &&
- git config pretty.test-alias "tformat:%h" &&
+ test_config pretty.test-alias "tformat:%h" &&
git log --pretty=test-alias >actual &&
test_cmp expected actual
'
test_expect_success 'alias non-existent format' '
- git config pretty.test-alias format-that-will-never-exist &&
+ test_config pretty.test-alias format-that-will-never-exist &&
test_must_fail git log --pretty=test-alias
'
test_expect_success 'alias of an alias' '
git log --pretty="tformat:%h" >expected &&
- git config pretty.test-foo "tformat:%h" &&
- git config pretty.test-bar test-foo &&
+ test_config pretty.test-foo "tformat:%h" &&
+ test_config pretty.test-bar test-foo &&
git log --pretty=test-bar >actual && test_cmp expected actual
'
test_expect_success 'alias masking an alias' '
git log --pretty=format:"Two %H" >expected &&
- git config pretty.duplicate "format:One %H" &&
- git config --add pretty.duplicate "format:Two %H" &&
+ test_config pretty.duplicate "format:One %H" &&
+ test_config pretty.duplicate "format:Two %H" --add &&
git log --pretty=duplicate >actual &&
test_cmp expected actual
'
test_expect_success 'alias loop' '
- git config pretty.test-foo test-bar &&
- git config pretty.test-bar test-foo &&
+ test_config pretty.test-foo test-bar &&
+ test_config pretty.test-bar test-foo &&
test_must_fail git log --pretty=test-foo
'
export GIT_DIR
force=
+object_format=
mkdir -p "$dir"
echo
;;
list)
- echo ":object-format $(git rev-parse --show-object-format=storage)"
+ test -n "$object_format" &&
+ echo ":object-format $(git rev-parse --show-object-format=storage)"
git for-each-ref --format='? %(refname)' 'refs/heads/' 'refs/tags/'
head=$(git symbolic-ref HEAD)
echo "@$head HEAD"
# behavior, make sure we always pack everything to one pack by
# default
git config gc.bigPackThreshold 2g &&
-
- # These are simply values which, when hashed as a blob with a newline,
- # produce a hash where the first byte is 0x17 in their respective
- # algorithms.
- test_oid_cache <<-EOF
- obj1 sha1:263
- obj1 sha256:34
-
- obj2 sha1:410
- obj2 sha256:174
-
- obj3 sha1:523
- obj3 sha256:313
-
- obj4 sha1:790
- obj4 sha256:481
- EOF
+ test_oid_init
'
test_expect_success 'gc empty repository' '
# We need to create two object whose sha1s start with 17
# since this is what git gc counts. As it happens, these
# two blobs will do so.
- test_commit "$(test_oid obj1)" &&
- test_commit "$(test_oid obj2)" &&
+ test_commit "$(test_oid blob17_1)" &&
+ test_commit "$(test_oid blob17_2)" &&
git gc --auto >../out.actual 2>../err.actual
) &&
# We need to create two object whose sha1s start with 17
# since this is what git gc counts. As it happens, these
# two blobs will do so.
- test_commit "$(test_oid obj1)" &&
- test_commit "$(test_oid obj2)" &&
+ test_commit "$(test_oid blob17_1)" &&
+ test_commit "$(test_oid blob17_2)" &&
# Our first gc will create a pack; our second will create a second pack
git gc --auto &&
ls .git/objects/pack/pack-*.pack | sort >existing_packs &&
- test_commit "$(test_oid obj3)" &&
- test_commit "$(test_oid obj4)" &&
+ test_commit "$(test_oid blob17_3)" &&
+ test_commit "$(test_oid blob17_4)" &&
git gc --auto 2>err &&
test_grep ! "^warning:" err &&
'
test_expect_success 'recursive tagging should give advice' '
- sed -e "s/|$//" <<-EOF >expect &&
+ cat >expect <<-EOF &&
hint: You have created a nested tag. The object referred to by your new tag is
hint: already a tag. If you meant to tag the object that it points to, use:
- hint: |
+ hint:
hint: git tag -f nested annotated-v4.0^{}
hint: Disable this message with "git config advice.nestedTag false"
EOF
The following paths are ignored by one of your .gitignore files:
submod
hint: Use -f if you really want to add them.
- hint: Turn this message off by running
- hint: "git config advice.addIgnoredFile false"
+ hint: Disable this message with "git config advice.addIgnoredFile false"
EOF
# Does not use test_commit due to the ignore
echo "*" > .gitignore &&
test_grep "Aborting commit due to empty commit message." err
'
+test_expect_success 'verbose diff is stripped with multi-byte comment char' '
+ (
+ GIT_EDITOR=cat &&
+ export GIT_EDITOR &&
+ test_must_fail git -c core.commentchar="foo>" commit -a -v >out 2>err
+ ) &&
+ grep "^foo> " out &&
+ test_grep "Aborting commit due to empty commit message." err
+'
+
test_expect_success 'status does not verbose without --verbose' '
git status >actual &&
! grep "^diff --git" actual
test_expect_success "status (core.commentchar with two chars with submodule summary)" '
test_config core.commentchar ";;" &&
- test_must_fail git -c status.displayCommentPrefix=true status
+ sed "s/^/;/" <expect >expect.double &&
+ git -c status.displayCommentPrefix=true status >output &&
+ test_cmp expect.double output
'
test_expect_success "--ignore-submodules=all suppresses submodule summary" '
# Tests that verify the scheduler integration must set this locally
# to avoid errors.
GIT_TEST_MAINT_SCHEDULER="none:exit 1"
+export GIT_TEST_MAINT_SCHEDULER
# Does this platform support `git fsmonitor--daemon`
#
clear_prio_queue(&pq);
}
-#define BASIC_INPUT 2, 6, 3, 10, 9, 5, 7, 4, 5, 8, 1, DUMP
-#define BASIC_RESULT 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10
-
-#define MIXED_PUT_GET_INPUT 6, 2, 4, GET, 5, 3, GET, GET, 1, DUMP
-#define MIXED_PUT_GET_RESULT 2, 3, 4, 1, 5, 6
-
-#define EMPTY_QUEUE_INPUT 1, 2, GET, GET, GET, 1, 2, GET, GET, GET
-#define EMPTY_QUEUE_RESULT 1, 2, MISSING, 1, 2, MISSING
-
-#define STACK_INPUT STACK, 8, 1, 5, 4, 6, 2, 3, DUMP
-#define STACK_RESULT 3, 2, 6, 4, 5, 1, 8
-
-#define REVERSE_STACK_INPUT STACK, 1, 2, 3, 4, 5, 6, REVERSE, DUMP
-#define REVERSE_STACK_RESULT 1, 2, 3, 4, 5, 6
-
-#define TEST_INPUT(INPUT, RESULT, name) \
- static void test_##name(void) \
-{ \
- int input[] = {INPUT}; \
- int result[] = {RESULT}; \
- test_prio_queue(input, ARRAY_SIZE(input), \
- result, ARRAY_SIZE(result)); \
-}
-
-TEST_INPUT(BASIC_INPUT, BASIC_RESULT, basic)
-TEST_INPUT(MIXED_PUT_GET_INPUT, MIXED_PUT_GET_RESULT, mixed)
-TEST_INPUT(EMPTY_QUEUE_INPUT, EMPTY_QUEUE_RESULT, empty)
-TEST_INPUT(STACK_INPUT, STACK_RESULT, stack)
-TEST_INPUT(REVERSE_STACK_INPUT, REVERSE_STACK_RESULT, reverse)
+#define TEST_INPUT(input, result) \
+ test_prio_queue(input, ARRAY_SIZE(input), result, ARRAY_SIZE(result))
int cmd_main(int argc, const char **argv)
{
- TEST(test_basic(), "prio-queue works for basic input");
- TEST(test_mixed(), "prio-queue works for mixed put & get commands");
- TEST(test_empty(), "prio-queue works when queue is empty");
- TEST(test_stack(), "prio-queue works when used as a LIFO stack");
- TEST(test_reverse(), "prio-queue works when LIFO stack is reversed");
+ TEST(TEST_INPUT(((int []){ 2, 6, 3, 10, 9, 5, 7, 4, 5, 8, 1, DUMP }),
+ ((int []){ 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10 })),
+ "prio-queue works for basic input");
+ TEST(TEST_INPUT(((int []){ 6, 2, 4, GET, 5, 3, GET, GET, 1, DUMP }),
+ ((int []){ 2, 3, 4, 1, 5, 6 })),
+ "prio-queue works for mixed put & get commands");
+ TEST(TEST_INPUT(((int []){ 1, 2, GET, GET, GET, 1, 2, GET, GET, GET }),
+ ((int []){ 1, 2, MISSING, 1, 2, MISSING })),
+ "prio-queue works when queue is empty");
+ TEST(TEST_INPUT(((int []){ STACK, 8, 1, 5, 4, 6, 2, 3, DUMP }),
+ ((int []){ 3, 2, 6, 4, 5, 1, 8 })),
+ "prio-queue works when used as a LIFO stack");
+ TEST(TEST_INPUT(((int []){ STACK, 1, 2, 3, 4, 5, 6, REVERSE, DUMP }),
+ ((int []){ 1, 2, 3, 4, 5, 6 })),
+ "prio-queue works when LIFO stack is reversed");
return test_done();
}
/* The first paragraph is the title and cannot be trailers */
for (s = buf; s < buf + len; s = next_line(s)) {
- if (s[0] == comment_line_char)
+ if (starts_with_mem(s, buf + len - s, comment_line_str))
continue;
if (is_blank_line(s))
break;
const char **p;
ssize_t separator_pos;
- if (bol[0] == comment_line_char) {
+ if (starts_with_mem(bol, buf + len - bol, comment_line_str)) {
non_trailer_lines += possible_continuation_lines;
possible_continuation_lines = 0;
continue;
for (i = 0; i < info->trailer_nr; i++) {
int separator_pos;
char *trailer = info->trailers[i];
- if (trailer[0] == comment_line_char)
+ if (starts_with(trailer, comment_line_str))
continue;
separator_pos = find_separator(trailer, separators);
if (separator_pos >= 1) {
data->get_refs_list_called = 1;
helper = get_helper(transport);
- if (data->object_format) {
- write_str_in_full(helper->in, "option object-format\n");
- if (recvline(data, &buf) || strcmp(buf.buf, "ok"))
- exit(128);
- }
+ if (data->object_format)
+ set_helper_option(transport, "object-format", "true");
if (data->push && for_push)
- write_str_in_full(helper->in, "list for-push\n");
+ write_constant(helper->in, "list for-push\n");
else
- write_str_in_full(helper->in, "list\n");
+ write_constant(helper->in, "list\n");
while (1) {
char *eov, *eon;
static int move_config_setting(const char *key, const char *value,
const char *from_file, const char *to_file)
{
- if (git_config_set_in_file_gently(to_file, key, value))
+ if (git_config_set_in_file_gently(to_file, key, NULL, value))
return error(_("unable to set %s in '%s'"), key, to_file);
- if (git_config_set_in_file_gently(from_file, key, NULL))
+ if (git_config_set_in_file_gently(from_file, key, NULL, NULL))
return error(_("unable to unset %s in '%s'"), key, from_file);
return 0;
}
strbuf_vaddf(&sb, fmt, ap);
if (!sb.len) {
if (s->display_comment_prefix) {
- strbuf_addch(&sb, comment_line_char);
+ strbuf_addstr(&sb, comment_line_str);
if (!trail)
strbuf_addch(&sb, ' ');
}
strbuf_reset(&linebuf);
if (at_bol && s->display_comment_prefix) {
- strbuf_addch(&linebuf, comment_line_char);
+ strbuf_addstr(&linebuf, comment_line_str);
if (*line != '\n' && *line != '\t')
strbuf_addch(&linebuf, ' ');
}
if (s->display_comment_prefix) {
size_t len;
summary_content = strbuf_detach(&summary, &len);
- strbuf_add_commented_lines(&summary, summary_content, len, comment_line_char);
+ strbuf_add_commented_lines(&summary, summary_content, len, comment_line_str);
free(summary_content);
}
const char *p;
struct strbuf pattern = STRBUF_INIT;
- strbuf_addf(&pattern, "\n%c %s", comment_line_char, cut_line);
+ strbuf_addf(&pattern, "\n%s %s", comment_line_str, cut_line);
if (starts_with(s, pattern.buf + 1))
len = 0;
else if ((p = strstr(s, pattern.buf))) {
{
const char *explanation = _("Do not modify or remove the line above.\nEverything below it will be ignored.");
- strbuf_commented_addf(buf, comment_line_char, "%s", cut_line);
- strbuf_add_commented_lines(buf, explanation, strlen(explanation), comment_line_char);
+ strbuf_commented_addf(buf, comment_line_str, "%s", cut_line);
+ strbuf_add_commented_lines(buf, explanation, strlen(explanation), comment_line_str);
}
void wt_status_add_cut_line(struct wt_status *s)
struct strbuf sb = STRBUF_INIT;
const char *cp, *ep, *branch_name;
struct branch *branch;
- char comment_line_string[3];
- int i;
uint64_t t_begin = 0;
assert(s->branch && !s->is_initial);
}
}
- i = 0;
- if (s->display_comment_prefix) {
- comment_line_string[i++] = comment_line_char;
- comment_line_string[i++] = ' ';
- }
- comment_line_string[i] = '\0';
-
for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
- "%s%.*s", comment_line_string,
+ "%s%s%.*s",
+ s->display_comment_prefix ? comment_line_str : "",
+ s->display_comment_prefix ? " " : "",
(int)(ep - cp), cp);
if (s->display_comment_prefix)
- color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%c",
- comment_line_char);
+ color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%s",
+ comment_line_str);
else
fputs("\n", s->fp);
strbuf_release(&sb);
git_path("%s", fname));
}
while (!strbuf_getline_lf(&line, f)) {
- if (line.len && line.buf[0] == comment_line_char)
+ if (starts_with(line.buf, comment_line_str))
continue;
strbuf_trim(&line);
if (!line.len)