mirror of
https://github.com/git/git.git
synced 2026-05-25 11:25:06 +02:00
Merge branch 'master' of https://github.com/git/git
Upstream adds 18 new translatable messages. * 'master' of https://github.com/git/git: (41 commits) A bit more on top of 2.54-rc0 ... Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
This commit is contained in:
@@ -109,6 +109,16 @@ UI, Workflows & Features
|
||||
* "git repo info -h" and "git repo structure -h" limit their help output
|
||||
to the part that is specific to the subcommand.
|
||||
|
||||
* "git format-patch --cover-letter" learns to use a simpler format
|
||||
instead of the traditional shortlog format to list its commits with
|
||||
a new --commit-list-format option and format.commitListFormat
|
||||
configuration variable.
|
||||
|
||||
* `git backfill` learned to accept revision and pathspec arguments.
|
||||
|
||||
* "git replay" (experimental) learns, in addition to "pick" and
|
||||
"replay", a new operating mode "revert".
|
||||
|
||||
|
||||
Performance, Internal Implementation, Development Support etc.
|
||||
--------------------------------------------------------------
|
||||
@@ -495,3 +505,4 @@ Fixes since v2.53
|
||||
(merge fc8a4f15e7 gi/doc-boolean-config-typofix later to maint).
|
||||
(merge 37182267a0 kh/doc-interpret-trailers-1 later to maint).
|
||||
(merge f64c50e768 jc/rerere-modern-strbuf-handling later to maint).
|
||||
(merge 699248d89e th/t8003-unhide-git-failures later to maint).
|
||||
|
||||
@@ -101,6 +101,11 @@ format.coverLetter::
|
||||
generate a cover-letter only when there's more than one patch.
|
||||
Default is false.
|
||||
|
||||
format.commitListFormat::
|
||||
When the `--cover-letter-format` option is not given, `format-patch`
|
||||
uses the value of this variable to decide how to format the entry of
|
||||
each commit. Defaults to `shortlog`.
|
||||
|
||||
format.outputDirectory::
|
||||
Set a custom directory to store the resulting files instead of the
|
||||
current working directory. All directory components will be created.
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
hook.<name>.command::
|
||||
The command to execute for `hook.<name>`. `<name>` is a unique
|
||||
"friendly" name that identifies this hook. (The hook events that
|
||||
trigger the command are configured with `hook.<name>.event`.) The
|
||||
value can be an executable path or a shell oneliner. If more than
|
||||
one value is specified for the same `<name>`, only the last value
|
||||
parsed is used. See linkgit:git-hook[1].
|
||||
hook.<friendly-name>.command::
|
||||
The command to execute for `hook.<friendly-name>`. `<friendly-name>`
|
||||
is a unique name that identifies this hook. The hook events that
|
||||
trigger the command are configured with `hook.<friendly-name>.event`.
|
||||
The value can be an executable path or a shell oneliner. If more than
|
||||
one value is specified for the same `<friendly-name>`, only the last
|
||||
value parsed is used. See linkgit:git-hook[1].
|
||||
|
||||
hook.<name>.event::
|
||||
The hook events that trigger `hook.<name>`. The value is the name
|
||||
of a hook event, like "pre-commit" or "update". (See
|
||||
hook.<friendly-name>.event::
|
||||
The hook events that trigger `hook.<friendly-name>`. The value is the
|
||||
name of a hook event, like "pre-commit" or "update". (See
|
||||
linkgit:githooks[5] for a complete list of hook events.) On the
|
||||
specified event, the associated `hook.<name>.command` is executed.
|
||||
This is a multi-valued key. To run `hook.<name>` on multiple
|
||||
specified event, the associated `hook.<friendly-name>.command` is executed.
|
||||
This is a multi-valued key. To run `hook.<friendly-name>` on multiple
|
||||
events, specify the key more than once. An empty value resets
|
||||
the list of events, clearing any previously defined events for
|
||||
`hook.<name>`. See linkgit:git-hook[1].
|
||||
`hook.<friendly-name>`. See linkgit:git-hook[1].
|
||||
|
||||
hook.<name>.enabled::
|
||||
Whether the hook `hook.<name>` is enabled. Defaults to `true`.
|
||||
hook.<friendly-name>.enabled::
|
||||
Whether the hook `hook.<friendly-name>` is enabled. Defaults to `true`.
|
||||
Set to `false` to disable the hook without removing its
|
||||
configuration. This is particularly useful when a hook is defined
|
||||
in a system or global config file and needs to be disabled for a
|
||||
|
||||
@@ -63,9 +63,12 @@ OPTIONS
|
||||
current sparse-checkout. If the sparse-checkout feature is enabled,
|
||||
then `--sparse` is assumed and can be disabled with `--no-sparse`.
|
||||
|
||||
You may also specify the commit limiting options from linkgit:git-rev-list[1].
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
linkgit:git-clone[1].
|
||||
linkgit:git-clone[1],
|
||||
linkgit:git-rev-list[1]
|
||||
|
||||
GIT
|
||||
---
|
||||
|
||||
@@ -24,6 +24,7 @@ SYNOPSIS
|
||||
[(--reroll-count|-v) <n>]
|
||||
[--to=<email>] [--cc=<email>]
|
||||
[--[no-]cover-letter] [--quiet]
|
||||
[--commit-list-format=<format-spec>]
|
||||
[--[no-]encode-email-headers]
|
||||
[--no-notes | --notes[=<ref>]]
|
||||
[--interdiff=<previous>]
|
||||
@@ -318,9 +319,21 @@ feeding the result to `git send-email`.
|
||||
|
||||
--cover-letter::
|
||||
--no-cover-letter::
|
||||
In addition to the patches, generate a cover letter file
|
||||
containing the branch description, shortlog and the overall diffstat. You can
|
||||
fill in a description in the file before sending it out.
|
||||
In addition to the patches, generate a cover letter file containing the
|
||||
branch description, commit list and the overall diffstat. You can fill
|
||||
in a description in the file before sending it out.
|
||||
|
||||
--commit-list-format=<format-spec>::
|
||||
Specify the format in which to generate the commit list of the patch
|
||||
series. The accepted values for format-spec are `shortlog`, `modern` or
|
||||
a format-string prefixed with `log:`. E.g. `log: %s (%an)`.
|
||||
`modern` is the same as `log:%w(72)[%(count)/%(total)] %s`.
|
||||
The `log:` prefix can be omitted if the format-string has a `%` in it
|
||||
(expecting that it is part of `%<placeholder>`).
|
||||
Defaults to the `format.commitListFormat` configuration variable, if
|
||||
set, or `shortlog`.
|
||||
This option given from the command-line implies the use of
|
||||
`--cover-letter` unless `--no-cover-letter` is given.
|
||||
|
||||
--encode-email-headers::
|
||||
--no-encode-email-headers::
|
||||
@@ -453,6 +466,7 @@ with configuration variables.
|
||||
signOff = true
|
||||
outputDirectory = <directory>
|
||||
coverLetter = auto
|
||||
commitListFormat = shortlog
|
||||
coverFromDescription = auto
|
||||
------------
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ git-hook - Run git hooks
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git hook' run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]
|
||||
'git hook' list [-z] <hook-name>
|
||||
'git hook' run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]
|
||||
'git hook' list [--allow-unknown-hook-name] [-z] [--show-scope] <hook-name>
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@@ -44,7 +44,7 @@ event`), and then `~/bin/spellchecker` will have a chance to check your commit
|
||||
message (during the `commit-msg` hook event).
|
||||
|
||||
Commands are run in the order Git encounters their associated
|
||||
`hook.<name>.event` configs during the configuration parse (see
|
||||
`hook.<friendly-name>.event` configs during the configuration parse (see
|
||||
linkgit:git-config[1]). Although multiple `hook.linter.event` configs can be
|
||||
added, only one `hook.linter.command` event is valid - Git uses "last-one-wins"
|
||||
to determine which command to run.
|
||||
@@ -76,10 +76,10 @@ first start `~/bin/linter --cpp20` and second start `~/bin/leak-detector`. It
|
||||
would evaluate the output of each when deciding whether to proceed with the
|
||||
commit.
|
||||
|
||||
For a full list of hook events which you can set your `hook.<name>.event` to,
|
||||
For a full list of hook events which you can set your `hook.<friendly-name>.event` to,
|
||||
and how hooks are invoked during those events, see linkgit:githooks[5].
|
||||
|
||||
Git will ignore any `hook.<name>.event` that specifies an event it doesn't
|
||||
Git will ignore any `hook.<friendly-name>.event` that specifies an event it doesn't
|
||||
recognize. This is intended so that tools which wrap Git can use the hook
|
||||
infrastructure to run their own hooks; see "WRAPPERS" for more guidance.
|
||||
|
||||
@@ -113,7 +113,7 @@ Any positional arguments to the hook should be passed after a
|
||||
mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
|
||||
linkgit:githooks[5] for arguments hooks might expect (if any).
|
||||
|
||||
list [-z]::
|
||||
list [-z] [--show-scope]::
|
||||
Print a list of hooks which will be run on `<hook-name>` event. If no
|
||||
hooks are configured for that event, print a warning and return 1.
|
||||
Use `-z` to terminate output lines with NUL instead of newlines.
|
||||
@@ -121,6 +121,13 @@ list [-z]::
|
||||
OPTIONS
|
||||
-------
|
||||
|
||||
--allow-unknown-hook-name::
|
||||
By default `git hook run` and `git hook list` will bail out when
|
||||
`<hook-name>` is not a hook event known to Git (see linkgit:githooks[5]
|
||||
for the list of known hooks). This is meant to help catch typos
|
||||
such as `prereceive` when `pre-receive` was intended. Pass this
|
||||
flag to allow unknown hook names.
|
||||
|
||||
--to-stdin::
|
||||
For "run"; specify a file which will be streamed into the
|
||||
hook's stdin. The hook will receive the entire file from
|
||||
@@ -134,6 +141,12 @@ OPTIONS
|
||||
-z::
|
||||
Terminate "list" output lines with NUL instead of newlines.
|
||||
|
||||
--show-scope::
|
||||
For "list"; prefix each configured hook's friendly name with a
|
||||
tab-separated config scope (e.g. `local`, `global`, `system`),
|
||||
mirroring the output style of `git config --show-scope`. Traditional
|
||||
hooks from the hookdir are unaffected.
|
||||
|
||||
WRAPPERS
|
||||
--------
|
||||
|
||||
@@ -153,7 +166,7 @@ Then, in your 'mywrapper' tool, you can invoke any users' configured hooks by
|
||||
running:
|
||||
|
||||
----
|
||||
git hook run mywrapper-start-tests \
|
||||
git hook run --allow-unknown-hook-name mywrapper-start-tests \
|
||||
# providing something to stdin
|
||||
--stdin some-tempfile-123 \
|
||||
# execute hooks in serial
|
||||
|
||||
@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) [--ref-action[=<mode>]] <revision-range>
|
||||
(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) [--ref-action[=<mode>]] <revision-range>
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@@ -42,6 +42,25 @@ The history is replayed on top of the <branch> and <branch> is updated to
|
||||
point at the tip of the resulting history. This is different from `--onto`,
|
||||
which uses the target only as a starting point without updating it.
|
||||
|
||||
--revert <branch>::
|
||||
Starting point at which to create the reverted commits; must be a
|
||||
branch name.
|
||||
+
|
||||
When `--revert` is specified, the commits in the revision range are reverted
|
||||
(their changes are undone) and the reverted commits are created on top of
|
||||
<branch>. The <branch> is then updated to point at the new commits. This is
|
||||
the same as running `git revert <revision-range>` but does not update the
|
||||
working tree.
|
||||
+
|
||||
The commit messages follow `git revert` conventions: they are prefixed with
|
||||
"Revert" and include "This reverts commit <hash>." When reverting a commit
|
||||
whose message starts with "Revert", the new message uses "Reapply" instead.
|
||||
Unlike cherry-pick which preserves the original author, revert commits use
|
||||
the current user as the author, matching the behavior of `git revert`.
|
||||
+
|
||||
This option is mutually exclusive with `--onto` and `--advance`. It is also
|
||||
incompatible with `--contained` (which is a modifier for `--onto` only).
|
||||
|
||||
--contained::
|
||||
Update all branches that point at commits in
|
||||
<revision-range>. Requires `--onto`.
|
||||
@@ -60,10 +79,11 @@ The default mode can be configured via the `replay.refAction` configuration vari
|
||||
|
||||
<revision-range>::
|
||||
Range of commits to replay; see "Specifying Ranges" in
|
||||
linkgit:git-rev-parse[1]. In `--advance <branch>` mode, the
|
||||
range should have a single tip, so that it's clear to which tip the
|
||||
advanced <branch> should point. Any commits in the range whose
|
||||
changes are already present in the branch the commits are being
|
||||
linkgit:git-rev-parse[1]. In `--advance <branch>` or
|
||||
`--revert <branch>` mode, the range should have a single tip,
|
||||
so that it's clear to which tip the advanced or reverted
|
||||
<branch> should point. Any commits in the range whose changes
|
||||
are already present in the branch the commits are being
|
||||
replayed onto will be dropped.
|
||||
|
||||
:git-replay: 1
|
||||
@@ -84,9 +104,10 @@ When using `--ref-action=print`, the output is usable as input to
|
||||
update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}
|
||||
|
||||
where the number of refs updated depends on the arguments passed and
|
||||
the shape of the history being replayed. When using `--advance`, the
|
||||
number of refs updated is always one, but for `--onto`, it can be one
|
||||
or more (rebasing multiple branches simultaneously is supported).
|
||||
the shape of the history being replayed. When using `--advance` or
|
||||
`--revert`, the number of refs updated is always one, but for `--onto`,
|
||||
it can be one or more (rebasing multiple branches simultaneously is
|
||||
supported).
|
||||
|
||||
There is no stderr output on conflicts; see the <<exit-status,EXIT
|
||||
STATUS>> section below.
|
||||
@@ -152,6 +173,21 @@ all commits they have since `base`, playing them on top of
|
||||
`origin/main`. These three branches may have commits on top of `base`
|
||||
that they have in common, but that does not need to be the case.
|
||||
|
||||
To revert commits on a branch:
|
||||
|
||||
------------
|
||||
$ git replay --revert main topic~2..topic
|
||||
------------
|
||||
|
||||
This reverts the last two commits from `topic`, creating revert commits on
|
||||
top of `main`, and updates `main` to point at the result. This is useful when
|
||||
commits from `topic` were previously merged or cherry-picked into `main` and
|
||||
need to be undone.
|
||||
|
||||
NOTE: For reverting an entire merge request as a single commit (rather than
|
||||
commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE`
|
||||
which can avoid unnecessary merge conflicts.
|
||||
|
||||
GIT
|
||||
---
|
||||
Part of the linkgit:git[1] suite
|
||||
|
||||
@@ -253,6 +253,10 @@ The placeholders are:
|
||||
linkgit:git-rev-list[1])
|
||||
+%d+:: ref names, like the --decorate option of linkgit:git-log[1]
|
||||
+%D+:: ref names without the " (", ")" wrapping.
|
||||
+%(count)+:: the number of a patch within a patch series. Used only in
|
||||
`--commit-list-format` in `format-patch`
|
||||
+%(total)+:: the total number of patches in a patch series. Used only in
|
||||
`--commit-list-format` in `format-patch`
|
||||
++%(decorate++`[:<option>,...]`++)++::
|
||||
ref names with custom decorations. The `decorate` string may be followed by a
|
||||
colon and zero or more comma-separated options. Option values may contain
|
||||
|
||||
@@ -2675,6 +2675,7 @@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
|
||||
|
||||
help.sp help.s help.o: command-list.h
|
||||
builtin/bugreport.sp builtin/bugreport.s builtin/bugreport.o: hook-list.h
|
||||
builtin/hook.sp builtin/hook.s builtin/hook.o: hook-list.h
|
||||
|
||||
builtin/help.sp builtin/help.s builtin/help.o: config-list.h GIT-PREFIX
|
||||
builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
|
||||
|
||||
+16
-6
@@ -35,6 +35,7 @@ struct backfill_context {
|
||||
struct oid_array current_batch;
|
||||
size_t min_batch_size;
|
||||
int sparse;
|
||||
struct rev_info revs;
|
||||
};
|
||||
|
||||
static void backfill_context_clear(struct backfill_context *ctx)
|
||||
@@ -79,7 +80,6 @@ static int fill_missing_blobs(const char *path UNUSED,
|
||||
|
||||
static int do_backfill(struct backfill_context *ctx)
|
||||
{
|
||||
struct rev_info revs;
|
||||
struct path_walk_info info = PATH_WALK_INFO_INIT;
|
||||
int ret;
|
||||
|
||||
@@ -91,13 +91,14 @@ static int do_backfill(struct backfill_context *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
repo_init_revisions(ctx->repo, &revs, "");
|
||||
handle_revision_arg("HEAD", &revs, 0, 0);
|
||||
/* Walk from HEAD if otherwise unspecified. */
|
||||
if (!ctx->revs.pending.nr)
|
||||
add_head_to_pending(&ctx->revs);
|
||||
|
||||
info.blobs = 1;
|
||||
info.tags = info.commits = info.trees = 0;
|
||||
|
||||
info.revs = &revs;
|
||||
info.revs = &ctx->revs;
|
||||
info.path_fn = fill_missing_blobs;
|
||||
info.path_fn_data = ctx;
|
||||
|
||||
@@ -108,7 +109,6 @@ static int do_backfill(struct backfill_context *ctx)
|
||||
download_batch(ctx);
|
||||
|
||||
path_walk_info_clear(&info);
|
||||
release_revisions(&revs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit
|
||||
.current_batch = OID_ARRAY_INIT,
|
||||
.min_batch_size = 50000,
|
||||
.sparse = 0,
|
||||
.revs = REV_INFO_INIT,
|
||||
};
|
||||
struct option options[] = {
|
||||
OPT_UNSIGNED(0, "min-batch-size", &ctx.min_batch_size,
|
||||
@@ -134,7 +135,15 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit
|
||||
builtin_backfill_usage, options);
|
||||
|
||||
argc = parse_options(argc, argv, prefix, options, builtin_backfill_usage,
|
||||
0);
|
||||
PARSE_OPT_KEEP_UNKNOWN_OPT |
|
||||
PARSE_OPT_KEEP_ARGV0 |
|
||||
PARSE_OPT_KEEP_DASHDASH);
|
||||
|
||||
repo_init_revisions(repo, &ctx.revs, prefix);
|
||||
argc = setup_revisions(argc, argv, &ctx.revs, NULL);
|
||||
|
||||
if (argc > 1)
|
||||
die(_("unrecognized argument: %s"), argv[1]);
|
||||
|
||||
repo_config(repo, git_default_config, NULL);
|
||||
|
||||
@@ -143,5 +152,6 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit
|
||||
|
||||
result = do_backfill(&ctx);
|
||||
backfill_context_clear(&ctx);
|
||||
release_revisions(&ctx.revs);
|
||||
return result;
|
||||
}
|
||||
|
||||
+52
-9
@@ -4,14 +4,22 @@
|
||||
#include "environment.h"
|
||||
#include "gettext.h"
|
||||
#include "hook.h"
|
||||
#include "hook-list.h"
|
||||
#include "parse-options.h"
|
||||
#include "strvec.h"
|
||||
#include "abspath.h"
|
||||
|
||||
#define BUILTIN_HOOK_RUN_USAGE \
|
||||
N_("git hook run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]")
|
||||
N_("git hook run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]")
|
||||
#define BUILTIN_HOOK_LIST_USAGE \
|
||||
N_("git hook list [-z] <hook-name>")
|
||||
N_("git hook list [--allow-unknown-hook-name] [-z] [--show-scope] <hook-name>")
|
||||
|
||||
static int is_known_hook(const char *name)
|
||||
{
|
||||
const char **p;
|
||||
for (p = hook_name_list; *p; p++)
|
||||
if (!strcmp(*p, name))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char * const builtin_hook_usage[] = {
|
||||
BUILTIN_HOOK_RUN_USAGE,
|
||||
@@ -35,11 +43,17 @@ static int list(int argc, const char **argv, const char *prefix,
|
||||
struct string_list_item *item;
|
||||
const char *hookname = NULL;
|
||||
int line_terminator = '\n';
|
||||
int show_scope = 0;
|
||||
int allow_unknown = 0;
|
||||
int ret = 0;
|
||||
|
||||
struct option list_options[] = {
|
||||
OPT_SET_INT('z', NULL, &line_terminator,
|
||||
N_("use NUL as line terminator"), '\0'),
|
||||
OPT_BOOL(0, "show-scope", &show_scope,
|
||||
N_("show the config scope that defined each hook")),
|
||||
OPT_BOOL(0, "allow-unknown-hook-name", &allow_unknown,
|
||||
N_("allow running a hook with a non-native hook name")),
|
||||
OPT_END(),
|
||||
};
|
||||
|
||||
@@ -51,15 +65,22 @@ static int list(int argc, const char **argv, const char *prefix,
|
||||
* arguments later they probably should be caught by parse_options.
|
||||
*/
|
||||
if (argc != 1)
|
||||
usage_msg_opt(_("You must specify a hook event name to list."),
|
||||
usage_msg_opt(_("you must specify a hook event name to list"),
|
||||
builtin_hook_list_usage, list_options);
|
||||
|
||||
hookname = argv[0];
|
||||
|
||||
if (!allow_unknown && !is_known_hook(hookname)) {
|
||||
error(_("unknown hook event '%s';\n"
|
||||
"use --allow-unknown-hook-name to allow non-native hook names"),
|
||||
hookname);
|
||||
return 1;
|
||||
}
|
||||
|
||||
head = list_hooks(repo, hookname, NULL);
|
||||
|
||||
if (!head->nr) {
|
||||
warning(_("No hooks found for event '%s'"), hookname);
|
||||
warning(_("no hooks found for event '%s'"), hookname);
|
||||
ret = 1; /* no hooks found */
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -71,16 +92,27 @@ static int list(int argc, const char **argv, const char *prefix,
|
||||
case HOOK_TRADITIONAL:
|
||||
printf("%s%c", _("hook from hookdir"), line_terminator);
|
||||
break;
|
||||
case HOOK_CONFIGURED:
|
||||
printf("%s%c", h->u.configured.friendly_name, line_terminator);
|
||||
case HOOK_CONFIGURED: {
|
||||
const char *name = h->u.configured.friendly_name;
|
||||
const char *scope = show_scope ?
|
||||
config_scope_name(h->u.configured.scope) : NULL;
|
||||
if (scope)
|
||||
printf("%s\t%s%s%c", scope,
|
||||
h->u.configured.disabled ? "disabled\t" : "",
|
||||
name, line_terminator);
|
||||
else
|
||||
printf("%s%s%c",
|
||||
h->u.configured.disabled ? "disabled\t" : "",
|
||||
name, line_terminator);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BUG("unknown hook kind");
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
hook_list_clear(head, NULL);
|
||||
string_list_clear_func(head, hook_free);
|
||||
free(head);
|
||||
return ret;
|
||||
}
|
||||
@@ -91,8 +123,11 @@ static int run(int argc, const char **argv, const char *prefix,
|
||||
int i;
|
||||
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
|
||||
int ignore_missing = 0;
|
||||
int allow_unknown = 0;
|
||||
const char *hook_name;
|
||||
struct option run_options[] = {
|
||||
OPT_BOOL(0, "allow-unknown-hook-name", &allow_unknown,
|
||||
N_("allow running a hook with a non-native hook name")),
|
||||
OPT_BOOL(0, "ignore-missing", &ignore_missing,
|
||||
N_("silently ignore missing requested <hook-name>")),
|
||||
OPT_STRING(0, "to-stdin", &opt.path_to_stdin, N_("path"),
|
||||
@@ -124,6 +159,14 @@ static int run(int argc, const char **argv, const char *prefix,
|
||||
repo_config(the_repository, git_default_config, NULL);
|
||||
|
||||
hook_name = argv[0];
|
||||
|
||||
if (!allow_unknown && !is_known_hook(hook_name)) {
|
||||
error(_("unknown hook event '%s';\n"
|
||||
"use --allow-unknown-hook-name to allow non-native hook names"),
|
||||
hook_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!ignore_missing)
|
||||
opt.error_if_missing = 1;
|
||||
ret = run_hooks_opt(the_repository, hook_name, &opt);
|
||||
|
||||
+75
-14
@@ -40,6 +40,7 @@
|
||||
#include "progress.h"
|
||||
#include "commit-slab.h"
|
||||
#include "advice.h"
|
||||
#include "utf8.h"
|
||||
|
||||
#include "commit-reach.h"
|
||||
#include "range-diff.h"
|
||||
@@ -886,6 +887,7 @@ struct format_config {
|
||||
char *signature;
|
||||
char *signature_file;
|
||||
enum cover_setting config_cover_letter;
|
||||
char *fmt_cover_letter_commit_list;
|
||||
char *config_output_directory;
|
||||
enum cover_from_description cover_from_description_mode;
|
||||
int show_notes;
|
||||
@@ -930,6 +932,7 @@ static void format_config_release(struct format_config *cfg)
|
||||
string_list_clear(&cfg->extra_cc, 0);
|
||||
strbuf_release(&cfg->sprefix);
|
||||
free(cfg->fmt_patch_suffix);
|
||||
free(cfg->fmt_cover_letter_commit_list);
|
||||
}
|
||||
|
||||
static enum cover_from_description parse_cover_from_description(const char *arg)
|
||||
@@ -1052,6 +1055,10 @@ static int git_format_config(const char *var, const char *value,
|
||||
cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(var, "format.commitlistformat")) {
|
||||
FREE_AND_NULL(cfg->fmt_cover_letter_commit_list);
|
||||
return git_config_string(&cfg->fmt_cover_letter_commit_list, var, value);
|
||||
}
|
||||
if (!strcmp(var, "format.outputdirectory")) {
|
||||
FREE_AND_NULL(cfg->config_output_directory);
|
||||
return git_config_string(&cfg->config_output_directory, var, value);
|
||||
@@ -1335,13 +1342,54 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
|
||||
}
|
||||
}
|
||||
|
||||
static void generate_shortlog_cover_letter(struct shortlog *log,
|
||||
struct rev_info *rev,
|
||||
struct commit **list,
|
||||
int nr)
|
||||
{
|
||||
shortlog_init(log);
|
||||
log->wrap_lines = 1;
|
||||
log->wrap = MAIL_DEFAULT_WRAP;
|
||||
log->in1 = 2;
|
||||
log->in2 = 4;
|
||||
log->file = rev->diffopt.file;
|
||||
log->groups = SHORTLOG_GROUP_AUTHOR;
|
||||
shortlog_finish_setup(log);
|
||||
for (int i = 0; i < nr; i++)
|
||||
shortlog_add_commit(log, list[i]);
|
||||
|
||||
shortlog_output(log);
|
||||
}
|
||||
|
||||
static void generate_commit_list_cover(FILE *cover_file, const char *format,
|
||||
struct commit **list, int n)
|
||||
{
|
||||
struct strbuf commit_line = STRBUF_INIT;
|
||||
struct pretty_print_context ctx = {0};
|
||||
struct rev_info rev = REV_INFO_INIT;
|
||||
|
||||
rev.total = n;
|
||||
ctx.rev = &rev;
|
||||
for (int i = 1; i <= n; i++) {
|
||||
rev.nr = i;
|
||||
repo_format_commit_message(the_repository, list[n - i], format,
|
||||
&commit_line, &ctx);
|
||||
fprintf(cover_file, "%s\n", commit_line.buf);
|
||||
strbuf_reset(&commit_line);
|
||||
}
|
||||
fprintf(cover_file, "\n");
|
||||
|
||||
strbuf_release(&commit_line);
|
||||
}
|
||||
|
||||
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
|
||||
struct commit *origin,
|
||||
int nr, struct commit **list,
|
||||
const char *description_file,
|
||||
const char *branch_name,
|
||||
int quiet,
|
||||
const struct format_config *cfg)
|
||||
const struct format_config *cfg,
|
||||
const char *format)
|
||||
{
|
||||
const char *from;
|
||||
struct shortlog log;
|
||||
@@ -1388,18 +1436,17 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
|
||||
free(pp.after_subject);
|
||||
strbuf_release(&sb);
|
||||
|
||||
shortlog_init(&log);
|
||||
log.wrap_lines = 1;
|
||||
log.wrap = MAIL_DEFAULT_WRAP;
|
||||
log.in1 = 2;
|
||||
log.in2 = 4;
|
||||
log.file = rev->diffopt.file;
|
||||
log.groups = SHORTLOG_GROUP_AUTHOR;
|
||||
shortlog_finish_setup(&log);
|
||||
for (i = 0; i < nr; i++)
|
||||
shortlog_add_commit(&log, list[i]);
|
||||
|
||||
shortlog_output(&log);
|
||||
if (skip_prefix(format, "log:", &format))
|
||||
generate_commit_list_cover(rev->diffopt.file, format, list, nr);
|
||||
else if (!strcmp(format, "shortlog"))
|
||||
generate_shortlog_cover_letter(&log, rev, list, nr);
|
||||
else if (!strcmp(format, "modern"))
|
||||
generate_commit_list_cover(rev->diffopt.file, "%w(72)[%(count)/%(total)] %s",
|
||||
list, nr);
|
||||
else if (strchr(format, '%'))
|
||||
generate_commit_list_cover(rev->diffopt.file, format, list, nr);
|
||||
else
|
||||
die(_("'%s' is not a valid format string"), format);
|
||||
|
||||
/* We can only do diffstat with a unique reference point */
|
||||
if (origin)
|
||||
@@ -1917,6 +1964,7 @@ int cmd_format_patch(int argc,
|
||||
int just_numbers = 0;
|
||||
int ignore_if_in_upstream = 0;
|
||||
int cover_letter = -1;
|
||||
const char *cover_letter_fmt = NULL;
|
||||
int boundary_count = 0;
|
||||
int no_binary_diff = 0;
|
||||
int zero_commit = 0;
|
||||
@@ -1963,6 +2011,8 @@ int cmd_format_patch(int argc,
|
||||
N_("print patches to standard out")),
|
||||
OPT_BOOL(0, "cover-letter", &cover_letter,
|
||||
N_("generate a cover letter")),
|
||||
OPT_STRING(0, "commit-list-format", &cover_letter_fmt, N_("format-spec"),
|
||||
N_("format spec used for the commit list in the cover letter")),
|
||||
OPT_BOOL(0, "numbered-files", &just_numbers,
|
||||
N_("use simple number sequence for output file names")),
|
||||
OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
|
||||
@@ -2300,6 +2350,15 @@ int cmd_format_patch(int argc,
|
||||
/* nothing to do */
|
||||
goto done;
|
||||
total = list.nr;
|
||||
|
||||
if (!cover_letter_fmt) {
|
||||
cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
|
||||
if (!cover_letter_fmt)
|
||||
cover_letter_fmt = "shortlog";
|
||||
} else if (cover_letter == -1) {
|
||||
cover_letter = 1;
|
||||
}
|
||||
|
||||
if (cover_letter == -1) {
|
||||
if (cfg.config_cover_letter == COVER_AUTO)
|
||||
cover_letter = (total > 1);
|
||||
@@ -2386,12 +2445,14 @@ int cmd_format_patch(int argc,
|
||||
}
|
||||
rev.numbered_files = just_numbers;
|
||||
rev.patch_suffix = fmt_patch_suffix;
|
||||
|
||||
if (cover_letter) {
|
||||
if (cfg.thread)
|
||||
gen_message_id(&rev, "cover");
|
||||
make_cover_letter(&rev, !!output_directory,
|
||||
origin, list.nr, list.items,
|
||||
description_file, branch_name, quiet, &cfg);
|
||||
description_file, branch_name, quiet, &cfg,
|
||||
cover_letter_fmt);
|
||||
print_bases(&bases, rev.diffopt.file);
|
||||
print_signature(signature, rev.diffopt.file);
|
||||
total++;
|
||||
|
||||
+36
-32
@@ -3,46 +3,45 @@
|
||||
|
||||
#include "builtin.h"
|
||||
#include "abspath.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "environment.h"
|
||||
#include "gettext.h"
|
||||
#include "hex.h"
|
||||
#include "lockfile.h"
|
||||
#include "pack.h"
|
||||
#include "refs.h"
|
||||
#include "pkt-line.h"
|
||||
#include "sideband.h"
|
||||
#include "run-command.h"
|
||||
#include "hook.h"
|
||||
#include "exec-cmd.h"
|
||||
#include "commit.h"
|
||||
#include "object.h"
|
||||
#include "remote.h"
|
||||
#include "commit-reach.h"
|
||||
#include "config.h"
|
||||
#include "connect.h"
|
||||
#include "string-list.h"
|
||||
#include "oid-array.h"
|
||||
#include "connected.h"
|
||||
#include "strvec.h"
|
||||
#include "version.h"
|
||||
#include "gpg-interface.h"
|
||||
#include "sigchain.h"
|
||||
#include "environment.h"
|
||||
#include "exec-cmd.h"
|
||||
#include "fsck.h"
|
||||
#include "tmp-objdir.h"
|
||||
#include "oidset.h"
|
||||
#include "packfile.h"
|
||||
#include "gettext.h"
|
||||
#include "gpg-interface.h"
|
||||
#include "hex.h"
|
||||
#include "hook.h"
|
||||
#include "lockfile.h"
|
||||
#include "object.h"
|
||||
#include "object-file.h"
|
||||
#include "object-name.h"
|
||||
#include "odb.h"
|
||||
#include "oid-array.h"
|
||||
#include "oidset.h"
|
||||
#include "pack.h"
|
||||
#include "packfile.h"
|
||||
#include "parse-options.h"
|
||||
#include "pkt-line.h"
|
||||
#include "protocol.h"
|
||||
#include "commit-reach.h"
|
||||
#include "refs.h"
|
||||
#include "remote.h"
|
||||
#include "run-command.h"
|
||||
#include "server-info.h"
|
||||
#include "setup.h"
|
||||
#include "shallow.h"
|
||||
#include "sideband.h"
|
||||
#include "sigchain.h"
|
||||
#include "string-list.h"
|
||||
#include "strvec.h"
|
||||
#include "tmp-objdir.h"
|
||||
#include "trace.h"
|
||||
#include "trace2.h"
|
||||
#include "version.h"
|
||||
#include "worktree.h"
|
||||
#include "shallow.h"
|
||||
#include "setup.h"
|
||||
#include "parse-options.h"
|
||||
|
||||
static const char * const receive_pack_usage[] = {
|
||||
N_("git receive-pack <git-dir>"),
|
||||
@@ -904,11 +903,14 @@ static int feed_receive_hook_cb(int hook_stdin_fd, void *pp_cb UNUSED, void *pp_
|
||||
static void *receive_hook_feed_state_alloc(void *feed_pipe_ctx)
|
||||
{
|
||||
struct receive_hook_feed_state *init_state = feed_pipe_ctx;
|
||||
struct receive_hook_feed_state *data = xcalloc(1, sizeof(*data));
|
||||
struct receive_hook_feed_state *data;
|
||||
|
||||
CALLOC_ARRAY(data, 1);
|
||||
data->report = init_state->report;
|
||||
data->cmd = init_state->cmd;
|
||||
data->skip_broken = init_state->skip_broken;
|
||||
strbuf_init(&data->buf, 0);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -928,7 +930,11 @@ static int run_receive_hook(struct command *commands,
|
||||
{
|
||||
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
|
||||
struct command *iter = commands;
|
||||
struct receive_hook_feed_state feed_init_state = { 0 };
|
||||
struct receive_hook_feed_state feed_init_state = {
|
||||
.cmd = commands,
|
||||
.skip_broken = skip_broken,
|
||||
.buf = STRBUF_INIT,
|
||||
};
|
||||
struct async sideband_async;
|
||||
int sideband_async_started = 0;
|
||||
int saved_stderr = -1;
|
||||
@@ -961,8 +967,6 @@ static int run_receive_hook(struct command *commands,
|
||||
prepare_sideband_async(&sideband_async, &saved_stderr, &sideband_async_started);
|
||||
|
||||
/* set up stdin callback */
|
||||
feed_init_state.cmd = commands;
|
||||
feed_init_state.skip_broken = skip_broken;
|
||||
opt.feed_pipe_ctx = &feed_init_state;
|
||||
opt.feed_pipe = feed_receive_hook_cb;
|
||||
opt.feed_pipe_cb_data_alloc = receive_hook_feed_state_alloc;
|
||||
|
||||
+27
-9
@@ -79,11 +79,12 @@ int cmd_replay(int argc,
|
||||
struct ref_transaction *transaction = NULL;
|
||||
struct strbuf transaction_err = STRBUF_INIT;
|
||||
struct strbuf reflog_msg = STRBUF_INIT;
|
||||
int desired_reverse;
|
||||
int ret = 0;
|
||||
|
||||
const char *const replay_usage[] = {
|
||||
N_("(EXPERIMENTAL!) git replay "
|
||||
"([--contained] --onto <newbase> | --advance <branch>) "
|
||||
"([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) "
|
||||
"[--ref-action[=<mode>]] <revision-range>"),
|
||||
NULL
|
||||
};
|
||||
@@ -96,6 +97,9 @@ int cmd_replay(int argc,
|
||||
N_("replay onto given commit")),
|
||||
OPT_BOOL(0, "contained", &opts.contained,
|
||||
N_("update all branches that point at commits in <revision-range>")),
|
||||
OPT_STRING(0, "revert", &opts.revert,
|
||||
N_("branch"),
|
||||
N_("revert commits onto given branch")),
|
||||
OPT_STRING(0, "ref-action", &ref_action,
|
||||
N_("mode"),
|
||||
N_("control ref update behavior (update|print)")),
|
||||
@@ -105,19 +109,31 @@ int cmd_replay(int argc,
|
||||
argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
|
||||
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
|
||||
|
||||
if (!opts.onto && !opts.advance) {
|
||||
error(_("option --onto or --advance is mandatory"));
|
||||
/* Exactly one mode must be specified */
|
||||
if (!opts.onto && !opts.advance && !opts.revert) {
|
||||
error(_("exactly one of --onto, --advance, or --revert is required"));
|
||||
usage_with_options(replay_usage, replay_options);
|
||||
}
|
||||
|
||||
die_for_incompatible_opt3(!!opts.onto, "--onto",
|
||||
!!opts.advance, "--advance",
|
||||
!!opts.revert, "--revert");
|
||||
die_for_incompatible_opt2(!!opts.advance, "--advance",
|
||||
opts.contained, "--contained");
|
||||
die_for_incompatible_opt2(!!opts.advance, "--advance",
|
||||
!!opts.onto, "--onto");
|
||||
die_for_incompatible_opt2(!!opts.revert, "--revert",
|
||||
opts.contained, "--contained");
|
||||
|
||||
/* Parse ref action mode from command line or config */
|
||||
ref_mode = get_ref_action_mode(repo, ref_action);
|
||||
|
||||
/*
|
||||
* Cherry-pick/rebase need oldest-first ordering so that each
|
||||
* replayed commit can build on its already-replayed parent.
|
||||
* Revert needs newest-first ordering (like git revert) to
|
||||
* reduce conflicts by peeling off changes from the top.
|
||||
*/
|
||||
desired_reverse = !opts.revert;
|
||||
|
||||
repo_init_revisions(repo, &revs, prefix);
|
||||
|
||||
/*
|
||||
@@ -129,7 +145,7 @@ int cmd_replay(int argc,
|
||||
* some options changing these values if we think they could
|
||||
* be useful.
|
||||
*/
|
||||
revs.reverse = 1;
|
||||
revs.reverse = desired_reverse;
|
||||
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
|
||||
revs.topo_order = 1;
|
||||
revs.simplify_history = 0;
|
||||
@@ -144,11 +160,11 @@ int cmd_replay(int argc,
|
||||
* Detect and warn if we override some user specified rev
|
||||
* walking options.
|
||||
*/
|
||||
if (revs.reverse != 1) {
|
||||
if (revs.reverse != desired_reverse) {
|
||||
warning(_("some rev walking options will be overridden as "
|
||||
"'%s' bit in 'struct rev_info' will be forced"),
|
||||
"reverse");
|
||||
revs.reverse = 1;
|
||||
revs.reverse = desired_reverse;
|
||||
}
|
||||
if (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {
|
||||
warning(_("some rev walking options will be overridden as "
|
||||
@@ -174,7 +190,9 @@ int cmd_replay(int argc,
|
||||
goto cleanup;
|
||||
|
||||
/* Build reflog message */
|
||||
if (opts.advance) {
|
||||
if (opts.revert) {
|
||||
strbuf_addf(&reflog_msg, "replay --revert %s", opts.revert);
|
||||
} else if (opts.advance) {
|
||||
strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance);
|
||||
} else {
|
||||
struct object_id oid;
|
||||
|
||||
+2
-19
@@ -692,25 +692,8 @@ static int can_use_local_refs(const struct add_opts *opts)
|
||||
if (refs_head_ref(get_main_ref_store(the_repository), first_valid_ref, NULL)) {
|
||||
return 1;
|
||||
} else if (refs_for_each_branch_ref(get_main_ref_store(the_repository), first_valid_ref, NULL)) {
|
||||
if (!opts->quiet) {
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
struct strbuf contents = STRBUF_INIT;
|
||||
char *wt_gitdir = get_worktree_git_dir(NULL);
|
||||
|
||||
strbuf_add_real_path(&path, wt_gitdir);
|
||||
strbuf_addstr(&path, "/HEAD");
|
||||
strbuf_read_file(&contents, path.buf, 64);
|
||||
strbuf_stripspace(&contents, NULL);
|
||||
strbuf_strip_suffix(&contents, "\n");
|
||||
|
||||
warning(_("HEAD points to an invalid (or orphaned) reference.\n"
|
||||
"HEAD path: '%s'\n"
|
||||
"HEAD contents: '%s'"),
|
||||
path.buf, contents.buf);
|
||||
strbuf_release(&path);
|
||||
strbuf_release(&contents);
|
||||
free(wt_gitdir);
|
||||
}
|
||||
if (!opts->quiet)
|
||||
warning(_("HEAD points to an invalid (or orphaned) reference.\n"));
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
#include "git-compat-util.h"
|
||||
#include "abspath.h"
|
||||
#include "advice.h"
|
||||
#include "config.h"
|
||||
#include "environment.h"
|
||||
#include "gettext.h"
|
||||
#include "hook.h"
|
||||
#include "path.h"
|
||||
#include "parse.h"
|
||||
#include "path.h"
|
||||
#include "run-command.h"
|
||||
#include "config.h"
|
||||
#include "setup.h"
|
||||
#include "strbuf.h"
|
||||
#include "strmap.h"
|
||||
#include "environment.h"
|
||||
#include "setup.h"
|
||||
|
||||
const char *find_hook(struct repository *r, const char *name)
|
||||
{
|
||||
@@ -52,34 +52,26 @@ const char *find_hook(struct repository *r, const char *name)
|
||||
return path.buf;
|
||||
}
|
||||
|
||||
static void hook_clear(struct hook *h, cb_data_free_fn cb_data_free)
|
||||
void hook_free(void *p, const char *str UNUSED)
|
||||
{
|
||||
struct hook *h = p;
|
||||
|
||||
if (!h)
|
||||
return;
|
||||
|
||||
if (h->kind == HOOK_TRADITIONAL)
|
||||
if (h->kind == HOOK_TRADITIONAL) {
|
||||
free((void *)h->u.traditional.path);
|
||||
else if (h->kind == HOOK_CONFIGURED) {
|
||||
} else if (h->kind == HOOK_CONFIGURED) {
|
||||
free((void *)h->u.configured.friendly_name);
|
||||
free((void *)h->u.configured.command);
|
||||
}
|
||||
|
||||
if (cb_data_free)
|
||||
cb_data_free(h->feed_pipe_cb_data);
|
||||
if (h->data_free && h->feed_pipe_cb_data)
|
||||
h->data_free(h->feed_pipe_cb_data);
|
||||
|
||||
free(h);
|
||||
}
|
||||
|
||||
void hook_list_clear(struct string_list *hooks, cb_data_free_fn cb_data_free)
|
||||
{
|
||||
struct string_list_item *item;
|
||||
|
||||
for_each_string_list_item(item, hooks)
|
||||
hook_clear(item->util, cb_data_free);
|
||||
|
||||
string_list_clear(hooks, 0);
|
||||
}
|
||||
|
||||
/* Helper to detect and add default "traditional" hooks from the hookdir. */
|
||||
static void list_hooks_add_default(struct repository *r, const char *hookname,
|
||||
struct string_list *hook_list,
|
||||
@@ -91,7 +83,7 @@ static void list_hooks_add_default(struct repository *r, const char *hookname,
|
||||
if (!hook_path)
|
||||
return;
|
||||
|
||||
h = xcalloc(1, sizeof(struct hook));
|
||||
CALLOC_ARRAY(h, 1);
|
||||
|
||||
/*
|
||||
* If the hook is to run in a specific dir, a relative path can
|
||||
@@ -100,9 +92,15 @@ static void list_hooks_add_default(struct repository *r, const char *hookname,
|
||||
if (options && options->dir)
|
||||
hook_path = absolute_path(hook_path);
|
||||
|
||||
/* Setup per-hook internal state cb data */
|
||||
if (options && options->feed_pipe_cb_data_alloc)
|
||||
/*
|
||||
* Setup per-hook internal state callback data.
|
||||
* When provided, the alloc/free callbacks are always provided
|
||||
* together, so use them to alloc/free the internal hook state.
|
||||
*/
|
||||
if (options && options->feed_pipe_cb_data_alloc) {
|
||||
h->feed_pipe_cb_data = options->feed_pipe_cb_data_alloc(options->feed_pipe_ctx);
|
||||
h->data_free = options->feed_pipe_cb_data_free;
|
||||
}
|
||||
|
||||
h->kind = HOOK_TRADITIONAL;
|
||||
h->u.traditional.path = xstrdup(hook_path);
|
||||
@@ -110,19 +108,21 @@ static void list_hooks_add_default(struct repository *r, const char *hookname,
|
||||
string_list_append(hook_list, hook_path)->util = h;
|
||||
}
|
||||
|
||||
static void unsorted_string_list_remove(struct string_list *list,
|
||||
const char *str)
|
||||
{
|
||||
struct string_list_item *item = unsorted_string_list_lookup(list, str);
|
||||
if (item)
|
||||
unsorted_string_list_delete_item(list, item - list->items, 0);
|
||||
}
|
||||
/*
|
||||
* Cache entry stored as the .util pointer of string_list items inside the
|
||||
* hook config cache.
|
||||
*/
|
||||
struct hook_config_cache_entry {
|
||||
char *command;
|
||||
enum config_scope scope;
|
||||
bool disabled;
|
||||
};
|
||||
|
||||
/*
|
||||
* Callback struct to collect all hook.* keys in a single config pass.
|
||||
* commands: friendly-name to command map.
|
||||
* event_hooks: event-name to list of friendly-names map.
|
||||
* disabled_hooks: set of friendly-names with hook.name.enabled = false.
|
||||
* disabled_hooks: set of friendly-names with hook.<friendly-name>.enabled = false.
|
||||
*/
|
||||
struct hook_all_config_cb {
|
||||
struct strmap commands;
|
||||
@@ -132,7 +132,7 @@ struct hook_all_config_cb {
|
||||
|
||||
/* repo_config() callback that collects all hook.* configuration in one pass. */
|
||||
static int hook_config_lookup_all(const char *key, const char *value,
|
||||
const struct config_context *ctx UNUSED,
|
||||
const struct config_context *ctx,
|
||||
void *cb_data)
|
||||
{
|
||||
struct hook_all_config_cb *data = cb_data;
|
||||
@@ -156,20 +156,32 @@ static int hook_config_lookup_all(const char *key, const char *value,
|
||||
struct strmap_entry *e;
|
||||
|
||||
strmap_for_each_entry(&data->event_hooks, &iter, e)
|
||||
unsorted_string_list_remove(e->value, hook_name);
|
||||
unsorted_string_list_remove(e->value, hook_name, 0);
|
||||
} else {
|
||||
struct string_list *hooks =
|
||||
strmap_get(&data->event_hooks, value);
|
||||
|
||||
if (!hooks) {
|
||||
hooks = xcalloc(1, sizeof(*hooks));
|
||||
CALLOC_ARRAY(hooks, 1);
|
||||
string_list_init_dup(hooks);
|
||||
strmap_put(&data->event_hooks, value, hooks);
|
||||
}
|
||||
|
||||
/* Re-insert if necessary to preserve last-seen order. */
|
||||
unsorted_string_list_remove(hooks, hook_name);
|
||||
string_list_append(hooks, hook_name);
|
||||
unsorted_string_list_remove(hooks, hook_name, 0);
|
||||
|
||||
if (!ctx->kvi)
|
||||
BUG("hook config callback called without key-value info");
|
||||
|
||||
/*
|
||||
* Stash the config scope in the util pointer for
|
||||
* later retrieval in build_hook_config_map(). This
|
||||
* intermediate struct is transient and never leaves
|
||||
* that function, so we pack the enum value into the
|
||||
* pointer rather than heap-allocating a wrapper.
|
||||
*/
|
||||
string_list_append(hooks, hook_name)->util =
|
||||
(void *)(uintptr_t)ctx->kvi->scope;
|
||||
}
|
||||
} else if (!strcmp(subkey, "command")) {
|
||||
/* Store command overwriting the old value */
|
||||
@@ -186,7 +198,7 @@ static int hook_config_lookup_all(const char *key, const char *value,
|
||||
break;
|
||||
case 1: /* enabled: undo a prior disabled entry */
|
||||
unsorted_string_list_remove(&data->disabled_hooks,
|
||||
hook_name);
|
||||
hook_name, 0);
|
||||
break;
|
||||
default:
|
||||
break; /* ignore unrecognised values */
|
||||
@@ -202,8 +214,10 @@ static int hook_config_lookup_all(const char *key, const char *value,
|
||||
* every item's string is the hook's friendly-name and its util pointer is
|
||||
* the corresponding command string. Both strings are owned by the map.
|
||||
*
|
||||
* Disabled hooks and hooks missing a command are already filtered out at
|
||||
* parse time, so callers can iterate the list directly.
|
||||
* Disabled hooks are kept in the cache with entry->disabled set, so that
|
||||
* "git hook list" can display them. A non-disabled hook missing a command
|
||||
* is fatal; a disabled hook missing a command emits a warning and is kept
|
||||
* in the cache with entry->command = NULL.
|
||||
*/
|
||||
void hook_cache_clear(struct strmap *cache)
|
||||
{
|
||||
@@ -212,7 +226,12 @@ void hook_cache_clear(struct strmap *cache)
|
||||
|
||||
strmap_for_each_entry(cache, &iter, e) {
|
||||
struct string_list *hooks = e->value;
|
||||
string_list_clear(hooks, 1); /* free util (command) pointers */
|
||||
for (size_t i = 0; i < hooks->nr; i++) {
|
||||
struct hook_config_cache_entry *entry = hooks->items[i].util;
|
||||
free(entry->command);
|
||||
free(entry);
|
||||
}
|
||||
string_list_clear(hooks, 0);
|
||||
free(hooks);
|
||||
}
|
||||
strmap_clear(cache, 0);
|
||||
@@ -235,28 +254,39 @@ static void build_hook_config_map(struct repository *r, struct strmap *cache)
|
||||
/* Construct the cache from parsed configs. */
|
||||
strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
|
||||
struct string_list *hook_names = e->value;
|
||||
struct string_list *hooks = xcalloc(1, sizeof(*hooks));
|
||||
struct string_list *hooks;
|
||||
|
||||
CALLOC_ARRAY(hooks, 1);
|
||||
string_list_init_dup(hooks);
|
||||
|
||||
for (size_t i = 0; i < hook_names->nr; i++) {
|
||||
const char *hname = hook_names->items[i].string;
|
||||
enum config_scope scope =
|
||||
(enum config_scope)(uintptr_t)hook_names->items[i].util;
|
||||
struct hook_config_cache_entry *entry;
|
||||
char *command;
|
||||
|
||||
/* filter out disabled hooks */
|
||||
if (unsorted_string_list_lookup(&cb_data.disabled_hooks,
|
||||
hname))
|
||||
continue;
|
||||
bool is_disabled =
|
||||
!!unsorted_string_list_lookup(
|
||||
&cb_data.disabled_hooks, hname);
|
||||
|
||||
command = strmap_get(&cb_data.commands, hname);
|
||||
if (!command)
|
||||
die(_("'hook.%s.command' must be configured or "
|
||||
"'hook.%s.event' must be removed;"
|
||||
" aborting."), hname, hname);
|
||||
if (!command) {
|
||||
if (is_disabled)
|
||||
warning(_("disabled hook '%s' has no "
|
||||
"command configured"), hname);
|
||||
else
|
||||
die(_("'hook.%s.command' must be configured or "
|
||||
"'hook.%s.event' must be removed;"
|
||||
" aborting."), hname, hname);
|
||||
}
|
||||
|
||||
/* util stores the command; owned by the cache. */
|
||||
string_list_append(hooks, hname)->util =
|
||||
xstrdup(command);
|
||||
/* util stores a cache entry; owned by the cache. */
|
||||
CALLOC_ARRAY(entry, 1);
|
||||
entry->command = xstrdup_or_null(command);
|
||||
entry->scope = scope;
|
||||
entry->disabled = is_disabled;
|
||||
string_list_append(hooks, hname)->util = entry;
|
||||
}
|
||||
|
||||
strmap_put(cache, e->key, hooks);
|
||||
@@ -289,7 +319,7 @@ static struct strmap *get_hook_config_cache(struct repository *r)
|
||||
* it just once on the first call.
|
||||
*/
|
||||
if (!r->hook_config_cache) {
|
||||
r->hook_config_cache = xcalloc(1, sizeof(*cache));
|
||||
CALLOC_ARRAY(r->hook_config_cache, 1);
|
||||
strmap_init(r->hook_config_cache);
|
||||
build_hook_config_map(r, r->hook_config_cache);
|
||||
}
|
||||
@@ -297,9 +327,9 @@ static struct strmap *get_hook_config_cache(struct repository *r)
|
||||
} else {
|
||||
/*
|
||||
* Out-of-repo calls (no gitdir) allocate and return a temporary
|
||||
* map cache which gets free'd immediately by the caller.
|
||||
* cache which gets freed immediately by the caller.
|
||||
*/
|
||||
cache = xcalloc(1, sizeof(*cache));
|
||||
CALLOC_ARRAY(cache, 1);
|
||||
strmap_init(cache);
|
||||
build_hook_config_map(r, cache);
|
||||
}
|
||||
@@ -318,17 +348,28 @@ static void list_hooks_add_configured(struct repository *r,
|
||||
/* Iterate through configured hooks and initialize internal states */
|
||||
for (size_t i = 0; configured_hooks && i < configured_hooks->nr; i++) {
|
||||
const char *friendly_name = configured_hooks->items[i].string;
|
||||
const char *command = configured_hooks->items[i].util;
|
||||
struct hook *hook = xcalloc(1, sizeof(struct hook));
|
||||
struct hook_config_cache_entry *entry = configured_hooks->items[i].util;
|
||||
struct hook *hook;
|
||||
|
||||
if (options && options->feed_pipe_cb_data_alloc)
|
||||
CALLOC_ARRAY(hook, 1);
|
||||
|
||||
/*
|
||||
* When provided, the alloc/free callbacks are always provided
|
||||
* together, so use them to alloc/free the internal hook state.
|
||||
*/
|
||||
if (options && options->feed_pipe_cb_data_alloc) {
|
||||
hook->feed_pipe_cb_data =
|
||||
options->feed_pipe_cb_data_alloc(
|
||||
options->feed_pipe_ctx);
|
||||
hook->data_free = options->feed_pipe_cb_data_free;
|
||||
}
|
||||
|
||||
hook->kind = HOOK_CONFIGURED;
|
||||
hook->u.configured.friendly_name = xstrdup(friendly_name);
|
||||
hook->u.configured.command = xstrdup(command);
|
||||
hook->u.configured.command =
|
||||
entry->command ? xstrdup(entry->command) : NULL;
|
||||
hook->u.configured.scope = entry->scope;
|
||||
hook->u.configured.disabled = entry->disabled;
|
||||
|
||||
string_list_append(list, friendly_name)->util = hook;
|
||||
}
|
||||
@@ -351,7 +392,7 @@ struct string_list *list_hooks(struct repository *r, const char *hookname,
|
||||
if (!hookname)
|
||||
BUG("null hookname was provided to hook_list()!");
|
||||
|
||||
hook_head = xmalloc(sizeof(struct string_list));
|
||||
CALLOC_ARRAY(hook_head, 1);
|
||||
string_list_init_dup(hook_head);
|
||||
|
||||
/* Add hooks from the config, e.g. hook.myhook.event = pre-commit */
|
||||
@@ -366,8 +407,17 @@ struct string_list *list_hooks(struct repository *r, const char *hookname,
|
||||
int hook_exists(struct repository *r, const char *name)
|
||||
{
|
||||
struct string_list *hooks = list_hooks(r, name, NULL);
|
||||
int exists = hooks->nr > 0;
|
||||
hook_list_clear(hooks, NULL);
|
||||
int exists = 0;
|
||||
|
||||
for (size_t i = 0; i < hooks->nr; i++) {
|
||||
struct hook *h = hooks->items[i].util;
|
||||
if (h->kind == HOOK_TRADITIONAL ||
|
||||
!h->u.configured.disabled) {
|
||||
exists = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
string_list_clear_func(hooks, hook_free);
|
||||
free(hooks);
|
||||
return exists;
|
||||
}
|
||||
@@ -381,10 +431,11 @@ static int pick_next_hook(struct child_process *cp,
|
||||
struct string_list *hook_list = hook_cb->hook_command_list;
|
||||
struct hook *h;
|
||||
|
||||
if (hook_cb->hook_to_run_index >= hook_list->nr)
|
||||
return 0;
|
||||
|
||||
h = hook_list->items[hook_cb->hook_to_run_index++].util;
|
||||
do {
|
||||
if (hook_cb->hook_to_run_index >= hook_list->nr)
|
||||
return 0;
|
||||
h = hook_list->items[hook_cb->hook_to_run_index++].util;
|
||||
} while (h->kind == HOOK_CONFIGURED && h->u.configured.disabled);
|
||||
|
||||
cp->no_stdin = 1;
|
||||
strvec_pushv(&cp->env, hook_cb->options->env.v);
|
||||
@@ -414,7 +465,11 @@ static int pick_next_hook(struct child_process *cp,
|
||||
} else if (h->kind == HOOK_CONFIGURED) {
|
||||
/* to enable oneliners, let config-specified hooks run in shell. */
|
||||
cp->use_shell = true;
|
||||
if (!h->u.configured.command)
|
||||
BUG("non-disabled HOOK_CONFIGURED hook has no command");
|
||||
strvec_push(&cp->args, h->u.configured.command);
|
||||
} else {
|
||||
BUG("unknown hook kind");
|
||||
}
|
||||
|
||||
if (!cp->args.nr)
|
||||
@@ -501,8 +556,7 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
|
||||
* Ensure cb_data copy and free functions are either provided together,
|
||||
* or neither one is provided.
|
||||
*/
|
||||
if ((options->feed_pipe_cb_data_alloc && !options->feed_pipe_cb_data_free) ||
|
||||
(!options->feed_pipe_cb_data_alloc && options->feed_pipe_cb_data_free))
|
||||
if (!options->feed_pipe_cb_data_alloc != !options->feed_pipe_cb_data_free)
|
||||
BUG("feed_pipe_cb_data_alloc and feed_pipe_cb_data_free must be set together");
|
||||
|
||||
if (options->invoked_hook)
|
||||
@@ -518,7 +572,7 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
|
||||
run_processes_parallel(&opts);
|
||||
ret = cb_data.rc;
|
||||
cleanup:
|
||||
hook_list_clear(cb_data.hook_command_list, options->feed_pipe_cb_data_free);
|
||||
string_list_clear_func(cb_data.hook_command_list, hook_free);
|
||||
free(cb_data.hook_command_list);
|
||||
run_hooks_opt_clear(options);
|
||||
return ret;
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
#ifndef HOOK_H
|
||||
#define HOOK_H
|
||||
#include "strvec.h"
|
||||
#include "config.h"
|
||||
#include "run-command.h"
|
||||
#include "string-list.h"
|
||||
#include "strmap.h"
|
||||
#include "strvec.h"
|
||||
|
||||
struct repository;
|
||||
|
||||
typedef void (*hook_data_free_fn)(void *data);
|
||||
typedef void *(*hook_data_alloc_fn)(void *init_ctx);
|
||||
|
||||
/**
|
||||
* Represents a hook command to be run.
|
||||
* Hooks can be:
|
||||
* 1. "traditional" (found in the hooks directory)
|
||||
* 2. "configured" (defined in Git's configuration via hook.<name>.event).
|
||||
* 2. "configured" (defined in Git's configuration via hook.<friendly-name>.event).
|
||||
* The 'kind' field determines which part of the union 'u' is valid.
|
||||
*/
|
||||
struct hook {
|
||||
@@ -26,6 +30,8 @@ struct hook {
|
||||
struct {
|
||||
const char *friendly_name;
|
||||
const char *command;
|
||||
enum config_scope scope;
|
||||
bool disabled;
|
||||
} configured;
|
||||
} u;
|
||||
|
||||
@@ -41,13 +47,17 @@ struct hook {
|
||||
* Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it.
|
||||
*/
|
||||
void *feed_pipe_cb_data;
|
||||
|
||||
/**
|
||||
* Callback to free `feed_pipe_cb_data`.
|
||||
*
|
||||
* It is called automatically and points to the `feed_pipe_cb_data_free`
|
||||
* provided via the `run_hook_opt` parameter.
|
||||
*/
|
||||
hook_data_free_fn data_free;
|
||||
};
|
||||
|
||||
typedef void (*cb_data_free_fn)(void *data);
|
||||
typedef void *(*cb_data_alloc_fn)(void *init_ctx);
|
||||
|
||||
struct run_hooks_opt
|
||||
{
|
||||
struct run_hooks_opt {
|
||||
/* Environment vars to be set for each hook */
|
||||
struct strvec env;
|
||||
|
||||
@@ -132,14 +142,14 @@ struct run_hooks_opt
|
||||
*
|
||||
* The `feed_pipe_ctx` pointer can be used to pass initialization data.
|
||||
*/
|
||||
cb_data_alloc_fn feed_pipe_cb_data_alloc;
|
||||
hook_data_alloc_fn feed_pipe_cb_data_alloc;
|
||||
|
||||
/**
|
||||
* Called to free the memory initialized by `feed_pipe_cb_data_alloc`.
|
||||
*
|
||||
* Must always be provided when `feed_pipe_cb_data_alloc` is provided.
|
||||
*/
|
||||
cb_data_free_fn feed_pipe_cb_data_free;
|
||||
hook_data_free_fn feed_pipe_cb_data_free;
|
||||
};
|
||||
|
||||
#define RUN_HOOKS_OPT_INIT { \
|
||||
@@ -186,10 +196,10 @@ struct string_list *list_hooks(struct repository *r, const char *hookname,
|
||||
struct run_hooks_opt *options);
|
||||
|
||||
/**
|
||||
* Frees the memory allocated for the hook list, including the `struct hook`
|
||||
* items and their internal state.
|
||||
* Frees a struct hook stored as the util pointer of a string_list_item.
|
||||
* Suitable for use as a string_list_clear_func_t callback.
|
||||
*/
|
||||
void hook_list_clear(struct string_list *hooks, cb_data_free_fn cb_data_free);
|
||||
void hook_free(void *p, const char *str);
|
||||
|
||||
/**
|
||||
* Frees the hook configuration cache stored in `struct repository`.
|
||||
|
||||
+43
@@ -11,6 +11,7 @@
|
||||
#include "list-objects.h"
|
||||
#include "object.h"
|
||||
#include "oid-array.h"
|
||||
#include "path.h"
|
||||
#include "prio-queue.h"
|
||||
#include "repository.h"
|
||||
#include "revision.h"
|
||||
@@ -62,6 +63,8 @@ struct path_walk_context {
|
||||
*/
|
||||
struct prio_queue path_stack;
|
||||
struct strset path_stack_pushed;
|
||||
|
||||
unsigned exact_pathspecs:1;
|
||||
};
|
||||
|
||||
static int compare_by_type(const void *one, const void *two, void *cb_data)
|
||||
@@ -206,6 +209,33 @@ static int add_tree_entries(struct path_walk_context *ctx,
|
||||
match != MATCHED)
|
||||
continue;
|
||||
}
|
||||
if (ctx->revs->prune_data.nr && ctx->exact_pathspecs) {
|
||||
struct pathspec *pd = &ctx->revs->prune_data;
|
||||
bool found = false;
|
||||
int did_strip_suffix = strbuf_strip_suffix(&path, "/");
|
||||
|
||||
|
||||
for (int i = 0; i < pd->nr; i++) {
|
||||
struct pathspec_item *item = &pd->items[i];
|
||||
|
||||
/*
|
||||
* Continue if either is a directory prefix
|
||||
* of the other.
|
||||
*/
|
||||
if (dir_prefix(path.buf, item->match) ||
|
||||
dir_prefix(item->match, path.buf)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (did_strip_suffix)
|
||||
strbuf_addch(&path, '/');
|
||||
|
||||
/* Skip paths that do not match the prefix. */
|
||||
if (!found)
|
||||
continue;
|
||||
}
|
||||
|
||||
add_path_to_list(ctx, path.buf, type, &entry.oid,
|
||||
!(o->flags & UNINTERESTING));
|
||||
@@ -274,6 +304,13 @@ static int walk_path(struct path_walk_context *ctx,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (list->type == OBJ_BLOB &&
|
||||
ctx->revs->prune_data.nr &&
|
||||
!match_pathspec(ctx->repo->index, &ctx->revs->prune_data,
|
||||
path, strlen(path), 0,
|
||||
NULL, 0))
|
||||
return 0;
|
||||
|
||||
/* Evaluate function pointer on this data, if requested. */
|
||||
if ((list->type == OBJ_TREE && ctx->info->trees) ||
|
||||
(list->type == OBJ_BLOB && ctx->info->blobs) ||
|
||||
@@ -481,6 +518,12 @@ int walk_objects_by_path(struct path_walk_info *info)
|
||||
if (info->tags)
|
||||
info->revs->tag_objects = 1;
|
||||
|
||||
if (ctx.revs->prune_data.nr) {
|
||||
if (!ctx.revs->prune_data.has_wildcard &&
|
||||
!ctx.revs->prune_data.magic)
|
||||
ctx.exact_pathspecs = 1;
|
||||
}
|
||||
|
||||
/* Insert a single list for the root tree into the paths. */
|
||||
CALLOC_ARRAY(root_tree_list, 1);
|
||||
root_tree_list->type = OBJ_TREE;
|
||||
|
||||
@@ -56,7 +56,7 @@ static void strbuf_cleanup_path(struct strbuf *sb)
|
||||
strbuf_remove(sb, 0, path - sb->buf);
|
||||
}
|
||||
|
||||
static int dir_prefix(const char *buf, const char *dir)
|
||||
int dir_prefix(const char *buf, const char *dir)
|
||||
{
|
||||
size_t len = strlen(dir);
|
||||
return !strncmp(buf, dir, len) &&
|
||||
|
||||
@@ -112,6 +112,12 @@ const char *repo_submodule_path_replace(struct repository *repo,
|
||||
const char *fmt, ...)
|
||||
__attribute__((format (printf, 4, 5)));
|
||||
|
||||
/*
|
||||
* Given a directory name 'dir' (not ending with a trailing '/'),
|
||||
* determine if 'buf' is equal to 'dir' or has prefix 'dir'+'/'.
|
||||
*/
|
||||
int dir_prefix(const char *buf, const char *dir);
|
||||
|
||||
void report_linked_checkout_garbage(struct repository *r);
|
||||
|
||||
/*
|
||||
|
||||
@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
|
||||
if (!commit->object.parsed)
|
||||
parse_object(the_repository, &commit->object.oid);
|
||||
|
||||
if (starts_with(placeholder, "(count)")) {
|
||||
if (!c->pretty_ctx->rev)
|
||||
die(_("%s is not supported by this command"), "%(count)");
|
||||
strbuf_addf(sb, "%0*d", decimal_width(c->pretty_ctx->rev->total),
|
||||
c->pretty_ctx->rev->nr);
|
||||
return 7;
|
||||
}
|
||||
|
||||
if (starts_with(placeholder, "(total)")) {
|
||||
if (!c->pretty_ctx->rev)
|
||||
die(_("%s is not supported by this command"), "%(total)");
|
||||
strbuf_addf(sb, "%d", c->pretty_ctx->rev->total);
|
||||
return 7;
|
||||
}
|
||||
|
||||
switch (placeholder[0]) {
|
||||
case 'H': /* commit hash */
|
||||
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
|
||||
|
||||
@@ -2595,7 +2595,8 @@ static int transaction_hook_feed_stdin(int hook_stdin_fd, void *pp_cb, void *pp_
|
||||
|
||||
static void *transaction_feed_cb_data_alloc(void *feed_pipe_ctx UNUSED)
|
||||
{
|
||||
struct transaction_feed_cb_data *data = xmalloc(sizeof(*data));
|
||||
struct transaction_feed_cb_data *data;
|
||||
CALLOC_ARRAY(data, 1);
|
||||
strbuf_init(&data->buf, 0);
|
||||
data->index = 0;
|
||||
return data;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "refs.h"
|
||||
#include "replay.h"
|
||||
#include "revision.h"
|
||||
#include "sequencer.h"
|
||||
#include "strmap.h"
|
||||
#include "tree.h"
|
||||
|
||||
@@ -17,6 +18,11 @@
|
||||
*/
|
||||
#define the_repository DO_NOT_USE_THE_REPOSITORY
|
||||
|
||||
enum replay_mode {
|
||||
REPLAY_MODE_PICK,
|
||||
REPLAY_MODE_REVERT,
|
||||
};
|
||||
|
||||
static const char *short_commit_name(struct repository *repo,
|
||||
struct commit *commit)
|
||||
{
|
||||
@@ -50,15 +56,37 @@ static char *get_author(const char *message)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void generate_revert_message(struct strbuf *msg,
|
||||
struct commit *commit,
|
||||
struct repository *repo)
|
||||
{
|
||||
const char *out_enc = get_commit_output_encoding();
|
||||
const char *message = repo_logmsg_reencode(repo, commit, NULL, out_enc);
|
||||
const char *subject_start;
|
||||
int subject_len;
|
||||
char *subject;
|
||||
|
||||
subject_len = find_commit_subject(message, &subject_start);
|
||||
subject = xmemdupz(subject_start, subject_len);
|
||||
|
||||
sequencer_format_revert_message(repo, subject, commit,
|
||||
commit->parents ? commit->parents->item : NULL,
|
||||
false, msg);
|
||||
|
||||
free(subject);
|
||||
repo_unuse_commit_buffer(repo, commit, message);
|
||||
}
|
||||
|
||||
static struct commit *create_commit(struct repository *repo,
|
||||
struct tree *tree,
|
||||
struct commit *based_on,
|
||||
struct commit *parent)
|
||||
struct commit *parent,
|
||||
enum replay_mode mode)
|
||||
{
|
||||
struct object_id ret;
|
||||
struct object *obj = NULL;
|
||||
struct commit_list *parents = NULL;
|
||||
char *author;
|
||||
char *author = NULL;
|
||||
char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
|
||||
struct commit_extra_header *extra = NULL;
|
||||
struct strbuf msg = STRBUF_INIT;
|
||||
@@ -70,9 +98,16 @@ static struct commit *create_commit(struct repository *repo,
|
||||
|
||||
commit_list_insert(parent, &parents);
|
||||
extra = read_commit_extra_headers(based_on, exclude_gpgsig);
|
||||
find_commit_subject(message, &orig_message);
|
||||
strbuf_addstr(&msg, orig_message);
|
||||
author = get_author(message);
|
||||
if (mode == REPLAY_MODE_REVERT) {
|
||||
generate_revert_message(&msg, based_on, repo);
|
||||
/* For revert, use current user as author (NULL = use default) */
|
||||
} else if (mode == REPLAY_MODE_PICK) {
|
||||
find_commit_subject(message, &orig_message);
|
||||
strbuf_addstr(&msg, orig_message);
|
||||
author = get_author(message);
|
||||
} else {
|
||||
BUG("unexpected replay mode %d", mode);
|
||||
}
|
||||
reset_ident_date();
|
||||
if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
|
||||
&ret, author, NULL, sign_commit, extra)) {
|
||||
@@ -153,11 +188,35 @@ static void get_ref_information(struct repository *repo,
|
||||
}
|
||||
}
|
||||
|
||||
static void set_up_branch_mode(struct repository *repo,
|
||||
char **branch_name,
|
||||
const char *option_name,
|
||||
struct ref_info *rinfo,
|
||||
struct commit **onto)
|
||||
{
|
||||
struct object_id oid;
|
||||
char *fullname = NULL;
|
||||
|
||||
if (repo_dwim_ref(repo, *branch_name, strlen(*branch_name),
|
||||
&oid, &fullname, 0) == 1) {
|
||||
free(*branch_name);
|
||||
*branch_name = fullname;
|
||||
} else {
|
||||
die(_("argument to %s must be a reference"), option_name);
|
||||
}
|
||||
*onto = peel_committish(repo, *branch_name, option_name);
|
||||
if (rinfo->positive_refexprs > 1)
|
||||
die(_("'%s' cannot be used with multiple revision ranges "
|
||||
"because the ordering would be ill-defined"),
|
||||
option_name);
|
||||
}
|
||||
|
||||
static void set_up_replay_mode(struct repository *repo,
|
||||
struct rev_cmdline_info *cmd_info,
|
||||
const char *onto_name,
|
||||
bool *detached_head,
|
||||
char **advance_name,
|
||||
char **revert_name,
|
||||
struct commit **onto,
|
||||
struct strset **update_refs)
|
||||
{
|
||||
@@ -172,9 +231,6 @@ static void set_up_replay_mode(struct repository *repo,
|
||||
if (!rinfo.positive_refexprs)
|
||||
die(_("need some commits to replay"));
|
||||
|
||||
if (!onto_name == !*advance_name)
|
||||
BUG("one and only one of onto_name and *advance_name must be given");
|
||||
|
||||
if (onto_name) {
|
||||
*onto = peel_committish(repo, onto_name, "--onto");
|
||||
if (rinfo.positive_refexprs <
|
||||
@@ -183,23 +239,12 @@ static void set_up_replay_mode(struct repository *repo,
|
||||
*update_refs = xcalloc(1, sizeof(**update_refs));
|
||||
**update_refs = rinfo.positive_refs;
|
||||
memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
|
||||
} else if (*advance_name) {
|
||||
set_up_branch_mode(repo, advance_name, "--advance", &rinfo, onto);
|
||||
} else if (*revert_name) {
|
||||
set_up_branch_mode(repo, revert_name, "--revert", &rinfo, onto);
|
||||
} else {
|
||||
struct object_id oid;
|
||||
char *fullname = NULL;
|
||||
|
||||
if (!*advance_name)
|
||||
BUG("expected either onto_name or *advance_name in this function");
|
||||
|
||||
if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
|
||||
&oid, &fullname, 0) == 1) {
|
||||
free(*advance_name);
|
||||
*advance_name = fullname;
|
||||
} else {
|
||||
die(_("argument to --advance must be a reference"));
|
||||
}
|
||||
*onto = peel_committish(repo, *advance_name, "--advance");
|
||||
if (rinfo.positive_refexprs > 1)
|
||||
die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
|
||||
BUG("expected one of onto_name, *advance_name, or *revert_name");
|
||||
}
|
||||
strset_clear(&rinfo.negative_refs);
|
||||
strset_clear(&rinfo.positive_refs);
|
||||
@@ -220,7 +265,8 @@ static struct commit *pick_regular_commit(struct repository *repo,
|
||||
kh_oid_map_t *replayed_commits,
|
||||
struct commit *onto,
|
||||
struct merge_options *merge_opt,
|
||||
struct merge_result *result)
|
||||
struct merge_result *result,
|
||||
enum replay_mode mode)
|
||||
{
|
||||
struct commit *base, *replayed_base;
|
||||
struct tree *pickme_tree, *base_tree, *replayed_base_tree;
|
||||
@@ -232,25 +278,45 @@ static struct commit *pick_regular_commit(struct repository *repo,
|
||||
pickme_tree = repo_get_commit_tree(repo, pickme);
|
||||
base_tree = repo_get_commit_tree(repo, base);
|
||||
|
||||
merge_opt->branch1 = short_commit_name(repo, replayed_base);
|
||||
merge_opt->branch2 = short_commit_name(repo, pickme);
|
||||
merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
|
||||
if (mode == REPLAY_MODE_PICK) {
|
||||
/* Cherry-pick: normal order */
|
||||
merge_opt->branch1 = short_commit_name(repo, replayed_base);
|
||||
merge_opt->branch2 = short_commit_name(repo, pickme);
|
||||
merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
|
||||
|
||||
merge_incore_nonrecursive(merge_opt,
|
||||
base_tree,
|
||||
replayed_base_tree,
|
||||
pickme_tree,
|
||||
result);
|
||||
merge_incore_nonrecursive(merge_opt,
|
||||
base_tree,
|
||||
replayed_base_tree,
|
||||
pickme_tree,
|
||||
result);
|
||||
|
||||
free((char*)merge_opt->ancestor);
|
||||
free((char *)merge_opt->ancestor);
|
||||
} else if (mode == REPLAY_MODE_REVERT) {
|
||||
/* Revert: swap base and pickme to reverse the diff */
|
||||
const char *pickme_name = short_commit_name(repo, pickme);
|
||||
merge_opt->branch1 = short_commit_name(repo, replayed_base);
|
||||
merge_opt->branch2 = xstrfmt("parent of %s", pickme_name);
|
||||
merge_opt->ancestor = pickme_name;
|
||||
|
||||
merge_incore_nonrecursive(merge_opt,
|
||||
pickme_tree,
|
||||
replayed_base_tree,
|
||||
base_tree,
|
||||
result);
|
||||
|
||||
free((char *)merge_opt->branch2);
|
||||
} else {
|
||||
BUG("unexpected replay mode %d", mode);
|
||||
}
|
||||
merge_opt->ancestor = NULL;
|
||||
merge_opt->branch2 = NULL;
|
||||
if (!result->clean)
|
||||
return NULL;
|
||||
/* Drop commits that become empty */
|
||||
if (oideq(&replayed_base_tree->object.oid, &result->tree->object.oid) &&
|
||||
!oideq(&pickme_tree->object.oid, &base_tree->object.oid))
|
||||
return replayed_base;
|
||||
return create_commit(repo, result->tree, pickme, replayed_base);
|
||||
return create_commit(repo, result->tree, pickme, replayed_base, mode);
|
||||
}
|
||||
|
||||
void replay_result_release(struct replay_result *result)
|
||||
@@ -287,11 +353,16 @@ int replay_revisions(struct rev_info *revs,
|
||||
};
|
||||
bool detached_head;
|
||||
char *advance;
|
||||
char *revert;
|
||||
enum replay_mode mode = REPLAY_MODE_PICK;
|
||||
int ret;
|
||||
|
||||
advance = xstrdup_or_null(opts->advance);
|
||||
revert = xstrdup_or_null(opts->revert);
|
||||
if (revert)
|
||||
mode = REPLAY_MODE_REVERT;
|
||||
set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto,
|
||||
&detached_head, &advance, &onto, &update_refs);
|
||||
&detached_head, &advance, &revert, &onto, &update_refs);
|
||||
|
||||
/* FIXME: Should allow replaying commits with the first as a root commit */
|
||||
|
||||
@@ -315,7 +386,8 @@ int replay_revisions(struct rev_info *revs,
|
||||
die(_("replaying merge commits is not supported yet!"));
|
||||
|
||||
last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
|
||||
onto, &merge_opt, &result);
|
||||
mode == REPLAY_MODE_REVERT ? last_commit : onto,
|
||||
&merge_opt, &result, mode);
|
||||
if (!last_commit)
|
||||
break;
|
||||
|
||||
@@ -327,7 +399,7 @@ int replay_revisions(struct rev_info *revs,
|
||||
kh_value(replayed_commits, pos) = last_commit;
|
||||
|
||||
/* Update any necessary branches */
|
||||
if (advance)
|
||||
if (advance || revert)
|
||||
continue;
|
||||
|
||||
for (decoration = get_name_decoration(&commit->object);
|
||||
@@ -361,11 +433,13 @@ int replay_revisions(struct rev_info *revs,
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* In --advance mode, advance the target ref */
|
||||
if (advance)
|
||||
replay_result_queue_update(out, advance,
|
||||
/* In --advance or --revert mode, update the target ref */
|
||||
if (advance || revert) {
|
||||
const char *ref = advance ? advance : revert;
|
||||
replay_result_queue_update(out, ref,
|
||||
&onto->object.oid,
|
||||
&last_commit->object.oid);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
@@ -377,5 +451,6 @@ out:
|
||||
kh_destroy_oid_map(replayed_commits);
|
||||
merge_finalize(&merge_opt, &result);
|
||||
free(advance);
|
||||
free(revert);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ struct replay_revisions_options {
|
||||
/*
|
||||
* Starting point at which to create the new commits; must be a branch
|
||||
* name. The branch will be updated to point to the rewritten commits.
|
||||
* This option is mutually exclusive with `onto`.
|
||||
* This option is mutually exclusive with `onto` and `revert`.
|
||||
*/
|
||||
const char *advance;
|
||||
|
||||
@@ -22,7 +22,14 @@ struct replay_revisions_options {
|
||||
* committish. References pointing at decendants of `onto` will be
|
||||
* updated to point to the new commits.
|
||||
*/
|
||||
const char *onto;
|
||||
const char *onto;
|
||||
|
||||
/*
|
||||
* Starting point at which to create revert commits; must be a branch
|
||||
* name. The branch will be updated to point to the revert commits.
|
||||
* This option is mutually exclusive with `onto` and `advance`.
|
||||
*/
|
||||
const char *revert;
|
||||
|
||||
/*
|
||||
* Update branches that point at commits in the given revision range.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "commit.h"
|
||||
#include "grep.h"
|
||||
#include "notes.h"
|
||||
#include "object-name.h"
|
||||
#include "oidset.h"
|
||||
#include "pretty.h"
|
||||
#include "diff.h"
|
||||
|
||||
+46
-32
@@ -2211,15 +2211,16 @@ static int should_edit(struct replay_opts *opts) {
|
||||
return opts->edit;
|
||||
}
|
||||
|
||||
static void refer_to_commit(struct replay_opts *opts,
|
||||
struct strbuf *msgbuf, struct commit *commit)
|
||||
static void refer_to_commit(struct repository *r, struct strbuf *msgbuf,
|
||||
const struct commit *commit,
|
||||
bool use_commit_reference)
|
||||
{
|
||||
if (opts->commit_use_reference) {
|
||||
if (use_commit_reference) {
|
||||
struct pretty_print_context ctx = {
|
||||
.abbrev = DEFAULT_ABBREV,
|
||||
.date_mode.type = DATE_SHORT,
|
||||
};
|
||||
repo_format_commit_message(the_repository, commit,
|
||||
repo_format_commit_message(r, commit,
|
||||
"%h (%s, %ad)", msgbuf, &ctx);
|
||||
} else {
|
||||
strbuf_addstr(msgbuf, oid_to_hex(&commit->object.oid));
|
||||
@@ -2369,38 +2370,14 @@ static int do_pick_commit(struct repository *r,
|
||||
*/
|
||||
|
||||
if (command == TODO_REVERT) {
|
||||
const char *orig_subject;
|
||||
|
||||
base = commit;
|
||||
base_label = msg.label;
|
||||
next = parent;
|
||||
next_label = msg.parent_label;
|
||||
if (opts->commit_use_reference) {
|
||||
strbuf_commented_addf(&ctx->message, comment_line_str,
|
||||
"*** SAY WHY WE ARE REVERTING ON THE TITLE LINE ***");
|
||||
} else if (skip_prefix(msg.subject, "Revert \"", &orig_subject) &&
|
||||
/*
|
||||
* We don't touch pre-existing repeated reverts, because
|
||||
* theoretically these can be nested arbitrarily deeply,
|
||||
* thus requiring excessive complexity to deal with.
|
||||
*/
|
||||
!starts_with(orig_subject, "Revert \"")) {
|
||||
strbuf_addstr(&ctx->message, "Reapply \"");
|
||||
strbuf_addstr(&ctx->message, orig_subject);
|
||||
strbuf_addstr(&ctx->message, "\n");
|
||||
} else {
|
||||
strbuf_addstr(&ctx->message, "Revert \"");
|
||||
strbuf_addstr(&ctx->message, msg.subject);
|
||||
strbuf_addstr(&ctx->message, "\"\n");
|
||||
}
|
||||
strbuf_addstr(&ctx->message, "\nThis reverts commit ");
|
||||
refer_to_commit(opts, &ctx->message, commit);
|
||||
|
||||
if (commit->parents && commit->parents->next) {
|
||||
strbuf_addstr(&ctx->message, ", reversing\nchanges made to ");
|
||||
refer_to_commit(opts, &ctx->message, parent);
|
||||
}
|
||||
strbuf_addstr(&ctx->message, ".\n");
|
||||
sequencer_format_revert_message(r, msg.subject, commit,
|
||||
parent,
|
||||
opts->commit_use_reference,
|
||||
&ctx->message);
|
||||
} else {
|
||||
const char *p;
|
||||
|
||||
@@ -5628,6 +5605,43 @@ out:
|
||||
return res;
|
||||
}
|
||||
|
||||
void sequencer_format_revert_message(struct repository *r,
|
||||
const char *subject,
|
||||
const struct commit *commit,
|
||||
const struct commit *parent,
|
||||
bool use_commit_reference,
|
||||
struct strbuf *message)
|
||||
{
|
||||
const char *orig_subject;
|
||||
|
||||
if (use_commit_reference) {
|
||||
strbuf_commented_addf(message, comment_line_str,
|
||||
"*** SAY WHY WE ARE REVERTING ON THE TITLE LINE ***");
|
||||
} else if (skip_prefix(subject, "Revert \"", &orig_subject) &&
|
||||
/*
|
||||
* We don't touch pre-existing repeated reverts, because
|
||||
* theoretically these can be nested arbitrarily deeply,
|
||||
* thus requiring excessive complexity to deal with.
|
||||
*/
|
||||
!starts_with(orig_subject, "Revert \"")) {
|
||||
strbuf_addstr(message, "Reapply \"");
|
||||
strbuf_addstr(message, orig_subject);
|
||||
strbuf_addstr(message, "\n");
|
||||
} else {
|
||||
strbuf_addstr(message, "Revert \"");
|
||||
strbuf_addstr(message, subject);
|
||||
strbuf_addstr(message, "\"\n");
|
||||
}
|
||||
strbuf_addstr(message, "\nThis reverts commit ");
|
||||
refer_to_commit(r, message, commit, use_commit_reference);
|
||||
|
||||
if (commit->parents && commit->parents->next) {
|
||||
strbuf_addstr(message, ", reversing\nchanges made to ");
|
||||
refer_to_commit(r, message, parent, use_commit_reference);
|
||||
}
|
||||
strbuf_addstr(message, ".\n");
|
||||
}
|
||||
|
||||
void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
|
||||
{
|
||||
unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
|
||||
|
||||
+13
@@ -274,4 +274,17 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)
|
||||
*/
|
||||
int sequencer_get_update_refs_state(const char *wt_dir, struct string_list *refs);
|
||||
|
||||
/*
|
||||
* Format a revert commit message with appropriate 'Revert "<subject>"' or
|
||||
* 'Reapply "<subject>"' prefix and 'This reverts commit <ref>.' body.
|
||||
* When use_commit_reference is set, <ref> is an abbreviated hash with
|
||||
* subject and date; otherwise the full hex hash is used.
|
||||
*/
|
||||
void sequencer_format_revert_message(struct repository *r,
|
||||
const char *subject,
|
||||
const struct commit *commit,
|
||||
const struct commit *parent,
|
||||
bool use_commit_reference,
|
||||
struct strbuf *message);
|
||||
|
||||
#endif /* SEQUENCER_H */
|
||||
|
||||
@@ -281,6 +281,15 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_
|
||||
list->nr--;
|
||||
}
|
||||
|
||||
void unsorted_string_list_remove(struct string_list *list, const char *str,
|
||||
int free_util)
|
||||
{
|
||||
struct string_list_item *item = unsorted_string_list_lookup(list, str);
|
||||
if (item)
|
||||
unsorted_string_list_delete_item(list, item - list->items,
|
||||
free_util);
|
||||
}
|
||||
|
||||
/*
|
||||
* append a substring [p..end] to list; return number of things it
|
||||
* appended to the list.
|
||||
|
||||
@@ -265,6 +265,14 @@ struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
|
||||
*/
|
||||
void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);
|
||||
|
||||
/**
|
||||
* Remove the first item matching `str` from an unsorted string_list.
|
||||
* No-op if `str` is not found. If `free_util` is non-zero, the `util`
|
||||
* pointer of the removed item is freed before deletion.
|
||||
*/
|
||||
void unsorted_string_list_remove(struct string_list *list, const char *str,
|
||||
int free_util);
|
||||
|
||||
/**
|
||||
* Split string into substrings on characters in `delim` and append the
|
||||
* substrings to `list`. The input string is not modified.
|
||||
|
||||
+136
-31
@@ -25,18 +25,47 @@ test_expect_success 'git hook usage' '
|
||||
test_expect_code 129 git hook &&
|
||||
test_expect_code 129 git hook run &&
|
||||
test_expect_code 129 git hook run -h &&
|
||||
test_expect_code 129 git hook list -h &&
|
||||
test_expect_code 129 git hook run --unknown 2>err &&
|
||||
test_expect_code 129 git hook list &&
|
||||
test_expect_code 129 git hook list -h &&
|
||||
grep "unknown option" err
|
||||
'
|
||||
|
||||
test_expect_success 'git hook list: unknown hook name is rejected' '
|
||||
test_must_fail git hook list prereceive 2>err &&
|
||||
test_grep "unknown hook event" err
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run: unknown hook name is rejected' '
|
||||
test_must_fail git hook run prereceive 2>err &&
|
||||
test_grep "unknown hook event" err
|
||||
'
|
||||
|
||||
test_expect_success 'git hook list: known hook name is accepted' '
|
||||
test_must_fail git hook list pre-receive 2>err &&
|
||||
test_grep ! "unknown hook event" err
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run: known hook name is accepted' '
|
||||
git hook run --ignore-missing pre-receive 2>err &&
|
||||
test_grep ! "unknown hook event" err
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run: --allow-unknown-hook-name overrides rejection' '
|
||||
git hook run --allow-unknown-hook-name --ignore-missing custom-hook 2>err &&
|
||||
test_grep ! "unknown hook event" err
|
||||
'
|
||||
|
||||
test_expect_success 'git hook list: --allow-unknown-hook-name overrides rejection' '
|
||||
test_must_fail git hook list --allow-unknown-hook-name custom-hook 2>err &&
|
||||
test_grep ! "unknown hook event" err
|
||||
'
|
||||
|
||||
test_expect_success 'git hook list: nonexistent hook' '
|
||||
cat >stderr.expect <<-\EOF &&
|
||||
warning: No hooks found for event '\''test-hook'\''
|
||||
warning: no hooks found for event '\''test-hook'\''
|
||||
EOF
|
||||
test_expect_code 1 git hook list test-hook 2>stderr.actual &&
|
||||
test_expect_code 1 git hook list --allow-unknown-hook-name test-hook 2>stderr.actual &&
|
||||
test_cmp stderr.expect stderr.actual
|
||||
'
|
||||
|
||||
@@ -48,7 +77,7 @@ test_expect_success 'git hook list: traditional hook from hookdir' '
|
||||
cat >expect <<-\EOF &&
|
||||
hook from hookdir
|
||||
EOF
|
||||
git hook list test-hook >actual &&
|
||||
git hook list --allow-unknown-hook-name test-hook >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
@@ -57,7 +86,7 @@ test_expect_success 'git hook list: configured hook' '
|
||||
test_config hook.myhook.event test-hook --add &&
|
||||
|
||||
echo "myhook" >expect &&
|
||||
git hook list test-hook >actual &&
|
||||
git hook list --allow-unknown-hook-name test-hook >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
@@ -69,7 +98,7 @@ test_expect_success 'git hook list: -z shows NUL-terminated output' '
|
||||
test_config hook.myhook.event test-hook --add &&
|
||||
|
||||
printf "myhookQhook from hookdirQ" >expect &&
|
||||
git hook list -z test-hook >actual.raw &&
|
||||
git hook list --allow-unknown-hook-name -z test-hook >actual.raw &&
|
||||
nul_to_q <actual.raw >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
@@ -78,12 +107,12 @@ test_expect_success 'git hook run: nonexistent hook' '
|
||||
cat >stderr.expect <<-\EOF &&
|
||||
error: cannot find a hook named test-hook
|
||||
EOF
|
||||
test_expect_code 1 git hook run test-hook 2>stderr.actual &&
|
||||
test_expect_code 1 git hook run --allow-unknown-hook-name test-hook 2>stderr.actual &&
|
||||
test_cmp stderr.expect stderr.actual
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '
|
||||
git hook run --ignore-missing does-not-exist 2>stderr.actual &&
|
||||
git hook run --allow-unknown-hook-name --ignore-missing does-not-exist 2>stderr.actual &&
|
||||
test_must_be_empty stderr.actual
|
||||
'
|
||||
|
||||
@@ -95,7 +124,7 @@ test_expect_success 'git hook run: basic' '
|
||||
cat >expect <<-\EOF &&
|
||||
Test hook
|
||||
EOF
|
||||
git hook run test-hook 2>actual &&
|
||||
git hook run --allow-unknown-hook-name test-hook 2>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
@@ -109,7 +138,7 @@ test_expect_success 'git hook run: stdout and stderr both write to our stderr' '
|
||||
Will end up on stderr
|
||||
Will end up on stderr
|
||||
EOF
|
||||
git hook run test-hook >stdout.actual 2>stderr.actual &&
|
||||
git hook run --allow-unknown-hook-name test-hook >stdout.actual 2>stderr.actual &&
|
||||
test_cmp stderr.expect stderr.actual &&
|
||||
test_must_be_empty stdout.actual
|
||||
'
|
||||
@@ -121,12 +150,12 @@ do
|
||||
exit $code
|
||||
EOF
|
||||
|
||||
test_expect_code $code git hook run test-hook
|
||||
test_expect_code $code git hook run --allow-unknown-hook-name test-hook
|
||||
'
|
||||
done
|
||||
|
||||
test_expect_success 'git hook run arg u ments without -- is not allowed' '
|
||||
test_expect_code 129 git hook run test-hook arg u ments
|
||||
test_expect_code 129 git hook run --allow-unknown-hook-name test-hook arg u ments
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run -- pass arguments' '
|
||||
@@ -140,7 +169,7 @@ test_expect_success 'git hook run -- pass arguments' '
|
||||
u ments
|
||||
EOF
|
||||
|
||||
git hook run test-hook -- arg "u ments" 2>actual &&
|
||||
git hook run --allow-unknown-hook-name test-hook -- arg "u ments" 2>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
@@ -149,12 +178,12 @@ test_expect_success 'git hook run: out-of-repo runs execute global hooks' '
|
||||
test_config_global hook.global-hook.command "echo no repo no problems" --add &&
|
||||
|
||||
echo "global-hook" >expect &&
|
||||
nongit git hook list test-hook >actual &&
|
||||
nongit git hook list --allow-unknown-hook-name test-hook >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
echo "no repo no problems" >expect &&
|
||||
|
||||
nongit git hook run test-hook 2>actual &&
|
||||
nongit git hook run --allow-unknown-hook-name test-hook 2>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
@@ -179,11 +208,11 @@ test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
|
||||
# Test various ways of specifying the path. See also
|
||||
# t1350-config-hooks-path.sh
|
||||
>actual &&
|
||||
git hook run test-hook -- ignored 2>>actual &&
|
||||
git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&
|
||||
git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&
|
||||
git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual &&
|
||||
git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual &&
|
||||
git hook run --allow-unknown-hook-name test-hook -- ignored 2>>actual &&
|
||||
git -c core.hooksPath=my-hooks hook run --allow-unknown-hook-name test-hook -- one 2>>actual &&
|
||||
git -c core.hooksPath=my-hooks/ hook run --allow-unknown-hook-name test-hook -- two 2>>actual &&
|
||||
git -c core.hooksPath="$PWD/my-hooks" hook run --allow-unknown-hook-name test-hook -- three 2>>actual &&
|
||||
git -c core.hooksPath="$PWD/my-hooks/" hook run --allow-unknown-hook-name test-hook -- four 2>>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
@@ -263,7 +292,7 @@ test_expect_success 'hook can be configured for multiple events' '
|
||||
# 'ghi' should be included in both 'pre-commit' and 'test-hook'
|
||||
git hook list pre-commit >actual &&
|
||||
grep "ghi" actual &&
|
||||
git hook list test-hook >actual &&
|
||||
git hook list --allow-unknown-hook-name test-hook >actual &&
|
||||
grep "ghi" actual
|
||||
'
|
||||
|
||||
@@ -337,15 +366,15 @@ test_expect_success 'stdin to multiple hooks' '
|
||||
b3
|
||||
EOF
|
||||
|
||||
git hook run --to-stdin=input test-hook 2>actual &&
|
||||
git hook run --allow-unknown-hook-name --to-stdin=input test-hook 2>actual &&
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'rejects hooks with no commands configured' '
|
||||
test_config hook.broken.event "test-hook" &&
|
||||
test_must_fail git hook list test-hook 2>actual &&
|
||||
test_must_fail git hook list --allow-unknown-hook-name test-hook 2>actual &&
|
||||
test_grep "hook.broken.command" actual &&
|
||||
test_must_fail git hook run test-hook 2>actual &&
|
||||
test_must_fail git hook run --allow-unknown-hook-name test-hook 2>actual &&
|
||||
test_grep "hook.broken.command" actual
|
||||
'
|
||||
|
||||
@@ -354,11 +383,19 @@ test_expect_success 'disabled hook is not run' '
|
||||
test_config hook.skipped.command "echo \"Should not run\"" &&
|
||||
test_config hook.skipped.enabled false &&
|
||||
|
||||
git hook run --ignore-missing test-hook 2>actual &&
|
||||
git hook run --allow-unknown-hook-name --ignore-missing test-hook 2>actual &&
|
||||
test_must_be_empty actual
|
||||
'
|
||||
|
||||
test_expect_success 'disabled hook does not appear in git hook list' '
|
||||
test_expect_success 'disabled hook with no command warns' '
|
||||
test_config hook.nocommand.event "pre-commit" &&
|
||||
test_config hook.nocommand.enabled false &&
|
||||
|
||||
git hook list pre-commit 2>actual &&
|
||||
test_grep "disabled hook.*nocommand.*no command configured" actual
|
||||
'
|
||||
|
||||
test_expect_success 'disabled hook appears as disabled in git hook list' '
|
||||
test_config hook.active.event "pre-commit" &&
|
||||
test_config hook.active.command "echo active" &&
|
||||
test_config hook.inactive.event "pre-commit" &&
|
||||
@@ -366,8 +403,27 @@ test_expect_success 'disabled hook does not appear in git hook list' '
|
||||
test_config hook.inactive.enabled false &&
|
||||
|
||||
git hook list pre-commit >actual &&
|
||||
test_grep "active" actual &&
|
||||
test_grep ! "inactive" actual
|
||||
test_grep "^active$" actual &&
|
||||
test_grep "^disabled inactive$" actual
|
||||
'
|
||||
|
||||
test_expect_success 'disabled hook shows scope with --show-scope' '
|
||||
test_config hook.myhook.event "pre-commit" &&
|
||||
test_config hook.myhook.command "echo hi" &&
|
||||
test_config hook.myhook.enabled false &&
|
||||
|
||||
git hook list --show-scope pre-commit >actual &&
|
||||
test_grep "^local disabled myhook$" actual
|
||||
'
|
||||
|
||||
test_expect_success 'disabled configured hook is not reported as existing by hook_exists' '
|
||||
test_when_finished "rm -f git-bugreport-hook-exists-test.txt" &&
|
||||
test_config hook.linter.event "pre-commit" &&
|
||||
test_config hook.linter.command "echo lint" &&
|
||||
test_config hook.linter.enabled false &&
|
||||
|
||||
git bugreport -s hook-exists-test &&
|
||||
test_grep ! "pre-commit" git-bugreport-hook-exists-test.txt
|
||||
'
|
||||
|
||||
test_expect_success 'globally disabled hook can be re-enabled locally' '
|
||||
@@ -377,10 +433,59 @@ test_expect_success 'globally disabled hook can be re-enabled locally' '
|
||||
test_config hook.global-hook.enabled true &&
|
||||
|
||||
echo "global-hook ran" >expected &&
|
||||
git hook run test-hook 2>actual &&
|
||||
git hook run --allow-unknown-hook-name test-hook 2>actual &&
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'configured hooks run before hookdir hook' '
|
||||
setup_hookdir &&
|
||||
test_config hook.first.event "pre-commit" &&
|
||||
test_config hook.first.command "echo first" &&
|
||||
test_config hook.second.event "pre-commit" &&
|
||||
test_config hook.second.command "echo second" &&
|
||||
|
||||
cat >expected <<-\EOF &&
|
||||
first
|
||||
second
|
||||
hook from hookdir
|
||||
EOF
|
||||
|
||||
git hook list pre-commit >actual &&
|
||||
test_cmp expected actual &&
|
||||
|
||||
# "Legacy Hook" is the output of the hookdir pre-commit script
|
||||
# written by setup_hookdir() above.
|
||||
cat >expected <<-\EOF &&
|
||||
first
|
||||
second
|
||||
"Legacy Hook"
|
||||
EOF
|
||||
|
||||
git hook run pre-commit 2>actual &&
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'git hook list --show-scope shows config scope' '
|
||||
setup_hookdir &&
|
||||
test_config_global hook.global-hook.command "echo global" &&
|
||||
test_config_global hook.global-hook.event pre-commit --add &&
|
||||
test_config hook.local-hook.command "echo local" &&
|
||||
test_config hook.local-hook.event pre-commit --add &&
|
||||
|
||||
cat >expected <<-\EOF &&
|
||||
global global-hook
|
||||
local local-hook
|
||||
hook from hookdir
|
||||
EOF
|
||||
git hook list --show-scope pre-commit >actual &&
|
||||
test_cmp expected actual &&
|
||||
|
||||
# without --show-scope the scope must not appear
|
||||
git hook list pre-commit >actual &&
|
||||
test_grep ! "^global " actual &&
|
||||
test_grep ! "^local " actual
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run a hook with a bad shebang' '
|
||||
test_when_finished "rm -rf bad-hooks" &&
|
||||
mkdir bad-hooks &&
|
||||
@@ -388,7 +493,7 @@ test_expect_success 'git hook run a hook with a bad shebang' '
|
||||
|
||||
test_expect_code 1 git \
|
||||
-c core.hooksPath=bad-hooks \
|
||||
hook run test-hook >out 2>err &&
|
||||
hook run --allow-unknown-hook-name test-hook >out 2>err &&
|
||||
test_must_be_empty out &&
|
||||
|
||||
# TODO: We should emit the same (or at least a more similar)
|
||||
@@ -412,7 +517,7 @@ test_expect_success 'stdin to hooks' '
|
||||
EOF
|
||||
|
||||
echo hello >input &&
|
||||
git hook run --to-stdin=input test-hook 2>actual &&
|
||||
git hook run --allow-unknown-hook-name --to-stdin=input test-hook 2>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
|
||||
+12
-16
@@ -987,7 +987,7 @@ test_dwim_orphan () {
|
||||
then
|
||||
test_must_be_empty actual
|
||||
else
|
||||
grep "$info_text" actual
|
||||
test_grep "$info_text" actual
|
||||
fi
|
||||
elif [ "$outcome" = "no_infer" ]
|
||||
then
|
||||
@@ -996,39 +996,35 @@ test_dwim_orphan () {
|
||||
then
|
||||
test_must_be_empty actual
|
||||
else
|
||||
! grep "$info_text" actual
|
||||
test_grep ! "$info_text" actual
|
||||
fi
|
||||
elif [ "$outcome" = "fetch_error" ]
|
||||
then
|
||||
test_must_fail git $dashc_args worktree add $args 2>actual &&
|
||||
grep "$fetch_error_text" actual
|
||||
test_grep "$fetch_error_text" actual
|
||||
elif [ "$outcome" = "fatal_orphan_bad_combo" ]
|
||||
then
|
||||
test_must_fail git $dashc_args worktree add $args 2>actual &&
|
||||
if [ $use_quiet -eq 1 ]
|
||||
then
|
||||
! grep "$info_text" actual
|
||||
test_grep ! "$info_text" actual
|
||||
else
|
||||
grep "$info_text" actual
|
||||
test_grep "$info_text" actual
|
||||
fi &&
|
||||
grep "$bad_combo_regex" actual
|
||||
test_grep "$bad_combo_regex" actual
|
||||
elif [ "$outcome" = "warn_bad_head" ]
|
||||
then
|
||||
test_must_fail git $dashc_args worktree add $args 2>actual &&
|
||||
if [ $use_quiet -eq 1 ]
|
||||
then
|
||||
grep "$invalid_ref_regex" actual &&
|
||||
! grep "$orphan_hint" actual
|
||||
test_grep "$invalid_ref_regex" actual &&
|
||||
test_grep ! "$orphan_hint" actual
|
||||
else
|
||||
headpath=$(git $dashc_args rev-parse --path-format=absolute --git-path HEAD) &&
|
||||
headcontents=$(cat "$headpath") &&
|
||||
grep "HEAD points to an invalid (or orphaned) reference" actual &&
|
||||
grep "HEAD path: .$headpath." actual &&
|
||||
grep "HEAD contents: .$headcontents." actual &&
|
||||
grep "$orphan_hint" actual &&
|
||||
! grep "$info_text" actual
|
||||
test_grep "HEAD points to an invalid (or orphaned) reference" actual &&
|
||||
test_grep "$orphan_hint" actual &&
|
||||
test_grep ! "$info_text" actual
|
||||
fi &&
|
||||
grep "$invalid_ref_regex" actual
|
||||
test_grep "$invalid_ref_regex" actual
|
||||
else
|
||||
# Unreachable
|
||||
false
|
||||
|
||||
+104
-7
@@ -74,8 +74,8 @@ test_expect_success '--onto with invalid commit-ish' '
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'option --onto or --advance is mandatory' '
|
||||
echo "error: option --onto or --advance is mandatory" >expect &&
|
||||
test_expect_success 'exactly one of --onto, --advance, or --revert is required' '
|
||||
echo "error: exactly one of --onto, --advance, or --revert is required" >expect &&
|
||||
test_might_fail git replay -h >>expect &&
|
||||
test_must_fail git replay topic1..topic2 2>actual &&
|
||||
test_cmp expect actual
|
||||
@@ -87,16 +87,14 @@ test_expect_success 'no base or negative ref gives no-replaying down to root err
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'options --advance and --contained cannot be used together' '
|
||||
printf "fatal: options ${SQ}--advance${SQ} " >expect &&
|
||||
printf "and ${SQ}--contained${SQ} cannot be used together\n" >>expect &&
|
||||
test_expect_success '--advance and --contained cannot be used together' '
|
||||
test_must_fail git replay --advance=main --contained \
|
||||
topic1..topic2 2>actual &&
|
||||
test_cmp expect actual
|
||||
test_grep "cannot be used together" actual
|
||||
'
|
||||
|
||||
test_expect_success 'cannot advance target ... ordering would be ill-defined' '
|
||||
echo "fatal: cannot advance target with multiple sources because ordering would be ill-defined" >expect &&
|
||||
echo "fatal: ${SQ}--advance${SQ} cannot be used with multiple revision ranges because the ordering would be ill-defined" >expect &&
|
||||
test_must_fail git replay --advance=main main topic1 topic2 2>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
@@ -398,4 +396,103 @@ test_expect_success 'invalid replay.refAction value' '
|
||||
test_grep "invalid.*replay.refAction.*value" error
|
||||
'
|
||||
|
||||
test_expect_success 'argument to --revert must be a reference' '
|
||||
echo "fatal: argument to --revert must be a reference" >expect &&
|
||||
oid=$(git rev-parse main) &&
|
||||
test_must_fail git replay --revert=$oid topic1..topic2 2>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'cannot revert with multiple sources' '
|
||||
echo "fatal: ${SQ}--revert${SQ} cannot be used with multiple revision ranges because the ordering would be ill-defined" >expect &&
|
||||
test_must_fail git replay --revert main main topic1 topic2 2>actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'using replay --revert to revert commits' '
|
||||
# Reuse existing topic4 branch (has commits I and J on top of main)
|
||||
START=$(git rev-parse topic4) &&
|
||||
test_when_finished "git branch -f topic4 $START" &&
|
||||
|
||||
# Revert commits I and J
|
||||
git replay --revert topic4 topic4~2..topic4 &&
|
||||
|
||||
# Verify the revert commits were created (newest-first ordering
|
||||
# means J is reverted first, then I on top)
|
||||
git log --format=%s -4 topic4 >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
Revert "I"
|
||||
Revert "J"
|
||||
J
|
||||
I
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
|
||||
# Verify commit message format includes hash (tip is Revert "I")
|
||||
test_commit_message topic4 <<-EOF &&
|
||||
Revert "I"
|
||||
|
||||
This reverts commit $(git rev-parse I).
|
||||
EOF
|
||||
|
||||
# Verify reflog message
|
||||
git reflog topic4 -1 --format=%gs >reflog-msg &&
|
||||
echo "replay --revert topic4" >expect-reflog &&
|
||||
test_cmp expect-reflog reflog-msg
|
||||
'
|
||||
|
||||
test_expect_success 'using replay --revert in bare repo' '
|
||||
# Reuse existing topic4 in bare repo
|
||||
START=$(git -C bare rev-parse topic4) &&
|
||||
test_when_finished "git -C bare update-ref refs/heads/topic4 $START" &&
|
||||
|
||||
# Revert commit J in bare repo
|
||||
git -C bare replay --revert topic4 topic4~1..topic4 &&
|
||||
|
||||
# Verify revert was created
|
||||
git -C bare log -1 --format=%s topic4 >actual &&
|
||||
echo "Revert \"J\"" >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'revert of revert uses Reapply' '
|
||||
# Use topic4 and first revert J, then revert the revert
|
||||
START=$(git rev-parse topic4) &&
|
||||
test_when_finished "git branch -f topic4 $START" &&
|
||||
|
||||
# First revert J
|
||||
git replay --revert topic4 topic4~1..topic4 &&
|
||||
REVERT_J=$(git rev-parse topic4) &&
|
||||
|
||||
# Now revert the revert - should become Reapply
|
||||
git replay --revert topic4 topic4~1..topic4 &&
|
||||
|
||||
# Verify Reapply prefix and message format
|
||||
test_commit_message topic4 <<-EOF
|
||||
Reapply "J"
|
||||
|
||||
This reverts commit $REVERT_J.
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'git replay --revert with conflict' '
|
||||
# conflict branch has C.conflict which conflicts with topic1s C
|
||||
test_expect_code 1 git replay --revert conflict B..topic1
|
||||
'
|
||||
|
||||
test_expect_success 'git replay --revert incompatible with --contained' '
|
||||
test_must_fail git replay --revert topic4 --contained topic4~1..topic4 2>error &&
|
||||
test_grep "cannot be used together" error
|
||||
'
|
||||
|
||||
test_expect_success 'git replay --revert incompatible with --onto' '
|
||||
test_must_fail git replay --revert topic4 --onto main topic4~1..topic4 2>error &&
|
||||
test_grep "cannot be used together" error
|
||||
'
|
||||
|
||||
test_expect_success 'git replay --revert incompatible with --advance' '
|
||||
test_must_fail git replay --revert topic4 --advance main topic4~1..topic4 2>error &&
|
||||
test_grep "cannot be used together" error
|
||||
'
|
||||
|
||||
test_done
|
||||
|
||||
@@ -380,6 +380,131 @@ test_expect_success 'filename limit applies only to basename' '
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'cover letter with subject, author and count' '
|
||||
rm -rf patches &&
|
||||
test_when_finished "git reset --hard HEAD~1" &&
|
||||
test_when_finished "rm -rf patches test_file" &&
|
||||
touch test_file &&
|
||||
git add test_file &&
|
||||
git commit -m "This is a subject" &&
|
||||
git format-patch --commit-list-format="log:[%(count)/%(total)] %s (%an)" \
|
||||
-o patches HEAD~1 &&
|
||||
test_grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch
|
||||
'
|
||||
|
||||
test_expect_success 'cover letter with custom format no prefix' '
|
||||
rm -rf patches &&
|
||||
test_when_finished "git reset --hard HEAD~1" &&
|
||||
test_when_finished "rm -rf patches test_file" &&
|
||||
touch test_file &&
|
||||
git add test_file &&
|
||||
git commit -m "This is a subject" &&
|
||||
git format-patch --commit-list-format="[%(count)/%(total)] %s (%an)" \
|
||||
-o patches HEAD~1 &&
|
||||
test_grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch
|
||||
'
|
||||
|
||||
test_expect_success 'cover letter fail when no prefix and no placeholder' '
|
||||
rm -rf patches &&
|
||||
test_when_finished "git reset --hard HEAD~1" &&
|
||||
test_when_finished "rm -rf patches test_file err" &&
|
||||
touch test_file &&
|
||||
git add test_file &&
|
||||
git commit -m "This is a subject" &&
|
||||
test_must_fail git format-patch --commit-list-format="this should fail" \
|
||||
-o patches HEAD~1 2>err &&
|
||||
test_grep "is not a valid format string" err
|
||||
'
|
||||
|
||||
test_expect_success 'cover letter modern format' '
|
||||
test_when_finished "git reset --hard HEAD~1" &&
|
||||
test_when_finished "rm -rf patches test_file" &&
|
||||
touch test_file &&
|
||||
git add test_file &&
|
||||
git commit -m "This is a subject" &&
|
||||
git format-patch --commit-list-format="modern" -o patches HEAD~1 &&
|
||||
test_grep "^\[1/1\] This is a subject$" patches/0000-cover-letter.patch
|
||||
'
|
||||
|
||||
test_expect_success 'cover letter shortlog format' '
|
||||
test_when_finished "git reset --hard HEAD~1" &&
|
||||
test_when_finished "rm -rf expect patches result test_file" &&
|
||||
cat >expect <<-"EOF" &&
|
||||
A U Thor (1):
|
||||
This is a subject
|
||||
EOF
|
||||
touch test_file &&
|
||||
git add test_file &&
|
||||
git commit -m "This is a subject" &&
|
||||
git format-patch --commit-list-format=shortlog -o patches HEAD~1 &&
|
||||
grep -E -A 1 "^A U Thor \([[:digit:]]+\):$" patches/0000-cover-letter.patch >result &&
|
||||
cat result &&
|
||||
test_cmp expect result
|
||||
'
|
||||
|
||||
test_expect_success 'no cover letter but with format specified' '
|
||||
test_when_finished "git reset --hard HEAD~1" &&
|
||||
test_when_finished "rm -rf patches result test_file" &&
|
||||
touch test_file &&
|
||||
git add test_file &&
|
||||
git commit -m "This is a subject" &&
|
||||
git format-patch --no-cover-letter --commit-list-format="[%(count)] %s" -o patches HEAD~1 &&
|
||||
test_path_is_missing patches/0000-cover-letter.patch
|
||||
'
|
||||
|
||||
test_expect_success 'cover letter config with count, subject and author' '
|
||||
test_when_finished "rm -rf patches result" &&
|
||||
test_when_finished "git config unset format.coverletter" &&
|
||||
test_when_finished "git config unset format.commitlistformat" &&
|
||||
git config set format.coverletter true &&
|
||||
git config set format.commitlistformat "log:[%(count)/%(total)] %s (%an)" &&
|
||||
git format-patch -o patches HEAD~2 &&
|
||||
grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result &&
|
||||
test_line_count = 2 result
|
||||
'
|
||||
|
||||
test_expect_success 'cover letter config with count and author' '
|
||||
test_when_finished "rm -rf patches result" &&
|
||||
test_when_finished "git config unset format.coverletter" &&
|
||||
test_when_finished "git config unset format.commitlistformat" &&
|
||||
git config set format.coverletter true &&
|
||||
git config set format.commitlistformat "log:[%(count)/%(total)] (%an)" &&
|
||||
git format-patch -o patches HEAD~2 &&
|
||||
grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result &&
|
||||
test_line_count = 2 result
|
||||
'
|
||||
|
||||
test_expect_success 'cover letter config commitlistformat set to modern' '
|
||||
test_when_finished "rm -rf patches result" &&
|
||||
test_when_finished "git config unset format.coverletter" &&
|
||||
test_when_finished "git config unset format.commitlistformat" &&
|
||||
git config set format.coverletter true &&
|
||||
git config set format.commitlistformat modern &&
|
||||
git format-patch -o patches HEAD~2 &&
|
||||
grep -E "^[[[:digit:]]+/[[:digit:]]+] .*$" patches/0000-cover-letter.patch >result &&
|
||||
test_line_count = 2 result
|
||||
'
|
||||
|
||||
test_expect_success 'cover letter config commitlistformat set to shortlog' '
|
||||
test_when_finished "rm -rf patches result" &&
|
||||
test_when_finished "git config unset format.coverletter" &&
|
||||
test_when_finished "git config unset format.commitlistformat" &&
|
||||
git config set format.coverletter true &&
|
||||
git config set format.commitlistformat shortlog &&
|
||||
git format-patch -o patches HEAD~2 &&
|
||||
grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
|
||||
test_line_count = 1 result
|
||||
'
|
||||
|
||||
test_expect_success 'cover letter config commitlistformat not set' '
|
||||
test_when_finished "rm -rf patches result" &&
|
||||
test_when_finished "git config unset format.coverletter" &&
|
||||
git config set format.coverletter true &&
|
||||
git format-patch -o patches HEAD~2 &&
|
||||
grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
|
||||
test_line_count = 1 result
|
||||
'
|
||||
|
||||
test_expect_success 'reroll count' '
|
||||
rm -fr patches &&
|
||||
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
|
||||
|
||||
+209
-2
@@ -7,6 +7,14 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'backfill rejects unexpected arguments' '
|
||||
test_must_fail git backfill unexpected-arg 2>err &&
|
||||
test_grep "ambiguous argument .*unexpected-arg" err &&
|
||||
|
||||
test_must_fail git backfill --all --unexpected-arg --first-parent 2>err &&
|
||||
test_grep "unrecognized argument: --unexpected-arg" err
|
||||
'
|
||||
|
||||
# We create objects in the 'src' repo.
|
||||
test_expect_success 'setup repo for object creation' '
|
||||
echo "{print \$1}" >print_1.awk &&
|
||||
@@ -15,7 +23,7 @@ test_expect_success 'setup repo for object creation' '
|
||||
git init src &&
|
||||
|
||||
mkdir -p src/a/b/c &&
|
||||
mkdir -p src/d/e &&
|
||||
mkdir -p src/d/f &&
|
||||
|
||||
for i in 1 2
|
||||
do
|
||||
@@ -26,8 +34,9 @@ test_expect_success 'setup repo for object creation' '
|
||||
echo "Version $i of file a/b/$n" > src/a/b/file.$n.txt &&
|
||||
echo "Version $i of file a/b/c/$n" > src/a/b/c/file.$n.txt &&
|
||||
echo "Version $i of file d/$n" > src/d/file.$n.txt &&
|
||||
echo "Version $i of file d/e/$n" > src/d/e/file.$n.txt &&
|
||||
echo "Version $i of file d/f/$n" > src/d/f/file.$n.txt &&
|
||||
git -C src add . &&
|
||||
test_tick &&
|
||||
git -C src commit -m "Iteration $n" || return 1
|
||||
done
|
||||
done
|
||||
@@ -41,6 +50,53 @@ test_expect_success 'setup bare clone for server' '
|
||||
git -C srv.bare config --local uploadpack.allowanysha1inwant 1
|
||||
'
|
||||
|
||||
# Create a version of the repo with branches for testing revision
|
||||
# arguments like --all, --first-parent, and --since.
|
||||
#
|
||||
# main: 8 commits (linear) + merge of side branch
|
||||
# 48 original blobs + 4 side blobs = 52 blobs from main HEAD
|
||||
# side: 2 commits adding s/file.{1,2}.txt (v1, v2), merged into main
|
||||
# other: 1 commit adding o/file.{1,2}.txt (not merged)
|
||||
# 54 total blobs reachable from --all
|
||||
test_expect_success 'setup branched repo for revision tests' '
|
||||
git clone src src-revs &&
|
||||
|
||||
# Side branch from tip of main with unique files
|
||||
git -C src-revs checkout -b side HEAD &&
|
||||
mkdir -p src-revs/s &&
|
||||
echo "Side version 1 of file 1" >src-revs/s/file.1.txt &&
|
||||
echo "Side version 1 of file 2" >src-revs/s/file.2.txt &&
|
||||
test_tick &&
|
||||
git -C src-revs add . &&
|
||||
git -C src-revs commit -m "Side commit 1" &&
|
||||
|
||||
echo "Side version 2 of file 1" >src-revs/s/file.1.txt &&
|
||||
echo "Side version 2 of file 2" >src-revs/s/file.2.txt &&
|
||||
test_tick &&
|
||||
git -C src-revs add . &&
|
||||
git -C src-revs commit -m "Side commit 2" &&
|
||||
|
||||
# Merge side into main
|
||||
git -C src-revs checkout main &&
|
||||
test_tick &&
|
||||
git -C src-revs merge side --no-ff -m "Merge side branch" &&
|
||||
|
||||
# Other branch (not merged) for --all testing
|
||||
git -C src-revs checkout -b other main~1 &&
|
||||
mkdir -p src-revs/o &&
|
||||
echo "Other content 1" >src-revs/o/file.1.txt &&
|
||||
echo "Other content 2" >src-revs/o/file.2.txt &&
|
||||
test_tick &&
|
||||
git -C src-revs add . &&
|
||||
git -C src-revs commit -m "Other commit" &&
|
||||
|
||||
git -C src-revs checkout main &&
|
||||
|
||||
git clone --bare "file://$(pwd)/src-revs" srv-revs.bare &&
|
||||
git -C srv-revs.bare config --local uploadpack.allowfilter 1 &&
|
||||
git -C srv-revs.bare config --local uploadpack.allowanysha1inwant 1
|
||||
'
|
||||
|
||||
# do basic partial clone from "srv.bare"
|
||||
test_expect_success 'do partial clone 1, backfill gets all objects' '
|
||||
git clone --no-checkout --filter=blob:none \
|
||||
@@ -176,6 +232,157 @@ test_expect_success 'backfill --sparse without cone mode (negative)' '
|
||||
test_line_count = 12 missing
|
||||
'
|
||||
|
||||
test_expect_success 'backfill with revision range' '
|
||||
test_when_finished rm -rf backfill-revs &&
|
||||
git clone --no-checkout --filter=blob:none \
|
||||
--single-branch --branch=main \
|
||||
"file://$(pwd)/srv.bare" backfill-revs &&
|
||||
|
||||
# No blobs yet
|
||||
git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 48 missing &&
|
||||
|
||||
git -C backfill-revs backfill HEAD~2..HEAD &&
|
||||
|
||||
# 30 objects downloaded.
|
||||
git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 18 missing
|
||||
'
|
||||
|
||||
test_expect_success 'backfill with revisions over stdin' '
|
||||
test_when_finished rm -rf backfill-revs &&
|
||||
git clone --no-checkout --filter=blob:none \
|
||||
--single-branch --branch=main \
|
||||
"file://$(pwd)/srv.bare" backfill-revs &&
|
||||
|
||||
# No blobs yet
|
||||
git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 48 missing &&
|
||||
|
||||
cat >in <<-EOF &&
|
||||
HEAD
|
||||
^HEAD~2
|
||||
EOF
|
||||
|
||||
git -C backfill-revs backfill --stdin <in &&
|
||||
|
||||
# 30 objects downloaded.
|
||||
git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 18 missing
|
||||
'
|
||||
|
||||
test_expect_success 'backfill with prefix pathspec' '
|
||||
test_when_finished rm -rf backfill-path &&
|
||||
git clone --bare --filter=blob:none \
|
||||
--single-branch --branch=main \
|
||||
"file://$(pwd)/srv.bare" backfill-path &&
|
||||
|
||||
# No blobs yet
|
||||
git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 48 missing &&
|
||||
|
||||
git -C backfill-path backfill HEAD -- d/f 2>err &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 40 missing
|
||||
'
|
||||
|
||||
test_expect_success 'backfill with multiple pathspecs' '
|
||||
test_when_finished rm -rf backfill-path &&
|
||||
git clone --bare --filter=blob:none \
|
||||
--single-branch --branch=main \
|
||||
"file://$(pwd)/srv.bare" backfill-path &&
|
||||
|
||||
# No blobs yet
|
||||
git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 48 missing &&
|
||||
|
||||
git -C backfill-path backfill HEAD -- d/f a 2>err &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 16 missing
|
||||
'
|
||||
|
||||
test_expect_success 'backfill with wildcard pathspec' '
|
||||
test_when_finished rm -rf backfill-path &&
|
||||
git clone --bare --filter=blob:none \
|
||||
--single-branch --branch=main \
|
||||
"file://$(pwd)/srv.bare" backfill-path &&
|
||||
|
||||
# No blobs yet
|
||||
git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 48 missing &&
|
||||
|
||||
git -C backfill-path backfill HEAD -- "d/file.*.txt" 2>err &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 40 missing
|
||||
'
|
||||
|
||||
test_expect_success 'backfill with --all' '
|
||||
test_when_finished rm -rf backfill-all &&
|
||||
git clone --no-checkout --filter=blob:none \
|
||||
"file://$(pwd)/srv-revs.bare" backfill-all &&
|
||||
|
||||
# All blobs from all refs are missing
|
||||
git -C backfill-all rev-list --quiet --objects --all --missing=print >missing &&
|
||||
test_line_count = 54 missing &&
|
||||
|
||||
# Backfill from HEAD gets main blobs only
|
||||
git -C backfill-all backfill HEAD &&
|
||||
|
||||
# Other branch blobs still missing
|
||||
git -C backfill-all rev-list --quiet --objects --all --missing=print >missing &&
|
||||
test_line_count = 2 missing &&
|
||||
|
||||
# Backfill with --all gets everything
|
||||
git -C backfill-all backfill --all &&
|
||||
|
||||
git -C backfill-all rev-list --quiet --objects --all --missing=print >missing &&
|
||||
test_line_count = 0 missing
|
||||
'
|
||||
|
||||
test_expect_success 'backfill with --first-parent' '
|
||||
test_when_finished rm -rf backfill-fp &&
|
||||
git clone --no-checkout --filter=blob:none \
|
||||
--single-branch --branch=main \
|
||||
"file://$(pwd)/srv-revs.bare" backfill-fp &&
|
||||
|
||||
git -C backfill-fp rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 52 missing &&
|
||||
|
||||
# --first-parent skips the side branch commits, so
|
||||
# s/file.{1,2}.txt v1 blobs (only in side commit 1) are missed.
|
||||
git -C backfill-fp backfill --first-parent HEAD &&
|
||||
|
||||
git -C backfill-fp rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 2 missing
|
||||
'
|
||||
|
||||
test_expect_success 'backfill with --since' '
|
||||
test_when_finished rm -rf backfill-since &&
|
||||
git clone --no-checkout --filter=blob:none \
|
||||
--single-branch --branch=main \
|
||||
"file://$(pwd)/srv-revs.bare" backfill-since &&
|
||||
|
||||
git -C backfill-since rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 52 missing &&
|
||||
|
||||
# Use a cutoff between commits 4 and 5 (between v1 and v2
|
||||
# iterations). Commits 5-8 still carry v1 of files 2-4 in
|
||||
# their trees, but v1 of file.1.txt is only in commits 1-4.
|
||||
SINCE=$(git -C backfill-since log --first-parent --reverse \
|
||||
--format=%ct HEAD~1 | sed -n 5p) &&
|
||||
git -C backfill-since backfill --since="@$((SINCE - 1))" HEAD &&
|
||||
|
||||
# 6 missing: v1 of file.1.txt in all 6 directories
|
||||
git -C backfill-since rev-list --quiet --objects --missing=print HEAD >missing &&
|
||||
test_line_count = 6 missing
|
||||
'
|
||||
|
||||
. "$TEST_DIRECTORY"/lib-httpd.sh
|
||||
start_httpd
|
||||
|
||||
|
||||
@@ -49,80 +49,69 @@ test_expect_success setup '
|
||||
'
|
||||
|
||||
test_expect_success 'straight copy without -C' '
|
||||
|
||||
git blame uno | grep Second
|
||||
|
||||
git blame uno >actual &&
|
||||
test_grep Second actual
|
||||
'
|
||||
|
||||
test_expect_success 'straight move without -C' '
|
||||
|
||||
git blame dos | grep Initial
|
||||
|
||||
git blame dos >actual &&
|
||||
test_grep Initial actual
|
||||
'
|
||||
|
||||
test_expect_success 'straight copy with -C' '
|
||||
|
||||
git blame -C1 uno | grep Second
|
||||
|
||||
git blame -C1 uno >actual &&
|
||||
test_grep Second actual
|
||||
'
|
||||
|
||||
test_expect_success 'straight move with -C' '
|
||||
|
||||
git blame -C1 dos | grep Initial
|
||||
|
||||
git blame -C1 dos >actual &&
|
||||
test_grep Initial actual
|
||||
'
|
||||
|
||||
test_expect_success 'straight copy with -C -C' '
|
||||
|
||||
git blame -C -C1 uno | grep Initial
|
||||
|
||||
git blame -C -C1 uno >actual &&
|
||||
test_grep Initial actual
|
||||
'
|
||||
|
||||
test_expect_success 'straight move with -C -C' '
|
||||
|
||||
git blame -C -C1 dos | grep Initial
|
||||
|
||||
git blame -C -C1 dos >actual &&
|
||||
test_grep Initial actual
|
||||
'
|
||||
|
||||
test_expect_success 'append without -C' '
|
||||
|
||||
git blame -L2 tres | grep Second
|
||||
|
||||
git blame -L2 tres >actual &&
|
||||
test_grep Second actual
|
||||
'
|
||||
|
||||
test_expect_success 'append with -C' '
|
||||
|
||||
git blame -L2 -C1 tres | grep Second
|
||||
|
||||
git blame -L2 -C1 tres >actual &&
|
||||
test_grep Second actual
|
||||
'
|
||||
|
||||
test_expect_success 'append with -C -C' '
|
||||
|
||||
git blame -L2 -C -C1 tres | grep Second
|
||||
|
||||
git blame -L2 -C -C1 tres >actual &&
|
||||
test_grep Second actual
|
||||
'
|
||||
|
||||
test_expect_success 'append with -C -C -C' '
|
||||
|
||||
git blame -L2 -C -C -C1 tres | grep Initial
|
||||
|
||||
git blame -L2 -C -C -C1 tres >actual &&
|
||||
test_grep Initial actual
|
||||
'
|
||||
|
||||
test_expect_success 'blame wholesale copy' '
|
||||
|
||||
git blame -f -C -C1 HEAD^ -- cow | sed -e "$pick_fc" >current &&
|
||||
git blame -f -C -C1 HEAD^ -- cow >actual &&
|
||||
sed -e "$pick_fc" actual >current &&
|
||||
cat >expected <<-\EOF &&
|
||||
mouse-Initial
|
||||
mouse-Second
|
||||
mouse-Third
|
||||
EOF
|
||||
test_cmp expected current
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'blame wholesale copy and more' '
|
||||
|
||||
git blame -f -C -C1 HEAD -- cow | sed -e "$pick_fc" >current &&
|
||||
git blame -f -C -C1 HEAD -- cow >actual &&
|
||||
sed -e "$pick_fc" actual >current &&
|
||||
cat >expected <<-\EOF &&
|
||||
mouse-Initial
|
||||
mouse-Second
|
||||
@@ -130,11 +119,9 @@ test_expect_success 'blame wholesale copy and more' '
|
||||
mouse-Third
|
||||
EOF
|
||||
test_cmp expected current
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'blame wholesale copy and more in the index' '
|
||||
|
||||
cat >horse <<-\EOF &&
|
||||
ABC
|
||||
DEF
|
||||
@@ -144,7 +131,8 @@ test_expect_success 'blame wholesale copy and more in the index' '
|
||||
EOF
|
||||
git add horse &&
|
||||
test_when_finished "git rm -f horse" &&
|
||||
git blame -f -C -C1 -- horse | sed -e "$pick_fc" >current &&
|
||||
git blame -f -C -C1 -- horse >actual &&
|
||||
sed -e "$pick_fc" actual >current &&
|
||||
cat >expected <<-\EOF &&
|
||||
mouse-Initial
|
||||
mouse-Second
|
||||
@@ -153,11 +141,9 @@ test_expect_success 'blame wholesale copy and more in the index' '
|
||||
mouse-Third
|
||||
EOF
|
||||
test_cmp expected current
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'blame during cherry-pick with file rename conflict' '
|
||||
|
||||
test_when_finished "git reset --hard && git checkout main" &&
|
||||
git checkout HEAD~3 &&
|
||||
echo MOUSE >> mouse &&
|
||||
@@ -168,7 +154,8 @@ test_expect_success 'blame during cherry-pick with file rename conflict' '
|
||||
(git cherry-pick HEAD@{1} || test $? -eq 1) &&
|
||||
git show HEAD@{1}:rodent > rodent &&
|
||||
git add rodent &&
|
||||
git blame -f -C -C1 rodent | sed -e "$pick_fc" >current &&
|
||||
git blame -f -C -C1 rodent >actual &&
|
||||
sed -e "$pick_fc" actual >current &&
|
||||
cat >expected <<-\EOF &&
|
||||
mouse-Initial
|
||||
mouse-Second
|
||||
@@ -246,14 +233,14 @@ test_expect_success 'setup file with CRLF newlines' '
|
||||
test_expect_success 'blame file with CRLF core.autocrlf true' '
|
||||
git config core.autocrlf true &&
|
||||
git blame crlffile >actual &&
|
||||
grep "A U Thor" actual
|
||||
test_grep "A U Thor" actual
|
||||
'
|
||||
|
||||
test_expect_success 'blame file with CRLF attributes text' '
|
||||
git config core.autocrlf false &&
|
||||
echo "crlffile text" >.gitattributes &&
|
||||
git blame crlffile >actual &&
|
||||
grep "A U Thor" actual
|
||||
test_grep "A U Thor" actual
|
||||
'
|
||||
|
||||
test_expect_success 'blame file with CRLF core.autocrlf=true' '
|
||||
@@ -267,7 +254,7 @@ test_expect_success 'blame file with CRLF core.autocrlf=true' '
|
||||
git checkout crlfinrepo &&
|
||||
rm tmp &&
|
||||
git blame crlfinrepo >actual &&
|
||||
grep "A U Thor" actual
|
||||
test_grep "A U Thor" actual
|
||||
'
|
||||
|
||||
test_expect_success 'setup coalesce tests' '
|
||||
|
||||
+2
-1
@@ -1360,7 +1360,8 @@ static int pre_push_hook_feed_stdin(int hook_stdin_fd, void *pp_cb UNUSED, void
|
||||
|
||||
static void *pre_push_hook_data_alloc(void *feed_pipe_ctx)
|
||||
{
|
||||
struct feed_pre_push_hook_data *data = xmalloc(sizeof(*data));
|
||||
struct feed_pre_push_hook_data *data;
|
||||
CALLOC_ARRAY(data, 1);
|
||||
strbuf_init(&data->buf, 0);
|
||||
data->refs = (struct ref *)feed_pipe_ctx;
|
||||
return data;
|
||||
|
||||
+5
-5
@@ -58,7 +58,7 @@ static void add_head_info(struct worktree *wt)
|
||||
|
||||
static int is_current_worktree(struct worktree *wt)
|
||||
{
|
||||
char *git_dir = absolute_pathdup(repo_get_git_dir(the_repository));
|
||||
char *git_dir = absolute_pathdup(repo_get_git_dir(wt->repo));
|
||||
char *wt_git_dir = get_worktree_git_dir(wt);
|
||||
int is_current = !fspathcmp(git_dir, absolute_path(wt_git_dir));
|
||||
free(wt_git_dir);
|
||||
@@ -78,7 +78,7 @@ struct worktree *get_worktree_from_repository(struct repository *repo)
|
||||
wt->is_bare = !repo->worktree;
|
||||
if (fspathcmp(gitdir, commondir))
|
||||
wt->id = xstrdup(find_last_dir_sep(gitdir) + 1);
|
||||
wt->is_current = is_current_worktree(wt);
|
||||
wt->is_current = true;
|
||||
add_head_info(wt);
|
||||
|
||||
free(gitdir);
|
||||
@@ -227,11 +227,11 @@ struct worktree **get_worktrees_without_reading_head(void)
|
||||
char *get_worktree_git_dir(const struct worktree *wt)
|
||||
{
|
||||
if (!wt)
|
||||
return xstrdup(repo_get_git_dir(the_repository));
|
||||
BUG("%s() called with NULL worktree", __func__);
|
||||
else if (!wt->id)
|
||||
return xstrdup(repo_get_common_dir(the_repository));
|
||||
return xstrdup(repo_get_common_dir(wt->repo));
|
||||
else
|
||||
return repo_common_path(the_repository, "worktrees/%s", wt->id);
|
||||
return repo_common_path(wt->repo, "worktrees/%s", wt->id);
|
||||
}
|
||||
|
||||
static struct worktree *find_worktree_by_suffix(struct worktree **list,
|
||||
|
||||
+1
-2
@@ -16,7 +16,7 @@ struct worktree {
|
||||
struct object_id head_oid;
|
||||
int is_detached;
|
||||
int is_bare;
|
||||
int is_current;
|
||||
int is_current; /* does `path` match `repo->worktree` */
|
||||
int lock_reason_valid; /* private */
|
||||
int prune_reason_valid; /* private */
|
||||
};
|
||||
@@ -51,7 +51,6 @@ int submodule_uses_worktrees(const char *path);
|
||||
|
||||
/*
|
||||
* Return git dir of the worktree. Note that the path may be relative.
|
||||
* If wt is NULL, git dir of current worktree is returned.
|
||||
*/
|
||||
char *get_worktree_git_dir(const struct worktree *wt);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user