aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes/2.46.0.txt44
-rw-r--r--Documentation/SubmittingPatches201
-rw-r--r--Documentation/diff-options.txt5
-rw-r--r--Documentation/git-format-patch.txt5
-rw-r--r--Documentation/git-rev-parse.txt15
-rw-r--r--Documentation/git-update-index.txt1
-rw-r--r--Documentation/glossary-content.txt72
-rw-r--r--Makefile5
-rw-r--r--builtin/config.c970
-rw-r--r--builtin/for-each-ref.c2
-rw-r--r--builtin/interpret-trailers.c12
-rw-r--r--builtin/log.c2
-rw-r--r--builtin/pack-objects.c19
-rw-r--r--config.c4
-rw-r--r--config.h2
-rw-r--r--contrib/completion/git-completion.bash42
-rw-r--r--contrib/credential/osxkeychain/git-credential-osxkeychain.c14
-rw-r--r--fetch-pack.c5
-rwxr-xr-xgit-send-email.perl11
-rw-r--r--mergetools/vimdiff2
-rw-r--r--midx-write.c17
-rw-r--r--object.h1
-rw-r--r--pack-bitmap-write.c248
-rw-r--r--pack-bitmap.h38
-rw-r--r--range-diff.h6
-rw-r--r--ref-filter.c16
-rw-r--r--ref-filter.h4
-rw-r--r--refs.c98
-rw-r--r--refs.h48
-rw-r--r--refs/files-backend.c7
-rw-r--r--refs/reftable-backend.c3
-rw-r--r--sequencer.c27
-rwxr-xr-xt/t0017-env-helper.sh9
-rwxr-xr-xt/t0211-trace2-perf.sh2
-rwxr-xr-xt/t0600-reffiles-backend.sh2
-rwxr-xr-xt/t1300-config.sh9
-rwxr-xr-xt/t1404-update-ref-errors.sh2
-rwxr-xr-xt/t1700-split-index.sh2
-rwxr-xr-xt/t4202-log.sh2
-rwxr-xr-xt/t5510-fetch.sh22
-rwxr-xr-xt/t6302-for-each-ref-filter.sh34
-rwxr-xr-xt/t9001-send-email.sh2
-rwxr-xr-xt/t9118-git-svn-funky-branch-names.sh2
-rwxr-xr-xt/t9902-completion.sh58
-rw-r--r--t/unit-tests/t-trailer.c315
-rw-r--r--trailer.c167
-rw-r--r--trailer.h93
47 files changed, 1767 insertions, 900 deletions
diff --git a/Documentation/RelNotes/2.46.0.txt b/Documentation/RelNotes/2.46.0.txt
index ff3ce813da..28835de05f 100644
--- a/Documentation/RelNotes/2.46.0.txt
+++ b/Documentation/RelNotes/2.46.0.txt
@@ -40,6 +40,15 @@ UI, Workflows & Features
* Updates to symbolic refs can now be made as a part of ref
transaction.
+ * The trailer API has been reshuffled a bit.
+
+ * Terminology to call various ref-like things are getting
+ straightened out.
+
+ * The command line completion script (in contrib/) has been adjusted
+ to the recent update to "git config" that adopted subcommand based
+ UI.
+
Performance, Internal Implementation, Development Support etc.
@@ -74,6 +83,19 @@ Performance, Internal Implementation, Development Support etc.
primary ref_store by forcing the callers to pass a ref_store as an
argument.
+ * Code clean-up to reduce inter-function communication inside
+ builtin/config.c done via the use of global variables.
+
+ * The pack bitmap code saw some clean-up to prepare for a follow-up topic.
+ (merge 85f360fee5 tb/pack-bitmap-write-cleanups later to maint).
+
+ * Preliminary code clean-up for "git send-email".
+ (merge c02dc38570 ds/send-email-per-message-block later to maint).
+
+ * The default "creation-factor" used by "git format-patch" has been
+ raised to make it more aggressively find matching commits.
+ (merge c22d41d641 jc/format-patch-more-aggressive-range-diff later to maint).
+
Fixes since v2.45
-----------------
@@ -140,6 +162,24 @@ Fixes since v2.45
the wrong order almost consistently, which has been corrected.
(merge f01301aabe jc/compat-regex-calloc-fix later to maint).
+ * Expose "name conflict" error when a ref creation fails due to D/F
+ conflict in the ref namespace, to improve an error message given by
+ "git fetch".
+ (merge 9339fca23e it/refs-name-conflict later to maint).
+
+ * The SubmittingPatches document now refers folks to manpages
+ translation project.
+ (merge 558a5b8cd0 jc/doc-manpages-l10n later to maint).
+
+ * The documentation for "git diff --name-only" has been clarified
+ that it is about showing the names in the post-image tree.
+ (merge 4986662cbc jc/doc-diff-name-only later to maint).
+
+ * The credential helper that talks with osx keychain learned to avoid
+ storing back the authentication material it just got received from
+ the keychain.
+ (merge e1ab45b2da kn/osxkeychain-skip-idempotent-store later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge 4cf6e7bf5e jt/doc-submitting-rerolled-series later to maint).
(merge a5a4cb7b27 rs/diff-parseopts-cleanup later to maint).
@@ -148,3 +188,7 @@ Fixes since v2.45
(merge 55702c543e fa/p4-error later to maint).
(merge 2566a77774 vd/doc-merge-tree-x-option later to maint).
(merge b64b0df9da ds/scalar-reconfigure-all-fix later to maint).
+ (merge c81ffcff83 dm/update-index-doc-fix later to maint).
+ (merge fc0202b0e9 dg/fetch-pack-code-cleanup later to maint).
+ (merge 7150f140f9 mt/t0211-typofix later to maint).
+ (merge d424488901 jc/rev-parse-fatal-doc later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 0690ae2140..d8a8caa791 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -7,6 +7,73 @@ Here are some guidelines for contributing back to this
project. There is also a link:MyFirstContribution.html[step-by-step tutorial]
available which covers many of these same guidelines.
+[[patch-flow]]
+=== A typical life cycle of a patch series
+
+To help us understand the reason behind various guidelines given later
+in the document, first let's understand how the life cycle of a
+typical patch series for this project goes.
+
+. You come up with an itch. You code it up. You do not need any
+ pre-authorization from the project to do so.
++
+Your patches will be reviewed by other contributors on the mailing
+list, and the reviews will be done to assess the merit of various
+things, like the general idea behind your patch (including "is it
+solving a problem worth solving in the first place?"), the reason
+behind the design of the solution, and the actual implementation.
+The guidelines given here are there to help your patches by making
+them easier to understand by the reviewers.
+
+. You send the patches to the list and cc people who may need to know
+ about the change. Your goal is *not* necessarily to convince others
+ that what you are building is good. Your goal is to get help in
+ coming up with a solution for the "itch" that is better than what
+ you can build alone.
++
+The people who may need to know are the ones who worked on the code
+you are touching. These people happen to be the ones who are
+most likely to be knowledgeable enough to help you, but
+they have no obligation to help you (i.e. you ask them for help,
+you don't demand). +git log -p {litdd} _$area_you_are_modifying_+ would
+help you find out who they are.
+
+. You get comments and suggestions for improvements. You may even get
+ them in an "on top of your change" patch form. You are expected to
+ respond to them with "Reply-All" on the mailing list, while taking
+ them into account while preparing an updated set of patches.
+
+. Polish, refine, and re-send your patches to the list and to the people
+ who spent their time to improve your patch. Go back to step (2).
+
+. While the above iterations improve your patches, the maintainer may
+ pick the patches up from the list and queue them to the `seen`
+ branch, in order to make it easier for people to play with it
+ without having to pick up and apply the patches to their trees
+ themselves. Being in `seen` has no other meaning. Specifically, it
+ does not mean the patch was "accepted" in any way.
+
+. When the discussion reaches a consensus that the latest iteration of
+ the patches are in good enough shape, the maintainer includes the
+ topic in the "What's cooking" report that are sent out a few times a
+ week to the mailing list, marked as "Will merge to 'next'." This
+ decision is primarily made by the maintainer with help from those
+ who participated in the review discussion.
+
+. After the patches are merged to the 'next' branch, the discussion
+ can still continue to further improve them by adding more patches on
+ top, but by the time a topic gets merged to 'next', it is expected
+ that everybody agrees that the scope and the basic direction of the
+ topic are appropriate, so such an incremental updates are limited to
+ small corrections and polishing. After a topic cooks for some time
+ (like 7 calendar days) in 'next' without needing further tweaks on
+ top, it gets merged to the 'master' branch and wait to become part
+ of the next major release.
+
+In the following sections, many techniques and conventions are listed
+to help your patches get reviewed effectively in such a life cycle.
+
+
[[choose-starting-point]]
=== Choose a starting point.
@@ -192,8 +259,9 @@ reasons:
which case, they can explain why they extend your code to cover
files, too).
-The goal of your log message is to convey the _why_ behind your
-change to help future developers.
+The goal of your log message is to convey the _why_ behind your change
+to help future developers. The reviewers will also make sure that
+your proposed log message will serve this purpose well.
The first line of the commit message should be a short description (50
characters is the soft limit, see DISCUSSION in linkgit:git-commit[1]),
@@ -540,6 +608,85 @@ patch, format it as "multipart/signed", not a text/plain message
that starts with `-----BEGIN PGP SIGNED MESSAGE-----`. That is
not a text/plain, it's something else.
+=== Handling Conflicts and Iterating Patches
+
+When revising changes made to your patches, it's important to
+acknowledge the possibility of conflicts with other ongoing topics. To
+navigate these potential conflicts effectively, follow the recommended
+steps outlined below:
+
+. Build on a suitable base branch, see the <<choose-starting-point, section above>>,
+and format-patch the series. If you are doing "rebase -i" in-place to
+update from the previous round, this will reuse the previous base so
+(2) and (3) may become trivial.
+
+. Find the base of where the last round was queued
++
+ $ mine='kn/ref-transaction-symref'
+ $ git checkout "origin/seen^{/^Merge branch '$mine'}...master"
+
+. Apply your format-patch result. There are two cases
+.. Things apply cleanly and tests fine. Go to (4).
+.. Things apply cleanly but does not build or test fails, or things do
+not apply cleanly.
++
+In the latter case, you have textual or semantic conflicts coming from
+the difference between the old base and the base you used to build in
+(1). Identify what caused the breakages (e.g., a topic or two may have
+merged since the base used by (2) until the base used by (1)).
++
+Check out the latest 'origin/master' (which may be newer than the base
+used by (2)), "merge --no-ff" the topics you newly depend on in there,
+and use the result of the merge(s) as the base, rebuild the series and
+test again. Run format-patch from the last such merges to the tip of
+your topic. If you did
++
+ $ git checkout origin/master
+ $ git merge --no-ff --into-name kn/ref-transaction-symref fo/obar
+ $ git merge --no-ff --into-name kn/ref-transaction-symref ba/zqux
+ ... rebuild the topic ...
++
+Then you'd just format your topic above these "preparing the ground"
+merges, e.g.
++
+ $ git format-patch "HEAD^{/^Merge branch 'ba/zqux'}"..HEAD
++
+Do not forget to write in the cover letter you did this, including the
+topics you have in your base on top of 'master'. Then go to (4).
+
+. Make a trial merge of your topic into 'next' and 'seen', e.g.
++
+ $ git checkout --detach 'origin/seen'
+ $ git revert -m 1 <the merge of the previous iteration into seen>
+ $ git merge kn/ref-transaction-symref
++
+The "revert" is needed if the previous iteration of your topic is
+already in 'seen' (like in this case). You could choose to rebuild
+master..origin/seen from scratch while excluding your previous
+iteration, which may emulate what happens on the maintainers end more
+closely.
++
+This trial merge may conflict. It is primarily to see what conflicts
+_other_ topics may have with your topic. In other words, you do not
+have to depend on it to make your topic work on 'master'. It may
+become the job of the other topic owners to resolve conflicts if your
+topic goes to 'next' before theirs.
++
+Make a note on what conflict you saw in the cover letter. You do not
+necessarily have to resolve them, but it would be a good opportunity to
+learn what others are doing in related areas.
++
+ $ git checkout --detach 'origin/next'
+ $ git merge kn/ref-transaction-symref
++
+This is to see what conflicts your topic has with other topics that are
+already cooking. This should not conflict if (3)-2 prepared a base on
+top of updated master plus dependent topics taken from 'next'. Unless
+the context is severe (one way to tell is try the same trial merge with
+your old iteration, which may conflict in a similar way), expect that it
+will be handled on maintainers end (if it gets unmanageable, I'll ask to
+rebase when I receive your patches).
+
== Subsystems with dedicated maintainers
Some parts of the system have dedicated maintainers with their own
@@ -562,54 +709,12 @@ repositories.
Patches to these parts should be based on their trees.
-[[patch-flow]]
-== An ideal patch flow
-
-Here is an ideal patch flow for this project the current maintainer
-suggests to the contributors:
-
-. You come up with an itch. You code it up.
-
-. Send it to the list and cc people who may need to know about
- the change.
-+
-The people who may need to know are the ones whose code you
-are butchering. These people happen to be the ones who are
-most likely to be knowledgeable enough to help you, but
-they have no obligation to help you (i.e. you ask for help,
-don't demand). +git log -p {litdd} _$area_you_are_modifying_+ would
-help you find out who they are.
-
-. You get comments and suggestions for improvements. You may
- even get them in an "on top of your change" patch form.
-
-. Polish, refine, and re-send to the list and the people who
- spend their time to improve your patch. Go back to step (2).
-
-. The list forms consensus that the last round of your patch is
- good. Send it to the maintainer and cc the list.
-
-. A topic branch is created with the patch and is merged to `next`,
- and cooked further and eventually graduates to `master`.
-
-In any time between the (2)-(3) cycle, the maintainer may pick it up
-from the list and queue it to `seen`, in order to make it easier for
-people to play with it without having to pick up and apply the patch to
-their trees themselves.
-
-[[patch-status]]
-== Know the status of your patch after submission
+- The "Git documentation translations" project, led by Jean-Noël
+ Avila, translates our documentation pages. Their work products are
+ maintained separately from this project, not as part of our tree:
-* You can use Git itself to find out when your patch is merged in
- master. `git pull --rebase` will automatically skip already-applied
- patches, and will let you know. This works only if you rebase on top
- of the branch in which your patch has been merged (i.e. it will not
- tell you if your patch is merged in `seen` if you rebase on top of
- master).
+ https://github.com/jnavila/git-manpages-l10n/
-* Read the Git mailing list, the maintainer regularly posts messages
- entitled "What's cooking in git.git" giving
- the status of various proposed changes.
== GitHub CI[[GHCI]]
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 0e9456957e..c7df20e571 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -329,12 +329,13 @@ explained for the configuration variable `core.quotePath` (see
linkgit:git-config[1]).
--name-only::
- Show only names of changed files. The file names are often encoded in UTF-8.
+ Show only the name of each changed file in the post-image tree.
+ The file names are often encoded in UTF-8.
For more information see the discussion about encoding in the linkgit:git-log[1]
manual page.
--name-status::
- Show only names and status of changed files. See the description
+ Show only the name(s) and status of each changed file. See the description
of the `--diff-filter` option on what the status letters mean.
Just like `--name-only` the file names are often encoded in UTF-8.
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 369af2c4a7..8708b31593 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -357,6 +357,11 @@ material (this may change in the future).
between the previous and current series of patches by adjusting the
creation/deletion cost fudge factor. See linkgit:git-range-diff[1])
for details.
++
+Defaults to 999 (the linkgit:git-range-diff[1] uses 60), as the use
+case is to show comparison with an older iteration of the same
+topic and the tool should find more correspondence between the two
+sets of patches.
--notes[=<ref>]::
--no-notes::
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index f9d5a35fa0..dc12d38534 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -18,8 +18,15 @@ Many Git porcelainish commands take a mixture of flags
(i.e. parameters that begin with a dash '-') and parameters
meant for the underlying 'git rev-list' command they use internally
and flags and parameters for the other commands they use
-downstream of 'git rev-list'. This command is used to
-distinguish between them.
+downstream of 'git rev-list'. The primary purpose of this command
+is to allow calling programs to distinguish between them. There are
+a few other operation modes that have nothing to do with the above
+"help parse command line options".
+
+Unless otherwise specified, most of the options and operation modes
+require you to run this command inside a git repository or a working
+tree that is under the control of a git repository, and will give you
+a fatal error otherwise.
OPTIONS
@@ -32,11 +39,15 @@ Each of these options must appear first on the command line.
--parseopt::
Use 'git rev-parse' in option parsing mode (see PARSEOPT section below).
+ The command in this mode can be used outside a repository or
+ a working tree controlled by a repository.
--sq-quote::
Use 'git rev-parse' in shell quoting mode (see SQ-QUOTE
section below). In contrast to the `--sq` option below, this
mode only does quoting. Nothing else is done to command input.
+ The command in this mode can be used outside a repository or
+ a working tree controlled by a repository.
Options for --parseopt
~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index 8c47890a6a..7128aed540 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -25,6 +25,7 @@ SYNOPSIS
[--really-refresh] [--unresolve] [--again | -g]
[--info-only] [--index-info]
[-z] [--stdin] [--index-version <n>]
+ [--show-index-version]
[--verbose]
[--] [<file>...]
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index 1272809e13..30b394ab47 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -497,20 +497,18 @@ exclude;;
unusual refs.
[[def_pseudoref]]pseudoref::
- Pseudorefs are a class of files under `$GIT_DIR` which behave
- like refs for the purposes of rev-parse, but which are treated
- specially by git. Pseudorefs both have names that are all-caps,
- and always start with a line consisting of a
- <<def_SHA1,SHA-1>> followed by whitespace. So, HEAD is not a
- pseudoref, because it is sometimes a symbolic ref. They might
- optionally contain some additional data. `MERGE_HEAD` and
- `CHERRY_PICK_HEAD` are examples. Unlike
- <<def_per_worktree_ref,per-worktree refs>>, these files cannot
- be symbolic refs, and never have reflogs. They also cannot be
- updated through the normal ref update machinery. Instead,
- they are updated by directly writing to the files. However,
- they can be read as if they were refs, so `git rev-parse
- MERGE_HEAD` will work.
+ A ref that has different semantics than normal refs. These refs can be
+ read via normal Git commands, but cannot be written to by commands like
+ linkgit:git-update-ref[1].
++
+The following pseudorefs are known to Git:
+
+ - `FETCH_HEAD` is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It
+ may refer to multiple object IDs. Each object ID is annotated with metadata
+ indicating where it was fetched from and its fetch status.
+
+ - `MERGE_HEAD` is written by linkgit:git-merge[1] when resolving merge
+ conflicts. It contains all commit IDs which are being merged.
[[def_pull]]pull::
Pulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and
@@ -552,20 +550,38 @@ exclude;;
to the result.
[[def_ref]]ref::
- A name that begins with `refs/` (e.g. `refs/heads/master`)
- that points to an <<def_object_name,object name>> or another
- ref (the latter is called a <<def_symref,symbolic ref>>).
+ A name that that points to an <<def_object_name,object name>> or
+ another ref (the latter is called a <<def_symref,symbolic ref>>).
For convenience, a ref can sometimes be abbreviated when used
as an argument to a Git command; see linkgit:gitrevisions[7]
for details.
Refs are stored in the <<def_repository,repository>>.
+
The ref namespace is hierarchical.
-Different subhierarchies are used for different purposes (e.g. the
-`refs/heads/` hierarchy is used to represent local branches).
+Ref names must either start with `refs/` or be located in the root of
+the hierarchy. For the latter, their name must follow these rules:
++
+ - The name consists of only upper-case characters or underscores.
+
+ - The name ends with "`_HEAD`" or is equal to "`HEAD`".
+
-There are a few special-purpose refs that do not begin with `refs/`.
-The most notable example is `HEAD`.
+There are some irregular refs in the root of the hierarchy that do not
+match these rules. The following list is exhaustive and shall not be
+extended in the future:
++
+ - `AUTO_MERGE`
+
+ - `BISECT_EXPECTED_REV`
+
+ - `NOTES_MERGE_PARTIAL`
+
+ - `NOTES_MERGE_REF`
+
+ - `MERGE_AUTOSTASH`
++
+Different subhierarchies are used for different purposes. For example,
+the `refs/heads/` hierarchy is used to represent local branches whereas
+the `refs/tags/` hierarchy is used to represent local tags..
[[def_reflog]]reflog::
A reflog shows the local "history" of a ref. In other words,
@@ -639,20 +655,6 @@ The most notable example is `HEAD`.
An <<def_object,object>> used to temporarily store the contents of a
<<def_dirty,dirty>> working directory and the index for future reuse.
-[[def_special_ref]]special ref::
- A ref that has different semantics than normal refs. These refs can be
- accessed via normal Git commands but may not behave the same as a
- normal ref in some cases.
-+
-The following special refs are known to Git:
-
- - "`FETCH_HEAD`" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It
- may refer to multiple object IDs. Each object ID is annotated with metadata
- indicating where it was fetched from and its fetch status.
-
- - "`MERGE_HEAD`" is written by linkgit:git-merge[1] when resolving merge
- conflicts. It contains all commit IDs which are being merged.
-
[[def_submodule]]submodule::
A <<def_repository,repository>> that holds the history of a
separate project inside another repository (the latter of
diff --git a/Makefile b/Makefile
index d4000fb1d6..dec283578a 100644
--- a/Makefile
+++ b/Makefile
@@ -1334,11 +1334,12 @@ THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
+UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-mem-pool
+UNIT_TEST_PROGRAMS += t-prio-queue
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strvec
-UNIT_TEST_PROGRAMS += t-ctype
-UNIT_TEST_PROGRAMS += t-prio-queue
+UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
diff --git a/builtin/config.c b/builtin/config.c
index cc343f55ca..20a0b64090 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -62,56 +62,66 @@ static const char *const builtin_config_edit_usage[] = {
NULL
};
-static char *key;
-static regex_t *key_regexp;
-static const char *value_pattern;
-static regex_t *regexp;
-static int show_keys;
-static int omit_values;
-static int use_key_regexp;
-static int do_all;
-static int do_not_match;
-static char delim = '=';
-static char key_delim = ' ';
-static char term = '\n';
-
-static parse_opt_subcommand_fn *subcommand;
-static int use_global_config, use_system_config, use_local_config;
-static int use_worktree_config;
-static struct git_config_source given_config_source;
-static int actions, type;
-static char *default_value;
-static int end_nul;
-static int respect_includes_opt = -1;
-static struct config_options config_options;
-static int show_origin;
-static int show_scope;
-static int fixed_value;
-static const char *comment_arg;
-
-#define ACTION_GET (1<<0)
-#define ACTION_GET_ALL (1<<1)
-#define ACTION_GET_REGEXP (1<<2)
-#define ACTION_REPLACE_ALL (1<<3)
-#define ACTION_ADD (1<<4)
-#define ACTION_UNSET (1<<5)
-#define ACTION_UNSET_ALL (1<<6)
-#define ACTION_RENAME_SECTION (1<<7)
-#define ACTION_REMOVE_SECTION (1<<8)
-#define ACTION_LIST (1<<9)
-#define ACTION_EDIT (1<<10)
-#define ACTION_SET (1<<11)
-#define ACTION_SET_ALL (1<<12)
-#define ACTION_GET_COLOR (1<<13)
-#define ACTION_GET_COLORBOOL (1<<14)
-#define ACTION_GET_URLMATCH (1<<15)
-
-/*
- * The actions "ACTION_LIST | ACTION_GET_*" which may produce more than
- * one line of output and which should therefore be paged.
- */
-#define PAGING_ACTIONS (ACTION_LIST | ACTION_GET_ALL | \
- ACTION_GET_REGEXP | ACTION_GET_URLMATCH)
+#define CONFIG_LOCATION_OPTIONS(opts) \
+ OPT_GROUP(N_("Config file location")), \
+ OPT_BOOL(0, "global", &opts.use_global_config, N_("use global config file")), \
+ OPT_BOOL(0, "system", &opts.use_system_config, N_("use system config file")), \
+ OPT_BOOL(0, "local", &opts.use_local_config, N_("use repository config file")), \
+ OPT_BOOL(0, "worktree", &opts.use_worktree_config, N_("use per-worktree config file")), \
+ OPT_STRING('f', "file", &opts.source.file, N_("file"), N_("use given config file")), \
+ OPT_STRING(0, "blob", &opts.source.blob, N_("blob-id"), N_("read config from given blob object"))
+
+struct config_location_options {
+ struct git_config_source source;
+ struct config_options options;
+ char *file_to_free;
+ int use_global_config;
+ int use_system_config;
+ int use_local_config;
+ int use_worktree_config;
+ int respect_includes_opt;
+};
+#define CONFIG_LOCATION_OPTIONS_INIT { \
+ .respect_includes_opt = -1, \
+}
+
+#define CONFIG_TYPE_OPTIONS(type) \
+ OPT_GROUP(N_("Type")), \
+ OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), \
+ OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL), \
+ OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT), \
+ OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), \
+ OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR), \
+ OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), \
+ OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE)
+
+#define CONFIG_DISPLAY_OPTIONS(opts) \
+ OPT_GROUP(N_("Display options")), \
+ OPT_BOOL('z', "null", &opts.end_nul, N_("terminate values with NUL byte")), \
+ OPT_BOOL(0, "name-only", &opts.omit_values, N_("show variable names only")), \
+ OPT_BOOL(0, "show-origin", &opts.show_origin, N_("show origin of config (file, standard input, blob, command line)")), \
+ OPT_BOOL(0, "show-scope", &opts.show_scope, N_("show scope of config (worktree, local, global, system, command)")), \
+ OPT_BOOL(0, "show-names", &opts.show_keys, N_("show config keys in addition to their values")), \
+ CONFIG_TYPE_OPTIONS(opts.type)
+
+struct config_display_options {
+ int end_nul;
+ int omit_values;
+ int show_origin;
+ int show_scope;
+ int show_keys;
+ int type;
+ char *default_value;
+ /* Populated via `display_options_init()`. */
+ int term;
+ int delim;
+ int key_delim;
+};
+#define CONFIG_DISPLAY_OPTIONS_INIT { \
+ .term = '\n', \
+ .delim = '=', \
+ .key_delim = ' ', \
+}
#define TYPE_BOOL 1
#define TYPE_INT 2
@@ -125,8 +135,6 @@ static const char *comment_arg;
{ OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \
PARSE_OPT_NONEG, option_parse_type, (i) }
-static NORETURN void usage_builtin_config(void);
-
static int option_parse_type(const struct option *opt, const char *arg,
int unset)
{
@@ -171,7 +179,7 @@ static int option_parse_type(const struct option *opt, const char *arg,
* --type=int'.
*/
error(_("only one type at a time"));
- usage_builtin_config();
+ exit(129);
}
*to_type = new_type;
@@ -187,27 +195,29 @@ static void check_argc(int argc, int min, int max)
else
error(_("wrong number of arguments, should be from %d to %d"),
min, max);
- usage_builtin_config();
+ exit(129);
}
-static void show_config_origin(const struct key_value_info *kvi,
+static void show_config_origin(const struct config_display_options *opts,
+ const struct key_value_info *kvi,
struct strbuf *buf)
{
- const char term = end_nul ? '\0' : '\t';
+ const char term = opts->end_nul ? '\0' : '\t';
strbuf_addstr(buf, config_origin_type_name(kvi->origin_type));
strbuf_addch(buf, ':');
- if (end_nul)
+ if (opts->end_nul)
strbuf_addstr(buf, kvi->filename ? kvi->filename : "");
else
quote_c_style(kvi->filename ? kvi->filename : "", buf, NULL, 0);
strbuf_addch(buf, term);
}
-static void show_config_scope(const struct key_value_info *kvi,
+static void show_config_scope(const struct config_display_options *opts,
+ const struct key_value_info *kvi,
struct strbuf *buf)
{
- const char term = end_nul ? '\0' : '\t';
+ const char term = opts->end_nul ? '\0' : '\t';
const char *scope = config_scope_name(kvi->scope);
strbuf_addstr(buf, N_(scope));
@@ -216,24 +226,25 @@ static void show_config_scope(const struct key_value_info *kvi,
static int show_all_config(const char *key_, const char *value_,
const struct config_context *ctx,
- void *cb UNUSED)
+ void *cb)
{
+ const struct config_display_options *opts = cb;
const struct key_value_info *kvi = ctx->kvi;
- if (show_origin || show_scope) {
+ if (opts->show_origin || opts->show_scope) {
struct strbuf buf = STRBUF_INIT;
- if (show_scope)
- show_config_scope(kvi, &buf);
- if (show_origin)
- show_config_origin(kvi, &buf);
+ if (opts->show_scope)
+ show_config_scope(opts, kvi, &buf);
+ if (opts->show_origin)
+ show_config_origin(opts, kvi, &buf);
/* Use fwrite as "buf" can contain \0's if "end_null" is set. */
fwrite(buf.buf, 1, buf.len, stdout);
strbuf_release(&buf);
}
- if (!omit_values && value_)
- printf("%s%c%s%c", key_, delim, value_, term);
+ if (!opts->omit_values && value_)
+ printf("%s%c%s%c", key_, opts->delim, value_, opts->term);
else
- printf("%s%c", key_, term);
+ printf("%s%c", key_, opts->term);
return 0;
}
@@ -243,26 +254,27 @@ struct strbuf_list {
int alloc;
};
-static int format_config(struct strbuf *buf, const char *key_,
+static int format_config(const struct config_display_options *opts,
+ struct strbuf *buf, const char *key_,
const char *value_, const struct key_value_info *kvi)
{
- if (show_scope)
- show_config_scope(kvi, buf);
- if (show_origin)
- show_config_origin(kvi, buf);
- if (show_keys)
+ if (opts->show_scope)
+ show_config_scope(opts, kvi, buf);
+ if (opts->show_origin)
+ show_config_origin(opts, kvi, buf);
+ if (opts->show_keys)
strbuf_addstr(buf, key_);
- if (!omit_values) {
- if (show_keys)
- strbuf_addch(buf, key_delim);
+ if (!opts->omit_values) {
+ if (opts->show_keys)
+ strbuf_addch(buf, opts->key_delim);
- if (type == TYPE_INT)
+ if (opts->type == TYPE_INT)
strbuf_addf(buf, "%"PRId64,
git_config_int64(key_, value_ ? value_ : "", kvi));
- else if (type == TYPE_BOOL)
+ else if (opts->type == TYPE_BOOL)
strbuf_addstr(buf, git_config_bool(key_, value_) ?
"true" : "false");
- else if (type == TYPE_BOOL_OR_INT) {
+ else if (opts->type == TYPE_BOOL_OR_INT) {
int is_bool, v;
v = git_config_bool_or_int(key_, value_, kvi,
&is_bool);
@@ -270,24 +282,24 @@ static int format_config(struct strbuf *buf, const char *key_,
strbuf_addstr(buf, v ? "true" : "false");
else
strbuf_addf(buf, "%d", v);
- } else if (type == TYPE_BOOL_OR_STR) {
+ } else if (opts->type == TYPE_BOOL_OR_STR) {
int v = git_parse_maybe_bool(value_);
if (v < 0)
strbuf_addstr(buf, value_);
else
strbuf_addstr(buf, v ? "true" : "false");
- } else if (type == TYPE_PATH) {
+ } else if (opts->type == TYPE_PATH) {
char *v;
if (git_config_pathname(&v, key_, value_) < 0)
return -1;
strbuf_addstr(buf, v);
free((char *)v);
- } else if (type == TYPE_EXPIRY_DATE) {
+ } else if (opts->type == TYPE_EXPIRY_DATE) {
timestamp_t t;
if (git_config_expiry_date(&t, key_, value_) < 0)
return -1;
strbuf_addf(buf, "%"PRItime, t);
- } else if (type == TYPE_COLOR) {
+ } else if (opts->type == TYPE_COLOR) {
char v[COLOR_MAXLEN];
if (git_config_color(v, key_, value_) < 0)
return -1;
@@ -296,43 +308,73 @@ static int format_config(struct strbuf *buf, const char *key_,
strbuf_addstr(buf, value_);
} else {
/* Just show the key name; back out delimiter */
- if (show_keys)
+ if (opts->show_keys)
strbuf_setlen(buf, buf->len - 1);
}
}
- strbuf_addch(buf, term);
+ strbuf_addch(buf, opts->term);
return 0;
}
+#define GET_VALUE_ALL (1 << 0)
+#define GET_VALUE_KEY_REGEXP (1 << 1)
+
+struct collect_config_data {
+ const struct config_display_options *display_opts;
+ struct strbuf_list *values;
+ const char *value_pattern;
+ const char *key;
+ regex_t *regexp;
+ regex_t *key_regexp;
+ int do_not_match;
+ unsigned get_value_flags;
+ unsigned flags;
+};
+
static int collect_config(const char *key_, const char *value_,
const struct config_context *ctx, void *cb)
{
- struct strbuf_list *values = cb;
+ struct collect_config_data *data = cb;
+ struct strbuf_list *values = data->values;
const struct key_value_info *kvi = ctx->kvi;
- if (!use_key_regexp && strcmp(key_, key))
+ if (!(data->get_value_flags & GET_VALUE_KEY_REGEXP) &&
+ strcmp(key_, data->key))
return 0;
- if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
+ if ((data->get_value_flags & GET_VALUE_KEY_REGEXP) &&
+ regexec(data->key_regexp, key_, 0, NULL, 0))
return 0;
- if (fixed_value && strcmp(value_pattern, (value_?value_:"")))
+ if ((data->flags & CONFIG_FLAGS_FIXED_VALUE) &&
+ strcmp(data->value_pattern, (value_?value_:"")))
return 0;
- if (regexp != NULL &&
- (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
+ if (data->regexp &&
+ (data->do_not_match ^ !!regexec(data->regexp, (value_?value_:""), 0, NULL, 0)))
return 0;
ALLOC_GROW(values->items, values->nr + 1, values->alloc);
strbuf_init(&values->items[values->nr], 0);
- return format_config(&values->items[values->nr++], key_, value_, kvi);
+ return format_config(data->display_opts, &values->items[values->nr++],
+ key_, value_, kvi);
}
-static int get_value(const char *key_, const char *regex_, unsigned flags)
+static int get_value(const struct config_location_options *opts,
+ const struct config_display_options *display_opts,
+ const char *key_, const char *regex_,
+ unsigned get_value_flags, unsigned flags)
{
int ret = CONFIG_GENERIC_ERROR;
struct strbuf_list values = {NULL};
+ struct collect_config_data data = {
+ .display_opts = display_opts,
+ .values = &values,
+ .get_value_flags = get_value_flags,
+ .flags = flags,
+ };
+ char *key = NULL;
int i;
- if (use_key_regexp) {
+ if (get_value_flags & GET_VALUE_KEY_REGEXP) {
char *tl;
/*
@@ -349,10 +391,10 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)
for (tl = key; *tl && *tl != '.'; tl++)
*tl = tolower(*tl);
- key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
- if (regcomp(key_regexp, key, REG_EXTENDED)) {
+ data.key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
+ if (regcomp(data.key_regexp, key, REG_EXTENDED)) {
error(_("invalid key pattern: %s"), key_);
- FREE_AND_NULL(key_regexp);
+ FREE_AND_NULL(data.key_regexp);
ret = CONFIG_INVALID_PATTERN;
goto free_strings;
}
@@ -361,30 +403,32 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)
ret = CONFIG_INVALID_KEY;
goto free_strings;
}
+
+ data.key = key;
}
if (regex_ && (flags & CONFIG_FLAGS_FIXED_VALUE))
- value_pattern = regex_;
+ data.value_pattern = regex_;
else if (regex_) {
if (regex_[0] == '!') {
- do_not_match = 1;
+ data.do_not_match = 1;
regex_++;
}
- regexp = (regex_t*)xmalloc(sizeof(regex_t));
- if (regcomp(regexp, regex_, REG_EXTENDED)) {
+ data.regexp = (regex_t*)xmalloc(sizeof(regex_t));
+ if (regcomp(data.regexp, regex_, REG_EXTENDED)) {
error(_("invalid pattern: %s"), regex_);
- FREE_AND_NULL(regexp);
+ FREE_AND_NULL(data.regexp);
ret = CONFIG_INVALID_PATTERN;
goto free_strings;
}
}
- config_with_options(collect_config, &values,
- &given_config_source, the_repository,
- &config_options);
+ config_with_options(collect_config, &data,
+ &opts->source, the_repository,
+ &opts->options);
- if (!values.nr && default_value) {
+ if (!values.nr && display_opts->default_value) {
struct key_value_info kvi = KVI_INIT;
struct strbuf *item;
@@ -392,16 +436,17 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)
ALLOC_GROW(values.items, values.nr + 1, values.alloc);
item = &values.items[values.nr++];
strbuf_init(item, 0);
- if (format_config(item, key_, default_value, &kvi) < 0)
+ if (format_config(display_opts, item, key_,
+ display_opts->default_value, &kvi) < 0)
die(_("failed to format default config value: %s"),
- default_value);
+ display_opts->default_value);
}
ret = !values.nr;
for (i = 0; i < values.nr; i++) {
struct strbuf *buf = values.items + i;
- if (do_all || i == values.nr - 1)
+ if ((get_value_flags & GET_VALUE_ALL) || i == values.nr - 1)
fwrite(buf->buf, 1, buf->len, stdout);
strbuf_release(buf);
}
@@ -409,20 +454,20 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)
free_strings:
free(key);
- if (key_regexp) {
- regfree(key_regexp);
- free(key_regexp);
+ if (data.key_regexp) {
+ regfree(data.key_regexp);
+ free(data.key_regexp);
}
- if (regexp) {
- regfree(regexp);
- free(regexp);
+ if (data.regexp) {
+ regfree(data.regexp);
+ free(data.regexp);
}
return ret;
}
static char *normalize_value(const char *key, const char *value,
- struct key_value_info *kvi)
+ int type, struct key_value_info *kvi)
{
if (!value)
return NULL;
@@ -473,97 +518,113 @@ static char *normalize_value(const char *key, const char *value,
BUG("cannot normalize type %d", type);
}
-static int get_color_found;
-static const char *get_color_slot;
-static const char *get_colorbool_slot;
-static char parsed_color[COLOR_MAXLEN];
+struct get_color_config_data {
+ int get_color_found;
+ const char *get_color_slot;
+ char parsed_color[COLOR_MAXLEN];
+};
static int git_get_color_config(const char *var, const char *value,
const struct config_context *ctx UNUSED,
- void *cb UNUSED)
+ void *cb)
{
- if (!strcmp(var, get_color_slot)) {
+ struct get_color_config_data *data = cb;
+
+ if (!strcmp(var, data->get_color_slot)) {
if (!value)
config_error_nonbool(var);
- if (color_parse(value, parsed_color) < 0)
+ if (color_parse(value, data->parsed_color) < 0)
return -1;
- get_color_found = 1;
+ data->get_color_found = 1;
}
return 0;
}
-static void get_color(const char *var, const char *def_color)
+static void get_color(const struct config_location_options *opts,
+ const char *var, const char *def_color)
{
- get_color_slot = var;
- get_color_found = 0;
- parsed_color[0] = '\0';
- config_with_options(git_get_color_config, NULL,
- &given_config_source, the_repository,
- &config_options);
-
- if (!get_color_found && def_color) {
- if (color_parse(def_color, parsed_color) < 0)
+ struct get_color_config_data data = {
+ .get_color_slot = var,
+ .parsed_color[0] = '\0',
+ };
+
+ config_with_options(git_get_color_config, &data,
+ &opts->source, the_repository,
+ &opts->options);
+
+ if (!data.get_color_found && def_color) {
+ if (color_parse(def_color, data.parsed_color) < 0)
die(_("unable to parse default color value"));
}
- fputs(parsed_color, stdout);
+ fputs(data.parsed_color, stdout);
}
-static int get_colorbool_found;
-static int get_diff_color_found;
-static int get_color_ui_found;
+struct get_colorbool_config_data {
+ int get_colorbool_found;
+ int get_diff_color_found;
+ int get_color_ui_found;
+ const char *get_colorbool_slot;
+};
+
static int git_get_colorbool_config(const char *var, const char *value,
const struct config_context *ctx UNUSED,
- void *data UNUSED)
+ void *cb)
{
- if (!strcmp(var, get_colorbool_slot))
- get_colorbool_found = git_config_colorbool(var, value);
+ struct get_colorbool_config_data *data = cb;
+
+ if (!strcmp(var, data->get_colorbool_slot))
+ data->get_colorbool_found = git_config_colorbool(var, value);
else if (!strcmp(var, "diff.color"))
- get_diff_color_found = git_config_colorbool(var, value);
+ data->get_diff_color_found = git_config_colorbool(var, value);
else if (!strcmp(var, "color.ui"))
- get_color_ui_found = git_config_colorbool(var, value);
+ data->get_color_ui_found = git_config_colorbool(var, value);
return 0;
}
-static int get_colorbool(const char *var, int print)
+static int get_colorbool(const struct config_location_options *opts,
+ const char *var, int print)
{
- get_colorbool_slot = var;
- get_colorbool_found = -1;
- get_diff_color_found = -1;
- get_color_ui_found = -1;
- config_with_options(git_get_colorbool_config, NULL,
- &given_config_source, the_repository,
- &config_options);
-
- if (get_colorbool_found < 0) {
- if (!strcmp(get_colorbool_slot, "color.diff"))
- get_colorbool_found = get_diff_color_found;
- if (get_colorbool_found < 0)
- get_colorbool_found = get_color_ui_found;
+ struct get_colorbool_config_data data = {
+ .get_colorbool_slot = var,
+ .get_colorbool_found = -1,
+ .get_diff_color_found = -1,
+ .get_color_ui_found = -1,
+ };
+
+ config_with_options(git_get_colorbool_config, &data,
+ &opts->source, the_repository,
+ &opts->options);
+
+ if (data.get_colorbool_found < 0) {
+ if (!strcmp(data.get_colorbool_slot, "color.diff"))
+ data.get_colorbool_found = data.get_diff_color_found;
+ if (data.get_colorbool_found < 0)
+ data.get_colorbool_found = data.get_color_ui_found;
}
- if (get_colorbool_found < 0)
+ if (data.get_colorbool_found < 0)
/* default value if none found in config */
- get_colorbool_found = GIT_COLOR_AUTO;
+ data.get_colorbool_found = GIT_COLOR_AUTO;
- get_colorbool_found = want_color(get_colorbool_found);
+ data.get_colorbool_found = want_color(data.get_colorbool_found);
if (print) {
- printf("%s\n", get_colorbool_found ? "true" : "false");
+ printf("%s\n", data.get_colorbool_found ? "true" : "false");
return 0;
} else
- return get_colorbool_found ? 0 : 1;
+ return data.get_colorbool_found ? 0 : 1;
}
-static void check_write(void)
+static void check_write(const struct git_config_source *source)
{
- if (!given_config_source.file && !startup_info->have_repository)
+ if (!source->file && !startup_info->have_repository)
die(_("not in a git directory"));
- if (given_config_source.use_stdin)
+ if (source->use_stdin)
die(_("writing to stdin is not supported"));
- if (given_config_source.blob)
+ if (source->blob)
die(_("writing config blobs is not supported"));
}
@@ -600,10 +661,13 @@ static int urlmatch_collect_fn(const char *var, const char *value,
return 0;
}
-static int get_urlmatch(const char *var, const char *url)
+static int get_urlmatch(const struct config_location_options *opts,
+ const struct config_display_options *_display_opts,
+ const char *var, const char *url)
{
int ret;
char *section_tail;
+ struct config_display_options display_opts = *_display_opts;
struct string_list_item *item;
struct urlmatch_config config = URLMATCH_CONFIG_INIT;
struct string_list values = STRING_LIST_INIT_DUP;
@@ -620,15 +684,15 @@ static int get_urlmatch(const char *var, const char *url)
if (section_tail) {
*section_tail = '\0';
config.key = section_tail + 1;
- show_keys = 0;
+ display_opts.show_keys = 0;
} else {
config.key = NULL;
- show_keys = 1;
+ display_opts.show_keys = 1;
}
config_with_options(urlmatch_config_entry, &config,
- &given_config_source, the_repository,
- &config_options);
+ &opts->source, the_repository,
+ &opts->options);
ret = !values.nr;
@@ -636,7 +700,7 @@ static int get_urlmatch(const char *var, const char *url)
struct urlmatch_current_candidate_value *matched = item->util;
struct strbuf buf = STRBUF_INIT;
- format_config(&buf, item->string,
+ format_config(&display_opts, &buf, item->string,
matched->value_is_null ? NULL : matched->value.buf,
&matched->kvi);
fwrite(buf.buf, 1, buf.len, stdout);
@@ -666,34 +730,39 @@ static char *default_user_config(void)
return strbuf_detach(&buf, NULL);
}
-static void handle_config_location(const char *prefix)
+static void location_options_init(struct config_location_options *opts,
+ const char *prefix)
{
- if (use_global_config + use_system_config + use_local_config +
- use_worktree_config +
- !!given_config_source.file + !!given_config_source.blob > 1) {
+ if (!opts->source.file)
+ opts->source.file = opts->file_to_free =
+ xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));
+
+ if (opts->use_global_config + opts->use_system_config +
+ opts->use_local_config + opts->use_worktree_config +
+ !!opts->source.file + !!opts->source.blob > 1) {
error(_("only one config file at a time"));
- usage_builtin_config();
+ exit(129);
}
if (!startup_info->have_repository) {
- if (use_local_config)
+ if (opts->use_local_config)
die(_("--local can only be used inside a git repository"));
- if (given_config_source.blob)
+ if (opts->source.blob)
die(_("--blob can only be used inside a git repository"));
- if (use_worktree_config)
+ if (opts->use_worktree_config)
die(_("--worktree can only be used inside a git repository"));
}
- if (given_config_source.file &&
- !strcmp(given_config_source.file, "-")) {
- given_config_source.file = NULL;
- given_config_source.use_stdin = 1;
- given_config_source.scope = CONFIG_SCOPE_COMMAND;
+ if (opts->source.file &&
+ !strcmp(opts->source.file, "-")) {
+ opts->source.file = NULL;
+ opts->source.use_stdin = 1;
+ opts->source.scope = CONFIG_SCOPE_COMMAND;
}
- if (use_global_config) {
- given_config_source.file = git_global_config();
- if (!given_config_source.file)
+ if (opts->use_global_config) {
+ opts->source.file = opts->file_to_free = git_global_config();
+ if (!opts->source.file)
/*
* It is unknown if HOME/.gitconfig exists, so
* we do not know if we should write to XDG
@@ -701,17 +770,18 @@ static void handle_config_location(const char *prefix)
* is set and points at a sane location.
*/
die(_("$HOME not set"));
- given_config_source.scope = CONFIG_SCOPE_GLOBAL;
- } else if (use_system_config) {
- given_config_source.file = git_system_config();
- given_config_source.scope = CONFIG_SCOPE_SYSTEM;
- } else if (use_local_config) {
- given_config_source.file = git_pathdup("config");
- given_config_source.scope = CONFIG_SCOPE_LOCAL;
- } else if (use_worktree_config) {
+ opts->source.scope = CONFIG_SCOPE_GLOBAL;
+ } else if (opts->use_system_config) {
+ opts->source.file = opts->file_to_free = git_system_config();
+ opts->source.scope = CONFIG_SCOPE_SYSTEM;
+ } else if (opts->use_local_config) {
+ opts->source.file = opts->file_to_free = git_pathdup("config");
+ opts->source.scope = CONFIG_SCOPE_LOCAL;
+ } else if (opts->use_worktree_config) {
struct worktree **worktrees = get_worktrees();
if (the_repository->repository_format_worktree_config)
- given_config_source.file = git_pathdup("config.worktree");
+ opts->source.file = opts->file_to_free =
+ git_pathdup("config.worktree");
else if (worktrees[0] && worktrees[1])
die(_("--worktree cannot be used with multiple "
"working trees unless the config\n"
@@ -719,145 +789,102 @@ static void handle_config_location(const char *prefix)
"Please read \"CONFIGURATION FILE\"\n"
"section in \"git help worktree\" for details"));
else
- given_config_source.file = git_pathdup("config");
- given_config_source.scope = CONFIG_SCOPE_LOCAL;
+ opts->source.file = opts->file_to_free =
+ git_pathdup("config");
+ opts->source.scope = CONFIG_SCOPE_LOCAL;
free_worktrees(worktrees);
- } else if (given_config_source.file) {
- if (!is_absolute_path(given_config_source.file) && prefix)
- given_config_source.file =
- prefix_filename(prefix, given_config_source.file);
- given_config_source.scope = CONFIG_SCOPE_COMMAND;
- } else if (given_config_source.blob) {
- given_config_source.scope = CONFIG_SCOPE_COMMAND;
+ } else if (opts->source.file) {
+ if (!is_absolute_path(opts->source.file) && prefix)
+ opts->source.file = opts->file_to_free =
+ prefix_filename(prefix, opts->source.file);
+ opts->source.scope = CONFIG_SCOPE_COMMAND;
+ } else if (opts->source.blob) {
+ opts->source.scope = CONFIG_SCOPE_COMMAND;
}
- if (respect_includes_opt == -1)
- config_options.respect_includes = !given_config_source.file;
+ if (opts->respect_includes_opt == -1)
+ opts->options.respect_includes = !opts->source.file;
else
- config_options.respect_includes = respect_includes_opt;
+ opts->options.respect_includes = opts->respect_includes_opt;
if (startup_info->have_repository) {
- config_options.commondir = get_git_common_dir();
- config_options.git_dir = get_git_dir();
+ opts->options.commondir = get_git_common_dir();
+ opts->options.git_dir = get_git_dir();
}
}
-static void handle_nul(void) {
- if (end_nul) {
- term = '\0';
- delim = '\n';
- key_delim = '\n';
- }
+static void location_options_release(struct config_location_options *opts)
+{
+ free(opts->file_to_free);
}
-#define CONFIG_LOCATION_OPTIONS \
- OPT_GROUP(N_("Config file location")), \
- OPT_BOOL(0, "global", &use_global_config, N_("use global config file")), \
- OPT_BOOL(0, "system", &use_system_config, N_("use system config file")), \
- OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")), \
- OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")), \
- OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")), \
- OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object"))
-
-#define CONFIG_TYPE_OPTIONS \
- OPT_GROUP(N_("Type")), \
- OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), \
- OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL), \
- OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT), \
- OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), \
- OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR), \
- OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), \
- OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE)
-
-#define CONFIG_DISPLAY_OPTIONS \
- OPT_GROUP(N_("Display options")), \
- OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")), \
- OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), \
- 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)"))
-
-static struct option builtin_config_options[] = {
- CONFIG_LOCATION_OPTIONS,
- OPT_GROUP(N_("Action")),
- OPT_CMDMODE(0, "get", &actions, N_("get value: name [<value-pattern>]"), ACTION_GET),
- OPT_CMDMODE(0, "get-all", &actions, N_("get all values: key [<value-pattern>]"), ACTION_GET_ALL),
- OPT_CMDMODE(0, "get-regexp", &actions, N_("get values for regexp: name-regex [<value-pattern>]"), ACTION_GET_REGEXP),
- OPT_CMDMODE(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH),
- OPT_CMDMODE(0, "replace-all", &actions, N_("replace all matching variables: name value [<value-pattern>]"), ACTION_REPLACE_ALL),
- OPT_CMDMODE(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
- OPT_CMDMODE(0, "unset", &actions, N_("remove a variable: name [<value-pattern>]"), ACTION_UNSET),
- OPT_CMDMODE(0, "unset-all", &actions, N_("remove all matches: name [<value-pattern>]"), ACTION_UNSET_ALL),
- OPT_CMDMODE(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION),
- OPT_CMDMODE(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
- OPT_CMDMODE('l', "list", &actions, N_("list all"), ACTION_LIST),
- OPT_CMDMODE('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
- OPT_CMDMODE(0, "get-color", &actions, N_("find the color configured: slot [<default>]"), ACTION_GET_COLOR),
- OPT_CMDMODE(0, "get-colorbool", &actions, N_("find the color setting: slot [<stdout-is-tty>]"), ACTION_GET_COLORBOOL),
- CONFIG_TYPE_OPTIONS,
- CONFIG_DISPLAY_OPTIONS,
- OPT_GROUP(N_("Other")),
- OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")),
- OPT_STRING(0, "comment", &comment_arg, N_("value"), N_("human-readable comment string (# will be prepended as needed)")),
- OPT_BOOL(0, "fixed-value", &fixed_value, N_("use string equality when comparing values to 'value-pattern'")),
- OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
- OPT_END(),
-};
-
-static NORETURN void usage_builtin_config(void)
+static void display_options_init(struct config_display_options *opts)
{
- usage_with_options(builtin_config_usage, builtin_config_options);
+ if (opts->end_nul) {
+ opts->term = '\0';
+ opts->delim = '\n';
+ opts->key_delim = '\n';
+ }
}
static int cmd_config_list(int argc, const char **argv, const char *prefix)
{
+ struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT;
+ struct config_display_options display_opts = CONFIG_DISPLAY_OPTIONS_INIT;
struct option opts[] = {
- CONFIG_LOCATION_OPTIONS,
- CONFIG_DISPLAY_OPTIONS,
+ CONFIG_LOCATION_OPTIONS(location_opts),
+ CONFIG_DISPLAY_OPTIONS(display_opts),
OPT_GROUP(N_("Other")),
- OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
+ OPT_BOOL(0, "includes", &location_opts.respect_includes_opt,
+ N_("respect include directives on lookup")),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, opts, builtin_config_list_usage, 0);
check_argc(argc, 0, 0);
- handle_config_location(prefix);
- handle_nul();
+ location_options_init(&location_opts, prefix);
+ display_options_init(&display_opts);
setup_auto_pager("config", 1);
- if (config_with_options(show_all_config, NULL,
- &given_config_source, the_repository,
- &config_options) < 0) {
- if (given_config_source.file)
+ if (config_with_options(show_all_config, &display_opts,
+ &location_opts.source, the_repository,
+ &location_opts.options) < 0) {
+ if (location_opts.source.file)
die_errno(_("unable to read config file '%s'"),
- given_config_source.file);
+ location_opts.source.file);
else
die(_("error processing config file(s)"));
}
+ location_options_release(&location_opts);
return 0;
}
static int cmd_config_get(int argc, const char **argv, const char *prefix)
{
+ struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT;
+ struct config_display_options display_opts = CONFIG_DISPLAY_OPTIONS_INIT;
const char *value_pattern = NULL, *url = NULL;
int flags = 0;
+ unsigned get_value_flags = 0;
struct option opts[] = {
- CONFIG_LOCATION_OPTIONS,
- CONFIG_TYPE_OPTIONS,
+ CONFIG_LOCATION_OPTIONS(location_opts),
OPT_GROUP(N_("Filter options")),
- OPT_BOOL(0, "all", &do_all, N_("return all values for multi-valued config options")),
- OPT_BOOL(0, "regexp", &use_key_regexp, N_("interpret the name as a regular expression")),
+ OPT_BIT(0, "all", &get_value_flags, N_("return all values for multi-valued config options"), GET_VALUE_ALL),
+ OPT_BIT(0, "regexp", &get_value_flags, N_("interpret the name as a regular expression"), GET_VALUE_KEY_REGEXP),
OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
OPT_STRING(0, "url", &url, N_("URL"), N_("show config matching the given URL")),
- CONFIG_DISPLAY_OPTIONS,
- OPT_BOOL(0, "show-names", &show_keys, N_("show config keys in addition to their values")),
+ CONFIG_DISPLAY_OPTIONS(display_opts),
OPT_GROUP(N_("Other")),
- OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
- OPT_STRING(0, "default", &default_value, N_("value"), N_("use default value when missing entry")),
+ OPT_BOOL(0, "includes", &location_opts.respect_includes_opt,
+ N_("respect include directives on lookup")),
+ OPT_STRING(0, "default", &display_opts.default_value,
+ N_("value"), N_("use default value when missing entry")),
OPT_END(),
};
+ int ret;
argc = parse_options(argc, argv, prefix, opts, builtin_config_get_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
@@ -865,29 +892,38 @@ static int cmd_config_get(int argc, const char **argv, const char *prefix)
if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
die(_("--fixed-value only applies with 'value-pattern'"));
- if (default_value && (do_all || url))
+ if (display_opts.default_value &&
+ ((get_value_flags & GET_VALUE_ALL) || url))
die(_("--default= cannot be used with --all or --url="));
- if (url && (do_all || use_key_regexp || value_pattern))
+ if (url && ((get_value_flags & GET_VALUE_ALL) ||
+ (get_value_flags & GET_VALUE_KEY_REGEXP) ||
+ value_pattern))
die(_("--url= cannot be used with --all, --regexp or --value"));
- handle_config_location(prefix);
- handle_nul();
+ location_options_init(&location_opts, prefix);
+ display_options_init(&display_opts);
setup_auto_pager("config", 1);
if (url)
- return get_urlmatch(argv[0], url);
- return get_value(argv[0], value_pattern, flags);
+ ret = get_urlmatch(&location_opts, &display_opts, argv[0], url);
+ else
+ ret = get_value(&location_opts, &display_opts, argv[0], value_pattern,
+ get_value_flags, flags);
+
+ location_options_release(&location_opts);
+ return ret;
}
static int cmd_config_set(int argc, const char **argv, const char *prefix)
{
+ struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT;
const char *value_pattern = NULL, *comment_arg = NULL;
char *comment = NULL;
- int flags = 0, append = 0;
+ int flags = 0, append = 0, type = 0;
struct option opts[] = {
- CONFIG_LOCATION_OPTIONS,
- CONFIG_TYPE_OPTIONS,
+ CONFIG_LOCATION_OPTIONS(location_opts),
+ CONFIG_TYPE_OPTIONS(type),
OPT_GROUP(N_("Filter")),
OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE),
OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
@@ -903,7 +939,6 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, opts, builtin_config_set_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- check_write();
check_argc(argc, 2, 2);
if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
@@ -915,22 +950,24 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix)
comment = git_config_prepare_comment_string(comment_arg);
- handle_config_location(prefix);
+ location_options_init(&location_opts, prefix);
+ check_write(&location_opts.source);
- value = normalize_value(argv[0], argv[1], &default_kvi);
+ value = normalize_value(argv[0], argv[1], type, &default_kvi);
if ((flags & CONFIG_FLAGS_MULTI_REPLACE) || value_pattern) {
- ret = git_config_set_multivar_in_file_gently(given_config_source.file,
+ ret = git_config_set_multivar_in_file_gently(location_opts.source.file,
argv[0], value, value_pattern,
comment, flags);
} else {
- ret = git_config_set_in_file_gently(given_config_source.file,
+ ret = git_config_set_in_file_gently(location_opts.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]);
}
+ location_options_release(&location_opts);
free(comment);
free(value);
return ret;
@@ -938,101 +975,114 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix)
static int cmd_config_unset(int argc, const char **argv, const char *prefix)
{
+ struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT;
const char *value_pattern = NULL;
int flags = 0;
struct option opts[] = {
- CONFIG_LOCATION_OPTIONS,
+ CONFIG_LOCATION_OPTIONS(location_opts),
OPT_GROUP(N_("Filter")),
OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE),
OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
OPT_END(),
};
+ int ret;
argc = parse_options(argc, argv, prefix, opts, builtin_config_unset_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- check_write();
check_argc(argc, 1, 1);
if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
die(_("--fixed-value only applies with 'value-pattern'"));
- handle_config_location(prefix);
+ location_options_init(&location_opts, prefix);
+ check_write(&location_opts.source);
if ((flags & CONFIG_FLAGS_MULTI_REPLACE) || value_pattern)
- return git_config_set_multivar_in_file_gently(given_config_source.file,
- argv[0], NULL, value_pattern,
- NULL, flags);
+ ret = git_config_set_multivar_in_file_gently(location_opts.source.file,
+ argv[0], NULL, value_pattern,
+ NULL, flags);
else
- return git_config_set_in_file_gently(given_config_source.file, argv[0],
- NULL, NULL);
+ ret = git_config_set_in_file_gently(location_opts.source.file, argv[0],
+ NULL, NULL);
+
+ location_options_release(&location_opts);
+ return ret;
}
static int cmd_config_rename_section(int argc, const char **argv, const char *prefix)
{
+ struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT;
struct option opts[] = {
- CONFIG_LOCATION_OPTIONS,
+ CONFIG_LOCATION_OPTIONS(location_opts),
OPT_END(),
};
int ret;
argc = parse_options(argc, argv, prefix, opts, builtin_config_rename_section_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- check_write();
check_argc(argc, 2, 2);
- handle_config_location(prefix);
+ location_options_init(&location_opts, prefix);
+ check_write(&location_opts.source);
- ret = git_config_rename_section_in_file(given_config_source.file,
+ ret = git_config_rename_section_in_file(location_opts.source.file,
argv[0], argv[1]);
if (ret < 0)
- return ret;
+ goto out;
else if (!ret)
die(_("no such section: %s"), argv[0]);
+ ret = 0;
- return 0;
+out:
+ location_options_release(&location_opts);
+ return ret;
}
static int cmd_config_remove_section(int argc, const char **argv, const char *prefix)
{
+ struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT;
struct option opts[] = {
- CONFIG_LOCATION_OPTIONS,
+ CONFIG_LOCATION_OPTIONS(location_opts),
OPT_END(),
};
int ret;
argc = parse_options(argc, argv, prefix, opts, builtin_config_remove_section_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- check_write();
check_argc(argc, 1, 1);
- handle_config_location(prefix);
+ location_options_init(&location_opts, prefix);
+ check_write(&location_opts.source);
- ret = git_config_rename_section_in_file(given_config_source.file,
+ ret = git_config_rename_section_in_file(location_opts.source.file,
argv[0], NULL);
if (ret < 0)
- return ret;
+ goto out;
else if (!ret)
die(_("no such section: %s"), argv[0]);
+ ret = 0;
- return 0;
+out:
+ location_options_release(&location_opts);
+ return ret;
}
-static int show_editor(void)
+static int show_editor(struct config_location_options *opts)
{
char *config_file;
- if (!given_config_source.file && !startup_info->have_repository)
+ if (!opts->source.file && !startup_info->have_repository)
die(_("not in a git directory"));
- if (given_config_source.use_stdin)
+ if (opts->source.use_stdin)
die(_("editing stdin is not supported"));
- if (given_config_source.blob)
+ if (opts->source.blob)
die(_("editing blobs is not supported"));
git_config(git_default_config, NULL);
- config_file = given_config_source.file ?
- xstrdup(given_config_source.file) :
+ config_file = opts->source.file ?
+ xstrdup(opts->source.file) :
git_pathdup("config");
- if (use_global_config) {
+ if (opts->use_global_config) {
int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (fd >= 0) {
char *content = default_user_config();
@@ -1051,66 +1101,90 @@ static int show_editor(void)
static int cmd_config_edit(int argc, const char **argv, const char *prefix)
{
+ struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT;
struct option opts[] = {
- CONFIG_LOCATION_OPTIONS,
+ CONFIG_LOCATION_OPTIONS(location_opts),
OPT_END(),
};
+ int ret;
argc = parse_options(argc, argv, prefix, opts, builtin_config_edit_usage, 0);
- check_write();
check_argc(argc, 0, 0);
- handle_config_location(prefix);
+ location_options_init(&location_opts, prefix);
+ check_write(&location_opts.source);
- return show_editor();
+ ret = show_editor(&location_opts);
+ location_options_release(&location_opts);
+ return ret;
}
-static struct option builtin_subcommand_options[] = {
- OPT_SUBCOMMAND("list", &subcommand, cmd_config_list),
- OPT_SUBCOMMAND("get", &subcommand, cmd_config_get),
- OPT_SUBCOMMAND("set", &subcommand, cmd_config_set),
- OPT_SUBCOMMAND("unset", &subcommand, cmd_config_unset),
- OPT_SUBCOMMAND("rename-section", &subcommand, cmd_config_rename_section),
- OPT_SUBCOMMAND("remove-section", &subcommand, cmd_config_remove_section),
- OPT_SUBCOMMAND("edit", &subcommand, cmd_config_edit),
- OPT_END(),
-};
-
-int cmd_config(int argc, const char **argv, const char *prefix)
+static int cmd_config_actions(int argc, const char **argv, const char *prefix)
{
+ enum {
+ ACTION_GET = (1<<0),
+ ACTION_GET_ALL = (1<<1),
+ ACTION_GET_REGEXP = (1<<2),
+ ACTION_REPLACE_ALL = (1<<3),
+ ACTION_ADD = (1<<4),
+ ACTION_UNSET = (1<<5),
+ ACTION_UNSET_ALL = (1<<6),
+ ACTION_RENAME_SECTION = (1<<7),
+ ACTION_REMOVE_SECTION = (1<<8),
+ ACTION_LIST = (1<<9),
+ ACTION_EDIT = (1<<10),
+ ACTION_SET = (1<<11),
+ ACTION_SET_ALL = (1<<12),
+ ACTION_GET_COLOR = (1<<13),
+ ACTION_GET_COLORBOOL = (1<<14),
+ ACTION_GET_URLMATCH = (1<<15),
+ };
+ struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT;
+ struct config_display_options display_opts = CONFIG_DISPLAY_OPTIONS_INIT;
+ const char *comment_arg = NULL;
+ int actions = 0;
+ unsigned flags = 0;
+ struct option opts[] = {
+ CONFIG_LOCATION_OPTIONS(location_opts),
+ OPT_GROUP(N_("Action")),
+ OPT_CMDMODE(0, "get", &actions, N_("get value: name [<value-pattern>]"), ACTION_GET),
+ OPT_CMDMODE(0, "get-all", &actions, N_("get all values: key [<value-pattern>]"), ACTION_GET_ALL),
+ OPT_CMDMODE(0, "get-regexp", &actions, N_("get values for regexp: name-regex [<value-pattern>]"), ACTION_GET_REGEXP),
+ OPT_CMDMODE(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH),
+ OPT_CMDMODE(0, "replace-all", &actions, N_("replace all matching variables: name value [<value-pattern>]"), ACTION_REPLACE_ALL),
+ OPT_CMDMODE(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
+ OPT_CMDMODE(0, "unset", &actions, N_("remove a variable: name [<value-pattern>]"), ACTION_UNSET),
+ OPT_CMDMODE(0, "unset-all", &actions, N_("remove all matches: name [<value-pattern>]"), ACTION_UNSET_ALL),
+ OPT_CMDMODE(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION),
+ OPT_CMDMODE(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
+ OPT_CMDMODE('l', "list", &actions, N_("list all"), ACTION_LIST),
+ OPT_CMDMODE('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
+ OPT_CMDMODE(0, "get-color", &actions, N_("find the color configured: slot [<default>]"), ACTION_GET_COLOR),
+ OPT_CMDMODE(0, "get-colorbool", &actions, N_("find the color setting: slot [<stdout-is-tty>]"), ACTION_GET_COLORBOOL),
+ CONFIG_DISPLAY_OPTIONS(display_opts),
+ OPT_GROUP(N_("Other")),
+ OPT_STRING(0, "default", &display_opts.default_value,
+ N_("value"), N_("with --get, use default value when missing entry")),
+ OPT_STRING(0, "comment", &comment_arg, N_("value"), N_("human-readable comment string (# will be prepended as needed)")),
+ OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
+ OPT_BOOL(0, "includes", &location_opts.respect_includes_opt,
+ N_("respect include directives on lookup")),
+ OPT_END(),
+ };
char *value = NULL, *comment = NULL;
- int flags = 0;
int ret = 0;
struct key_value_info default_kvi = KVI_INIT;
- given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));
-
- /*
- * This is somewhat hacky: we first parse the command line while
- * keeping all args intact in order to determine whether a subcommand
- * has been specified. If so, we re-parse it a second time, but this
- * time we drop KEEP_ARGV0. This is so that we don't munge the command
- * line in case no subcommand was given, which would otherwise confuse
- * us when parsing the legacy-style modes that don't use subcommands.
- */
- argc = parse_options(argc, argv, prefix, builtin_subcommand_options, builtin_config_usage,
- PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_ARGV0|PARSE_OPT_KEEP_UNKNOWN_OPT);
- if (subcommand) {
- argc = parse_options(argc, argv, prefix, builtin_subcommand_options, builtin_config_usage,
- PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_UNKNOWN_OPT);
- return subcommand(argc, argv, prefix);
- }
-
- argc = parse_options(argc, argv, prefix, builtin_config_options,
+ argc = parse_options(argc, argv, prefix, opts,
builtin_config_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- handle_config_location(prefix);
- handle_nul();
+ location_options_init(&location_opts, prefix);
+ display_options_init(&display_opts);
- if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && type) {
+ if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && display_opts.type) {
error(_("--get-color and variable type are incoherent"));
- usage_builtin_config();
+ exit(129);
}
if (actions == 0)
@@ -1119,34 +1193,35 @@ int cmd_config(int argc, const char **argv, const char *prefix)
case 2: actions = ACTION_SET; break;
case 3: actions = ACTION_SET_ALL; break;
default:
- usage_builtin_config();
+ error(_("no action specified"));
+ exit(129);
}
- if (omit_values &&
+ if (display_opts.omit_values &&
!(actions == ACTION_LIST || actions == ACTION_GET_REGEXP)) {
error(_("--name-only is only applicable to --list or --get-regexp"));
- usage_builtin_config();
+ exit(129);
}
- if (show_origin && !(actions &
+ if (display_opts.show_origin && !(actions &
(ACTION_GET|ACTION_GET_ALL|ACTION_GET_REGEXP|ACTION_LIST))) {
error(_("--show-origin is only applicable to --get, --get-all, "
"--get-regexp, and --list"));
- usage_builtin_config();
+ exit(129);
}
- if (default_value && !(actions & ACTION_GET)) {
+ if (display_opts.default_value && !(actions & ACTION_GET)) {
error(_("--default is only applicable to --get"));
- usage_builtin_config();
+ exit(129);
}
if (comment_arg &&
!(actions & (ACTION_ADD|ACTION_SET|ACTION_SET_ALL|ACTION_REPLACE_ALL))) {
error(_("--comment is only applicable to add/set/replace operations"));
- usage_builtin_config();
+ exit(129);
}
/* check usage of --fixed-value */
- if (fixed_value) {
+ if (flags & CONFIG_FLAGS_FIXED_VALUE) {
int allowed_usage = 0;
switch (actions) {
@@ -1175,123 +1250,125 @@ int cmd_config(int argc, const char **argv, const char *prefix)
if (!allowed_usage) {
error(_("--fixed-value only applies with 'value-pattern'"));
- usage_builtin_config();
+ exit(129);
}
-
- flags |= CONFIG_FLAGS_FIXED_VALUE;
}
comment = git_config_prepare_comment_string(comment_arg);
- if (actions & PAGING_ACTIONS)
+ /*
+ * The following actions may produce more than one line of output and
+ * should therefore be paged.
+ */
+ if (actions & (ACTION_LIST | ACTION_GET_ALL | ACTION_GET_REGEXP | ACTION_GET_URLMATCH))
setup_auto_pager("config", 1);
if (actions == ACTION_LIST) {
check_argc(argc, 0, 0);
- if (config_with_options(show_all_config, NULL,
- &given_config_source, the_repository,
- &config_options) < 0) {
- if (given_config_source.file)
+ if (config_with_options(show_all_config, &display_opts,
+ &location_opts.source, the_repository,
+ &location_opts.options) < 0) {
+ if (location_opts.source.file)
die_errno(_("unable to read config file '%s'"),
- given_config_source.file);
+ location_opts.source.file);
else
die(_("error processing config file(s)"));
}
}
else if (actions == ACTION_EDIT) {
- ret = show_editor();
+ ret = show_editor(&location_opts);
}
else if (actions == ACTION_SET) {
- check_write();
+ check_write(&location_opts.source);
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], comment, value);
+ value = normalize_value(argv[0], argv[1], display_opts.type, &default_kvi);
+ ret = git_config_set_in_file_gently(location_opts.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]);
}
else if (actions == ACTION_SET_ALL) {
- check_write();
+ check_write(&location_opts.source);
check_argc(argc, 2, 3);
- value = normalize_value(argv[0], argv[1], &default_kvi);
- ret = git_config_set_multivar_in_file_gently(given_config_source.file,
+ value = normalize_value(argv[0], argv[1], display_opts.type, &default_kvi);
+ ret = git_config_set_multivar_in_file_gently(location_opts.source.file,
argv[0], value, argv[2],
comment, flags);
}
else if (actions == ACTION_ADD) {
- check_write();
+ check_write(&location_opts.source);
check_argc(argc, 2, 2);
- value = normalize_value(argv[0], argv[1], &default_kvi);
- ret = git_config_set_multivar_in_file_gently(given_config_source.file,
+ value = normalize_value(argv[0], argv[1], display_opts.type, &default_kvi);
+ ret = git_config_set_multivar_in_file_gently(location_opts.source.file,
argv[0], value,
CONFIG_REGEX_NONE,
comment, flags);
}
else if (actions == ACTION_REPLACE_ALL) {
- check_write();
+ check_write(&location_opts.source);
check_argc(argc, 2, 3);
- value = normalize_value(argv[0], argv[1], &default_kvi);
- ret = git_config_set_multivar_in_file_gently(given_config_source.file,
+ value = normalize_value(argv[0], argv[1], display_opts.type, &default_kvi);
+ ret = git_config_set_multivar_in_file_gently(location_opts.source.file,
argv[0], value, argv[2],
comment, flags | CONFIG_FLAGS_MULTI_REPLACE);
}
else if (actions == ACTION_GET) {
check_argc(argc, 1, 2);
- return get_value(argv[0], argv[1], flags);
+ ret = get_value(&location_opts, &display_opts, argv[0], argv[1],
+ 0, flags);
}
else if (actions == ACTION_GET_ALL) {
- do_all = 1;
check_argc(argc, 1, 2);
- return get_value(argv[0], argv[1], flags);
+ ret = get_value(&location_opts, &display_opts, argv[0], argv[1],
+ GET_VALUE_ALL, flags);
}
else if (actions == ACTION_GET_REGEXP) {
- show_keys = 1;
- use_key_regexp = 1;
- do_all = 1;
+ display_opts.show_keys = 1;
check_argc(argc, 1, 2);
- return get_value(argv[0], argv[1], flags);
+ ret = get_value(&location_opts, &display_opts, argv[0], argv[1],
+ GET_VALUE_ALL|GET_VALUE_KEY_REGEXP, flags);
}
else if (actions == ACTION_GET_URLMATCH) {
check_argc(argc, 2, 2);
- return get_urlmatch(argv[0], argv[1]);
+ ret = get_urlmatch(&location_opts, &display_opts, argv[0], argv[1]);
}
else if (actions == ACTION_UNSET) {
- check_write();
+ check_write(&location_opts.source);
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],
- NULL, flags);
+ ret = git_config_set_multivar_in_file_gently(location_opts.source.file,
+ argv[0], NULL, argv[1],
+ NULL, flags);
else
- return git_config_set_in_file_gently(given_config_source.file,
- argv[0], NULL, NULL);
+ ret = git_config_set_in_file_gently(location_opts.source.file,
+ argv[0], NULL, NULL);
}
else if (actions == ACTION_UNSET_ALL) {
- check_write();
+ check_write(&location_opts.source);
check_argc(argc, 1, 2);
- return git_config_set_multivar_in_file_gently(given_config_source.file,
- argv[0], NULL, argv[1],
- NULL, flags | CONFIG_FLAGS_MULTI_REPLACE);
+ ret = git_config_set_multivar_in_file_gently(location_opts.source.file,
+ argv[0], NULL, argv[1],
+ NULL, flags | CONFIG_FLAGS_MULTI_REPLACE);
}
else if (actions == ACTION_RENAME_SECTION) {
- check_write();
+ check_write(&location_opts.source);
check_argc(argc, 2, 2);
- ret = git_config_rename_section_in_file(given_config_source.file,
+ ret = git_config_rename_section_in_file(location_opts.source.file,
argv[0], argv[1]);
if (ret < 0)
- return ret;
+ goto out;
else if (!ret)
die(_("no such section: %s"), argv[0]);
else
ret = 0;
}
else if (actions == ACTION_REMOVE_SECTION) {
- check_write();
+ check_write(&location_opts.source);
check_argc(argc, 1, 1);
- ret = git_config_rename_section_in_file(given_config_source.file,
+ ret = git_config_rename_section_in_file(location_opts.source.file,
argv[0], NULL);
if (ret < 0)
- return ret;
+ goto out;
else if (!ret)
die(_("no such section: %s"), argv[0]);
else
@@ -1299,16 +1376,51 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (actions == ACTION_GET_COLOR) {
check_argc(argc, 1, 2);
- get_color(argv[0], argv[1]);
+ get_color(&location_opts, argv[0], argv[1]);
}
else if (actions == ACTION_GET_COLORBOOL) {
check_argc(argc, 1, 2);
if (argc == 2)
color_stdout_is_tty = git_config_bool("command line", argv[1]);
- return get_colorbool(argv[0], argc == 2);
+ ret = get_colorbool(&location_opts, argv[0], argc == 2);
}
+out:
+ location_options_release(&location_opts);
free(comment);
free(value);
return ret;
}
+
+int cmd_config(int argc, const char **argv, const char *prefix)
+{
+ parse_opt_subcommand_fn *subcommand = NULL;
+ struct option subcommand_opts[] = {
+ OPT_SUBCOMMAND("list", &subcommand, cmd_config_list),
+ OPT_SUBCOMMAND("get", &subcommand, cmd_config_get),
+ OPT_SUBCOMMAND("set", &subcommand, cmd_config_set),
+ OPT_SUBCOMMAND("unset", &subcommand, cmd_config_unset),
+ OPT_SUBCOMMAND("rename-section", &subcommand, cmd_config_rename_section),
+ OPT_SUBCOMMAND("remove-section", &subcommand, cmd_config_remove_section),
+ OPT_SUBCOMMAND("edit", &subcommand, cmd_config_edit),
+ OPT_END(),
+ };
+
+ /*
+ * This is somewhat hacky: we first parse the command line while
+ * keeping all args intact in order to determine whether a subcommand
+ * has been specified. If so, we re-parse it a second time, but this
+ * time we drop KEEP_ARGV0. This is so that we don't munge the command
+ * line in case no subcommand was given, which would otherwise confuse
+ * us when parsing the legacy-style modes that don't use subcommands.
+ */
+ argc = parse_options(argc, argv, prefix, subcommand_opts, builtin_config_usage,
+ PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_ARGV0|PARSE_OPT_KEEP_UNKNOWN_OPT);
+ if (subcommand) {
+ argc = parse_options(argc, argv, prefix, subcommand_opts, builtin_config_usage,
+ PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_UNKNOWN_OPT);
+ return subcommand(argc, argv, prefix);
+ }
+
+ return cmd_config_actions(argc, argv, prefix);
+}
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 919282e12a..5517a4a1c0 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -98,7 +98,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
}
if (include_root_refs)
- flags |= FILTER_REFS_ROOT_REFS;
+ flags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD;
filter.match_as_path = 1;
filter_and_format_refs(&filter, flags, sorting, &format);
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
index 8768bfea3c..1d969494cf 100644
--- a/builtin/interpret-trailers.c
+++ b/builtin/interpret-trailers.c
@@ -141,7 +141,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,
LIST_HEAD(head);
struct strbuf sb = STRBUF_INIT;
struct strbuf trailer_block = STRBUF_INIT;
- struct trailer_info info;
+ struct trailer_info *info;
FILE *outfile = stdout;
trailer_config_init();
@@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts,
if (opts->in_place)
outfile = create_in_place_tempfile(file);
- parse_trailers(opts, &info, sb.buf, &head);
+ info = parse_trailers(opts, sb.buf, &head);
/* Print the lines before the trailers */
if (!opts->only_trailers)
- fwrite(sb.buf, 1, info.trailer_block_start, outfile);
+ fwrite(sb.buf, 1, trailer_block_start(info), outfile);
- if (!opts->only_trailers && !info.blank_line_before_trailer)
+ if (!opts->only_trailers && !blank_line_before_trailer_block(info))
fprintf(outfile, "\n");
@@ -178,8 +178,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,
/* Print the lines after the trailers as is */
if (!opts->only_trailers)
- fwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);
- trailer_info_release(&info);
+ fwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);
+ trailer_info_release(info);
if (opts->in_place)
if (rename_tempfile(&trailers_tempfile, file))
diff --git a/builtin/log.c b/builtin/log.c
index 517d7982f1..78a247d8a9 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -2401,7 +2401,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
}
if (creation_factor < 0)
- creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT;
+ creation_factor = CREATION_FACTOR_FOR_THE_SAME_SERIES;
else if (!rdiff_prev)
die(_("the option '%s' requires '%s'"), "--creation-factor", "--range-diff");
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index cd2396896d..a6992eaabb 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1315,6 +1315,7 @@ static void write_pack_file(void)
if (!pack_to_stdout) {
struct stat st;
struct strbuf tmpname = STRBUF_INIT;
+ struct bitmap_writer bitmap_writer;
char *idx_tmp_name = NULL;
/*
@@ -1340,8 +1341,9 @@ static void write_pack_file(void)
hash_to_hex(hash));
if (write_bitmap_index) {
- bitmap_writer_set_checksum(hash);
- bitmap_writer_build_type_index(
+ bitmap_writer_init(&bitmap_writer);
+ bitmap_writer_set_checksum(&bitmap_writer, hash);
+ bitmap_writer_build_type_index(&bitmap_writer,
&to_pack, written_list, nr_written);
}
@@ -1359,12 +1361,17 @@ static void write_pack_file(void)
strbuf_addstr(&tmpname, "bitmap");
stop_progress(&progress_state);
- bitmap_writer_show_progress(progress);
- bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1);
- if (bitmap_writer_build(&to_pack) < 0)
+ bitmap_writer_show_progress(&bitmap_writer,
+ progress);
+ bitmap_writer_select_commits(&bitmap_writer,
+ indexed_commits,
+ indexed_commits_nr);
+ if (bitmap_writer_build(&bitmap_writer, &to_pack) < 0)
die(_("failed to write bitmap index"));
- bitmap_writer_finish(written_list, nr_written,
+ bitmap_writer_finish(&bitmap_writer,
+ written_list, nr_written,
tmpname.buf, write_bitmap_options);
+ bitmap_writer_free(&bitmap_writer);
write_bitmap_index = 0;
strbuf_setlen(&tmpname, tmpname_len);
}
diff --git a/config.c b/config.c
index 496cd1a61e..3031eb8a53 100644
--- a/config.c
+++ b/config.c
@@ -125,7 +125,7 @@ struct config_include_data {
config_fn_t fn;
void *data;
const struct config_options *opts;
- struct git_config_source *config_source;
+ const struct git_config_source *config_source;
struct repository *repo;
/*
@@ -2137,7 +2137,7 @@ static int do_git_config_sequence(const struct config_options *opts,
}
int config_with_options(config_fn_t fn, void *data,
- struct git_config_source *config_source,
+ const struct git_config_source *config_source,
struct repository *repo,
const struct config_options *opts)
{
diff --git a/config.h b/config.h
index 2d2e22ed64..8620aa731b 100644
--- a/config.h
+++ b/config.h
@@ -232,7 +232,7 @@ void git_config(config_fn_t fn, void *);
* sets `opts.respect_includes` to `1` by default.
*/
int config_with_options(config_fn_t fn, void *,
- struct git_config_source *config_source,
+ const struct git_config_source *config_source,
struct repository *repo,
const struct config_options *opts);
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 5c0ddeb3d4..60a22d619a 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2989,22 +2989,42 @@ __git_complete_config_variable_name_and_value ()
_git_config ()
{
- case "$prev" in
- --get|--get-all|--unset|--unset-all)
- __gitcomp_nl "$(__git_config_get_set_variables)"
+ local subcommands subcommand
+
+ __git_resolve_builtins "config"
+
+ subcommands="$___git_resolved_builtins"
+ subcommand="$(__git_find_subcommand "$subcommands")"
+
+ if [ -z "$subcommand" ]
+ then
+ __gitcomp "$subcommands"
return
- ;;
- *.*)
- __git_complete_config_variable_value
+ fi
+
+ case "$cur" in
+ --*)
+ __gitcomp_builtin "config_$subcommand"
return
;;
esac
- case "$cur" in
- --*)
- __gitcomp_builtin config
+
+ case "$subcommand" in
+ get)
+ __gitcomp_nl "$(__git_config_get_set_variables)"
;;
- *)
- __git_complete_config_variable_name
+ set)
+ case "$prev" in
+ *.*)
+ __git_complete_config_variable_value
+ ;;
+ *)
+ __git_complete_config_variable_name
+ ;;
+ esac
+ ;;
+ unset)
+ __gitcomp_nl "$(__git_config_get_set_variables)"
;;
esac
}
diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
index 6a40917b1e..6ce22a28ed 100644
--- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c
+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -12,6 +12,7 @@ static CFStringRef username;
static CFDataRef password;
static CFDataRef password_expiry_utc;
static CFDataRef oauth_refresh_token;
+static int state_seen;
static void clear_credential(void)
{
@@ -171,6 +172,9 @@ static OSStatus find_internet_password(void)
CFRelease(item);
+ write_item("capability[]", "state", strlen("state"));
+ write_item("state[]", "osxkeychain:seen=1", strlen("osxkeychain:seen=1"));
+
out:
CFRelease(attrs);
@@ -284,6 +288,9 @@ static OSStatus add_internet_password(void)
CFDictionaryRef attrs;
OSStatus result;
+ if (state_seen)
+ return errSecSuccess;
+
/* Only store complete credentials */
if (!protocol || !host || !username || !password)
return -1;
@@ -395,6 +402,10 @@ static void read_credential(void)
oauth_refresh_token = CFDataCreate(kCFAllocatorDefault,
(UInt8 *)v,
strlen(v));
+ else if (!strcmp(buf, "state[]")) {
+ if (!strcmp(v, "osxkeychain:seen=1"))
+ state_seen = 1;
+ }
/*
* Ignore other lines; we don't know what they mean, but
* this future-proofs us when later versions of git do
@@ -414,6 +425,9 @@ int main(int argc, const char **argv)
if (!argv[1])
die("%s", usage);
+ if (open(argv[0], O_RDONLY | O_EXLOCK) == -1)
+ die("failed to lock %s", argv[0]);
+
read_credential();
if (!strcmp(argv[1], "get"))
diff --git a/fetch-pack.c b/fetch-pack.c
index d80e9c92dd..eba9e420ea 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -733,11 +733,6 @@ static void mark_alternate_complete(struct fetch_negotiator *negotiator UNUSED,
mark_complete(&obj->oid);
}
-struct loose_object_iter {
- struct oidset *loose_object_set;
- struct ref *refs;
-};
-
/*
* Mark recent commits available locally and reachable from a local ref as
* COMPLETE.
diff --git a/git-send-email.perl b/git-send-email.perl
index 821b2b3a13..f0be4b4560 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -1664,9 +1664,11 @@ EOF
$smtp->code =~ /250|200/ or die sprintf(__("Failed to send %s\n"), $subject).$smtp->message;
}
if ($quiet) {
- printf($dry_run ? __("Dry-Sent %s\n") : __("Sent %s\n"), $subject);
+ printf($dry_run ? __("Dry-Sent %s") : __("Sent %s"), $subject);
+ print "\n";
} else {
- print($dry_run ? __("Dry-OK. Log says:\n") : __("OK. Log says:\n"));
+ print($dry_run ? __("Dry-OK. Log says:") : __("OK. Log says:"));
+ print "\n";
if (!defined $sendmail_cmd && !file_name_is_absolute($smtp_server)) {
print "Server: $smtp_server\n";
print "MAIL FROM:<$raw_from>\n";
@@ -1686,10 +1688,11 @@ EOF
print $header, "\n";
if ($smtp) {
print __("Result: "), $smtp->code, ' ',
- ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
+ ($smtp->message =~ /\n([^\n]+\n)$/s);
} else {
- print __("Result: OK\n");
+ print __("Result: OK");
}
+ print "\n";
}
return 1;
diff --git a/mergetools/vimdiff b/mergetools/vimdiff
index 734d15a03b..f8ad6b35d4 100644
--- a/mergetools/vimdiff
+++ b/mergetools/vimdiff
@@ -325,7 +325,7 @@ gen_cmd () {
fi
# If this is a single window diff with all the buffers
- if ! echo "$tab" | grep ",\|/" >/dev/null
+ if ! echo "$tab" | grep -E ",|/" >/dev/null
then
CMD="$CMD | silent execute 'bufdo diffthis'"
fi
diff --git a/midx-write.c b/midx-write.c
index 9d096d5a28..2fa120c213 100644
--- a/midx-write.c
+++ b/midx-write.c
@@ -799,6 +799,7 @@ static int write_midx_bitmap(const char *midx_name,
{
int ret, i;
uint16_t options = 0;
+ struct bitmap_writer writer;
struct pack_idx_entry **index;
char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name,
hash_to_hex(midx_hash));
@@ -820,8 +821,10 @@ static int write_midx_bitmap(const char *midx_name,
for (i = 0; i < pdata->nr_objects; i++)
index[i] = &pdata->objects[i].idx;
- bitmap_writer_show_progress(flags & MIDX_PROGRESS);
- bitmap_writer_build_type_index(pdata, index, pdata->nr_objects);
+ bitmap_writer_init(&writer);
+ bitmap_writer_show_progress(&writer, flags & MIDX_PROGRESS);
+ bitmap_writer_build_type_index(&writer, pdata, index,
+ pdata->nr_objects);
/*
* bitmap_writer_finish expects objects in lex order, but pack_order
@@ -839,17 +842,19 @@ static int write_midx_bitmap(const char *midx_name,
for (i = 0; i < pdata->nr_objects; i++)
index[pack_order[i]] = &pdata->objects[i].idx;
- bitmap_writer_select_commits(commits, commits_nr, -1);
- ret = bitmap_writer_build(pdata);
+ bitmap_writer_select_commits(&writer, commits, commits_nr);
+ ret = bitmap_writer_build(&writer, pdata);
if (ret < 0)
goto cleanup;
- bitmap_writer_set_checksum(midx_hash);
- bitmap_writer_finish(index, pdata->nr_objects, bitmap_name, options);
+ bitmap_writer_set_checksum(&writer, midx_hash);
+ bitmap_writer_finish(&writer, index, pdata->nr_objects, bitmap_name,
+ options);
cleanup:
free(index);
free(bitmap_name);
+ bitmap_writer_free(&writer);
trace2_region_leave("midx", "write_midx_bitmap", the_repository);
diff --git a/object.h b/object.h
index 9293e703cc..99b9c8f114 100644
--- a/object.h
+++ b/object.h
@@ -81,6 +81,7 @@ void object_array_init(struct object_array *array);
* reflog.c: 10--12
* builtin/show-branch.c: 0-------------------------------------------26
* builtin/unpack-objects.c: 2021
+ * pack-bitmap.h: 22
*/
#define FLAG_BITS 28
diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c
index c6c8f94cc5..6cae670412 100644
--- a/pack-bitmap-write.c
+++ b/pack-bitmap-write.c
@@ -27,43 +27,53 @@ struct bitmapped_commit {
uint32_t commit_pos;
};
-struct bitmap_writer {
- struct ewah_bitmap *commits;
- struct ewah_bitmap *trees;
- struct ewah_bitmap *blobs;
- struct ewah_bitmap *tags;
+void bitmap_writer_init(struct bitmap_writer *writer)
+{
+ memset(writer, 0, sizeof(struct bitmap_writer));
+}
+
+void bitmap_writer_free(struct bitmap_writer *writer)
+{
+ uint32_t i;
- kh_oid_map_t *bitmaps;
- struct packing_data *to_pack;
+ if (!writer)
+ return;
- struct bitmapped_commit *selected;
- unsigned int selected_nr, selected_alloc;
+ ewah_free(writer->commits);
+ ewah_free(writer->trees);
+ ewah_free(writer->blobs);
+ ewah_free(writer->tags);
- struct progress *progress;
- int show_progress;
- unsigned char pack_checksum[GIT_MAX_RAWSZ];
-};
+ kh_destroy_oid_map(writer->bitmaps);
-static struct bitmap_writer writer;
+ for (i = 0; i < writer->selected_nr; i++) {
+ struct bitmapped_commit *bc = &writer->selected[i];
+ if (bc->write_as != bc->bitmap)
+ ewah_free(bc->write_as);
+ ewah_free(bc->bitmap);
+ }
+ free(writer->selected);
+}
-void bitmap_writer_show_progress(int show)
+void bitmap_writer_show_progress(struct bitmap_writer *writer, int show)
{
- writer.show_progress = show;
+ writer->show_progress = show;
}
/**
* Build the initial type index for the packfile or multi-pack-index
*/
-void bitmap_writer_build_type_index(struct packing_data *to_pack,
+void bitmap_writer_build_type_index(struct bitmap_writer *writer,
+ struct packing_data *to_pack,
struct pack_idx_entry **index,
uint32_t index_nr)
{
uint32_t i;
- writer.commits = ewah_new();
- writer.trees = ewah_new();
- writer.blobs = ewah_new();
- writer.tags = ewah_new();
+ writer->commits = ewah_new();
+ writer->trees = ewah_new();
+ writer->blobs = ewah_new();
+ writer->tags = ewah_new();
ALLOC_ARRAY(to_pack->in_pack_pos, to_pack->nr_objects);
for (i = 0; i < index_nr; ++i) {
@@ -88,19 +98,19 @@ void bitmap_writer_build_type_index(struct packing_data *to_pack,
switch (real_type) {
case OBJ_COMMIT:
- ewah_set(writer.commits, i);
+ ewah_set(writer->commits, i);
break;
case OBJ_TREE:
- ewah_set(writer.trees, i);
+ ewah_set(writer->trees, i);
break;
case OBJ_BLOB:
- ewah_set(writer.blobs, i);
+ ewah_set(writer->blobs, i);
break;
case OBJ_TAG:
- ewah_set(writer.tags, i);
+ ewah_set(writer->tags, i);
break;
default:
@@ -115,23 +125,26 @@ void bitmap_writer_build_type_index(struct packing_data *to_pack,
* Compute the actual bitmaps
*/
-static inline void push_bitmapped_commit(struct commit *commit)
+static inline void push_bitmapped_commit(struct bitmap_writer *writer,
+ struct commit *commit)
{
- if (writer.selected_nr >= writer.selected_alloc) {
- writer.selected_alloc = (writer.selected_alloc + 32) * 2;
- REALLOC_ARRAY(writer.selected, writer.selected_alloc);
+ if (writer->selected_nr >= writer->selected_alloc) {
+ writer->selected_alloc = (writer->selected_alloc + 32) * 2;
+ REALLOC_ARRAY(writer->selected, writer->selected_alloc);
}
- writer.selected[writer.selected_nr].commit = commit;
- writer.selected[writer.selected_nr].bitmap = NULL;
- writer.selected[writer.selected_nr].flags = 0;
+ writer->selected[writer->selected_nr].commit = commit;
+ writer->selected[writer->selected_nr].bitmap = NULL;
+ writer->selected[writer->selected_nr].write_as = NULL;
+ writer->selected[writer->selected_nr].flags = 0;
- writer.selected_nr++;
+ writer->selected_nr++;
}
-static uint32_t find_object_pos(const struct object_id *oid, int *found)
+static uint32_t find_object_pos(struct bitmap_writer *writer,
+ const struct object_id *oid, int *found)
{
- struct object_entry *entry = packlist_find(writer.to_pack, oid);
+ struct object_entry *entry = packlist_find(writer->to_pack, oid);
if (!entry) {
if (found)
@@ -143,17 +156,17 @@ static uint32_t find_object_pos(const struct object_id *oid, int *found)
if (found)
*found = 1;
- return oe_in_pack_pos(writer.to_pack, entry);
+ return oe_in_pack_pos(writer->to_pack, entry);
}
-static void compute_xor_offsets(void)
+static void compute_xor_offsets(struct bitmap_writer *writer)
{
static const int MAX_XOR_OFFSET_SEARCH = 10;
int i, next = 0;
- while (next < writer.selected_nr) {
- struct bitmapped_commit *stored = &writer.selected[next];
+ while (next < writer->selected_nr) {
+ struct bitmapped_commit *stored = &writer->selected[next];
int best_offset = 0;
struct ewah_bitmap *best_bitmap = stored->bitmap;
@@ -166,7 +179,7 @@ static void compute_xor_offsets(void)
break;
test_xor = ewah_pool_new();
- ewah_xor(writer.selected[curr].bitmap, stored->bitmap, test_xor);
+ ewah_xor(writer->selected[curr].bitmap, stored->bitmap, test_xor);
if (test_xor->buffer_size < best_bitmap->buffer_size) {
if (best_bitmap != stored->bitmap)
@@ -348,7 +361,8 @@ static void bitmap_builder_clear(struct bitmap_builder *bb)
bb->commits_nr = bb->commits_alloc = 0;
}
-static int fill_bitmap_tree(struct bitmap *bitmap,
+static int fill_bitmap_tree(struct bitmap_writer *writer,
+ struct bitmap *bitmap,
struct tree *tree)
{
int found;
@@ -360,7 +374,7 @@ static int fill_bitmap_tree(struct bitmap *bitmap,
* If our bit is already set, then there is nothing to do. Both this
* tree and all of its children will be set.
*/
- pos = find_object_pos(&tree->object.oid, &found);
+ pos = find_object_pos(writer, &tree->object.oid, &found);
if (!found)
return -1;
if (bitmap_get(bitmap, pos))
@@ -375,12 +389,12 @@ static int fill_bitmap_tree(struct bitmap *bitmap,
while (tree_entry(&desc, &entry)) {
switch (object_type(entry.mode)) {
case OBJ_TREE:
- if (fill_bitmap_tree(bitmap,
+ if (fill_bitmap_tree(writer, bitmap,
lookup_tree(the_repository, &entry.oid)) < 0)
return -1;
break;
case OBJ_BLOB:
- pos = find_object_pos(&entry.oid, &found);
+ pos = find_object_pos(writer, &entry.oid, &found);
if (!found)
return -1;
bitmap_set(bitmap, pos);
@@ -397,7 +411,8 @@ static int fill_bitmap_tree(struct bitmap *bitmap,
static int reused_bitmaps_nr;
-static int fill_bitmap_commit(struct bb_commit *ent,
+static int fill_bitmap_commit(struct bitmap_writer *writer,
+ struct bb_commit *ent,
struct commit *commit,
struct prio_queue *queue,
struct prio_queue *tree_queue,
@@ -436,7 +451,7 @@ static int fill_bitmap_commit(struct bb_commit *ent,
* Mark ourselves and queue our tree. The commit
* walk ensures we cover all parents.
*/
- pos = find_object_pos(&c->object.oid, &found);
+ pos = find_object_pos(writer, &c->object.oid, &found);
if (!found)
return -1;
bitmap_set(ent->bitmap, pos);
@@ -444,7 +459,8 @@ static int fill_bitmap_commit(struct bb_commit *ent,
repo_get_commit_tree(the_repository, c));
for (p = c->parents; p; p = p->next) {
- pos = find_object_pos(&p->item->object.oid, &found);
+ pos = find_object_pos(writer, &p->item->object.oid,
+ &found);
if (!found)
return -1;
if (!bitmap_get(ent->bitmap, pos)) {
@@ -455,29 +471,31 @@ static int fill_bitmap_commit(struct bb_commit *ent,
}
while (tree_queue->nr) {
- if (fill_bitmap_tree(ent->bitmap,
+ if (fill_bitmap_tree(writer, ent->bitmap,
prio_queue_get(tree_queue)) < 0)
return -1;
}
return 0;
}
-static void store_selected(struct bb_commit *ent, struct commit *commit)
+static void store_selected(struct bitmap_writer *writer,
+ struct bb_commit *ent, struct commit *commit)
{
- struct bitmapped_commit *stored = &writer.selected[ent->idx];
+ struct bitmapped_commit *stored = &writer->selected[ent->idx];
khiter_t hash_pos;
int hash_ret;
stored->bitmap = bitmap_to_ewah(ent->bitmap);
- hash_pos = kh_put_oid_map(writer.bitmaps, commit->object.oid, &hash_ret);
+ hash_pos = kh_put_oid_map(writer->bitmaps, commit->object.oid, &hash_ret);
if (hash_ret == 0)
die("Duplicate entry when writing index: %s",
oid_to_hex(&commit->object.oid));
- kh_value(writer.bitmaps, hash_pos) = stored;
+ kh_value(writer->bitmaps, hash_pos) = stored;
}
-int bitmap_writer_build(struct packing_data *to_pack)
+int bitmap_writer_build(struct bitmap_writer *writer,
+ struct packing_data *to_pack)
{
struct bitmap_builder bb;
size_t i;
@@ -488,11 +506,12 @@ int bitmap_writer_build(struct packing_data *to_pack)
uint32_t *mapping;
int closed = 1; /* until proven otherwise */
- writer.bitmaps = kh_init_oid_map();
- writer.to_pack = to_pack;
+ writer->bitmaps = kh_init_oid_map();
+ writer->to_pack = to_pack;
- if (writer.show_progress)
- writer.progress = start_progress("Building bitmaps", writer.selected_nr);
+ if (writer->show_progress)
+ writer->progress = start_progress("Building bitmaps",
+ writer->selected_nr);
trace2_region_enter("pack-bitmap-write", "building_bitmaps_total",
the_repository);
@@ -502,23 +521,23 @@ int bitmap_writer_build(struct packing_data *to_pack)
else
mapping = NULL;
- bitmap_builder_init(&bb, &writer, old_bitmap);
+ bitmap_builder_init(&bb, writer, old_bitmap);
for (i = bb.commits_nr; i > 0; i--) {
struct commit *commit = bb.commits[i-1];
struct bb_commit *ent = bb_data_at(&bb.data, commit);
struct commit *child;
int reused = 0;
- if (fill_bitmap_commit(ent, commit, &queue, &tree_queue,
+ if (fill_bitmap_commit(writer, ent, commit, &queue, &tree_queue,
old_bitmap, mapping) < 0) {
closed = 0;
break;
}
if (ent->selected) {
- store_selected(ent, commit);
+ store_selected(writer, ent, commit);
nr_stored++;
- display_progress(writer.progress, nr_stored);
+ display_progress(writer->progress, nr_stored);
}
while ((child = pop_commit(&ent->reverse_edges))) {
@@ -549,10 +568,10 @@ int bitmap_writer_build(struct packing_data *to_pack)
trace2_data_intmax("pack-bitmap-write", the_repository,
"building_bitmaps_reused", reused_bitmaps_nr);
- stop_progress(&writer.progress);
+ stop_progress(&writer->progress);
if (closed)
- compute_xor_offsets();
+ compute_xor_offsets(writer);
return closed ? 0 : -1;
}
@@ -590,9 +609,9 @@ static int date_compare(const void *_a, const void *_b)
return (long)b->date - (long)a->date;
}
-void bitmap_writer_select_commits(struct commit **indexed_commits,
- unsigned int indexed_commits_nr,
- int max_bitmaps)
+void bitmap_writer_select_commits(struct bitmap_writer *writer,
+ struct commit **indexed_commits,
+ unsigned int indexed_commits_nr)
{
unsigned int i = 0, j, next;
@@ -600,12 +619,12 @@ void bitmap_writer_select_commits(struct commit **indexed_commits,
if (indexed_commits_nr < 100) {
for (i = 0; i < indexed_commits_nr; ++i)
- push_bitmapped_commit(indexed_commits[i]);
+ push_bitmapped_commit(writer, indexed_commits[i]);
return;
}
- if (writer.show_progress)
- writer.progress = start_progress("Selecting bitmap commits", 0);
+ if (writer->show_progress)
+ writer->progress = start_progress("Selecting bitmap commits", 0);
for (;;) {
struct commit *chosen = NULL;
@@ -615,11 +634,6 @@ void bitmap_writer_select_commits(struct commit **indexed_commits,
if (i + next >= indexed_commits_nr)
break;
- if (max_bitmaps > 0 && writer.selected_nr >= max_bitmaps) {
- writer.selected_nr = max_bitmaps;
- break;
- }
-
if (next == 0) {
chosen = indexed_commits[i];
} else {
@@ -638,13 +652,13 @@ void bitmap_writer_select_commits(struct commit **indexed_commits,
}
}
- push_bitmapped_commit(chosen);
+ push_bitmapped_commit(writer, chosen);
i += next + 1;
- display_progress(writer.progress, i);
+ display_progress(writer->progress, i);
}
- stop_progress(&writer.progress);
+ stop_progress(&writer->progress);
}
@@ -670,19 +684,18 @@ static const struct object_id *oid_access(size_t pos, const void *table)
return &index[pos]->oid;
}
-static void write_selected_commits_v1(struct hashfile *f,
- uint32_t *commit_positions,
- off_t *offsets)
+static void write_selected_commits_v1(struct bitmap_writer *writer,
+ struct hashfile *f, off_t *offsets)
{
int i;
- for (i = 0; i < writer.selected_nr; ++i) {
- struct bitmapped_commit *stored = &writer.selected[i];
+ for (i = 0; i < writer->selected_nr; ++i) {
+ struct bitmapped_commit *stored = &writer->selected[i];
if (offsets)
offsets[i] = hashfile_total(f);
- hashwrite_be32(f, commit_positions[i]);
+ hashwrite_be32(f, stored->commit_pos);
hashwrite_u8(f, stored->xor_offset);
hashwrite_u8(f, stored->flags);
@@ -692,29 +705,28 @@ static void write_selected_commits_v1(struct hashfile *f,
static int table_cmp(const void *_va, const void *_vb, void *_data)
{
- uint32_t *commit_positions = _data;
- uint32_t a = commit_positions[*(uint32_t *)_va];
- uint32_t b = commit_positions[*(uint32_t *)_vb];
+ struct bitmap_writer *writer = _data;
+ struct bitmapped_commit *a = &writer->selected[*(uint32_t *)_va];
+ struct bitmapped_commit *b = &writer->selected[*(uint32_t *)_vb];
- if (a > b)
- return 1;
- else if (a < b)
+ if (a->commit_pos < b->commit_pos)
return -1;
+ else if (a->commit_pos > b->commit_pos)
+ return 1;
return 0;
}
-static void write_lookup_table(struct hashfile *f,
- uint32_t *commit_positions,
+static void write_lookup_table(struct bitmap_writer *writer, struct hashfile *f,
off_t *offsets)
{
uint32_t i;
uint32_t *table, *table_inv;
- ALLOC_ARRAY(table, writer.selected_nr);
- ALLOC_ARRAY(table_inv, writer.selected_nr);
+ ALLOC_ARRAY(table, writer->selected_nr);
+ ALLOC_ARRAY(table_inv, writer->selected_nr);
- for (i = 0; i < writer.selected_nr; i++)
+ for (i = 0; i < writer->selected_nr; i++)
table[i] = i;
/*
@@ -722,17 +734,17 @@ static void write_lookup_table(struct hashfile *f,
* bitmap corresponds to j'th bitmapped commit (among the selected
* commits) in lex order of OIDs.
*/
- QSORT_S(table, writer.selected_nr, table_cmp, commit_positions);
+ QSORT_S(table, writer->selected_nr, table_cmp, writer);
/* table_inv helps us discover that relationship (i'th bitmap
* to j'th commit by j = table_inv[i])
*/
- for (i = 0; i < writer.selected_nr; i++)
+ for (i = 0; i < writer->selected_nr; i++)
table_inv[table[i]] = i;
trace2_region_enter("pack-bitmap-write", "writing_lookup_table", the_repository);
- for (i = 0; i < writer.selected_nr; i++) {
- struct bitmapped_commit *selected = &writer.selected[table[i]];
+ for (i = 0; i < writer->selected_nr; i++) {
+ struct bitmapped_commit *selected = &writer->selected[table[i]];
uint32_t xor_offset = selected->xor_offset;
uint32_t xor_row;
@@ -753,7 +765,7 @@ static void write_lookup_table(struct hashfile *f,
xor_row = 0xffffffff;
}
- hashwrite_be32(f, commit_positions[table[i]]);
+ hashwrite_be32(f, writer->selected[table[i]].commit_pos);
hashwrite_be64(f, (uint64_t)offsets[table[i]]);
hashwrite_be32(f, xor_row);
}
@@ -775,12 +787,14 @@ static void write_hash_cache(struct hashfile *f,
}
}
-void bitmap_writer_set_checksum(const unsigned char *sha1)
+void bitmap_writer_set_checksum(struct bitmap_writer *writer,
+ const unsigned char *sha1)
{
- hashcpy(writer.pack_checksum, sha1);
+ hashcpy(writer->pack_checksum, sha1);
}
-void bitmap_writer_finish(struct pack_idx_entry **index,
+void bitmap_writer_finish(struct bitmap_writer *writer,
+ struct pack_idx_entry **index,
uint32_t index_nr,
const char *filename,
uint16_t options)
@@ -789,7 +803,6 @@ void bitmap_writer_finish(struct pack_idx_entry **index,
static uint16_t flags = BITMAP_OPT_FULL_DAG;
struct strbuf tmp_file = STRBUF_INIT;
struct hashfile *f;
- uint32_t *commit_positions = NULL;
off_t *offsets = NULL;
uint32_t i;
@@ -802,34 +815,32 @@ void bitmap_writer_finish(struct pack_idx_entry **index,
memcpy(header.magic, BITMAP_IDX_SIGNATURE, sizeof(BITMAP_IDX_SIGNATURE));
header.version = htons(default_version);
header.options = htons(flags | options);
- header.entry_count = htonl(writer.selected_nr);
- hashcpy(header.checksum, writer.pack_checksum);
+ header.entry_count = htonl(writer->selected_nr);
+ hashcpy(header.checksum, writer->pack_checksum);
hashwrite(f, &header, sizeof(header) - GIT_MAX_RAWSZ + the_hash_algo->rawsz);
- dump_bitmap(f, writer.commits);
- dump_bitmap(f, writer.trees);
- dump_bitmap(f, writer.blobs);
- dump_bitmap(f, writer.tags);
+ dump_bitmap(f, writer->commits);
+ dump_bitmap(f, writer->trees);
+ dump_bitmap(f, writer->blobs);
+ dump_bitmap(f, writer->tags);
if (options & BITMAP_OPT_LOOKUP_TABLE)
CALLOC_ARRAY(offsets, index_nr);
- ALLOC_ARRAY(commit_positions, writer.selected_nr);
-
- for (i = 0; i < writer.selected_nr; i++) {
- struct bitmapped_commit *stored = &writer.selected[i];
- int commit_pos = oid_pos(&stored->commit->object.oid, index, index_nr, oid_access);
+ for (i = 0; i < writer->selected_nr; i++) {
+ struct bitmapped_commit *stored = &writer->selected[i];
+ int commit_pos = oid_pos(&stored->commit->object.oid, index,
+ index_nr, oid_access);
if (commit_pos < 0)
BUG(_("trying to write commit not in index"));
-
- commit_positions[i] = commit_pos;
+ stored->commit_pos = commit_pos;
}
- write_selected_commits_v1(f, commit_positions, offsets);
+ write_selected_commits_v1(writer, f, offsets);
if (options & BITMAP_OPT_LOOKUP_TABLE)
- write_lookup_table(f, commit_positions, offsets);
+ write_lookup_table(writer, f, offsets);
if (options & BITMAP_OPT_HASH_CACHE)
write_hash_cache(f, index, index_nr);
@@ -844,6 +855,5 @@ void bitmap_writer_finish(struct pack_idx_entry **index,
die_errno("unable to rename temporary bitmap file to '%s'", filename);
strbuf_release(&tmp_file);
- free(commit_positions);
free(offsets);
}
diff --git a/pack-bitmap.h b/pack-bitmap.h
index c7dea13217..3091095f33 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -97,9 +97,29 @@ int bitmap_has_oid_in_uninteresting(struct bitmap_index *, const struct object_i
off_t get_disk_usage_from_bitmap(struct bitmap_index *, struct rev_info *);
-void bitmap_writer_show_progress(int show);
-void bitmap_writer_set_checksum(const unsigned char *sha1);
-void bitmap_writer_build_type_index(struct packing_data *to_pack,
+struct bitmap_writer {
+ struct ewah_bitmap *commits;
+ struct ewah_bitmap *trees;
+ struct ewah_bitmap *blobs;
+ struct ewah_bitmap *tags;
+
+ kh_oid_map_t *bitmaps;
+ struct packing_data *to_pack;
+
+ struct bitmapped_commit *selected;
+ unsigned int selected_nr, selected_alloc;
+
+ struct progress *progress;
+ int show_progress;
+ unsigned char pack_checksum[GIT_MAX_RAWSZ];
+};
+
+void bitmap_writer_init(struct bitmap_writer *writer);
+void bitmap_writer_show_progress(struct bitmap_writer *writer, int show);
+void bitmap_writer_set_checksum(struct bitmap_writer *writer,
+ const unsigned char *sha1);
+void bitmap_writer_build_type_index(struct bitmap_writer *writer,
+ struct packing_data *to_pack,
struct pack_idx_entry **index,
uint32_t index_nr);
uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git,
@@ -109,13 +129,17 @@ int rebuild_bitmap(const uint32_t *reposition,
struct bitmap *dest);
struct ewah_bitmap *bitmap_for_commit(struct bitmap_index *bitmap_git,
struct commit *commit);
-void bitmap_writer_select_commits(struct commit **indexed_commits,
- unsigned int indexed_commits_nr, int max_bitmaps);
-int bitmap_writer_build(struct packing_data *to_pack);
-void bitmap_writer_finish(struct pack_idx_entry **index,
+void bitmap_writer_select_commits(struct bitmap_writer *writer,
+ struct commit **indexed_commits,
+ unsigned int indexed_commits_nr);
+int bitmap_writer_build(struct bitmap_writer *writer,
+ struct packing_data *to_pack);
+void bitmap_writer_finish(struct bitmap_writer *writer,
+ struct pack_idx_entry **index,
uint32_t index_nr,
const char *filename,
uint16_t options);
+void bitmap_writer_free(struct bitmap_writer *writer);
char *midx_bitmap_filename(struct multi_pack_index *midx);
char *pack_bitmap_filename(struct packed_git *p);
diff --git a/range-diff.h b/range-diff.h
index 04ffe217be..2f69f6a434 100644
--- a/range-diff.h
+++ b/range-diff.h
@@ -6,6 +6,12 @@
#define RANGE_DIFF_CREATION_FACTOR_DEFAULT 60
+/*
+ * A much higher value than the default, when we KNOW we are comparing
+ * the same series (e.g., used when format-patch calls range-diff).
+ */
+#define CREATION_FACTOR_FOR_THE_SAME_SERIES 999
+
struct range_diff_options {
int creation_factor;
unsigned dual_color:1;
diff --git a/ref-filter.c b/ref-filter.c
index 31cc096644..cf6525195a 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2634,7 +2634,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
each_ref_fn cb,
void *cb_data)
{
- if (filter->kind == FILTER_REFS_KIND_MASK) {
+ if (filter->kind & FILTER_REFS_ROOT_REFS) {
/* In this case, we want to print all refs including root refs. */
return refs_for_each_include_root_refs(get_main_ref_store(the_repository),
cb, cb_data);
@@ -2764,8 +2764,10 @@ static int ref_kind_from_refname(const char *refname)
return ref_kind[i].kind;
}
- if (is_pseudoref(get_main_ref_store(the_repository), refname))
+ if (is_pseudo_ref(refname))
return FILTER_REFS_PSEUDOREFS;
+ if (is_root_ref(refname))
+ return FILTER_REFS_ROOT_REFS;
return FILTER_REFS_OTHERS;
}
@@ -2802,11 +2804,11 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const struct
/*
* Generally HEAD refs are printed with special description denoting a rebase,
* detached state and so forth. This is useful when only printing the HEAD ref
- * But when it is being printed along with other pseudorefs, it makes sense to
- * keep the formatting consistent. So we mask the type to act like a pseudoref.
+ * But when it is being printed along with other root refs, it makes sense to
+ * keep the formatting consistent. So we mask the type to act like a root ref.
*/
- if (filter->kind == FILTER_REFS_KIND_MASK && kind == FILTER_REFS_DETACHED_HEAD)
- kind = FILTER_REFS_PSEUDOREFS;
+ if (filter->kind & FILTER_REFS_ROOT_REFS && kind == FILTER_REFS_DETACHED_HEAD)
+ kind = FILTER_REFS_ROOT_REFS;
else if (!(kind & filter->kind))
return NULL;
@@ -3086,7 +3088,7 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref
* When printing all ref types, HEAD is already included,
* so we don't want to print HEAD again.
*/
- if (!ret && (filter->kind != FILTER_REFS_KIND_MASK) &&
+ if (!ret && !(filter->kind & FILTER_REFS_ROOT_REFS) &&
(filter->kind & FILTER_REFS_DETACHED_HEAD))
refs_head_ref(get_main_ref_store(the_repository), fn,
cb_data);
diff --git a/ref-filter.h b/ref-filter.h
index 0ca28d2bba..27ae1aa0d1 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -23,9 +23,9 @@
FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)
#define FILTER_REFS_DETACHED_HEAD 0x0020
#define FILTER_REFS_PSEUDOREFS 0x0040
-#define FILTER_REFS_ROOT_REFS (FILTER_REFS_DETACHED_HEAD | FILTER_REFS_PSEUDOREFS)
+#define FILTER_REFS_ROOT_REFS 0x0080
#define FILTER_REFS_KIND_MASK (FILTER_REFS_REGULAR | FILTER_REFS_DETACHED_HEAD | \
- FILTER_REFS_PSEUDOREFS)
+ FILTER_REFS_PSEUDOREFS | FILTER_REFS_ROOT_REFS)
struct atom_value;
struct ref_sorting;
diff --git a/refs.c b/refs.c
index 32e91ff740..8260c27cde 100644
--- a/refs.c
+++ b/refs.c
@@ -819,7 +819,22 @@ int is_per_worktree_ref(const char *refname)
starts_with(refname, "refs/rewritten/");
}
-static int is_pseudoref_syntax(const char *refname)
+int is_pseudo_ref(const char *refname)
+{
+ static const char * const pseudo_refs[] = {
+ "FETCH_HEAD",
+ "MERGE_HEAD",
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)
+ if (!strcmp(refname, pseudo_refs[i]))
+ return 1;
+
+ return 0;
+}
+
+static int is_root_ref_syntax(const char *refname)
{
const char *c;
@@ -828,56 +843,37 @@ static int is_pseudoref_syntax(const char *refname)
return 0;
}
- /*
- * HEAD is not a pseudoref, but it certainly uses the
- * pseudoref syntax.
- */
return 1;
}
-int is_pseudoref(struct ref_store *refs, const char *refname)
+int is_root_ref(const char *refname)
{
- static const char *const irregular_pseudorefs[] = {
+ static const char *const irregular_root_refs[] = {
+ "HEAD",
"AUTO_MERGE",
"BISECT_EXPECTED_REV",
"NOTES_MERGE_PARTIAL",
"NOTES_MERGE_REF",
"MERGE_AUTOSTASH",
};
- struct object_id oid;
size_t i;
- if (!is_pseudoref_syntax(refname))
+ if (!is_root_ref_syntax(refname) ||
+ is_pseudo_ref(refname))
return 0;
- if (ends_with(refname, "_HEAD")) {
- refs_resolve_ref_unsafe(refs, refname,
- RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- &oid, NULL);
- return !is_null_oid(&oid);
- }
-
- for (i = 0; i < ARRAY_SIZE(irregular_pseudorefs); i++)
- if (!strcmp(refname, irregular_pseudorefs[i])) {
- refs_resolve_ref_unsafe(refs, refname,
- RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- &oid, NULL);
- return !is_null_oid(&oid);
- }
-
- return 0;
-}
+ if (ends_with(refname, "_HEAD"))
+ return 1;
-int is_headref(struct ref_store *refs, const char *refname)
-{
- if (!strcmp(refname, "HEAD"))
- return refs_ref_exists(refs, refname);
+ for (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)
+ if (!strcmp(refname, irregular_root_refs[i]))
+ return 1;
return 0;
}
static int is_current_worktree_ref(const char *ref) {
- return is_pseudoref_syntax(ref) || is_per_worktree_ref(ref);
+ return is_root_ref_syntax(ref) || is_per_worktree_ref(ref);
}
enum ref_worktree_type parse_worktree_ref(const char *maybe_worktree_ref,
@@ -1243,6 +1239,13 @@ int ref_transaction_update(struct ref_transaction *transaction,
return -1;
}
+ if (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&
+ is_pseudo_ref(refname)) {
+ strbuf_addf(err, _("refusing to update pseudoref '%s'"),
+ refname);
+ return -1;
+ }
+
if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)
BUG("illegal flags 0x%x passed to ref_transaction_update()", flags);
@@ -1816,43 +1819,12 @@ done:
return result;
}
-static int is_special_ref(const char *refname)
-{
- /*
- * Special references are refs that have different semantics compared
- * to "normal" refs. These refs can thus not be stored in the ref
- * backend, but must always be accessed via the filesystem. The
- * following refs are special:
- *
- * - FETCH_HEAD may contain multiple object IDs, and each one of them
- * carries additional metadata like where it came from.
- *
- * - MERGE_HEAD may contain multiple object IDs when merging multiple
- * heads.
- *
- * Reading, writing or deleting references must consistently go either
- * through the filesystem (special refs) or through the reference
- * backend (normal ones).
- */
- static const char * const special_refs[] = {
- "FETCH_HEAD",
- "MERGE_HEAD",
- };
- size_t i;
-
- for (i = 0; i < ARRAY_SIZE(special_refs); i++)
- if (!strcmp(refname, special_refs[i]))
- return 1;
-
- return 0;
-}
-
int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,
struct object_id *oid, struct strbuf *referent,
unsigned int *type, int *failure_errno)
{
assert(failure_errno);
- if (is_special_ref(refname))
+ if (is_pseudo_ref(refname))
return refs_read_special_head(ref_store, refname, oid, referent,
type, failure_errno);
diff --git a/refs.h b/refs.h
index fb419ab2ed..34568ee1fb 100644
--- a/refs.h
+++ b/refs.h
@@ -1013,8 +1013,52 @@ extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT];
*/
void update_ref_namespace(enum ref_namespace namespace, char *ref);
-int is_pseudoref(struct ref_store *refs, const char *refname);
-int is_headref(struct ref_store *refs, const char *refname);
+/*
+ * Check whether the provided name names a root reference. This function only
+ * performs a syntactic check.
+ *
+ * A root ref is a reference that lives in the root of the reference hierarchy.
+ * These references must conform to special syntax:
+ *
+ * - Their name must be all-uppercase or underscores ("_").
+ *
+ * - Their name must end with "_HEAD". As a special rule, "HEAD" is a root
+ * ref, as well.
+ *
+ * - Their name may not contain a slash.
+ *
+ * There is a special set of irregular root refs that exist due to historic
+ * reasons, only. This list shall not be expanded in the future:
+ *
+ * - AUTO_MERGE
+ *
+ * - BISECT_EXPECTED_REV
+ *
+ * - NOTES_MERGE_PARTIAL
+ *
+ * - NOTES_MERGE_REF
+ *
+ * - MERGE_AUTOSTASH
+ */
+int is_root_ref(const char *refname);
+
+/*
+ * Pseudorefs are refs that have different semantics compared to
+ * "normal" refs. These refs can thus not be stored in the ref backend,
+ * but must always be accessed via the filesystem. The following refs
+ * are pseudorefs:
+ *
+ * - FETCH_HEAD may contain multiple object IDs, and each one of them
+ * carries additional metadata like where it came from.
+ *
+ * - MERGE_HEAD may contain multiple object IDs when merging multiple
+ * heads.
+ *
+ * Reading, writing or deleting references must consistently go either
+ * through the filesystem (pseudorefs) or through the reference
+ * backend (normal ones).
+ */
+int is_pseudo_ref(const char *refname);
/*
* The following functions have been removed in Git v2.45 in favor of functions
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 3957bfa579..5f3089d947 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -351,8 +351,7 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store,
strbuf_addstr(&refname, de->d_name);
dtype = get_dtype(de, &path, 1);
- if (dtype == DT_REG && (is_pseudoref(ref_store, de->d_name) ||
- is_headref(ref_store, de->d_name)))
+ if (dtype == DT_REG && is_root_ref(de->d_name))
loose_fill_ref_dir_regular_file(refs, refname.buf, dir);
strbuf_setlen(&refname, dirnamelen);
@@ -794,8 +793,10 @@ retry:
*/
if (refs_verify_refname_available(
refs->packed_ref_store, refname,
- extras, NULL, err))
+ extras, NULL, err)) {
+ ret = TRANSACTION_NAME_CONFLICT;
goto error_return;
+ }
}
ret = 0;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 98cebbcf39..1af86bbdec 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -354,8 +354,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
*/
if (!starts_with(iter->ref.refname, "refs/") &&
!(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&
- (is_pseudoref(&iter->refs->base, iter->ref.refname) ||
- is_headref(&iter->refs->base, iter->ref.refname)))) {
+ is_root_ref(iter->ref.refname))) {
continue;
}
diff --git a/sequencer.c b/sequencer.c
index 3c81ef9ca5..30513e87bf 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -359,35 +359,32 @@ static const char *get_todo_path(const struct replay_opts *opts)
static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
size_t ignore_footer)
{
- struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
- struct trailer_info info;
- size_t i;
+ struct trailer_iterator iter;
+ size_t i = 0;
int found_sob = 0, found_sob_last = 0;
char saved_char;
- opts.no_divider = 1;
-
if (ignore_footer) {
saved_char = sb->buf[sb->len - ignore_footer];
sb->buf[sb->len - ignore_footer] = '\0';
}
- trailer_info_get(&opts, sb->buf, &info);
+ trailer_iterator_init(&iter, sb->buf);
if (ignore_footer)
sb->buf[sb->len - ignore_footer] = saved_char;
- if (info.trailer_block_start == info.trailer_block_end)
- return 0;
+ while (trailer_iterator_advance(&iter)) {
+ i++;
+ if (sob && !strncmp(iter.raw, sob->buf, sob->len))
+ found_sob = i;
+ }
+ trailer_iterator_release(&iter);
- for (i = 0; i < info.trailer_nr; i++)
- if (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {
- found_sob = 1;
- if (i == info.trailer_nr - 1)
- found_sob_last = 1;
- }
+ if (!i)
+ return 0;
- trailer_info_release(&info);
+ found_sob_last = (int)i == found_sob;
if (found_sob_last)
return 3;
diff --git a/t/t0017-env-helper.sh b/t/t0017-env-helper.sh
index fc14ba091c..f3a16859cc 100755
--- a/t/t0017-env-helper.sh
+++ b/t/t0017-env-helper.sh
@@ -91,9 +91,16 @@ test_expect_success 'test-tool env-helper reads config thanks to trace2' '
git config -l 2>err &&
grep "exceeded maximum include depth" err &&
+ # This validates that the assumption that we attempt to
+ # read the configuration and fail very early in the start-up
+ # sequence (due to trace2 subsystem), even before we notice
+ # that the directory named with "test-tool -C" does not exist
+ # and die. It is a dubious thing to test, though.
test_must_fail \
env HOME="$(pwd)/home" GIT_TEST_ENV_HELPER=true \
- test-tool -C cycle env-helper --type=bool --default=0 --exit-code GIT_TEST_ENV_HELPER 2>err &&
+ test-tool -C no-such-directory \
+ env-helper --type=bool --default=0 \
+ --exit-code GIT_TEST_ENV_HELPER 2>err &&
grep "exceeded maximum include depth" err
'
diff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh
index 13ef69b92f..070fe7a5da 100755
--- a/t/t0211-trace2-perf.sh
+++ b/t/t0211-trace2-perf.sh
@@ -233,7 +233,7 @@ have_counter_event () {
pattern="d0|${thread}|${event}||||${category}|name:${name} value:${value}" &&
- grep "${patern}" ${file}
+ grep "${pattern}" ${file}
}
test_expect_success 'global counter test/test1' '
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh
index a390cffc80..92f570313d 100755
--- a/t/t0600-reffiles-backend.sh
+++ b/t/t0600-reffiles-backend.sh
@@ -424,7 +424,7 @@ test_expect_success SYMLINKS 'git branch -m with symlinked .git/refs' '
test_when_finished "rm -rf subdir" &&
git init --bare subdir &&
- rm -rfv subdir/refs subdir/objects subdir/packed-refs &&
+ rm -rf subdir/refs subdir/objects subdir/packed-refs &&
ln -s ../.git/refs subdir/refs &&
ln -s ../.git/objects subdir/objects &&
ln -s ../.git/packed-refs subdir/packed-refs &&
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index f3c4d28e06..9de2d95f06 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -596,7 +596,8 @@ test_expect_success 'get bool variable with empty value' '
test_expect_success 'no arguments, but no crash' '
test_must_fail git config >output 2>&1 &&
- test_grep usage output
+ echo "error: no action specified" >expect &&
+ test_cmp expect output
'
cat > .git/config << EOF
@@ -2834,6 +2835,12 @@ test_expect_success 'specifying multiple modes causes failure' '
test_cmp expect err
'
+test_expect_success 'writing to stdin is rejected' '
+ echo "fatal: writing to stdin is not supported" >expect &&
+ test_must_fail git config ${mode_set} --file - foo.bar baz 2>err &&
+ test_cmp expect err
+'
+
done
test_done
diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh
index 98e9158bd2..67ebd81a4c 100755
--- a/t/t1404-update-ref-errors.sh
+++ b/t/t1404-update-ref-errors.sh
@@ -100,7 +100,7 @@ df_test() {
printf "%s\n" "delete $delname" "create $addname $D"
fi >commands &&
test_must_fail git update-ref --stdin <commands 2>output.err &&
- grep "fatal:\( cannot lock ref $SQ$addname$SQ:\)\? $SQ$delref$SQ exists; cannot create $SQ$addref$SQ" output.err &&
+ grep -E "fatal:( cannot lock ref $SQ$addname$SQ:)? $SQ$delref$SQ exists; cannot create $SQ$addref$SQ" output.err &&
printf "%s\n" "$C $delref" >expected-refs &&
git for-each-ref --format="%(objectname) %(refname)" $prefix/r >actual-refs &&
test_cmp expected-refs actual-refs
diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh
index a7b7263b35..ac4a5b2734 100755
--- a/t/t1700-split-index.sh
+++ b/t/t1700-split-index.sh
@@ -527,7 +527,7 @@ test_expect_success 'reading split index at alternate location' '
# ... and, for backwards compatibility, in the current GIT_DIR
# as well.
- mv -v ./reading-alternate-location/.git/sharedindex.* .git &&
+ mv ./reading-alternate-location/.git/sharedindex.* .git &&
GIT_INDEX_FILE=./reading-alternate-location/.git/index \
git ls-files --cached >actual &&
test_cmp expect actual
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 60fe60d761..86c695eb0a 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -2022,7 +2022,7 @@ test_expect_success GPGSM 'log --graph --show-signature x509' '
test_expect_success GPGSSH 'log --graph --show-signature ssh' '
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
git log --graph --show-signature -n1 signed-ssh >actual &&
- grep "${GOOD_SIGNATURE_TRUSTED}" actual
+ grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual
'
test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on expired signature key' '
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 9441793d06..3b3991ab86 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -518,7 +518,7 @@ test_expect_success 'fetch with a non-applying branch.<name>.merge' '
test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [1]' '
one_head=$(cd one && git rev-parse HEAD) &&
this_head=$(git rev-parse HEAD) &&
- git update-ref -d FETCH_HEAD &&
+ rm .git/FETCH_HEAD &&
git fetch one &&
test $one_head = "$(git rev-parse --verify FETCH_HEAD)" &&
test $this_head = "$(git rev-parse --verify HEAD)"
@@ -530,7 +530,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge
one_ref=$(cd one && git symbolic-ref HEAD) &&
git config branch.main.remote blub &&
git config branch.main.merge "$one_ref" &&
- git update-ref -d FETCH_HEAD &&
+ rm .git/FETCH_HEAD &&
git fetch one &&
test $one_head = "$(git rev-parse --verify FETCH_HEAD)" &&
test $this_head = "$(git rev-parse --verify HEAD)"
@@ -540,7 +540,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge
# the merge spec does not match the branch the remote HEAD points to
test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [3]' '
git config branch.main.merge "${one_ref}_not" &&
- git update-ref -d FETCH_HEAD &&
+ rm .git/FETCH_HEAD &&
git fetch one &&
test $one_head = "$(git rev-parse --verify FETCH_HEAD)" &&
test $this_head = "$(git rev-parse --verify HEAD)"
@@ -1091,6 +1091,22 @@ test_expect_success 'branchname D/F conflict resolved by --prune' '
test_cmp expect actual
'
+test_expect_success 'branchname D/F conflict rejected with targeted error message' '
+ git clone . df-conflict-error &&
+ git branch dir_conflict &&
+ (
+ cd df-conflict-error &&
+ git update-ref refs/remotes/origin/dir_conflict/file HEAD &&
+ test_must_fail git fetch 2>err &&
+ test_grep "error: some local refs could not be updated; try running" err &&
+ test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err &&
+ git pack-refs --all &&
+ test_must_fail git fetch 2>err-packed &&
+ test_grep "error: some local refs could not be updated; try running" err-packed &&
+ test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err-packed
+ )
+'
+
test_expect_success 'fetching a one-level ref works' '
test_commit extra &&
git reset --hard HEAD^ &&
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
index 948f1bb5f4..163c378cfd 100755
--- a/t/t6302-for-each-ref-filter.sh
+++ b/t/t6302-for-each-ref-filter.sh
@@ -52,6 +52,23 @@ test_expect_success '--include-root-refs pattern prints pseudorefs' '
test_cmp expect actual
'
+test_expect_success '--include-root-refs pattern does not print special refs' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit initial &&
+ git rev-parse HEAD >.git/MERGE_HEAD &&
+ git for-each-ref --format="%(refname)" --include-root-refs >actual &&
+ cat >expect <<-EOF &&
+ HEAD
+ $(git symbolic-ref HEAD)
+ refs/tags/initial
+ EOF
+ test_cmp expect actual
+ )
+'
+
test_expect_success '--include-root-refs with other patterns' '
cat >expect <<-\EOF &&
HEAD
@@ -62,6 +79,23 @@ test_expect_success '--include-root-refs with other patterns' '
test_cmp expect actual
'
+test_expect_success '--include-root-refs omits dangling symrefs' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit initial &&
+ git symbolic-ref DANGLING_HEAD refs/heads/missing &&
+ cat >expect <<-EOF &&
+ HEAD
+ $(git symbolic-ref HEAD)
+ refs/tags/initial
+ EOF
+ git for-each-ref --format="%(refname)" --include-root-refs >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'filtering with --points-at' '
cat >expect <<-\EOF &&
refs/heads/main
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 5a771000c9..58699f8e4e 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -2526,7 +2526,7 @@ test_expect_success $PREREQ 'test forbidSendmailVariables behavior override' '
test_expect_success $PREREQ '--compose handles lowercase headers' '
write_script fake-editor <<-\EOF &&
- sed "s/^From:.*/from: edited-from@example.com/i" "$1" >"$1.tmp" &&
+ sed "s/^[Ff][Rr][Oo][Mm]:.*/from: edited-from@example.com/" "$1" >"$1.tmp" &&
mv "$1.tmp" "$1"
EOF
clean_fake_sendmail &&
diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh
index d3261e35b8..a34fd46ecc 100755
--- a/t/t9118-git-svn-funky-branch-names.sh
+++ b/t/t9118-git-svn-funky-branch-names.sh
@@ -38,7 +38,7 @@ test_expect_success 'setup svnrepo' '
# SVN 1.7 will truncate "not-a%40{0]" to just "not-a".
# Look at what SVN wound up naming the branch and use that.
# Be sure to escape the @ if it shows up.
-non_reflog=$(svn_cmd ls "$svnrepo/pr ject/branches" | sed -ne '/not-a/ { s/\///; s/@/%40/; p }')
+non_reflog=$(svn_cmd ls "$svnrepo/pr ject/branches" | sed -ne '/not-a/ { s/\///; s/@/%40/; p; }')
test_expect_success 'test clone with funky branch names' '
git svn clone -s "$svnrepo/pr ject" project &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 963f865f27..932d5ad759 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -73,7 +73,7 @@ _get_comp_words_by_ref ()
print_comp ()
{
local IFS=$'\n'
- echo "${COMPREPLY[*]}" > out
+ printf '%s\n' "${COMPREPLY[*]}" > out
}
run_completion ()
@@ -2742,30 +2742,58 @@ do
'
done
-test_expect_success 'git config - section' '
- test_completion "git config br" <<-\EOF
+test_expect_success 'git config subcommand' '
+ test_completion "git config " <<-\EOF
+ edit Z
+ get Z
+ list Z
+ remove-section Z
+ rename-section Z
+ set Z
+ unset Z
+ EOF
+'
+
+test_expect_success 'git config subcommand options' '
+ test_completion "git config get --show-" <<-\EOF
+ --show-names Z
+ --show-origin Z
+ --show-scope Z
+ EOF
+'
+
+test_expect_success 'git config get' '
+ test_when_finished "rm -f cfgfile" &&
+ git config set --file cfgfile foo.bar baz &&
+ test_completion "git config get --file cfgfile foo." <<-\EOF
+ foo.bar Z
+ EOF
+'
+
+test_expect_success 'git config set - section' '
+ test_completion "git config set br" <<-\EOF
branch.Z
browser.Z
EOF
'
-test_expect_success 'git config - section include, includeIf' '
- test_completion "git config inclu" <<-\EOF
+test_expect_success 'git config set - section include, includeIf' '
+ test_completion "git config set inclu" <<-\EOF
include.Z
includeIf.Z
EOF
'
-test_expect_success 'git config - variable name' '
- test_completion "git config log.d" <<-\EOF
+test_expect_success 'git config set - variable name' '
+ test_completion "git config set log.d" <<-\EOF
log.date Z
log.decorate Z
log.diffMerges Z
EOF
'
-test_expect_success 'git config - variable name include' '
- test_completion "git config include.p" <<-\EOF
+test_expect_success 'git config set - variable name include' '
+ test_completion "git config set include.p" <<-\EOF
include.path Z
EOF
'
@@ -2776,8 +2804,8 @@ test_expect_success 'setup for git config submodule tests' '
git submodule add ./sub
'
-test_expect_success 'git config - variable name - submodule and __git_compute_first_level_config_vars_for_section' '
- test_completion "git config submodule." <<-\EOF
+test_expect_success 'git config set - variable name - submodule and __git_compute_first_level_config_vars_for_section' '
+ test_completion "git config set submodule." <<-\EOF
submodule.active Z
submodule.alternateErrorStrategy Z
submodule.alternateLocation Z
@@ -2788,8 +2816,8 @@ test_expect_success 'git config - variable name - submodule and __git_compute_fi
EOF
'
-test_expect_success 'git config - variable name - __git_compute_second_level_config_vars_for_section' '
- test_completion "git config submodule.sub." <<-\EOF
+test_expect_success 'git config set - variable name - __git_compute_second_level_config_vars_for_section' '
+ test_completion "git config set submodule.sub." <<-\EOF
submodule.sub.url Z
submodule.sub.update Z
submodule.sub.branch Z
@@ -2799,8 +2827,8 @@ test_expect_success 'git config - variable name - __git_compute_second_level_con
EOF
'
-test_expect_success 'git config - value' '
- test_completion "git config color.pager " <<-\EOF
+test_expect_success 'git config set - value' '
+ test_completion "git config set color.pager " <<-\EOF
false Z
true Z
EOF
diff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c
new file mode 100644
index 0000000000..2ecca359d9
--- /dev/null
+++ b/t/unit-tests/t-trailer.c
@@ -0,0 +1,315 @@
+#include "test-lib.h"
+#include "trailer.h"
+
+struct contents {
+ const char *raw;
+ const char *key;
+ const char *val;
+};
+
+static void t_trailer_iterator(const char *msg, size_t num_expected,
+ struct contents *contents)
+{
+ struct trailer_iterator iter;
+ size_t i = 0;
+
+ trailer_iterator_init(&iter, msg);
+ while (trailer_iterator_advance(&iter)) {
+ if (num_expected) {
+ check_str(iter.raw, contents[i].raw);
+ check_str(iter.key.buf, contents[i].key);
+ check_str(iter.val.buf, contents[i].val);
+ }
+ i++;
+ }
+ trailer_iterator_release(&iter);
+
+ check_uint(i, ==, num_expected);
+}
+
+static void run_t_trailer_iterator(void)
+{
+
+ static struct test_cases {
+ const char *name;
+ const char *msg;
+ size_t num_expected;
+ struct contents contents[10];
+ } tc[] = {
+ {
+ "empty input",
+ "",
+ 0,
+ {{0}},
+ },
+ {
+ "no newline at beginning",
+ "Fixes: x\n"
+ "Acked-by: x\n"
+ "Reviewed-by: x\n",
+ 0,
+ {{0}},
+ },
+ {
+ "newline at beginning",
+ "\n"
+ "Fixes: x\n"
+ "Acked-by: x\n"
+ "Reviewed-by: x\n",
+ 3,
+ {
+ {
+ .raw = "Fixes: x\n",
+ .key = "Fixes",
+ .val = "x",
+ },
+ {
+ .raw = "Acked-by: x\n",
+ .key = "Acked-by",
+ .val = "x",
+ },
+ {
+ .raw = "Reviewed-by: x\n",
+ .key = "Reviewed-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "without body text",
+ "subject: foo bar\n"
+ "\n"
+ "Fixes: x\n"
+ "Acked-by: x\n"
+ "Reviewed-by: x\n",
+ 3,
+ {
+ {
+ .raw = "Fixes: x\n",
+ .key = "Fixes",
+ .val = "x",
+ },
+ {
+ .raw = "Acked-by: x\n",
+ .key = "Acked-by",
+ .val = "x",
+ },
+ {
+ .raw = "Reviewed-by: x\n",
+ .key = "Reviewed-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "with body text, without divider",
+ "my subject\n"
+ "\n"
+ "my body which is long\n"
+ "and contains some special\n"
+ "chars like : = ? !\n"
+ "hello\n"
+ "\n"
+ "Fixes: x\n"
+ "Acked-by: x\n"
+ "Reviewed-by: x\n"
+ "Signed-off-by: x\n",
+ 4,
+ {
+ {
+ .raw = "Fixes: x\n",
+ .key = "Fixes",
+ .val = "x",
+ },
+ {
+ .raw = "Acked-by: x\n",
+ .key = "Acked-by",
+ .val = "x",
+ },
+ {
+ .raw = "Reviewed-by: x\n",
+ .key = "Reviewed-by",
+ .val = "x",
+ },
+ {
+ .raw = "Signed-off-by: x\n",
+ .key = "Signed-off-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "with body text, without divider (second trailer block)",
+ "my subject\n"
+ "\n"
+ "my body which is long\n"
+ "and contains some special\n"
+ "chars like : = ? !\n"
+ "hello\n"
+ "\n"
+ "Fixes: x\n"
+ "Acked-by: x\n"
+ "Reviewed-by: x\n"
+ "Signed-off-by: x\n"
+ "\n"
+ /*
+ * Because this is the last trailer block, it takes
+ * precedence over the first one encountered above.
+ */
+ "Helped-by: x\n"
+ "Signed-off-by: x\n",
+ 2,
+ {
+ {
+ .raw = "Helped-by: x\n",
+ .key = "Helped-by",
+ .val = "x",
+ },
+ {
+ .raw = "Signed-off-by: x\n",
+ .key = "Signed-off-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "with body text, with divider",
+ "my subject\n"
+ "\n"
+ "my body which is long\n"
+ "and contains some special\n"
+ "chars like : = ? !\n"
+ "hello\n"
+ "\n"
+ "---\n"
+ "\n"
+ /*
+ * This trailer still counts because the iterator
+ * always ignores the divider.
+ */
+ "Signed-off-by: x\n",
+ 1,
+ {
+ {
+ .raw = "Signed-off-by: x\n",
+ .key = "Signed-off-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "with non-trailer lines in trailer block",
+ "subject: foo bar\n"
+ "\n"
+ /*
+ * Even though this trailer block has a non-trailer line
+ * in it, it's still a valid trailer block because it's
+ * at least 25% trailers and is Git-generated (see
+ * git_generated_prefixes[] in trailer.c).
+ */
+ "not a trailer line\n"
+ "not a trailer line\n"
+ "not a trailer line\n"
+ "Signed-off-by: x\n",
+ /*
+ * Even though there is only really 1 real "trailer"
+ * (Signed-off-by), we still have 4 trailer objects
+ * because we still want to iterate through the entire
+ * block.
+ */
+ 4,
+ {
+ {
+ .raw = "not a trailer line\n",
+ .key = "not a trailer line",
+ .val = "",
+ },
+ {
+ .raw = "not a trailer line\n",
+ .key = "not a trailer line",
+ .val = "",
+ },
+ {
+ .raw = "not a trailer line\n",
+ .key = "not a trailer line",
+ .val = "",
+ },
+ {
+ .raw = "Signed-off-by: x\n",
+ .key = "Signed-off-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "with non-trailer lines (one too many) in trailer block",
+ "subject: foo bar\n"
+ "\n"
+ /*
+ * This block has only 20% trailers, so it's below the
+ * 25% threshold.
+ */
+ "not a trailer line\n"
+ "not a trailer line\n"
+ "not a trailer line\n"
+ "not a trailer line\n"
+ "Signed-off-by: x\n",
+ 0,
+ {{0}},
+ },
+ {
+ "with non-trailer lines (only 1) in trailer block, but no Git-generated trailers",
+ "subject: foo bar\n"
+ "\n"
+ /*
+ * This block has only 1 non-trailer out of 10 (IOW, 90%
+ * trailers) but is not considered a trailer block
+ * because the 25% threshold only applies to cases where
+ * there was a Git-generated trailer.
+ */
+ "Reviewed-by: x\n"
+ "Reviewed-by: x\n"
+ "Reviewed-by: x\n"
+ "Helped-by: x\n"
+ "Helped-by: x\n"
+ "Helped-by: x\n"
+ "Acked-by: x\n"
+ "Acked-by: x\n"
+ "Acked-by: x\n"
+ "not a trailer line\n",
+ 0,
+ {{0}},
+ },
+ };
+
+ for (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {
+ TEST(t_trailer_iterator(tc[i].msg,
+ tc[i].num_expected,
+ tc[i].contents),
+ "%s", tc[i].name);
+ }
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ run_t_trailer_iterator();
+ return test_done();
+}
diff --git a/trailer.c b/trailer.c
index 724cb78be5..2bcb9ba8f7 100644
--- a/trailer.c
+++ b/trailer.c
@@ -11,6 +11,27 @@
* Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
*/
+struct trailer_info {
+ /*
+ * True if there is a blank line before the location pointed to by
+ * trailer_block_start.
+ */
+ int blank_line_before_trailer;
+
+ /*
+ * Offsets to the trailer block start and end positions in the input
+ * string. If no trailer block is found, these are both set to the
+ * "true" end of the input (find_end_of_log_message()).
+ */
+ size_t trailer_block_start, trailer_block_end;
+
+ /*
+ * Array of trailers found.
+ */
+ char **trailers;
+ size_t trailer_nr;
+};
+
struct conf_info {
char *name;
char *key;
@@ -952,20 +973,72 @@ static void unfold_value(struct strbuf *val)
strbuf_release(&out);
}
+static struct trailer_info *trailer_info_new(void)
+{
+ struct trailer_info *info = xcalloc(1, sizeof(*info));
+ return info;
+}
+
+static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,
+ const char *str)
+{
+ struct trailer_info *info = trailer_info_new();
+ size_t end_of_log_message = 0, trailer_block_start = 0;
+ struct strbuf **trailer_lines, **ptr;
+ char **trailer_strings = NULL;
+ size_t nr = 0, alloc = 0;
+ char **last = NULL;
+
+ trailer_config_init();
+
+ end_of_log_message = find_end_of_log_message(str, opts->no_divider);
+ trailer_block_start = find_trailer_block_start(str, end_of_log_message);
+
+ trailer_lines = strbuf_split_buf(str + trailer_block_start,
+ end_of_log_message - trailer_block_start,
+ '\n',
+ 0);
+ for (ptr = trailer_lines; *ptr; ptr++) {
+ if (last && isspace((*ptr)->buf[0])) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_attach(&sb, *last, strlen(*last), strlen(*last));
+ strbuf_addbuf(&sb, *ptr);
+ *last = strbuf_detach(&sb, NULL);
+ continue;
+ }
+ ALLOC_GROW(trailer_strings, nr + 1, alloc);
+ trailer_strings[nr] = strbuf_detach(*ptr, NULL);
+ last = find_separator(trailer_strings[nr], separators) >= 1
+ ? &trailer_strings[nr]
+ : NULL;
+ nr++;
+ }
+ strbuf_list_free(trailer_lines);
+
+ info->blank_line_before_trailer = ends_with_blank_line(str,
+ trailer_block_start);
+ info->trailer_block_start = trailer_block_start;
+ info->trailer_block_end = end_of_log_message;
+ info->trailers = trailer_strings;
+ info->trailer_nr = nr;
+
+ return info;
+}
+
/*
- * Parse trailers in "str", populating the trailer info and "head"
+ * Parse trailers in "str", populating the trailer info and "trailer_objects"
* linked list structure.
*/
-void parse_trailers(const struct process_trailer_options *opts,
- struct trailer_info *info,
- const char *str,
- struct list_head *head)
+struct trailer_info *parse_trailers(const struct process_trailer_options *opts,
+ const char *str,
+ struct list_head *trailer_objects)
{
+ struct trailer_info *info;
struct strbuf tok = STRBUF_INIT;
struct strbuf val = STRBUF_INIT;
size_t i;
- trailer_info_get(opts, str, info);
+ info = trailer_info_get(opts, str);
for (i = 0; i < info->trailer_nr; i++) {
int separator_pos;
@@ -978,17 +1051,19 @@ void parse_trailers(const struct process_trailer_options *opts,
separator_pos);
if (opts->unfold)
unfold_value(&val);
- add_trailer_item(head,
+ add_trailer_item(trailer_objects,
strbuf_detach(&tok, NULL),
strbuf_detach(&val, NULL));
} else if (!opts->only_trailers) {
strbuf_addstr(&val, trailer);
strbuf_strip_suffix(&val, "\n");
- add_trailer_item(head,
+ add_trailer_item(trailer_objects,
NULL,
strbuf_detach(&val, NULL));
}
}
+
+ return info;
}
void free_trailers(struct list_head *trailers)
@@ -1000,48 +1075,19 @@ void free_trailers(struct list_head *trailers)
}
}
-void trailer_info_get(const struct process_trailer_options *opts,
- const char *str,
- struct trailer_info *info)
+size_t trailer_block_start(struct trailer_info *info)
{
- size_t end_of_log_message = 0, trailer_block_start = 0;
- struct strbuf **trailer_lines, **ptr;
- char **trailer_strings = NULL;
- size_t nr = 0, alloc = 0;
- char **last = NULL;
-
- trailer_config_init();
-
- end_of_log_message = find_end_of_log_message(str, opts->no_divider);
- trailer_block_start = find_trailer_block_start(str, end_of_log_message);
+ return info->trailer_block_start;
+}
- trailer_lines = strbuf_split_buf(str + trailer_block_start,
- end_of_log_message - trailer_block_start,
- '\n',
- 0);
- for (ptr = trailer_lines; *ptr; ptr++) {
- if (last && isspace((*ptr)->buf[0])) {
- struct strbuf sb = STRBUF_INIT;
- strbuf_attach(&sb, *last, strlen(*last), strlen(*last));
- strbuf_addbuf(&sb, *ptr);
- *last = strbuf_detach(&sb, NULL);
- continue;
- }
- ALLOC_GROW(trailer_strings, nr + 1, alloc);
- trailer_strings[nr] = strbuf_detach(*ptr, NULL);
- last = find_separator(trailer_strings[nr], separators) >= 1
- ? &trailer_strings[nr]
- : NULL;
- nr++;
- }
- strbuf_list_free(trailer_lines);
+size_t trailer_block_end(struct trailer_info *info)
+{
+ return info->trailer_block_end;
+}
- info->blank_line_before_trailer = ends_with_blank_line(str,
- trailer_block_start);
- info->trailer_block_start = trailer_block_start;
- info->trailer_block_end = end_of_log_message;
- info->trailers = trailer_strings;
- info->trailer_nr = nr;
+int blank_line_before_trailer_block(struct trailer_info *info)
+{
+ return info->blank_line_before_trailer;
}
void trailer_info_release(struct trailer_info *info)
@@ -1050,6 +1096,7 @@ void trailer_info_release(struct trailer_info *info)
for (i = 0; i < info->trailer_nr; i++)
free(info->trailers[i]);
free(info->trailers);
+ free(info);
}
void format_trailers(const struct process_trailer_options *opts,
@@ -1117,21 +1164,19 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,
struct strbuf *out)
{
LIST_HEAD(trailer_objects);
- struct trailer_info info;
-
- parse_trailers(opts, &info, msg, &trailer_objects);
+ struct trailer_info *info = parse_trailers(opts, msg, &trailer_objects);
/* If we want the whole block untouched, we can take the fast path. */
if (!opts->only_trailers && !opts->unfold && !opts->filter &&
!opts->separator && !opts->key_only && !opts->value_only &&
!opts->key_value_separator) {
- strbuf_add(out, msg + info.trailer_block_start,
- info.trailer_block_end - info.trailer_block_start);
+ strbuf_add(out, msg + info->trailer_block_start,
+ info->trailer_block_end - info->trailer_block_start);
} else
format_trailers(opts, &trailer_objects, out);
free_trailers(&trailer_objects);
- trailer_info_release(&info);
+ trailer_info_release(info);
}
void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)
@@ -1140,23 +1185,21 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)
strbuf_init(&iter->key, 0);
strbuf_init(&iter->val, 0);
opts.no_divider = 1;
- trailer_info_get(&opts, msg, &iter->internal.info);
+ iter->internal.info = trailer_info_get(&opts, msg);
iter->internal.cur = 0;
}
int trailer_iterator_advance(struct trailer_iterator *iter)
{
- while (iter->internal.cur < iter->internal.info.trailer_nr) {
- char *trailer = iter->internal.info.trailers[iter->internal.cur++];
- int separator_pos = find_separator(trailer, separators);
-
- if (separator_pos < 1)
- continue; /* not a real trailer */
+ if (iter->internal.cur < iter->internal.info->trailer_nr) {
+ char *line = iter->internal.info->trailers[iter->internal.cur++];
+ int separator_pos = find_separator(line, separators);
+ iter->raw = line;
strbuf_reset(&iter->key);
strbuf_reset(&iter->val);
parse_trailer(&iter->key, &iter->val, NULL,
- trailer, separator_pos);
+ line, separator_pos);
/* Always unfold values during iteration. */
unfold_value(&iter->val);
return 1;
@@ -1166,7 +1209,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)
void trailer_iterator_release(struct trailer_iterator *iter)
{
- trailer_info_release(&iter->internal.info);
+ trailer_info_release(iter->internal.info);
strbuf_release(&iter->val);
strbuf_release(&iter->key);
}
diff --git a/trailer.h b/trailer.h
index c364405267..6eb53df155 100644
--- a/trailer.h
+++ b/trailer.h
@@ -4,6 +4,7 @@
#include "list.h"
#include "strbuf.h"
+struct trailer_info;
struct strvec;
enum trailer_where {
@@ -31,27 +32,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);
int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);
int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);
-struct trailer_info {
- /*
- * True if there is a blank line before the location pointed to by
- * trailer_block_start.
- */
- int blank_line_before_trailer;
-
- /*
- * Offsets to the trailer block start and end positions in the input
- * string. If no trailer block is found, these are both set to the
- * "true" end of the input (find_end_of_log_message()).
- */
- size_t trailer_block_start, trailer_block_end;
-
- /*
- * Array of trailers found.
- */
- char **trailers;
- size_t trailer_nr;
-};
-
/*
* A list that represents newly-added trailers, such as those provided
* with the --trailer command line option of git-interpret-trailers.
@@ -91,15 +71,63 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,
void process_trailers_lists(struct list_head *head,
struct list_head *arg_head);
-void parse_trailers(const struct process_trailer_options *,
- struct trailer_info *,
- const char *str,
- struct list_head *head);
+/*
+ * Given some input string "str", return a pointer to an opaque trailer_info
+ * structure. Also populate the trailer_objects list with parsed trailer
+ * objects. Internally this calls trailer_info_get() to get the opaque pointer,
+ * but does some extra work to populate the trailer_objects linked list.
+ *
+ * The opaque trailer_info pointer can be used to check the position of the
+ * trailer block as offsets relative to the beginning of "str" in
+ * trailer_block_start() and trailer_block_end().
+ * blank_line_before_trailer_block() returns 1 if there is a blank line just
+ * before the trailer block. All of these functions are useful for preserving
+ * the input before and after the trailer block, if we were to write out the
+ * original input (but with the trailer block itself modified); see
+ * builtin/interpret-trailers.c for an example.
+ *
+ * For iterating through the parsed trailer block (if you don't care about the
+ * position of the trailer block itself in the context of the larger string text
+ * from which it was parsed), please see trailer_iterator_init() which uses the
+ * trailer_info struct internally.
+ *
+ * Lastly, callers should call trailer_info_release() when they are done using
+ * the opaque pointer.
+ *
+ * NOTE: Callers should treat both trailer_info and trailer_objects as
+ * read-only items, because there is some overlap between the two (trailer_info
+ * has "char **trailers" string array, and trailer_objects will have the same
+ * data but as a linked list of trailer_item objects). This API does not perform
+ * any synchronization between the two. In the future we should be able to
+ * reduce the duplication and use just the linked list.
+ */
+struct trailer_info *parse_trailers(const struct process_trailer_options *,
+ const char *str,
+ struct list_head *trailer_objects);
+
+/*
+ * Return the offset of the start of the trailer block. That is, 0 is the start
+ * of the input ("str" in parse_trailers()) and some other positive number
+ * indicates how many bytes we have to skip over before we get to the beginning
+ * of the trailer block.
+ */
+size_t trailer_block_start(struct trailer_info *);
+
+/*
+ * Return the end of the trailer block, again relative to the start of the
+ * input.
+ */
+size_t trailer_block_end(struct trailer_info *);
-void trailer_info_get(const struct process_trailer_options *,
- const char *str,
- struct trailer_info *);
+/*
+ * Return 1 if the trailer block had an extra newline (blank line) just before
+ * it.
+ */
+int blank_line_before_trailer_block(struct trailer_info *);
+/*
+ * Free trailer_info struct.
+ */
void trailer_info_release(struct trailer_info *info);
void trailer_config_init(void);
@@ -127,12 +155,19 @@ void format_trailers_from_commit(const struct process_trailer_options *,
* trailer_iterator_release(&iter);
*/
struct trailer_iterator {
+ /*
+ * Raw line (e.g., "foo: bar baz") before being parsed as a trailer
+ * key/val pair as part of a trailer block (as the "key" and "val"
+ * fields below). If a line fails to parse as a trailer, then the "key"
+ * will be the entire line and "val" will be the empty string.
+ */
+ const char *raw;
struct strbuf key;
struct strbuf val;
/* private */
struct {
- struct trailer_info info;
+ struct trailer_info *info;
size_t cur;
} internal;
};