merge upstream & fix conflicts

This commit is contained in:
Manush Dodunekov 2020-01-10 19:06:42 +01:00
commit 57744b54c4
62 changed files with 4142 additions and 1933 deletions

View File

@ -0,0 +1,106 @@
// Copyright 2020 The Gitea 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 integrations
import (
"fmt"
"net/http"
"testing"
"code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
func TestAPINotification(t *testing.T) {
defer prepareTestEnv(t)()
user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
thread5 := models.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification)
assert.NoError(t, thread5.LoadAttributes())
session := loginUser(t, user2.Name)
token := getTokenForLoggedInUser(t, session)
// -- GET /notifications --
// test filter
since := "2000-01-01T00%3A50%3A01%2B00%3A00" //946687801
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?since=%s&token=%s", since, token))
resp := session.MakeRequest(t, req, http.StatusOK)
var apiNL []api.NotificationThread
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 1)
assert.EqualValues(t, 5, apiNL[0].ID)
// test filter
before := "2000-01-01T01%3A06%3A59%2B00%3A00" //946688819
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?all=%s&before=%s&token=%s", "true", before, token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 3)
assert.EqualValues(t, 4, apiNL[0].ID)
assert.EqualValues(t, true, apiNL[0].Unread)
assert.EqualValues(t, false, apiNL[0].Pinned)
assert.EqualValues(t, 3, apiNL[1].ID)
assert.EqualValues(t, false, apiNL[1].Unread)
assert.EqualValues(t, true, apiNL[1].Pinned)
assert.EqualValues(t, 2, apiNL[2].ID)
assert.EqualValues(t, false, apiNL[2].Unread)
assert.EqualValues(t, false, apiNL[2].Pinned)
// -- GET /repos/{owner}/{repo}/notifications --
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?token=%s", user2.Name, repo1.Name, token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 1)
assert.EqualValues(t, 4, apiNL[0].ID)
// -- GET /notifications/threads/{id} --
// get forbidden
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d?token=%s", 1, token))
resp = session.MakeRequest(t, req, http.StatusForbidden)
// get own
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d?token=%s", thread5.ID, token))
resp = session.MakeRequest(t, req, http.StatusOK)
var apiN api.NotificationThread
DecodeJSON(t, resp, &apiN)
assert.EqualValues(t, 5, apiN.ID)
assert.EqualValues(t, false, apiN.Pinned)
assert.EqualValues(t, true, apiN.Unread)
assert.EqualValues(t, "issue4", apiN.Subject.Title)
assert.EqualValues(t, "Issue", apiN.Subject.Type)
assert.EqualValues(t, thread5.Issue.APIURL(), apiN.Subject.URL)
assert.EqualValues(t, thread5.Repository.HTMLURL(), apiN.Repository.HTMLURL)
// -- mark notifications as read --
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 2)
lastReadAt := "2000-01-01T00%3A50%3A01%2B00%3A00" //946687801 <- only Notification 4 is in this filter ...
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
resp = session.MakeRequest(t, req, http.StatusResetContent)
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 1)
// -- PATCH /notifications/threads/{id} --
req = NewRequest(t, "PATCH", fmt.Sprintf("/api/v1/notifications/threads/%d?token=%s", thread5.ID, token))
resp = session.MakeRequest(t, req, http.StatusResetContent)
assert.Equal(t, models.NotificationStatusUnread, thread5.Status)
thread5 = models.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification)
assert.Equal(t, models.NotificationStatusRead, thread5.Status)
}

View File

@ -71,19 +71,33 @@ func TestAPITeam(t *testing.T) {
teamID := apiTeam.ID
// Edit team.
editDescription := "team 1"
editFalse := false
teamToEdit := &api.EditTeamOption{
Name: "teamone",
Description: "team 1",
IncludesAllRepositories: false,
Description: &editDescription,
Permission: "admin",
IncludesAllRepositories: &editFalse,
Units: []string{"repo.code", "repo.pulls", "repo.releases"},
}
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit)
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, &apiTeam, teamToEdit.Name, teamToEdit.Description, teamToEdit.IncludesAllRepositories,
checkTeamResponse(t, &apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
teamToEdit.Permission, teamToEdit.Units)
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, teamToEdit.Description, teamToEdit.IncludesAllRepositories,
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
teamToEdit.Permission, teamToEdit.Units)
// Edit team Description only
editDescription = "first team"
teamToEditDesc := api.EditTeamOption{Description: &editDescription}
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEditDesc)
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, &apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
teamToEdit.Permission, teamToEdit.Units)
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
teamToEdit.Permission, teamToEdit.Units)
// Read team.
@ -91,7 +105,7 @@ func TestAPITeam(t *testing.T) {
req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID)
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, &apiTeam, teamRead.Name, teamRead.Description, teamRead.IncludesAllRepositories,
checkTeamResponse(t, &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
teamRead.Authorize.String(), teamRead.GetUnitNames())
// Delete team.

View File

