aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes/2.32.0.txt37
-rw-r--r--Documentation/config/pack.txt15
-rw-r--r--Documentation/git-apply.txt11
-rw-r--r--Documentation/gitweb.conf.txt11
-rw-r--r--Documentation/user-manual.txt3
-rw-r--r--Makefile1
-rw-r--r--apply.c22
-rw-r--r--branch.c1
-rw-r--r--builtin/pack-objects.c34
-rw-r--r--builtin/rebase.c1
-rwxr-xr-xci/run-build-and-tests.sh1
-rw-r--r--compat/precompose_utf8.c9
-rw-r--r--compat/precompose_utf8.h1
-rw-r--r--config.c16
-rw-r--r--contrib/completion/git-completion.bash8
-rw-r--r--diffcore-rename.c230
-rw-r--r--diffcore.h19
-rw-r--r--git-compat-util.h5
-rwxr-xr-xgit-send-email.perl45
-rw-r--r--git.c2
-rwxr-xr-xgitweb/gitweb.perl34
-rw-r--r--merge-ort.c322
-rw-r--r--merge-recursive.c37
-rw-r--r--pack-bitmap.c24
-rw-r--r--pack-bitmap.h4
-rw-r--r--path.c1
-rw-r--r--path.h2
-rw-r--r--ref-filter.c2
-rw-r--r--sequencer.c5
-rw-r--r--setup.c28
-rw-r--r--t/helper/test-bitmap.c24
-rw-r--r--t/helper/test-bloom.c2
-rw-r--r--t/helper/test-tool.c1
-rw-r--r--t/helper/test-tool.h1
-rwxr-xr-xt/t3512-cherry-pick-submodule.sh7
-rwxr-xr-xt/t3513-revert-submodule.sh5
-rwxr-xr-xt/t4108-apply-threeway.sh70
-rwxr-xr-xt/t5310-pack-bitmaps.sh38
-rwxr-xr-xt/t5572-pull-submodule.sh7
-rwxr-xr-xt/t6300-for-each-ref.sh10
-rwxr-xr-xt/t6423-merge-rename-directories.sh2
-rwxr-xr-xt/t6428-merge-conflicts-sparse.sh158
-rwxr-xr-xt/t6437-submodule-merge.sh5
-rwxr-xr-xt/t6438-submodule-directory-file-conflicts.sh7
-rwxr-xr-xt/t9001-send-email.sh35
-rw-r--r--t/test-lib.sh2
46 files changed, 1136 insertions, 169 deletions
diff --git a/Documentation/RelNotes/2.32.0.txt b/Documentation/RelNotes/2.32.0.txt
index 5c329d5a1b..ea220f2a51 100644
--- a/Documentation/RelNotes/2.32.0.txt
+++ b/Documentation/RelNotes/2.32.0.txt
@@ -57,6 +57,23 @@ UI, Workflows & Features
* "git clone --reject-shallow" option fails the clone as soon as we
notice that we are cloning from a shallow repository.
+ * A configuration variable has been added to force tips of certain
+ refs to be given a reachability bitmap.
+
+ * "gitweb" learned "e-mail privacy" feature to redact strings that
+ look like e-mail addresses on various pages.
+
+ * "git apply --3way" has always been "to fall back to 3-way merge
+ only when straight application fails". Swap the order of falling
+ back so that 3-way is always attempted first (only when the option
+ is given, of course) and then straight patch application is used as
+ a fallback when it fails.
+
+ * "git apply" now takes "--3way" and "--cached" at the same time, and
+ work and record results only in the index.
+
+ * The command line completion (in contrib/) has learned that
+ CHERRY_PICK_HEAD is a possible pseudo-ref.
Performance, Internal Implementation, Development Support etc.
@@ -98,6 +115,11 @@ Performance, Internal Implementation, Development Support etc.
* Generate [ec]tags under $(QUIET_GEN).
+ * Clean-up codepaths that implements "git send-email --validate"
+ option and improves the message from it.
+
+ * The last remnant of gettext-poison has been removed.
+
Fixes since v2.31
-----------------
@@ -174,6 +196,19 @@ Fixes since v2.31
as directory separator.
(merge 9a7f1ce8b7 rs/daemon-sanitize-dir-sep later to maint).
+ * A NULL-dereference bug has been corrected in an error codepath in
+ "git for-each-ref", "git branch --list" etc.
+ (merge c685450880 jk/ref-filter-segfault-fix later to maint).
+
+ * Streamline the codepath to fix the UTF-8 encoding issues in the
+ argv[] and the prefix on macOS.
+ (merge c7d0e61016 tb/precompose-prefix-simplify later to maint).
+
+ * The command-line completion script (in contrib/) had a couple of
+ references that would have given a warning under the "-u" (nounset)
+ option.
+ (merge c5c0548d79 vs/completion-with-set-u later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge f451960708 dl/cat-file-doc-cleanup later to maint).
(merge 12604a8d0c sv/t9801-test-path-is-file-cleanup later to maint).
@@ -186,3 +221,5 @@ Fixes since v2.31
(merge 2be927f3d1 ab/diff-no-index-tests later to maint).
(merge 76593c09bb ab/detox-gettext-tests later to maint).
(merge 28e29ee38b jc/doc-format-patch-clarify later to maint).
+ (merge fc12b6fdde fm/user-manual-use-preface later to maint).
+ (merge dba94e3a85 cc/test-helper-bloom-usage-fix later to maint).
diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt
index 3da4ea98e2..c0844d8d8e 100644
--- a/Documentation/config/pack.txt
+++ b/Documentation/config/pack.txt
@@ -122,6 +122,21 @@ pack.useSparse::
commits contain certain types of direct renames. Default is
`true`.
+pack.preferBitmapTips::
+ When selecting which commits will receive bitmaps, prefer a
+ commit at the tip of any reference that is a suffix of any value
+ of this configuration over any other commits in the "selection
+ window".
++
+Note that setting this configuration to `refs/foo` does not mean that
+the commits at the tips of `refs/foo/bar` and `refs/foo/baz` will
+necessarily be selected. This is because commits are selected for
+bitmaps from within a series of windows of variable length.
++
+If a commit at the tip of any reference which is a suffix of any value
+of this configuration is seen in a window, it is immediately given
+preference over any other commit in that window.
+
pack.writeBitmaps (deprecated)::
This is a deprecated synonym for `repack.writeBitmaps`.
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index 91d9a8601c..aa1ae56a25 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -84,12 +84,13 @@ OPTIONS
-3::
--3way::
- When the patch does not apply cleanly, fall back on 3-way merge if
- the patch records the identity of blobs it is supposed to apply to,
- and we have those blobs available locally, possibly leaving the
+ Attempt 3-way merge if the patch records the identity of blobs it is supposed
+ to apply to and we have those blobs available locally, possibly leaving the
conflict markers in the files in the working tree for the user to
- resolve. This option implies the `--index` option, and is incompatible
- with the `--reject` and the `--cached` options.
+ resolve. This option implies the `--index` option unless the
+ `--cached` option is used, and is incompatible with the `--reject` option.
+ When used with the `--cached` option, any conflicts are left at higher stages
+ in the cache.
--build-fake-ancestor=<file>::
Newer 'git diff' output has embedded 'index information'
diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt
index 7963a79ba9..34b1d6e224 100644
--- a/Documentation/gitweb.conf.txt
+++ b/Documentation/gitweb.conf.txt
@@ -751,6 +751,17 @@ default font sizes or lineheights are changed (e.g. via adding extra
CSS stylesheet in `@stylesheets`), it may be appropriate to change
these values.
+email-privacy::
+ Redact e-mail addresses from the generated HTML, etc. content.
+ This obscures e-mail addresses retrieved from the author/committer
+ and comment sections of the Git log.
+ It is meant to hinder web crawlers that harvest and abuse addresses.
+ Such crawlers may not respect robots.txt.
+ Note that users and user tools also see the addresses as redacted.
+ If Gitweb is not the final step in a workflow then subsequent steps
+ may misbehave because of the redacted information they receive.
+ Disabled by default.
+
highlight::
Server-side syntax highlight support in "blob" view. It requires
`$highlight_bin` program to be available (see the description of
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index fd480b8645..f9e54b8674 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -1,5 +1,8 @@
= Git User Manual
+[preface]
+== Introduction
+
Git is a fast distributed revision control system.
This manual is designed to be readable by someone with basic UNIX
diff --git a/Makefile b/Makefile
index ffc2ddfd93..21c0bf1667 100644
--- a/Makefile
+++ b/Makefile
@@ -693,6 +693,7 @@ X =
PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
TEST_BUILTINS_OBJS += test-advise.o
+TEST_BUILTINS_OBJS += test-bitmap.o
TEST_BUILTINS_OBJS += test-bloom.o
TEST_BUILTINS_OBJS += test-chmtime.o
TEST_BUILTINS_OBJS += test-config.o
diff --git a/apply.c b/apply.c
index 466f880d73..8c5b29809b 100644
--- a/apply.c
+++ b/apply.c
@@ -134,8 +134,6 @@ int check_apply_state(struct apply_state *state, int force_apply)
if (state->apply_with_reject && state->threeway)
return error(_("--reject and --3way cannot be used together."));
- if (state->cached && state->threeway)
- return error(_("--cached and --3way cannot be used together."));
if (state->threeway) {
if (is_not_gitdir)
return error(_("--3way outside a repository"));
@@ -3570,10 +3568,10 @@ static int try_threeway(struct apply_state *state,
write_object_file("", 0, blob_type, &pre_oid);
else if (get_oid(patch->old_oid_prefix, &pre_oid) ||
read_blob_object(&buf, &pre_oid, patch->old_mode))
- return error(_("repository lacks the necessary blob to fall back on 3-way merge."));
+ return error(_("repository lacks the necessary blob to perform 3-way merge."));
if (state->apply_verbosity > verbosity_silent)
- fprintf(stderr, _("Falling back to three-way merge...\n"));
+ fprintf(stderr, _("Performing three-way merge...\n"));
img = strbuf_detach(&buf, &len);
prepare_image(&tmp_image, img, len, 1);
@@ -3605,7 +3603,7 @@ static int try_threeway(struct apply_state *state,
if (status < 0) {
if (state->apply_verbosity > verbosity_silent)
fprintf(stderr,
- _("Failed to fall back on three-way merge...\n"));
+ _("Failed to perform three-way merge...\n"));
return status;
}
@@ -3638,10 +3636,9 @@ static int apply_data(struct apply_state *state, struct patch *patch,
if (load_preimage(state, &image, patch, st, ce) < 0)
return -1;
- if (patch->direct_to_threeway ||
- apply_fragments(state, &image, patch) < 0) {
+ if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0) {
/* Note: with --reject, apply_fragments() returns 0 */
- if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0)
+ if (patch->direct_to_threeway || apply_fragments(state, &image, patch) < 0)
return -1;
}
patch->result = image.buf;
@@ -4647,7 +4644,12 @@ static int write_out_results(struct apply_state *state, struct patch *list)
}
string_list_clear(&cpath, 0);
- repo_rerere(state->repo, 0);
+ /*
+ * rerere relies on the partially merged result being in the working
+ * tree with conflict markers, but that isn't written with --cached.
+ */
+ if (!state->cached)
+ repo_rerere(state->repo, 0);
}
return errs;
@@ -5018,7 +5020,7 @@ int apply_parse_options(int argc, const char **argv,
OPT_BOOL(0, "apply", force_apply,
N_("also apply the patch (use with --stat/--summary/--check)")),
OPT_BOOL('3', "3way", &state->threeway,
- N_( "attempt three-way merge if a patch does not apply")),
+ N_( "attempt three-way merge, fall back on normal patch if that fails")),
OPT_FILENAME(0, "build-fake-ancestor", &state->fake_ancestor,
N_("build a temporary index based on embedded index information")),
/* Think twice before adding "--nul" synonym to this */
diff --git a/branch.c b/branch.c
index 9c9dae1eae..b71a2de29d 100644
--- a/branch.c
+++ b/branch.c
@@ -344,6 +344,7 @@ void remove_merge_branch_state(struct repository *r)
unlink(git_path_merge_rr(r));
unlink(git_path_merge_msg(r));
unlink(git_path_merge_mode(r));
+ unlink(git_path_auto_merge(r));
save_autostash(git_path_merge_autostash(r));
}
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 525c2d8552..a1e33d7507 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -3547,6 +3547,37 @@ static void record_recent_commit(struct commit *commit, void *data)
oid_array_append(&recent_objects, &commit->object.oid);
}
+static int mark_bitmap_preferred_tip(const char *refname,
+ const struct object_id *oid, int flags,
+ void *_data)
+{
+ struct object_id peeled;
+ struct object *object;
+
+ if (!peel_iterated_oid(oid, &peeled))
+ oid = &peeled;
+
+ object = parse_object_or_die(oid, refname);
+ if (object->type == OBJ_COMMIT)
+ object->flags |= NEEDS_BITMAP;
+
+ return 0;
+}
+
+static void mark_bitmap_preferred_tips(void)
+{
+ struct string_list_item *item;
+ const struct string_list *preferred_tips;
+
+ preferred_tips = bitmap_preferred_tips(the_repository);
+ if (!preferred_tips)
+ return;
+
+ for_each_string_list_item(item, preferred_tips) {
+ for_each_ref_in(item->string, mark_bitmap_preferred_tip, NULL);
+ }
+}
+
static void get_object_list(int ac, const char **av)
{
struct rev_info revs;
@@ -3601,6 +3632,9 @@ static void get_object_list(int ac, const char **av)
if (use_delta_islands)
load_delta_islands(the_repository, progress);
+ if (write_bitmap_index)
+ mark_bitmap_preferred_tips();
+
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
mark_edges_uninteresting(&revs, show_edge, sparse);
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 783b526f6e..ed1da1760e 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -738,6 +738,7 @@ static int finish_rebase(struct rebase_options *opts)
int ret = 0;
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
+ unlink(git_path_auto_merge(the_repository));
apply_autostash(state_dir_path("autostash", opts));
close_object_store(the_repository->objects);
/*
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index a66b5e8c75..d19be40544 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -16,6 +16,7 @@ linux-gcc)
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
make test
export GIT_TEST_SPLIT_INDEX=yes
+ export GIT_TEST_MERGE_ALGORITHM=recursive
export GIT_TEST_FULL_IN_PACK_ARRAY=true
export GIT_TEST_OE_SIZE=10
export GIT_TEST_OE_DELTA_SIZE=5
diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c
index ec560565a8..cce1d57a46 100644
--- a/compat/precompose_utf8.c
+++ b/compat/precompose_utf8.c
@@ -60,10 +60,12 @@ void probe_utf8_pathname_composition(void)
strbuf_release(&path);
}
-static inline const char *precompose_string_if_needed(const char *in)
+const char *precompose_string_if_needed(const char *in)
{
size_t inlen;
size_t outlen;
+ if (!in)
+ return NULL;
if (has_non_ascii(in, (size_t)-1, &inlen)) {
iconv_t ic_prec;
char *out;
@@ -96,10 +98,7 @@ const char *precompose_argv_prefix(int argc, const char **argv, const char *pref
argv[i] = precompose_string_if_needed(argv[i]);
i++;
}
- if (prefix) {
- prefix = precompose_string_if_needed(prefix);
- }
- return prefix;
+ return precompose_string_if_needed(prefix);
}
diff --git a/compat/precompose_utf8.h b/compat/precompose_utf8.h
index d70b84665c..fea06cf28a 100644
--- a/compat/precompose_utf8.h
+++ b/compat/precompose_utf8.h
@@ -29,6 +29,7 @@ typedef struct {
} PREC_DIR;
const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix);
+const char *precompose_string_if_needed(const char *in);
void probe_utf8_pathname_composition(void);
PREC_DIR *precompose_utf8_opendir(const char *dirname);
diff --git a/config.c b/config.c
index 6428393a41..870d9534de 100644
--- a/config.c
+++ b/config.c
@@ -1180,20 +1180,6 @@ static void die_bad_number(const char *name, const char *value)
}
}
-NORETURN
-static void die_bad_bool(const char *name, const char *value)
-{
- if (!strcmp(name, "GIT_TEST_GETTEXT_POISON"))
- /*
- * We explicitly *don't* use _() here since it would
- * cause an infinite loop with _() needing to call
- * use_gettext_poison().
- */
- die("bad boolean config value '%s' for '%s'", value, name);
- else
- die(_("bad boolean config value '%s' for '%s'"), value, name);
-}
-
int git_config_int(const char *name, const char *value)
{
int ret;
@@ -1268,7 +1254,7 @@ int git_config_bool(const char *name, const char *value)
{
int v = git_parse_maybe_bool(value);
if (v < 0)
- die_bad_bool(name, value);
+ die(_("bad boolean config value '%s' for '%s'"), value, name);
return v;
}
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e1a66954fe..dfa735ea62 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -77,7 +77,7 @@ __git_find_repo_path ()
test -d "$__git_dir" &&
__git_repo_path="$__git_dir"
elif [ -n "${GIT_DIR-}" ]; then
- test -d "${GIT_DIR-}" &&
+ test -d "$GIT_DIR" &&
__git_repo_path="$GIT_DIR"
elif [ -d .git ]; then
__git_repo_path=.git
@@ -427,7 +427,7 @@ __gitcomp_builtin ()
if [ -z "$options" ]; then
local completion_helper
- if [ "$GIT_COMPLETION_SHOW_ALL" = "1" ]; then
+ if [ "${GIT_COMPLETION_SHOW_ALL-}" = "1" ]; then
completion_helper="--git-completion-helper-all"
else
completion_helper="--git-completion-helper"
@@ -744,7 +744,7 @@ __git_refs ()
track=""
;;
*)
- for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD; do
+ for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD CHERRY_PICK_HEAD; do
case "$i" in
$match*)
if [ -e "$dir/$i" ]; then
@@ -1910,7 +1910,7 @@ _git_help ()
return
;;
esac
- if test -n "$GIT_TESTING_ALL_COMMAND_LIST"
+ if test -n "${GIT_TESTING_ALL_COMMAND_LIST-}"
then
__gitcomp "$GIT_TESTING_ALL_COMMAND_LIST $(__git --list-cmds=alias,list-guide) gitk"
else
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 36a98f9c49..963ca58221 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -371,7 +371,7 @@ struct dir_rename_info {
struct strintmap idx_map;
struct strmap dir_rename_guess;
struct strmap *dir_rename_count;
- struct strset *relevant_source_dirs;
+ struct strintmap *relevant_source_dirs;
unsigned setup;
};
@@ -407,6 +407,28 @@ static const char *get_highest_rename_path(struct strintmap *counts)
return highest_destination_dir;
}
+static char *UNKNOWN_DIR = "/"; /* placeholder -- short, illegal directory */
+
+static int dir_rename_already_determinable(struct strintmap *counts)
+{
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+ int first = 0, second = 0, unknown = 0;
+ strintmap_for_each_entry(counts, &iter, entry) {
+ const char *destination_dir = entry->key;
+ intptr_t count = (intptr_t)entry->value;
+ if (!strcmp(destination_dir, UNKNOWN_DIR)) {
+ unknown = count;
+ } else if (count >= first) {
+ second = first;
+ first = count;
+ } else if (count >= second) {
+ second = count;
+ }
+ }
+ return first > second + unknown;
+}
+
static void increment_count(struct dir_rename_info *info,
char *old_dir,
char *new_dir)
@@ -429,7 +451,7 @@ static void increment_count(struct dir_rename_info *info,
}
static void update_dir_rename_counts(struct dir_rename_info *info,
- struct strset *dirs_removed,
+ struct strintmap *dirs_removed,
const char *oldname,
const char *newname)
{
@@ -461,10 +483,12 @@ static void update_dir_rename_counts(struct dir_rename_info *info,
return;
while (1) {
+ int drd_flag = NOT_RELEVANT;
+
/* Get old_dir, skip if its directory isn't relevant. */
dirname_munge(old_dir);
if (info->relevant_source_dirs &&
- !strset_contains(info->relevant_source_dirs, old_dir))
+ !strintmap_contains(info->relevant_source_dirs, old_dir))
break;
/* Get new_dir */
@@ -509,16 +533,31 @@ static void update_dir_rename_counts(struct dir_rename_info *info,
}
}
- if (strset_contains(dirs_removed, old_dir))
+ /*
+ * Above we suggested that we'd keep recording renames for
+ * all ancestor directories where the trailing directories
+ * matched, i.e. for
+ * "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c"
+ * we'd increment rename counts for each of
+ * a/b/c/d/e/ => a/b/some/thing/else/e/
+ * a/b/c/d/ => a/b/some/thing/else/
+ * However, we only need the rename counts for directories
+ * in dirs_removed whose value is RELEVANT_FOR_SELF.
+ * However, we add one special case of also recording it for
+ * first_time_in_loop because find_basename_matches() can
+ * use that as a hint to find a good pairing.
+ */
+ if (dirs_removed)
+ drd_flag = strintmap_get(dirs_removed, old_dir);
+ if (drd_flag == RELEVANT_FOR_SELF || first_time_in_loop)
increment_count(info, old_dir, new_dir);
- else
- break;
+ first_time_in_loop = 0;
+ if (drd_flag == NOT_RELEVANT)
+ break;
/* If we hit toplevel directory ("") for old or new dir, quit */
if (!*old_dir || !*new_dir)
break;
-
- first_time_in_loop = 0;
}
/* Free resources we don't need anymore */
@@ -527,8 +566,8 @@ static void update_dir_rename_counts(struct dir_rename_info *info,
}
static void initialize_dir_rename_info(struct dir_rename_info *info,
- struct strset *relevant_sources,
- struct strset *dirs_removed,
+ struct strintmap *relevant_sources,
+ struct strintmap *dirs_removed,
struct strmap *dir_rename_count)
{
struct hashmap_iter iter;
@@ -555,12 +594,13 @@ static void initialize_dir_rename_info(struct dir_rename_info *info,
info->relevant_source_dirs = dirs_removed; /* might be NULL */
} else {
info->relevant_source_dirs = xmalloc(sizeof(struct strintmap));
- strset_init(info->relevant_source_dirs);
- strset_for_each_entry(relevant_sources, &iter, entry) {
+ strintmap_init(info->relevant_source_dirs, 0 /* unused */);
+ strintmap_for_each_entry(relevant_sources, &iter, entry) {
char *dirname = get_dirname(entry->key);
if (!dirs_removed ||
- strset_contains(dirs_removed, dirname))
- strset_add(info->relevant_source_dirs, dirname);
+ strintmap_contains(dirs_removed, dirname))
+ strintmap_set(info->relevant_source_dirs,
+ dirname, 0 /* value irrelevant */);
free(dirname);
}
}
@@ -624,7 +664,7 @@ void partial_clear_dir_rename_count(struct strmap *dir_rename_count)
}
static void cleanup_dir_rename_info(struct dir_rename_info *info,
- struct strset *dirs_removed,
+ struct strintmap *dirs_removed,
int keep_dir_rename_count)
{
struct hashmap_iter iter;
@@ -644,7 +684,7 @@ static void cleanup_dir_rename_info(struct dir_rename_info *info,
/* relevant_source_dirs */
if (info->relevant_source_dirs &&
info->relevant_source_dirs != dirs_removed) {
- strset_clear(info->relevant_source_dirs);
+ strintmap_clear(info->relevant_source_dirs);
FREE_AND_NULL(info->relevant_source_dirs);
}
@@ -659,18 +699,22 @@ static void cleanup_dir_rename_info(struct dir_rename_info *info,
/*
* Although dir_rename_count was passed in
* diffcore_rename_extended() and we want to keep it around and
- * return it to that caller, we first want to remove any data
+ * return it to that caller, we first want to remove any counts in
+ * the maps associated with UNKNOWN_DIR entries and any data
* associated with directories that weren't renamed.
*/
strmap_for_each_entry(info->dir_rename_count, &iter, entry) {
const char *source_dir = entry->key;
struct strintmap *counts = entry->value;
- if (!strset_contains(dirs_removed, source_dir)) {
+ if (!strintmap_get(dirs_removed, source_dir)) {
string_list_append(&to_remove, source_dir);
strintmap_clear(counts);
continue;
}
+
+ if (strintmap_contains(counts, UNKNOWN_DIR))
+ strintmap_remove(counts, UNKNOWN_DIR);
}
for (i = 0; i < to_remove.nr; ++i)
strmap_remove(info->dir_rename_count,
@@ -770,8 +814,8 @@ static int idx_possible_rename(char *filename, struct dir_rename_info *info)
static int find_basename_matches(struct diff_options *options,
int minimum_score,
struct dir_rename_info *info,
- struct strset *relevant_sources,
- struct strset *dirs_removed)
+ struct strintmap *relevant_sources,
+ struct strintmap *dirs_removed)
{
/*
* When I checked in early 2020, over 76% of file renames in linux
@@ -863,7 +907,7 @@ static int find_basename_matches(struct diff_options *options,
/* Skip irrelevant sources */
if (relevant_sources &&
- !strset_contains(relevant_sources, filename))
+ !strintmap_contains(relevant_sources, filename))
continue;
/*
@@ -994,7 +1038,7 @@ static int find_renames(struct diff_score *mx,
int minimum_score,
int copies,
struct dir_rename_info *info,
- struct strset *dirs_removed)
+ struct strintmap *dirs_removed)
{
int count = 0, i;
@@ -1019,7 +1063,7 @@ static int find_renames(struct diff_score *mx,
}
static void remove_unneeded_paths_from_src(int detecting_copies,
- struct strset *interesting)
+ struct strintmap *interesting)
{
int i, new_num_src;
@@ -1061,7 +1105,7 @@ static void remove_unneeded_paths_from_src(int detecting_copies,
continue;
/* If we don't care about the source path, skip it */
- if (interesting && !strset_contains(interesting, one->path))
+ if (interesting && !strintmap_contains(interesting, one->path))
continue;
if (new_num_src < i)
@@ -1073,9 +1117,136 @@ static void remove_unneeded_paths_from_src(int detecting_copies,
rename_src_nr = new_num_src;
}
+static void handle_early_known_dir_renames(struct dir_rename_info *info,
+ struct strintmap *relevant_sources,
+ struct strintmap *dirs_removed)
+{
+ /*
+ * Directory renames are determined via an aggregate of all renames
+ * under them and using a "majority wins" rule. The fact that
+ * "majority wins", though, means we don't need all the renames
+ * under the given directory, we only need enough to ensure we have
+ * a majority.
+ */
+
+ int i, new_num_src;
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+
+ if (!dirs_removed || !relevant_sources)
+ return; /* nothing to cull */
+ if (break_idx)
+ return; /* culling incompatbile with break detection */
+
+ /*
+ * Supplement dir_rename_count with number of potential renames,
+ * marking all potential rename sources as mapping to UNKNOWN_DIR.
+ */
+ for (i = 0; i < rename_src_nr; i++) {
+ char *old_dir;
+ struct diff_filespec *one = rename_src[i].p->one;
+
+ /*
+ * sources that are part of a rename will have already been
+ * removed by a prior call to remove_unneeded_paths_from_src()
+ */
+ assert(!one->rename_used);
+
+ old_dir = get_dirname(one->path);
+ while (*old_dir != '\0' &&
+ NOT_RELEVANT != strintmap_get(dirs_removed, old_dir)) {
+ char *freeme = old_dir;
+
+ increment_count(info, old_dir, UNKNOWN_DIR);
+ old_dir = get_dirname(old_dir);
+
+ /* Free resources we don't need anymore */
+ free(freeme);
+ }
+ /*
+ * old_dir and new_dir free'd in increment_count, but
+ * get_dirname() gives us a new pointer we need to free for
+ * old_dir. Also, if the loop runs 0 times we need old_dir
+ * to be freed.
+ */
+ free(old_dir);
+ }
+
+ /*
+ * For any directory which we need a potential rename detected for
+ * (i.e. those marked as RELEVANT_FOR_SELF in dirs_removed), check
+ * whether we have enough renames to satisfy the "majority rules"
+ * requirement such that detecting any more renames of files under
+ * it won't change the result. For any such directory, mark that
+ * we no longer need to detect a rename for it. However, since we
+ * might need to still detect renames for an ancestor of that
+ * directory, use RELEVANT_FOR_ANCESTOR.
+ */
+ strmap_for_each_entry(info->dir_rename_count, &iter, entry) {
+ /* entry->key is source_dir */
+ struct strintmap *counts = entry->value;
+
+ if (strintmap_get(dirs_removed, entry->key) ==
+ RELEVANT_FOR_SELF &&
+ dir_rename_already_determinable(counts)) {
+ strintmap_set(dirs_removed, entry->key,
+ RELEVANT_FOR_ANCESTOR);
+ }
+ }
+
+ for (i = 0, new_num_src = 0; i < rename_src_nr; i++) {
+ struct diff_filespec *one = rename_src[i].p->one;
+ int val;
+
+ val = strintmap_get(relevant_sources, one->path);
+
+ /*
+ * sources that were not found in relevant_sources should
+ * have already been removed by a prior call to
+ * remove_unneeded_paths_from_src()
+ */
+ assert(val != -1);
+
+ if (val == RELEVANT_LOCATION) {
+ int removable = 1;
+ char *dir = get_dirname(one->path);
+ while (1) {
+ char *freeme = dir;
+ int res = strintmap_get(dirs_removed, dir);
+
+ /* Quit if not found or irrelevant */
+ if (res == NOT_RELEVANT)
+ break;
+ /* If RELEVANT_FOR_SELF, can't remove */
+ if (res == RELEVANT_FOR_SELF) {
+ removable = 0;
+ break;
+ }
+ /* Else continue searching upwards */
+ assert(res == RELEVANT_FOR_ANCESTOR);
+ dir = get_dirname(dir);
+ free(freeme);
+ }
+ free(dir);
+ if (removable) {
+ strintmap_set(relevant_sources, one->path,
+ RELEVANT_NO_MORE);
+ continue;
+ }
+ }
+
+ if (new_num_src < i)
+ memcpy(&rename_src[new_num_src], &rename_src[i],
+ sizeof(struct diff_rename_src));
+ new_num_src++;
+ }
+
+ rename_src_nr = new_num_src;
+}
+
void diffcore_rename_extended(struct diff_options *options,
- struct strset *relevant_sources,
- struct strset *dirs_removed,
+ struct strintmap *relevant_sources,
+ struct strintmap *dirs_removed,
struct strmap *dir_rename_count)
{
int detect_rename = options->detect_rename;
@@ -1208,9 +1379,16 @@ void diffcore_rename_extended(struct diff_options *options,
* Cull sources, again:
* - remove ones involved in renames (found via basenames)
* - remove ones not found in relevant_sources
+ * and
+ * - remove ones in relevant_sources which are needed only
+ * for directory renames IF no ancestory directory
+ * actually needs to know any more individual path
+ * renames under them
*/
trace2_region_enter("diff", "cull basename", options->repo);
remove_unneeded_paths_from_src(want_copies, relevant_sources);
+ handle_early_known_dir_renames(&info, relevant_sources,
+ dirs_removed);
trace2_region_leave("diff", "cull basename", options->repo);
}
diff --git a/diffcore.h b/diffcore.h
index d76982f220..f5c6de4841 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -8,8 +8,8 @@
struct diff_options;
struct repository;
+struct strintmap;
struct strmap;
-struct strset;
struct userdiff_driver;
/* This header file is internal between diff.c and its diff transformers
@@ -161,13 +161,26 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *,
struct diff_filespec *);
void diff_q(struct diff_queue_struct *, struct diff_filepair *);
+/* dir_rename_relevance: the reason we want rename information for a dir */
+enum dir_rename_relevance {
+ NOT_RELEVANT = 0,
+ RELEVANT_FOR_ANCESTOR = 1,
+ RELEVANT_FOR_SELF = 2
+};
+/* file_rename_relevance: the reason(s) we want rename information for a file */
+enum file_rename_relevance {
+ RELEVANT_NO_MORE = 0, /* i.e. NOT relevant */
+ RELEVANT_CONTENT = 1,
+ RELEVANT_LOCATION = 2
+};
+
void partial_clear_dir_rename_count(struct strmap *dir_rename_count);
void diffcore_break(struct repository *, int);
void diffcore_rename(struct diff_options *);
void diffcore_rename_extended(struct diff_options *options,
- struct strset *relevant_sources,
- struct strset *dirs_removed,
+ struct strintmap *relevant_sources,
+ struct strintmap *dirs_removed,
struct strmap *dir_rename_count);
void diffcore_merge_broken(void);
void diffcore_pickaxe(struct diff_options *);
diff --git a/git-compat-util.h b/git-compat-util.h
index 9ddf9d7044..a508dbe5a3 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -256,6 +256,11 @@ static inline const char *precompose_argv_prefix(int argc, const char **argv, co
{
return prefix;
}
+static inline const char *precompose_string_if_needed(const char *in)
+{
+ return in;
+}
+
#define probe_utf8_pathname_composition()
#endif
diff --git a/git-send-email.perl b/git-send-email.perl
index f5bbf1647e..175da07d94 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -212,22 +212,31 @@ my $dump_aliases = 0;
my $multiedit;
my $editor;
+sub system_or_msg {
+ my ($args, $msg) = @_;
+ system(@$args);
+ my $signalled = $? & 127;
+ my $exit_code = $? >> 8;
+ return unless $signalled or $exit_code;
+
+ return sprintf(__("fatal: command '%s' died with exit code %d"),
+ $args->[0], $exit_code);
+}
+
+sub system_or_die {
+ my $msg = system_or_msg(@_);
+ die $msg if $msg;
+}
+
sub do_edit {
if (!defined($editor)) {
$editor = Git::command_oneline('var', 'GIT_EDITOR');
}
+ my $die_msg = __("the editor exited uncleanly, aborting everything");
if (defined($multiedit) && !$multiedit) {
- map {
- system('sh', '-c', $editor.' "$@"', $editor, $_);
- if (($? & 127) || ($? >> 8)) {
- die(__("the editor exited uncleanly, aborting everything"));
- }
- } @_;
+ system_or_die(['sh', '-c', $editor.' "$@"', $editor, $_], $die_msg) for @_;
} else {
- system('sh', '-c', $editor.' "$@"', $editor, @_);
- if (($? & 127) || ($? >> 8)) {
- die(__("the editor exited uncleanly, aborting everything"));
- }
+ system_or_die(['sh', '-c', $editor.' "$@"', $editor, @_], $die_msg);
}
}
@@ -698,9 +707,7 @@ if (@rev_list_opts) {
if ($validate) {
foreach my $f (@files) {
unless (-p $f) {
- my $error = validate_patch($f, $target_xfer_encoding);
- $error and die sprintf(__("fatal: %s: %s\nwarning: no patches were sent\n"),
- $f, $error);
+ validate_patch($f, $target_xfer_encoding);
}
}
}
@@ -1952,11 +1959,14 @@ sub validate_patch {
chdir($repo->wc_path() or $repo->repo_path())
or die("chdir: $!");
local $ENV{"GIT_DIR"} = $repo->repo_path();
- $hook_error = "rejected by sendemail-validate hook"
- if system($validate_hook, $target);
+ $hook_error = system_or_msg([$validate_hook, $target]);
chdir($cwd_save) or die("chdir: $!");
}
- return $hook_error if $hook_error;
+ if ($hook_error) {
+ die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" .
+ "%s\n" .
+ "warning: no patches were sent\n"), $fn, $hook_error);
+ }
}
# Any long lines will be automatically fixed if we use a suitable transfer
@@ -1966,7 +1976,8 @@ sub validate_patch {
or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
while (my $line = <$fh>) {
if (length($line) > 998) {
- return sprintf(__("%s: patch contains a line longer than 998 characters"), $.);
+ die sprintf(__("fatal: %s:%d is longer than 998 characters\n" .
+ "warning: no patches were sent\n"), $fn, $.);
}
}
}
diff --git a/git.c b/git.c
index 9bc077a025..b53e665671 100644
--- a/git.c
+++ b/git.c
@@ -423,7 +423,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
int nongit_ok;
prefix = setup_git_directory_gently(&nongit_ok);
}
- prefix = precompose_argv_prefix(argc, argv, prefix);
+ precompose_argv_prefix(argc, argv, NULL);
if (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY) &&
!(p->option & DELAY_PAGER_CONFIG))
use_pager = check_pager_config(p->cmd);
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 0959a782ec..e09e024a09 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -569,6 +569,15 @@ our %feature = (
'sub' => \&feature_extra_branch_refs,
'override' => 0,
'default' => []},
+
+ # Redact e-mail addresses.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'email-privacy'}{'default'} = [1];
+ 'email-privacy' => {
+ 'sub' => sub { feature_bool('email-privacy', @_) },
+ 'override' => 1,
+ 'default' => [0]},
);
sub gitweb_get_feature {
@@ -3449,6 +3458,13 @@ sub parse_date {
return %date;
}
+sub hide_mailaddrs_if_private {
+ my $line = shift;
+ return $line unless gitweb_check_feature('email-privacy');
+ $line =~ s/<[^@>]+@[^>]+>/<redacted>/g;
+ return $line;
+}
+
sub parse_tag {
my $tag_id = shift;
my %tag;
@@ -3465,7 +3481,7 @@ sub parse_tag {
} elsif ($line =~ m/^tag (.+)$/) {
$tag{'name'} = $1;
} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
- $tag{'author'} = $1;
+ $tag{'author'} = hide_mailaddrs_if_private($1);
$tag{'author_epoch'} = $2;
$tag{'author_tz'} = $3;
if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
@@ -3513,7 +3529,7 @@ sub parse_commit_text {
} elsif ((!defined $withparents) && ($line =~ m/^parent ($oid_regex)$/)) {
push @parents, $1;
} elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
- $co{'author'} = to_utf8($1);
+ $co{'author'} = hide_mailaddrs_if_private(to_utf8($1));
$co{'author_epoch'} = $2;
$co{'author_tz'} = $3;
if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
@@ -3523,7 +3539,7 @@ sub parse_commit_text {
$co{'author_name'} = $co{'author'};
}
} elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
- $co{'committer'} = to_utf8($1);
+ $co{'committer'} = hide_mailaddrs_if_private(to_utf8($1));
$co{'committer_epoch'} = $2;
$co{'committer_tz'} = $3;
if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
@@ -3568,9 +3584,10 @@ sub parse_commit_text {
if (! defined $co{'title'} || $co{'title'} eq "") {
$co{'title'} = $co{'title_short'} = '(no commit message)';
}
- # remove added spaces
+ # remove added spaces, redact e-mail addresses if applicable.
foreach my $line (@commit_lines) {
$line =~ s/^ //;
+ $line = hide_mailaddrs_if_private($line);
}
$co{'comment'} = \@commit_lines;
@@ -7489,7 +7506,8 @@ sub git_log_generic {
-accesskey => "n", -title => "Alt-n"}, "next");
}
my $patch_max = gitweb_get_feature('patches');
- if ($patch_max && !defined $file_name) {
+ if ($patch_max && !defined $file_name &&
+ !gitweb_check_feature('email-privacy')) {
if ($patch_max < 0 || @commitlist <= $patch_max) {
$paging_nav .= " &sdot; " .
$cgi->a({-href => href(action=>"patches", -replay=>1)},
@@ -7550,7 +7568,8 @@ sub git_commit {
} @$parents ) .
')';
}
- if (gitweb_check_feature('patches') && @$parents <= 1) {
+ if (gitweb_check_feature('patches') && @$parents <= 1 &&
+ !gitweb_check_feature('email-privacy')) {
$formats_nav .= " | " .
$cgi->a({-href => href(action=>"patch", -replay=>1)},
"patch");
@@ -7863,7 +7882,8 @@ sub git_commitdiff {
$formats_nav =
$cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
"raw");
- if ($patch_max && @{$co{'parents'}} <= 1) {
+ if ($patch_max && @{$co{'parents'}} <= 1 &&
+ !gitweb_check_feature('email-privacy')) {
$formats_nav .= " | " .
$cgi->a({-href => href(action=>"patch", -replay=>1)},
"patch");
diff --git a/merge-ort.c b/merge-ort.c
index 3f55b438ec..b1795d838e 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -18,6 +18,7 @@
#include "merge-ort.h"
#include "alloc.h"
+#include "attr.h"
#include "blob.h"
#include "cache-tree.h"
#include "commit.h"
@@ -25,6 +26,7 @@
#include "diff.h"
#include "diffcore.h"
#include "dir.h"
+#include "entry.h"
#include "ll-merge.h"
#include "object-store.h"
#include "revision.h"
@@ -73,8 +75,12 @@ struct rename_info {
/*
* dirs_removed: directories removed on a given side of history.
+ *
+ * The keys of dirs_removed[side] are the directories that were removed
+ * on the given side of history. The value of the strintmap for each
+ * directory is a value from enum dir_rename_relevance.
*/
- struct strset dirs_removed[3];
+ struct strintmap dirs_removed[3];
/*
* dir_rename_count: tracking where parts of a directory were renamed to
@@ -95,18 +101,20 @@ struct rename_info {
struct strmap dir_renames[3];
/*
- * relevant_sources: deleted paths for which we need rename detection
+ * relevant_sources: deleted paths wanted in rename detection, and why
*
* relevant_sources is a set of deleted paths on each side of
* history for which we need rename detection. If a path is deleted
* on one side of history, we need to detect if it is part of a
* rename if either
- * * we need to detect renames for an ancestor directory
* * the file is modified/deleted on the other side of history
+ * * we need to detect renames for an ancestor directory
* If neither of those are true, we can skip rename detection for
- * that path.
+ * that path. The reason is stored as a value from enum
+ * file_rename_relevance, as the reason can inform the algorithm in
+ * diffcore_rename_extended().
*/
- struct strset relevant_sources[3];
+ struct strintmap relevant_sources[3];
/*
* dir_rename_mask:
@@ -215,6 +223,16 @@ struct merge_options_internal {
struct rename_info renames;
/*
+ * attr_index: hacky minimal index used for renormalization
+ *
+ * renormalization code _requires_ an index, though it only needs to
+ * find a .gitattributes file within the index. So, when
+ * renormalization is important, we create a special index with just
+ * that one file.
+ */
+ struct index_state attr_index;
+
+ /*
* current_dir_name, toplevel_dir: temporary vars
*
* These are used in collect_merge_info_callback(), and will set the
@@ -362,8 +380,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
int i;
void (*strmap_func)(struct strmap *, int) =
reinitialize ? strmap_partial_clear : strmap_clear;
- void (*strset_func)(struct strset *) =
- reinitialize ? strset_partial_clear : strset_clear;
+ void (*strintmap_func)(struct strintmap *) =
+ reinitialize ? strintmap_partial_clear : strintmap_clear;
/*
* We marked opti->paths with strdup_strings = 0, so that we
@@ -393,9 +411,12 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
string_list_clear(&opti->paths_to_free, 0);
opti->paths_to_free.strdup_strings = 0;
+ if (opti->attr_index.cache_nr) /* true iff opt->renormalize */
+ discard_index(&opti->attr_index);
+
/* Free memory used by various renames maps */
for (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {
- strset_func(&renames->dirs_removed[i]);
+ strintmap_func(&renames->dirs_removed[i]);
partial_clear_dir_rename_count(&renames->dir_rename_count[i]);
if (!reinitialize)
@@ -403,7 +424,7 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
strmap_func(&renames->dir_renames[i], 0);
- strset_func(&renames->relevant_sources[i]);
+ strintmap_func(&renames->relevant_sources[i]);
}
if (!reinitialize) {
@@ -673,8 +694,11 @@ static void add_pair(struct merge_options *opt,
unsigned content_relevant = (match_mask == 0);
unsigned location_relevant = (dir_rename_mask == 0x07);
- if (content_relevant || location_relevant)
- strset_add(&renames->relevant_sources[side], pathname);
+ if (content_relevant || location_relevant) {
+ /* content_relevant trumps location_relevant */
+ strintmap_set(&renames->relevant_sources[side], pathname,
+ content_relevant ? RELEVANT_CONTENT : RELEVANT_LOCATION);
+ }
}
one = alloc_filespec(pathname);
@@ -729,10 +753,41 @@ static void collect_rename_info(struct merge_options *opt,
if (dirmask == 1 || dirmask == 3 || dirmask == 5) {
/* absent_mask = 0x07 - dirmask; sides = absent_mask/2 */
unsigned sides = (0x07 - dirmask)/2;
+ unsigned relevance = (renames->dir_rename_mask == 0x07) ?
+ RELEVANT_FOR_ANCESTOR : NOT_RELEVANT;
+ /*
+ * Record relevance of this directory. However, note that
+ * when collect_merge_info_callback() recurses into this
+ * directory and calls collect_rename_info() on paths
+ * within that directory, if we find a path that was added
+ * to this directory on the other side of history, we will
+ * upgrade this value to RELEVANT_FOR_SELF; see below.
+ */
if (sides & 1)
- strset_add(&renames->dirs_removed[1], fullname);
+ strintmap_set(&renames->dirs_removed[1], fullname,
+ relevance);
if (sides & 2)
- strset_add(&renames->dirs_removed[2], fullname);
+ strintmap_set(&renames->dirs_removed[2], fullname,
+ relevance);
+ }
+
+ /*
+ * Here's the block that potentially upgrades to RELEVANT_FOR_SELF.
+ * When we run across a file added to a directory. In such a case,
+ * find the directory of the file and upgrade its relevance.
+ */
+ if (renames->dir_rename_mask == 0x07 &&
+ (filemask == 2 || filemask == 4)) {
+ /*
+ * Need directory rename for parent directory on other side
+ * of history from added file. Thus
+ * side = (~filemask & 0x06) >> 1
+ * or
+ * side = 3 - (filemask/2).
+ */
+ unsigned side = 3 - (filemask >> 1);
+ strintmap_set(&renames->dirs_removed[side], dirname,
+ RELEVANT_FOR_SELF);
}
if (filemask == 0 || filemask == 7)
@@ -1147,6 +1202,63 @@ static int merge_submodule(struct merge_options *opt,
return 0;
}
+static void initialize_attr_index(struct merge_options *opt)
+{
+ /*
+ * The renormalize_buffer() functions require attributes, and
+ * annoyingly those can only be read from the working tree or from
+ * an index_state. merge-ort doesn't have an index_state, so we
+ * generate a fake one containing only attribute information.
+ */
+ struct merged_info *mi;
+ struct index_state *attr_index = &opt->priv->attr_index;
+ struct cache_entry *ce;
+
+ attr_index->initialized = 1;
+
+ if (!opt->renormalize)
+ return;
+
+ mi = strmap_get(&opt->priv->paths, GITATTRIBUTES_FILE);
+ if (!mi)
+ return;
+
+ if (mi->clean) {
+ int len = strlen(GITATTRIBUTES_FILE);
+ ce = make_empty_cache_entry(attr_index, len);
+ ce->ce_mode = create_ce_mode(mi->result.mode);
+ ce->ce_flags = create_ce_flags(0);
+ ce->ce_namelen = len;
+ oidcpy(&ce->oid, &mi->result.oid);
+ memcpy(ce->name, GITATTRIBUTES_FILE, len);
+ add_index_entry(attr_index, ce,
+ ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+ get_stream_filter(attr_index, GITATTRIBUTES_FILE, &ce->oid);
+ } else {
+ int stage, len;
+ struct conflict_info *ci;
+
+ ASSIGN_AND_VERIFY_CI(ci, mi);
+ for (stage = 0; stage < 3; stage++) {
+ unsigned stage_mask = (1 << stage);
+
+ if (!(ci->filemask & stage_mask))
+ continue;
+ len = strlen(GITATTRIBUTES_FILE);
+ ce = make_empty_cache_entry(attr_index, len);
+ ce->ce_mode = create_ce_mode(ci->stages[stage].mode);
+ ce->ce_flags = create_ce_flags(stage);
+ ce->ce_namelen = len;
+ oidcpy(&ce->oid, &ci->stages[stage].oid);
+ memcpy(ce->name, GITATTRIBUTES_FILE, len);
+ add_index_entry(attr_index, ce,
+ ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+ get_stream_filter(attr_index, GITATTRIBUTES_FILE,
+ &ce->oid);
+ }
+ }
+}
+
static int merge_3way(struct merge_options *opt,
const char *path,
const struct object_id *o,
@@ -1161,6 +1273,9 @@ static int merge_3way(struct merge_options *opt,
char *base, *name1, *name2;
int merge_status;
+ if (!opt->priv->attr_index.initialized)
+ initialize_attr_index(opt);
+
ll_opts.renormalize = opt->renormalize;
ll_opts.extra_marker_size = extra_marker_size;
ll_opts.xdl_opts = opt->xdl_opts;
@@ -1199,7 +1314,7 @@ static int merge_3way(struct merge_options *opt,
merge_status = ll_merge(result_buf, path, &orig, base,
&src1, name1, &src2, name2,
- opt->repo->index, &ll_opts);
+ &opt->priv->attr_index, &ll_opts);
free(base);
free(name1);
@@ -1511,6 +1626,9 @@ static void get_provisional_directory_renames(struct merge_options *opt,
}
}
+ if (max == 0)
+ continue;
+
if (bad_max == max) {
path_msg(opt, source_dir, 0,
_("CONFLICT (directory rename split): "
@@ -1519,18 +1637,7 @@ static void get_provisional_directory_renames(struct merge_options *opt,
"no destination getting a majority of the "
"files."),
source_dir);
- /*
- * We should mark this as unclean IF something attempts
- * to use this rename. We do not yet have the logic
- * in place to detect if this directory rename is being
- * used, and optimizations that reduce the number of
- * renames cause this to falsely trigger. For now,
- * just disable it, causing t6423 testcase 2a to break.
- * We'll later fix the detection, and when we do we
- * will re-enable setting *clean to 0 (and thereby fix
- * t6423 testcase 2a).
- */
- /* *clean = 0; */
+ *clean = 0;
} else {
strmap_put(&renames->dir_renames[side],
source_dir, (void*)best);
@@ -2160,7 +2267,7 @@ static inline int possible_side_renames(struct rename_info *renames,
unsigned side_index)
{
return renames->pairs[side_index].nr > 0 &&
- !strset_empty(&renames->relevant_sources[side_index]);
+ !strintmap_empty(&renames->relevant_sources[side_index]);
}
static inline int possible_renames(struct rename_info *renames)
@@ -2361,7 +2468,7 @@ static int detect_and_process_renames(struct merge_options *opt,
clean &= collect_renames(opt, &combined, MERGE_SIDE2,
&renames->dir_renames[1],
&renames->dir_renames[2]);
- QSORT(combined.queue, combined.nr, compare_pairs);
+ STABLE_QSORT(combined.queue, combined.nr, compare_pairs);
trace2_region_leave("merge", "directory renames", opt->repo);
trace2_region_enter("merge", "process renames", opt->repo);
@@ -2431,6 +2538,61 @@ static int string_list_df_name_compare(const char *one, const char *two)
return onelen - twolen;
}
+static int read_oid_strbuf(struct merge_options *opt,
+ const struct object_id *oid,
+ struct strbuf *dst)
+{
+ void *buf;
+ enum object_type type;
+ unsigned long size;
+ buf = read_object_file(oid, &type, &size);
+ if (!buf)
+ return err(opt, _("cannot read object %s"), oid_to_hex(oid));
+ if (type != OBJ_BLOB) {
+ free(buf);
+ return err(opt, _("object %s is not a blob"), oid_to_hex(oid));
+ }
+ strbuf_attach(dst, buf, size, size + 1);
+ return 0;
+}
+
+static int blob_unchanged(struct merge_options *opt,
+ const struct version_info *base,
+ const struct version_info *side,
+ const char *path)
+{
+ struct strbuf basebuf = STRBUF_INIT;
+ struct strbuf sidebuf = STRBUF_INIT;
+ int ret = 0; /* assume changed for safety */
+ const struct index_state *idx = &opt->priv->attr_index;
+
+ if (!idx->initialized)
+ initialize_attr_index(opt);
+
+ if (base->mode != side->mode)
+ return 0;
+ if (oideq(&base->oid, &side->oid))
+ return 1;
+
+ if (read_oid_strbuf(opt, &base->oid, &basebuf) ||
+ read_oid_strbuf(opt, &side->oid, &sidebuf))
+ goto error_return;
+ /*
+ * Note: binary | is used so that both renormalizations are
+ * performed. Comparison can be skipped if both files are
+ * unchanged since their sha1s have already been compared.
+ */
+ if (renormalize_buffer(idx, path, basebuf.buf, basebuf.len, &basebuf) |
+ renormalize_buffer(idx, path, sidebuf.buf, sidebuf.len, &sidebuf))
+ ret = (basebuf.len == sidebuf.len &&
+ !memcmp(basebuf.buf, sidebuf.buf, basebuf.len));
+
+error_return:
+ strbuf_release(&basebuf);
+ strbuf_release(&sidebuf);
+ return ret;
+}
+
struct directory_versions {
/*
* versions: list of (basename -> version_info)
@@ -2498,6 +2660,7 @@ static void write_tree(struct object_id *result_oid,
assert(offset <= versions->nr);
nr = versions->nr - offset;
if (versions->nr)
+ /* No need for STABLE_QSORT -- filenames must be unique */
QSORT(versions->items + offset, nr, tree_entry_order);
/* Pre-allocate some space in buf */
@@ -3009,8 +3172,13 @@ static void process_entry(struct merge_options *opt,
modify_branch = (side == 1) ? opt->branch1 : opt->branch2;
delete_branch = (side == 1) ? opt->branch2 : opt->branch1;
- if (ci->path_conflict &&
- oideq(&ci->stages[0].oid, &ci->stages[side].oid)) {
+ if (opt->renormalize &&
+ blob_unchanged(opt, &ci->stages[0], &ci->stages[side],
+ path)) {
+ ci->merged.is_null = 1;
+ ci->merged.clean = 1;
+ } else if (ci->path_conflict &&
+ oideq(&ci->stages[0].oid, &ci->stages[side].oid)) {
/*
* This came from a rename/delete; no action to take,
* but avoid printing "modify/delete" conflict notice
@@ -3182,23 +3350,27 @@ static int checkout(struct merge_options *opt,
return ret;
}
-static int record_conflicted_index_entries(struct merge_options *opt,
- struct index_state *index,
- struct strmap *paths,
- struct strmap *conflicted)
+static int record_conflicted_index_entries(struct merge_options *opt)
{
struct hashmap_iter iter;
struct strmap_entry *e;
+ struct index_state *index = opt->repo->index;
+ struct checkout state = CHECKOUT_INIT;
int errs = 0;
int original_cache_nr;
- if (strmap_empty(conflicted))
+ if (strmap_empty(&opt->priv->conflicted))
return 0;
+ /* If any entries have skip_worktree set, we'll have to check 'em out */
+ state.force = 1;
+ state.quiet = 1;
+ state.refresh_cache = 1;
+ state.istate = index;
original_cache_nr = index->cache_nr;
/* Put every entry from paths into plist, then sort */
- strmap_for_each_entry(conflicted, &iter, e) {
+ strmap_for_each_entry(&opt->priv->conflicted, &iter, e) {
const char *path = e->key;
struct conflict_info *ci = e->value;
int pos;
@@ -3239,9 +3411,23 @@ static int record_conflicted_index_entries(struct merge_options *opt,
* the higher order stages. Thus, we need override
* the CE_SKIP_WORKTREE bit and manually write those
* files to the working disk here.
- *
- * TODO: Implement this CE_SKIP_WORKTREE fixup.
*/
+ if (ce_skip_worktree(ce)) {
+ struct stat st;
+
+ if (!lstat(path, &st)) {
+ char *new_name = unique_path(&opt->priv->paths,
+ path,
+ "cruft");
+
+ path_msg(opt, path, 1,
+ _("Note: %s not up to date and in way of checking out conflicted version; old copy renamed to %s"),
+ path, new_name);
+ errs |= rename(path, new_name);
+ free(new_name);
+ }
+ errs |= checkout_entry(ce, &state, NULL, NULL);
+ }
/*
* Mark this cache entry for removal and instead add
@@ -3273,6 +3459,11 @@ static int record_conflicted_index_entries(struct merge_options *opt,
* entries we added to the end into their right locations.
*/
remove_marked_cache_entries(index, 1);
+ /*
+ * No need for STABLE_QSORT -- cmp_cache_name_compare sorts primarily
+ * on filename and secondarily on stage, and (name, stage #) are a
+ * unique tuple.
+ */
QSORT(index->cache, index->cache_nr, cmp_cache_name_compare);
return errs;
@@ -3286,7 +3477,8 @@ void merge_switch_to_result(struct merge_options *opt,
{
assert(opt->priv == NULL);
if (result->clean >= 0 && update_worktree_and_index) {
- struct merge_options_internal *opti = result->priv;
+ const char *filename;
+ FILE *fp;
trace2_region_enter("merge", "checkout", opt->repo);
if (checkout(opt, head, result->tree)) {
@@ -3297,14 +3489,22 @@ void merge_switch_to_result(struct merge_options *opt,
trace2_region_leave("merge", "checkout", opt->repo);
trace2_region_enter("merge", "record_conflicted", opt->repo);
- if (record_conflicted_index_entries(opt, opt->repo->index,
- &opti->paths,
- &opti->conflicted)) {
+ opt->priv = result->priv;
+ if (record_conflicted_index_entries(opt)) {
/* failure to function */
+ opt->priv = NULL;
result->clean = -1;
return;
}
+ opt->priv = NULL;
trace2_region_leave("merge", "record_conflicted", opt->repo);
+
+ trace2_region_enter("merge", "write_auto_merge", opt->repo);
+ filename = git_path_auto_merge(opt->repo);
+ fp = xfopen(filename, "w");
+ fprintf(fp, "%s\n", oid_to_hex(&result->tree->object.oid));
+ fclose(fp);
+ trace2_region_leave("merge", "write_auto_merge", opt->repo);
}
if (display_update_msgs) {
@@ -3349,6 +3549,8 @@ void merge_finalize(struct merge_options *opt,
{
struct merge_options_internal *opti = result->priv;
+ if (opt->renormalize)
+ git_attr_set_direction(GIT_ATTR_CHECKIN);
assert(opt->priv == NULL);
clear_or_reinit_internal_opts(opti, 0);
@@ -3357,6 +3559,23 @@ void merge_finalize(struct merge_options *opt,
/*** Function Grouping: helper functions for merge_incore_*() ***/
+static struct tree *shift_tree_object(struct repository *repo,
+ struct tree *one, struct tree *two,
+ const char *subtree_shift)
+{
+ struct object_id shifted;
+
+ if (!*subtree_shift) {
+ shift_tree(repo, &one->object.oid, &two->object.oid, &shifted, 0);
+ } else {
+ shift_tree_by(repo, &one->object.oid, &two->object.oid, &shifted,
+ subtree_shift);
+ }
+ if (oideq(&two->object.oid, &shifted))
+ return two;
+ return lookup_tree(repo, &shifted);
+}
+
static inline void set_commit_tree(struct commit *c, struct tree *t)
{
c->maybe_tree = t;
@@ -3424,6 +3643,10 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
/* Default to histogram diff. Actually, just hardcode it...for now. */
opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF);
+ /* Handle attr direction stuff for renormalization */
+ if (opt->renormalize)
+ git_attr_set_direction(GIT_ATTR_CHECKOUT);
+
/* Initialization of opt->priv, our internal merge data */
trace2_region_enter("merge", "allocate/init", opt->repo);
if (opt->priv) {
@@ -3436,14 +3659,14 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
/* Initialization of various renames fields */
renames = &opt->priv->renames;
for (i = MERGE_SIDE1; i <= MERGE_SIDE2; i++) {
- strset_init_with_options(&renames->dirs_removed[i],
- NULL, 0);
+ strintmap_init_with_options(&renames->dirs_removed[i],
+ NOT_RELEVANT, NULL, 0);
strmap_init_with_options(&renames->dir_rename_count[i],
NULL, 1);
strmap_init_with_options(&renames->dir_renames[i],
NULL, 0);
- strset_init_with_options(&renames->relevant_sources[i],
- NULL, 0);
+ strintmap_init_with_options(&renames->relevant_sources[i],
+ 0, NULL, 0);
}
/*
@@ -3482,6 +3705,13 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,
{
struct object_id working_tree_oid;
+ if (opt->subtree_shift) {
+ side2 = shift_tree_object(opt->repo, side1, side2,
+ opt->subtree_shift);
+ merge_base = shift_tree_object(opt->repo, side1, merge_base,
+ opt->subtree_shift);
+ }
+
trace2_region_enter("merge", "collect_merge_info", opt->repo);
if (collect_merge_info(opt, merge_base, side1, side2) != 0) {
/*
diff --git a/merge-recursive.c b/merge-recursive.c
index ed31f9496c..7618303f7b 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1075,6 +1075,11 @@ static int merge_3way(struct merge_options *opt,
read_mmblob(&src1, &a->oid);
read_mmblob(&src2, &b->oid);
+ /*
+ * FIXME: Using a->path for normalization rules in ll_merge could be
+ * wrong if we renamed from a->path to b->path. We should use the
+ * target path for where the file will be written.
+ */
merge_status = ll_merge(result_buf, a->path, &orig, base,
&src1, name1, &src2, name2,
opt->repo->index, &ll_opts);
@@ -1154,6 +1159,8 @@ static void print_commit(struct commit *commit)
struct strbuf sb = STRBUF_INIT;
struct pretty_print_context ctx = {0};
ctx.date_mode.type = DATE_NORMAL;
+ /* FIXME: Merge this with output_commit_title() */
+ assert(!merge_remote_util(commit));
format_commit_message(commit, " %h: %m %s", &sb, &ctx);
fprintf(stderr, "%s\n", sb.buf);
strbuf_release(&sb);
@@ -1177,6 +1184,11 @@ static int merge_submodule(struct merge_options *opt,
int search = !opt->priv->call_depth;
/* store a in result in case we fail */
+ /* FIXME: This is the WRONG resolution for the recursive case when
+ * we need to be careful to avoid accidentally matching either side.
+ * Should probably use o instead there, much like we do for merging
+ * binaries.
+ */
oidcpy(result, a);
/* we can not handle deletion conflicts */
@@ -1301,6 +1313,13 @@ static int merge_mode_and_contents(struct merge_options *opt,
if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
result->clean = 0;
+ /*
+ * FIXME: This is a bad resolution for recursive case; for
+ * the recursive case we want something that is unlikely to
+ * accidentally match either side. Also, while it makes
+ * sense to prefer regular files over symlinks, it doesn't
+ * make sense to prefer regular files over submodules.
+ */
if (S_ISREG(a->mode)) {
result->blob.mode = a->mode;
oidcpy(&result->blob.oid, &a->oid);
@@ -1349,6 +1368,7 @@ static int merge_mode_and_contents(struct merge_options *opt,
free(result_buf.ptr);
if (ret)
return ret;
+ /* FIXME: bug, what if modes didn't match? */
result->clean = (merge_status == 0);
} else if (S_ISGITLINK(a->mode)) {
result->clean = merge_submodule(opt, &result->blob.oid,
@@ -2663,6 +2683,14 @@ static int process_renames(struct merge_options *opt,
struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
const struct rename *sre;
+ /*
+ * FIXME: As string-list.h notes, it's O(n^2) to build a sorted
+ * string_list one-by-one, but O(n log n) to build it unsorted and
+ * then sort it. Note that as we build the list, we do not need to
+ * check if the existing destination path is already in the list,
+ * because the structure of diffcore_rename guarantees we won't
+ * have duplicates.
+ */
for (i = 0; i < a_renames->nr; i++) {
sre = a_renames->items[i].util;
string_list_insert(&a_by_dst, sre->pair->two->path)->util
@@ -3601,6 +3629,15 @@ static int merge_recursive_internal(struct merge_options *opt,
return err(opt, _("merge returned no commit"));
}
+ /*
+ * FIXME: Since merge_recursive_internal() is only ever called by
+ * places that ensure the index is loaded first
+ * (e.g. builtin/merge.c, rebase/sequencer, etc.), in the common
+ * case where the merge base was unique that means when we get here
+ * we immediately discard the index and re-read it, which is a
+ * complete waste of time. We should only be discarding and
+ * re-reading if we were forced to recurse.
+ */
discard_index(opt->repo->index);
if (!opt->priv->call_depth)
repo_read_index(opt->repo);
diff --git a/pack-bitmap.c b/pack-bitmap.c
index b4513f8672..3ed15431cd 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -13,6 +13,7 @@
#include "repository.h"
#include "object-store.h"
#include "list-objects-filter-options.h"
+#include "config.h"
/*
* An entry on the bitmap index, representing the bitmap for a given
@@ -1351,6 +1352,24 @@ void test_bitmap_walk(struct rev_info *revs)
free_bitmap_index(bitmap_git);
}
+int test_bitmap_commits(struct repository *r)
+{
+ struct bitmap_index *bitmap_git = prepare_bitmap_git(r);
+ struct object_id oid;
+ MAYBE_UNUSED void *value;
+
+ if (!bitmap_git)
+ die("failed to load bitmap indexes");
+
+ kh_foreach(bitmap_git->bitmaps, oid, value, {
+ printf("%s\n", oid_to_hex(&oid));
+ });
+
+ free_bitmap_index(bitmap_git);
+
+ return 0;
+}
+
int rebuild_bitmap(const uint32_t *reposition,
struct ewah_bitmap *source,
struct bitmap *dest)
@@ -1512,3 +1531,8 @@ off_t get_disk_usage_from_bitmap(struct bitmap_index *bitmap_git,
return total;
}
+
+const struct string_list *bitmap_preferred_tips(struct repository *r)
+{
+ return repo_config_get_value_multi(r, "pack.preferbitmaptips");
+}
diff --git a/pack-bitmap.h b/pack-bitmap.h
index 36d99930d8..78f2b3ff79 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -5,6 +5,7 @@
#include "khash.h"
#include "pack.h"
#include "pack-objects.h"
+#include "string-list.h"
struct commit;
struct repository;
@@ -49,6 +50,7 @@ void traverse_bitmap_commit_list(struct bitmap_index *,
struct rev_info *revs,
show_reachable_fn show_reachable);
void test_bitmap_walk(struct rev_info *revs);
+int test_bitmap_commits(struct repository *r);
struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
struct list_objects_filter_options *filter);
int reuse_partial_packfile_from_bitmap(struct bitmap_index *,
@@ -90,4 +92,6 @@ void bitmap_writer_finish(struct pack_idx_entry **index,
const char *filename,
uint16_t options);
+const struct string_list *bitmap_preferred_tips(struct repository *r);
+
#endif
diff --git a/path.c b/path.c
index 7b385e5eb2..9e883eb524 100644
--- a/path.c
+++ b/path.c
@@ -1534,5 +1534,6 @@ REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE")
REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD")
REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH")
+REPO_GIT_PATH_FUNC(auto_merge, "AUTO_MERGE")
REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD")
REPO_GIT_PATH_FUNC(shallow, "shallow")
diff --git a/path.h b/path.h
index e7e77da6aa..251c78d980 100644
--- a/path.h
+++ b/path.h
@@ -176,6 +176,7 @@ struct path_cache {
const char *merge_mode;
const char *merge_head;
const char *merge_autostash;
+ const char *auto_merge;
const char *fetch_head;
const char *shallow;
};
@@ -191,6 +192,7 @@ const char *git_path_merge_rr(struct repository *r);
const char *git_path_merge_mode(struct repository *r);
const char *git_path_merge_head(struct repository *r);
const char *git_path_merge_autostash(struct repository *r);
+const char *git_path_auto_merge(struct repository *r);
const char *git_path_fetch_head(struct repository *r);
const char *git_path_shallow(struct repository *r);
diff --git a/ref-filter.c b/ref-filter.c
index f0bd32f714..a0adb4551d 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -1608,7 +1608,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj
if (oi->info.contentp) {
*obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten);
- if (!obj) {
+ if (!*obj) {
if (!eaten)
free(oi->content);
return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
diff --git a/sequencer.c b/sequencer.c
index fd183b5593..c29a36824c 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2281,6 +2281,7 @@ static int do_pick_commit(struct repository *r,
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
NULL, 0);
unlink(git_path_merge_msg(r));
+ unlink(git_path_auto_merge(r));
fprintf(stderr,
_("dropping %s %s -- patch contents already upstream\n"),
oid_to_hex(&commit->object.oid), msg.subject);
@@ -2644,6 +2645,8 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)
need_cleanup = 1;
}
+ unlink(git_path_auto_merge(r));
+
if (!need_cleanup)
return;
@@ -4304,6 +4307,7 @@ static int pick_commits(struct repository *r,
unlink(rebase_path_stopped_sha());
unlink(rebase_path_amend());
unlink(git_path_merge_head(r));
+ unlink(git_path_auto_merge(r));
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
if (item->command == TODO_BREAK) {
@@ -4717,6 +4721,7 @@ static int commit_staged_changes(struct repository *r,
return error(_("could not commit staged changes."));
unlink(rebase_path_amend());
unlink(git_path_merge_head(r));
+ unlink(git_path_auto_merge(r));
if (final_fixup) {
unlink(rebase_path_fixup_msg());
unlink(rebase_path_squash_msg());
diff --git a/setup.c b/setup.c
index c04cd25a30..59e2facd9d 100644
--- a/setup.c
+++ b/setup.c
@@ -1274,18 +1274,10 @@ const char *setup_git_directory_gently(int *nongit_ok)
* the GIT_PREFIX environment variable must always match. For details
* see Documentation/config/alias.txt.
*/
- if (nongit_ok && *nongit_ok) {
+ if (nongit_ok && *nongit_ok)
startup_info->have_repository = 0;
- startup_info->prefix = NULL;
- setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
- } else {
+ else
startup_info->have_repository = 1;
- startup_info->prefix = prefix;
- if (prefix)
- setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
- else
- setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
- }
/*
* Not all paths through the setup code will call 'set_git_dir()' (which
@@ -1311,6 +1303,22 @@ const char *setup_git_directory_gently(int *nongit_ok)
if (startup_info->have_repository)
repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
}
+ /*
+ * Since precompose_string_if_needed() needs to look at
+ * the core.precomposeunicode configuration, this
+ * has to happen after the above block that finds
+ * out where the repository is, i.e. a preparation
+ * for calling git_config_get_bool().
+ */
+ if (prefix) {
+ prefix = precompose_string_if_needed(prefix);
+ startup_info->prefix = prefix;
+ setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
+ } else {
+ startup_info->prefix = NULL;
+ setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
+ }
+
strbuf_release(&dir);
strbuf_release(&gitdir);
diff --git a/t/helper/test-bitmap.c b/t/helper/test-bitmap.c
new file mode 100644
index 0000000000..134a1e9d76
--- /dev/null
+++ b/t/helper/test-bitmap.c
@@ -0,0 +1,24 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "pack-bitmap.h"
+
+static int bitmap_list_commits(void)
+{
+ return test_bitmap_commits(the_repository);
+}
+
+int cmd__bitmap(int argc, const char **argv)
+{
+ setup_git_directory();
+
+ if (argc != 2)
+ goto usage;
+
+ if (!strcmp(argv[1], "list-commits"))
+ return bitmap_list_commits();
+
+usage:
+ usage("\ttest-tool bitmap list-commits");
+
+ return -1;
+}
diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c
index 2a1ae3dae6..ad3ef1cd77 100644
--- a/t/helper/test-bloom.c
+++ b/t/helper/test-bloom.c
@@ -48,7 +48,7 @@ static void get_bloom_filter_for_commit(const struct object_id *commit_oid)
static const char *bloom_usage = "\n"
" test-tool bloom get_murmur3 <string>\n"
" test-tool bloom generate_filter <string> [<string>...]\n"
-" test-tool get_filter_for_commit <commit-hex>\n";
+" test-tool bloom get_filter_for_commit <commit-hex>\n";
int cmd__bloom(int argc, const char **argv)
{
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 287aa60023..25c6a37e93 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -15,6 +15,7 @@ struct test_cmd {
static struct test_cmd cmds[] = {
{ "advise", cmd__advise_if_enabled },
+ { "bitmap", cmd__bitmap },
{ "bloom", cmd__bloom },
{ "chmtime", cmd__chmtime },
{ "config", cmd__config },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 9ea4b31011..f03c5988b2 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -5,6 +5,7 @@
#include "git-compat-util.h"
int cmd__advise_if_enabled(int argc, const char **argv);
+int cmd__bitmap(int argc, const char **argv);
int cmd__bloom(int argc, const char **argv);
int cmd__chmtime(int argc, const char **argv);
int cmd__config(int argc, const char **argv);
diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh
index 822f2d4bfb..c657840db3 100755
--- a/t/t3512-cherry-pick-submodule.sh
+++ b/t/t3512-cherry-pick-submodule.sh
@@ -8,8 +8,11 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-submodule-update.sh
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+ KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+fi
test_submodule_switch "cherry-pick"
test_expect_success 'unrelated submodule/file conflict is ignored' '
diff --git a/t/t3513-revert-submodule.sh b/t/t3513-revert-submodule.sh
index a759f12cbb..74cd96e582 100755
--- a/t/t3513-revert-submodule.sh
+++ b/t/t3513-revert-submodule.sh
@@ -30,7 +30,10 @@ git_revert () {
git revert HEAD
}
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+fi
test_submodule_switch_func "git_revert"
test_done
diff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh
index d62db3fbe1..65147efdea 100755
--- a/t/t4108-apply-threeway.sh
+++ b/t/t4108-apply-threeway.sh
@@ -160,4 +160,74 @@ test_expect_success 'apply -3 with add/add conflict (dirty working tree)' '
test_cmp three.save three
'
+test_expect_success 'apply -3 with ambiguous repeating file' '
+ git reset --hard &&
+ test_write_lines 1 2 1 2 1 2 1 2 1 2 1 >one_two_repeat &&
+ git add one_two_repeat &&
+ git commit -m "init one" &&
+ test_write_lines 1 2 1 2 1 2 1 2 one 2 1 >one_two_repeat &&
+ git commit -a -m "change one" &&
+
+ git diff HEAD~ >Repeat.diff &&
+ git reset --hard HEAD~ &&
+
+ test_write_lines 1 2 1 2 1 2 one 2 1 2 one >one_two_repeat &&
+ git commit -a -m "change surrounding one" &&
+
+ git apply --index --3way Repeat.diff &&
+ test_write_lines 1 2 1 2 1 2 one 2 one 2 one >expect &&
+
+ test_cmp expect one_two_repeat
+'
+
+test_expect_success 'apply with --3way --cached clean apply' '
+ # Merging side should be similar to applying this patch
+ git diff ...side >P.diff &&
+
+ # The corresponding cleanly applied merge
+ git reset --hard &&
+ git checkout main~ &&
+ git merge --no-commit side &&
+ git ls-files -s >expect.ls &&
+
+ # should succeed
+ git reset --hard &&
+ git checkout main~ &&
+ git apply --cached --3way P.diff &&
+ git ls-files -s >actual.ls &&
+ print_sanitized_conflicted_diff >actual.diff &&
+
+ # The cache should resemble the corresponding merge
+ # (both files at stage #0)
+ test_cmp expect.ls actual.ls &&
+ # However the working directory should not change
+ >expect.diff &&
+ test_cmp expect.diff actual.diff
+'
+
+test_expect_success 'apply with --3way --cached and conflicts' '
+ # Merging side should be similar to applying this patch
+ git diff ...side >P.diff &&
+
+ # The corresponding conflicted merge
+ git reset --hard &&
+ git checkout main^0 &&
+ test_must_fail git merge --no-commit side &&
+ git ls-files -s >expect.ls &&
+
+ # should fail to apply
+ git reset --hard &&
+ git checkout main^0 &&
+ test_must_fail git apply --cached --3way P.diff &&
+ git ls-files -s >actual.ls &&
+ print_sanitized_conflicted_diff >actual.diff &&
+
+ # The cache should resemble the corresponding merge
+ # (one file at stage #0, one file at stages #1 #2 #3)
+ test_cmp expect.ls actual.ls &&
+ # However the working directory should not change
+ >expect.diff &&
+ test_cmp expect.diff actual.diff
+'
+
test_done
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
index 40b9f63244..f53efc8229 100755
--- a/t/t5310-pack-bitmaps.sh
+++ b/t/t5310-pack-bitmaps.sh
@@ -554,4 +554,42 @@ test_expect_success 'fetch with bitmaps can reuse old base' '
)
'
+test_expect_success 'pack.preferBitmapTips' '
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ # create enough commits that not all are receive bitmap
+ # coverage even if they are all at the tip of some reference.
+ test_commit_bulk --message="%s" 103 &&
+
+ git rev-list HEAD >commits.raw &&
+ sort <commits.raw >commits &&
+
+ git log --format="create refs/tags/%s %H" HEAD >refs &&
+ git update-ref --stdin <refs &&
+
+ git repack -adb &&
+ test-tool bitmap list-commits | sort >bitmaps &&
+
+ # remember which commits did not receive bitmaps
+ comm -13 bitmaps commits >before &&
+ test_file_not_empty before &&
+
+ # mark the commits which did not receive bitmaps as preferred,
+ # and generate the bitmap again
+ perl -pe "s{^}{create refs/tags/include/$. }" <before |
+ git update-ref --stdin &&
+ git -c pack.preferBitmapTips=refs/tags/include repack -adb &&
+
+ # finally, check that the commit(s) without bitmap coverage
+ # are not the same ones as before
+ test-tool bitmap list-commits | sort >bitmaps &&
+ comm -13 bitmaps commits >after &&
+
+ ! test_cmp before after
+ )
+'
+
test_done
diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh
index 29537f4798..4f92a116e1 100755
--- a/t/t5572-pull-submodule.sh
+++ b/t/t5572-pull-submodule.sh
@@ -42,8 +42,11 @@ git_pull_noff () {
$2 git pull --no-ff
}
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+ KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+fi
test_submodule_switch_func "git_pull_noff"
test_expect_success 'pull --recurse-submodule setup' '
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index cac7f443d0..2e7c32d50c 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -1134,4 +1134,14 @@ test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
test_cmp expect actual
'
+test_expect_success 'for-each-ref reports broken tags' '
+ git tag -m "good tag" broken-tag-good HEAD &&
+ git cat-file tag broken-tag-good >good &&
+ sed s/commit/blob/ <good >bad &&
+ bad=$(git hash-object -w -t tag bad) &&
+ git update-ref refs/tags/broken-tag-bad $bad &&
+ test_must_fail git for-each-ref --format="%(*objectname)" \
+ refs/tags/broken-tag-*
+'
+
test_done
diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh
index 379aac0103..7134769149 100755
--- a/t/t6423-merge-rename-directories.sh
+++ b/t/t6423-merge-rename-directories.sh
@@ -4797,7 +4797,7 @@ test_setup_12f () {
)
}
-test_expect_merge_algorithm failure success '12f: Trivial directory resolve, caching, all kinds of fun' '
+test_expect_merge_algorithm failure failure '12f: Trivial directory resolve, caching, all kinds of fun' '
test_setup_12f &&
(
cd 12f &&
diff --git a/t/t6428-merge-conflicts-sparse.sh b/t/t6428-merge-conflicts-sparse.sh
new file mode 100755
index 0000000000..7e8bf497f8
--- /dev/null
+++ b/t/t6428-merge-conflicts-sparse.sh
@@ -0,0 +1,158 @@
+#!/bin/sh
+
+test_description="merge cases"
+
+# The setup for all of them, pictorially, is:
+#
+# A
+# o
+# / \
+# O o ?
+# \ /
+# o
+# B
+#
+# To help make it easier to follow the flow of tests, they have been
+# divided into sections and each test will start with a quick explanation
+# of what commits O, A, and B contain.
+#
+# Notation:
+# z/{b,c} means files z/b and z/c both exist
+# x/d_1 means file x/d exists with content d1. (Purpose of the
+# underscore notation is to differentiate different
+# files that might be renamed into each other's paths.)
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-merge.sh
+
+
+# Testcase basic, conflicting changes in 'numerals'
+
+test_setup_numerals () {
+ test_create_repo numerals_$1 &&
+ (
+ cd numerals_$1 &&
+
+ >README &&
+ test_write_lines I II III >numerals &&
+ git add README numerals &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_write_lines I II III IIII >numerals &&
+ git add numerals &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ test_write_lines I II III IV >numerals &&
+ git add numerals &&
+ test_tick &&
+ git commit -m "B" &&
+
+ cat <<-EOF >expected-index &&
+ H README
+ M numerals
+ M numerals
+ M numerals
+ EOF
+
+ cat <<-EOF >expected-merge
+ I
+ II
+ III
+ <<<<<<< HEAD
+ IIII
+ =======
+ IV
+ >>>>>>> B^0
+ EOF
+
+ )
+}
+
+test_expect_success 'conflicting entries written to worktree even if sparse' '
+ test_setup_numerals plain &&
+ (
+ cd numerals_plain &&
+
+ git checkout A^0 &&
+
+ test_path_is_file README &&
+ test_path_is_file numerals &&
+
+ git sparse-checkout init &&
+ git sparse-checkout set README &&
+
+ test_path_is_file README &&
+ test_path_is_missing numerals &&
+
+ test_must_fail git merge -s recursive B^0 &&
+
+ git ls-files -t >index_files &&
+ test_cmp expected-index index_files &&
+
+ test_path_is_file README &&
+ test_path_is_file numerals &&
+
+ test_cmp expected-merge numerals &&
+
+ # 4 other files:
+ # * expected-merge
+ # * expected-index
+ # * index_files
+ # * others
+ git ls-files -o >others &&
+ test_line_count = 4 others
+ )
+'
+
+test_expect_merge_algorithm failure success 'present-despite-SKIP_WORKTREE handled reasonably' '
+ test_setup_numerals in_the_way &&
+ (
+ cd numerals_in_the_way &&
+
+ git checkout A^0 &&
+
+ test_path_is_file README &&
+ test_path_is_file numerals &&
+
+ git sparse-checkout init &&
+ git sparse-checkout set README &&
+
+ test_path_is_file README &&
+ test_path_is_missing numerals &&
+
+ echo foobar >numerals &&
+
+ test_must_fail git merge -s recursive B^0 &&
+
+ git ls-files -t >index_files &&
+ test_cmp expected-index index_files &&
+
+ test_path_is_file README &&
+ test_path_is_file numerals &&
+
+ test_cmp expected-merge numerals &&
+
+ # There should still be a file with "foobar" in it
+ grep foobar * &&
+
+ # 5 other files:
+ # * expected-merge
+ # * expected-index
+ # * index_files
+ # * others
+ # * whatever name was given to the numerals file that had
+ # "foobar" in it
+ git ls-files -o >others &&
+ test_line_count = 5 others
+ )
+'
+
+test_done
diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh
index 0f92bcf326..e5e89c2045 100755
--- a/t/t6437-submodule-merge.sh
+++ b/t/t6437-submodule-merge.sh
@@ -6,6 +6,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-merge.sh
#
# history
@@ -328,7 +329,7 @@ test_expect_success 'setup file/submodule conflict' '
)
'
-test_expect_failure 'file/submodule conflict' '
+test_expect_merge_algorithm failure success 'file/submodule conflict' '
test_when_finished "git -C file-submodule reset --hard" &&
(
cd file-submodule &&
@@ -437,7 +438,7 @@ test_expect_failure 'directory/submodule conflict; keep submodule clean' '
)
'
-test_expect_failure !FAIL_PREREQS 'directory/submodule conflict; should not treat submodule files as untracked or in the way' '
+test_expect_merge_algorithm failure success !FAIL_PREREQS 'directory/submodule conflict; should not treat submodule files as untracked or in the way' '
test_when_finished "git -C directory-submodule/path reset --hard" &&
test_when_finished "git -C directory-submodule reset --hard" &&
(
diff --git a/t/t6438-submodule-directory-file-conflicts.sh b/t/t6438-submodule-directory-file-conflicts.sh
index 04bf4be7d7..8df67a0ef9 100755
--- a/t/t6438-submodule-directory-file-conflicts.sh
+++ b/t/t6438-submodule-directory-file-conflicts.sh
@@ -12,8 +12,11 @@ test_submodule_switch "merge --ff"
test_submodule_switch "merge --ff-only"
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+ KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+fi
test_submodule_switch "merge --no-ff"
test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 1a1caf8f2e..65b3035371 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -415,15 +415,23 @@ test_expect_success $PREREQ 'reject long lines' '
z512=$z64$z64$z64$z64$z64$z64$z64$z64 &&
clean_fake_sendmail &&
cp $patches longline.patch &&
- echo $z512$z512 >>longline.patch &&
+ cat >>longline.patch <<-EOF &&
+ $z512$z512
+ not a long line
+ $z512$z512
+ EOF
test_must_fail git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
--transfer-encoding=8bit \
$patches longline.patch \
- 2>errors &&
- grep longline.patch errors
+ 2>actual &&
+ cat >expect <<-\EOF &&
+ fatal: longline.patch:35 is longer than 998 characters
+ warning: no patches were sent
+ EOF
+ test_cmp expect actual
'
test_expect_success $PREREQ 'no patch was sent' '
@@ -527,22 +535,33 @@ test_expect_success $PREREQ "--validate respects relative core.hooksPath path" '
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
--validate \
- longline.patch 2>err &&
+ longline.patch 2>actual &&
test_path_is_file my-hooks.ran &&
- grep "rejected by sendemail-validate" err
+ cat >expect <<-EOF &&
+ fatal: longline.patch: rejected by sendemail-validate hook
+ fatal: command '"'"'$(pwd)/my-hooks/sendemail-validate'"'"' died with exit code 1
+ warning: no patches were sent
+ EOF
+ test_cmp expect actual
'
test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" '
- test_config core.hooksPath "$(pwd)/my-hooks" &&
+ hooks_path="$(pwd)/my-hooks" &&
+ test_config core.hooksPath "$hooks_path" &&
test_when_finished "rm my-hooks.ran" &&
test_must_fail git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
--validate \
- longline.patch 2>err &&
+ longline.patch 2>actual &&
test_path_is_file my-hooks.ran &&
- grep "rejected by sendemail-validate" err
+ cat >expect <<-EOF &&
+ fatal: longline.patch: rejected by sendemail-validate hook
+ fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1
+ warning: no patches were sent
+ EOF
+ test_cmp expect actual
'
for enc in 7bit 8bit quoted-printable base64
diff --git a/t/test-lib.sh b/t/test-lib.sh
index d3f6af6a65..3dec266221 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -448,6 +448,8 @@ export EDITOR
GIT_DEFAULT_HASH="${GIT_TEST_DEFAULT_HASH:-sha1}"
export GIT_DEFAULT_HASH
+GIT_TEST_MERGE_ALGORITHM="${GIT_TEST_MERGE_ALGORITHM:-ort}"
+export GIT_TEST_MERGE_ALGORITHM
# Tests using GIT_TRACE typically don't want <timestamp> <file>:<line> output
GIT_TRACE_BARE=1