提交 37f7a857 编写于 作者: J Jay Soffian 提交者: Junio C Hamano

Teach commit about CHERRY_PICK_HEAD

Previously the user was advised to use commit -c CHERRY_PICK_HEAD after
a conflicting cherry-pick. While this would preserve the original
commit's authorship, it would sadly discard cherry-pick's carefully
crafted MERGE_MSG (which contains the list of conflicts as well as the
original commit-id in the case of cherry-pick -x).

On the other hand, if a bare 'commit' were performed, it would preserve
the MERGE_MSG while resetting the authorship.

In other words, there was no way to simultaneously take the authorship
from CHERRY_PICK_HEAD and the commit message from MERGE_MSG.

This change fixes that situation. A bare 'commit' will now take the
authorship from CHERRY_PICK_HEAD and the commit message from MERGE_MSG.
If the user wishes to reset authorship, that must now be done explicitly
via --reset-author.

A side-benefit of passing commit authorship along this way is that we
can eliminate redundant authorship parsing code from revert.c.

(Also removed an unused include from revert.c)
Signed-off-by: NJay Soffian <jaysoffian@gmail.com>
Signed-off-by: NJunio C Hamano <gitster@pobox.com>
上级 5b2af8ca
...@@ -84,9 +84,10 @@ OPTIONS ...@@ -84,9 +84,10 @@ OPTIONS
linkgit:git-rebase[1] for details. linkgit:git-rebase[1] for details.
--reset-author:: --reset-author::
When used with -C/-c/--amend options, declare that the When used with -C/-c/--amend options, or when committing after a
authorship of the resulting commit now belongs of the committer. a conflicting cherry-pick, declare that the authorship of the
This also renews the author timestamp. resulting commit now belongs of the committer. This also renews
the author timestamp.
--short:: --short::
When doing a dry-run, give the output in the short-format. See When doing a dry-run, give the output in the short-format. See
......
...@@ -54,9 +54,17 @@ static const char empty_amend_advice[] = ...@@ -54,9 +54,17 @@ static const char empty_amend_advice[] =
"it empty. You can repeat your command with --allow-empty, or you can\n" "it empty. You can repeat your command with --allow-empty, or you can\n"
"remove the commit entirely with \"git reset HEAD^\".\n"; "remove the commit entirely with \"git reset HEAD^\".\n";
static const char empty_cherry_pick_advice[] =
"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
"If you wish to commit it anyway, use:\n"
"\n"
" git commit --allow-empty\n"
"\n"
"Otherwise, please use 'git reset'\n";
static unsigned char head_sha1[20]; static unsigned char head_sha1[20];
static char *use_message_buffer; static const char *use_message_buffer;
static const char commit_editmsg[] = "COMMIT_EDITMSG"; static const char commit_editmsg[] = "COMMIT_EDITMSG";
static struct lock_file index_lock; /* real index */ static struct lock_file index_lock; /* real index */
static struct lock_file false_lock; /* used only for partial commits */ static struct lock_file false_lock; /* used only for partial commits */
...@@ -68,6 +76,11 @@ static enum { ...@@ -68,6 +76,11 @@ static enum {
static const char *logfile, *force_author; static const char *logfile, *force_author;
static const char *template_file; static const char *template_file;
/*
* The _message variables are commit names from which to take
* the commit message and/or authorship.
*/
static const char *author_message, *author_message_buffer;
static char *edit_message, *use_message; static char *edit_message, *use_message;
static char *fixup_message, *squash_message; static char *fixup_message, *squash_message;
static int all, edit_flag, also, interactive, only, amend, signoff; static int all, edit_flag, also, interactive, only, amend, signoff;
...@@ -88,7 +101,8 @@ static enum { ...@@ -88,7 +101,8 @@ static enum {
} cleanup_mode; } cleanup_mode;
static char *cleanup_arg; static char *cleanup_arg;
static int use_editor = 1, initial_commit, in_merge, include_status = 1; static enum commit_whence whence;
static int use_editor = 1, initial_commit, include_status = 1;
static int show_ignored_in_status; static int show_ignored_in_status;
static const char *only_include_assumed; static const char *only_include_assumed;
static struct strbuf message; static struct strbuf message;
...@@ -163,6 +177,36 @@ static struct option builtin_commit_options[] = { ...@@ -163,6 +177,36 @@ static struct option builtin_commit_options[] = {
OPT_END() OPT_END()
}; };
static void determine_whence(struct wt_status *s)
{
if (file_exists(git_path("MERGE_HEAD")))
whence = FROM_MERGE;
else if (file_exists(git_path("CHERRY_PICK_HEAD")))
whence = FROM_CHERRY_PICK;
else
whence = FROM_COMMIT;
if (s)
s->whence = whence;
}
static const char *whence_s(void)
{
char *s = "";
switch (whence) {
case FROM_COMMIT:
break;
case FROM_MERGE:
s = "merge";
break;
case FROM_CHERRY_PICK:
s = "cherry-pick";
break;
}
return s;
}
static void rollback_index_files(void) static void rollback_index_files(void)
{ {
switch (commit_style) { switch (commit_style) {
...@@ -378,8 +422,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int ...@@ -378,8 +422,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
*/ */
commit_style = COMMIT_PARTIAL; commit_style = COMMIT_PARTIAL;
if (in_merge) if (whence != FROM_COMMIT)
die("cannot do a partial commit during a merge."); die("cannot do a partial commit during a %s.", whence_s());
memset(&partial, 0, sizeof(partial)); memset(&partial, 0, sizeof(partial));
partial.strdup_strings = 1; partial.strdup_strings = 1;
...@@ -469,18 +513,18 @@ static void determine_author_info(struct strbuf *author_ident) ...@@ -469,18 +513,18 @@ static void determine_author_info(struct strbuf *author_ident)
email = getenv("GIT_AUTHOR_EMAIL"); email = getenv("GIT_AUTHOR_EMAIL");
date = getenv("GIT_AUTHOR_DATE"); date = getenv("GIT_AUTHOR_DATE");
if (use_message && !renew_authorship) { if (author_message) {
const char *a, *lb, *rb, *eol; const char *a, *lb, *rb, *eol;
a = strstr(use_message_buffer, "\nauthor "); a = strstr(author_message_buffer, "\nauthor ");
if (!a) if (!a)
die("invalid commit: %s", use_message); die("invalid commit: %s", author_message);
lb = strchrnul(a + strlen("\nauthor "), '<'); lb = strchrnul(a + strlen("\nauthor "), '<');
rb = strchrnul(lb, '>'); rb = strchrnul(lb, '>');
eol = strchrnul(rb, '\n'); eol = strchrnul(rb, '\n');
if (!*lb || !*rb || !*eol) if (!*lb || !*rb || !*eol)
die("invalid commit: %s", use_message); die("invalid commit: %s", author_message);
if (lb == a + strlen("\nauthor ")) if (lb == a + strlen("\nauthor "))
/* \nauthor <foo@example.com> */ /* \nauthor <foo@example.com> */
...@@ -641,11 +685,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix, ...@@ -641,11 +685,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
} }
/* /*
* This final case does not modify the template message, * The remaining cases don't modify the template message, but
* it just sets the argument to the prepare-commit-msg hook. * just set the argument(s) to the prepare-commit-msg hook.
*/ */
else if (in_merge) else if (whence == FROM_MERGE)
hook_arg1 = "merge"; hook_arg1 = "merge";
else if (whence == FROM_CHERRY_PICK) {
hook_arg1 = "commit";
hook_arg2 = "CHERRY_PICK_HEAD";
}
if (squash_message) { if (squash_message) {
/* /*
...@@ -694,16 +742,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix, ...@@ -694,16 +742,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
strbuf_addstr(&committer_ident, git_committer_info(0)); strbuf_addstr(&committer_ident, git_committer_info(0));
if (use_editor && include_status) { if (use_editor && include_status) {
char *ai_tmp, *ci_tmp; char *ai_tmp, *ci_tmp;
if (in_merge) if (whence != FROM_COMMIT)
fprintf(fp, fprintf(fp,
"#\n" "#\n"
"# It looks like you may be committing a MERGE.\n" "# It looks like you may be committing a %s.\n"
"# If this is not correct, please remove the file\n" "# If this is not correct, please remove the file\n"
"# %s\n" "# %s\n"
"# and try again.\n" "# and try again.\n"
"#\n", "#\n",
git_path("MERGE_HEAD")); whence_s(),
git_path(whence == FROM_MERGE
? "MERGE_HEAD"
: "CHERRY_PICK_HEAD"));
fprintf(fp, fprintf(fp,
"\n" "\n"
"# Please enter the commit message for your changes."); "# Please enter the commit message for your changes.");
...@@ -766,11 +816,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix, ...@@ -766,11 +816,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
fclose(fp); fclose(fp);
if (!commitable && !in_merge && !allow_empty && /*
* Reject an attempt to record a non-merge empty commit without
* explicit --allow-empty. In the cherry-pick case, it may be
* empty due to conflict resolution, which the user should okay.
*/
if (!commitable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(head_sha1))) { !(amend && is_a_merge(head_sha1))) {
run_status(stdout, index_file, prefix, 0, s); run_status(stdout, index_file, prefix, 0, s);
if (amend) if (amend)
fputs(empty_amend_advice, stderr); fputs(empty_amend_advice, stderr);
else if (whence == FROM_CHERRY_PICK)
fputs(empty_cherry_pick_advice, stderr);
return 0; return 0;
} }
...@@ -898,6 +955,28 @@ static void handle_untracked_files_arg(struct wt_status *s) ...@@ -898,6 +955,28 @@ static void handle_untracked_files_arg(struct wt_status *s)
die("Invalid untracked files mode '%s'", untracked_files_arg); die("Invalid untracked files mode '%s'", untracked_files_arg);
} }
static const char *read_commit_message(const char *name)
{
const char *out_enc, *out;
struct commit *commit;
commit = lookup_commit_reference_by_name(name);
if (!commit)
die("could not lookup commit %s", name);
out_enc = get_commit_output_encoding();
out = logmsg_reencode(commit, out_enc);
/*
* If we failed to reencode the buffer, just copy it
* byte for byte so the user can try to fix it up.
* This also handles the case where input and output
* encodings are identical.
*/
if (out == NULL)
out = xstrdup(commit->buffer);
return out;
}
static int parse_and_validate_options(int argc, const char *argv[], static int parse_and_validate_options(int argc, const char *argv[],
const char * const usage[], const char * const usage[],
const char *prefix, const char *prefix,
...@@ -927,8 +1006,8 @@ static int parse_and_validate_options(int argc, const char *argv[], ...@@ -927,8 +1006,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
/* Sanity check options */ /* Sanity check options */
if (amend && initial_commit) if (amend && initial_commit)
die("You have nothing to amend."); die("You have nothing to amend.");
if (amend && in_merge) if (amend && whence != FROM_COMMIT)
die("You are in the middle of a merge -- cannot amend."); die("You are in the middle of a %s -- cannot amend.", whence_s());
if (fixup_message && squash_message) if (fixup_message && squash_message)
die("Options --squash and --fixup cannot be used together"); die("Options --squash and --fixup cannot be used together");
if (use_message) if (use_message)
...@@ -947,26 +1026,18 @@ static int parse_and_validate_options(int argc, const char *argv[], ...@@ -947,26 +1026,18 @@ static int parse_and_validate_options(int argc, const char *argv[],
use_message = edit_message; use_message = edit_message;
if (amend && !use_message && !fixup_message) if (amend && !use_message && !fixup_message)
use_message = "HEAD"; use_message = "HEAD";
if (!use_message && renew_authorship) if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship)
die("--reset-author can be used only with -C, -c or --amend."); die("--reset-author can be used only with -C, -c or --amend.");
if (use_message) { if (use_message) {
const char *out_enc; use_message_buffer = read_commit_message(use_message);
struct commit *commit; if (!renew_authorship) {
author_message = use_message;
commit = lookup_commit_reference_by_name(use_message); author_message_buffer = use_message_buffer;
if (!commit) }
die("could not lookup commit %s", use_message); }
out_enc = get_commit_output_encoding(); if (whence == FROM_CHERRY_PICK && !renew_authorship) {
use_message_buffer = logmsg_reencode(commit, out_enc); author_message = "CHERRY_PICK_HEAD";
author_message_buffer = read_commit_message(author_message);
/*
* If we failed to reencode the buffer, just copy it
* byte for byte so the user can try to fix it up.
* This also handles the case where input and output
* encodings are identical.
*/
if (use_message_buffer == NULL)
use_message_buffer = xstrdup(commit->buffer);
} }
if (!!also + !!only + !!all + !!interactive > 1) if (!!also + !!only + !!all + !!interactive > 1)
...@@ -1117,7 +1188,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) ...@@ -1117,7 +1188,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
wt_status_prepare(&s); wt_status_prepare(&s);
gitmodules_config(); gitmodules_config();
git_config(git_status_config, &s); git_config(git_status_config, &s);
in_merge = file_exists(git_path("MERGE_HEAD")); determine_whence(&s);
argc = parse_options(argc, argv, prefix, argc = parse_options(argc, argv, prefix,
builtin_status_options, builtin_status_options,
builtin_status_usage, 0); builtin_status_usage, 0);
...@@ -1140,7 +1211,6 @@ int cmd_status(int argc, const char **argv, const char *prefix) ...@@ -1140,7 +1211,6 @@ int cmd_status(int argc, const char **argv, const char *prefix)
} }
s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
s.in_merge = in_merge;
s.ignore_submodule_arg = ignore_submodule_arg; s.ignore_submodule_arg = ignore_submodule_arg;
wt_status_collect(&s); wt_status_collect(&s);
...@@ -1302,8 +1372,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) ...@@ -1302,8 +1372,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
wt_status_prepare(&s); wt_status_prepare(&s);
git_config(git_commit_config, &s); git_config(git_commit_config, &s);
in_merge = file_exists(git_path("MERGE_HEAD")); determine_whence(&s);
s.in_merge = in_merge;
if (s.use_color == -1) if (s.use_color == -1)
s.use_color = git_use_color_default; s.use_color = git_use_color_default;
...@@ -1340,7 +1409,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) ...@@ -1340,7 +1409,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
for (c = commit->parents; c; c = c->next) for (c = commit->parents; c; c = c->next)
pptr = &commit_list_insert(c->item, pptr)->next; pptr = &commit_list_insert(c->item, pptr)->next;
} else if (in_merge) { } else if (whence == FROM_MERGE) {
struct strbuf m = STRBUF_INIT; struct strbuf m = STRBUF_INIT;
FILE *fp; FILE *fp;
...@@ -1369,7 +1438,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) ...@@ -1369,7 +1438,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
parents = reduce_heads(parents); parents = reduce_heads(parents);
} else { } else {
if (!reflog_msg) if (!reflog_msg)
reflog_msg = "commit"; reflog_msg = (whence == FROM_CHERRY_PICK)
? "commit (cherry-pick)"
: "commit";
pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
} }
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
#include "object.h" #include "object.h"
#include "commit.h" #include "commit.h"
#include "tag.h" #include "tag.h"
#include "wt-status.h"
#include "run-command.h" #include "run-command.h"
#include "exec_cmd.h" #include "exec_cmd.h"
#include "utf8.h" #include "utf8.h"
...@@ -181,56 +180,6 @@ static void add_message_to_msg(struct strbuf *msgbuf, const char *message) ...@@ -181,56 +180,6 @@ static void add_message_to_msg(struct strbuf *msgbuf, const char *message)
strbuf_addstr(msgbuf, p); strbuf_addstr(msgbuf, p);
} }
static void set_author_ident_env(const char *message)
{
const char *p = message;
if (!p)
die ("Could not read commit message of %s",
sha1_to_hex(commit->object.sha1));
while (*p && *p != '\n') {
const char *eol;
for (eol = p; *eol && *eol != '\n'; eol++)
; /* do nothing */
if (!prefixcmp(p, "author ")) {
char *line, *pend, *email, *timestamp;
p += 7;
line = xmemdupz(p, eol - p);
email = strchr(line, '<');
if (!email)
die ("Could not extract author email from %s",
sha1_to_hex(commit->object.sha1));
if (email == line)
pend = line;
else
for (pend = email; pend != line + 1 &&
isspace(pend[-1]); pend--);
; /* do nothing */
*pend = '\0';
email++;
timestamp = strchr(email, '>');
if (!timestamp)
die ("Could not extract author time from %s",
sha1_to_hex(commit->object.sha1));
*timestamp = '\0';
for (timestamp++; *timestamp && isspace(*timestamp);
timestamp++)
; /* do nothing */
setenv("GIT_AUTHOR_NAME", line, 1);
setenv("GIT_AUTHOR_EMAIL", email, 1);
setenv("GIT_AUTHOR_DATE", timestamp, 1);
free(line);
return;
}
p = eol;
if (*p == '\n')
p++;
}
die ("No author information found in %s",
sha1_to_hex(commit->object.sha1));
}
static void write_cherry_pick_head(void) static void write_cherry_pick_head(void)
{ {
int fd; int fd;
...@@ -273,9 +222,7 @@ static void print_advice(void) ...@@ -273,9 +222,7 @@ static void print_advice(void)
advise("after resolving the conflicts, mark the corrected paths"); advise("after resolving the conflicts, mark the corrected paths");
advise("with 'git add <paths>' or 'git rm <paths>'"); advise("with 'git add <paths>' or 'git rm <paths>'");
advise("and commit the result with 'git commit'");
if (action == CHERRY_PICK)
advise("and commit the result with 'git commit -c CHERRY_PICK_HEAD'");
} }
static void write_message(struct strbuf *msgbuf, const char *filename) static void write_message(struct strbuf *msgbuf, const char *filename)
...@@ -503,7 +450,6 @@ static int do_pick_commit(void) ...@@ -503,7 +450,6 @@ static int do_pick_commit(void)
base_label = msg.parent_label; base_label = msg.parent_label;
next = commit; next = commit;
next_label = msg.label; next_label = msg.label;
set_author_ident_env(msg.message);
add_message_to_msg(&msgbuf, msg.message); add_message_to_msg(&msgbuf, msg.message);
if (no_replay) { if (no_replay) {
strbuf_addstr(&msgbuf, "(cherry picked from commit "); strbuf_addstr(&msgbuf, "(cherry picked from commit ");
......
...@@ -52,7 +52,7 @@ test_expect_success 'advice from failed cherry-pick' " ...@@ -52,7 +52,7 @@ test_expect_success 'advice from failed cherry-pick' "
error: could not apply \$picked... picked error: could not apply \$picked... picked
hint: after resolving the conflicts, mark the corrected paths hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>' hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit -c CHERRY_PICK_HEAD' hint: and commit the result with 'git commit'
EOF EOF
test_must_fail git cherry-pick picked 2>actual && test_must_fail git cherry-pick picked 2>actual &&
......
...@@ -157,4 +157,33 @@ test_expect_success '--reset-author should be rejected without -c/-C/--amend' ' ...@@ -157,4 +157,33 @@ test_expect_success '--reset-author should be rejected without -c/-C/--amend' '
test_must_fail git commit -a --reset-author -m done test_must_fail git commit -a --reset-author -m done
' '
test_expect_success 'commit respects CHERRY_PICK_HEAD and MERGE_MSG' '
echo "cherry-pick 1a" >>foo &&
test_tick &&
git commit -am "cherry-pick 1" --author="Cherry <cherry@pick.er>" &&
git tag cherry-pick-head &&
git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD &&
echo "This is a MERGE_MSG" >.git/MERGE_MSG &&
echo "cherry-pick 1b" >>foo &&
test_tick &&
git commit -a &&
author_header cherry-pick-head >expect &&
author_header HEAD >actual &&
test_cmp expect actual &&
echo "This is a MERGE_MSG" >expect &&
message_body HEAD >actual &&
test_cmp expect actual
'
test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD &&
echo "cherry-pick 2" >>foo &&
test_tick &&
git commit -am "cherry-pick 2" --reset-author &&
echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
author_header HEAD >actual &&
test_cmp expect actual
'
test_done test_done
...@@ -60,7 +60,7 @@ static void wt_status_print_unmerged_header(struct wt_status *s) ...@@ -60,7 +60,7 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
color_fprintf_ln(s->fp, c, "# Unmerged paths:"); color_fprintf_ln(s->fp, c, "# Unmerged paths:");
if (!advice_status_hints) if (!advice_status_hints)
return; return;
if (s->in_merge) if (s->whence != FROM_COMMIT)
; ;
else if (!s->is_initial) else if (!s->is_initial)
color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference);
...@@ -77,7 +77,7 @@ static void wt_status_print_cached_header(struct wt_status *s) ...@@ -77,7 +77,7 @@ static void wt_status_print_cached_header(struct wt_status *s)
color_fprintf_ln(s->fp, c, "# Changes to be committed:"); color_fprintf_ln(s->fp, c, "# Changes to be committed:");
if (!advice_status_hints) if (!advice_status_hints)
return; return;
if (s->in_merge) if (s->whence != FROM_COMMIT)
; /* NEEDSWORK: use "git reset --unresolve"??? */ ; /* NEEDSWORK: use "git reset --unresolve"??? */
else if (!s->is_initial) else if (!s->is_initial)
color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference);
......
...@@ -24,6 +24,13 @@ enum untracked_status_type { ...@@ -24,6 +24,13 @@ enum untracked_status_type {
SHOW_ALL_UNTRACKED_FILES SHOW_ALL_UNTRACKED_FILES
}; };
/* from where does this commit originate */
enum commit_whence {
FROM_COMMIT, /* normal */
FROM_MERGE, /* commit came from merge */
FROM_CHERRY_PICK /* commit came from cherry-pick */
};
struct wt_status_change_data { struct wt_status_change_data {
int worktree_status; int worktree_status;
int index_status; int index_status;
...@@ -40,7 +47,7 @@ struct wt_status { ...@@ -40,7 +47,7 @@ struct wt_status {
const char **pathspec; const char **pathspec;
int verbose; int verbose;
int amend; int amend;
int in_merge; enum commit_whence whence;
int nowarn; int nowarn;
int use_color; int use_color;
int relative_paths; int relative_paths;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册