mirror of
https://github.com/gogs/gogs.git
synced 2025-05-31 11:42:13 +00:00
webhook: add Release event (#2387)
This commit is contained in:
parent
b9bb4a62d6
commit
b615d670b3
@ -776,6 +776,8 @@ settings.event_issue_comment = Issue Comment
|
||||
settings.event_issue_comment_desc = Issue comment created, edited, or deleted.
|
||||
settings.event_pull_request = Pull Request
|
||||
settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, demilestoned, or synchronized.
|
||||
settings.event_release = Release
|
||||
settings.event_release_desc = Release published in a repository.
|
||||
settings.active = Active
|
||||
settings.active_helper = Details regarding the event which triggered the hook will be delivered as well.
|
||||
settings.add_hook_success = New webhook has been added.
|
||||
|
@ -12,9 +12,10 @@ import (
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
@ -11,8 +11,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogits/git-module"
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
)
|
||||
@ -21,6 +23,7 @@ import (
|
||||
type Release struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64
|
||||
Repo *Repository `xorm:"-"`
|
||||
PublisherID int64
|
||||
Publisher *User `xorm:"-"`
|
||||
TagName string
|
||||
@ -52,6 +55,13 @@ func (r *Release) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
|
||||
func (r *Release) loadAttributes(e Engine) (err error) {
|
||||
if r.Repo == nil {
|
||||
r.Repo, err = getRepositoryByID(e, r.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getRepositoryByID [repo_id: %d]: %v", r.RepoID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if r.Publisher == nil {
|
||||
r.Publisher, err = getUserByID(e, r.PublisherID)
|
||||
if err != nil {
|
||||
@ -59,7 +69,7 @@ func (r *Release) loadAttributes(e Engine) (err error) {
|
||||
r.PublisherID = -1
|
||||
r.Publisher = NewGhostUser()
|
||||
} else {
|
||||
return fmt.Errorf("getUserByID.(Publisher) [%d]: %v", r.PublisherID, err)
|
||||
return fmt.Errorf("getUserByID.(Publisher) [publisher_id: %d]: %v", r.PublisherID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,6 +81,22 @@ func (r *Release) LoadAttributes() error {
|
||||
return r.loadAttributes(x)
|
||||
}
|
||||
|
||||
// This method assumes some fields assigned with values:
|
||||
// Required - Publisher
|
||||
func (r *Release) APIFormat() *api.Release {
|
||||
return &api.Release{
|
||||
ID: r.ID,
|
||||
TagName: r.TagName,
|
||||
TargetCommitish: r.Target,
|
||||
Name: r.Title,
|
||||
Body: r.Note,
|
||||
Draft: r.IsDraft,
|
||||
Prerelease: r.IsPrerelease,
|
||||
Author: r.Publisher.APIFormat(),
|
||||
Created: r.Created,
|
||||
}
|
||||
}
|
||||
|
||||
// IsReleaseExist returns true if release with given tag name already exists.
|
||||
func IsReleaseExist(repoID int64, tagName string) (bool, error) {
|
||||
if len(tagName) == 0 {
|
||||
@ -113,6 +139,17 @@ func createTag(gitRepo *git.Repository, r *Release) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Release) preparePublishWebhooks() {
|
||||
if err := PrepareWebhooks(r.Repo, HOOK_EVENT_RELEASE, &api.ReleasePayload{
|
||||
Action: api.HOOK_RELEASE_PUBLISHED,
|
||||
Release: r.APIFormat(),
|
||||
Repository: r.Repo.APIFormat(nil),
|
||||
Sender: r.Publisher.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error(2, "PrepareWebhooks: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateRelease creates a new release of repository.
|
||||
func CreateRelease(gitRepo *git.Repository, r *Release) error {
|
||||
isExist, err := IsReleaseExist(r.RepoID, r.TagName)
|
||||
@ -126,8 +163,20 @@ func CreateRelease(gitRepo *git.Repository, r *Release) error {
|
||||
return err
|
||||
}
|
||||
r.LowerTagName = strings.ToLower(r.TagName)
|
||||
_, err = x.InsertOne(r)
|
||||
return err
|
||||
if _, err = x.Insert(r); err != nil {
|
||||
return fmt.Errorf("Insert: %v", err)
|
||||
}
|
||||
|
||||
// Only send webhook when actually published, skip drafts
|
||||
if r.IsDraft {
|
||||
return nil
|
||||
}
|
||||
r, err = GetReleaseByID(r.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetReleaseByID: %v", err)
|
||||
}
|
||||
r.preparePublishWebhooks()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRelease returns release by given ID.
|
||||
@ -205,12 +254,22 @@ func SortReleases(rels []*Release) {
|
||||
}
|
||||
|
||||
// UpdateRelease updates information of a release.
|
||||
func UpdateRelease(gitRepo *git.Repository, rel *Release) (err error) {
|
||||
if err = createTag(gitRepo, rel); err != nil {
|
||||
func UpdateRelease(doer *User, gitRepo *git.Repository, r *Release, isPublish bool) (err error) {
|
||||
if err = createTag(gitRepo, r); err != nil {
|
||||
return fmt.Errorf("createTag: %v", err)
|
||||
}
|
||||
|
||||
r.PublisherID = doer.ID
|
||||
if _, err = x.Id(r.ID).AllCols().Update(r); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.Id(rel.ID).AllCols().Update(rel)
|
||||
return err
|
||||
|
||||
if !isPublish {
|
||||
return nil
|
||||
}
|
||||
r.Publisher = doer
|
||||
r.preparePublishWebhooks()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteReleaseOfRepoByID deletes a release and corresponding Git tag by given ID.
|
||||
|
@ -69,6 +69,7 @@ type HookEvents struct {
|
||||
Issues bool `json:"issues"`
|
||||
IssueComment bool `json:"issue_comment"`
|
||||
PullRequest bool `json:"pull_request"`
|
||||
Release bool `json:"release"`
|
||||
}
|
||||
|
||||
// HookEvent represents events that will delivery hook.
|
||||
@ -196,6 +197,12 @@ func (w *Webhook) HasPullRequestEvent() bool {
|
||||
(w.ChooseEvents && w.HookEvents.PullRequest)
|
||||
}
|
||||
|
||||
// HasReleaseEvent returns true if hook enabled release event.
|
||||
func (w *Webhook) HasReleaseEvent() bool {
|
||||
return w.SendEverything ||
|
||||
(w.ChooseEvents && w.HookEvents.Release)
|
||||
}
|
||||
|
||||
type eventChecker struct {
|
||||
checker func() bool
|
||||
typ HookEventType
|
||||
@ -211,6 +218,7 @@ func (w *Webhook) EventsArray() []string {
|
||||
{w.HasIssuesEvent, HOOK_EVENT_ISSUES},
|
||||
{w.HasIssueCommentEvent, HOOK_EVENT_ISSUE_COMMENT},
|
||||
{w.HasPullRequestEvent, HOOK_EVENT_PULL_REQUEST},
|
||||
{w.HasReleaseEvent, HOOK_EVENT_RELEASE},
|
||||
}
|
||||
for _, c := range eventCheckers {
|
||||
if c.checker() {
|
||||
@ -381,6 +389,7 @@ const (
|
||||
HOOK_EVENT_ISSUES HookEventType = "issues"
|
||||
HOOK_EVENT_ISSUE_COMMENT HookEventType = "issue_comment"
|
||||
HOOK_EVENT_PULL_REQUEST HookEventType = "pull_request"
|
||||
HOOK_EVENT_RELEASE HookEventType = "release"
|
||||
)
|
||||
|
||||
// HookRequest represents hook task request information.
|
||||
|
@ -353,6 +353,22 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getDiscordReleasePayload(p *api.ReleasePayload) (*DiscordPayload, error) {
|
||||
repoLink := DiscordLinkFormatter(p.Repository.HTMLURL, p.Repository.Name)
|
||||
refLink := DiscordLinkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName)
|
||||
content := fmt.Sprintf("Published new release %s of %s", refLink, repoLink)
|
||||
return &DiscordPayload{
|
||||
Embeds: []*DiscordEmbedObject{{
|
||||
Description: content,
|
||||
URL: setting.AppUrl + p.Sender.UserName,
|
||||
Author: &DiscordEmbedAuthorObject{
|
||||
Name: p.Sender.UserName,
|
||||
IconURL: p.Sender.AvatarUrl,
|
||||
},
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (payload *DiscordPayload, err error) {
|
||||
slack := &SlackMeta{}
|
||||
if err := json.Unmarshal([]byte(meta), &slack); err != nil {
|
||||
@ -374,6 +390,8 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (paylo
|
||||
payload, err = getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
|
||||
case HOOK_EVENT_PULL_REQUEST:
|
||||
payload, err = getDiscordPullRequestPayload(p.(*api.PullRequestPayload), slack)
|
||||
case HOOK_EVENT_RELEASE:
|
||||
payload, err = getDiscordReleasePayload(p.(*api.ReleasePayload))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("event '%s': %v", event, err)
|
||||
|
@ -277,6 +277,15 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getSlackReleasePayload(p *api.ReleasePayload) (*SlackPayload, error) {
|
||||
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.Name)
|
||||
refLink := SlackLinkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName)
|
||||
text := fmt.Sprintf("[%s] new release %s published by %s", repoLink, refLink, p.Sender.UserName)
|
||||
return &SlackPayload{
|
||||
Text: text,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (payload *SlackPayload, err error) {
|
||||
slack := &SlackMeta{}
|
||||
if err := json.Unmarshal([]byte(meta), &slack); err != nil {
|
||||
@ -298,6 +307,8 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (payload
|
||||
payload, err = getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
|
||||
case HOOK_EVENT_PULL_REQUEST:
|
||||
payload, err = getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
|
||||
case HOOK_EVENT_RELEASE:
|
||||
payload, err = getSlackReleasePayload(p.(*api.ReleasePayload))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("event '%s': %v", event, err)
|
||||
|
File diff suppressed because one or more lines are too long
@ -141,6 +141,7 @@ type Webhook struct {
|
||||
Issues bool
|
||||
IssueComment bool
|
||||
PullRequest bool
|
||||
Release bool
|
||||
Active bool
|
||||
}
|
||||
|
||||
|
@ -1227,30 +1227,30 @@ footer .ui.language .menu {
|
||||
right: 0!important;
|
||||
left: auto!important;
|
||||
}
|
||||
.repository.branches .ui.list {
|
||||
.repository.branches:not(.settings) .ui.list {
|
||||
padding: 0;
|
||||
}
|
||||
.repository.branches .ui.list > .item {
|
||||
.repository.branches:not(.settings) .ui.list > .item {
|
||||
margin: 0;
|
||||
line-height: 31px;
|
||||
}
|
||||
.repository.branches .ui.list > .item:not(:last-child) {
|
||||
.repository.branches:not(.settings) .ui.list > .item:not(:last-child) {
|
||||
border-bottom: 1px solid #DDD;
|
||||
}
|
||||
.repository.branches .ui.list > .item .column {
|
||||
.repository.branches:not(.settings) .ui.list > .item .column {
|
||||
padding: 5px 15px;
|
||||
}
|
||||
.repository.branches .ui.list > .item .column .octicon {
|
||||
.repository.branches:not(.settings) .ui.list > .item .column .octicon {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
.repository.branches .ui.list > .item .column code {
|
||||
.repository.branches:not(.settings) .ui.list > .item .column code {
|
||||
padding: 4px 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
.repository.branches .ui.list > .item .column .ui.text:not(i) {
|
||||
.repository.branches:not(.settings) .ui.list > .item .column .ui.text:not(i) {
|
||||
font-size: 12px;
|
||||
}
|
||||
.repository.branches .ui.list > .item .column .ui.button {
|
||||
.repository.branches:not(.settings) .ui.list > .item .column .ui.button {
|
||||
font-size: 12px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
@ -2338,28 +2338,28 @@ footer .ui.language .menu {
|
||||
margin-left: 5px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
.repository.settings.branches .protected-branches .selection.dropdown {
|
||||
.repository.settings.settings.branches .protected-branches .selection.dropdown {
|
||||
width: 300px;
|
||||
}
|
||||
.repository.settings.branches .protected-branches .item {
|
||||
.repository.settings.settings.branches .protected-branches .item {
|
||||
border: 1px solid #eaeaea;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
.repository.settings.branches .protected-branches .item:not(:last-child) {
|
||||
.repository.settings.settings.branches .protected-branches .item:not(:last-child) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.repository.settings.branches .branch-protection .help {
|
||||
.repository.settings.settings.branches .branch-protection .help {
|
||||
margin-left: 26px;
|
||||
padding-top: 0;
|
||||
}
|
||||
.repository.settings.branches .branch-protection .fields {
|
||||
.repository.settings.settings.branches .branch-protection .fields {
|
||||
margin-left: 20px;
|
||||
display: block;
|
||||
}
|
||||
.repository.settings.branches .branch-protection .whitelist {
|
||||
.repository.settings.settings.branches .branch-protection .whitelist {
|
||||
margin-left: 26px;
|
||||
}
|
||||
.repository.settings.branches .branch-protection .whitelist .dropdown img {
|
||||
.repository.settings.settings.branches .branch-protection .whitelist .dropdown img {
|
||||
display: inline-block;
|
||||
}
|
||||
.repository.settings.webhooks .types .menu .item {
|
||||
@ -3035,10 +3035,6 @@ footer .ui.language .menu {
|
||||
.admin .table.segment:not(.select) td:first-of-type {
|
||||
padding-left: 15px !important;
|
||||
}
|
||||
.admin .ui.header,
|
||||
.admin .ui.segment {
|
||||
box-shadow: 0 1px 2px 0 rgba(34, 36, 38, 0.15);
|
||||
}
|
||||
.admin.user .email {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
@ -29,10 +29,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.ui.header,
|
||||
.ui.segment {
|
||||
box-shadow: 0 1px 2px 0 rgba(34,36,38,.15);
|
||||
}
|
||||
|
||||
&.user {
|
||||
.email {
|
||||
|
@ -150,7 +150,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.branches {
|
||||
&.branches:not(.settings) {
|
||||
.ui.list {
|
||||
padding: 0;
|
||||
>.item {
|
||||
@ -1351,7 +1351,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.branches {
|
||||
&.settings.branches {
|
||||
.protected-branches {
|
||||
.selection.dropdown {
|
||||
width: 300px;
|
||||
|
@ -203,7 +203,6 @@ func NewReleasePost(ctx *context.Context, f form.NewRelease) {
|
||||
IsPrerelease: f.Prerelease,
|
||||
CreatedUnix: tagCreatedUnix,
|
||||
}
|
||||
|
||||
if err = models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil {
|
||||
ctx.Data["Err_TagName"] = true
|
||||
switch {
|
||||
@ -274,11 +273,12 @@ func EditReleasePost(ctx *context.Context, f form.EditRelease) {
|
||||
return
|
||||
}
|
||||
|
||||
isPublish := rel.IsDraft && len(f.Draft) == 0
|
||||
rel.Title = f.Title
|
||||
rel.Note = f.Content
|
||||
rel.IsDraft = len(f.Draft) > 0
|
||||
rel.IsPrerelease = f.Prerelease
|
||||
if err = models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil {
|
||||
if err = models.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, isPublish); err != nil {
|
||||
ctx.Handle(500, "UpdateRelease", err)
|
||||
return
|
||||
}
|
||||
|
@ -116,6 +116,7 @@ func ParseHookEvent(f form.Webhook) *models.HookEvent {
|
||||
Issues: f.Issues,
|
||||
IssueComment: f.IssueComment,
|
||||
PullRequest: f.PullRequest,
|
||||
Release: f.Release,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +92,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Release -->
|
||||
<div class="seven wide column">
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input class="hidden" name="release" type="checkbox" tabindex="0" {{if .Webhook.Release}}checked{{end}}>
|
||||
<label>{{.i18n.Tr "repo.settings.event_release"}}</label>
|
||||
<span class="help">{{.i18n.Tr "repo.settings.event_release_desc"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
2
vendor/github.com/gogits/go-gogs-client/gogs.go
generated
vendored
2
vendor/github.com/gogits/go-gogs-client/gogs.go
generated
vendored
@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func Version() string {
|
||||
return "0.12.9"
|
||||
return "0.12.10"
|
||||
}
|
||||
|
||||
// Client represents a Gogs API client.
|
||||
|
22
vendor/github.com/gogits/go-gogs-client/release.go
generated
vendored
Normal file
22
vendor/github.com/gogits/go-gogs-client/release.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gogs
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Release represents a release API object.
|
||||
type Release struct {
|
||||
ID int64 `json:"id"`
|
||||
TagName string `json:"tag_name"`
|
||||
TargetCommitish string `json:"target_commitish"`
|
||||
Name string `json:"name"`
|
||||
Body string `json:"body"`
|
||||
Draft bool `json:"draft"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
Author *User `json:"author"`
|
||||
Created time.Time `json:"created_at"`
|
||||
}
|
27
vendor/github.com/gogits/go-gogs-client/repo_hook.go
generated
vendored
27
vendor/github.com/gogits/go-gogs-client/repo_hook.go
generated
vendored
@ -256,6 +256,7 @@ type ChangesPayload struct {
|
||||
Body *ChangesFromPayload `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// IssuesPayload represents a payload information of issues event.
|
||||
type IssuesPayload struct {
|
||||
Action HookIssueAction `json:"action"`
|
||||
Index int64 `json:"number"`
|
||||
@ -277,6 +278,7 @@ const (
|
||||
HOOK_ISSUE_COMMENT_DELETED HookIssueCommentAction = "deleted"
|
||||
)
|
||||
|
||||
// IssueCommentPayload represents a payload information of issue comment event.
|
||||
type IssueCommentPayload struct {
|
||||
Action HookIssueCommentAction `json:"action"`
|
||||
Issue *Issue `json:"issue"`
|
||||
@ -310,3 +312,28 @@ type PullRequestPayload struct {
|
||||
func (p *PullRequestPayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
||||
// __________ .__
|
||||
// \______ \ ____ | | ____ _____ ______ ____
|
||||
// | _// __ \| | _/ __ \\__ \ / ___// __ \
|
||||
// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
|
||||
// |____|_ /\___ >____/\___ >____ /____ >\___ >
|
||||
// \/ \/ \/ \/ \/ \/
|
||||
|
||||
type HookReleaseAction string
|
||||
|
||||
const (
|
||||
HOOK_RELEASE_PUBLISHED HookReleaseAction = "published"
|
||||
)
|
||||
|
||||
// ReleasePayload represents a payload information of release event.
|
||||
type ReleasePayload struct {
|
||||
Action HookReleaseAction `json:"action"`
|
||||
Release *Release `json:"release"`
|
||||
Repository *Repository `json:"repository"`
|
||||
Sender *User `json:"sender"`
|
||||
}
|
||||
|
||||
func (p *ReleasePayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
@ -165,10 +165,10 @@
|
||||
"revisionTime": "2017-03-10T19:06:55Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Rvj0LCHGhFQyIM7MzBPt1iRP89c=",
|
||||
"checksumSHA1": "1p1/OSDPORWbSBCD791BbGh2vVc=",
|
||||
"path": "github.com/gogits/go-gogs-client",
|
||||
"revision": "8e438478f71b840fcd0b3e810684ea4f6bf476bb",
|
||||
"revisionTime": "2017-03-09T09:10:09Z"
|
||||
"revision": "08824b5ad7408bc38f2b9287c94be2f059c9966a",
|
||||
"revisionTime": "2017-03-11T23:40:19Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "p4yoFWgDiTfpu1JYgh26t6+VDTk=",
|
||||
|
Loading…
x
Reference in New Issue
Block a user