Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
github
hub
提交
e70b7fe1
H
hub
项目概览
github
/
hub
12 个月 前同步成功
通知
3
Star
22523
Fork
2406
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
H
hub
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
e70b7fe1
编写于
1月 31, 2016
作者:
M
Mislav Marohnić
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Reimplement `release create` with simpleApi instead of go-octokit
上级
0fb96e05
变更
7
隐藏空白更改
内联
并排
Showing
7 changed file
with
188 addition
and
279 deletion
+188
-279
commands/release.go
commands/release.go
+71
-178
commands/release_test.go
commands/release_test.go
+0
-28
commands/release_tpl.go
commands/release_tpl.go
+2
-1
commands/utils.go
commands/utils.go
+20
-19
commands/utils_test.go
commands/utils_test.go
+2
-2
github/client.go
github/client.go
+33
-51
github/http.go
github/http.go
+60
-0
未找到文件。
commands/release.go
浏览文件 @
e70b7fe1
package
commands
import
(
"bytes"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/github/hub/git"
"github.com/github/hub/github"
"github.com/github/hub/ui"
"github.com/github/hub/utils"
"github.com/octokit/go-octokit/octokit"
)
var
(
...
...
@@ -51,6 +46,9 @@ With '--include-drafs', include draft releases in the listing.
-a, --asset <FILE>
Attach a file as an asset for this release.
If <FILE> is in the "<filename>#<text>" format, the text after the '#'
character is taken as asset label.
-m, --message <MESSAGE>
Use the first line of <MESSAGE> as release title, and the rest as release description.
...
...
@@ -175,204 +173,99 @@ func showRelease(cmd *Command, args *Args) {
}
func
createRelease
(
cmd
*
Command
,
args
*
Args
)
{
if
args
.
IsParamsEmpty
()
{
utils
.
Check
(
fmt
.
Errorf
(
"Missed argument TAG"
))
tagName
:=
args
.
LastParam
()
if
tagName
==
""
{
utils
.
Check
(
fmt
.
Errorf
(
"Missing argument TAG"
))
return
}
tag
:=
args
.
LastParam
()
runInLocalRepo
(
func
(
localRepo
*
github
.
GitHubRepo
,
project
*
github
.
Project
,
client
*
github
.
Client
)
{
release
,
err
:=
client
.
Release
(
project
,
tag
)
utils
.
Check
(
err
)
if
release
==
nil
{
commitish
:=
flagReleaseCommitish
if
commitish
==
""
{
currentBranch
,
err
:=
localRepo
.
CurrentBranch
()
utils
.
Check
(
err
)
commitish
=
currentBranch
.
ShortName
()
}
title
,
body
,
err
:=
getTitleAndBodyFromFlags
(
flagReleaseMessage
,
flagReleaseFile
)
utils
.
Check
(
err
)
var
editor
*
github
.
Editor
if
title
==
""
{
cs
:=
git
.
CommentChar
()
message
,
err
:=
renderReleaseTpl
(
cs
,
tag
,
project
.
Name
,
commitish
)
utils
.
Check
(
err
)
editor
,
err
=
github
.
NewEditor
(
"RELEASE"
,
"release"
,
message
)
utils
.
Check
(
err
)
title
,
body
,
err
=
editor
.
EditTitleAndBody
()
utils
.
Check
(
err
)
}
params
:=
octokit
.
ReleaseParams
{
TagName
:
tag
,
TargetCommitish
:
commitish
,
Name
:
title
,
Body
:
body
,
Draft
:
flagReleaseDraft
,
Prerelease
:
flagReleasePrerelease
,
}
release
,
err
=
client
.
CreateRelease
(
project
,
params
)
utils
.
Check
(
err
)
if
editor
!=
nil
{
defer
editor
.
DeleteFile
()
}
}
if
len
(
flagReleaseAssets
)
>
0
{
paths
:=
make
([]
string
,
0
)
for
_
,
asset
:=
range
flagReleaseAssets
{
finder
:=
assetFinder
{}
p
,
err
:=
finder
.
Find
(
asset
)
utils
.
Check
(
err
)
paths
=
append
(
paths
,
p
...
)
}
uploader
:=
assetUploader
{
Client
:
client
,
Release
:
release
,
}
err
=
uploader
.
UploadAll
(
paths
)
if
err
!=
nil
{
ui
.
Println
(
""
)
utils
.
Check
(
err
)
}
}
ui
.
Printf
(
"
\n
%s
\n
"
,
release
.
HTMLURL
)
})
}
type
assetUploader
struct
{
Client
*
github
.
Client
Release
*
octokit
.
Release
}
func
(
a
*
assetUploader
)
UploadAll
(
paths
[]
string
)
error
{
errUploadChan
:=
make
(
chan
string
)
successChan
:=
make
(
chan
bool
)
total
:=
len
(
paths
)
count
:=
0
for
_
,
path
:=
range
paths
{
go
a
.
uploadAsync
(
path
,
successChan
,
errUploadChan
)
}
localRepo
,
err
:=
github
.
LocalRepo
()
utils
.
Check
(
err
)
a
.
printUploadProgress
(
count
,
total
)
errUploads
:=
make
([]
string
,
0
)
for
{
select
{
case
_
=
<-
successChan
:
count
++
a
.
printUploadProgress
(
count
,
total
)
case
errUpload
:=
<-
errUploadChan
:
errUploads
=
append
(
errUploads
,
errUpload
)
count
++
a
.
printUploadProgress
(
count
,
total
)
}
project
,
err
:=
localRepo
.
CurrentProject
()
utils
.
Check
(
err
)
if
count
==
total
{
break
}
}
gh
:=
github
.
NewClient
(
project
.
Host
)
var
err
error
if
len
(
errUploads
)
>
0
{
err
=
fmt
.
Errorf
(
"Error uploading %s"
,
strings
.
Join
(
errUploads
,
", "
))
commitish
:=
flagReleaseCommitish
if
commitish
==
""
{
currentBranch
,
err
:=
localRepo
.
CurrentBranch
()
utils
.
Check
(
err
)
commitish
=
currentBranch
.
ShortName
()
}
return
err
}
var
title
string
var
body
string
var
editor
*
github
.
Editor
func
(
a
*
assetUploader
)
uploadAsync
(
path
string
,
successChan
chan
bool
,
errUploadChan
chan
string
)
{
err
:=
a
.
Upload
(
path
)
if
err
==
nil
{
successChan
<-
true
if
flagReleaseMessage
!=
""
{
title
,
body
=
readMsg
(
flagReleaseMessage
)
}
else
if
flagReleaseFile
!=
""
{
title
,
body
,
err
=
readMsgFromFile
(
flagReleaseMessage
)
utils
.
Check
(
err
)
}
else
{
errUploadChan
<-
path
}
}
func
(
a
*
assetUploader
)
Upload
(
path
string
)
error
{
contentType
,
err
:=
a
.
detectContentType
(
path
)
if
err
!=
nil
{
return
err
}
f
,
err
:=
os
.
Open
(
path
)
if
err
!=
nil
{
return
err
}
defer
f
.
Close
()
uploadUrl
,
err
:=
a
.
Release
.
UploadURL
.
Expand
(
octokit
.
M
{
"name"
:
filepath
.
Base
(
path
)})
if
err
!=
nil
{
return
err
}
cs
:=
git
.
CommentChar
()
message
,
err
:=
renderReleaseTpl
(
cs
,
tagName
,
project
.
String
(),
commitish
)
utils
.
Check
(
err
)
return
a
.
Client
.
UploadReleaseAsset
(
uploadUrl
,
f
,
contentTyp
e
)
}
editor
,
err
:=
github
.
NewEditor
(
"RELEASE"
,
"release"
,
messag
e
)
utils
.
Check
(
err
)
func
(
a
*
assetUploader
)
detectContentType
(
path
string
)
(
string
,
error
)
{
file
,
err
:=
os
.
Open
(
path
)
if
err
!=
nil
{
return
""
,
err
title
,
body
,
err
=
editor
.
EditTitleAndBody
()
utils
.
Check
(
err
)
}
defer
file
.
Close
()
fi
,
err
:=
file
.
Stat
()
if
err
!=
nil
{
return
""
,
err
if
title
==
""
{
utils
.
Check
(
fmt
.
Errorf
(
"Aborting release due to empty release title"
))
}
fileHeader
:=
&
bytes
.
Buffer
{}
headerSize
:=
int64
(
512
)
if
fi
.
Size
()
<
headerSize
{
headerSize
=
fi
.
Size
()
params
:=
&
github
.
Release
{
TagName
:
tagName
,
TargetCommitish
:
commitish
,
Name
:
title
,
Body
:
body
,
Draft
:
flagReleaseDraft
,
Prerelease
:
flagReleasePrerelease
,
}
// The content type detection only uses 512 bytes at most.
// This way we avoid copying the whole content for big files.
_
,
err
=
io
.
CopyN
(
fileHeader
,
file
,
headerSize
)
if
err
!=
nil
{
return
""
,
err
}
var
release
*
github
.
Release
t
:=
http
.
DetectContentType
(
fileHeader
.
Bytes
())
if
args
.
Noop
{
ui
.
Printf
(
"Would create release `%s' for %s with tag name `%s'
\n
"
,
title
,
project
,
tagName
)
}
else
{
release
,
err
=
gh
.
CreateRelease
(
project
,
params
)
utils
.
Check
(
err
)
return
strings
.
Split
(
t
,
";"
)[
0
],
nil
}
if
editor
!=
nil
{
defer
editor
.
DeleteFile
()
}
func
(
a
*
assetUploader
)
printUploadProgress
(
count
int
,
total
int
)
{
out
:=
fmt
.
Sprintf
(
"Uploading assets (%d/%d)"
,
count
,
total
)
fmt
.
Print
(
"
\r
"
+
out
)
}
ui
.
Println
(
release
.
HtmlUrl
)
}
type
assetFinder
struct
{
uploadAssets
(
gh
,
release
,
flagReleaseAssets
,
args
)
os
.
Exit
(
0
)
}
func
(
a
*
assetFinder
)
Find
(
path
string
)
([]
string
,
error
)
{
result
:=
make
([]
string
,
0
)
err
:=
filepath
.
Walk
(
path
,
func
(
path
string
,
info
os
.
FileInfo
,
err
error
)
error
{
if
err
!=
nil
{
return
err
func
uploadAssets
(
gh
*
github
.
Client
,
release
*
github
.
Release
,
assets
[]
string
,
args
*
Args
)
{
for
_
,
asset
:=
range
assets
{
var
label
string
parts
:=
strings
.
SplitN
(
asset
,
"#"
,
2
)
asset
=
parts
[
0
]
if
len
(
parts
)
>
1
{
label
=
parts
[
1
]
}
if
!
info
.
IsDir
()
{
result
=
append
(
result
,
path
)
if
args
.
Noop
{
if
label
==
""
{
ui
.
Errorf
(
"Would attach release asset `%s'
\n
"
,
asset
)
}
else
{
ui
.
Errorf
(
"Would attach release asset `%s' with label `%s'
\n
"
,
asset
,
label
)
}
}
else
{
ui
.
Errorf
(
"Attaching release asset `%s'...
\n
"
,
asset
)
_
,
err
:=
gh
.
UploadReleaseAsset
(
release
,
asset
,
label
)
utils
.
Check
(
err
)
}
return
nil
})
return
result
,
err
}
}
commands/release_test.go
已删除
100644 → 0
浏览文件 @
0fb96e05
package
commands
import
(
"testing"
"github.com/bmizerany/assert"
"github.com/github/hub/fixtures"
)
func
TestAssetFinder_Find
(
t
*
testing
.
T
)
{
finder
:=
assetFinder
{}
paths
,
err
:=
finder
.
Find
(
fixtures
.
Path
(
"release_dir"
,
"file1"
))
assert
.
Equal
(
t
,
nil
,
err
)
assert
.
Equal
(
t
,
1
,
len
(
paths
))
paths
,
err
=
finder
.
Find
(
fixtures
.
Path
(
"release_dir"
,
"dir"
))
assert
.
Equal
(
t
,
nil
,
err
)
assert
.
Equal
(
t
,
3
,
len
(
paths
))
}
func
TestAssetUploader_detectContentType
(
t
*
testing
.
T
)
{
u
:=
&
assetUploader
{}
ct
,
err
:=
u
.
detectContentType
(
fixtures
.
Path
(
"release_dir"
,
"file1"
))
assert
.
Equal
(
t
,
nil
,
err
)
assert
.
Equal
(
t
,
"text/plain"
,
ct
)
}
commands/release_tpl.go
浏览文件 @
e70b7fe1
...
...
@@ -5,7 +5,8 @@ import (
"text/template"
)
const
releaseTmpl
=
`{{.CS}} Creating release {{.TagName}} for {{.ProjectName}} from {{.BranchName}}
const
releaseTmpl
=
`
{{.CS}} Creating release {{.TagName}} for {{.ProjectName}} from {{.BranchName}}
{{.CS}}
{{.CS}} Write a message for this release. The first block of
{{.CS}} text is the title and the rest is the description.`
...
...
commands/utils.go
浏览文件 @
e70b7fe1
...
...
@@ -80,34 +80,35 @@ func getTitleAndBodyFromFlags(messageFlag, fileFlag string) (title, body string,
if
messageFlag
!=
""
{
title
,
body
=
readMsg
(
messageFlag
)
}
else
if
fileFlag
!=
""
{
var
(
content
[]
byte
err
error
)
title
,
body
,
err
=
readMsgFromFile
(
fileFlag
)
}
if
fileFlag
==
"-"
{
content
,
err
=
ioutil
.
ReadAll
(
os
.
Stdin
)
}
else
{
content
,
err
=
ioutil
.
ReadFile
(
fileFlag
)
}
utils
.
Check
(
err
)
return
}
title
,
body
=
readMsg
(
string
(
content
))
func
readMsgFromFile
(
filename
string
)
(
title
,
body
string
,
err
error
)
{
var
content
[]
byte
if
filename
==
"-"
{
content
,
err
=
ioutil
.
ReadAll
(
os
.
Stdin
)
}
else
{
content
,
err
=
ioutil
.
ReadFile
(
filename
)
}
if
err
!=
nil
{
return
}
text
:=
strings
.
Replace
(
string
(
content
),
"
\r\n
"
,
"
\n
"
,
-
1
)
title
,
body
=
readMsg
(
text
)
return
}
func
readMsg
(
msg
string
)
(
title
,
body
string
)
{
s
:=
bufio
.
NewScanner
(
strings
.
NewReader
(
msg
))
if
s
.
Scan
()
{
title
=
s
.
Text
()
body
=
strings
.
TrimLeft
(
msg
,
title
)
func
readMsg
(
message
string
)
(
title
,
body
string
)
{
parts
:=
strings
.
SplitN
(
message
,
"
\n\n
"
,
2
)
title
=
strings
.
TrimSpace
(
title
)
body
=
strings
.
TrimSpace
(
body
)
title
=
strings
.
TrimSpace
(
strings
.
Replace
(
parts
[
0
],
"
\n
"
,
" "
,
-
1
))
if
len
(
parts
)
>
1
{
body
=
strings
.
TrimSpace
(
parts
[
1
])
}
return
}
...
...
commands/utils_test.go
浏览文件 @
e70b7fe1
...
...
@@ -21,9 +21,9 @@ func TestReadMsg(t *testing.T) {
assert
.
Equal
(
t
,
"my pull title"
,
title
)
assert
.
Equal
(
t
,
"my description
\n\n
another line"
,
body
)
title
,
body
=
readMsg
(
"my pull
title
\r\n\r\n
my description
\r\n\r
\n
another line"
)
title
,
body
=
readMsg
(
"my pull
\n
title
\n\n
my description
\n
\n
another line"
)
assert
.
Equal
(
t
,
"my pull title"
,
title
)
assert
.
Equal
(
t
,
"my description
\
r\n\r
\n
another line"
,
body
)
assert
.
Equal
(
t
,
"my description
\
n
\n
another line"
,
body
)
}
func
TestDirIsNotEmpty
(
t
*
testing
.
T
)
{
...
...
github/client.go
浏览文件 @
e70b7fe1
...
...
@@ -6,6 +6,7 @@ import (
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/octokit/go-octokit/octokit"
...
...
@@ -246,19 +247,22 @@ func (client *Client) CreateRepository(project *Project, description, homepage s
}
type
Release
struct
{
Name
string
`json:"name"`
TagName
string
`json:"tag_name"`
Body
string
`json:"body"`
Draft
bool
`json:"draft"`
Prerelease
bool
`json:"prerelease"`
Assets
[]
ReleaseAsset
`json:"assets"`
TarballUrl
string
`json:"tarball_url"`
ZipballUrl
string
`json:"zipball_url"`
Name
string
`json:"name"`
TagName
string
`json:"tag_name"`
TargetCommitish
string
`json:"target_commitish"`
Body
string
`json:"body"`
Draft
bool
`json:"draft"`
Prerelease
bool
`json:"prerelease"`
Assets
[]
ReleaseAsset
`json:"assets"`
TarballUrl
string
`json:"tarball_url"`
ZipballUrl
string
`json:"zipball_url"`
HtmlUrl
string
`json:"html_url"`
UploadUrl
string
`json:"upload_url"`
}
type
ReleaseAsset
struct
{
Name
string
`json:"name"`
Label
string
`json:"label"`
Name
string
`json:"name"`
Label
string
`json:"label"`
DownloadUrl
string
`json:"browser_download_url"`
}
...
...
@@ -302,72 +306,50 @@ func (client *Client) FetchRelease(project *Project, tagName string) (foundRelea
return
}
func
(
client
*
Client
)
Release
(
project
*
Project
,
tagName
string
)
(
release
*
octokit
.
Release
,
err
error
)
{
url
,
err
:=
octokit
.
ReleasesURL
.
Expand
(
octokit
.
M
{
"owner"
:
project
.
Owner
,
"repo"
:
project
.
Name
}
)
func
(
client
*
Client
)
CreateRelease
(
project
*
Project
,
releaseParams
*
Release
)
(
release
*
Release
,
err
error
)
{
api
,
err
:=
client
.
simpleApi
(
)
if
err
!=
nil
{
return
}
api
,
err
:=
client
.
api
(
)
res
,
err
:=
api
.
PostJSON
(
fmt
.
Sprintf
(
"repos/%s/%s/releases"
,
project
.
Owner
,
project
.
Name
),
releaseParams
)
if
err
!=
nil
{
err
=
FormatError
(
"getting release"
,
err
)
return
}
releases
,
result
:=
api
.
Releases
(
client
.
requestURL
(
url
))
.
All
()
if
result
.
HasError
()
{
err
=
FormatError
(
"creating release"
,
result
.
Err
)
if
res
.
StatusCode
!=
201
{
err
=
fmt
.
Errorf
(
"Unexpected HTTP status code: %d"
,
res
.
StatusCode
)
return
}
for
_
,
release
:=
range
releases
{
if
release
.
TagName
==
tagName
{
return
&
release
,
nil
}
}
release
=
&
Release
{}
err
=
res
.
Unmarshal
(
release
)
return
}
func
(
client
*
Client
)
CreateRelease
(
project
*
Project
,
params
octokit
.
ReleaseParams
)
(
release
*
octokit
.
Release
,
err
error
)
{
url
,
err
:=
octokit
.
ReleasesURL
.
Expand
(
octokit
.
M
{
"owner"
:
project
.
Owner
,
"repo"
:
project
.
Name
})
if
err
!=
nil
{
return
}
api
,
err
:=
client
.
api
()
func
(
client
*
Client
)
UploadReleaseAsset
(
release
*
Release
,
filename
,
label
string
)
(
asset
*
ReleaseAsset
,
err
error
)
{
api
,
err
:=
client
.
simpleApi
()
if
err
!=
nil
{
err
=
FormatError
(
"creating release"
,
err
)
return
}
release
,
result
:=
api
.
Releases
(
client
.
requestURL
(
url
))
.
Create
(
params
)
if
result
.
HasError
()
{
err
=
FormatError
(
"creating release"
,
result
.
Err
)
return
}
return
}
func
(
client
*
Client
)
UploadReleaseAsset
(
uploadUrl
*
url
.
URL
,
asset
*
os
.
File
,
contentType
string
)
(
err
error
)
{
fileInfo
,
err
:=
asset
.
Stat
()
if
err
!=
nil
{
return
parts
:=
strings
.
SplitN
(
release
.
UploadUrl
,
"{"
,
2
)
uploadUrl
:=
parts
[
0
]
uploadUrl
+=
"?name="
+
url
.
QueryEscape
(
filepath
.
Base
(
filename
))
if
label
!=
""
{
uploadUrl
+=
"&label="
+
url
.
QueryEscape
(
label
)
}
api
,
err
:=
client
.
api
(
)
res
,
err
:=
api
.
PostFile
(
uploadUrl
,
filename
)
if
err
!=
nil
{
err
=
FormatError
(
"uploading asset"
,
err
)
return
}
result
:=
api
.
Uploads
(
uploadUrl
)
.
UploadAsset
(
asset
,
contentType
,
fileInfo
.
Size
())
if
result
.
HasError
()
{
err
=
FormatError
(
"uploading asset"
,
result
.
Err
)
if
res
.
StatusCode
!=
201
{
err
=
fmt
.
Errorf
(
"Unexpected HTTP status code: %d"
,
res
.
StatusCode
)
return
}
asset
=
&
ReleaseAsset
{}
err
=
res
.
Unmarshal
(
asset
)
return
}
...
...
github/http.go
浏览文件 @
e70b7fe1
...
...
@@ -200,6 +200,66 @@ func (c *simpleClient) Get(path string) (res *simpleResponse, err error) {
return
}
func
(
c
*
simpleClient
)
PostJSON
(
path
string
,
payload
interface
{})
(
res
*
simpleResponse
,
err
error
)
{
url
,
err
:=
url
.
Parse
(
path
)
if
err
!=
nil
{
return
}
json
,
err
:=
json
.
Marshal
(
payload
)
buf
:=
bytes
.
NewBuffer
(
json
)
url
=
c
.
rootUrl
.
ResolveReference
(
url
)
req
,
err
:=
http
.
NewRequest
(
"POST"
,
url
.
String
(),
buf
)
if
err
!=
nil
{
return
}
req
.
Header
.
Set
(
"Content-Type"
,
"application/json"
)
req
.
Header
.
Set
(
"Authorization"
,
"token "
+
c
.
accessToken
)
httpResponse
,
err
:=
c
.
httpClient
.
Do
(
req
)
if
err
==
nil
{
res
=
&
simpleResponse
{
httpResponse
}
}
return
}
func
(
c
*
simpleClient
)
PostFile
(
path
,
filename
string
)
(
res
*
simpleResponse
,
err
error
)
{
url
,
err
:=
url
.
Parse
(
path
)
if
err
!=
nil
{
return
}
stat
,
err
:=
os
.
Stat
(
filename
)
if
err
!=
nil
{
return
}
fileSize
:=
stat
.
Size
()
file
,
err
:=
os
.
Open
(
filename
)
if
err
!=
nil
{
return
}
defer
file
.
Close
()
url
=
c
.
rootUrl
.
ResolveReference
(
url
)
req
,
err
:=
http
.
NewRequest
(
"POST"
,
url
.
String
(),
file
)
if
err
!=
nil
{
return
}
req
.
ContentLength
=
fileSize
req
.
Header
.
Set
(
"Content-Type"
,
"application/octet-stream"
)
req
.
Header
.
Set
(
"Authorization"
,
"token "
+
c
.
accessToken
)
httpResponse
,
err
:=
c
.
httpClient
.
Do
(
req
)
if
err
==
nil
{
res
=
&
simpleResponse
{
httpResponse
}
}
return
}
type
simpleResponse
struct
{
*
http
.
Response
}
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录