move struct pathspec and related functions to pathspec.[ch]
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:

committed by
Junio C Hamano

parent
5fee4df7f4
commit
64acde94ef
@ -5,6 +5,7 @@
|
|||||||
#include "archive.h"
|
#include "archive.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
#include "unpack-trees.h"
|
#include "unpack-trees.h"
|
||||||
|
#include "pathspec.h"
|
||||||
|
|
||||||
static char const * const archive_usage[] = {
|
static char const * const archive_usage[] = {
|
||||||
N_("git archive [options] <tree-ish> [<path>...]"),
|
N_("git archive [options] <tree-ish> [<path>...]"),
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include "grep.h"
|
#include "grep.h"
|
||||||
#include "quote.h"
|
#include "quote.h"
|
||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
|
#include "pathspec.h"
|
||||||
|
|
||||||
static char const * const grep_usage[] = {
|
static char const * const grep_usage[] = {
|
||||||
N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"),
|
N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"),
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
#include "resolve-undo.h"
|
#include "resolve-undo.h"
|
||||||
#include "string-list.h"
|
#include "string-list.h"
|
||||||
|
#include "pathspec.h"
|
||||||
|
|
||||||
static int abbrev;
|
static int abbrev;
|
||||||
static int show_deleted;
|
static int show_deleted;
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "quote.h"
|
#include "quote.h"
|
||||||
#include "builtin.h"
|
#include "builtin.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
|
#include "pathspec.h"
|
||||||
|
|
||||||
static int line_termination = '\n';
|
static int line_termination = '\n';
|
||||||
#define LS_RECURSIVE 1
|
#define LS_RECURSIVE 1
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
#include "resolve-undo.h"
|
#include "resolve-undo.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
|
#include "pathspec.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Default to not allowing changes to the list of files. The
|
* Default to not allowing changes to the list of files. The
|
||||||
|
22
cache.h
22
cache.h
@ -189,6 +189,8 @@ struct cache_entry {
|
|||||||
#error "CE_EXTENDED_FLAGS out of range"
|
#error "CE_EXTENDED_FLAGS out of range"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct pathspec;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copy the sha1 and stat state of a cache entry from one to
|
* Copy the sha1 and stat state of a cache entry from one to
|
||||||
* another. But we never change the name, or the hash state!
|
* another. But we never change the name, or the hash state!
|
||||||
@ -489,28 +491,8 @@ extern void *read_blob_data_from_index(struct index_state *, const char *, unsig
|
|||||||
extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
|
extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
|
||||||
extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
|
extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
|
||||||
|
|
||||||
#define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */
|
|
||||||
|
|
||||||
struct pathspec {
|
|
||||||
const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
|
|
||||||
int nr;
|
|
||||||
unsigned int has_wildcard:1;
|
|
||||||
unsigned int recursive:1;
|
|
||||||
int max_depth;
|
|
||||||
struct pathspec_item {
|
|
||||||
const char *match;
|
|
||||||
int len;
|
|
||||||
int nowildcard_len;
|
|
||||||
int flags;
|
|
||||||
} *items;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern int init_pathspec(struct pathspec *, const char **);
|
|
||||||
extern void free_pathspec(struct pathspec *);
|
|
||||||
extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
|
extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
|
||||||
|
|
||||||
extern int limit_pathspec_to_literal(void);
|
|
||||||
|
|
||||||
#define HASH_WRITE_OBJECT 1
|
#define HASH_WRITE_OBJECT 1
|
||||||
#define HASH_FORMAT_CHECK 2
|
#define HASH_FORMAT_CHECK 2
|
||||||
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
|
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
|
||||||
|
1
diff.h
1
diff.h
@ -5,6 +5,7 @@
|
|||||||
#define DIFF_H
|
#define DIFF_H
|
||||||
|
|
||||||
#include "tree-walk.h"
|
#include "tree-walk.h"
|
||||||
|
#include "pathspec.h"
|
||||||
|
|
||||||
struct rev_info;
|
struct rev_info;
|
||||||
struct diff_options;
|
struct diff_options;
|
||||||
|
1
dir.c
1
dir.c
@ -11,6 +11,7 @@
|
|||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
#include "wildmatch.h"
|
#include "wildmatch.h"
|
||||||
|
#include "pathspec.h"
|
||||||
|
|
||||||
struct path_simplify {
|
struct path_simplify {
|
||||||
int len;
|
int len;
|
||||||
|
150
pathspec.c
150
pathspec.c
@ -99,3 +99,153 @@ void die_if_path_beyond_symlink(const char *path, const char *prefix)
|
|||||||
die(_("'%s' is beyond a symbolic link"), path + len);
|
die(_("'%s' is beyond a symbolic link"), path + len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Magic pathspec
|
||||||
|
*
|
||||||
|
* NEEDSWORK: These need to be moved to dir.h or even to a new
|
||||||
|
* pathspec.h when we restructure get_pathspec() users to use the
|
||||||
|
* "struct pathspec" interface.
|
||||||
|
*
|
||||||
|
* Possible future magic semantics include stuff like:
|
||||||
|
*
|
||||||
|
* { PATHSPEC_NOGLOB, '!', "noglob" },
|
||||||
|
* { PATHSPEC_ICASE, '\0', "icase" },
|
||||||
|
* { PATHSPEC_RECURSIVE, '*', "recursive" },
|
||||||
|
* { PATHSPEC_REGEXP, '\0', "regexp" },
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define PATHSPEC_FROMTOP (1<<0)
|
||||||
|
|
||||||
|
static struct pathspec_magic {
|
||||||
|
unsigned bit;
|
||||||
|
char mnemonic; /* this cannot be ':'! */
|
||||||
|
const char *name;
|
||||||
|
} pathspec_magic[] = {
|
||||||
|
{ PATHSPEC_FROMTOP, '/', "top" },
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Take an element of a pathspec and check for magic signatures.
|
||||||
|
* Append the result to the prefix.
|
||||||
|
*
|
||||||
|
* For now, we only parse the syntax and throw out anything other than
|
||||||
|
* "top" magic.
|
||||||
|
*
|
||||||
|
* NEEDSWORK: This needs to be rewritten when we start migrating
|
||||||
|
* get_pathspec() users to use the "struct pathspec" interface. For
|
||||||
|
* example, a pathspec element may be marked as case-insensitive, but
|
||||||
|
* the prefix part must always match literally, and a single stupid
|
||||||
|
* string cannot express such a case.
|
||||||
|
*/
|
||||||
|
static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt)
|
||||||
|
{
|
||||||
|
unsigned magic = 0;
|
||||||
|
const char *copyfrom = elt;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (elt[0] != ':') {
|
||||||
|
; /* nothing to do */
|
||||||
|
} else if (elt[1] == '(') {
|
||||||
|
/* longhand */
|
||||||
|
const char *nextat;
|
||||||
|
for (copyfrom = elt + 2;
|
||||||
|
*copyfrom && *copyfrom != ')';
|
||||||
|
copyfrom = nextat) {
|
||||||
|
size_t len = strcspn(copyfrom, ",)");
|
||||||
|
if (copyfrom[len] == ',')
|
||||||
|
nextat = copyfrom + len + 1;
|
||||||
|
else
|
||||||
|
/* handle ')' and '\0' */
|
||||||
|
nextat = copyfrom + len;
|
||||||
|
if (!len)
|
||||||
|
continue;
|
||||||
|
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
|
||||||
|
if (strlen(pathspec_magic[i].name) == len &&
|
||||||
|
!strncmp(pathspec_magic[i].name, copyfrom, len)) {
|
||||||
|
magic |= pathspec_magic[i].bit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ARRAY_SIZE(pathspec_magic) <= i)
|
||||||
|
die("Invalid pathspec magic '%.*s' in '%s'",
|
||||||
|
(int) len, copyfrom, elt);
|
||||||
|
}
|
||||||
|
if (*copyfrom != ')')
|
||||||
|
die("Missing ')' at the end of pathspec magic in '%s'", elt);
|
||||||
|
copyfrom++;
|
||||||
|
} else {
|
||||||
|
/* shorthand */
|
||||||
|
for (copyfrom = elt + 1;
|
||||||
|
*copyfrom && *copyfrom != ':';
|
||||||
|
copyfrom++) {
|
||||||
|
char ch = *copyfrom;
|
||||||
|
|
||||||
|
if (!is_pathspec_magic(ch))
|
||||||
|
break;
|
||||||
|
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
|
||||||
|
if (pathspec_magic[i].mnemonic == ch) {
|
||||||
|
magic |= pathspec_magic[i].bit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ARRAY_SIZE(pathspec_magic) <= i)
|
||||||
|
die("Unimplemented pathspec magic '%c' in '%s'",
|
||||||
|
ch, elt);
|
||||||
|
}
|
||||||
|
if (*copyfrom == ':')
|
||||||
|
copyfrom++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magic & PATHSPEC_FROMTOP)
|
||||||
|
return xstrdup(copyfrom);
|
||||||
|
else
|
||||||
|
return prefix_path(prefix, prefixlen, copyfrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
|
||||||
|
* based interface - see pathspec_magic above.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* - prefix - a path relative to the root of the working tree
|
||||||
|
* - pathspec - a list of paths underneath the prefix path
|
||||||
|
*
|
||||||
|
* Iterates over pathspec, prepending each path with prefix,
|
||||||
|
* and return the resulting list.
|
||||||
|
*
|
||||||
|
* If pathspec is empty, return a singleton list containing prefix.
|
||||||
|
*
|
||||||
|
* If pathspec and prefix are both empty, return an empty list.
|
||||||
|
*
|
||||||
|
* This is typically used by built-in commands such as add.c, in order
|
||||||
|
* to normalize argv arguments provided to the built-in into a list of
|
||||||
|
* paths to process, all relative to the root of the working tree.
|
||||||
|
*/
|
||||||
|
const char **get_pathspec(const char *prefix, const char **pathspec)
|
||||||
|
{
|
||||||
|
const char *entry = *pathspec;
|
||||||
|
const char **src, **dst;
|
||||||
|
int prefixlen;
|
||||||
|
|
||||||
|
if (!prefix && !entry)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
static const char *spec[2];
|
||||||
|
spec[0] = prefix;
|
||||||
|
spec[1] = NULL;
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise we have to re-write the entries.. */
|
||||||
|
src = pathspec;
|
||||||
|
dst = pathspec;
|
||||||
|
prefixlen = prefix ? strlen(prefix) : 0;
|
||||||
|
while (*src) {
|
||||||
|
*(dst++) = prefix_pathspec(prefix, prefixlen, *src);
|
||||||
|
src++;
|
||||||
|
}
|
||||||
|
*dst = NULL;
|
||||||
|
if (!*pathspec)
|
||||||
|
return NULL;
|
||||||
|
return pathspec;
|
||||||
|
}
|
||||||
|
21
pathspec.h
21
pathspec.h
@ -1,6 +1,27 @@
|
|||||||
#ifndef PATHSPEC_H
|
#ifndef PATHSPEC_H
|
||||||
#define PATHSPEC_H
|
#define PATHSPEC_H
|
||||||
|
|
||||||
|
#define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */
|
||||||
|
|
||||||
|
struct pathspec {
|
||||||
|
const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
|
||||||
|
int nr;
|
||||||
|
unsigned int has_wildcard:1;
|
||||||
|
unsigned int recursive:1;
|
||||||
|
int max_depth;
|
||||||
|
struct pathspec_item {
|
||||||
|
const char *match;
|
||||||
|
int len;
|
||||||
|
int nowildcard_len;
|
||||||
|
int flags;
|
||||||
|
} *items;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int init_pathspec(struct pathspec *, const char **);
|
||||||
|
extern void free_pathspec(struct pathspec *);
|
||||||
|
|
||||||
|
extern int limit_pathspec_to_literal(void);
|
||||||
|
|
||||||
extern char *find_pathspecs_matching_against_index(const char **pathspec);
|
extern char *find_pathspecs_matching_against_index(const char **pathspec);
|
||||||
extern void add_pathspec_matches_against_index(const char **pathspec, char *seen, int specs);
|
extern void add_pathspec_matches_against_index(const char **pathspec, char *seen, int specs);
|
||||||
extern const char *check_path_for_gitlink(const char *path);
|
extern const char *check_path_for_gitlink(const char *path);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
* Copyright (C) 2008 Linus Torvalds
|
* Copyright (C) 2008 Linus Torvalds
|
||||||
*/
|
*/
|
||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
|
#include "pathspec.h"
|
||||||
|
|
||||||
#ifdef NO_PTHREADS
|
#ifdef NO_PTHREADS
|
||||||
static void preload_index(struct index_state *index, const char **pathspec)
|
static void preload_index(struct index_state *index, const char **pathspec)
|
||||||
|
149
setup.c
149
setup.c
@ -154,155 +154,6 @@ void verify_non_filename(const char *prefix, const char *arg)
|
|||||||
"'git <command> [<revision>...] -- [<file>...]'", arg);
|
"'git <command> [<revision>...] -- [<file>...]'", arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Magic pathspec
|
|
||||||
*
|
|
||||||
* NEEDSWORK: These need to be moved to dir.h or even to a new
|
|
||||||
* pathspec.h when we restructure get_pathspec() users to use the
|
|
||||||
* "struct pathspec" interface.
|
|
||||||
*
|
|
||||||
* Possible future magic semantics include stuff like:
|
|
||||||
*
|
|
||||||
* { PATHSPEC_NOGLOB, '!', "noglob" },
|
|
||||||
* { PATHSPEC_ICASE, '\0', "icase" },
|
|
||||||
* { PATHSPEC_RECURSIVE, '*', "recursive" },
|
|
||||||
* { PATHSPEC_REGEXP, '\0', "regexp" },
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#define PATHSPEC_FROMTOP (1<<0)
|
|
||||||
|
|
||||||
static struct pathspec_magic {
|
|
||||||
unsigned bit;
|
|
||||||
char mnemonic; /* this cannot be ':'! */
|
|
||||||
const char *name;
|
|
||||||
} pathspec_magic[] = {
|
|
||||||
{ PATHSPEC_FROMTOP, '/', "top" },
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Take an element of a pathspec and check for magic signatures.
|
|
||||||
* Append the result to the prefix.
|
|
||||||
*
|
|
||||||
* For now, we only parse the syntax and throw out anything other than
|
|
||||||
* "top" magic.
|
|
||||||
*
|
|
||||||
* NEEDSWORK: This needs to be rewritten when we start migrating
|
|
||||||
* get_pathspec() users to use the "struct pathspec" interface. For
|
|
||||||
* example, a pathspec element may be marked as case-insensitive, but
|
|
||||||
* the prefix part must always match literally, and a single stupid
|
|
||||||
* string cannot express such a case.
|
|
||||||
*/
|
|
||||||
static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt)
|
|
||||||
{
|
|
||||||
unsigned magic = 0;
|
|
||||||
const char *copyfrom = elt;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (elt[0] != ':') {
|
|
||||||
; /* nothing to do */
|
|
||||||
} else if (elt[1] == '(') {
|
|
||||||
/* longhand */
|
|
||||||
const char *nextat;
|
|
||||||
for (copyfrom = elt + 2;
|
|
||||||
*copyfrom && *copyfrom != ')';
|
|
||||||
copyfrom = nextat) {
|
|
||||||
size_t len = strcspn(copyfrom, ",)");
|
|
||||||
if (copyfrom[len] == ',')
|
|
||||||
nextat = copyfrom + len + 1;
|
|
||||||
else
|
|
||||||
/* handle ')' and '\0' */
|
|
||||||
nextat = copyfrom + len;
|
|
||||||
if (!len)
|
|
||||||
continue;
|
|
||||||
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
|
|
||||||
if (strlen(pathspec_magic[i].name) == len &&
|
|
||||||
!strncmp(pathspec_magic[i].name, copyfrom, len)) {
|
|
||||||
magic |= pathspec_magic[i].bit;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ARRAY_SIZE(pathspec_magic) <= i)
|
|
||||||
die("Invalid pathspec magic '%.*s' in '%s'",
|
|
||||||
(int) len, copyfrom, elt);
|
|
||||||
}
|
|
||||||
if (*copyfrom != ')')
|
|
||||||
die("Missing ')' at the end of pathspec magic in '%s'", elt);
|
|
||||||
copyfrom++;
|
|
||||||
} else {
|
|
||||||
/* shorthand */
|
|
||||||
for (copyfrom = elt + 1;
|
|
||||||
*copyfrom && *copyfrom != ':';
|
|
||||||
copyfrom++) {
|
|
||||||
char ch = *copyfrom;
|
|
||||||
|
|
||||||
if (!is_pathspec_magic(ch))
|
|
||||||
break;
|
|
||||||
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
|
|
||||||
if (pathspec_magic[i].mnemonic == ch) {
|
|
||||||
magic |= pathspec_magic[i].bit;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ARRAY_SIZE(pathspec_magic) <= i)
|
|
||||||
die("Unimplemented pathspec magic '%c' in '%s'",
|
|
||||||
ch, elt);
|
|
||||||
}
|
|
||||||
if (*copyfrom == ':')
|
|
||||||
copyfrom++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (magic & PATHSPEC_FROMTOP)
|
|
||||||
return xstrdup(copyfrom);
|
|
||||||
else
|
|
||||||
return prefix_path(prefix, prefixlen, copyfrom);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
|
|
||||||
* based interface - see pathspec_magic above.
|
|
||||||
*
|
|
||||||
* Arguments:
|
|
||||||
* - prefix - a path relative to the root of the working tree
|
|
||||||
* - pathspec - a list of paths underneath the prefix path
|
|
||||||
*
|
|
||||||
* Iterates over pathspec, prepending each path with prefix,
|
|
||||||
* and return the resulting list.
|
|
||||||
*
|
|
||||||
* If pathspec is empty, return a singleton list containing prefix.
|
|
||||||
*
|
|
||||||
* If pathspec and prefix are both empty, return an empty list.
|
|
||||||
*
|
|
||||||
* This is typically used by built-in commands such as add.c, in order
|
|
||||||
* to normalize argv arguments provided to the built-in into a list of
|
|
||||||
* paths to process, all relative to the root of the working tree.
|
|
||||||
*/
|
|
||||||
const char **get_pathspec(const char *prefix, const char **pathspec)
|
|
||||||
{
|
|
||||||
const char *entry = *pathspec;
|
|
||||||
const char **src, **dst;
|
|
||||||
int prefixlen;
|
|
||||||
|
|
||||||
if (!prefix && !entry)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (!entry) {
|
|
||||||
static const char *spec[2];
|
|
||||||
spec[0] = prefix;
|
|
||||||
spec[1] = NULL;
|
|
||||||
return spec;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Otherwise we have to re-write the entries.. */
|
|
||||||
src = pathspec;
|
|
||||||
dst = pathspec;
|
|
||||||
prefixlen = prefix ? strlen(prefix) : 0;
|
|
||||||
while (*src) {
|
|
||||||
*(dst++) = prefix_pathspec(prefix, prefixlen, *src);
|
|
||||||
src++;
|
|
||||||
}
|
|
||||||
*dst = NULL;
|
|
||||||
if (!*pathspec)
|
|
||||||
return NULL;
|
|
||||||
return pathspec;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Test if it looks like we're at a git directory.
|
* Test if it looks like we're at a git directory.
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "unpack-trees.h"
|
#include "unpack-trees.h"
|
||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
#include "tree.h"
|
#include "tree.h"
|
||||||
|
#include "pathspec.h"
|
||||||
|
|
||||||
static const char *get_mode(const char *str, unsigned int *modep)
|
static const char *get_mode(const char *str, unsigned int *modep)
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user