Merge branch 'en/merge-ort-prepare-to-remove-recursive'
First step of deprecating and removing merge-recursive. * en/merge-ort-prepare-to-remove-recursive: am: switch from merge_recursive_generic() to merge_ort_generic() merge-ort: fix merge.directoryRenames=false t3650: document bug when directory renames are turned off merge-ort: support having merge verbosity be set to 0 merge-ort: allow rename detection to be disabled merge-ort: add new merge_ort_generic() function
This commit is contained in:
@@ -82,6 +82,11 @@ find-renames[=<n>];;
|
|||||||
rename-threshold=<n>;;
|
rename-threshold=<n>;;
|
||||||
Deprecated synonym for `find-renames=<n>`.
|
Deprecated synonym for `find-renames=<n>`.
|
||||||
|
|
||||||
|
no-renames;;
|
||||||
|
Turn off rename detection. This overrides the `merge.renames`
|
||||||
|
configuration variable.
|
||||||
|
See also linkgit:git-diff[1] `--no-renames`.
|
||||||
|
|
||||||
subtree[=<path>];;
|
subtree[=<path>];;
|
||||||
This option is a more advanced form of 'subtree' strategy, where
|
This option is a more advanced form of 'subtree' strategy, where
|
||||||
the strategy makes a guess on how two trees must be shifted to
|
the strategy makes a guess on how two trees must be shifted to
|
||||||
@@ -107,7 +112,7 @@ For a path that is a submodule, the same caution as 'ort' applies to this
|
|||||||
strategy.
|
strategy.
|
||||||
+
|
+
|
||||||
The 'recursive' strategy takes the same options as 'ort'. However,
|
The 'recursive' strategy takes the same options as 'ort'. However,
|
||||||
there are three additional options that 'ort' ignores (not documented
|
there are two additional options that 'ort' ignores (not documented
|
||||||
above) that are potentially useful with the 'recursive' strategy:
|
above) that are potentially useful with the 'recursive' strategy:
|
||||||
|
|
||||||
patience;;
|
patience;;
|
||||||
@@ -121,11 +126,6 @@ diff-algorithm=[patience|minimal|histogram|myers];;
|
|||||||
specifically uses `diff-algorithm=histogram`, while `recursive`
|
specifically uses `diff-algorithm=histogram`, while `recursive`
|
||||||
defaults to the `diff.algorithm` config setting.
|
defaults to the `diff.algorithm` config setting.
|
||||||
|
|
||||||
no-renames;;
|
|
||||||
Turn off rename detection. This overrides the `merge.renames`
|
|
||||||
configuration variable.
|
|
||||||
See also linkgit:git-diff[1] `--no-renames`.
|
|
||||||
|
|
||||||
resolve::
|
resolve::
|
||||||
This can only resolve two heads (i.e. the current branch
|
This can only resolve two heads (i.e. the current branch
|
||||||
and another branch you pulled from) using a 3-way merge
|
and another branch you pulled from) using a 3-way merge
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
#include "preload-index.h"
|
#include "preload-index.h"
|
||||||
#include "sequencer.h"
|
#include "sequencer.h"
|
||||||
#include "revision.h"
|
#include "revision.h"
|
||||||
#include "merge-recursive.h"
|
#include "merge-ort-wrappers.h"
|
||||||
#include "log-tree.h"
|
#include "log-tree.h"
|
||||||
#include "notes-utils.h"
|
#include "notes-utils.h"
|
||||||
#include "rerere.h"
|
#include "rerere.h"
|
||||||
@@ -1638,12 +1638,13 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
|
|||||||
o.branch1 = "HEAD";
|
o.branch1 = "HEAD";
|
||||||
their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
|
their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
|
||||||
o.branch2 = their_tree_name;
|
o.branch2 = their_tree_name;
|
||||||
|
o.ancestor = "constructed fake ancestor";
|
||||||
o.detect_directory_renames = MERGE_DIRECTORY_RENAMES_NONE;
|
o.detect_directory_renames = MERGE_DIRECTORY_RENAMES_NONE;
|
||||||
|
|
||||||
if (state->quiet)
|
if (state->quiet)
|
||||||
o.verbosity = 0;
|
o.verbosity = 0;
|
||||||
|
|
||||||
if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
|
if (merge_ort_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
|
||||||
repo_rerere(the_repository, state->allow_rerere_autoupdate);
|
repo_rerere(the_repository, state->allow_rerere_autoupdate);
|
||||||
free(their_tree_name);
|
free(their_tree_name);
|
||||||
return error(_("Failed to merge in the changes."));
|
return error(_("Failed to merge in the changes."));
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
#include "git-compat-util.h"
|
#include "git-compat-util.h"
|
||||||
#include "gettext.h"
|
#include "gettext.h"
|
||||||
#include "hash.h"
|
#include "hash.h"
|
||||||
|
#include "hex.h"
|
||||||
|
#include "lockfile.h"
|
||||||
#include "merge-ort.h"
|
#include "merge-ort.h"
|
||||||
#include "merge-ort-wrappers.h"
|
#include "merge-ort-wrappers.h"
|
||||||
#include "read-cache-ll.h"
|
#include "read-cache-ll.h"
|
||||||
|
#include "repository.h"
|
||||||
|
#include "tag.h"
|
||||||
#include "tree.h"
|
#include "tree.h"
|
||||||
|
|
||||||
#include "commit.h"
|
#include "commit.h"
|
||||||
@@ -29,6 +33,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,
|
|||||||
struct tree *merge_base)
|
struct tree *merge_base)
|
||||||
{
|
{
|
||||||
struct merge_result result;
|
struct merge_result result;
|
||||||
|
int show_msgs;
|
||||||
|
|
||||||
if (unclean(opt, head))
|
if (unclean(opt, head))
|
||||||
return -1;
|
return -1;
|
||||||
@@ -38,9 +43,10 @@ int merge_ort_nonrecursive(struct merge_options *opt,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show_msgs = !!opt->verbosity;
|
||||||
memset(&result, 0, sizeof(result));
|
memset(&result, 0, sizeof(result));
|
||||||
merge_incore_nonrecursive(opt, merge_base, head, merge, &result);
|
merge_incore_nonrecursive(opt, merge_base, head, merge, &result);
|
||||||
merge_switch_to_result(opt, head, &result, 1, 1);
|
merge_switch_to_result(opt, head, &result, 1, show_msgs);
|
||||||
|
|
||||||
return result.clean;
|
return result.clean;
|
||||||
}
|
}
|
||||||
@@ -53,14 +59,76 @@ int merge_ort_recursive(struct merge_options *opt,
|
|||||||
{
|
{
|
||||||
struct tree *head = repo_get_commit_tree(opt->repo, side1);
|
struct tree *head = repo_get_commit_tree(opt->repo, side1);
|
||||||
struct merge_result tmp;
|
struct merge_result tmp;
|
||||||
|
int show_msgs;
|
||||||
|
|
||||||
if (unclean(opt, head))
|
if (unclean(opt, head))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
show_msgs = !!opt->verbosity;
|
||||||
memset(&tmp, 0, sizeof(tmp));
|
memset(&tmp, 0, sizeof(tmp));
|
||||||
merge_incore_recursive(opt, merge_bases, side1, side2, &tmp);
|
merge_incore_recursive(opt, merge_bases, side1, side2, &tmp);
|
||||||
merge_switch_to_result(opt, head, &tmp, 1, 1);
|
merge_switch_to_result(opt, head, &tmp, 1, show_msgs);
|
||||||
*result = NULL;
|
*result = NULL;
|
||||||
|
|
||||||
return tmp.clean;
|
return tmp.clean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct commit *get_ref(struct repository *repo,
|
||||||
|
const struct object_id *oid,
|
||||||
|
const char *name)
|
||||||
|
{
|
||||||
|
struct object *object;
|
||||||
|
|
||||||
|
object = deref_tag(repo, parse_object(repo, oid),
|
||||||
|
name, strlen(name));
|
||||||
|
if (!object)
|
||||||
|
return NULL;
|
||||||
|
if (object->type == OBJ_TREE)
|
||||||
|
return make_virtual_commit(repo, (struct tree*)object, name);
|
||||||
|
if (object->type != OBJ_COMMIT)
|
||||||
|
return NULL;
|
||||||
|
if (repo_parse_commit(repo, (struct commit *)object))
|
||||||
|
return NULL;
|
||||||
|
return (struct commit *)object;
|
||||||
|
}
|
||||||
|
|
||||||
|
int merge_ort_generic(struct merge_options *opt,
|
||||||
|
const struct object_id *head,
|
||||||
|
const struct object_id *merge,
|
||||||
|
int num_merge_bases,
|
||||||
|
const struct object_id *merge_bases,
|
||||||
|
struct commit **result)
|
||||||
|
{
|
||||||
|
int clean;
|
||||||
|
struct lock_file lock = LOCK_INIT;
|
||||||
|
struct commit *head_commit = get_ref(opt->repo, head, opt->branch1);
|
||||||
|
struct commit *next_commit = get_ref(opt->repo, merge, opt->branch2);
|
||||||
|
struct commit_list *ca = NULL;
|
||||||
|
|
||||||
|
if (merge_bases) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < num_merge_bases; ++i) {
|
||||||
|
struct commit *base;
|
||||||
|
if (!(base = get_ref(opt->repo, &merge_bases[i],
|
||||||
|
oid_to_hex(&merge_bases[i]))))
|
||||||
|
return error(_("Could not parse object '%s'"),
|
||||||
|
oid_to_hex(&merge_bases[i]));
|
||||||
|
commit_list_insert(base, &ca);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);
|
||||||
|
clean = merge_ort_recursive(opt, head_commit, next_commit, ca,
|
||||||
|
result);
|
||||||
|
free_commit_list(ca);
|
||||||
|
if (clean < 0) {
|
||||||
|
rollback_lock_file(&lock);
|
||||||
|
return clean;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write_locked_index(opt->repo->index, &lock,
|
||||||
|
COMMIT_LOCK | SKIP_IF_UNCHANGED))
|
||||||
|
return error(_("Unable to write index."));
|
||||||
|
|
||||||
|
return clean ? 0 : 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,4 +22,16 @@ int merge_ort_recursive(struct merge_options *opt,
|
|||||||
const struct commit_list *ancestors,
|
const struct commit_list *ancestors,
|
||||||
struct commit **result);
|
struct commit **result);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* rename-detecting three-way merge. num_merge_bases must be at least 1.
|
||||||
|
* Recursive ancestor consolidation will be performed if num_merge_bases > 1.
|
||||||
|
* Wrapper mimicking the old merge_recursive_generic() function.
|
||||||
|
*/
|
||||||
|
int merge_ort_generic(struct merge_options *opt,
|
||||||
|
const struct object_id *head,
|
||||||
|
const struct object_id *merge,
|
||||||
|
int num_merge_bases,
|
||||||
|
const struct object_id *merge_bases,
|
||||||
|
struct commit **result);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
49
merge-ort.c
49
merge-ort.c
@@ -3405,6 +3405,11 @@ static int collect_renames(struct merge_options *opt,
|
|||||||
pool_diff_free_filepair(&opt->priv->pool, p);
|
pool_diff_free_filepair(&opt->priv->pool, p);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_NONE &&
|
||||||
|
p->status == 'R' && 1) {
|
||||||
|
possibly_cache_new_pair(renames, p, side_index, NULL);
|
||||||
|
goto skip_directory_renames;
|
||||||
|
}
|
||||||
|
|
||||||
new_path = check_for_directory_rename(opt, p->two->path,
|
new_path = check_for_directory_rename(opt, p->two->path,
|
||||||
side_index,
|
side_index,
|
||||||
@@ -3422,6 +3427,7 @@ static int collect_renames(struct merge_options *opt,
|
|||||||
if (new_path)
|
if (new_path)
|
||||||
apply_directory_rename_modifications(opt, p, new_path);
|
apply_directory_rename_modifications(opt, p, new_path);
|
||||||
|
|
||||||
|
skip_directory_renames:
|
||||||
/*
|
/*
|
||||||
* p->score comes back from diffcore_rename_extended() with
|
* p->score comes back from diffcore_rename_extended() with
|
||||||
* the similarity of the renamed file. The similarity is
|
* the similarity of the renamed file. The similarity is
|
||||||
@@ -3449,6 +3455,11 @@ static int detect_and_process_renames(struct merge_options *opt)
|
|||||||
|
|
||||||
if (!possible_renames(renames))
|
if (!possible_renames(renames))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
if (!opt->detect_renames) {
|
||||||
|
renames->redo_after_renames = 0;
|
||||||
|
renames->cached_pairs_valid_side = 0;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
trace2_region_enter("merge", "regular renames", opt->repo);
|
trace2_region_enter("merge", "regular renames", opt->repo);
|
||||||
detection_run |= detect_regular_renames(opt, MERGE_SIDE1);
|
detection_run |= detect_regular_renames(opt, MERGE_SIDE1);
|
||||||
@@ -4879,7 +4890,7 @@ static inline void set_commit_tree(struct commit *c, struct tree *t)
|
|||||||
c->maybe_tree = t;
|
c->maybe_tree = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct commit *make_virtual_commit(struct repository *repo,
|
struct commit *make_virtual_commit(struct repository *repo,
|
||||||
struct tree *tree,
|
struct tree *tree,
|
||||||
const char *comment)
|
const char *comment)
|
||||||
{
|
{
|
||||||
@@ -5021,7 +5032,8 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
|
|||||||
trace2_region_leave("merge", "allocate/init", opt->repo);
|
trace2_region_leave("merge", "allocate/init", opt->repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void merge_check_renames_reusable(struct merge_result *result,
|
static void merge_check_renames_reusable(struct merge_options *opt,
|
||||||
|
struct merge_result *result,
|
||||||
struct tree *merge_base,
|
struct tree *merge_base,
|
||||||
struct tree *side1,
|
struct tree *side1,
|
||||||
struct tree *side2)
|
struct tree *side2)
|
||||||
@@ -5046,6 +5058,26 @@ static void merge_check_renames_reusable(struct merge_result *result,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Avoid using cached renames when directory rename detection is
|
||||||
|
* turned off. Cached renames are far less important in that case,
|
||||||
|
* and they lead to testcases with an interesting intersection of
|
||||||
|
* effects from relevant renames optimization, trivial directory
|
||||||
|
* resolution optimization, and cached renames all converging when
|
||||||
|
* the target of a cached rename is in a directory that
|
||||||
|
* collect_merge_info() does not recurse into. To avoid such
|
||||||
|
* problems, simply disable cached renames for this case (similar
|
||||||
|
* to the rename/rename(1to1) case; see the "disabling the
|
||||||
|
* optimization" comment near that case).
|
||||||
|
*
|
||||||
|
* This could be revisited in the future; see the commit message
|
||||||
|
* where this comment was added for some possible pointers.
|
||||||
|
*/
|
||||||
|
if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_NONE) {
|
||||||
|
renames->cached_pairs_valid_side = 0; /* neither side valid */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handle other cases; note that merge_trees[0..2] will only
|
* Handle other cases; note that merge_trees[0..2] will only
|
||||||
* be NULL if opti is, or if all three were manually set to
|
* be NULL if opti is, or if all three were manually set to
|
||||||
@@ -5187,6 +5219,8 @@ static void merge_ort_internal(struct merge_options *opt,
|
|||||||
ancestor_name = "empty tree";
|
ancestor_name = "empty tree";
|
||||||
} else if (merge_bases) {
|
} else if (merge_bases) {
|
||||||
ancestor_name = "merged common ancestors";
|
ancestor_name = "merged common ancestors";
|
||||||
|
} else if (opt->ancestor) {
|
||||||
|
ancestor_name = opt->ancestor;
|
||||||
} else {
|
} else {
|
||||||
strbuf_add_unique_abbrev(&merge_base_abbrev,
|
strbuf_add_unique_abbrev(&merge_base_abbrev,
|
||||||
&merged_merge_bases->object.oid,
|
&merged_merge_bases->object.oid,
|
||||||
@@ -5252,7 +5286,7 @@ void merge_incore_nonrecursive(struct merge_options *opt,
|
|||||||
|
|
||||||
trace2_region_enter("merge", "merge_start", opt->repo);
|
trace2_region_enter("merge", "merge_start", opt->repo);
|
||||||
assert(opt->ancestor != NULL);
|
assert(opt->ancestor != NULL);
|
||||||
merge_check_renames_reusable(result, merge_base, side1, side2);
|
merge_check_renames_reusable(opt, result, merge_base, side1, side2);
|
||||||
merge_start(opt, result);
|
merge_start(opt, result);
|
||||||
/*
|
/*
|
||||||
* Record the trees used in this merge, so if there's a next merge in
|
* Record the trees used in this merge, so if there's a next merge in
|
||||||
@@ -5276,8 +5310,13 @@ void merge_incore_recursive(struct merge_options *opt,
|
|||||||
{
|
{
|
||||||
trace2_region_enter("merge", "incore_recursive", opt->repo);
|
trace2_region_enter("merge", "incore_recursive", opt->repo);
|
||||||
|
|
||||||
/* We set the ancestor label based on the merge_bases */
|
/*
|
||||||
assert(opt->ancestor == NULL);
|
* We set the ancestor label based on the merge_bases...but we
|
||||||
|
* allow one exception through so that builtin/am can override
|
||||||
|
* with its constructed fake ancestor.
|
||||||
|
*/
|
||||||
|
assert(opt->ancestor == NULL ||
|
||||||
|
(merge_bases && !merge_bases->next));
|
||||||
|
|
||||||
trace2_region_enter("merge", "merge_start", opt->repo);
|
trace2_region_enter("merge", "merge_start", opt->repo);
|
||||||
merge_start(opt, result);
|
merge_start(opt, result);
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ struct merge_result {
|
|||||||
unsigned _properly_initialized;
|
unsigned _properly_initialized;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Mostly internal function also used by merge-ort-wrappers.c */
|
||||||
|
struct commit *make_virtual_commit(struct repository *repo,
|
||||||
|
struct tree *tree,
|
||||||
|
const char *comment);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* rename-detecting three-way merge with recursive ancestor consolidation.
|
* rename-detecting three-way merge with recursive ancestor consolidation.
|
||||||
* working tree and index are untouched.
|
* working tree and index are untouched.
|
||||||
|
|||||||
@@ -195,4 +195,26 @@ test_expect_success 'using replay on bare repo to rebase multiple divergent bran
|
|||||||
done
|
done
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'merge.directoryRenames=false' '
|
||||||
|
# create a test case that stress-tests the rename caching
|
||||||
|
git switch -c rename-onto &&
|
||||||
|
|
||||||
|
mkdir -p to-rename &&
|
||||||
|
test_commit to-rename/move &&
|
||||||
|
|
||||||
|
mkdir -p renamed-directory &&
|
||||||
|
git mv to-rename/move* renamed-directory/ &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m renamed-directory &&
|
||||||
|
|
||||||
|
git switch -c rename-from HEAD^ &&
|
||||||
|
test_commit to-rename/add-a-file &&
|
||||||
|
echo modified >to-rename/add-a-file.t &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m modified to-rename/add-a-file.t &&
|
||||||
|
|
||||||
|
git -c merge.directoryRenames=false replay \
|
||||||
|
--onto rename-onto rename-onto..rename-from
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ test_expect_success 'am --abort will keep dirty index intact' '
|
|||||||
test_expect_success 'am -3 stops on conflict on unborn branch' '
|
test_expect_success 'am -3 stops on conflict on unborn branch' '
|
||||||
git checkout -f --orphan orphan &&
|
git checkout -f --orphan orphan &&
|
||||||
git reset &&
|
git reset &&
|
||||||
rm -f otherfile-4 &&
|
rm -f file-1 otherfile-4 &&
|
||||||
test_must_fail git am -3 0003-*.patch &&
|
test_must_fail git am -3 0003-*.patch &&
|
||||||
test 2 -eq $(git ls-files -u | wc -l) &&
|
test 2 -eq $(git ls-files -u | wc -l) &&
|
||||||
test 4 = "$(cat otherfile-4)"
|
test 4 = "$(cat otherfile-4)"
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ am_3way () {
|
|||||||
$2 git am --3way patch
|
$2 git am --3way patch
|
||||||
}
|
}
|
||||||
|
|
||||||
KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
|
|
||||||
test_submodule_switch_func "am_3way"
|
test_submodule_switch_func "am_3way"
|
||||||
|
|
||||||
test_expect_success 'setup diff.submodule' '
|
test_expect_success 'setup diff.submodule' '
|
||||||
|
|||||||
@@ -73,6 +73,12 @@ test_expect_success 'Clean merge' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# Repeat the previous test, but turn off rename detection
|
||||||
|
test_expect_success 'Failed merge without rename detection' '
|
||||||
|
test_must_fail git -c diff.renames=false merge-tree --write-tree side1 side3 >out &&
|
||||||
|
grep "CONFLICT (modify/delete): numbers deleted" out
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'Content merge and a few conflicts' '
|
test_expect_success 'Content merge and a few conflicts' '
|
||||||
git checkout side1^0 &&
|
git checkout side1^0 &&
|
||||||
test_must_fail git merge side2 &&
|
test_must_fail git merge side2 &&
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ test_expect_success 'rebase --apply describes fake ancestor base' '
|
|||||||
cd rebase &&
|
cd rebase &&
|
||||||
git rebase --abort &&
|
git rebase --abort &&
|
||||||
test_must_fail git -c merge.conflictstyle=diff3 rebase --apply main &&
|
test_must_fail git -c merge.conflictstyle=diff3 rebase --apply main &&
|
||||||
grep "||||||| constructed merge base" file
|
grep "||||||| constructed fake ancestor" file
|
||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user