@ -13,10 +13,8 @@ import (
"time"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"github.com/unknwon/com"
@ -284,130 +282,6 @@ func (a *Action) GetIssueContent() string {
return issue.Content
}
// PushCommit represents a commit in a push operation.
type PushCommit struct {
Sha1 string
Message string
AuthorEmail string
AuthorName string
CommitterEmail string
CommitterName string
Timestamp time.Time
}
// PushCommits represents list of commits in a push operation.
type PushCommits struct {
Len int
Commits []*PushCommit
CompareURL string
avatars map[string]string
emailUsers map[string]*User
}
// NewPushCommits creates a new PushCommits object.
func NewPushCommits() *PushCommits {
return &PushCommits{
avatars: make(map[string]string),
emailUsers: make(map[string]*User),
}
}
// ToAPIPayloadCommits converts a PushCommits object to
// api.PayloadCommit format.
func (pc *PushCommits) ToAPIPayloadCommits(repoPath, repoLink string) ([]*api.PayloadCommit, error) {
commits := make([]*api.PayloadCommit, len(pc.Commits))
if pc.emailUsers == nil {
pc.emailUsers = make(map[string]*User)
}
var err error
for i, commit := range pc.Commits {
authorUsername := ""
author, ok := pc.emailUsers[commit.AuthorEmail]
if !ok {
author, err = GetUserByEmail(commit.AuthorEmail)
if err == nil {
authorUsername = author.Name
pc.emailUsers[commit.AuthorEmail] = author
}
} else {
authorUsername = author.Name
}
committerUsername := ""
committer, ok := pc.emailUsers[commit.CommitterEmail]
if !ok {
committer, err = GetUserByEmail(commit.CommitterEmail)
if err == nil {
// TODO: check errors other than email not found.
committerUsername = committer.Name
pc.emailUsers[commit.CommitterEmail] = committer
}
} else {
committerUsername = committer.Name
}
fileStatus, err := git.GetCommitFileStatus(repoPath, commit.Sha1)
if err != nil {
return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %v", commit.Sha1, err)
}
commits[i] = &api.PayloadCommit{
ID: commit.Sha1,
Message: commit.Message,
URL: fmt.Sprintf("%s/commit/%s", repoLink, commit.Sha1),
Author: &api.PayloadUser{
Name: commit.AuthorName,
Email: commit.AuthorEmail,
UserName: authorUsername,
},
Committer: &api.PayloadUser{
Name: commit.CommitterName,
Email: commit.CommitterEmail,
UserName: committerUsername,
},
Added: fileStatus.Added,
Removed: fileStatus.Removed,
Modified: fileStatus.Modified,
Timestamp: commit.Timestamp,
}
}
return commits, nil
}
// AvatarLink tries to match user in database with e-mail
// in order to show custom avatar, and falls back to general avatar link.
func (pc *PushCommits) AvatarLink(email string) string {
if pc.avatars == nil {
pc.avatars = make(map[string]string)
}
avatar, ok := pc.avatars[email]
if ok {
return avatar
}
u, ok := pc.emailUsers[email]
if !ok {
var err error
u, err = GetUserByEmail(email)
if err != nil {
pc.avatars[email] = base.AvatarLink(email)
if !IsErrUserNotExist(err) {
log.Error("GetUserByEmail: %v", err)
return ""
}
} else {
pc.emailUsers[email] = u
}
}
if u != nil {
pc.avatars[email] = u.RelAvatarLink()
}
return pc.avatars[email]
}
// GetFeedsOptions options for retrieving feeds
type GetFeedsOptions struct {
RequestedUser *User // the user we want activity for

View File

@ -27,106 +27,6 @@ func TestAction_GetRepoLink(t *testing.T) {
assert.Equal(t, expected, action.GetRepoLink())
}
func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
pushCommits := NewPushCommits()
pushCommits.Commits = []*PushCommit{
{
Sha1: "69554a6",
CommitterEmail: "user2@example.com",
CommitterName: "User2",
AuthorEmail: "user2@example.com",
AuthorName: "User2",
Message: "not signed commit",
},
{
Sha1: "27566bd",
CommitterEmail: "user2@example.com",
CommitterName: "User2",
AuthorEmail: "user2@example.com",
AuthorName: "User2",
Message: "good signed commit (with not yet validated email)",
},
{
Sha1: "5099b81",
CommitterEmail: "user2@example.com",
CommitterName: "User2",
AuthorEmail: "user2@example.com",
AuthorName: "User2",
Message: "good signed commit",
},
}
pushCommits.Len = len(pushCommits.Commits)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 16}).(*Repository)
payloadCommits, err := pushCommits.ToAPIPayloadCommits(repo.RepoPath(), "/user2/repo16")
assert.NoError(t, err)
assert.EqualValues(t, 3, len(payloadCommits))
assert.Equal(t, "69554a6", payloadCommits[0].ID)
assert.Equal(t, "not signed commit", payloadCommits[0].Message)
assert.Equal(t, "/user2/repo16/commit/69554a6", payloadCommits[0].URL)
assert.Equal(t, "User2", payloadCommits[0].Committer.Name)
assert.Equal(t, "user2", payloadCommits[0].Committer.UserName)
assert.Equal(t, "User2", payloadCommits[0].Author.Name)
assert.Equal(t, "user2", payloadCommits[0].Author.UserName)
assert.EqualValues(t, []string{}, payloadCommits[0].Added)
assert.EqualValues(t, []string{}, payloadCommits[0].Removed)
assert.EqualValues(t, []string{"readme.md"}, payloadCommits[0].Modified)
assert.Equal(t, "27566bd", payloadCommits[1].ID)
assert.Equal(t, "good signed commit (with not yet validated email)", payloadCommits[1].Message)
assert.Equal(t, "/user2/repo16/commit/27566bd", payloadCommits[1].URL)
assert.Equal(t, "User2", payloadCommits[1].Committer.Name)
assert.Equal(t, "user2", payloadCommits[1].Committer.UserName)
assert.Equal(t, "User2", payloadCommits[1].Author.Name)
assert.Equal(t, "user2", payloadCommits[1].Author.UserName)
assert.EqualValues(t, []string{}, payloadCommits[1].Added)
assert.EqualValues(t, []string{}, payloadCommits[1].Removed)
assert.EqualValues(t, []string{"readme.md"}, payloadCommits[1].Modified)
assert.Equal(t, "5099b81", payloadCommits[2].ID)
assert.Equal(t, "good signed commit", payloadCommits[2].Message)
assert.Equal(t, "/user2/repo16/commit/5099b81", payloadCommits[2].URL)
assert.Equal(t, "User2", payloadCommits[2].Committer.Name)
assert.Equal(t, "user2", payloadCommits[2].Committer.UserName)
assert.Equal(t, "User2", payloadCommits[2].Author.Name)
assert.Equal(t, "user2", payloadCommits[2].Author.UserName)
assert.EqualValues(t, []string{"readme.md"}, payloadCommits[2].Added)
assert.EqualValues(t, []string{}, payloadCommits[2].Removed)
assert.EqualValues(t, []string{}, payloadCommits[2].Modified)
}
func TestPushCommits_AvatarLink(t *testing.T) {
pushCommits := NewPushCommits()
pushCommits.Commits = []*PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user4@example.com",
AuthorName: "User Four",
Message: "message1",
},
{
Sha1: "abcdef2",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user2@example.com",
AuthorName: "User Two",
Message: "message2",
},
}
pushCommits.Len = len(pushCommits.Commits)
assert.Equal(t,
"/suburl/user/avatar/user2/-1",
pushCommits.AvatarLink("user2@example.com"))
assert.Equal(t,
"https://secure.gravatar.com/avatar/19ade630b94e1e0535b3df7387434154?d=identicon",
pushCommits.AvatarLink("nonexistent@example.com"))
}
func TestGetFeeds(t *testing.T) {
// test with an individual user
assert.NoError(t, PrepareTestDatabase())

View File

@ -7,7 +7,7 @@
updated_by: 2
issue_id: 1
created_unix: 946684800
updated_unix: 946684800
updated_unix: 946684820
-
id: 2
@ -17,8 +17,8 @@
source: 1 # issue
updated_by: 1
issue_id: 2
created_unix: 946684800
updated_unix: 946684800
created_unix: 946685800
updated_unix: 946685820
-
id: 3
@ -27,9 +27,9 @@
status: 3 # pinned
source: 1 # issue
updated_by: 1
issue_id: 2
created_unix: 946684800
updated_unix: 946684800
issue_id: 3
created_unix: 946686800
updated_unix: 946686800
-
id: 4
@ -38,6 +38,17 @@
status: 1 # unread
source: 1 # issue
updated_by: 1
issue_id: 2
created_unix: 946684800
updated_unix: 946684800
issue_id: 5
created_unix: 946687800
updated_unix: 946687800
-
id: 5
user_id: 2
repo_id: 2
status: 1 # unread
source: 1 # issue
updated_by: 5
issue_id: 4
created_unix: 946688800
updated_unix: 946688820

View File

@ -843,6 +843,20 @@ func (issue *Issue) GetLastEventLabel() string {
return "repo.issues.opened_by"
}
// GetLastComment return last comment for the current issue.
func (issue *Issue) GetLastComment() (*Comment, error) {
var c Comment
exist, err := x.Where("type = ?", CommentTypeComment).
And("issue_id = ?", issue.ID).Desc("id").Get(&c)
if err != nil {
return nil, err
}
if !exist {
return nil, nil
}
return &c, nil
}
// GetLastEventLabelFake returns the localization label for the current issue without providing a link in the username.
func (issue *Issue) GetLastEventLabelFake() string {
if issue.IsClosed {

View File

@ -8,6 +8,7 @@ package models
import (
"fmt"
"path"
"strings"
"code.gitea.io/gitea/modules/git"
@ -235,6 +236,22 @@ func (c *Comment) HTMLURL() string {
return fmt.Sprintf("%s#%s", c.Issue.HTMLURL(), c.HashTag())
}
// APIURL formats a API-string to the issue-comment
func (c *Comment) APIURL() string {
err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused:
log.Error("LoadIssue(%d): %v", c.IssueID, err)
return ""
}
err = c.Issue.loadRepo(x)
if err != nil { // Silently dropping errors :unamused:
log.Error("loadRepo(%d): %v", c.Issue.RepoID, err)
return ""
}
return c.Issue.Repo.APIURL() + "/" + path.Join("issues/comments", fmt.Sprint(c.ID))
}
// IssueURL formats a URL-string to the issue
func (c *Comment) IssueURL() string {
err := c.LoadIssue()

View File

@ -293,6 +293,8 @@ var migrations = []Migration{
// v118 -> v119
NewMigration("Add commit id and stale to reviews", addReviewCommitAndStale),
// v119 -> v120
NewMigration("Fix migrated repositories' git service type", fixMigratedRepositoryServiceType),
// v120 -> v121
NewMigration("add is_restricted column for users table", addIsRestricted),
}

View File

@ -4,14 +4,13 @@
package migrations
import "xorm.io/xorm"
import (
"code.gitea.io/gitea/modules/structs"
func addIsRestricted(x *xorm.Engine) error {
// User see models/user.go
type User struct {
ID int64 `xorm:"pk autoincr"`
IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
}
"xorm.io/xorm"
)
return x.Sync2(new(User))
func fixMigratedRepositoryServiceType(x *xorm.Engine) error {
_, err := x.Exec("UPDATE repository SET original_service_type = ? WHERE original_url LIKE 'https://github.com/%'", structs.GithubService)
return err
}

17
models/migrations/v120.go Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2020 The Gitea 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 migrations
import "xorm.io/xorm"
func addIsRestricted(x *xorm.Engine) error {
// User see models/user.go
type User struct {
ID int64 `xorm:"pk autoincr"`
IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync2(new(User))
}

View File

@ -6,8 +6,14 @@ package models
import (
"fmt"
"path"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
"xorm.io/xorm"
)
type (
@ -47,17 +53,67 @@ type Notification struct {
IssueID int64 `xorm:"INDEX NOT NULL"`
CommitID string `xorm:"INDEX"`
CommentID int64
Comment *Comment `xorm:"-"`
UpdatedBy int64 `xorm:"INDEX NOT NULL"`
Issue *Issue `xorm:"-"`
Repository *Repository `xorm:"-"`
Comment *Comment `xorm:"-"`
User *User `xorm:"-"`
CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"`
}
// FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored.
type FindNotificationOptions struct {
UserID int64
RepoID int64
IssueID int64
Status NotificationStatus
UpdatedAfterUnix int64
UpdatedBeforeUnix int64
}
// ToCond will convert each condition into a xorm-Cond
func (opts *FindNotificationOptions) ToCond() builder.Cond {
cond := builder.NewCond()
if opts.UserID != 0 {
cond = cond.And(builder.Eq{"notification.user_id": opts.UserID})
}
if opts.RepoID != 0 {
cond = cond.And(builder.Eq{"notification.repo_id": opts.RepoID})
}
if opts.IssueID != 0 {
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
}
if opts.Status != 0 {
cond = cond.And(builder.Eq{"notification.status": opts.Status})
}
if opts.UpdatedAfterUnix != 0 {
cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})
}
if opts.UpdatedBeforeUnix != 0 {
cond = cond.And(builder.Lte{"notification.updated_unix": opts.UpdatedBeforeUnix})
}
return cond
}
// ToSession will convert the given options to a xorm Session by using the conditions from ToCond and joining with issue table if required
func (opts *FindNotificationOptions) ToSession(e Engine) *xorm.Session {
return e.Where(opts.ToCond())
}
func getNotifications(e Engine, options FindNotificationOptions) (nl NotificationList, err error) {
err = options.ToSession(e).OrderBy("notification.updated_unix DESC").Find(&nl)
return
}
// GetNotifications returns all notifications that fit to the given options.
func GetNotifications(opts FindNotificationOptions) (NotificationList, error) {
return getNotifications(x, opts)
}
// CreateOrUpdateIssueNotifications creates an issue notification
// for each watcher, or updates it if already exists
func CreateOrUpdateIssueNotifications(issueID, commentID int64, notificationAuthorID int64) error {
@ -238,22 +294,124 @@ func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, p
return
}
// APIFormat converts a Notification to api.NotificationThread
func (n *Notification) APIFormat() *api.NotificationThread {
result := &api.NotificationThread{
ID: n.ID,
Unread: !(n.Status == NotificationStatusRead || n.Status == NotificationStatusPinned),
Pinned: n.Status == NotificationStatusPinned,
UpdatedAt: n.UpdatedUnix.AsTime(),
URL: n.APIURL(),
}
//since user only get notifications when he has access to use minimal access mode
if n.Repository != nil {
result.Repository = n.Repository.APIFormat(AccessModeRead)
}
//handle Subject
switch n.Source {
case NotificationSourceIssue:
result.Subject = &api.NotificationSubject{Type: "Issue"}
if n.Issue != nil {
result.Subject.Title = n.Issue.Title
result.Subject.URL = n.Issue.APIURL()
comment, err := n.Issue.GetLastComment()
if err == nil && comment != nil {
result.Subject.LatestCommentURL = comment.APIURL()
}
}
case NotificationSourcePullRequest:
result.Subject = &api.NotificationSubject{Type: "Pull"}
if n.Issue != nil {
result.Subject.Title = n.Issue.Title
result.Subject.URL = n.Issue.APIURL()
comment, err := n.Issue.GetLastComment()
if err == nil && comment != nil {
result.Subject.LatestCommentURL = comment.APIURL()
}
}
case NotificationSourceCommit:
result.Subject = &api.NotificationSubject{
Type: "Commit",
Title: n.CommitID,
}
//unused until now
}
return result
}
// LoadAttributes load Repo Issue User and Comment if not loaded
func (n *Notification) LoadAttributes() (err error) {
return n.loadAttributes(x)
}
func (n *Notification) loadAttributes(e Engine) (err error) {
if err = n.loadRepo(e); err != nil {
return
}
if err = n.loadIssue(e); err != nil {
return
}
if err = n.loadUser(e); err != nil {
return
}
if err = n.loadComment(e); err != nil {
return
}
return
}
func (n *Notification) loadRepo(e Engine) (err error) {
if n.Repository == nil {
n.Repository, err = getRepositoryByID(e, n.RepoID)
if err != nil {
return fmt.Errorf("getRepositoryByID [%d]: %v", n.RepoID, err)
}
}
return nil
}
func (n *Notification) loadIssue(e Engine) (err error) {
if n.Issue == nil {
n.Issue, err = getIssueByID(e, n.IssueID)
if err != nil {
return fmt.Errorf("getIssueByID [%d]: %v", n.IssueID, err)
}
return n.Issue.loadAttributes(e)
}
return nil
}
func (n *Notification) loadComment(e Engine) (err error) {
if n.Comment == nil && n.CommentID > 0 {
n.Comment, err = GetCommentByID(n.CommentID)
if err != nil {
return fmt.Errorf("GetCommentByID [%d]: %v", n.CommentID, err)
}
}
return nil
}
func (n *Notification) loadUser(e Engine) (err error) {
if n.User == nil {
n.User, err = getUserByID(e, n.UserID)
if err != nil {
return fmt.Errorf("getUserByID [%d]: %v", n.UserID, err)
}
}
return nil
}
// GetRepo returns the repo of the notification
func (n *Notification) GetRepo() (*Repository, error) {
n.Repository = new(Repository)
_, err := x.
Where("id = ?", n.RepoID).
Get(n.Repository)
return n.Repository, err
return n.Repository, n.loadRepo(x)
}
// GetIssue returns the issue of the notification
func (n *Notification) GetIssue() (*Issue, error) {
n.Issue = new(Issue)
_, err := x.
Where("id = ?", n.IssueID).
Get(n.Issue)
return n.Issue, err
return n.Issue, n.loadIssue(x)
}
// HTMLURL formats a URL-string to the notification
@ -264,9 +422,34 @@ func (n *Notification) HTMLURL() string {
return n.Issue.HTMLURL()
}
// APIURL formats a URL-string to the notification
func (n *Notification) APIURL() string {
return setting.AppURL + path.Join("api/v1/notifications/threads", fmt.Sprintf("%d", n.ID))
}
// NotificationList contains a list of notifications
type NotificationList []*Notification
// APIFormat converts a NotificationList to api.NotificationThread list
func (nl NotificationList) APIFormat() []*api.NotificationThread {
var result = make([]*api.NotificationThread, 0, len(nl))
for _, n := range nl {
result = append(result, n.APIFormat())
}
return result
}
// LoadAttributes load Repo Issue User and Comment if not loaded
func (nl NotificationList) LoadAttributes() (err error) {
for i := 0; i < len(nl); i++ {
err = nl[i].LoadAttributes()
if err != nil {
return
}
}
return
}
func (nl NotificationList) getPendingRepoIDs() []int64 {
var ids = make(map[int64]struct{}, len(nl))
for _, notification := range nl {
@ -486,7 +669,7 @@ func setNotificationStatusReadIfUnread(e Engine, userID, issueID int64) error {
// SetNotificationStatus change the notification status
func SetNotificationStatus(notificationID int64, user *User, status NotificationStatus) error {
notification, err := getNotificationByID(notificationID)
notification, err := getNotificationByID(x, notificationID)
if err != nil {
return err
}
@ -501,9 +684,14 @@ func SetNotificationStatus(notificationID int64, user *User, status Notification
return err
}
func getNotificationByID(notificationID int64) (*Notification, error) {
// GetNotificationByID return notification by ID
func GetNotificationByID(notificationID int64) (*Notification, error) {
return getNotificationByID(x, notificationID)
}
func getNotificationByID(e Engine, notificationID int64) (*Notification, error) {
notification := new(Notification)
ok, err := x.
ok, err := e.
Where("id = ?", notificationID).
Get(notification)
@ -512,7 +700,7 @@ func getNotificationByID(notificationID int64) (*Notification, error) {
}
if !ok {
return nil, fmt.Errorf("Notification %d does not exists", notificationID)
return nil, ErrNotExist{ID: notificationID}
}
return notification, nil

View File

@ -31,11 +31,13 @@ func TestNotificationsForUser(t *testing.T) {
statuses := []NotificationStatus{NotificationStatusRead, NotificationStatusUnread}
notfs, err := NotificationsForUser(user, statuses, 1, 10)
assert.NoError(t, err)
if assert.Len(t, notfs, 2) {
assert.EqualValues(t, 2, notfs[0].ID)
if assert.Len(t, notfs, 3) {
assert.EqualValues(t, 5, notfs[0].ID)
assert.EqualValues(t, user.ID, notfs[0].UserID)
assert.EqualValues(t, 4, notfs[1].ID)
assert.EqualValues(t, user.ID, notfs[1].UserID)
assert.EqualValues(t, 2, notfs[2].ID)
assert.EqualValues(t, user.ID, notfs[2].UserID)
}
}

View File

@ -590,7 +590,8 @@ func UpdateTeam(t *Team, authChanged bool, includeAllChanged bool) (err error) {
return ErrTeamAlreadyExist{t.OrgID, t.LowerName}
}
if _, err = sess.ID(t.ID).AllCols().Update(t); err != nil {
if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description",
"can_create_org_repo", "authorize", "includes_all_repositories").Update(t); err != nil {
return fmt.Errorf("update: %v", err)
}
@ -605,8 +606,7 @@ func UpdateTeam(t *Team, authChanged bool, includeAllChanged bool) (err error) {
Delete(new(TeamUnit)); err != nil {
return err
}
if _, err = sess.Insert(&t.Units); err != nil {
if _, err = sess.Cols("org_id", "team_id", "type").Insert(&t.Units); err != nil {
errRollback := sess.Rollback()
if errRollback != nil {
log.Error("UpdateTeam sess.Rollback: %v", errRollback)

View File

@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
)
@ -35,6 +34,7 @@ const (
PullRequestStatusChecking
PullRequestStatusMergeable
PullRequestStatusManuallyMerged
PullRequestStatusError
)
// PullRequest represents relation between pull request and repositories.
@ -396,139 +396,6 @@ func (pr *PullRequest) GetGitRefName() string {
return fmt.Sprintf("refs/pull/%d/head", pr.Index)
}
// APIFormat assumes following fields have been assigned with valid values:
// Required - Issue
// Optional - Merger
func (pr *PullRequest) APIFormat() *api.PullRequest {
return pr.apiFormat(x)
}
func (pr *PullRequest) apiFormat(e Engine) *api.PullRequest {
var (
baseBranch *git.Branch
headBranch *git.Branch
baseCommit *git.Commit
headCommit *git.Commit
err error
)
if err = pr.Issue.loadRepo(e); err != nil {
log.Error("loadRepo[%d]: %v", pr.ID, err)
return nil
}
apiIssue := pr.Issue.apiFormat(e)
if pr.BaseRepo == nil {
pr.BaseRepo, err = getRepositoryByID(e, pr.BaseRepoID)
if err != nil {
log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
return nil
}
}
if pr.HeadRepo == nil {
pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
if err != nil {
log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
return nil
}
}
if err = pr.Issue.loadRepo(e); err != nil {
log.Error("pr.Issue.loadRepo[%d]: %v", pr.ID, err)
return nil
}
apiPullRequest := &api.PullRequest{
ID: pr.ID,
URL: pr.Issue.HTMLURL(),
Index: pr.Index,
Poster: apiIssue.Poster,
Title: apiIssue.Title,
Body: apiIssue.Body,
Labels: apiIssue.Labels,
Milestone: apiIssue.Milestone,
Assignee: apiIssue.Assignee,
Assignees: apiIssue.Assignees,
State: apiIssue.State,
Comments: apiIssue.Comments,
HTMLURL: pr.Issue.HTMLURL(),
DiffURL: pr.Issue.DiffURL(),
PatchURL: pr.Issue.PatchURL(),
HasMerged: pr.HasMerged,
MergeBase: pr.MergeBase,
Deadline: apiIssue.Deadline,
Created: pr.Issue.CreatedUnix.AsTimePtr(),
Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
}
baseBranch, err = pr.BaseRepo.GetBranch(pr.BaseBranch)
if err != nil {
if git.IsErrBranchNotExist(err) {
apiPullRequest.Base = nil
} else {
log.Error("GetBranch[%s]: %v", pr.BaseBranch, err)
return nil
}
} else {
apiBaseBranchInfo := &api.PRBranchInfo{
Name: pr.BaseBranch,
Ref: pr.BaseBranch,
RepoID: pr.BaseRepoID,
Repository: pr.BaseRepo.innerAPIFormat(e, AccessModeNone, false),
}
baseCommit, err = baseBranch.GetCommit()
if err != nil {
if git.IsErrNotExist(err) {
apiBaseBranchInfo.Sha = ""
} else {
log.Error("GetCommit[%s]: %v", baseBranch.Name, err)
return nil
}
} else {
apiBaseBranchInfo.Sha = baseCommit.ID.String()
}
apiPullRequest.Base = apiBaseBranchInfo
}
headBranch, err = pr.HeadRepo.GetBranch(pr.HeadBranch)
if err != nil {
if git.IsErrBranchNotExist(err) {
apiPullRequest.Head = nil
} else {
log.Error("GetBranch[%s]: %v", pr.HeadBranch, err)
return nil
}
} else {
apiHeadBranchInfo := &api.PRBranchInfo{
Name: pr.HeadBranch,
Ref: pr.HeadBranch,
RepoID: pr.HeadRepoID,
Repository: pr.HeadRepo.innerAPIFormat(e, AccessModeNone, false),
}
headCommit, err = headBranch.GetCommit()
if err != nil {
if git.IsErrNotExist(err) {
apiHeadBranchInfo.Sha = ""
} else {
log.Error("GetCommit[%s]: %v", headBranch.Name, err)
return nil
}
} else {
apiHeadBranchInfo.Sha = headCommit.ID.String()
}
apiPullRequest.Head = apiHeadBranchInfo
}
if pr.Status != PullRequestStatusChecking {
mergeable := pr.Status != PullRequestStatusConflict && !pr.IsWorkInProgress()
apiPullRequest.Mergeable = mergeable
}
if pr.HasMerged {
apiPullRequest.Merged = pr.MergedUnix.AsTimePtr()
apiPullRequest.MergedCommitID = &pr.MergedCommitID
apiPullRequest.MergedBy = pr.Merger.APIFormat()
}
return apiPullRequest
}
func (pr *PullRequest) getHeadRepo(e Engine) (err error) {
pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
if err != nil && !IsErrRepoNotExist(err) {

View File

@ -29,16 +29,6 @@ func TestPullRequest_LoadIssue(t *testing.T) {
assert.Equal(t, int64(2), pr.Issue.ID)
}
func TestPullRequest_APIFormat(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest)
assert.NoError(t, pr.LoadAttributes())
assert.NoError(t, pr.LoadIssue())
apiPullRequest := pr.APIFormat()
assert.NotNil(t, apiPullRequest)
assert.Nil(t, apiPullRequest.Head)
}
func TestPullRequest_GetBaseRepo(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest)
@ -182,7 +172,7 @@ func TestPullRequest_UpdateCols(t *testing.T) {
BaseBranch: "baseBranch",
HeadBranch: "headBranch",
}
pr.UpdateCols("head_branch")
assert.NoError(t, pr.UpdateCols("head_branch"))
pr = AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest)
assert.Equal(t, "master", pr.BaseBranch)

View File

@ -1071,17 +1071,18 @@ func initRepoCommit(tmpPath string, repo *Repository, u *User) (err error) {
// CreateRepoOptions contains the create repository options
type CreateRepoOptions struct {
Name string
Description string
OriginalURL string
Gitignores string
IssueLabels string
License string
Readme string
IsPrivate bool
IsMirror bool
AutoInit bool
Status RepositoryStatus
Name string
Description string
OriginalURL string
GitServiceType structs.GitServiceType
Gitignores string
IssueLabels string
License string
Readme string
IsPrivate bool
IsMirror bool
AutoInit bool
Status RepositoryStatus
}
func getRepoInitFile(tp, name string) ([]byte, error) {
@ -1369,6 +1370,7 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
OriginalURL: opts.OriginalURL,
OriginalServiceType: opts.GitServiceType,
IsPrivate: opts.IsPrivate,
IsFsckEnabled: !opts.IsMirror,
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,

View File

@ -194,12 +194,13 @@ func CreateMigrateTask(doer, u *User, opts base.MigrateOptions) (*Task, error) {
}
repo, err := CreateRepository(doer, u, CreateRepoOptions{
Name: opts.RepoName,
Description: opts.Description,
OriginalURL: opts.OriginalURL,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: RepositoryBeingMigrated,
Name: opts.RepoName,
Description: opts.Description,
OriginalURL: opts.OriginalURL,
GitServiceType: opts.GitServiceType,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: RepositoryBeingMigrated,
})
if err != nil {
task.EndTime = timeutil.TimeStampNow()

View File

@ -5,7 +5,6 @@
package models
import (
"container/list"
"fmt"
"strings"
"time"
@ -27,33 +26,6 @@ const (
EnvIsInternal = "GITEA_INTERNAL_PUSH"
)
// CommitToPushCommit transforms a git.Commit to PushCommit type.
func CommitToPushCommit(commit *git.Commit) *PushCommit {
return &PushCommit{
Sha1: commit.ID.String(),
Message: commit.Message(),
AuthorEmail: commit.Author.Email,
AuthorName: commit.Author.Name,
CommitterEmail: commit.Committer.Email,
CommitterName: commit.Committer.Name,
Timestamp: commit.Author.When,
}
}
// ListToPushCommits transforms a list.List to PushCommits type.
func ListToPushCommits(l *list.List) *PushCommits {
var commits []*PushCommit
var actEmail string
for e := l.Front(); e != nil; e = e.Next() {
commit := e.Value.(*git.Commit)
if actEmail == "" {
actEmail = commit.Committer.Email
}
commits = append(commits, CommitToPushCommit(commit))
}
return &PushCommits{l.Len(), commits, "", make(map[string]string), make(map[string]*User)}
}
// PushUpdateAddDeleteTags updates a number of added and delete tags
func PushUpdateAddDeleteTags(repo *Repository, gitRepo *git.Repository, addTags, delTags []string) error {
sess := x.NewSession()
@ -258,75 +230,25 @@ func pushUpdateAddTags(e Engine, repo *Repository, gitRepo *git.Repository, tags
return nil
}
// PushUpdateAddTag must be called for any push actions to add tag
func PushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string) error {
rel, err := GetRelease(repo.ID, tagName)
// SaveOrUpdateTag must be called for any push actions to add tag
func SaveOrUpdateTag(repo *Repository, newRel *Release) error {
rel, err := GetRelease(repo.ID, newRel.TagName)
if err != nil && !IsErrReleaseNotExist(err) {
return fmt.Errorf("GetRelease: %v", err)
}
tag, err := gitRepo.GetTag(tagName)
if err != nil {
return fmt.Errorf("GetTag: %v", err)
}
commit, err := tag.Commit()
if err != nil {
return fmt.Errorf("Commit: %v", err)
}
sig := tag.Tagger
if sig == nil {
sig = commit.Author
}
if sig == nil {
sig = commit.Committer
}
var author *User
var createdAt = time.Unix(1, 0)
if sig != nil {
author, err = GetUserByEmail(sig.Email)
if err != nil && !IsErrUserNotExist(err) {
return fmt.Errorf("GetUserByEmail: %v", err)
}
createdAt = sig.When
}
commitsCount, err := commit.CommitsCount()
if err != nil {
return fmt.Errorf("CommitsCount: %v", err)
}
if rel == nil {
rel = &Release{
RepoID: repo.ID,
Title: "",
TagName: tagName,
LowerTagName: strings.ToLower(tagName),
Target: "",
Sha1: commit.ID.String(),
NumCommits: commitsCount,
Note: "",
IsDraft: false,
IsPrerelease: false,
IsTag: true,
CreatedUnix: timeutil.TimeStamp(createdAt.Unix()),
}
if author != nil {
rel.PublisherID = author.ID
}
if _, err = x.InsertOne(rel); err != nil {
rel = newRel
if _, err = x.Insert(rel); err != nil {
return fmt.Errorf("InsertOne: %v", err)
}
} else {
rel.Sha1 = commit.ID.String()
rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
rel.NumCommits = commitsCount
rel.Sha1 = newRel.Sha1
rel.CreatedUnix = newRel.CreatedUnix
rel.NumCommits = newRel.NumCommits
rel.IsDraft = false
if rel.IsTag && author != nil {
rel.PublisherID = author.ID
if rel.IsTag && newRel.PublisherID > 0 {
rel.PublisherID = newRel.PublisherID
}
if _, err = x.ID(rel.ID).AllCols().Update(rel); err != nil {
return fmt.Errorf("Update: %v", err)

View File

@ -1,86 +0,0 @@
// Copyright 2016 The Gitea 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 models
import (
"container/list"
"testing"
"time"
"code.gitea.io/gitea/modules/git"
"github.com/stretchr/testify/assert"
)
func TestCommitToPushCommit(t *testing.T) {
now := time.Now()
sig := &git.Signature{
Email: "example@example.com",
Name: "John Doe",
When: now,
}
const hexString = "0123456789abcdef0123456789abcdef01234567"
sha1, err := git.NewIDFromString(hexString)
assert.NoError(t, err)
pushCommit := CommitToPushCommit(&git.Commit{
ID: sha1,
Author: sig,
Committer: sig,
CommitMessage: "Commit Message",
})
assert.Equal(t, hexString, pushCommit.Sha1)
assert.Equal(t, "Commit Message", pushCommit.Message)
assert.Equal(t, "example@example.com", pushCommit.AuthorEmail)
assert.Equal(t, "John Doe", pushCommit.AuthorName)
assert.Equal(t, "example@example.com", pushCommit.CommitterEmail)
assert.Equal(t, "John Doe", pushCommit.CommitterName)
assert.Equal(t, now, pushCommit.Timestamp)
}
func TestListToPushCommits(t *testing.T) {
now := time.Now()
sig := &git.Signature{
Email: "example@example.com",
Name: "John Doe",
When: now,
}
const hexString1 = "0123456789abcdef0123456789abcdef01234567"
hash1, err := git.NewIDFromString(hexString1)
assert.NoError(t, err)
const hexString2 = "fedcba9876543210fedcba9876543210fedcba98"
hash2, err := git.NewIDFromString(hexString2)
assert.NoError(t, err)
l := list.New()
l.PushBack(&git.Commit{
ID: hash1,
Author: sig,
Committer: sig,
CommitMessage: "Message1",
})
l.PushBack(&git.Commit{
ID: hash2,
Author: sig,
Committer: sig,
CommitMessage: "Message2",
})
pushCommits := ListToPushCommits(l)
assert.Equal(t, 2, pushCommits.Len)
if assert.Len(t, pushCommits.Commits, 2) {
assert.Equal(t, "Message1", pushCommits.Commits[0].Message)
assert.Equal(t, hexString1, pushCommits.Commits[0].Sha1)
assert.Equal(t, "example@example.com", pushCommits.Commits[0].AuthorEmail)
assert.Equal(t, now, pushCommits.Commits[0].Timestamp)
assert.Equal(t, "Message2", pushCommits.Commits[1].Message)
assert.Equal(t, hexString2, pushCommits.Commits[1].Sha1)
assert.Equal(t, "example@example.com", pushCommits.Commits[1].AuthorEmail)
assert.Equal(t, now, pushCommits.Commits[1].Timestamp)
}
}
// TODO TestPushUpdate

View File

@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@ -122,7 +123,7 @@ func (ctx *Context) RedirectToFirst(location ...string) {
}
u, err := url.Parse(loc)
if err != nil || (u.Scheme != "" && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
continue
}

View File

@ -0,0 +1,16 @@
// Copyright 2020 The Gitea 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 convert
import (
"path/filepath"
"testing"
"code.gitea.io/gitea/models"
)
func TestMain(m *testing.M) {
models.MainTest(m, filepath.Join("..", ".."))
}

141
modules/convert/pull.go Normal file
View File

@ -0,0 +1,141 @@
// Copyright 2020 The Gitea 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 convert
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
)
// ToAPIPullRequest assumes following fields have been assigned with valid values:
// Required - Issue
// Optional - Merger
func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest {
var (
baseBranch *git.Branch
headBranch *git.Branch
baseCommit *git.Commit
headCommit *git.Commit
err error
)
if err = pr.Issue.LoadRepo(); err != nil {
log.Error("loadRepo[%d]: %v", pr.ID, err)
return nil
}
apiIssue := pr.Issue.APIFormat()
if pr.BaseRepo == nil {
pr.BaseRepo, err = models.GetRepositoryByID(pr.BaseRepoID)
if err != nil {
log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
return nil
}
}
if pr.HeadRepo == nil {
pr.HeadRepo, err = models.GetRepositoryByID(pr.HeadRepoID)
if err != nil {
log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
return nil
}
}
if err = pr.Issue.LoadRepo(); err != nil {
log.Error("pr.Issue.loadRepo[%d]: %v", pr.ID, err)
return nil
}
apiPullRequest := &api.PullRequest{
ID: pr.ID,
URL: pr.Issue.HTMLURL(),
Index: pr.Index,
Poster: apiIssue.Poster,
Title: apiIssue.Title,
Body: apiIssue.Body,
Labels: apiIssue.Labels,
Milestone: apiIssue.Milestone,
Assignee: apiIssue.Assignee,
Assignees: apiIssue.Assignees,
State: apiIssue.State,
Comments: apiIssue.Comments,
HTMLURL: pr.Issue.HTMLURL(),
DiffURL: pr.Issue.DiffURL(),
PatchURL: pr.Issue.PatchURL(),
HasMerged: pr.HasMerged,
MergeBase: pr.MergeBase,
Deadline: apiIssue.Deadline,
Created: pr.Issue.CreatedUnix.AsTimePtr(),
Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
}
baseBranch, err = pr.BaseRepo.GetBranch(pr.BaseBranch)
if err != nil {
if git.IsErrBranchNotExist(err) {
apiPullRequest.Base = nil
} else {
log.Error("GetBranch[%s]: %v", pr.BaseBranch, err)
return nil
}
} else {
apiBaseBranchInfo := &api.PRBranchInfo{
Name: pr.BaseBranch,
Ref: pr.BaseBranch,
RepoID: pr.BaseRepoID,
Repository: pr.BaseRepo.APIFormat(models.AccessModeNone),
}
baseCommit, err = baseBranch.GetCommit()
if err != nil {
if git.IsErrNotExist(err) {
apiBaseBranchInfo.Sha = ""
} else {
log.Error("GetCommit[%s]: %v", baseBranch.Name, err)
return nil
}
} else {
apiBaseBranchInfo.Sha = baseCommit.ID.String()
}
apiPullRequest.Base = apiBaseBranchInfo
}
headBranch, err = pr.HeadRepo.GetBranch(pr.HeadBranch)
if err != nil {
if git.IsErrBranchNotExist(err) {
apiPullRequest.Head = nil
} else {
log.Error("GetBranch[%s]: %v", pr.HeadBranch, err)
return nil
}
} else {
apiHeadBranchInfo := &api.PRBranchInfo{
Name: pr.HeadBranch,
Ref: pr.HeadBranch,
RepoID: pr.HeadRepoID,
Repository: pr.HeadRepo.APIFormat(models.AccessModeNone),
}
headCommit, err = headBranch.GetCommit()
if err != nil {
if git.IsErrNotExist(err) {
apiHeadBranchInfo.Sha = ""
} else {
log.Error("GetCommit[%s]: %v", headBranch.Name, err)
return nil
}
} else {
apiHeadBranchInfo.Sha = headCommit.ID.String()
}
apiPullRequest.Head = apiHeadBranchInfo
}
if pr.Status != models.PullRequestStatusChecking {
mergeable := !(pr.Status == models.PullRequestStatusConflict || pr.Status == models.PullRequestStatusError) && !pr.IsWorkInProgress()
apiPullRequest.Mergeable = mergeable
}
if pr.HasMerged {
apiPullRequest.Merged = pr.MergedUnix.AsTimePtr()
apiPullRequest.MergedCommitID = &pr.MergedCommitID
apiPullRequest.MergedBy = pr.Merger.APIFormat()
}
return apiPullRequest
}

View File

@ -0,0 +1,23 @@
// Copyright 2020 The Gitea 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 convert
import (
"testing"
"code.gitea.io/gitea/models"
"github.com/stretchr/testify/assert"
)
func TestPullRequest_APIFormat(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 1}).(*models.PullRequest)
assert.NoError(t, pr.LoadAttributes())
assert.NoError(t, pr.LoadIssue())
apiPullRequest := ToAPIPullRequest(pr)
assert.NotNil(t, apiPullRequest)
assert.Nil(t, apiPullRequest.Head)
}

View File

@ -101,12 +101,13 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
var r *models.Repository
if opts.MigrateToRepoID <= 0 {
r, err = models.CreateRepository(g.doer, owner, models.CreateRepoOptions{
Name: g.repoName,
Description: repo.Description,
OriginalURL: repo.OriginalURL,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: models.RepositoryBeingMigrated,
Name: g.repoName,
Description: repo.Description,
OriginalURL: repo.OriginalURL,
GitServiceType: opts.GitServiceType,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: models.RepositoryBeingMigrated,
})
} else {
r, err = models.GetRepositoryByID(opts.MigrateToRepoID)

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification/base"
"code.gitea.io/gitea/modules/repository"
)
type actionNotifier struct {
@ -266,7 +267,7 @@ func (*actionNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mode
}
}
func (a *actionNotifier) NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
func (a *actionNotifier) NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) {
data, err := json.Marshal(commits)
if err != nil {
log.Error("json.Marshal: %v", err)

View File

@ -7,6 +7,7 @@ package base
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/repository"
)
// Notifier defines an interface to notify receiver
@ -45,11 +46,11 @@ type Notifier interface {
NotifyUpdateRelease(doer *models.User, rel *models.Release)
NotifyDeleteRelease(doer *models.User, rel *models.Release)
NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits)
NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits)
NotifyCreateRef(doer *models.User, repo *models.Repository, refType, refFullName string)
NotifyDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string)
NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits)
NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits)
NotifySyncCreateRef(doer *models.User, repo *models.Repository, refType, refFullName string)
NotifySyncDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string)
}

View File

@ -7,6 +7,7 @@ package base
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/repository"
)
// NullNotifier implements a blank notifier
@ -116,7 +117,7 @@ func (*NullNotifier) NotifyMigrateRepository(doer *models.User, u *models.User,
}
// NotifyPushCommits notifies commits pushed to notifiers
func (*NullNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
func (*NullNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) {
}
// NotifyCreateRef notifies branch or tag creation to notifiers
@ -136,7 +137,7 @@ func (*NullNotifier) NotifyTransferRepository(doer *models.User, repo *models.Re
}
// NotifySyncPushCommits places a place holder function
func (*NullNotifier) NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
func (*NullNotifier) NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) {
}
// NotifySyncCreateRef places a place holder function

View File

@ -10,6 +10,7 @@ import (
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification/base"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
)
@ -117,7 +118,7 @@ func (r *indexerNotifier) NotifyMigrateRepository(doer *models.User, u *models.U
}
}
func (r *indexerNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
func (r *indexerNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) {
if setting.Indexer.RepoIndexerEnabled && refName == repo.DefaultBranch {
code_indexer.UpdateRepoIndexer(repo)
}

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/notification/mail"
"code.gitea.io/gitea/modules/notification/ui"
"code.gitea.io/gitea/modules/notification/webhook"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
)
@ -215,7 +216,7 @@ func NotifyRenameRepository(doer *models.User, repo *models.Repository, oldName
}
// NotifyPushCommits notifies commits pushed to notifiers
func NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
func NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) {
for _, notifier := range notifiers {
notifier.NotifyPushCommits(pusher, repo, refName, oldCommitID, newCommitID, commits)
}
@ -236,7 +237,7 @@ func NotifyDeleteRef(pusher *models.User, repo *models.Repository, refType, refF
}
// NotifySyncPushCommits notifies commits pushed to notifiers
func NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
func NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) {
for _, notifier := range notifiers {
notifier.NotifySyncPushCommits(pusher, repo, refName, oldCommitID, newCommitID, commits)
}

View File

@ -6,9 +6,11 @@ package webhook
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification/base"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
@ -49,7 +51,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *model
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueLabelCleared,
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
})
@ -135,7 +137,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *mo
issue.PullRequest.Issue = issue
apiPullRequest := &api.PullRequestPayload{
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}
@ -187,7 +189,7 @@ func (m *webhookNotifier) NotifyIssueChangeTitle(doer *models.User, issue *model
From: oldTitle,
},
},
PullRequest: issue.PullRequest.APIFormat(),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
})
@ -222,7 +224,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *mode
// Merge pull request calls issue.changeStatus so we need to handle separately.
apiPullRequest := &api.PullRequestPayload{
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}
@ -291,7 +293,7 @@ func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest) {
if err := webhook_module.PrepareWebhooks(pull.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueOpened,
Index: pull.Issue.Index,
PullRequest: pull.APIFormat(),
PullRequest: convert.ToAPIPullRequest(pull),
Repository: pull.Issue.Repo.APIFormat(mode),
Sender: pull.Issue.Poster.APIFormat(),
}); err != nil {
@ -312,7 +314,7 @@ func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod
From: oldContent,
},
},
PullRequest: issue.PullRequest.APIFormat(),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
})
@ -439,7 +441,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *mode
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueLabelUpdated,
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
Repository: issue.Repo.APIFormat(models.AccessModeNone),
Sender: doer.APIFormat(),
})
@ -481,7 +483,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
Action: hookAction,
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
})
@ -499,7 +501,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m
}
}
func (m *webhookNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
func (m *webhookNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) {
apiPusher := pusher.APIFormat()
apiCommits, err := commits.ToAPIPayloadCommits(repo.RepoPath(), repo.HTMLURL())
if err != nil {
@ -547,7 +549,7 @@ func (*webhookNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mod
// Merge pull request calls issue.changeStatus so we need to handle separately.
apiPullRequest := &api.PullRequestPayload{
Index: pr.Issue.Index,
PullRequest: pr.APIFormat(),
PullRequest: convert.ToAPIPullRequest(pr),
Repository: pr.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
Action: api.HookIssueClosed,
@ -580,7 +582,7 @@ func (m *webhookNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User,
From: oldBranch,
},
},
PullRequest: issue.PullRequest.APIFormat(),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
})
@ -619,7 +621,7 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review
if err := webhook_module.PrepareWebhooks(review.Issue.Repo, reviewHookType, &api.PullRequestPayload{
Action: api.HookIssueSynchronized,
Index: review.Issue.Index,
PullRequest: pr.APIFormat(),
PullRequest: convert.ToAPIPullRequest(pr),
Repository: review.Issue.Repo.APIFormat(mode),
Sender: review.Reviewer.APIFormat(),
Review: &api.ReviewPayload{
@ -674,7 +676,7 @@ func (m *webhookNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *m
if err := webhook_module.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueSynchronized,
Index: pr.Issue.Index,
PullRequest: pr.Issue.PullRequest.APIFormat(),
PullRequest: convert.ToAPIPullRequest(pr),
Repository: pr.Issue.Repo.APIFormat(models.AccessModeNone),
Sender: doer.APIFormat(),
}); err != nil {
@ -727,7 +729,7 @@ func (m *webhookNotifier) NotifyDeleteRelease(doer *models.User, rel *models.Rel
sendReleaseHook(doer, rel, api.HookReleaseDeleted)
}
func (m *webhookNotifier) NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
func (m *webhookNotifier) NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) {
apiPusher := pusher.APIFormat()
apiCommits, err := commits.ToAPIPayloadCommits(repo.RepoPath(), repo.HTMLURL())
if err != nil {

View File

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
)
@ -59,7 +60,7 @@ func changeIssueStatus(repo *models.Repository, issue *models.Issue, doer *model
}
// UpdateIssuesCommit checks if issues are manipulated by commit message.
func UpdateIssuesCommit(doer *models.User, repo *models.Repository, commits []*models.PushCommit, branchName string) error {
func UpdateIssuesCommit(doer *models.User, repo *models.Repository, commits []*repository.PushCommit, branchName string) error {
// Commits are appended in the reverse order.
for i := len(commits) - 1; i >= 0; i-- {
c := commits[i]
@ -154,7 +155,7 @@ type CommitRepoActionOptions struct {
RefFullName string
OldCommitID string
NewCommitID string
Commits *models.PushCommits
Commits *repository.PushCommits
}
// CommitRepoAction adds new commit action to the repository, and prepare
@ -216,10 +217,10 @@ func CommitRepoAction(optsList ...*CommitRepoActionOptions) error {
if opts.NewCommitID == git.EmptySHA {
opType = models.ActionDeleteTag
}
opts.Commits = &models.PushCommits{}
opts.Commits = &repository.PushCommits{}
} else if opts.NewCommitID == git.EmptySHA {
opType = models.ActionDeleteBranch
opts.Commits = &models.PushCommits{}
opts.Commits = &repository.PushCommits{}
} else {
// if not the first commit, set the compare URL.
if opts.OldCommitID == git.EmptySHA {

View File

@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/repository"
"github.com/stretchr/testify/assert"
)
@ -34,8 +35,8 @@ func TestCommitRepoAction(t *testing.T) {
RefFullName: "refName",
OldCommitID: "oldCommitID",
NewCommitID: "newCommitID",
Commits: &models.PushCommits{
Commits: []*models.PushCommit{
Commits: &repository.PushCommits{
Commits: []*repository.PushCommit{
{
Sha1: "69554a6",
CommitterEmail: "user2@example.com",
@ -68,7 +69,7 @@ func TestCommitRepoAction(t *testing.T) {
RefFullName: git.TagPrefix + "v1.1",
OldCommitID: git.EmptySHA,
NewCommitID: "newCommitID",
Commits: &models.PushCommits{},
Commits: &repository.PushCommits{},
},
action: models.Action{
OpType: models.ActionPushTag,
@ -82,7 +83,7 @@ func TestCommitRepoAction(t *testing.T) {
RefFullName: git.TagPrefix + "v1.1",
OldCommitID: "oldCommitID",
NewCommitID: git.EmptySHA,
Commits: &models.PushCommits{},
Commits: &repository.PushCommits{},
},
action: models.Action{
OpType: models.ActionDeleteTag,
@ -96,7 +97,7 @@ func TestCommitRepoAction(t *testing.T) {
RefFullName: git.BranchPrefix + "feature/1",
OldCommitID: "oldCommitID",
NewCommitID: git.EmptySHA,
Commits: &models.PushCommits{},
Commits: &repository.PushCommits{},
},
action: models.Action{
OpType: models.ActionDeleteBranch,
@ -127,7 +128,7 @@ func TestCommitRepoAction(t *testing.T) {
func TestUpdateIssuesCommit(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
pushCommits := []*models.PushCommit{
pushCommits := []*repository.PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
@ -174,7 +175,7 @@ func TestUpdateIssuesCommit(t *testing.T) {
models.CheckConsistencyFor(t, &models.Action{})
// Test that push to a non-default branch closes no issue.
pushCommits = []*models.PushCommit{
pushCommits = []*repository.PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
@ -203,7 +204,7 @@ func TestUpdateIssuesCommit(t *testing.T) {
func TestUpdateIssuesCommit_Colon(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
pushCommits := []*models.PushCommit{
pushCommits := []*repository.PushCommit{
{
Sha1: "abcdef2",
CommitterEmail: "user2@example.com",
@ -231,7 +232,7 @@ func TestUpdateIssuesCommit_Issue5957(t *testing.T) {
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
// Test that push to a non-default branch closes an issue.
pushCommits := []*models.PushCommit{
pushCommits := []*repository.PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
@ -266,7 +267,7 @@ func TestUpdateIssuesCommit_AnotherRepo(t *testing.T) {
// Test that a push to default branch closes issue in another repo
// If the user also has push permissions to that repo
pushCommits := []*models.PushCommit{
pushCommits := []*repository.PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
@ -301,7 +302,7 @@ func TestUpdateIssuesCommit_AnotherRepoNoPermission(t *testing.T) {
// Test that a push with close reference *can not* close issue
// If the commiter doesn't have push rights in that repo
pushCommits := []*models.PushCommit{
pushCommits := []*repository.PushCommit{
{
Sha1: "abcdef3",
CommitterEmail: "user10@example.com",

View File

@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
pull_service "code.gitea.io/gitea/services/pull"
@ -549,7 +550,7 @@ func createCommitRepoActions(repo *models.Repository, gitRepo *git.Repository, o
if isNewRef && isDelRef {
return nil, fmt.Errorf("Old and new revisions are both %s", git.EmptySHA)
}
var commits = &models.PushCommits{}
var commits = &repository.PushCommits{}
if strings.HasPrefix(opts.RefFullName, git.TagPrefix) {
// If is tag reference
tagName := opts.RefFullName[len(git.TagPrefix):]
@ -584,7 +585,7 @@ func createCommitRepoActions(repo *models.Repository, gitRepo *git.Repository, o
}
}
commits = models.ListToPushCommits(l)
commits = repository.ListToPushCommits(l)
}
actions = append(actions, &CommitRepoActionOptions{
PusherName: opts.PusherName,
@ -609,7 +610,7 @@ func createCommitRepoActionOption(repo *models.Repository, gitRepo *git.Reposito
return nil, fmt.Errorf("Old and new revisions are both %s", git.EmptySHA)
}
var commits = &models.PushCommits{}
var commits = &repository.PushCommits{}
if strings.HasPrefix(opts.RefFullName, git.TagPrefix) {
// If is tag reference
tagName := opts.RefFullName[len(git.TagPrefix):]
@ -620,7 +621,7 @@ func createCommitRepoActionOption(repo *models.Repository, gitRepo *git.Reposito
} else {
// Clear cache for tag commit count
cache.Remove(repo.GetCommitsCountCacheKey(tagName, true))
if err := models.PushUpdateAddTag(repo, gitRepo, tagName); err != nil {
if err := repository.PushUpdateAddTag(repo, gitRepo, tagName); err != nil {
return nil, fmt.Errorf("PushUpdateAddTag: %v", err)
}
}
@ -649,7 +650,7 @@ func createCommitRepoActionOption(repo *models.Repository, gitRepo *git.Reposito
}
}
commits = models.ListToPushCommits(l)
commits = repository.ListToPushCommits(l)
}
return &CommitRepoActionOptions{

View File

@ -0,0 +1,168 @@
// Copyright 2019 The Gitea 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 repository
import (
"container/list"
"fmt"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
)
// PushCommit represents a commit in a push operation.
type PushCommit struct {
Sha1 string
Message string
AuthorEmail string
AuthorName string
CommitterEmail string
CommitterName string
Timestamp time.Time
}
// PushCommits represents list of commits in a push operation.
type PushCommits struct {
Len int
Commits []*PushCommit
CompareURL string
avatars map[string]string
emailUsers map[string]*models.User
}
// NewPushCommits creates a new PushCommits object.
func NewPushCommits() *PushCommits {
return &PushCommits{
avatars: make(map[string]string),
emailUsers: make(map[string]*models.User),
}
}
// ToAPIPayloadCommits converts a PushCommits object to
// api.PayloadCommit format.
func (pc *PushCommits) ToAPIPayloadCommits(repoPath, repoLink string) ([]*api.PayloadCommit, error) {
commits := make([]*api.PayloadCommit, len(pc.Commits))
if pc.emailUsers == nil {
pc.emailUsers = make(map[string]*models.User)
}
var err error
for i, commit := range pc.Commits {
authorUsername := ""
author, ok := pc.emailUsers[commit.AuthorEmail]
if !ok {
author, err = models.GetUserByEmail(commit.AuthorEmail)
if err == nil {
authorUsername = author.Name
pc.emailUsers[commit.AuthorEmail] = author
}
} else {
authorUsername = author.Name
}
committerUsername := ""
committer, ok := pc.emailUsers[commit.CommitterEmail]
if !ok {
committer, err = models.GetUserByEmail(commit.CommitterEmail)
if err == nil {
// TODO: check errors other than email not found.
committerUsername = committer.Name
pc.emailUsers[commit.CommitterEmail] = committer
}
} else {
committerUsername = committer.Name
}
fileStatus, err := git.GetCommitFileStatus(repoPath, commit.Sha1)
if err != nil {
return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %v", commit.Sha1, err)
}
commits[i] = &api.PayloadCommit{
ID: commit.Sha1,
Message: commit.Message,
URL: fmt.Sprintf("%s/commit/%s", repoLink, commit.Sha1),
Author: &api.PayloadUser{
Name: commit.AuthorName,
Email: commit.AuthorEmail,
UserName: authorUsername,
},
Committer: &api.PayloadUser{
Name: commit.CommitterName,
Email: commit.CommitterEmail,
UserName: committerUsername,
},
Added: fileStatus.Added,
Removed: fileStatus.Removed,
Modified: fileStatus.Modified,
Timestamp: commit.Timestamp,
}
}
return commits, nil
}
// AvatarLink tries to match user in database with e-mail
// in order to show custom avatar, and falls back to general avatar link.
func (pc *PushCommits) AvatarLink(email string) string {
if pc.avatars == nil {
pc.avatars = make(map[string]string)
}
avatar, ok := pc.avatars[email]
if ok {
return avatar
}
u, ok := pc.emailUsers[email]
if !ok {
var err error
u, err = models.GetUserByEmail(email)
if err != nil {
pc.avatars[email] = base.AvatarLink(email)
if !models.IsErrUserNotExist(err) {
log.Error("GetUserByEmail: %v", err)
return ""
}
} else {
pc.emailUsers[email] = u
}
}
if u != nil {
pc.avatars[email] = u.RelAvatarLink()
}
return pc.avatars[email]
}
// CommitToPushCommit transforms a git.Commit to PushCommit type.
func CommitToPushCommit(commit *git.Commit) *PushCommit {
return &PushCommit{
Sha1: commit.ID.String(),
Message: commit.Message(),
AuthorEmail: commit.Author.Email,
AuthorName: commit.Author.Name,
CommitterEmail: commit.Committer.Email,
CommitterName: commit.Committer.Name,
Timestamp: commit.Author.When,
}
}
// ListToPushCommits transforms a list.List to PushCommits type.
func ListToPushCommits(l *list.List) *PushCommits {
var commits []*PushCommit
var actEmail string
for e := l.Front(); e != nil; e = e.Next() {
commit := e.Value.(*git.Commit)
if actEmail == "" {
actEmail = commit.Committer.Email
}
commits = append(commits, CommitToPushCommit(commit))
}
return &PushCommits{l.Len(), commits, "", make(map[string]string), make(map[string]*models.User)}
}

View File

@ -0,0 +1,190 @@
// Copyright 2019 The Gitea 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 repository
import (
"container/list"
"testing"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"github.com/stretchr/testify/assert"
)
func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
pushCommits := NewPushCommits()
pushCommits.Commits = []*PushCommit{
{
Sha1: "69554a6",
CommitterEmail: "user2@example.com",
CommitterName: "User2",
AuthorEmail: "user2@example.com",
AuthorName: "User2",
Message: "not signed commit",
},
{
Sha1: "27566bd",
CommitterEmail: "user2@example.com",
CommitterName: "User2",
AuthorEmail: "user2@example.com",
AuthorName: "User2",
Message: "good signed commit (with not yet validated email)",
},
{
Sha1: "5099b81",
CommitterEmail: "user2@example.com",
CommitterName: "User2",
AuthorEmail: "user2@example.com",
AuthorName: "User2",
Message: "good signed commit",
},
}
pushCommits.Len = len(pushCommits.Commits)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository)
payloadCommits, err := pushCommits.ToAPIPayloadCommits(repo.RepoPath(), "/user2/repo16")
assert.NoError(t, err)
assert.EqualValues(t, 3, len(payloadCommits))
assert.Equal(t, "69554a6", payloadCommits[0].ID)
assert.Equal(t, "not signed commit", payloadCommits[0].Message)
assert.Equal(t, "/user2/repo16/commit/69554a6", payloadCommits[0].URL)
assert.Equal(t, "User2", payloadCommits[0].Committer.Name)
assert.Equal(t, "user2", payloadCommits[0].Committer.UserName)
assert.Equal(t, "User2", payloadCommits[0].Author.Name)
assert.Equal(t, "user2", payloadCommits[0].Author.UserName)
assert.EqualValues(t, []string{}, payloadCommits[0].Added)
assert.EqualValues(t, []string{}, payloadCommits[0].Removed)
assert.EqualValues(t, []string{"readme.md"}, payloadCommits[0].Modified)
assert.Equal(t, "27566bd", payloadCommits[1].ID)
assert.Equal(t, "good signed commit (with not yet validated email)", payloadCommits[1].Message)
assert.Equal(t, "/user2/repo16/commit/27566bd", payloadCommits[1].URL)
assert.Equal(t, "User2", payloadCommits[1].Committer.Name)
assert.Equal(t, "user2", payloadCommits[1].Committer.UserName)
assert.Equal(t, "User2", payloadCommits[1].Author.Name)
assert.Equal(t, "user2", payloadCommits[1].Author.UserName)
assert.EqualValues(t, []string{}, payloadCommits[1].Added)
assert.EqualValues(t, []string{}, payloadCommits[1].Removed)
assert.EqualValues(t, []string{"readme.md"}, payloadCommits[1].Modified)
assert.Equal(t, "5099b81", payloadCommits[2].ID)
assert.Equal(t, "good signed commit", payloadCommits[2].Message)
assert.Equal(t, "/user2/repo16/commit/5099b81", payloadCommits[2].URL)
assert.Equal(t, "User2", payloadCommits[2].Committer.Name)
assert.Equal(t, "user2", payloadCommits[2].Committer.UserName)
assert.Equal(t, "User2", payloadCommits[2].Author.Name)
assert.Equal(t, "user2", payloadCommits[2].Author.UserName)
assert.EqualValues(t, []string{"readme.md"}, payloadCommits[2].Added)
assert.EqualValues(t, []string{}, payloadCommits[2].Removed)
assert.EqualValues(t, []string{}, payloadCommits[2].Modified)
}
func TestPushCommits_AvatarLink(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
pushCommits := NewPushCommits()
pushCommits.Commits = []*PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user4@example.com",
AuthorName: "User Four",
Message: "message1",
},
{
Sha1: "abcdef2",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user2@example.com",
AuthorName: "User Two",
Message: "message2",
},
}
pushCommits.Len = len(pushCommits.Commits)
assert.Equal(t,
"/user/avatar/user2/-1",
pushCommits.AvatarLink("user2@example.com"))
assert.Equal(t,
"https://secure.gravatar.com/avatar/19ade630b94e1e0535b3df7387434154?d=identicon",
pushCommits.AvatarLink("nonexistent@example.com"))
}
func TestCommitToPushCommit(t *testing.T) {
now := time.Now()
sig := &git.Signature{
Email: "example@example.com",
Name: "John Doe",
When: now,
}
const hexString = "0123456789abcdef0123456789abcdef01234567"
sha1, err := git.NewIDFromString(hexString)
assert.NoError(t, err)
pushCommit := CommitToPushCommit(&git.Commit{
ID: sha1,
Author: sig,
Committer: sig,
CommitMessage: "Commit Message",
})
assert.Equal(t, hexString, pushCommit.Sha1)
assert.Equal(t, "Commit Message", pushCommit.Message)
assert.Equal(t, "example@example.com", pushCommit.AuthorEmail)
assert.Equal(t, "John Doe", pushCommit.AuthorName)
assert.Equal(t, "example@example.com", pushCommit.CommitterEmail)
assert.Equal(t, "John Doe", pushCommit.CommitterName)
assert.Equal(t, now, pushCommit.Timestamp)
}
func TestListToPushCommits(t *testing.T) {
now := time.Now()
sig := &git.Signature{
Email: "example@example.com",
Name: "John Doe",
When: now,
}
const hexString1 = "0123456789abcdef0123456789abcdef01234567"
hash1, err := git.NewIDFromString(hexString1)
assert.NoError(t, err)
const hexString2 = "fedcba9876543210fedcba9876543210fedcba98"
hash2, err := git.NewIDFromString(hexString2)
assert.NoError(t, err)
l := list.New()
l.PushBack(&git.Commit{
ID: hash1,
Author: sig,
Committer: sig,
CommitMessage: "Message1",
})
l.PushBack(&git.Commit{
ID: hash2,
Author: sig,
Committer: sig,
CommitMessage: "Message2",
})
pushCommits := ListToPushCommits(l)
assert.Equal(t, 2, pushCommits.Len)
if assert.Len(t, pushCommits.Commits, 2) {
assert.Equal(t, "Message1", pushCommits.Commits[0].Message)
assert.Equal(t, hexString1, pushCommits.Commits[0].Sha1)
assert.Equal(t, "example@example.com", pushCommits.Commits[0].AuthorEmail)
assert.Equal(t, now, pushCommits.Commits[0].Timestamp)
assert.Equal(t, "Message2", pushCommits.Commits[1].Message)
assert.Equal(t, hexString2, pushCommits.Commits[1].Sha1)
assert.Equal(t, "example@example.com", pushCommits.Commits[1].AuthorEmail)
assert.Equal(t, now, pushCommits.Commits[1].Timestamp)
}
}
// TODO TestPushUpdate

View File

@ -0,0 +1,16 @@
// Copyright 2019 The Gitea 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 repository
import (
"path/filepath"
"testing"
"code.gitea.io/gitea/models"
)
func TestMain(m *testing.M) {
models.MainTest(m, filepath.Join("..", ".."))
}

View File

@ -214,10 +214,61 @@ func SyncReleasesWithTags(repo *models.Repository, gitRepo *git.Repository) erro
}
for _, tagName := range tags {
if _, ok := existingRelTags[strings.ToLower(tagName)]; !ok {
if err := models.PushUpdateAddTag(repo, gitRepo, tagName); err != nil {
return fmt.Errorf("pushUpdateAddTag: %s: %v", tagName, err)
if err := PushUpdateAddTag(repo, gitRepo, tagName); err != nil {
return fmt.Errorf("pushUpdateAddTag: %v", err)
}
}
}
return nil
}
// PushUpdateAddTag must be called for any push actions to add tag
func PushUpdateAddTag(repo *models.Repository, gitRepo *git.Repository, tagName string) error {
tag, err := gitRepo.GetTag(tagName)
if err != nil {
return fmt.Errorf("GetTag: %v", err)
}
commit, err := tag.Commit()
if err != nil {
return fmt.Errorf("Commit: %v", err)
}
sig := tag.Tagger
if sig == nil {
sig = commit.Author
}
if sig == nil {
sig = commit.Committer
}
var author *models.User
var createdAt = time.Unix(1, 0)
if sig != nil {
author, err = models.GetUserByEmail(sig.Email)
if err != nil && !models.IsErrUserNotExist(err) {
return fmt.Errorf("GetUserByEmail: %v", err)
}
createdAt = sig.When
}
commitsCount, err := commit.CommitsCount()
if err != nil {
return fmt.Errorf("CommitsCount: %v", err)
}
var rel = models.Release{
RepoID: repo.ID,
TagName: tagName,
LowerTagName: strings.ToLower(tagName),
Sha1: commit.ID.String(),
NumCommits: commitsCount,
CreatedUnix: timeutil.TimeStamp(createdAt.Unix()),
IsTag: true,
}
if author != nil {
rel.PublisherID = author.ID
}
return models.SaveOrUpdateTag(repo, &rel)
}

View File

@ -0,0 +1,28 @@
// Copyright 2019 The Gitea 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 structs
import (
"time"
)
// NotificationThread expose Notification on API
type NotificationThread struct {
ID int64 `json:"id"`
Repository *Repository `json:"repository"`
Subject *NotificationSubject `json:"subject"`
Unread bool `json:"unread"`
Pinned bool `json:"pinned"`
UpdatedAt time.Time `json:"updated_at"`
URL string `json:"url"`
}
// NotificationSubject contains the notification subject (Issue/Pull/Commit)
type NotificationSubject struct {
Title string `json:"title"`
URL string `json:"url"`
LatestCommentURL string `json:"latest_comment_url"`
Type string `json:"type" binding:"In(Issue,Pull,Commit)"`
}

View File

@ -35,12 +35,12 @@ type CreateTeamOption struct {
// EditTeamOption options for editing a team
type EditTeamOption struct {
// required: true
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
Description string `json:"description" binding:"MaxSize(255)"`
IncludesAllRepositories bool `json:"includes_all_repositories"`
Name string `json:"name" binding:"AlphaDashDot;MaxSize(30)"`
Description *string `json:"description" binding:"MaxSize(255)"`
IncludesAllRepositories *bool `json:"includes_all_repositories"`
// enum: read,write,admin
Permission string `json:"permission"`
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
Units []string `json:"units"`
CanCreateOrgRepo bool `json:"can_create_org_repo"`
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
}

View File

@ -27,6 +27,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@ -579,8 +580,8 @@ func ActionIcon(opType models.ActionType) string {
}
// ActionContent2Commits converts action content to push commits
func ActionContent2Commits(act Actioner) *models.PushCommits {
push := models.NewPushCommits()
func ActionContent2Commits(act Actioner) *repository.PushCommits {
push := repository.NewPushCommits()
if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil {
log.Error("json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err)
}

View File

@ -2025,6 +2025,20 @@ monitor.execute_time=Ausführungszeit
monitor.process.cancel=Prozess abbrechen
monitor.process.cancel_desc=Abbrechen eines Prozesses kann Datenverlust verursachen
monitor.process.cancel_notices=Abbrechen: <strong>%s</strong>?
monitor.queues=Warteschlangen
monitor.queue=Warteschlange: %s
monitor.queue.name=Name
monitor.queue.type=Typ
monitor.queue.numberworkers=Anzahl der Worker
monitor.queue.maxnumberworkers=Maximale Anzahl der Worker
monitor.queue.review=Konfiguration überprüfen
monitor.queue.review_add=Worker hinzufügen/prüfen
monitor.queue.configuration=Erstkonfiguration
monitor.queue.nopool.title=Kein Worker-Pool
monitor.queue.nopool.desc=Diese Warteschlange umgibt andere Warteschlangen und hat selbst keinen Worker-Pool.
monitor.queue.pool.timeout=Timeout
monitor.queue.pool.addworkers.title=Worker hinzufügen
monitor.queue.pool.addworkers.submit=Worker hinzufügen

View File

@ -1412,6 +1412,8 @@ settings.protect_approvals_whitelist_enabled=Restringir aprovações a usuários
settings.protect_approvals_whitelist_enabled_desc=Somente as avaliações de usuários ou equipes da lista permitida serão contadas com as aprovações necessárias. Sem aprovação da lista permitida, as revisões de qualquer pessoa com acesso de escrita contam para as aprovações necessárias.
settings.protect_approvals_whitelist_users=Usuários com permissão de revisão:
settings.protect_approvals_whitelist_teams=Equipes com permissão de revisão:
settings.dismiss_stale_approvals=Descartar aprovações obsoletas
settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas.
settings.add_protected_branch=Habilitar proteção
settings.delete_protected_branch=Desabilitar proteção
settings.update_protect_branch_success=Proteção do branch '%s' foi atualizada.

3266
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,26 +5,26 @@
"node": ">=10"
},
"devDependencies": {
"@babel/core": "7.7.4",
"@babel/plugin-transform-runtime": "7.7.4",
"@babel/preset-env": "7.7.4",
"@babel/runtime": "7.7.4",
"@babel/core": "7.7.7",
"@babel/plugin-transform-runtime": "7.7.6",
"@babel/preset-env": "7.7.7",
"@babel/runtime": "7.7.7",
"autoprefixer": "9.7.3",
"babel-loader": "8.0.6",
"core-js": "3.4.7",
"css-loader": "3.2.1",
"core-js": "3.6.2",
"css-loader": "3.4.1",
"cssnano": "4.1.10",
"eslint": "6.7.2",
"eslint": "6.8.0",
"eslint-config-airbnb-base": "14.0.0",
"eslint-plugin-import": "2.18.2",
"eslint-plugin-import": "2.19.1",
"less": "3.10.3",
"postcss-cli": "6.1.3",
"style-loader": "1.0.1",
"stylelint": "12.0.0",
"postcss-cli": "7.1.0",
"style-loader": "1.1.2",
"stylelint": "12.0.1",
"stylelint-config-standard": "19.0.0",
"terser-webpack-plugin": "2.2.1",
"updates": "9.3.0",
"webpack": "4.41.2",
"terser-webpack-plugin": "2.3.2",
"updates": "9.3.3",
"webpack": "4.41.5",
"webpack-cli": "3.3.10"
},
"browserslist": [

View File

@ -8,7 +8,6 @@
* http://opensource.org/licenses/MIT
*
*/
@import url('https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic&subset=latin&display=swap');
/*!
* # Fomantic-UI - Reset
* http://github.com/fomantic/Fomantic-UI/

View File

@ -7,8 +7,7 @@
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic&subset=latin&display=swap);/*!
*//*!
* # Fomantic-UI - Reset
* http://github.com/fomantic/Fomantic-UI/
*

View File

@ -56,10 +56,10 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
// responses:
// "201":
// "$ref": "#/responses/User"
// "403":
// "$ref": "#/responses/forbidden"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"

View File

@ -70,6 +70,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/admin"
"code.gitea.io/gitea/routers/api/v1/misc"
"code.gitea.io/gitea/routers/api/v1/notify"
"code.gitea.io/gitea/routers/api/v1/org"
"code.gitea.io/gitea/routers/api/v1/repo"
_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
@ -512,6 +513,16 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
m.Post("/markdown/raw", misc.MarkdownRaw)
// Notifications
m.Group("/notifications", func() {
m.Combo("").
Get(notify.ListNotifications).
Put(notify.ReadNotifications)
m.Combo("/threads/:id").
Get(notify.GetThread).
Patch(notify.ReadThread)
}, reqToken())
// Users
m.Group("/users", func() {
m.Get("/search", user.Search)
@ -593,23 +604,24 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqToken())
// Repositories
m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
m.Group("/repos", func() {
m.Get("/search", repo.Search)
})
m.Get("/repos/issues/search", repo.SearchIssues)
m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepoDeprecated)
m.Combo("/repositories/:id", reqToken()).Get(repo.GetByID)
m.Group("/repos", func() {
m.Get("/search", repo.Search)
m.Get("/issues/search", repo.SearchIssues)
m.Post("/migrate", reqToken(), bind(auth.MigrateRepoForm{}), repo.Migrate)
m.Group("/:username/:reponame", func() {
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
Delete(reqToken(), reqOwner(), repo.Delete).
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), context.RepoRef(), repo.Edit)
m.Combo("/notifications").
Get(reqToken(), notify.ListRepoNotifications).
Put(reqToken(), notify.ReadRepoNotifications)
m.Group("/hooks", func() {
m.Combo("").Get(repo.ListHooks).
Post(bind(api.CreateHookOption{}), repo.CreateHook)
@ -810,10 +822,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/users/:username/orgs", org.ListUserOrgs)
m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create)
m.Group("/orgs/:orgname", func() {
m.Get("/repos", user.ListOrgRepos)
m.Combo("").Get(org.Get).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
Delete(reqToken(), reqOrgOwnership(), org.Delete)
m.Combo("/repos").Get(user.ListOrgRepos).
Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
m.Group("/members", func() {
m.Get("", org.ListMembers)
m.Combo("/:username").Get(org.IsMember).

View File

@ -0,0 +1,151 @@
// Copyright 2020 The Gitea 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 notify
import (
"net/http"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/utils"
)
// ListRepoNotifications list users's notification threads on a specific repo
func ListRepoNotifications(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList
// ---
// summary: List users's notification threads on a specific repo
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: all
// in: query
// description: If true, show notifications marked as read. Default value is false
// type: string
// required: false
// - name: since
// in: query
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
// type: string
// format: date-time
// required: false
// - name: before
// in: query
// description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format
// type: string
// format: date-time
// required: false
// responses:
// "200":
// "$ref": "#/responses/NotificationThreadList"
before, since, err := utils.GetQueryBeforeSince(ctx)
if err != nil {
ctx.InternalServerError(err)
return
}
opts := models.FindNotificationOptions{
UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID,
UpdatedBeforeUnix: before,
UpdatedAfterUnix: since,
}
qAll := strings.Trim(ctx.Query("all"), " ")
if qAll != "true" {
opts.Status = models.NotificationStatusUnread
}
nl, err := models.GetNotifications(opts)
if err != nil {
ctx.InternalServerError(err)
return
}
err = nl.LoadAttributes()
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.JSON(http.StatusOK, nl.APIFormat())
}
// ReadRepoNotifications mark notification threads as read on a specific repo
func ReadRepoNotifications(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/notifications notification notifyReadRepoList
// ---
// summary: Mark notification threads as read on a specific repo
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: last_read_at
// in: query
// description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
// type: string
// format: date-time
// required: false
// responses:
// "205":
// "$ref": "#/responses/empty"
lastRead := int64(0)
qLastRead := strings.Trim(ctx.Query("last_read_at"), " ")
if len(qLastRead) > 0 {
tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
if err != nil {
ctx.InternalServerError(err)
return
}
if !tmpLastRead.IsZero() {
lastRead = tmpLastRead.Unix()
}
}
opts := models.FindNotificationOptions{
UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID,
UpdatedBeforeUnix: lastRead,
Status: models.NotificationStatusUnread,
}
nl, err := models.GetNotifications(opts)
if err != nil {
ctx.InternalServerError(err)
return
}
for _, n := range nl {
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.Status(http.StatusResetContent)
}
ctx.Status(http.StatusResetContent)
}

View File

@ -0,0 +1,101 @@
// Copyright 2020 The Gitea 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 notify
import (
"fmt"
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
)
// GetThread get notification by ID
func GetThread(ctx *context.APIContext) {
// swagger:operation GET /notifications/threads/{id} notification notifyGetThread
// ---
// summary: Get notification thread by ID
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: id
// in: path
// description: id of notification thread
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/NotificationThread"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
n := getThread(ctx)
if n == nil {
return
}
if err := n.LoadAttributes(); err != nil {
ctx.InternalServerError(err)
return
}
ctx.JSON(http.StatusOK, n.APIFormat())
}
// ReadThread mark notification as read by ID
func ReadThread(ctx *context.APIContext) {
// swagger:operation PATCH /notifications/threads/{id} notification notifyReadThread
// ---
// summary: Mark notification thread as read by ID
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: id
// in: path
// description: id of notification thread
// type: string
// required: true
// responses:
// "205":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
n := getThread(ctx)
if n == nil {
return
}
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.Status(http.StatusResetContent)
}
func getThread(ctx *context.APIContext) *models.Notification {
n, err := models.GetNotificationByID(ctx.ParamsInt64(":id"))
if err != nil {
if models.IsErrNotExist(err) {
ctx.Error(http.StatusNotFound, "GetNotificationByID", err)
} else {
ctx.InternalServerError(err)
}
return nil
}
if n.UserID != ctx.User.ID && !ctx.User.IsAdmin {
ctx.Error(http.StatusForbidden, "GetNotificationByID", fmt.Errorf("only user itself and admin are allowed to read/change this thread %d", n.ID))
return nil
}
return n
}

View File

@ -0,0 +1,129 @@
// Copyright 2020 The Gitea 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 notify
import (
"net/http"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/utils"
)
// ListNotifications list users's notification threads
func ListNotifications(ctx *context.APIContext) {
// swagger:operation GET /notifications notification notifyGetList
// ---
// summary: List users's notification threads
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: all
// in: query
// description: If true, show notifications marked as read. Default value is false
// type: string
// required: false
// - name: since
// in: query
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
// type: string
// format: date-time
// required: false
// - name: before
// in: query
// description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format
// type: string
// format: date-time
// required: false
// responses:
// "200":
// "$ref": "#/responses/NotificationThreadList"
before, since, err := utils.GetQueryBeforeSince(ctx)
if err != nil {
ctx.InternalServerError(err)
return
}
opts := models.FindNotificationOptions{
UserID: ctx.User.ID,
UpdatedBeforeUnix: before,
UpdatedAfterUnix: since,
}
qAll := strings.Trim(ctx.Query("all"), " ")
if qAll != "true" {
opts.Status = models.NotificationStatusUnread
}
nl, err := models.GetNotifications(opts)
if err != nil {
ctx.InternalServerError(err)
return
}
err = nl.LoadAttributes()
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.JSON(http.StatusOK, nl.APIFormat())
}
// ReadNotifications mark notification threads as read
func ReadNotifications(ctx *context.APIContext) {
// swagger:operation PUT /notifications notification notifyReadList
// ---
// summary: Mark notification threads as read
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: last_read_at
// in: query
// description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
// type: string
// format: date-time
// required: false
// responses:
// "205":
// "$ref": "#/responses/empty"
lastRead := int64(0)
qLastRead := strings.Trim(ctx.Query("last_read_at"), " ")
if len(qLastRead) > 0 {
tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
if err != nil {
ctx.InternalServerError(err)
return
}
if !tmpLastRead.IsZero() {
lastRead = tmpLastRead.Unix()
}
}
opts := models.FindNotificationOptions{
UserID: ctx.User.ID,
UpdatedBeforeUnix: lastRead,
Status: models.NotificationStatusUnread,
}
nl, err := models.GetNotifications(opts)
if err != nil {
ctx.InternalServerError(err)
return
}
for _, n := range nl {
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.Status(http.StatusResetContent)
}
ctx.Status(http.StatusResetContent)
}

View File

@ -192,37 +192,52 @@ func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
// "$ref": "#/responses/Team"
team := ctx.Org.Team
team.Description = form.Description
unitTypes := models.FindUnitTypes(form.Units...)
team.CanCreateOrgRepo = form.CanCreateOrgRepo
if err := team.GetUnits(); err != nil {
ctx.InternalServerError(err)
return
}
if form.CanCreateOrgRepo != nil {
team.CanCreateOrgRepo = *form.CanCreateOrgRepo
}
if len(form.Name) > 0 {
team.Name = form.Name
}
if form.Description != nil {
team.Description = *form.Description
}
isAuthChanged := false
isIncludeAllChanged := false
if !team.IsOwnerTeam() {
if !team.IsOwnerTeam() && len(form.Permission) != 0 {
// Validate permission level.
auth := models.ParseAccessMode(form.Permission)
team.Name = form.Name
if team.Authorize != auth {
isAuthChanged = true
team.Authorize = auth
}
if team.IncludesAllRepositories != form.IncludesAllRepositories {
if form.IncludesAllRepositories != nil {
isIncludeAllChanged = true
team.IncludesAllRepositories = form.IncludesAllRepositories
team.IncludesAllRepositories = *form.IncludesAllRepositories
}
}
if team.Authorize < models.AccessModeOwner {
var units = make([]*models.TeamUnit, 0, len(form.Units))
for _, tp := range unitTypes {
units = append(units, &models.TeamUnit{
OrgID: ctx.Org.Team.OrgID,
Type: tp,
})
if len(form.Units) > 0 {
var units = make([]*models.TeamUnit, 0, len(form.Units))
unitTypes := models.FindUnitTypes(form.Units...)
for _, tp := range unitTypes {
units = append(units, &models.TeamUnit{
OrgID: ctx.Org.Team.OrgID,
Type: tp,
})
}
team.Units = units
}
team.Units = units
}
if err := models.UpdateTeam(team, isAuthChanged, isIncludeAllChanged); err != nil {

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
@ -102,7 +103,7 @@ func ListPullRequests(ctx *context.APIContext, form api.ListPullRequestsOptions)
ctx.Error(http.StatusInternalServerError, "GetHeadRepo", err)
return
}
apiPrs[i] = prs[i].APIFormat()
apiPrs[i] = convert.ToAPIPullRequest(prs[i])
}
ctx.SetLinkHeader(int(maxResults), models.ItemsPerPage)
@ -136,6 +137,8 @@ func GetPullRequest(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/PullRequest"
// "404":
// "$ref": "#/responses/notFound"
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
@ -155,7 +158,7 @@ func GetPullRequest(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetHeadRepo", err)
return
}
ctx.JSON(http.StatusOK, pr.APIFormat())
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(pr))
}
// CreatePullRequest does what it says
@ -319,7 +322,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
notification.NotifyNewPullRequest(pr)
log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
ctx.JSON(http.StatusCreated, pr.APIFormat())
ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(pr))
}
// EditPullRequest does what it says
@ -477,7 +480,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
}
// TODO this should be 200, not 201
ctx.JSON(http.StatusCreated, pr.APIFormat())
ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(pr))
}
// IsPullRequestMerged checks if a PR exists given an index

View File

@ -282,11 +282,12 @@ func Create(ctx *context.APIContext, opt api.CreateRepoOption) {
CreateUserRepo(ctx, ctx.User, opt)
}
// CreateOrgRepo create one repository of the organization
func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
// swagger:operation POST /org/{org}/repos organization createOrgRepo
// CreateOrgRepoDeprecated create one repository of the organization
func CreateOrgRepoDeprecated(ctx *context.APIContext, opt api.CreateRepoOption) {
// swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated
// ---
// summary: Create a repository in an organization
// deprecated: true
// consumes:
// - application/json
// produces:
@ -309,6 +310,37 @@ func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
// "403":
// "$ref": "#/responses/forbidden"
CreateOrgRepo(ctx, opt)
}
// CreateOrgRepo create one repository of the organization
func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
// swagger:operation POST /orgs/{org}/repos organization createOrgRepo
// ---
// summary: Create a repository in an organization
// deprecated: true
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of organization
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreateRepoOption"
// responses:
// "201":
// "$ref": "#/responses/Repository"
// "404":
// "$ref": "#/responses/notFound"
// "403":
// "$ref": "#/responses/forbidden"
org, err := models.GetOrgByName(ctx.Params(":org"))
if err != nil {
if models.IsErrOrgNotExist(err) {
@ -452,12 +484,13 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
}
repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
Name: opts.RepoName,
Description: opts.Description,
OriginalURL: form.CloneAddr,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: models.RepositoryBeingMigrated,
Name: opts.RepoName,
Description: opts.Description,
OriginalURL: form.CloneAddr,
GitServiceType: gitServiceType,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: models.RepositoryBeingMigrated,
})
if err != nil {
handleMigrateError(ctx, ctxUser, remoteAddr, err)

View File

@ -0,0 +1,23 @@
// Copyright 2019 The Gitea 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 swagger
import (
api "code.gitea.io/gitea/modules/structs"
)
// NotificationThread
// swagger:response NotificationThread
type swaggerNotificationThread struct {
// in:body
Body api.NotificationThread `json:"body"`
}
// NotificationThreadList
// swagger:response NotificationThreadList
type swaggerNotificationThreadList struct {
// in:body
Body []api.NotificationThread `json:"body"`
}

View File

@ -6,6 +6,7 @@ package repo
import (
"fmt"
"net/url"
"os"
"path"
"strings"
@ -18,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/task"
"code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository"
@ -330,22 +332,29 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
return
}
var gitServiceType = structs.PlainGitService
u, err := url.Parse(form.CloneAddr)
if err == nil && strings.EqualFold(u.Host, "github.com") {
gitServiceType = structs.GithubService
}
var opts = migrations.MigrateOptions{
OriginalURL: form.CloneAddr,
CloneAddr: remoteAddr,
RepoName: form.RepoName,
Description: form.Description,
Private: form.Private || setting.Repository.ForcePrivate,
Mirror: form.Mirror,
AuthUsername: form.AuthUsername,
AuthPassword: form.AuthPassword,
Wiki: form.Wiki,
Issues: form.Issues,
Milestones: form.Milestones,
Labels: form.Labels,
Comments: true,
PullRequests: form.PullRequests,
Releases: form.Releases,
OriginalURL: form.CloneAddr,
GitServiceType: gitServiceType,
CloneAddr: remoteAddr,
RepoName: form.RepoName,
Description: form.Description,
Private: form.Private || setting.Repository.ForcePrivate,
Mirror: form.Mirror,
AuthUsername: form.AuthUsername,
AuthPassword: form.AuthPassword,
Wiki: form.Wiki,
Issues: form.Issues,
Milestones: form.Milestones,
Labels: form.Labels,
Comments: true,
PullRequests: form.PullRequests,
Releases: form.Releases,
}
if opts.Mirror {
opts.Issues = false

View File

@ -403,7 +403,7 @@ func syncMirror(repoID string) {
continue
}
theCommits := models.ListToPushCommits(commits)
theCommits := repository.ListToPushCommits(commits)
if len(theCommits.Commits) > setting.UI.FeedMaxCommitNum {
theCommits.Commits = theCommits.Commits[:setting.UI.FeedMaxCommitNum]
}

View File

@ -194,10 +194,16 @@ func TestPullRequests(ctx context.Context) {
if err != nil {
log.Error("GetPullRequestByID[%s]: %v", prID, err)
continue
} else if pr.Status != models.PullRequestStatusChecking {
continue
} else if manuallyMerged(pr) {
continue
} else if err = TestPatch(pr); err != nil {
log.Error("testPatch[%d]: %v", pr.ID, err)
pr.Status = models.PullRequestStatusError
if err := pr.UpdateCols("status"); err != nil {
log.Error("update pr [%d] status to PullRequestStatusError failed: %v", pr.ID, err)
}
continue
}
checkAndUpdateStatus(pr)

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
)
@ -43,7 +44,7 @@ func createTag(gitRepo *git.Repository, rel *models.Release) error {
}
notification.NotifyPushCommits(
rel.Publisher, rel.Repo, git.TagPrefix+rel.TagName,
git.EmptySHA, commit.ID.String(), models.NewPushCommits())
git.EmptySHA, commit.ID.String(), repository.NewPushCommits())
notification.NotifyCreateRef(rel.Publisher, rel.Repo, "tag", git.TagPrefix+rel.TagName)
}
commit, err := gitRepo.GetTagCommit(rel.TagName)

View File

@ -153,7 +153,7 @@
<input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage}}">
</div>
<div class="field">
<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">{{$approvers}}</textarea>
<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">Reviewed-on: {{$.Issue.HTMLURL}}&#13;&#10;{{$approvers}}</textarea>
</div>
<button class="ui green button" type="submit" name="do" value="merge">
{{$.i18n.Tr "repo.pulls.merge_pull_request"}}
@ -185,7 +185,7 @@
<input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage}}">
</div>
<div class="field">
<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">{{$approvers}}</textarea>
<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">Reviewed-on: {{$.Issue.HTMLURL}}&#13;&#10;{{$approvers}}</textarea>
</div>
<button class="ui green button" type="submit" name="do" value="rebase-merge">
{{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}}
@ -205,7 +205,7 @@
<input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultSquashMessage}}">
</div>
<div class="field">
<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">{{$commitMessages}}{{$approvers}}</textarea>
<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">{{$commitMessages}}Reviewed-on: {{$.Issue.HTMLURL}}&#13;&#10;{{$approvers}}</textarea>
</div>
<button class="ui green button" type="submit" name="do" value="squash">
{{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}}

View File

@ -425,6 +425,143 @@
}
}
},
"/notifications": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"notification"
],
"summary": "List users's notification threads",
"operationId": "notifyGetList",
"parameters": [
{
"type": "string",
"description": "If true, show notifications marked as read. Default value is false",
"name": "all",
"in": "query"
},
{
"type": "string",
"format": "date-time",
"description": "Only show notifications updated after the given time. This is a timestamp in RFC 3339 format",
"name": "since",
"in": "query"
},
{
"type": "string",
"format": "date-time",
"description": "Only show notifications updated before the given time. This is a timestamp in RFC 3339 format",
"name": "before",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/NotificationThreadList"
}
}
},
"put": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"notification"
],
"summary": "Mark notification threads as read",
"operationId": "notifyReadList",
"parameters": [
{
"type": "string",
"format": "date-time",
"description": "Describes the last point that notifications were checked. Anything updated since this time will not be updated.",
"name": "last_read_at",
"in": "query"
}
],
"responses": {
"205": {
"$ref": "#/responses/empty"
}
}
}
},
"/notifications/threads/{id}": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"notification"
],
"summary": "Get notification thread by ID",
"operationId": "notifyGetThread",
"parameters": [
{
"type": "string",
"description": "id of notification thread",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/NotificationThread"
},
"403": {
"$ref": "#/responses/forbidden"
},
"404": {
"$ref": "#/responses/notFound"
}
}
},
"patch": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"notification"
],
"summary": "Mark notification thread as read by ID",
"operationId": "notifyReadThread",
"parameters": [
{
"type": "string",
"description": "id of notification thread",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"205": {
"$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
},
"404": {
"$ref": "#/responses/notFound"
}
}
}
},
"/org/{org}/repos": {
"post": {
"consumes": [
@ -437,7 +574,8 @@
"organization"
],
"summary": "Create a repository in an organization",
"operationId": "createOrgRepo",
"operationId": "createOrgRepoDeprecated",
"deprecated": true,
"parameters": [
{
"type": "string",
@ -1003,6 +1141,47 @@
"$ref": "#/responses/RepositoryList"
}
}
},
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"organization"
],
"summary": "Create a repository in an organization",
"operationId": "createOrgRepo",
"deprecated": true,
"parameters": [
{
"type": "string",
"description": "name of organization",
"name": "org",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/CreateRepoOption"
}
}
],
"responses": {
"201": {
"$ref": "#/responses/Repository"
},
"403": {
"$ref": "#/responses/forbidden"
},
"404": {
"$ref": "#/responses/notFound"
}
}
}
},
"/orgs/{org}/teams": {
@ -5231,6 +5410,103 @@
}
}
},
"/repos/{owner}/{repo}/notifications": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"notification"
],
"summary": "List users's notification threads on a specific repo",
"operationId": "notifyGetRepoList",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "If true, show notifications marked as read. Default value is false",
"name": "all",
"in": "query"
},
{
"type": "string",
"format": "date-time",
"description": "Only show notifications updated after the given time. This is a timestamp in RFC 3339 format",
"name": "since",
"in": "query"
},
{
"type": "string",
"format": "date-time",
"description": "Only show notifications updated before the given time. This is a timestamp in RFC 3339 format",
"name": "before",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/NotificationThreadList"
}
}
},
"put": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"notification"
],
"summary": "Mark notification threads as read on a specific repo",
"operationId": "notifyReadRepoList",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"format": "date-time",
"description": "Describes the last point that notifications were checked. Anything updated since this time will not be updated.",
"name": "last_read_at",
"in": "query"
}
],
"responses": {
"205": {
"$ref": "#/responses/empty"
}
}
}
},
"/repos/{owner}/{repo}/pulls": {
"get": {
"produces": [
@ -5397,6 +5673,9 @@
"responses": {
"200": {
"$ref": "#/responses/PullRequest"
},
"404": {
"$ref": "#/responses/notFound"
}
}
},
@ -10584,6 +10863,64 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"NotificationSubject": {
"description": "NotificationSubject contains the notification subject (Issue/Pull/Commit)",
"type": "object",
"properties": {
"latest_comment_url": {
"type": "string",
"x-go-name": "LatestCommentURL"
},
"title": {
"type": "string",
"x-go-name": "Title"
},
"type": {
"type": "string",
"x-go-name": "Type"
},
"url": {
"type": "string",
"x-go-name": "URL"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"NotificationThread": {
"description": "NotificationThread expose Notification on API",
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64",
"x-go-name": "ID"
},
"pinned": {
"type": "boolean",
"x-go-name": "Pinned"
},
"repository": {
"$ref": "#/definitions/Repository"
},
"subject": {
"$ref": "#/definitions/NotificationSubject"
},
"unread": {
"type": "boolean",
"x-go-name": "Unread"
},
"updated_at": {
"type": "string",
"format": "date-time",
"x-go-name": "UpdatedAt"
},
"url": {
"type": "string",
"x-go-name": "URL"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"Organization": {
"description": "Organization represents an organization",
"type": "object",
@ -12012,6 +12349,21 @@
}
}
},
"NotificationThread": {
"description": "NotificationThread",
"schema": {
"$ref": "#/definitions/NotificationThread"
}
},
"NotificationThreadList": {
"description": "NotificationThreadList",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/NotificationThread"
}
}
},
"Organization": {
"description": "Organization",
"schema": {

View File

@ -774,6 +774,7 @@ footer {
.container {
width: 100vw !important;
padding: 0 0.5rem;
max-width: calc(100vw - 1rem) !important;
.fa {
width: 16px;