Merge branch 'master' into fix-6409

This commit is contained in:
Nicolas Gourdon
2019-07-11 15:38:13 +02:00
1152 changed files with 117643 additions and 29961 deletions
-7
View File
@@ -10,13 +10,6 @@ import (
"github.com/stretchr/testify/assert"
)
var accessModes = []AccessMode{
AccessModeRead,
AccessModeWrite,
AccessModeAdmin,
AccessModeOwner,
}
func TestAccessLevel(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
+7 -2
View File
@@ -24,7 +24,7 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"xorm.io/builder"
)
// ActionType represents the type of an action.
@@ -67,7 +67,7 @@ var (
const issueRefRegexpStr = `(?:([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+))?(#[0-9]+)+`
func assembleKeywordsPattern(words []string) string {
return fmt.Sprintf(`(?i)(?:%s) %s`, strings.Join(words, "|"), issueRefRegexpStr)
return fmt.Sprintf(`(?i)(?:%s)(?::?) %s`, strings.Join(words, "|"), issueRefRegexpStr)
}
func init() {
@@ -896,6 +896,11 @@ func mirrorSyncAction(e Engine, opType ActionType, repo *Repository, refName str
}); err != nil {
return fmt.Errorf("notifyWatchers: %v", err)
}
defer func() {
go HookQueue.Add(repo.ID)
}()
return nil
}
+26
View File
@@ -166,6 +166,7 @@ func Test_getIssueFromRef(t *testing.T) {
{"reopen #2", 2},
{"user2/repo2#1", 4},
{"fixes user2/repo2#1", 4},
{"fixes: user2/repo2#1", 4},
} {
issue, err := getIssueFromRef(repo, test.Ref)
assert.NoError(t, err)
@@ -260,6 +261,31 @@ func TestUpdateIssuesCommit(t *testing.T) {
CheckConsistencyFor(t, &Action{})
}
func TestUpdateIssuesCommit_Colon(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
pushCommits := []*PushCommit{
{
Sha1: "abcdef2",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user2@example.com",
AuthorName: "User Two",
Message: "close: #2",
},
}
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
repo.Owner = user
issueBean := &Issue{RepoID: repo.ID, Index: 2}
AssertNotExistsBean(t, &Issue{RepoID: repo.ID, Index: 2}, "is_closed=1")
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch))
AssertExistsAndLoadBean(t, issueBean, "is_closed=1")
CheckConsistencyFor(t, &Action{})
}
func TestUpdateIssuesCommit_Issue5957(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
+1 -1
View File
@@ -63,7 +63,7 @@ func RemoveAllWithNotice(title, path string) {
func removeAllWithNotice(e Engine, title, path string) {
if err := os.RemoveAll(path); err != nil {
desc := fmt.Sprintf("%s [%s]: %v", title, path, err)
log.Warn(desc)
log.Warn(title+" [%s]: %v", path, err)
if err = createNotice(e, NoticeRepository, desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
+6 -4
View File
@@ -19,6 +19,8 @@ import (
const (
// ProtectedBranchRepoID protected Repo ID
ProtectedBranchRepoID = "GITEA_REPO_ID"
// ProtectedBranchPRID protected Repo PR ID
ProtectedBranchPRID = "GITEA_PR_ID"
)
// ProtectedBranch struct
@@ -126,14 +128,14 @@ func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest)
}
// GetProtectedBranchByRepoID getting protected branch by repo ID
func GetProtectedBranchByRepoID(RepoID int64) ([]*ProtectedBranch, error) {
func GetProtectedBranchByRepoID(repoID int64) ([]*ProtectedBranch, error) {
protectedBranches := make([]*ProtectedBranch, 0)
return protectedBranches, x.Where("repo_id = ?", RepoID).Desc("updated_unix").Find(&protectedBranches)
return protectedBranches, x.Where("repo_id = ?", repoID).Desc("updated_unix").Find(&protectedBranches)
}
// GetProtectedBranchBy getting protected branch by ID/Name
func GetProtectedBranchBy(repoID int64, BranchName string) (*ProtectedBranch, error) {
rel := &ProtectedBranch{RepoID: repoID, BranchName: BranchName}
func GetProtectedBranchBy(repoID int64, branchName string) (*ProtectedBranch, error) {
rel := &ProtectedBranch{RepoID: repoID, BranchName: branchName}
has, err := x.Get(rel)
if err != nil {
return nil, err
+40 -74
View File
@@ -6,16 +6,14 @@ package models
import (
"container/list"
"crypto/sha1"
"fmt"
"strings"
"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/util"
"github.com/go-xorm/xorm"
)
// CommitStatusState holds the state of a Status
@@ -61,6 +59,7 @@ type CommitStatus struct {
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
TargetURL string `xorm:"TEXT"`
Description string `xorm:"TEXT"`
ContextHash string `xorm:"char(40) index"`
Context string `xorm:"TEXT"`
Creator *User `xorm:"-"`
CreatorID int64
@@ -87,7 +86,7 @@ func (status *CommitStatus) loadRepo(e Engine) (err error) {
// APIURL returns the absolute APIURL to this commit-status.
func (status *CommitStatus) APIURL() string {
status.loadRepo(x)
_ = status.loadRepo(x)
return fmt.Sprintf("%sapi/v1/%s/statuses/%s",
setting.AppURL, status.Repo.FullName(), status.SHA)
}
@@ -95,7 +94,7 @@ func (status *CommitStatus) APIURL() string {
// APIFormat assumes some fields assigned with values:
// Required - Repo, Creator
func (status *CommitStatus) APIFormat() *api.Status {
status.loadRepo(x)
_ = status.loadRepo(x)
apiStatus := &api.Status{
Created: status.CreatedUnix.AsTime(),
Updated: status.CreatedUnix.AsTime(),
@@ -146,7 +145,7 @@ func GetLatestCommitStatus(repo *Repository, sha string, page int) ([]*CommitSta
Table(&CommitStatus{}).
Where("repo_id = ?", repo.ID).And("sha = ?", sha).
Select("max( id ) as id").
GroupBy("context").OrderBy("max( id ) desc").Find(&ids)
GroupBy("context_hash").OrderBy("max( id ) desc").Find(&ids)
if err != nil {
return nil, err
}
@@ -157,27 +156,6 @@ func GetLatestCommitStatus(repo *Repository, sha string, page int) ([]*CommitSta
return statuses, x.In("id", ids).Find(&statuses)
}
// GetCommitStatus populates a given status for a given commit.
// NOTE: If ID or Index isn't given, and only Context, TargetURL and/or Description
// is given, the CommitStatus created _last_ will be returned.
func GetCommitStatus(repo *Repository, sha string, status *CommitStatus) (*CommitStatus, error) {
conds := &CommitStatus{
Context: status.Context,
State: status.State,
TargetURL: status.TargetURL,
Description: status.Description,
}
has, err := x.Where("repo_id = ?", repo.ID).And("sha = ?", sha).Desc("created_unix").Get(conds)
if err != nil {
return nil, fmt.Errorf("GetCommitStatus[%s, %s]: %v", repo.RepoPath(), sha, err)
}
if !has {
return nil, fmt.Errorf("GetCommitStatus[%s, %s]: not found", repo.RepoPath(), sha)
}
return conds, nil
}
// NewCommitStatusOptions holds options for creating a CommitStatus
type NewCommitStatusOptions struct {
Repo *Repository
@@ -186,30 +164,30 @@ type NewCommitStatusOptions struct {
CommitStatus *CommitStatus
}
func newCommitStatus(sess *xorm.Session, opts NewCommitStatusOptions) error {
// NewCommitStatus save commit statuses into database
func NewCommitStatus(opts NewCommitStatusOptions) error {
if opts.Repo == nil {
return fmt.Errorf("NewCommitStatus[nil, %s]: no repository specified", opts.SHA)
}
repoPath := opts.Repo.RepoPath()
if opts.Creator == nil {
return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA)
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", opts.Repo.ID, opts.Creator.ID, opts.SHA, err)
}
opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description)
opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context)
opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL)
opts.CommitStatus.SHA = opts.SHA
opts.CommitStatus.CreatorID = opts.Creator.ID
if opts.Repo == nil {
return fmt.Errorf("newCommitStatus[nil, %s]: no repository specified", opts.SHA)
}
opts.CommitStatus.RepoID = opts.Repo.ID
repoPath := opts.Repo.repoPath(sess)
if opts.Creator == nil {
return fmt.Errorf("newCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA)
}
gitRepo, err := git.OpenRepository(repoPath)
if err != nil {
return fmt.Errorf("OpenRepository[%s]: %v", repoPath, err)
}
if _, err := gitRepo.GetCommit(opts.SHA); err != nil {
return fmt.Errorf("GetCommit[%s]: %v", opts.SHA, err)
}
// Get the next Status Index
var nextIndex int64
@@ -219,43 +197,26 @@ func newCommitStatus(sess *xorm.Session, opts NewCommitStatusOptions) error {
}
has, err := sess.Desc("index").Limit(1).Get(lastCommitStatus)
if err != nil {
sess.Rollback()
return fmt.Errorf("newCommitStatus[%s, %s]: %v", repoPath, opts.SHA, err)
if err := sess.Rollback(); err != nil {
log.Error("NewCommitStatus: sess.Rollback: %v", err)
}
return fmt.Errorf("NewCommitStatus[%s, %s]: %v", repoPath, opts.SHA, err)
}
if has {
log.Debug("newCommitStatus[%s, %s]: found", repoPath, opts.SHA)
log.Debug("NewCommitStatus[%s, %s]: found", repoPath, opts.SHA)
nextIndex = lastCommitStatus.Index
}
opts.CommitStatus.Index = nextIndex + 1
log.Debug("newCommitStatus[%s, %s]: %d", repoPath, opts.SHA, opts.CommitStatus.Index)
log.Debug("NewCommitStatus[%s, %s]: %d", repoPath, opts.SHA, opts.CommitStatus.Index)
opts.CommitStatus.ContextHash = hashCommitStatusContext(opts.CommitStatus.Context)
// Insert new CommitStatus
if _, err = sess.Insert(opts.CommitStatus); err != nil {
sess.Rollback()
return fmt.Errorf("newCommitStatus[%s, %s]: %v", repoPath, opts.SHA, err)
}
return nil
}
// NewCommitStatus creates a new CommitStatus given a bunch of parameters
// NOTE: All text-values will be trimmed from whitespaces.
// Requires: Repo, Creator, SHA
func NewCommitStatus(repo *Repository, creator *User, sha string, status *CommitStatus) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", repo.ID, creator.ID, sha, err)
}
if err := newCommitStatus(sess, NewCommitStatusOptions{
Repo: repo,
Creator: creator,
SHA: sha,
CommitStatus: status,
}); err != nil {
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", repo.ID, creator.ID, sha, err)
if err := sess.Rollback(); err != nil {
log.Error("Insert CommitStatus: sess.Rollback: %v", err)
}
return fmt.Errorf("Insert CommitStatus[%s, %s]: %v", repoPath, opts.SHA, err)
}
return sess.Commit()
@@ -291,3 +252,8 @@ func ParseCommitsWithStatus(oldCommits *list.List, repo *Repository) *list.List
}
return newCommits
}
// hashCommitStatusContext hash context
func hashCommitStatusContext(context string) string {
return fmt.Sprintf("%x", sha1.Sum([]byte(context)))
}
+1 -1
View File
@@ -39,7 +39,7 @@ func CheckConsistencyFor(t *testing.T, beansToCheck ...interface{}) {
ptrToSliceValue := reflect.New(sliceType)
ptrToSliceValue.Elem().Set(sliceValue)
assert.NoError(t, x.Where(bean).Find(ptrToSliceValue.Interface()))
assert.NoError(t, x.Table(bean).Find(ptrToSliceValue.Interface()))
sliceValue = ptrToSliceValue.Elem()
for i := 0; i < sliceValue.Len(); i++ {
+27
View File
@@ -0,0 +1,27 @@
// 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 models
import "fmt"
// ConvertUtf8ToUtf8mb4 converts database and tables from utf8 to utf8mb4 if it's mysql
func ConvertUtf8ToUtf8mb4() error {
_, err := x.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci", DbCfg.Name))
if err != nil {
return err
}
tables, err := x.DBMetas()
if err != nil {
return err
}
for _, table := range tables {
if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;", table.Name)); err != nil {
return err
}
}
return nil
}
+17
View File
@@ -674,6 +674,23 @@ func (err ErrRepoAlreadyExist) Error() string {
return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
}
// ErrForkAlreadyExist represents a "ForkAlreadyExist" kind of error.
type ErrForkAlreadyExist struct {
Uname string
RepoName string
ForkName string
}
// IsErrForkAlreadyExist checks if an error is an ErrForkAlreadyExist.
func IsErrForkAlreadyExist(err error) bool {
_, ok := err.(ErrForkAlreadyExist)
return ok
}
func (err ErrForkAlreadyExist) Error() string {
return fmt.Sprintf("repository is already forked by user [uname: %s, repo path: %s, fork path: %s]", err.Uname, err.RepoName, err.ForkName)
}
// ErrRepoRedirectNotExist represents a "RepoRedirectNotExist" kind of error.
type ErrRepoRedirectNotExist struct {
OwnerID int64
+6
View File
@@ -9,3 +9,9 @@
repo_id: 4
user_id: 4
mode: 2 # write
-
id: 3
repo_id: 40
user_id: 4
mode: 2 # write
+11
View File
@@ -86,3 +86,14 @@
created_unix: 946684830
updated_unix: 978307200
-
id: 8
repo_id: 10
index: 1
poster_id: 11
name: pr2
content: a pull request
is_closed: false
is_pull: true
created_unix: 946684820
updated_unix: 978307180
+8
View File
@@ -13,3 +13,11 @@
content: content2
is_closed: false
num_issues: 0
-
id: 3
repo_id: 1
name: milestone3
content: content3
is_closed: true
num_issues: 0
+14
View File
@@ -26,3 +26,17 @@
base_branch: master
merge_base: fedcba9876543210
has_merged: false
-
id: 3
type: 0 # gitea pull request
status: 2 # mergable
issue_id: 8
index: 1
head_repo_id: 11
base_repo_id: 10
head_user_name: user13
head_branch: branch2
base_branch: master
merge_base: 0abcb056019adb83
has_merged: false
+3 -2
View File
@@ -8,7 +8,8 @@
num_closed_issues: 1
num_pulls: 2
num_closed_pulls: 0
num_milestones: 2
num_milestones: 3
num_closed_milestones: 1
num_watches: 3
-
@@ -117,7 +118,7 @@
is_private: false
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_pulls: 1
num_closed_pulls: 0
is_mirror: false
num_forks: 1
-124
View File
@@ -1,124 +0,0 @@
// 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 models
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/process"
)
// BlamePart represents block of blame - continuous lines with one sha
type BlamePart struct {
Sha string
Lines []string
}
// BlameReader returns part of file blame one by one
type BlameReader struct {
cmd *exec.Cmd
pid int64
output io.ReadCloser
scanner *bufio.Scanner
lastSha *string
}
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
// NextPart returns next part of blame (sequencial code lines with the same commit)
func (r *BlameReader) NextPart() (*BlamePart, error) {
var blamePart *BlamePart
scanner := r.scanner
if r.lastSha != nil {
blamePart = &BlamePart{*r.lastSha, make([]string, 0, 0)}
}
for scanner.Scan() {
line := scanner.Text()
// Skip empty lines
if len(line) == 0 {
continue
}
lines := shaLineRegex.FindStringSubmatch(line)
if lines != nil {
sha1 := lines[1]
if blamePart == nil {
blamePart = &BlamePart{sha1, make([]string, 0, 0)}
}
if blamePart.Sha != sha1 {
r.lastSha = &sha1
return blamePart, nil
}
} else if line[0] == '\t' {
code := line[1:]
blamePart.Lines = append(blamePart.Lines, code)
}
}
r.lastSha = nil
return blamePart, nil
}
// Close BlameReader - don't run NextPart after invoking that
func (r *BlameReader) Close() error {
process.GetManager().Remove(r.pid)
if err := r.cmd.Wait(); err != nil {
return fmt.Errorf("Wait: %v", err)
}
return nil
}
// CreateBlameReader creates reader for given repository, commit and file
func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) {
_, err := git.OpenRepository(repoPath)
if err != nil {
return nil, err
}
return createBlameReader(repoPath, "git", "blame", commitID, "--porcelain", "--", file)
}
func createBlameReader(dir string, command ...string) (*BlameReader, error) {
cmd := exec.Command(command[0], command[1:]...)
cmd.Dir = dir
cmd.Stderr = os.Stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("StdoutPipe: %v", err)
}
if err = cmd.Start(); err != nil {
return nil, fmt.Errorf("Start: %v", err)
}
pid := process.GetManager().Add(fmt.Sprintf("GetBlame [repo_path: %s]", dir), cmd)
scanner := bufio.NewScanner(stdout)
return &BlameReader{
cmd,
pid,
stdout,
scanner,
nil,
}, nil
}
-141
View File
@@ -1,141 +0,0 @@
// 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 models
import (
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
)
const exampleBlame = `
4b92a6c2df28054ad766bc262f308db9f6066596 1 1 1
author Unknown
author-mail <joe2010xtmf@163.com>
author-time 1392833071
author-tz -0500
committer Unknown
committer-mail <joe2010xtmf@163.com>
committer-time 1392833071
committer-tz -0500
summary Add code of delete user
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
filename gogs.go
// Copyright 2014 The Gogs Authors. All rights reserved.
ce21ed6c3490cdfad797319cbb1145e2330a8fef 2 2 1
author Joubert RedRat
author-mail <eu+github@redrat.com.br>
author-time 1482322397
author-tz -0200
committer Lunny Xiao
committer-mail <xiaolunwen@gmail.com>
committer-time 1482322397
committer-tz +0800
summary Remove remaining Gogs reference on locales and cmd (#430)
previous 618407c018cdf668ceedde7454c42fb22ba422d8 main.go
filename main.go
// Copyright 2016 The Gitea Authors. All rights reserved.
4b92a6c2df28054ad766bc262f308db9f6066596 2 3 2
author Unknown
author-mail <joe2010xtmf@163.com>
author-time 1392833071
author-tz -0500
committer Unknown
committer-mail <joe2010xtmf@163.com>
committer-time 1392833071
committer-tz -0500
summary Add code of delete user
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
filename gogs.go
// Use of this source code is governed by a MIT-style
4b92a6c2df28054ad766bc262f308db9f6066596 3 4
author Unknown
author-mail <joe2010xtmf@163.com>
author-time 1392833071
author-tz -0500
committer Unknown
committer-mail <joe2010xtmf@163.com>
committer-time 1392833071
committer-tz -0500
summary Add code of delete user
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
filename gogs.go
// license that can be found in the LICENSE file.
e2aa991e10ffd924a828ec149951f2f20eecead2 6 6 2
author Lunny Xiao
author-mail <xiaolunwen@gmail.com>
author-time 1478872595
author-tz +0800
committer Sandro Santilli
committer-mail <strk@kbt.io>
committer-time 1478872595
committer-tz +0100
summary ask for go get from code.gitea.io/gitea and change gogs to gitea on main file (#146)
previous 5fc370e332171b8658caed771b48585576f11737 main.go
filename main.go
// Gitea (git with a cup of tea) is a painless self-hosted Git Service.
e2aa991e10ffd924a828ec149951f2f20eecead2 7 7
package main // import "code.gitea.io/gitea"
`
func TestReadingBlameOutput(t *testing.T) {
tempFile, err := ioutil.TempFile("", ".txt")
if err != nil {
panic(err)
}
defer tempFile.Close()
if _, err = tempFile.WriteString(exampleBlame); err != nil {
panic(err)
}
blameReader, err := createBlameReader("", "cat", tempFile.Name())
if err != nil {
panic(err)
}
defer blameReader.Close()
parts := []*BlamePart{
{
"4b92a6c2df28054ad766bc262f308db9f6066596",
[]string{
"// Copyright 2014 The Gogs Authors. All rights reserved.",
},
},
{
"ce21ed6c3490cdfad797319cbb1145e2330a8fef",
[]string{
"// Copyright 2016 The Gitea Authors. All rights reserved.",
},
},
{
"4b92a6c2df28054ad766bc262f308db9f6066596",
[]string{
"// Use of this source code is governed by a MIT-style",
"// license that can be found in the LICENSE file.",
"",
},
},
{
"e2aa991e10ffd924a828ec149951f2f20eecead2",
[]string{
"// Gitea (git with a cup of tea) is a painless self-hosted Git Service.",
"package main // import \"code.gitea.io/gitea\"",
},
},
nil,
}
for _, part := range parts {
actualPart, err := blameReader.NextPart()
if err != nil {
panic(err)
}
assert.Equal(t, part, actualPart)
}
}
+43 -33
View File
@@ -80,6 +80,22 @@ func (d *DiffLine) GetCommentSide() string {
return d.Comments[0].DiffSide()
}
// GetLineTypeMarker returns the line type marker
func (d *DiffLine) GetLineTypeMarker() string {
if strings.IndexByte(" +-", d.Content[0]) > -1 {
return d.Content[0:1]
}
return ""
}
// escape a line's content or return <br> needed for copy/paste purposes
func getLineContent(content string) string {
if len(content) > 0 {
return html.EscapeString(content)
}
return "<br>"
}
// DiffSection represents a section of a DiffFile.
type DiffSection struct {
Name string
@@ -87,34 +103,26 @@ type DiffSection struct {
}
var (
addedCodePrefix = []byte("<span class=\"added-code\">")
removedCodePrefix = []byte("<span class=\"removed-code\">")
codeTagSuffix = []byte("</span>")
addedCodePrefix = []byte(`<span class="added-code">`)
removedCodePrefix = []byte(`<span class="removed-code">`)
codeTagSuffix = []byte(`</span>`)
)
func diffToHTML(diffs []diffmatchpatch.Diff, lineType DiffLineType) template.HTML {
buf := bytes.NewBuffer(nil)
// Reproduce signs which are cut for inline diff before.
switch lineType {
case DiffLineAdd:
buf.WriteByte('+')
case DiffLineDel:
buf.WriteByte('-')
}
for i := range diffs {
switch {
case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd:
buf.Write(addedCodePrefix)
buf.WriteString(html.EscapeString(diffs[i].Text))
buf.WriteString(getLineContent(diffs[i].Text))
buf.Write(codeTagSuffix)
case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel:
buf.Write(removedCodePrefix)
buf.WriteString(html.EscapeString(diffs[i].Text))
buf.WriteString(getLineContent(diffs[i].Text))
buf.Write(codeTagSuffix)
case diffs[i].Type == diffmatchpatch.DiffEqual:
buf.WriteString(html.EscapeString(diffs[i].Text))
buf.WriteString(getLineContent(diffs[i].Text))
}
}
@@ -173,7 +181,7 @@ func init() {
// GetComputedInlineDiffFor computes inline diff for the given line.
func (diffSection *DiffSection) GetComputedInlineDiffFor(diffLine *DiffLine) template.HTML {
if setting.Git.DisableDiffHighlight {
return template.HTML(html.EscapeString(diffLine.Content[1:]))
return template.HTML(getLineContent(diffLine.Content[1:]))
}
var (
compareDiffLine *DiffLine
@@ -186,19 +194,22 @@ func (diffSection *DiffSection) GetComputedInlineDiffFor(diffLine *DiffLine) tem
case DiffLineAdd:
compareDiffLine = diffSection.GetLine(DiffLineDel, diffLine.RightIdx)
if compareDiffLine == nil {
return template.HTML(html.EscapeString(diffLine.Content))
return template.HTML(getLineContent(diffLine.Content[1:]))
}
diff1 = compareDiffLine.Content
diff2 = diffLine.Content
case DiffLineDel:
compareDiffLine = diffSection.GetLine(DiffLineAdd, diffLine.LeftIdx)
if compareDiffLine == nil {
return template.HTML(html.EscapeString(diffLine.Content))
return template.HTML(getLineContent(diffLine.Content[1:]))
}
diff1 = diffLine.Content
diff2 = compareDiffLine.Content
default:
return template.HTML(html.EscapeString(diffLine.Content))
if strings.IndexByte(" +-", diffLine.Content[0]) > -1 {
return template.HTML(getLineContent(diffLine.Content[1:]))
}
return template.HTML(getLineContent(diffLine.Content))
}
diffRecord := diffMatchPatch.DiffMain(diff1[1:], diff2[1:], true)
@@ -384,13 +395,9 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
// headers + hunk header
newHunk := make([]string, headerLines)
// transfer existing headers
for idx, lof := range hunk[:headerLines] {
newHunk[idx] = lof
}
copy(newHunk, hunk[:headerLines])
// transfer last n lines
for _, lof := range hunk[len(hunk)-numbersOfLine-1:] {
newHunk = append(newHunk, lof)
}
newHunk = append(newHunk, hunk[len(hunk)-numbersOfLine-1:]...)
// calculate newBegin, ... by counting lines
for i := len(hunk) - 1; i >= len(hunk)-numbersOfLine; i-- {
switch hunk[i][0] {
@@ -582,7 +589,10 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
diff.Files = append(diff.Files, curFile)
if len(diff.Files) >= maxFiles {
diff.IsIncomplete = true
io.Copy(ioutil.Discard, reader)
_, err := io.Copy(ioutil.Discard, reader)
if err != nil {
return nil, fmt.Errorf("Copy: %v", err)
}
break
}
curFileLinesCount = 0
@@ -673,7 +683,7 @@ func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID
var cmd *exec.Cmd
if len(beforeCommitID) == 0 && commit.ParentCount() == 0 {
cmd = exec.Command("git", "show", afterCommitID)
cmd = exec.Command(git.GitExecutable, "show", afterCommitID)
} else {
actualBeforeCommitID := beforeCommitID
if len(actualBeforeCommitID) == 0 {
@@ -686,7 +696,7 @@ func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID
}
diffArgs = append(diffArgs, actualBeforeCommitID)
diffArgs = append(diffArgs, afterCommitID)
cmd = exec.Command("git", diffArgs...)
cmd = exec.Command(git.GitExecutable, diffArgs...)
}
cmd.Dir = repoPath
cmd.Stderr = os.Stderr
@@ -750,23 +760,23 @@ func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiff
switch diffType {
case RawDiffNormal:
if len(startCommit) != 0 {
cmd = exec.Command("git", append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
cmd = exec.Command(git.GitExecutable, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
} else if commit.ParentCount() == 0 {
cmd = exec.Command("git", append([]string{"show", endCommit}, fileArgs...)...)
cmd = exec.Command(git.GitExecutable, append([]string{"show", endCommit}, fileArgs...)...)
} else {
c, _ := commit.Parent(0)
cmd = exec.Command("git", append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
cmd = exec.Command(git.GitExecutable, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
}
case RawDiffPatch:
if len(startCommit) != 0 {
query := fmt.Sprintf("%s...%s", endCommit, startCommit)
cmd = exec.Command("git", append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
cmd = exec.Command(git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
} else if commit.ParentCount() == 0 {
cmd = exec.Command("git", append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
cmd = exec.Command(git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
} else {
c, _ := commit.Parent(0)
query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
cmd = exec.Command("git", append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
cmd = exec.Command(git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
}
default:
return fmt.Errorf("invalid diffType: %s", diffType)
+2 -8
View File
@@ -17,21 +17,15 @@ func assertEqual(t *testing.T, s1 string, s2 template.HTML) {
}
}
func assertLineEqual(t *testing.T, d1 *DiffLine, d2 *DiffLine) {
if d1 != d2 {
t.Errorf("%v should be equal %v", d1, d2)
}
}
func TestDiffToHTML(t *testing.T) {
assertEqual(t, "+foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{
assertEqual(t, "foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{
{Type: dmp.DiffEqual, Text: "foo "},
{Type: dmp.DiffInsert, Text: "bar"},
{Type: dmp.DiffDelete, Text: " baz"},
{Type: dmp.DiffEqual, Text: " biz"},
}, DiffLineAdd))
assertEqual(t, "-foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{
assertEqual(t, "foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{
{Type: dmp.DiffEqual, Text: "foo "},
{Type: dmp.DiffDelete, Text: "bar"},
{Type: dmp.DiffInsert, Text: " baz"},
+17 -8
View File
@@ -12,24 +12,33 @@ import (
// PushingEnvironment returns an os environment to allow hooks to work on push
func PushingEnvironment(doer *User, repo *Repository) []string {
return FullPushingEnvironment(doer, doer, repo, 0)
}
// FullPushingEnvironment returns an os environment to allow hooks to work on push
func FullPushingEnvironment(author, committer *User, repo *Repository, prID int64) []string {
isWiki := "false"
if strings.HasSuffix(repo.Name, ".wiki") {
isWiki = "true"
}
sig := doer.NewGitSig()
authorSig := author.NewGitSig()
committerSig := committer.NewGitSig()
// We should add "SSH_ORIGINAL_COMMAND=gitea-internal",
// once we have hook and pushing infrastructure working correctly
return append(os.Environ(),
"GIT_AUTHOR_NAME="+sig.Name,
"GIT_AUTHOR_EMAIL="+sig.Email,
"GIT_COMMITTER_NAME="+sig.Name,
"GIT_COMMITTER_EMAIL="+sig.Email,
"GIT_AUTHOR_NAME="+authorSig.Name,
"GIT_AUTHOR_EMAIL="+authorSig.Email,
"GIT_COMMITTER_NAME="+committerSig.Name,
"GIT_COMMITTER_EMAIL="+committerSig.Email,
EnvRepoName+"="+repo.Name,
EnvRepoUsername+"="+repo.OwnerName,
EnvRepoUsername+"="+repo.MustOwnerName(),
EnvRepoIsWiki+"="+isWiki,
EnvPusherName+"="+doer.Name,
EnvPusherID+"="+fmt.Sprintf("%d", doer.ID),
EnvPusherName+"="+committer.Name,
EnvPusherID+"="+fmt.Sprintf("%d", committer.ID),
ProtectedBranchRepoID+"="+fmt.Sprintf("%d", repo.ID),
ProtectedBranchPRID+"="+fmt.Sprintf("%d", prID),
"SSH_ORIGINAL_COMMAND=gitea-internal",
)
+56 -31
View File
@@ -5,6 +5,7 @@
package models
import (
"errors"
"fmt"
"path"
"regexp"
@@ -18,33 +19,35 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
"xorm.io/builder"
)
// Issue represents an issue or pull request of repository.
type Issue struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
Repo *Repository `xorm:"-"`
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
Title string `xorm:"name"`
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *User `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
IsRead bool `xorm:"-"`
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"`
NumComments int
Ref string
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
Repo *Repository `xorm:"-"`
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
OriginalAuthor string
OriginalAuthorID int64
Title string `xorm:"name"`
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *User `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
IsRead bool `xorm:"-"`
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"`
NumComments int
Ref string
DeadlineUnix util.TimeStamp `xorm:"INDEX"`
@@ -1015,9 +1018,35 @@ type NewIssueOptions struct {
IsPull bool
}
// GetMaxIndexOfIssue returns the max index on issue
func GetMaxIndexOfIssue(repoID int64) (int64, error) {
return getMaxIndexOfIssue(x, repoID)
}
func getMaxIndexOfIssue(e Engine, repoID int64) (int64, error) {
var (
maxIndex int64
has bool
err error
)
has, err = e.SQL("SELECT COALESCE((SELECT MAX(`index`) FROM issue WHERE repo_id = ?),0)", repoID).Get(&maxIndex)
if err != nil {
return 0, err
} else if !has {
return 0, errors.New("Retrieve Max index from issue failed")
}
return maxIndex, nil
}
func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
opts.Issue.Title = strings.TrimSpace(opts.Issue.Title)
opts.Issue.Index = opts.Repo.NextIssueIndex()
maxIndex, err := getMaxIndexOfIssue(e, opts.Issue.RepoID)
if err != nil {
return err
}
opts.Issue.Index = maxIndex + 1
if opts.Issue.MilestoneID > 0 {
milestone, err := getMilestoneByRepoID(e, opts.Issue.RepoID, opts.Issue.MilestoneID)
@@ -1303,7 +1332,7 @@ func sortIssuesSession(sess *xorm.Session, sortType string) {
}
}
func (opts *IssuesOptions) setupSession(sess *xorm.Session) error {
func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
if opts.Page >= 0 && opts.PageSize > 0 {
var start int
if opts.Page == 0 {
@@ -1362,7 +1391,6 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) error {
fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID))
}
}
return nil
}
// CountIssuesByRepo map from repoID to number of issues matching the options
@@ -1370,9 +1398,7 @@ func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
sess := x.NewSession()
defer sess.Close()
if err := opts.setupSession(sess); err != nil {
return nil, err
}
opts.setupSession(sess)
countsSlice := make([]*struct {
RepoID int64
@@ -1397,15 +1423,14 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
sess := x.NewSession()
defer sess.Close()
if err := opts.setupSession(sess); err != nil {
return nil, err
}
opts.setupSession(sess)
sortIssuesSession(sess, opts.SortType)
issues := make([]*Issue, 0, setting.UI.IssuePagingNum)
if err := sess.Find(&issues); err != nil {
return nil, fmt.Errorf("Find: %v", err)
}
sess.Close()
if err := IssueList(issues).LoadAttributes(); err != nil {
return nil, fmt.Errorf("LoadAttributes: %v", err)
+1 -2
View File
@@ -43,8 +43,7 @@ func TestUpdateAssignee(t *testing.T) {
assert.NoError(t, err)
var expectedAssignees []*User
expectedAssignees = append(expectedAssignees, user2)
expectedAssignees = append(expectedAssignees, user3)
expectedAssignees = append(expectedAssignees, user2, user3)
for in, assignee := range assignees {
assert.Equal(t, assignee.ID, expectedAssignees[in].ID)
+21 -23
View File
@@ -15,8 +15,8 @@ import (
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
"xorm.io/builder"
api "code.gitea.io/gitea/modules/structs"
@@ -101,8 +101,10 @@ const (
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
OriginalAuthor string
OriginalAuthorID int64
IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
LabelID int64
@@ -171,17 +173,6 @@ func (c *Comment) loadPoster(e Engine) (err error) {
return err
}
func (c *Comment) loadAttachments(e Engine) (err error) {
if len(c.Attachments) > 0 {
return
}
c.Attachments, err = getAttachmentsByCommentID(e, c.ID)
if err != nil {
log.Error("getAttachmentsByCommentID[%d]: %v", c.ID, err)
}
return err
}
// AfterDelete is invoked from XORM after the object is deleted.
func (c *Comment) AfterDelete() {
if c.ID <= 0 {
@@ -403,16 +394,23 @@ func (c *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue) (e
return fmt.Errorf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
}
content := c.Content
if len(c.Content) > 0 {
if err = mailIssueCommentToParticipants(e, issue, c.Poster, c.Content, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}
switch opType {
case ActionCloseIssue:
content = fmt.Sprintf("Closed #%d", issue.Index)
ct := fmt.Sprintf("Closed #%d.", issue.Index)
if err = mailIssueCommentToParticipants(e, issue, c.Poster, ct, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
case ActionReopenIssue:
content = fmt.Sprintf("Reopened #%d", issue.Index)
}
if err = mailIssueCommentToParticipants(e, issue, c.Poster, content, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
ct := fmt.Sprintf("Reopened #%d.", issue.Index)
if err = mailIssueCommentToParticipants(e, issue, c.Poster, ct, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}
return nil
@@ -456,7 +454,7 @@ func (c *Comment) LoadReview() error {
return c.loadReview(x)
}
func (c *Comment) checkInvalidation(e Engine, doer *User, repo *git.Repository, branch string) error {
func (c *Comment) checkInvalidation(doer *User, repo *git.Repository, branch string) error {
// FIXME differentiate between previous and proposed line
commit, err := repo.LineBlame(branch, repo.Path, c.TreePath, uint(c.UnsignedLine()))
if err != nil {
@@ -472,7 +470,7 @@ func (c *Comment) checkInvalidation(e Engine, doer *User, repo *git.Repository,
// CheckInvalidation checks if the line of code comment got changed by another commit.
// If the line got changed the comment is going to be invalidated.
func (c *Comment) CheckInvalidation(repo *git.Repository, doer *User, branch string) error {
return c.checkInvalidation(x, doer, repo, branch)
return c.checkInvalidation(doer, repo, branch)
}
// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
@@ -908,7 +906,7 @@ func CreateCodeComment(doer *User, repo *Repository, issue *Issue, content, tree
commit, err := gitRepo.LineBlame(pr.GetGitRefName(), gitRepo.Path, treePath, uint(line))
if err == nil {
commitID = commit.ID.String()
} else if err != nil && !strings.Contains(err.Error(), "exit status 128 - fatal: no such path") {
} else if !strings.Contains(err.Error(), "exit status 128 - fatal: no such path") {
return nil, fmt.Errorf("LineBlame[%s, %s, %s, %d]: %v", pr.GetGitRefName(), gitRepo.Path, treePath, line, err)
}
}
+19 -19
View File
@@ -36,7 +36,7 @@ func (comments CommentList) loadPosters(e Engine) error {
if err != nil {
return err
}
left = left - limit
left -= limit
posterIDs = posterIDs[limit:]
}
@@ -94,13 +94,13 @@ func (comments CommentList) loadLabels(e Engine) error {
var label Label
err = rows.Scan(&label)
if err != nil {
rows.Close()
_ = rows.Close()
return err
}
commentLabels[label.ID] = &label
}
rows.Close()
left = left - limit
_ = rows.Close()
left -= limit
labelIDs = labelIDs[limit:]
}
@@ -143,7 +143,7 @@ func (comments CommentList) loadMilestones(e Engine) error {
if err != nil {
return err
}
left = left - limit
left -= limit
milestoneIDs = milestoneIDs[limit:]
}
@@ -186,7 +186,7 @@ func (comments CommentList) loadOldMilestones(e Engine) error {
if err != nil {
return err
}
left = left - limit
left -= limit
milestoneIDs = milestoneIDs[limit:]
}
@@ -236,9 +236,9 @@ func (comments CommentList) loadAssignees(e Engine) error {
assignees[user.ID] = &user
}
rows.Close()
_ = rows.Close()
left = left - limit
left -= limit
assigneeIDs = assigneeIDs[limit:]
}
@@ -310,9 +310,9 @@ func (comments CommentList) loadIssues(e Engine) error {
issues[issue.ID] = &issue
}
rows.Close()
_ = rows.Close()
left = left - limit
left -= limit
issueIDs = issueIDs[limit:]
}
@@ -361,15 +361,15 @@ func (comments CommentList) loadDependentIssues(e Engine) error {
var issue Issue
err = rows.Scan(&issue)
if err != nil {
rows.Close()
_ = rows.Close()
return err
}
issues[issue.ID] = &issue
}
rows.Close()
_ = rows.Close()
left = left - limit
left -= limit
issueIDs = issueIDs[limit:]
}
@@ -406,14 +406,14 @@ func (comments CommentList) loadAttachments(e Engine) (err error) {
var attachment Attachment
err = rows.Scan(&attachment)
if err != nil {
rows.Close()
_ = rows.Close()
return err
}
attachments[attachment.CommentID] = append(attachments[attachment.CommentID], &attachment)
}
rows.Close()
left = left - limit
_ = rows.Close()
left -= limit
commentsIDs = commentsIDs[limit:]
}
@@ -457,15 +457,15 @@ func (comments CommentList) loadReviews(e Engine) error {
var review Review
err = rows.Scan(&review)
if err != nil {
rows.Close()
_ = rows.Close()
return err
}
reviews[review.ID] = &review
}
rows.Close()
_ = rows.Close()
left = left - limit
left -= limit
reviewIDs = reviewIDs[limit:]
}
+4 -11
View File
@@ -76,9 +76,10 @@ type Label struct {
// APIFormat converts a Label to the api.Label format
func (label *Label) APIFormat() *api.Label {
return &api.Label{
ID: label.ID,
Name: label.Name,
Color: strings.TrimLeft(label.Color, "#"),
ID: label.ID,
Name: label.Name,
Color: strings.TrimLeft(label.Color, "#"),
Description: label.Description,
}
}
@@ -401,14 +402,6 @@ func NewIssueLabels(issue *Issue, labels []*Label, doer *User) (err error) {
return sess.Commit()
}
func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) {
issueLabels := make([]*IssueLabel, 0, 10)
return issueLabels, e.
Where("issue_id=?", issueID).
Asc("label_id").
Find(&issueLabels)
}
func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err error) {
if count, err := e.Delete(&IssueLabel{
IssueID: issue.ID,
+63 -40
View File
@@ -7,7 +7,7 @@ package models
import (
"fmt"
"github.com/go-xorm/builder"
"xorm.io/builder"
)
// IssueList defines a list of issues
@@ -47,7 +47,7 @@ func (issues IssueList) loadRepositories(e Engine) ([]*Repository, error) {
if err != nil {
return nil, fmt.Errorf("find repository: %v", err)
}
left = left - limit
left -= limit
repoIDs = repoIDs[limit:]
}
@@ -91,7 +91,7 @@ func (issues IssueList) loadPosters(e Engine) error {
if err != nil {
return err
}
left = left - limit
left -= limit
posterIDs = posterIDs[limit:]
}
@@ -146,13 +146,19 @@ func (issues IssueList) loadLabels(e Engine) error {
var labelIssue LabelIssue
err = rows.Scan(&labelIssue)
if err != nil {
rows.Close()
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadLabels: Close: %v", err1)
}
return err
}
issueLabels[labelIssue.IssueLabel.IssueID] = append(issueLabels[labelIssue.IssueLabel.IssueID], labelIssue.Label)
}
rows.Close()
left = left - limit
// When there are no rows left and we try to close it.
// Since that is not relevant for us, we can safely ignore it.
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadLabels: Close: %v", err1)
}
left -= limit
issueIDs = issueIDs[limit:]
}
@@ -191,7 +197,7 @@ func (issues IssueList) loadMilestones(e Engine) error {
if err != nil {
return err
}
left = left - limit
left -= limit
milestoneIDs = milestoneIDs[limit:]
}
@@ -231,15 +237,18 @@ func (issues IssueList) loadAssignees(e Engine) error {
var assigneeIssue AssigneeIssue
err = rows.Scan(&assigneeIssue)
if err != nil {
rows.Close()
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAssignees: Close: %v", err1)
}
return err
}
assignees[assigneeIssue.IssueAssignee.IssueID] = append(assignees[assigneeIssue.IssueAssignee.IssueID], assigneeIssue.Assignee)
}
rows.Close()
left = left - limit
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAssignees: Close: %v", err1)
}
left -= limit
issueIDs = issueIDs[limit:]
}
@@ -283,14 +292,17 @@ func (issues IssueList) loadPullRequests(e Engine) error {
var pr PullRequest
err = rows.Scan(&pr)
if err != nil {
rows.Close()
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadPullRequests: Close: %v", err1)
}
return err
}
pullRequestMaps[pr.IssueID] = &pr
}
rows.Close()
left = left - limit
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadPullRequests: Close: %v", err1)
}
left -= limit
issuesIDs = issuesIDs[limit:]
}
@@ -325,14 +337,17 @@ func (issues IssueList) loadAttachments(e Engine) (err error) {
var attachment Attachment
err = rows.Scan(&attachment)
if err != nil {
rows.Close()
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAttachments: Close: %v", err1)
}
return err
}
attachments[attachment.IssueID] = append(attachments[attachment.IssueID], &attachment)
}
rows.Close()
left = left - limit
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAttachments: Close: %v", err1)
}
left -= limit
issuesIDs = issuesIDs[limit:]
}
@@ -368,13 +383,17 @@ func (issues IssueList) loadComments(e Engine, cond builder.Cond) (err error) {
var comment Comment
err = rows.Scan(&comment)
if err != nil {
rows.Close()
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadComments: Close: %v", err1)
}
return err
}
comments[comment.IssueID] = append(comments[comment.IssueID], &comment)
}
rows.Close()
left = left - limit
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadComments: Close: %v", err1)
}
left -= limit
issuesIDs = issuesIDs[limit:]
}
@@ -422,13 +441,17 @@ func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) {
var totalTime totalTimesByIssue
err = rows.Scan(&totalTime)
if err != nil {
rows.Close()
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %v", err1)
}
return err
}
trackedTimes[totalTime.IssueID] = totalTime.Time
}
rows.Close()
left = left - limit
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %v", err1)
}
left -= limit
ids = ids[limit:]
}
@@ -439,33 +462,33 @@ func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) {
}
// loadAttributes loads all attributes, expect for attachments and comments
func (issues IssueList) loadAttributes(e Engine) (err error) {
if _, err = issues.loadRepositories(e); err != nil {
return
func (issues IssueList) loadAttributes(e Engine) error {
if _, err := issues.loadRepositories(e); err != nil {
return fmt.Errorf("issue.loadAttributes: loadRepositories: %v", err)
}
if err = issues.loadPosters(e); err != nil {
return
if err := issues.loadPosters(e); err != nil {
return fmt.Errorf("issue.loadAttributes: loadPosters: %v", err)
}
if err = issues.loadLabels(e); err != nil {
return
if err := issues.loadLabels(e); err != nil {
return fmt.Errorf("issue.loadAttributes: loadLabels: %v", err)
}
if err = issues.loadMilestones(e); err != nil {
return
if err := issues.loadMilestones(e); err != nil {
return fmt.Errorf("issue.loadAttributes: loadMilestones: %v", err)
}
if err = issues.loadAssignees(e); err != nil {
return
if err := issues.loadAssignees(e); err != nil {
return fmt.Errorf("issue.loadAttributes: loadAssignees: %v", err)
}
if err = issues.loadPullRequests(e); err != nil {
return
if err := issues.loadPullRequests(e); err != nil {
return fmt.Errorf("issue.loadAttributes: loadPullRequests: %v", err)
}
if err = issues.loadTotalTrackedTimes(e); err != nil {
return
if err := issues.loadTotalTrackedTimes(e); err != nil {
return fmt.Errorf("issue.loadAttributes: loadTotalTrackedTimes: %v", err)
}
return nil
+27 -12
View File
@@ -16,7 +16,7 @@ import (
)
func (issue *Issue) mailSubject() string {
return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Title, issue.Index)
return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.FullName(), issue.Title, issue.Index)
}
// mailIssueCommentToParticipants can be used for both new issue creation and comment.
@@ -118,26 +118,41 @@ func mailIssueCommentToParticipants(e Engine, issue *Issue, doer *User, content
// MailParticipants sends new issue thread created emails to repository watchers
// and mentioned people.
func (issue *Issue) MailParticipants(opType ActionType) (err error) {
return issue.mailParticipants(x, opType)
func (issue *Issue) MailParticipants(doer *User, opType ActionType) (err error) {
return issue.mailParticipants(x, doer, opType)
}
func (issue *Issue) mailParticipants(e Engine, opType ActionType) (err error) {
func (issue *Issue) mailParticipants(e Engine, doer *User, opType ActionType) (err error) {
mentions := markup.FindAllMentions(issue.Content)
if err = UpdateIssueMentions(e, issue.ID, mentions); err != nil {
return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
}
var content = issue.Content
switch opType {
case ActionCloseIssue, ActionClosePullRequest:
content = fmt.Sprintf("Closed #%d", issue.Index)
case ActionReopenIssue, ActionReopenPullRequest:
content = fmt.Sprintf("Reopened #%d", issue.Index)
if len(issue.Content) > 0 {
if err = mailIssueCommentToParticipants(e, issue, doer, issue.Content, nil, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}
if err = mailIssueCommentToParticipants(e, issue, issue.Poster, content, nil, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
switch opType {
case ActionCreateIssue, ActionCreatePullRequest:
if len(issue.Content) == 0 {
ct := fmt.Sprintf("Created #%d.", issue.Index)
if err = mailIssueCommentToParticipants(e, issue, doer, ct, nil, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}
case ActionCloseIssue, ActionClosePullRequest:
ct := fmt.Sprintf("Closed #%d.", issue.Index)
if err = mailIssueCommentToParticipants(e, issue, doer, ct, nil, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
case ActionReopenIssue, ActionReopenPullRequest:
ct := fmt.Sprintf("Reopened #%d.", issue.Index)
if err = mailIssueCommentToParticipants(e, issue, doer, ct, nil, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}
return nil
+19 -3
View File
@@ -190,10 +190,26 @@ func (milestones MilestoneList) getMilestoneIDs() []int64 {
}
// GetMilestonesByRepoID returns all opened milestones of a repository.
func GetMilestonesByRepoID(repoID int64) (MilestoneList, error) {
func GetMilestonesByRepoID(repoID int64, state api.StateType) (MilestoneList, error) {
sess := x.Where("repo_id = ?", repoID)
switch state {
case api.StateClosed:
sess = sess.And("is_closed = ?", true)
case api.StateAll:
break
case api.StateOpen:
fallthrough
default:
sess = sess.And("is_closed = ?", false)
}
miles := make([]*Milestone, 0, 10)
return miles, x.Where("repo_id = ? AND is_closed = ?", repoID, false).
Asc("deadline_unix").Asc("id").Find(&miles)
return miles, sess.Asc("deadline_unix").Asc("id").Find(&miles)
}
// GetMilestones returns a list of milestones of given repository and status.
+30 -7
View File
@@ -69,20 +69,43 @@ func TestGetMilestoneByRepoID(t *testing.T) {
func TestGetMilestonesByRepoID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(repoID int64) {
test := func(repoID int64, state api.StateType) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
milestones, err := GetMilestonesByRepoID(repo.ID)
milestones, err := GetMilestonesByRepoID(repo.ID, state)
assert.NoError(t, err)
assert.Len(t, milestones, repo.NumMilestones)
var n int
switch state {
case api.StateClosed:
n = repo.NumClosedMilestones
case api.StateAll:
n = repo.NumMilestones
case api.StateOpen:
fallthrough
default:
n = repo.NumOpenMilestones
}
assert.Len(t, milestones, n)
for _, milestone := range milestones {
assert.EqualValues(t, repoID, milestone.RepoID)
}
}
test(1)
test(2)
test(3)
test(1, api.StateOpen)
test(1, api.StateAll)
test(1, api.StateClosed)
test(2, api.StateOpen)
test(2, api.StateAll)
test(2, api.StateClosed)
test(3, api.StateOpen)
test(3, api.StateClosed)
test(3, api.StateAll)
milestones, err := GetMilestonesByRepoID(NonexistentID)
milestones, err := GetMilestonesByRepoID(NonexistentID, api.StateOpen)
assert.NoError(t, err)
assert.Len(t, milestones, 0)
}
+1 -1
View File
@@ -11,8 +11,8 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
"xorm.io/builder"
)
// Reaction represents a reactions on issues and comments.
+1 -1
View File
@@ -10,8 +10,8 @@ import (
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
"xorm.io/builder"
)
// TrackedTime represents a time that was spent for a specific issue.
+105
View File
@@ -0,0 +1,105 @@
// Copyright 2017 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 (
"fmt"
"code.gitea.io/gitea/modules/log"
"xorm.io/core"
)
// XORMLogBridge a logger bridge from Logger to xorm
type XORMLogBridge struct {
showSQL bool
logger *log.Logger
}
// NewXORMLogger inits a log bridge for xorm
func NewXORMLogger(showSQL bool) core.ILogger {
return &XORMLogBridge{
showSQL: showSQL,
logger: log.GetLogger("xorm"),
}
}
// Log a message with defined skip and at logging level
func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...interface{}) error {
return l.logger.Log(skip+1, level, format, v...)
}
// Debug show debug log
func (l *XORMLogBridge) Debug(v ...interface{}) {
_ = l.Log(2, log.DEBUG, fmt.Sprint(v...))
}
// Debugf show debug log
func (l *XORMLogBridge) Debugf(format string, v ...interface{}) {
_ = l.Log(2, log.DEBUG, format, v...)
}
// Error show error log
func (l *XORMLogBridge) Error(v ...interface{}) {
_ = l.Log(2, log.ERROR, fmt.Sprint(v...))
}
// Errorf show error log
func (l *XORMLogBridge) Errorf(format string, v ...interface{}) {
_ = l.Log(2, log.ERROR, format, v...)
}
// Info show information level log
func (l *XORMLogBridge) Info(v ...interface{}) {
_ = l.Log(2, log.INFO, fmt.Sprint(v...))
}
// Infof show information level log
func (l *XORMLogBridge) Infof(format string, v ...interface{}) {
_ = l.Log(2, log.INFO, format, v...)
}
// Warn show warning log
func (l *XORMLogBridge) Warn(v ...interface{}) {
_ = l.Log(2, log.WARN, fmt.Sprint(v...))
}
// Warnf show warnning log
func (l *XORMLogBridge) Warnf(format string, v ...interface{}) {
_ = l.Log(2, log.WARN, format, v...)
}
// Level get logger level
func (l *XORMLogBridge) Level() core.LogLevel {
switch l.logger.GetLevel() {
case log.TRACE, log.DEBUG:
return core.LOG_DEBUG
case log.INFO:
return core.LOG_INFO
case log.WARN:
return core.LOG_WARNING
case log.ERROR, log.CRITICAL:
return core.LOG_ERR
}
return core.LOG_OFF
}
// SetLevel set the logger level
func (l *XORMLogBridge) SetLevel(lvl core.LogLevel) {
}
// ShowSQL set if record SQL
func (l *XORMLogBridge) ShowSQL(show ...bool) {
if len(show) > 0 {
l.showSQL = show[0]
} else {
l.showSQL = true
}
}
// IsShowSQL if record SQL
func (l *XORMLogBridge) IsShowSQL() bool {
return l.showSQL
}
+28 -11
View File
@@ -11,17 +11,18 @@ import (
"fmt"
"net/smtp"
"net/textproto"
"regexp"
"strings"
"github.com/Unknwon/com"
"github.com/go-macaron/binding"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"xorm.io/core"
"code.gitea.io/gitea/modules/auth/ldap"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/auth/pam"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
@@ -164,8 +165,7 @@ func Cell2Int64(val xorm.Cell) int64 {
// BeforeSet is invoked from XORM before setting the value of a field of this object.
func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
switch colName {
case "type":
if colName == "type" {
switch LoginType(Cell2Int64(val)) {
case LoginLDAP, LoginDLDAP:
source.Cfg = new(LDAPConfig)
@@ -282,10 +282,12 @@ func CreateLoginSource(source *LoginSource) error {
oAuth2Config := source.OAuth2()
err = oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
err = wrapOpenIDConnectInitializeError(err, source.Name, oAuth2Config)
if err != nil {
// remove the LoginSource in case of errors while registering OAuth2 providers
x.Delete(source)
if _, err := x.Delete(source); err != nil {
log.Error("CreateLoginSource: Error while wrapOpenIDConnectInitializeError: %v", err)
}
return err
}
}
return err
@@ -325,10 +327,12 @@ func UpdateSource(source *LoginSource) error {
oAuth2Config := source.OAuth2()
err = oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
err = wrapOpenIDConnectInitializeError(err, source.Name, oAuth2Config)
if err != nil {
// restore original values since we cannot update the provider it self
x.ID(source.ID).AllCols().Update(originalLoginSource)
if _, err := x.ID(source.ID).AllCols().Update(originalLoginSource); err != nil {
log.Error("UpdateSource: Error while wrapOpenIDConnectInitializeError: %v", err)
}
return err
}
}
return err
@@ -384,6 +388,10 @@ func composeFullName(firstname, surname, username string) string {
}
}
var (
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
)
// LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
// and create a local user if success when enabled.
func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
@@ -397,7 +405,7 @@ func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoR
if !autoRegister {
if isAttributeSSHPublicKeySet && synchronizeLdapSSHPublicKeys(user, source, sr.SSHPublicKey) {
RewriteAllPublicKeys()
return user, RewriteAllPublicKeys()
}
return user, nil
@@ -408,7 +416,7 @@ func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoR
sr.Username = login
}
// Validate username make sure it satisfies requirement.
if binding.AlphaDashDotPattern.MatchString(sr.Username) {
if alphaDashDotPattern.MatchString(sr.Username) {
return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", sr.Username)
}
@@ -431,7 +439,7 @@ func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoR
err := CreateUser(user)
if err == nil && isAttributeSSHPublicKeySet && addLdapSSHPublicKeys(user, source, sr.SSHPublicKey) {
RewriteAllPublicKeys()
err = RewriteAllPublicKeys()
}
return user, err
@@ -658,6 +666,15 @@ func UserSignIn(username, password string) (*User, error) {
switch user.LoginType {
case LoginNoType, LoginPlain, LoginOAuth2:
if user.IsPasswordSet() && user.ValidatePassword(password) {
// Update password hash if server password hash algorithm have changed
if user.PasswdHashAlgo != setting.PasswordHashAlgo {
user.HashPassword(password)
if err := UpdateUserCols(user, "passwd", "passwd_hash_algo"); err != nil {
return nil, err
}
}
// WARN: DON'T check user.IsActive, that will be checked on reqSign so that
// user could be hint to resend confirm email.
if user.ProhibitLogin {
+24 -16
View File
@@ -17,7 +17,6 @@ import (
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"gopkg.in/gomail.v2"
"gopkg.in/macaron.v1"
)
const (
@@ -34,7 +33,7 @@ const (
var templates *template.Template
// InitMailRender initializes the macaron mail renderer
// InitMailRender initializes the mail renderer
func InitMailRender(tmpls *template.Template) {
templates = tmpls
}
@@ -45,11 +44,11 @@ func SendTestMail(email string) error {
}
// SendUserMail sends a mail to the user
func SendUserMail(c *macaron.Context, u *User, tpl base.TplName, code, subject, info string) {
func SendUserMail(language string, u *User, tpl base.TplName, code, subject, info string) {
data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"ActiveCodeLives": base.MinutesToFriendly(setting.Service.ActiveCodeLives, c.Locale.Language()),
"ResetPwdCodeLives": base.MinutesToFriendly(setting.Service.ResetPwdCodeLives, c.Locale.Language()),
"ActiveCodeLives": base.MinutesToFriendly(setting.Service.ActiveCodeLives, language),
"ResetPwdCodeLives": base.MinutesToFriendly(setting.Service.ResetPwdCodeLives, language),
"Code": code,
}
@@ -66,21 +65,27 @@ func SendUserMail(c *macaron.Context, u *User, tpl base.TplName, code, subject,
mailer.SendAsync(msg)
}
// Locale represents an interface to translation
type Locale interface {
Language() string
Tr(string, ...interface{}) string
}
// SendActivateAccountMail sends an activation mail to the user (new user registration)
func SendActivateAccountMail(c *macaron.Context, u *User) {
SendUserMail(c, u, mailAuthActivate, u.GenerateActivateCode(), c.Tr("mail.activate_account"), "activate account")
func SendActivateAccountMail(locale Locale, u *User) {
SendUserMail(locale.Language(), u, mailAuthActivate, u.GenerateActivateCode(), locale.Tr("mail.activate_account"), "activate account")
}
// SendResetPasswordMail sends a password reset mail to the user
func SendResetPasswordMail(c *macaron.Context, u *User) {
SendUserMail(c, u, mailAuthResetPassword, u.GenerateActivateCode(), c.Tr("mail.reset_password"), "recover account")
func SendResetPasswordMail(locale Locale, u *User) {
SendUserMail(locale.Language(), u, mailAuthResetPassword, u.GenerateActivateCode(), locale.Tr("mail.reset_password"), "recover account")
}
// SendActivateEmailMail sends confirmation email to confirm new email address
func SendActivateEmailMail(c *macaron.Context, u *User, email *EmailAddress) {
func SendActivateEmailMail(locale Locale, u *User, email *EmailAddress) {
data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"ActiveCodeLives": base.MinutesToFriendly(setting.Service.ActiveCodeLives, c.Locale.Language()),
"ActiveCodeLives": base.MinutesToFriendly(setting.Service.ActiveCodeLives, locale.Language()),
"Code": u.GenerateEmailActivateCode(email.Email),
"Email": email.Email,
}
@@ -92,14 +97,14 @@ func SendActivateEmailMail(c *macaron.Context, u *User, email *EmailAddress) {
return
}
msg := mailer.NewMessage([]string{email.Email}, c.Tr("mail.activate_email"), content.String())
msg := mailer.NewMessage([]string{email.Email}, locale.Tr("mail.activate_email"), content.String())
msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID)
mailer.SendAsync(msg)
}
// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
func SendRegisterNotifyMail(c *macaron.Context, u *User) {
func SendRegisterNotifyMail(locale Locale, u *User) {
data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"Username": u.Name,
@@ -112,7 +117,7 @@ func SendRegisterNotifyMail(c *macaron.Context, u *User) {
return
}
msg := mailer.NewMessage([]string{u.Email}, c.Tr("mail.register_notify"), content.String())
msg := mailer.NewMessage([]string{u.Email}, locale.Tr("mail.register_notify"), content.String())
msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID)
mailer.SendAsync(msg)
@@ -152,10 +157,13 @@ func composeTplData(subject, body, link string) map[string]interface{} {
func composeIssueCommentMessage(issue *Issue, doer *User, content string, comment *Comment, tplName base.TplName, tos []string, info string) *mailer.Message {
subject := issue.mailSubject()
issue.LoadRepo()
err := issue.LoadRepo()
if err != nil {
log.Error("LoadRepo: %v", err)
}
body := string(markup.RenderByType(markdown.MarkupName, []byte(content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas()))
data := make(map[string]interface{}, 10)
var data = make(map[string]interface{}, 10)
if comment != nil {
data = composeTplData(subject, body, issue.HTMLURL()+"#"+comment.HashTag())
} else {
+80 -51
View File
@@ -6,38 +6,58 @@ package models
import "github.com/go-xorm/xorm"
// InsertIssue insert one issue to database
func InsertIssue(issue *Issue, labelIDs []int64) error {
// InsertMilestones creates milestones of repository.
func InsertMilestones(ms ...*Milestone) (err error) {
if len(ms) == 0 {
return nil
}
sess := x.NewSession()
if err := sess.Begin(); err != nil {
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if err := insertIssue(sess, issue, labelIDs); err != nil {
// to return the id, so we should not use batch insert
for _, m := range ms {
if _, err = sess.NoAutoTime().Insert(m); err != nil {
return err
}
}
if _, err = sess.Exec("UPDATE `repository` SET num_milestones = num_milestones + ? WHERE id = ?", len(ms), ms[0].RepoID); err != nil {
return err
}
return sess.Commit()
}
func insertIssue(sess *xorm.Session, issue *Issue, labelIDs []int64) error {
if issue.MilestoneID > 0 {
sess.Incr("num_issues")
if issue.IsClosed {
sess.Incr("num_closed_issues")
}
if _, err := sess.ID(issue.MilestoneID).NoAutoTime().Update(new(Milestone)); err != nil {
// InsertIssues insert issues to database
func InsertIssues(issues ...*Issue) error {
sess := x.NewSession()
if err := sess.Begin(); err != nil {
return err
}
for _, issue := range issues {
if err := insertIssue(sess, issue); err != nil {
return err
}
}
return sess.Commit()
}
func insertIssue(sess *xorm.Session, issue *Issue) error {
if _, err := sess.NoAutoTime().Insert(issue); err != nil {
return err
}
var issueLabels = make([]IssueLabel, 0, len(labelIDs))
for _, labelID := range labelIDs {
var issueLabels = make([]IssueLabel, 0, len(issue.Labels))
var labelIDs = make([]int64, 0, len(issue.Labels))
for _, label := range issue.Labels {
issueLabels = append(issueLabels, IssueLabel{
IssueID: issue.ID,
LabelID: labelID,
LabelID: label.ID,
})
labelIDs = append(labelIDs, label.ID)
}
if _, err := sess.Insert(issueLabels); err != nil {
return err
@@ -61,12 +81,20 @@ func insertIssue(sess *xorm.Session, issue *Issue, labelIDs []int64) error {
if issue.IsClosed {
sess.Incr("num_closed_issues")
}
if _, err := sess.In("id", labelIDs).Update(new(Label)); err != nil {
if _, err := sess.In("id", labelIDs).NoAutoTime().Update(new(Label)); err != nil {
return err
}
if issue.MilestoneID > 0 {
if _, err := sess.ID(issue.MilestoneID).SetExpr("completeness", "num_closed_issues * 100 / num_issues").Update(new(Milestone)); err != nil {
sess.Incr("num_issues")
if issue.IsClosed {
sess.Incr("num_closed_issues")
}
if _, err := sess.ID(issue.MilestoneID).
SetExpr("completeness", "num_closed_issues * 100 / num_issues").
NoAutoTime().
Update(new(Milestone)); err != nil {
return err
}
}
@@ -74,72 +102,73 @@ func insertIssue(sess *xorm.Session, issue *Issue, labelIDs []int64) error {
return nil
}
// InsertComment inserted a comment
func InsertComment(comment *Comment) error {
// InsertIssueComments inserts many comments of issues.
func InsertIssueComments(comments []*Comment) error {
if len(comments) == 0 {
return nil
}
var issueIDs = make(map[int64]bool)
for _, comment := range comments {
issueIDs[comment.IssueID] = true
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if _, err := sess.NoAutoTime().Insert(comment); err != nil {
if _, err := sess.NoAutoTime().Insert(comments); err != nil {
return err
}
if _, err := sess.ID(comment.IssueID).Incr("num_comments").Update(new(Issue)); err != nil {
return err
for issueID := range issueIDs {
if _, err := sess.Exec("UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ?) WHERE id = ?", issueID, issueID); err != nil {
return err
}
}
return sess.Commit()
}
// InsertPullRequest inserted a pull request
func InsertPullRequest(pr *PullRequest, labelIDs []int64) error {
// InsertPullRequests inserted pull requests
func InsertPullRequests(prs ...*PullRequest) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := insertIssue(sess, pr.Issue, labelIDs); err != nil {
return err
}
pr.IssueID = pr.Issue.ID
if _, err := sess.NoAutoTime().Insert(pr); err != nil {
return err
for _, pr := range prs {
if err := insertIssue(sess, pr.Issue); err != nil {
return err
}
pr.IssueID = pr.Issue.ID
if _, err := sess.NoAutoTime().Insert(pr); err != nil {
return err
}
}
return sess.Commit()
}
// MigrateRelease migrates release
func MigrateRelease(rel *Release) error {
// InsertReleases migrates release
func InsertReleases(rels ...*Release) error {
sess := x.NewSession()
if err := sess.Begin(); err != nil {
return err
}
var oriRel = Release{
RepoID: rel.RepoID,
TagName: rel.TagName,
}
exist, err := sess.Get(&oriRel)
if err != nil {
return err
}
if !exist {
for _, rel := range rels {
if _, err := sess.NoAutoTime().Insert(rel); err != nil {
return err
}
} else {
rel.ID = oriRel.ID
if _, err := sess.ID(rel.ID).Cols("target, title, note, is_tag, num_commits").Update(rel); err != nil {
for i := 0; i < len(rel.Attachments); i++ {
rel.Attachments[i].ReleaseID = rel.ID
}
if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil {
return err
}
}
for i := 0; i < len(rel.Attachments); i++ {
rel.Attachments[i].ReleaseID = rel.ID
}
if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil {
return err
}
return sess.Commit()
}
+100 -6
View File
@@ -13,6 +13,7 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"strings"
"time"
@@ -228,6 +229,12 @@ var migrations = []Migration{
// v86 -> v87
NewMigration("add http method to webhook", addHTTPMethodToWebhook),
// v87 -> v88
NewMigration("add avatar field to repository", addAvatarFieldToRepository),
// v88 -> v89
NewMigration("add commit status context field to commit_status", addCommitStatusContext),
// v89 -> v90
NewMigration("add original author/url migration info to issues, comments, and repo ", addOriginalMigrationInfo),
// v90 -> v91
NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories),
}
@@ -282,11 +289,98 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
if tableName == "" || len(columnNames) == 0 {
return nil
}
// TODO: This will not work if there are foreign keys
switch {
case setting.UseSQLite3:
log.Warn("Unable to drop columns in SQLite")
case setting.UseMySQL, setting.UseTiDB, setting.UsePostgreSQL:
// First drop the indexes on the columns
res, errIndex := sess.Query(fmt.Sprintf("PRAGMA index_list(`%s`)", tableName))
if errIndex != nil {
return errIndex
}
for _, row := range res {
indexName := row["name"]
indexRes, err := sess.Query(fmt.Sprintf("PRAGMA index_info(`%s`)", indexName))
if err != nil {
return err
}
if len(indexRes) != 1 {
continue
}
indexColumn := string(indexRes[0]["name"])
for _, name := range columnNames {
if name == indexColumn {
_, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s`", indexName))
if err != nil {
return err
}
}
}
}
// Here we need to get the columns from the original table
sql := fmt.Sprintf("SELECT sql FROM sqlite_master WHERE tbl_name='%s' and type='table'", tableName)
res, err := sess.Query(sql)
if err != nil {
return err
}
tableSQL := string(res[0]["sql"])
tableSQL = tableSQL[strings.Index(tableSQL, "("):]
for _, name := range columnNames {
tableSQL = regexp.MustCompile(regexp.QuoteMeta("`"+name+"`")+"[^`,)]*[,)]").ReplaceAllString(tableSQL, "")
}
columns := regexp.MustCompile("`([^`]*)`").FindAllString(tableSQL, -1)
tableSQL = fmt.Sprintf("CREATE TABLE `new_%s_new` ", tableName) + tableSQL
if _, err := sess.Exec(tableSQL); err != nil {
return err
}
// Now restore the data
columnsSeparated := strings.Join(columns, ",")
insertSQL := fmt.Sprintf("INSERT INTO `new_%s_new` (%s) SELECT %s FROM %s", tableName, columnsSeparated, columnsSeparated, tableName)
if _, err := sess.Exec(insertSQL); err != nil {
return err
}
// Now drop the old table
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
return err
}
// Rename the table
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `new_%s_new` RENAME TO `%s`", tableName, tableName)); err != nil {
return err
}
case setting.UsePostgreSQL:
cols := ""
for _, col := range columnNames {
if cols != "" {
cols += ", "
}
cols += "DROP COLUMN `" + col + "` CASCADE"
}
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
}
case setting.UseMySQL, setting.UseTiDB:
// Drop indexes on columns first
sql := fmt.Sprintf("SHOW INDEX FROM %s WHERE column_name IN ('%s')", tableName, strings.Join(columnNames, "','"))
res, err := sess.Query(sql)
if err != nil {
return err
}
for _, index := range res {
indexName := index["column_name"]
_, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s` ON `%s`", indexName, tableName))
if err != nil {
return err
}
}
// Now drop the columns
cols := ""
for _, col := range columnNames {
if cols != "" {
@@ -399,7 +493,7 @@ func trimCommitActionAppURLPrefix(x *xorm.Engine) error {
return fmt.Errorf("marshal action content[%d]: %v", actID, err)
}
if _, err = sess.Id(actID).Update(&Action{
if _, err = sess.ID(actID).Update(&Action{
Content: string(p),
}); err != nil {
return fmt.Errorf("update action[%d]: %v", actID, err)
@@ -503,7 +597,7 @@ func attachmentRefactor(x *xorm.Engine) error {
// Update database first because this is where error happens the most often.
for _, attach := range attachments {
if _, err = sess.Id(attach.ID).Update(attach); err != nil {
if _, err = sess.ID(attach.ID).Update(attach); err != nil {
return err
}
@@ -581,7 +675,7 @@ func renamePullRequestFields(x *xorm.Engine) (err error) {
if pull.Index == 0 {
continue
}
if _, err = sess.Id(pull.ID).Update(pull); err != nil {
if _, err = sess.ID(pull.ID).Update(pull); err != nil {
return err
}
}
@@ -661,7 +755,7 @@ func generateOrgRandsAndSalt(x *xorm.Engine) (err error) {
if org.Salt, err = generate.GetRandomString(10); err != nil {
return err
}
if _, err = sess.Id(org.ID).Update(org); err != nil {
if _, err = sess.ID(org.ID).Update(org); err != nil {
return err
}
}
+2 -2
View File
@@ -58,13 +58,13 @@ func convertIntervalToDuration(x *xorm.Engine) (err error) {
return fmt.Errorf("Query repositories: %v", err)
}
for _, mirror := range mirrors {
mirror.Interval = mirror.Interval * time.Hour
mirror.Interval *= time.Hour
if mirror.Interval < setting.Mirror.MinInterval {
log.Info("Mirror interval less than Mirror.MinInterval, setting default interval: repo id %v", mirror.RepoID)
mirror.Interval = setting.Mirror.DefaultInterval
}
log.Debug("Mirror interval set to %v for repo id %v", mirror.Interval, mirror.RepoID)
_, err := sess.Id(mirror.ID).Cols("interval").Update(mirror)
_, err := sess.ID(mirror.ID).Cols("interval").Update(mirror)
if err != nil {
return fmt.Errorf("update mirror interval failed: %v", err)
}
+1 -1
View File
@@ -8,8 +8,8 @@ import (
"fmt"
"time"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"xorm.io/core"
)
func addLoginSourceSyncEnabledColumn(x *xorm.Engine) error {
+1 -1
View File
@@ -9,8 +9,8 @@ import (
"code.gitea.io/gitea/models"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"xorm.io/core"
)
func removeCommitsUnitType(x *xorm.Engine) (err error) {
+2 -2
View File
@@ -83,7 +83,7 @@ func addMultipleAssignees(x *xorm.Engine) error {
return err
}
allIssues := []Issue{}
allIssues := []*Issue{}
if err := sess.Find(&allIssues); err != nil {
return err
}
@@ -104,7 +104,7 @@ func addMultipleAssignees(x *xorm.Engine) error {
return err
}
allAssignementComments := []Comment{}
allAssignementComments := []*Comment{}
if err := sess.Where("type = ?", 9).Find(&allAssignementComments); err != nil {
return err
}
+1 -1
View File
@@ -5,8 +5,8 @@
package migrations
import (
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
"xorm.io/builder"
)
func clearNonusedData(x *xorm.Engine) error {
+4 -1
View File
@@ -10,8 +10,8 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"xorm.io/core"
)
func renameRepoIsBareToIsEmpty(x *xorm.Engine) error {
@@ -48,6 +48,9 @@ func renameRepoIsBareToIsEmpty(x *xorm.Engine) error {
if len(indexes) >= 1 {
_, err = sess.Exec("DROP INDEX IDX_repository_is_bare ON repository")
if err != nil {
return fmt.Errorf("Drop index failed: %v", err)
}
}
} else {
_, err = sess.Exec("DROP INDEX IDX_repository_is_bare ON repository")
+4 -1
View File
@@ -7,8 +7,8 @@ package migrations
import (
"fmt"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"xorm.io/core"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/generate"
@@ -58,6 +58,9 @@ func hashAppToken(x *xorm.Engine) error {
if len(indexes) >= 1 {
_, err = sess.Exec("DROP INDEX UQE_access_token_sha1 ON access_token")
if err != nil {
return err
}
}
} else {
_, err = sess.Exec("DROP INDEX UQE_access_token_sha1 ON access_token")
+5 -12
View File
@@ -8,18 +8,11 @@ import (
"github.com/go-xorm/xorm"
)
func addTeamIncludesAllRepositories(x *xorm.Engine) error {
type Team struct {
ID int64 `xorm:"pk autoincr"`
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
func addAvatarFieldToRepository(x *xorm.Engine) error {
type Repository struct {
// ID(10-20)-md5(32) - must fit into 64 symbols
Avatar string `xorm:"VARCHAR(64)"`
}
if err := x.Sync2(new(Team)); err != nil {
return err
}
_, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?",
true, "Owners")
return err
return x.Sync2(new(Repository))
}
+66
View File
@@ -0,0 +1,66 @@
// 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 migrations
import (
"crypto/sha1"
"fmt"
"github.com/go-xorm/xorm"
)
func hashContext(context string) string {
return fmt.Sprintf("%x", sha1.Sum([]byte(context)))
}
func addCommitStatusContext(x *xorm.Engine) error {
type CommitStatus struct {
ID int64 `xorm:"pk autoincr"`
ContextHash string `xorm:"char(40) index"`
Context string `xorm:"TEXT"`
}
if err := x.Sync2(new(CommitStatus)); err != nil {
return err
}
sess := x.NewSession()
defer sess.Close()
var start = 0
for {
var statuses = make([]*CommitStatus, 0, 100)
err := sess.OrderBy("id").Limit(100, start).Find(&statuses)
if err != nil {
return err
}
if len(statuses) == 0 {
break
}
if err = sess.Begin(); err != nil {
return err
}
for _, status := range statuses {
status.ContextHash = hashContext(status.Context)
if _, err := sess.ID(status.ID).Cols("context_hash").Update(status); err != nil {
return err
}
}
if err := sess.Commit(); err != nil {
return err
}
if len(statuses) < 100 {
break
}
start += len(statuses)
}
return nil
}
+36
View File
@@ -0,0 +1,36 @@
// 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 migrations
import "github.com/go-xorm/xorm"
func addOriginalMigrationInfo(x *xorm.Engine) error {
// Issue see models/issue.go
type Issue struct {
OriginalAuthor string
OriginalAuthorID int64
}
if err := x.Sync2(new(Issue)); err != nil {
return err
}
// Issue see models/issue_comment.go
type Comment struct {
OriginalAuthor string
OriginalAuthorID int64
}
if err := x.Sync2(new(Comment)); err != nil {
return err
}
// Issue see models/repo.go
type Repository struct {
OriginalURL string
}
return x.Sync2(new(Repository))
}
+25
View File
@@ -0,0 +1,25 @@
// 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 migrations
import (
"github.com/go-xorm/xorm"
)
func addTeamIncludesAllRepositories(x *xorm.Engine) error {
type Team struct {
ID int64 `xorm:"pk autoincr"`
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
}
if err := x.Sync2(new(Team)); err != nil {
return err
}
_, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?",
true, "Owners")
return err
}
+25 -12
View File
@@ -14,14 +14,14 @@ import (
"path"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
// Needed for the MySQL driver
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"xorm.io/core"
// Needed for the Postgresql driver
_ "github.com/lib/pq"
@@ -48,6 +48,7 @@ type Engine interface {
Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *xorm.Session
SQL(interface{}, ...interface{}) *xorm.Session
Where(interface{}, ...interface{}) *xorm.Session
Asc(colNames ...string) *xorm.Session
}
var (
@@ -60,8 +61,8 @@ var (
// DbCfg holds the database settings
DbCfg struct {
Type, Host, Name, User, Passwd, Path, SSLMode string
Timeout int
Type, Host, Name, User, Passwd, Path, SSLMode, Charset string
Timeout int
}
// EnableSQLite3 use SQLite3
@@ -161,6 +162,7 @@ func LoadConfigs() {
DbCfg.Passwd = sec.Key("PASSWD").String()
}
DbCfg.SSLMode = sec.Key("SSL_MODE").MustString("disable")
DbCfg.Charset = sec.Key("CHARSET").In("utf8", []string{"utf8", "utf8mb4"})
DbCfg.Path = sec.Key("PATH").MustString(filepath.Join(setting.AppDataPath, "gitea.db"))
DbCfg.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500)
}
@@ -180,14 +182,14 @@ func parsePostgreSQLHostPort(info string) (string, string) {
return host, port
}
func getPostgreSQLConnectionString(DBHost, DBUser, DBPasswd, DBName, DBParam, DBSSLMode string) (connStr string) {
host, port := parsePostgreSQLHostPort(DBHost)
func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbParam, dbsslMode string) (connStr string) {
host, port := parsePostgreSQLHostPort(dbHost)
if host[0] == '/' { // looks like a unix socket
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
url.PathEscape(DBUser), url.PathEscape(DBPasswd), port, DBName, DBParam, DBSSLMode, host)
url.PathEscape(dbUser), url.PathEscape(dbPasswd), port, dbName, dbParam, dbsslMode, host)
} else {
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
url.PathEscape(DBUser), url.PathEscape(DBPasswd), host, port, DBName, DBParam, DBSSLMode)
url.PathEscape(dbUser), url.PathEscape(dbPasswd), host, port, dbName, dbParam, dbsslMode)
}
return
}
@@ -223,8 +225,8 @@ func getEngine() (*xorm.Engine, error) {
if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL
tls = "false"
}
connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=utf8&parseTime=true&tls=%s",
DbCfg.User, DbCfg.Passwd, connType, DbCfg.Host, DbCfg.Name, Param, tls)
connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s",
DbCfg.User, DbCfg.Passwd, connType, DbCfg.Host, DbCfg.Name, Param, DbCfg.Charset, tls)
case "postgres":
connStr = getPostgreSQLConnectionString(DbCfg.Host, DbCfg.User, DbCfg.Passwd, DbCfg.Name, Param, DbCfg.SSLMode)
case "mssql":
@@ -261,7 +263,7 @@ func NewTestEngine(x *xorm.Engine) (err error) {
}
x.SetMapper(core.GonicMapper{})
x.SetLogger(log.XORMLogger)
x.SetLogger(NewXORMLogger(!setting.ProdMode))
x.ShowSQL(!setting.ProdMode)
return x.StoreEngine("InnoDB").Sync2(tables...)
}
@@ -276,8 +278,13 @@ func SetEngine() (err error) {
x.SetMapper(core.GonicMapper{})
// WARNING: for serv command, MUST remove the output to os.stdout,
// so use log file to instead print to stdout.
x.SetLogger(log.XORMLogger)
x.SetLogger(NewXORMLogger(setting.LogSQL))
x.ShowSQL(setting.LogSQL)
if DbCfg.Type == "mysql" {
x.SetMaxIdleConns(0)
x.SetConnMaxLifetime(3 * time.Second)
}
return nil
}
@@ -361,3 +368,9 @@ func DumpDatabase(filePath string, dbType string) error {
}
return x.DumpTablesToFile(tbs, filePath)
}
// MaxBatchInsertSize returns the table's max batch insert size
func MaxBatchInsertSize(bean interface{}) int {
t := x.TableInfo(bean)
return 999 / len(t.ColumnsSeq())
}
+4 -1
View File
@@ -119,7 +119,10 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
}
}
issue.loadRepo(e)
err = issue.loadRepo(e)
if err != nil {
return err
}
for _, watch := range watches {
issue.Repo.Units = nil
+4 -1
View File
@@ -106,7 +106,10 @@ func InitOAuth2() error {
for _, source := range loginSources {
oAuth2Config := source.OAuth2()
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
err := oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
if err != nil {
return err
}
}
return nil
}
+5 -2
View File
@@ -142,6 +142,9 @@ func GetOAuth2ApplicationByID(id int64) (app *OAuth2Application, err error) {
func getOAuth2ApplicationByID(e Engine, id int64) (app *OAuth2Application, err error) {
app = new(OAuth2Application)
has, err := e.ID(id).Get(app)
if err != nil {
return nil, err
}
if !has {
return nil, ErrOAuthApplicationNotFound{ID: id}
}
@@ -295,10 +298,10 @@ func (code *OAuth2AuthorizationCode) invalidate(e Engine) error {
// ValidateCodeChallenge validates the given verifier against the saved code challenge. This is part of the PKCE implementation.
func (code *OAuth2AuthorizationCode) ValidateCodeChallenge(verifier string) bool {
return code.validateCodeChallenge(x, verifier)
return code.validateCodeChallenge(verifier)
}
func (code *OAuth2AuthorizationCode) validateCodeChallenge(e Engine, verifier string) bool {
func (code *OAuth2AuthorizationCode) validateCodeChallenge(verifier string) bool {
switch code.CodeChallengeMethod {
case "S256":
// base64url(SHA256(verifier)) see https://tools.ietf.org/html/rfc7636#section-4.6
+13 -10
View File
@@ -15,8 +15,8 @@ import (
"code.gitea.io/gitea/modules/structs"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
"xorm.io/builder"
)
var (
@@ -166,8 +166,8 @@ func CreateOrganization(org, owner *User) (err error) {
}
// insert units for team
var units = make([]TeamUnit, 0, len(allRepUnitTypes))
for _, tp := range allRepUnitTypes {
var units = make([]TeamUnit, 0, len(AllRepoUnitTypes))
for _, tp := range AllRepoUnitTypes {
units = append(units, TeamUnit{
OrgID: org.ID,
TeamID: t.ID,
@@ -176,7 +176,9 @@ func CreateOrganization(org, owner *User) (err error) {
}
if _, err = sess.Insert(&units); err != nil {
sess.Rollback()
if err := sess.Rollback(); err != nil {
log.Error("CreateOrganization: sess.Rollback: %v", err)
}
return err
}
@@ -380,10 +382,7 @@ func HasOrgVisible(org *User, user *User) bool {
func hasOrgVisible(e Engine, org *User, user *User) bool {
// Not SignedUser
if user == nil {
if org.Visibility == structs.VisibleTypePublic {
return true
}
return false
return org.Visibility == structs.VisibleTypePublic
}
if user.IsAdmin {
@@ -489,10 +488,14 @@ func AddOrgUser(orgID, uid int64) error {
}
if _, err := sess.Insert(ou); err != nil {
sess.Rollback()
if err := sess.Rollback(); err != nil {
log.Error("AddOrgUser: sess.Rollback: %v", err)
}
return err
} else if _, err = sess.Exec("UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil {
sess.Rollback()
if err := sess.Rollback(); err != nil {
log.Error("AddOrgUser: sess.Rollback: %v", err)
}
return err
}
+24 -7
View File
@@ -313,7 +313,8 @@ func NewTeam(t *Team) (err error) {
has, err := x.ID(t.OrgID).Get(new(User))
if err != nil {
return err
} else if !has {
}
if !has {
return ErrOrgNotExist{t.OrgID, ""}
}
@@ -324,7 +325,8 @@ func NewTeam(t *Team) (err error) {
Get(new(Team))
if err != nil {
return err
} else if has {
}
if has {
return ErrTeamAlreadyExist{t.OrgID, t.LowerName}
}
@@ -335,7 +337,10 @@ func NewTeam(t *Team) (err error) {
}
if _, err = sess.Insert(t); err != nil {
sess.Rollback()
errRollback := sess.Rollback()
if errRollback != nil {
log.Error("NewTeam sess.Rollback: %v", errRollback)
}
return err
}
@@ -345,7 +350,10 @@ func NewTeam(t *Team) (err error) {
unit.TeamID = t.ID
}
if _, err = sess.Insert(&t.Units); err != nil {
sess.Rollback()
errRollback := sess.Rollback()
if errRollback != nil {
log.Error("NewTeam sess.Rollback: %v", errRollback)
}
return err
}
}
@@ -360,7 +368,10 @@ func NewTeam(t *Team) (err error) {
// Update organization number of teams.
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
sess.Rollback()
errRollback := sess.Rollback()
if errRollback != nil {
log.Error("NewTeam sess.Rollback: %v", errRollback)
}
return err
}
return sess.Commit()
@@ -446,7 +457,10 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
}
if _, err = sess.Insert(&t.Units); err != nil {
sess.Rollback()
errRollback := sess.Rollback()
if errRollback != nil {
log.Error("UpdateTeam sess.Rollback: %v", errRollback)
}
return err
}
}
@@ -883,7 +897,10 @@ func UpdateTeamUnits(team *Team, units []TeamUnit) (err error) {
}
if _, err = sess.Insert(units); err != nil {
sess.Rollback()
errRollback := sess.Rollback()
if errRollback != nil {
log.Error("UpdateTeamUnits sess.Rollback: %v", errRollback)
}
return err
}
+3 -3
View File
@@ -242,10 +242,10 @@ func TestGetOrgByName(t *testing.T) {
assert.EqualValues(t, 3, org.ID)
assert.Equal(t, "user3", org.Name)
org, err = GetOrgByName("user2") // user2 is an individual
_, err = GetOrgByName("user2") // user2 is an individual
assert.True(t, IsErrOrgNotExist(err))
org, err = GetOrgByName("") // corner case
_, err = GetOrgByName("") // corner case
assert.True(t, IsErrOrgNotExist(err))
}
@@ -499,7 +499,7 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) {
func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
testSuccess := func(userID, page, pageSize int64, expectedRepoIDs []int64) {
testSuccess := func(userID, _, pageSize int64, expectedRepoIDs []int64) {
env, err := org.AccessibleReposEnv(userID)
assert.NoError(t, err)
repoIDs, err := env.RepoIDs(1, 100)
+104 -360
View File
@@ -7,7 +7,6 @@ package models
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
@@ -18,7 +17,6 @@ import (
"time"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
@@ -191,37 +189,15 @@ func (pr *PullRequest) apiFormat(e Engine) *api.PullRequest {
return nil
}
}
if baseBranch, err = pr.BaseRepo.GetBranch(pr.BaseBranch); err != nil {
return nil
}
if baseCommit, err = baseBranch.GetCommit(); err != nil {
return nil
}
if headBranch, err = pr.HeadRepo.GetBranch(pr.HeadBranch); err != nil {
return nil
}
if headCommit, err = headBranch.GetCommit(); err != nil {
return nil
}
apiBaseBranchInfo := &api.PRBranchInfo{
Name: pr.BaseBranch,
Ref: pr.BaseBranch,
Sha: baseCommit.ID.String(),
RepoID: pr.BaseRepoID,
Repository: pr.BaseRepo.innerAPIFormat(e, AccessModeNone, false),
}
apiHeadBranchInfo := &api.PRBranchInfo{
Name: pr.HeadBranch,
Ref: pr.HeadBranch,
Sha: headCommit.ID.String(),
RepoID: pr.HeadRepoID,
Repository: pr.HeadRepo.innerAPIFormat(e, AccessModeNone, false),
}
pr.Issue.loadRepo(e)
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,
@@ -236,13 +212,68 @@ func (pr *PullRequest) apiFormat(e Engine) *api.PullRequest {
DiffURL: pr.Issue.DiffURL(),
PatchURL: pr.Issue.PatchURL(),
HasMerged: pr.HasMerged,
Base: apiBaseBranchInfo,
Head: apiHeadBranchInfo,
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()
@@ -361,324 +392,8 @@ func (pr *PullRequest) CheckUserAllowedToMerge(doer *User) (err error) {
return nil
}
func getDiffTree(repoPath, baseBranch, headBranch string) (string, error) {
getDiffTreeFromBranch := func(repoPath, baseBranch, headBranch string) (string, error) {
var stdout, stderr string
// Compute the diff-tree for sparse-checkout
// The branch argument must be enclosed with double-quotes ("") in case it contains slashes (e.g "feature/test")
stdout, stderr, err := process.GetManager().ExecDir(-1, repoPath,
fmt.Sprintf("PullRequest.Merge (git diff-tree): %s", repoPath),
"git", "diff-tree", "--no-commit-id", "--name-only", "-r", "--root", baseBranch, headBranch)
if err != nil {
return "", fmt.Errorf("git diff-tree [%s base:%s head:%s]: %s", repoPath, baseBranch, headBranch, stderr)
}
return stdout, nil
}
list, err := getDiffTreeFromBranch(repoPath, baseBranch, headBranch)
if err != nil {
return "", err
}
// Prefixing '/' for each entry, otherwise all files with the same name in subdirectories would be matched.
out := bytes.Buffer{}
scanner := bufio.NewScanner(strings.NewReader(list))
for scanner.Scan() {
fmt.Fprintf(&out, "/%s\n", scanner.Text())
}
return out.String(), nil
}
// Merge merges pull request to base repository.
// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle MergeStyle, message string) (err error) {
if err = pr.GetHeadRepo(); err != nil {
return fmt.Errorf("GetHeadRepo: %v", err)
} else if err = pr.GetBaseRepo(); err != nil {
return fmt.Errorf("GetBaseRepo: %v", err)
}
prUnit, err := pr.BaseRepo.GetUnit(UnitTypePullRequests)
if err != nil {
return err
}
prConfig := prUnit.PullRequestsConfig()
if err := pr.CheckUserAllowedToMerge(doer); err != nil {
return fmt.Errorf("CheckUserAllowedToMerge: %v", err)
}
// Check if merge style is correct and allowed
if !prConfig.IsMergeStyleAllowed(mergeStyle) {
return ErrInvalidMergeStyle{pr.BaseRepo.ID, mergeStyle}
}
defer func() {
go HookQueue.Add(pr.BaseRepo.ID)
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false)
}()
// Clone base repo.
tmpBasePath, err := CreateTemporaryPath("merge")
if err != nil {
return err
}
defer RemoveTemporaryPath(tmpBasePath)
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
if err := git.Clone(baseGitRepo.Path, tmpBasePath, git.CloneRepoOptions{
Shared: true,
NoCheckout: true,
Branch: pr.BaseBranch,
}); err != nil {
return fmt.Errorf("git clone: %v", err)
}
remoteRepoName := "head_repo"
// Add head repo remote.
addCacheRepo := func(staging, cache string) error {
p := filepath.Join(staging, ".git", "objects", "info", "alternates")
f, err := os.OpenFile(p, os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return err
}
defer f.Close()
data := filepath.Join(cache, "objects")
if _, err := fmt.Fprintln(f, data); err != nil {
return err
}
return nil
}
if err := addCacheRepo(tmpBasePath, headRepoPath); err != nil {
return fmt.Errorf("addCacheRepo [%s -> %s]: %v", headRepoPath, tmpBasePath, err)
}
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath),
"git", "remote", "add", remoteRepoName, headRepoPath); err != nil {
return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
// Fetch head branch
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath),
"git", "fetch", remoteRepoName); err != nil {
return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
trackingBranch := path.Join(remoteRepoName, pr.HeadBranch)
stagingBranch := fmt.Sprintf("%s_%s", remoteRepoName, pr.HeadBranch)
// Enable sparse-checkout
sparseCheckoutList, err := getDiffTree(tmpBasePath, pr.BaseBranch, trackingBranch)
if err != nil {
return fmt.Errorf("getDiffTree: %v", err)
}
infoPath := filepath.Join(tmpBasePath, ".git", "info")
if err := os.MkdirAll(infoPath, 0700); err != nil {
return fmt.Errorf("creating directory failed [%s]: %v", infoPath, err)
}
sparseCheckoutListPath := filepath.Join(infoPath, "sparse-checkout")
if err := ioutil.WriteFile(sparseCheckoutListPath, []byte(sparseCheckoutList), 0600); err != nil {
return fmt.Errorf("Writing sparse-checkout file to %s: %v", sparseCheckoutListPath, err)
}
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git config): %s", tmpBasePath),
"git", "config", "--local", "core.sparseCheckout", "true"); err != nil {
return fmt.Errorf("git config [core.sparsecheckout -> true]: %v", stderr)
}
// Read base branch index
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git read-tree): %s", tmpBasePath),
"git", "read-tree", "HEAD"); err != nil {
return fmt.Errorf("git read-tree HEAD: %s", stderr)
}
// Merge commits.
switch mergeStyle {
case MergeStyleMerge:
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath),
"git", "merge", "--no-ff", "--no-commit", trackingBranch); err != nil {
return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr)
}
sig := doer.NewGitSig()
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath),
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
"-m", message); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr)
}
case MergeStyleRebase:
// Checkout head branch
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
"git", "checkout", "-b", stagingBranch, trackingBranch); err != nil {
return fmt.Errorf("git checkout: %s", stderr)
}
// Rebase before merging
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath),
"git", "rebase", "-q", pr.BaseBranch); err != nil {
return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
// Checkout base branch again
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
"git", "checkout", pr.BaseBranch); err != nil {
return fmt.Errorf("git checkout: %s", stderr)
}
// Merge fast forward
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath),
"git", "merge", "--ff-only", "-q", stagingBranch); err != nil {
return fmt.Errorf("git merge --ff-only [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
case MergeStyleRebaseMerge:
// Checkout head branch
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
"git", "checkout", "-b", stagingBranch, trackingBranch); err != nil {
return fmt.Errorf("git checkout: %s", stderr)
}
// Rebase before merging
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath),
"git", "rebase", "-q", pr.BaseBranch); err != nil {
return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
// Checkout base branch again
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
"git", "checkout", pr.BaseBranch); err != nil {
return fmt.Errorf("git checkout: %s", stderr)
}
// Prepare merge with commit
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath),
"git", "merge", "--no-ff", "--no-commit", "-q", stagingBranch); err != nil {
return fmt.Errorf("git merge --no-ff [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
// Set custom message and author and create merge commit
sig := doer.NewGitSig()
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git commit): %s", tmpBasePath),
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
"-m", message); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr)
}
case MergeStyleSquash:
// Merge with squash
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath),
"git", "merge", "-q", "--squash", trackingBranch); err != nil {
return fmt.Errorf("git merge --squash [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
}
sig := pr.Issue.Poster.NewGitSig()
if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath),
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
"-m", message); err != nil {
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr)
}
default:
return ErrInvalidMergeStyle{pr.BaseRepo.ID, mergeStyle}
}
env := PushingEnvironment(doer, pr.BaseRepo)
// Push back to upstream.
if _, stderr, err := process.GetManager().ExecDirEnv(-1, tmpBasePath,
fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath),
env, "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
return fmt.Errorf("git push: %s", stderr)
}
pr.MergedCommitID, err = baseGitRepo.GetBranchCommitID(pr.BaseBranch)
if err != nil {
return fmt.Errorf("GetBranchCommit: %v", err)
}
pr.MergedUnix = util.TimeStampNow()
pr.Merger = doer
pr.MergerID = doer.ID
if err = pr.setMerged(); err != nil {
log.Error("setMerged [%d]: %v", pr.ID, err)
}
if err = MergePullRequestAction(doer, pr.Issue.Repo, pr.Issue); err != nil {
log.Error("MergePullRequestAction [%d]: %v", pr.ID, err)
}
// Reset cached commit count
cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))
// Reload pull request information.
if err = pr.LoadAttributes(); err != nil {
log.Error("LoadAttributes: %v", err)
return nil
}
mode, _ := AccessLevel(doer, pr.Issue.Repo)
if err = PrepareWebhooks(pr.Issue.Repo, HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueClosed,
Index: pr.Index,
PullRequest: pr.APIFormat(),
Repository: pr.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error("PrepareWebhooks: %v", err)
} else {
go HookQueue.Add(pr.Issue.Repo.ID)
}
l, err := baseGitRepo.CommitsBetweenIDs(pr.MergedCommitID, pr.MergeBase)
if err != nil {
log.Error("CommitsBetweenIDs: %v", err)
return nil
}
// It is possible that head branch is not fully sync with base branch for merge commits,
// so we need to get latest head commit and append merge commit manually
// to avoid strange diff commits produced.
mergeCommit, err := baseGitRepo.GetBranchCommit(pr.BaseBranch)
if err != nil {
log.Error("GetBranchCommit: %v", err)
return nil
}
if mergeStyle == MergeStyleMerge {
l.PushFront(mergeCommit)
}
p := &api.PushPayload{
Ref: git.BranchPrefix + pr.BaseBranch,
Before: pr.MergeBase,
After: mergeCommit.ID.String(),
CompareURL: setting.AppURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
Commits: ListToPushCommits(l).ToAPIPayloadCommits(pr.BaseRepo.HTMLURL()),
Repo: pr.BaseRepo.APIFormat(mode),
Pusher: pr.HeadRepo.MustOwner().APIFormat(),
Sender: doer.APIFormat(),
}
if err = PrepareWebhooks(pr.BaseRepo, HookEventPush, p); err != nil {
log.Error("PrepareWebhooks: %v", err)
} else {
go HookQueue.Add(pr.BaseRepo.ID)
}
return nil
}
// setMerged sets a pull request to merged and closes the corresponding issue
func (pr *PullRequest) setMerged() (err error) {
// SetMerged sets a pull request to merged and closes the corresponding issue
func (pr *PullRequest) SetMerged() (err error) {
if pr.HasMerged {
return fmt.Errorf("PullRequest[%d] already merged", pr.Index)
}
@@ -745,7 +460,7 @@ func (pr *PullRequest) manuallyMerged() bool {
pr.Merger = merger
pr.MergerID = merger.ID
if err = pr.setMerged(); err != nil {
if err = pr.SetMerged(); err != nil {
log.Error("PullRequest[%d].setMerged : %v", pr.ID, err)
return false
}
@@ -774,7 +489,7 @@ func (pr *PullRequest) getMergeCommit() (*git.Commit, error) {
// Check if a pull request is merged into BaseBranch
_, stderr, err := process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("isMerged (git merge-base --is-ancestor): %d", pr.BaseRepo.ID),
[]string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
"git", "merge-base", "--is-ancestor", headFile, pr.BaseBranch)
git.GitExecutable, "merge-base", "--is-ancestor", headFile, pr.BaseBranch)
if err != nil {
// Errors are signaled by a non-zero status that is not 1
@@ -797,7 +512,7 @@ func (pr *PullRequest) getMergeCommit() (*git.Commit, error) {
// Get the commit from BaseBranch where the pull request got merged
mergeCommit, stderr, err := process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("isMerged (git rev-list --ancestry-path --merges --reverse): %d", pr.BaseRepo.ID),
[]string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
"git", "rev-list", "--ancestry-path", "--merges", "--reverse", cmd)
git.GitExecutable, "rev-list", "--ancestry-path", "--merges", "--reverse", cmd)
if err != nil {
return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %v %v", stderr, err)
} else if len(mergeCommit) < 40 {
@@ -859,7 +574,7 @@ func (pr *PullRequest) testPatch(e Engine) (err error) {
var stderr string
_, stderr, err = process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("testPatch (git read-tree): %d", pr.BaseRepo.ID),
[]string{"GIT_DIR=" + pr.BaseRepo.RepoPath(), "GIT_INDEX_FILE=" + indexTmpPath},
"git", "read-tree", pr.BaseBranch)
git.GitExecutable, "read-tree", pr.BaseBranch)
if err != nil {
return fmt.Errorf("git read-tree --index-output=%s %s: %v - %s", indexTmpPath, pr.BaseBranch, err, stderr)
}
@@ -879,7 +594,7 @@ func (pr *PullRequest) testPatch(e Engine) (err error) {
_, stderr, err = process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("testPatch (git apply --check): %d", pr.BaseRepo.ID),
[]string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
"git", args...)
git.GitExecutable, args...)
if err != nil {
for i := range patchConflicts {
if strings.Contains(stderr, patchConflicts[i]) {
@@ -892,7 +607,17 @@ func (pr *PullRequest) testPatch(e Engine) (err error) {
line := scanner.Text()
if strings.HasPrefix(line, prefix) {
pr.ConflictedFiles = append(pr.ConflictedFiles, strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0]))
var found bool
var filepath = strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0])
for _, f := range pr.ConflictedFiles {
if f == filepath {
found = true
break
}
}
if !found {
pr.ConflictedFiles = append(pr.ConflictedFiles, filepath)
}
}
// only list 10 conflicted files
if len(pr.ConflictedFiles) >= 10 {
@@ -1077,6 +802,20 @@ func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequ
Find(&prs)
}
// GetLatestPullRequestByHeadInfo returns the latest pull request (regardless of its status)
// by given head information (repo and branch).
func GetLatestPullRequestByHeadInfo(repoID int64, branch string) (*PullRequest, error) {
pr := new(PullRequest)
has, err := x.
Where("head_repo_id = ? AND head_branch = ?", repoID, branch).
OrderBy("id DESC").
Get(pr)
if !has {
return nil, err
}
return pr, err
}
// GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged
// by given base information (repo and branch).
func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) {
@@ -1163,7 +902,7 @@ func (pr *PullRequest) UpdatePatch() (err error) {
if err = pr.GetHeadRepo(); err != nil {
return fmt.Errorf("GetHeadRepo: %v", err)
} else if pr.HeadRepo == nil {
log.Trace("PullRequest[%d].UpdatePatch: ignored cruppted data", pr.ID)
log.Trace("PullRequest[%d].UpdatePatch: ignored corrupted data", pr.ID)
return nil
}
@@ -1182,10 +921,11 @@ func (pr *PullRequest) UpdatePatch() (err error) {
return fmt.Errorf("AddRemote: %v", err)
}
defer func() {
headGitRepo.RemoveRemote(tmpRemote)
if err := headGitRepo.RemoveRemote(tmpRemote); err != nil {
log.Error("UpdatePatch: RemoveRemote: %s", err)
}
}()
remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch
pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch)
pr.MergeBase, _, err = headGitRepo.GetMergeBase(tmpRemote, pr.BaseBranch, pr.HeadBranch)
if err != nil {
return fmt.Errorf("GetMergeBase: %v", err)
} else if err = pr.Update(); err != nil {
@@ -1221,7 +961,11 @@ func (pr *PullRequest) PushToBaseRepo() (err error) {
return fmt.Errorf("headGitRepo.AddRemote: %v", err)
}
// Make sure to remove the remote even if the push fails
defer headGitRepo.RemoveRemote(tmpRemoteName)
defer func() {
if err := headGitRepo.RemoveRemote(tmpRemoteName); err != nil {
log.Error("PushToBaseRepo: RemoveRemote: %s", err)
}
}()
headFile := pr.GetGitRefName()
+12 -4
View File
@@ -31,7 +31,15 @@ func TestPullRequest_LoadIssue(t *testing.T) {
assert.Equal(t, int64(2), pr.Issue.ID)
}
// TODO TestPullRequest_APIFormat
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())
@@ -94,7 +102,7 @@ func TestGetUnmergedPullRequest(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, int64(2), pr.ID)
pr, err = GetUnmergedPullRequest(1, 9223372036854775807, "branch1", "master")
_, err = GetUnmergedPullRequest(1, 9223372036854775807, "branch1", "master")
assert.Error(t, err)
assert.True(t, IsErrPullRequestNotExist(err))
}
@@ -128,7 +136,7 @@ func TestGetPullRequestByIndex(t *testing.T) {
assert.Equal(t, int64(1), pr.BaseRepoID)
assert.Equal(t, int64(2), pr.Index)
pr, err = GetPullRequestByIndex(9223372036854775807, 9223372036854775807)
_, err = GetPullRequestByIndex(9223372036854775807, 9223372036854775807)
assert.Error(t, err)
assert.True(t, IsErrPullRequestNotExist(err))
}
@@ -151,7 +159,7 @@ func TestGetPullRequestByIssueID(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, int64(2), pr.IssueID)
pr, err = GetPullRequestByIssueID(9223372036854775807)
_, err = GetPullRequestByIssueID(9223372036854775807)
assert.Error(t, err)
assert.True(t, IsErrPullRequestNotExist(err))
}
+10 -8
View File
@@ -16,7 +16,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"github.com/go-xorm/builder"
"xorm.io/builder"
)
// Release represents a release of repository.
@@ -50,12 +50,12 @@ func (r *Release) loadAttributes(e Engine) error {
}
}
if r.Publisher == nil {
r.Publisher, err = GetUserByID(r.PublisherID)
r.Publisher, err = getUserByID(e, r.PublisherID)
if err != nil {
return err
}
}
return GetReleaseAttachments(r)
return getReleaseAttachments(e, r)
}
// LoadAttributes load repo and publisher attributes for a release
@@ -316,6 +316,10 @@ func (s releaseMetaSearch) Less(i, j int) bool {
// GetReleaseAttachments retrieves the attachments for releases
func GetReleaseAttachments(rels ...*Release) (err error) {
return getReleaseAttachments(x, rels...)
}
func getReleaseAttachments(e Engine, rels ...*Release) (err error) {
if len(rels) == 0 {
return
}
@@ -335,11 +339,10 @@ func GetReleaseAttachments(rels ...*Release) (err error) {
sort.Sort(sortedRels)
// Select attachments
err = x.
err = e.
Asc("release_id").
In("release_id", sortedRels.ID).
Find(&attachments, Attachment{})
if err != nil {
return err
}
@@ -354,7 +357,6 @@ func GetReleaseAttachments(rels ...*Release) (err error) {
}
return
}
type releaseSorter struct {
@@ -432,7 +434,7 @@ func DeleteReleaseByID(id int64, u *User, delTag bool) error {
if delTag {
_, stderr, err := process.GetManager().ExecDir(-1, repo.RepoPath(),
fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID),
"git", "tag", "-d", rel.TagName)
git.GitExecutable, "tag", "-d", rel.TagName)
if err != nil && !strings.Contains(stderr, "not found") {
return fmt.Errorf("git tag -d: %v - %s", err, stderr)
}
@@ -493,7 +495,7 @@ func SyncReleasesWithTags(repo *Repository, gitRepo *git.Repository) error {
return fmt.Errorf("GetTagCommitID: %v", err)
}
if git.IsErrNotExist(err) || commitID != rel.Sha1 {
if err := pushUpdateDeleteTag(repo, gitRepo, rel.TagName); err != nil {
if err := pushUpdateDeleteTag(repo, rel.TagName); err != nil {
return fmt.Errorf("pushUpdateDeleteTag: %v", err)
}
} else {
+7 -6
View File
@@ -102,12 +102,13 @@ func TestRelease_MirrorDelete(t *testing.T) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
repoPath := RepoPath(user.Name, repo.Name)
migrationOptions := MigrateRepoOptions{
Name: "test_mirror",
Description: "Test mirror",
IsPrivate: false,
IsMirror: true,
RemoteAddr: repoPath,
Wiki: true,
Name: "test_mirror",
Description: "Test mirror",
IsPrivate: false,
IsMirror: true,
RemoteAddr: repoPath,
Wiki: true,
SyncReleasesWithTags: true,
}
mirror, err := MigrateRepository(user, user, migrationOptions)
assert.NoError(t, err)
+323 -130
View File
@@ -7,21 +7,25 @@ package models
import (
"bytes"
"crypto/md5"
"errors"
"fmt"
"html/template"
// Needed for jpeg support
_ "image/jpeg"
"image/png"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/modules/avatar"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
@@ -32,12 +36,10 @@ import (
"code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/util"
"github.com/Unknwon/cae/zip"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
version "github.com/mcuadros/go-version"
ini "gopkg.in/ini.v1"
"xorm.io/builder"
)
var repoWorkingPool = sync.NewExclusivePool()
@@ -67,8 +69,8 @@ var (
ItemsPerPage = 40
)
// LoadRepoConfig loads the repository config
func LoadRepoConfig() {
// loadRepoConfig loads the repository config
func loadRepoConfig() {
// Load .gitignore and license files and readme templates.
types := []string{"gitignore", "license", "readme", "label"}
typeFiles := make([][]string, 4)
@@ -119,45 +121,7 @@ func LoadRepoConfig() {
// NewRepoContext creates a new repository context
func NewRepoContext() {
zip.Verbose = false
// Check Git installation.
if _, err := exec.LookPath("git"); err != nil {
log.Fatal("Failed to test 'git' command: %v (forgotten install?)", err)
}
// Check Git version.
var err error
setting.Git.Version, err = git.BinVersion()
if err != nil {
log.Fatal("Failed to get Git version: %v", err)
}
log.Info("Git Version: %s", setting.Git.Version)
if version.Compare("1.7.1", setting.Git.Version, ">") {
log.Fatal("Gitea requires Git version greater or equal to 1.7.1")
}
// Git requires setting user.name and user.email in order to commit changes.
for configKey, defaultValue := range map[string]string{"user.name": "Gitea", "user.email": "gitea@fake.local"} {
if stdout, stderr, err := process.GetManager().Exec("NewRepoContext(get setting)", "git", "config", "--get", configKey); err != nil || strings.TrimSpace(stdout) == "" {
// ExitError indicates this config is not set
if _, ok := err.(*exec.ExitError); ok || strings.TrimSpace(stdout) == "" {
if _, stderr, gerr := process.GetManager().Exec("NewRepoContext(set "+configKey+")", "git", "config", "--global", configKey, defaultValue); gerr != nil {
log.Fatal("Failed to set git %s(%s): %s", configKey, gerr, stderr)
}
log.Info("Git config %s set to %s", configKey, defaultValue)
} else {
log.Fatal("Failed to get git %s(%s): %s", configKey, err, stderr)
}
}
}
// Set git some configurations.
if _, stderr, err := process.GetManager().Exec("NewRepoContext(git config --global core.quotepath false)",
"git", "config", "--global", "core.quotepath", "false"); err != nil {
log.Fatal("Failed to execute 'git config --global core.quotepath false': %s", stderr)
}
loadRepoConfig()
RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp"))
}
@@ -172,6 +136,7 @@ type Repository struct {
Name string `xorm:"INDEX NOT NULL"`
Description string
Website string
OriginalURL string
DefaultBranch string
NumWatches int
@@ -207,6 +172,9 @@ type Repository struct {
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
Topics []string `xorm:"TEXT JSON"`
// Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
Avatar string `xorm:"VARCHAR(64)"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
}
@@ -306,31 +274,64 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
parent = repo.BaseRepo.innerAPIFormat(e, mode, true)
}
}
hasIssues := false
if _, err := repo.getUnit(e, UnitTypeIssues); err == nil {
hasIssues = true
}
hasWiki := false
if _, err := repo.getUnit(e, UnitTypeWiki); err == nil {
hasWiki = true
}
hasPullRequests := false
ignoreWhitespaceConflicts := false
allowMerge := false
allowRebase := false
allowRebaseMerge := false
allowSquash := false
if unit, err := repo.getUnit(e, UnitTypePullRequests); err == nil {
config := unit.PullRequestsConfig()
hasPullRequests = true
ignoreWhitespaceConflicts = config.IgnoreWhitespaceConflicts
allowMerge = config.AllowMerge
allowRebase = config.AllowRebase
allowRebaseMerge = config.AllowRebaseMerge
allowSquash = config.AllowSquash
}
return &api.Repository{
ID: repo.ID,
Owner: repo.Owner.APIFormat(),
Name: repo.Name,
FullName: repo.FullName(),
Description: repo.Description,
Private: repo.IsPrivate,
Empty: repo.IsEmpty,
Archived: repo.IsArchived,
Size: int(repo.Size / 1024),
Fork: repo.IsFork,
Parent: parent,
Mirror: repo.IsMirror,
HTMLURL: repo.HTMLURL(),
SSHURL: cloneLink.SSH,
CloneURL: cloneLink.HTTPS,
Website: repo.Website,
Stars: repo.NumStars,
Forks: repo.NumForks,
Watchers: repo.NumWatches,
OpenIssues: repo.NumOpenIssues,
DefaultBranch: repo.DefaultBranch,
Created: repo.CreatedUnix.AsTime(),
Updated: repo.UpdatedUnix.AsTime(),
Permissions: permission,
ID: repo.ID,
Owner: repo.Owner.APIFormat(),
Name: repo.Name,
FullName: repo.FullName(),
Description: repo.Description,
Private: repo.IsPrivate,
Empty: repo.IsEmpty,
Archived: repo.IsArchived,
Size: int(repo.Size / 1024),
Fork: repo.IsFork,
Parent: parent,
Mirror: repo.IsMirror,
HTMLURL: repo.HTMLURL(),
SSHURL: cloneLink.SSH,
CloneURL: cloneLink.HTTPS,
Website: repo.Website,
Stars: repo.NumStars,
Forks: repo.NumForks,
Watchers: repo.NumWatches,
OpenIssues: repo.NumOpenIssues,
DefaultBranch: repo.DefaultBranch,
Created: repo.CreatedUnix.AsTime(),
Updated: repo.UpdatedUnix.AsTime(),
Permissions: permission,
HasIssues: hasIssues,
HasWiki: hasWiki,
HasPullRequests: hasPullRequests,
IgnoreWhitespaceConflicts: ignoreWhitespaceConflicts,
AllowMerge: allowMerge,
AllowRebase: allowRebase,
AllowRebaseMerge: allowRebaseMerge,
AllowSquash: allowSquash,
AvatarURL: repo.avatarLink(e),
}
}
@@ -377,10 +378,20 @@ func (repo *Repository) UnitEnabled(tp UnitType) bool {
return false
}
var (
// ErrUnitNotExist organization does not exist
ErrUnitNotExist = errors.New("Unit does not exist")
)
// ErrUnitTypeNotExist represents a "UnitTypeNotExist" kind of error.
type ErrUnitTypeNotExist struct {
UT UnitType
}
// IsErrUnitTypeNotExist checks if an error is a ErrUnitNotExist.
func IsErrUnitTypeNotExist(err error) bool {
_, ok := err.(ErrUnitTypeNotExist)
return ok
}
func (err ErrUnitTypeNotExist) Error() string {
return fmt.Sprintf("Unit type does not exist: %s", err.UT.String())
}
// MustGetUnit always returns a RepoUnit object
func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit {
@@ -404,6 +415,11 @@ func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit {
Type: tp,
Config: new(PullRequestsConfig),
}
} else if tp == UnitTypeIssues {
return &RepoUnit{
Type: tp,
Config: new(IssuesConfig),
}
}
return &RepoUnit{
Type: tp,
@@ -425,7 +441,7 @@ func (repo *Repository) getUnit(e Engine, tp UnitType) (*RepoUnit, error) {
return unit, nil
}
}
return nil, ErrUnitNotExist
return nil, ErrUnitTypeNotExist{tp}
}
func (repo *Repository) getOwner(e Engine) (err error) {
@@ -728,17 +744,6 @@ func (repo *Repository) getUsersWithAccessMode(e Engine, mode AccessMode) (_ []*
return users, nil
}
// NextIssueIndex returns the next issue index
// FIXME: should have a mutex to prevent producing same index for two issues that are created
// closely enough.
func (repo *Repository) NextIssueIndex() int64 {
return int64(repo.NumIssues+repo.NumPulls) + 1
}
var (
descPattern = regexp.MustCompile(`https?://\S+`)
)
// DescriptionHTML does special handles to description and return HTML string.
func (repo *Repository) DescriptionHTML() template.HTML {
desc, err := markup.RenderDescriptionHTML([]byte(repo.Description), repo.HTMLURL(), repo.ComposeMetas())
@@ -841,12 +846,14 @@ func (repo *Repository) CloneLink() (cl *CloneLink) {
// MigrateRepoOptions contains the repository migrate options
type MigrateRepoOptions struct {
Name string
Description string
IsPrivate bool
IsMirror bool
RemoteAddr string
Wiki bool // include wiki repository
Name string
Description string
OriginalURL string
IsPrivate bool
IsMirror bool
RemoteAddr string
Wiki bool // include wiki repository
SyncReleasesWithTags bool // sync releases from tags
}
/*
@@ -873,6 +880,7 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err
repo, err := CreateRepository(doer, u, CreateRepoOptions{
Name: opts.Name,
Description: opts.Description,
OriginalURL: opts.OriginalURL,
IsPrivate: opts.IsPrivate,
IsMirror: opts.IsMirror,
})
@@ -928,22 +936,18 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err
}
}
// Check if repository is empty.
_, stderr, err := com.ExecCmdDir(repoPath, "git", "log", "-1")
gitRepo, err := git.OpenRepository(repoPath)
if err != nil {
if strings.Contains(stderr, "fatal: bad default revision 'HEAD'") {
repo.IsEmpty = true
} else {
return repo, fmt.Errorf("check empty: %v - %s", err, stderr)
}
return repo, fmt.Errorf("OpenRepository: %v", err)
}
if !repo.IsEmpty {
repo.IsEmpty, err = gitRepo.IsEmpty()
if err != nil {
return repo, fmt.Errorf("git.IsEmpty: %v", err)
}
if opts.SyncReleasesWithTags && !repo.IsEmpty {
// Try to get HEAD branch and set it as default branch.
gitRepo, err := git.OpenRepository(repoPath)
if err != nil {
return repo, fmt.Errorf("OpenRepository: %v", err)
}
headBranch, err := gitRepo.GetHEADBranch()
if err != nil {
return repo, fmt.Errorf("GetHEADBranch: %v", err)
@@ -1068,20 +1072,20 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
var stderr string
if _, stderr, err = process.GetManager().ExecDir(-1,
tmpPath, fmt.Sprintf("initRepoCommit (git add): %s", tmpPath),
"git", "add", "--all"); err != nil {
git.GitExecutable, "add", "--all"); err != nil {
return fmt.Errorf("git add: %s", stderr)
}
if _, stderr, err = process.GetManager().ExecDir(-1,
tmpPath, fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath),
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
git.GitExecutable, "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
"-m", "Initial commit"); err != nil {
return fmt.Errorf("git commit: %s", stderr)
}
if _, stderr, err = process.GetManager().ExecDir(-1,
tmpPath, fmt.Sprintf("initRepoCommit (git push): %s", tmpPath),
"git", "push", "origin", "master"); err != nil {
git.GitExecutable, "push", "origin", "master"); err != nil {
return fmt.Errorf("git push: %s", stderr)
}
return nil
@@ -1091,6 +1095,7 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
type CreateRepoOptions struct {
Name string
Description string
OriginalURL string
Gitignores string
License string
Readme string
@@ -1127,7 +1132,7 @@ func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts
// Clone to temporary path and do the init commit.
_, stderr, err := process.GetManager().Exec(
fmt.Sprintf("initRepository(git clone): %s", repoPath),
"git", "clone", repoPath, tmpDir,
git.GitExecutable, "clone", repoPath, tmpDir,
)
if err != nil {
return fmt.Errorf("git clone: %v - %s", err, stderr)
@@ -1270,8 +1275,8 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
}
// insert units for repo
var units = make([]RepoUnit, 0, len(defaultRepoUnits))
for _, tp := range defaultRepoUnits {
var units = make([]RepoUnit, 0, len(DefaultRepoUnits))
for _, tp := range DefaultRepoUnits {
if tp == UnitTypeIssues {
units = append(units, RepoUnit{
RepoID: repo.ID,
@@ -1329,11 +1334,9 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
return fmt.Errorf("prepareWebhooks: %v", err)
}
go HookQueue.Add(repo.ID)
} else {
} else if err = repo.recalculateAccesses(e); err != nil {
// Organization automatically called this in addRepository method.
if err = repo.recalculateAccesses(e); err != nil {
return fmt.Errorf("recalculateAccesses: %v", err)
}
return fmt.Errorf("recalculateAccesses: %v", err)
}
if setting.Service.AutoWatchNewRepos {
@@ -1364,6 +1367,7 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err
Name: opts.Name,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
OriginalURL: opts.OriginalURL,
IsPrivate: opts.IsPrivate,
IsFsckEnabled: !opts.IsMirror,
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
@@ -1393,7 +1397,7 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err
_, stderr, err := process.GetManager().ExecDir(-1,
repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath),
"git", "update-server-info")
git.GitExecutable, "update-server-info")
if err != nil {
return nil, errors.New("CreateRepository(git update-server-info): " + stderr)
}
@@ -1512,11 +1516,9 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
}
}
}
} else {
} else if err = repo.recalculateAccesses(sess); err != nil {
// Organization called this in addRepository method.
if err = repo.recalculateAccesses(sess); err != nil {
return fmt.Errorf("recalculateAccesses: %v", err)
}
return fmt.Errorf("recalculateAccesses: %v", err)
}
// Update repository count.
@@ -1864,7 +1866,10 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
repoPath := repo.repoPath(sess)
removeAllWithNotice(sess, "Delete repository files", repoPath)
repo.deleteWiki(sess)
err = repo.deleteWiki(sess)
if err != nil {
return err
}
// Remove attachment files.
for i := range attachmentPaths {
@@ -1888,10 +1893,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
}
oidPath := filepath.Join(v.Oid[0:2], v.Oid[2:4], v.Oid[4:len(v.Oid)])
err = os.Remove(filepath.Join(setting.LFS.ContentPath, oidPath))
if err != nil {
return err
}
removeAllWithNotice(sess, "Delete orphaned LFS file", oidPath)
}
if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil {
@@ -1926,6 +1928,15 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
go HookQueue.Add(repo.ID)
}
if len(repo.Avatar) > 0 {
avatarPath := repo.CustomAvatarPath()
if com.IsExist(avatarPath) {
if err := os.Remove(avatarPath); err != nil {
return fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
}
}
}
DeleteRepoFromIndexer(repo)
return nil
}
@@ -2239,7 +2250,7 @@ func GitGcRepos() error {
_, stderr, err := process.GetManager().ExecDir(
time.Duration(setting.Git.Timeout.GC)*time.Second,
RepoPath(repo.Owner.Name, repo.Name), "Repository garbage collection",
"git", args...)
git.GitExecutable, args...)
if err != nil {
return fmt.Errorf("%v: %v", err, stderr)
}
@@ -2392,9 +2403,10 @@ func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *R
return nil, err
}
if forkedRepo != nil {
return nil, ErrRepoAlreadyExist{
Uname: u.Name,
Name: forkedRepo.Name,
return nil, ErrForkAlreadyExist{
Uname: u.Name,
RepoName: oldRepo.FullName(),
ForkName: forkedRepo.FullName(),
}
}
@@ -2428,14 +2440,14 @@ func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *R
repoPath := RepoPath(u.Name, repo.Name)
_, stderr, err := process.GetManager().ExecTimeout(10*time.Minute,
fmt.Sprintf("ForkRepository(git clone): %s/%s", u.Name, repo.Name),
"git", "clone", "--bare", oldRepo.repoPath(sess), repoPath)
git.GitExecutable, "clone", "--bare", oldRepo.repoPath(sess), repoPath)
if err != nil {
return nil, fmt.Errorf("git clone: %v", stderr)
}
_, stderr, err = process.GetManager().ExecDir(-1,
repoPath, fmt.Sprintf("ForkRepository(git update-server-info): %s", repoPath),
"git", "update-server-info")
git.GitExecutable, "update-server-info")
if err != nil {
return nil, fmt.Errorf("git update-server-info: %v", stderr)
}
@@ -2509,3 +2521,184 @@ func (repo *Repository) GetUserFork(userID int64) (*Repository, error) {
}
return &forkedRepo, nil
}
// CustomAvatarPath returns repository custom avatar file path.
func (repo *Repository) CustomAvatarPath() string {
// Avatar empty by default
if len(repo.Avatar) == 0 {
return ""
}
return filepath.Join(setting.RepositoryAvatarUploadPath, repo.Avatar)
}
// generateRandomAvatar generates a random avatar for repository.
func (repo *Repository) generateRandomAvatar(e Engine) error {
idToString := fmt.Sprintf("%d", repo.ID)
seed := idToString
img, err := avatar.RandomImage([]byte(seed))
if err != nil {
return fmt.Errorf("RandomImage: %v", err)
}
repo.Avatar = idToString
if err = os.MkdirAll(filepath.Dir(repo.CustomAvatarPath()), os.ModePerm); err != nil {
return fmt.Errorf("MkdirAll: %v", err)
}
fw, err := os.Create(repo.CustomAvatarPath())
if err != nil {
return fmt.Errorf("Create: %v", err)
}
defer fw.Close()
if err = png.Encode(fw, img); err != nil {
return fmt.Errorf("Encode: %v", err)
}
log.Info("New random avatar created for repository: %d", repo.ID)
if _, err := e.ID(repo.ID).Cols("avatar").NoAutoTime().Update(repo); err != nil {
return err
}
return nil
}
// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories
func RemoveRandomAvatars() error {
return x.
Where("id > 0").BufferSize(setting.IterateBufferSize).
Iterate(new(Repository),
func(idx int, bean interface{}) error {
repository := bean.(*Repository)
stringifiedID := strconv.FormatInt(repository.ID, 10)
if repository.Avatar == stringifiedID {
return repository.DeleteAvatar()
}
return nil
})
}
// RelAvatarLink returns a relative link to the repository's avatar.
func (repo *Repository) RelAvatarLink() string {
return repo.relAvatarLink(x)
}
func (repo *Repository) relAvatarLink(e Engine) string {
// If no avatar - path is empty
avatarPath := repo.CustomAvatarPath()
if len(avatarPath) == 0 || !com.IsFile(avatarPath) {
switch mode := setting.RepositoryAvatarFallback; mode {
case "image":
return setting.RepositoryAvatarFallbackImage
case "random":
if err := repo.generateRandomAvatar(e); err != nil {
log.Error("generateRandomAvatar: %v", err)
}
default:
// default behaviour: do not display avatar
return ""
}
}
return setting.AppSubURL + "/repo-avatars/" + repo.Avatar
}
// avatarLink returns user avatar absolute link.
func (repo *Repository) avatarLink(e Engine) string {
link := repo.relAvatarLink(e)
// link may be empty!
if len(link) > 0 {
if link[0] == '/' && link[1] != '/' {
return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:]
}
}
return link
}
// UploadAvatar saves custom avatar for repository.
// FIXME: split uploads to different subdirs in case we have massive number of repos.
func (repo *Repository) UploadAvatar(data []byte) error {
m, err := avatar.Prepare(data)
if err != nil {
return err
}
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
oldAvatarPath := repo.CustomAvatarPath()
// Users can upload the same image to other repo - prefix it with ID
// Then repo will be removed - only it avatar file will be removed
repo.Avatar = fmt.Sprintf("%d-%x", repo.ID, md5.Sum(data))
if _, err := sess.ID(repo.ID).Cols("avatar").Update(repo); err != nil {
return fmt.Errorf("UploadAvatar: Update repository avatar: %v", err)
}
if err := os.MkdirAll(setting.RepositoryAvatarUploadPath, os.ModePerm); err != nil {
return fmt.Errorf("UploadAvatar: Failed to create dir %s: %v", setting.RepositoryAvatarUploadPath, err)
}
fw, err := os.Create(repo.CustomAvatarPath())
if err != nil {
return fmt.Errorf("UploadAvatar: Create file: %v", err)
}
defer fw.Close()
if err = png.Encode(fw, *m); err != nil {
return fmt.Errorf("UploadAvatar: Encode png: %v", err)
}
if len(oldAvatarPath) > 0 && oldAvatarPath != repo.CustomAvatarPath() {
if err := os.Remove(oldAvatarPath); err != nil {
return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %v", oldAvatarPath, err)
}
}
return sess.Commit()
}
// DeleteAvatar deletes the repos's custom avatar.
func (repo *Repository) DeleteAvatar() error {
// Avatar not exists
if len(repo.Avatar) == 0 {
return nil
}
avatarPath := repo.CustomAvatarPath()
log.Trace("DeleteAvatar[%d]: %s", repo.ID, avatarPath)
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
repo.Avatar = ""
if _, err := sess.ID(repo.ID).Cols("avatar").Update(repo); err != nil {
return fmt.Errorf("DeleteAvatar: Update repository avatar: %v", err)
}
if _, err := os.Stat(avatarPath); err == nil {
if err := os.Remove(avatarPath); err != nil {
return fmt.Errorf("DeleteAvatar: Failed to remove %s: %v", avatarPath, err)
}
} else {
// // Schrodinger: file may or may not exist. See err for details.
log.Trace("DeleteAvatar[%d]: %v", err)
}
return sess.Commit()
}
// GetOriginalURLHostname returns the hostname of a URL or the URL
func (repo *Repository) GetOriginalURLHostname() string {
u, err := url.Parse(repo.OriginalURL)
if err != nil {
return repo.OriginalURL
}
return u.Host
}
+1 -1
View File
@@ -114,7 +114,7 @@ func GetActivityStatsTopAuthors(repo *Repository, timeFrom time.Time, count int)
v = append(v, u)
}
sort.Slice(v[:], func(i, j int) bool {
sort.Slice(v, func(i, j int) bool {
return v[i].Commits < v[j].Commits
})
+10 -2
View File
@@ -75,7 +75,11 @@ func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName st
if err != nil {
return err
}
defer RemoveTemporaryPath(basePath)
defer func() {
if err := RemoveTemporaryPath(basePath); err != nil {
log.Error("CreateNewBranch: RemoveTemporaryPath: %s", err)
}
}()
if err := git.Clone(repo.RepoPath(), basePath, git.CloneRepoOptions{
Bare: true,
@@ -117,7 +121,11 @@ func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName
if err != nil {
return err
}
defer RemoveTemporaryPath(basePath)
defer func() {
if err := RemoveTemporaryPath(basePath); err != nil {
log.Error("CreateNewBranchFromCommit: RemoveTemporaryPath: %s", err)
}
}()
if err := git.Clone(repo.RepoPath(), basePath, git.CloneRepoOptions{
Bare: true,
+1 -1
View File
@@ -142,7 +142,7 @@ func (repo *Repository) ChangeCollaborationAccessMode(uid int64, mode AccessMode
}
if _, err = sess.
Id(collaboration.ID).
ID(collaboration.ID).
Cols("mode").
Update(collaboration); err != nil {
return fmt.Errorf("update collaboration: %v", err)
+13 -8
View File
@@ -57,8 +57,9 @@ func (repo *Repository) updateIndexerStatus(sha string) error {
}
type repoIndexerOperation struct {
repo *Repository
deleted bool
repo *Repository
deleted bool
watchers []chan<- error
}
var repoIndexerOperationQueue chan repoIndexerOperation
@@ -312,26 +313,30 @@ func nonGenesisChanges(repo *Repository, revision string) (*repoChanges, error)
func processRepoIndexerOperationQueue() {
for {
op := <-repoIndexerOperationQueue
var err error
if op.deleted {
if err := indexer.DeleteRepoFromIndexer(op.repo.ID); err != nil {
if err = indexer.DeleteRepoFromIndexer(op.repo.ID); err != nil {
log.Error("DeleteRepoFromIndexer: %v", err)
}
} else {
if err := updateRepoIndexer(op.repo); err != nil {
if err = updateRepoIndexer(op.repo); err != nil {
log.Error("updateRepoIndexer: %v", err)
}
}
for _, watcher := range op.watchers {
watcher <- err
}
}
}
// DeleteRepoFromIndexer remove all of a repository's entries from the indexer
func DeleteRepoFromIndexer(repo *Repository) {
addOperationToQueue(repoIndexerOperation{repo: repo, deleted: true})
func DeleteRepoFromIndexer(repo *Repository, watchers ...chan<- error) {
addOperationToQueue(repoIndexerOperation{repo: repo, deleted: true, watchers: watchers})
}
// UpdateRepoIndexer update a repository's entries in the indexer
func UpdateRepoIndexer(repo *Repository) {
addOperationToQueue(repoIndexerOperation{repo: repo, deleted: false})
func UpdateRepoIndexer(repo *Repository, watchers ...chan<- error) {
addOperationToQueue(repoIndexerOperation{repo: repo, deleted: false, watchers: watchers})
}
func addOperationToQueue(op repoIndexerOperation) {
+89 -81
View File
@@ -11,8 +11,7 @@ import (
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"github.com/go-xorm/builder"
"github.com/go-xorm/core"
"xorm.io/builder"
)
// RepositoryListDefaultPageSize is the default number of repositories
@@ -112,15 +111,17 @@ func (repos MirrorRepositoryList) LoadAttributes() error {
// SearchRepoOptions holds the search options
type SearchRepoOptions struct {
Keyword string
OwnerID int64
OrderBy SearchOrderBy
Private bool // Include private repositories in results
Starred bool
Page int
IsProfile bool
AllPublic bool // Include also all public repositories
PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
UserID int64
UserIsAdmin bool
Keyword string
OwnerID int64
OrderBy SearchOrderBy
Private bool // Include private repositories in results
StarredByID int64
Page int
IsProfile bool
AllPublic bool // Include also all public repositories
PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
// None -> include collaborative AND non-collaborative
// True -> include just collaborative
// False -> incude just non-collaborative
@@ -147,19 +148,19 @@ func (s SearchOrderBy) String() string {
// Strings for sorting result
const (
SearchOrderByAlphabetically SearchOrderBy = "name ASC"
SearchOrderByAlphabeticallyReverse = "name DESC"
SearchOrderByLeastUpdated = "updated_unix ASC"
SearchOrderByRecentUpdated = "updated_unix DESC"
SearchOrderByOldest = "created_unix ASC"
SearchOrderByNewest = "created_unix DESC"
SearchOrderBySize = "size ASC"
SearchOrderBySizeReverse = "size DESC"
SearchOrderByID = "id ASC"
SearchOrderByIDReverse = "id DESC"
SearchOrderByStars = "num_stars ASC"
SearchOrderByStarsReverse = "num_stars DESC"
SearchOrderByForks = "num_forks ASC"
SearchOrderByForksReverse = "num_forks DESC"
SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC"
SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC"
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC"
SearchOrderByOldest SearchOrderBy = "created_unix ASC"
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
SearchOrderBySize SearchOrderBy = "size ASC"
SearchOrderBySizeReverse SearchOrderBy = "size DESC"
SearchOrderByID SearchOrderBy = "id ASC"
SearchOrderByIDReverse SearchOrderBy = "id DESC"
SearchOrderByStars SearchOrderBy = "num_stars ASC"
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC"
SearchOrderByForks SearchOrderBy = "num_forks ASC"
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
)
// SearchRepositoryByName takes keyword and part of repository name to search,
@@ -168,72 +169,79 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
if opts.Page <= 0 {
opts.Page = 1
}
var cond = builder.NewCond()
if !opts.Private {
if opts.Private {
if !opts.UserIsAdmin && opts.UserID != 0 && opts.UserID != opts.OwnerID {
// OK we're in the context of a User
// We should be Either
cond = cond.And(builder.Or(
// 1. Be able to see all non-private repositories that either:
cond.And(
builder.Eq{"is_private": false},
builder.Or(
// A. Aren't in organisations __OR__
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})),
// B. Isn't a private organisation. (Limited is OK because we're logged in)
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"visibility": structs.VisibleTypePrivate}))),
),
// 2. Be able to see all repositories that we have access to
builder.In("id", builder.Select("repo_id").
From("`access`").
Where(builder.And(
builder.Eq{"user_id": opts.UserID},
builder.Gt{"mode": int(AccessModeNone)}))),
// 3. Be able to see all repositories that we are in a team
builder.In("id", builder.Select("`team_repo`.repo_id").
From("team_repo").
Where(builder.Eq{"`team_user`.uid": opts.UserID}).
Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id"))))
}
} else {
// Not looking at private organisations
// We should be able to see all non-private repositories that either:
cond = cond.And(builder.Eq{"is_private": false})
accessCond := builder.Or(
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}))),
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})))
// A. Aren't in organisations __OR__
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})),
// B. Isn't a private or limited organisation.
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}))))
cond = cond.And(accessCond)
}
// Restrict to starred repositories
if opts.StarredByID > 0 {
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID})))
}
// Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate
if opts.OwnerID > 0 {
if opts.Starred {
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.OwnerID})))
} else {
var accessCond = builder.NewCond()
if opts.Collaborate != util.OptionalBoolTrue {
accessCond = builder.Eq{"owner_id": opts.OwnerID}
}
if opts.Collaborate != util.OptionalBoolFalse {
collaborateCond := builder.And(
builder.Expr("repository.id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
builder.Neq{"owner_id": opts.OwnerID})
if !opts.Private {
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
}
accessCond = accessCond.Or(collaborateCond)
}
var exprCond builder.Cond
if DbCfg.Type == core.POSTGRES {
exprCond = builder.Expr("org_user.org_id = \"user\".id")
} else if DbCfg.Type == core.MSSQL {
exprCond = builder.Expr("org_user.org_id = [user].id")
} else {
exprCond = builder.Eq{"org_user.org_id": "user.id"}
}
visibilityCond := builder.Or(
builder.In("owner_id",
builder.Select("org_id").From("org_user").
LeftJoin("`user`", exprCond).
Where(
builder.And(
builder.Eq{"uid": opts.OwnerID},
builder.Eq{"visibility": structs.VisibleTypePrivate})),
),
builder.In("owner_id",
builder.Select("id").From("`user`").
Where(
builder.Or(
builder.Eq{"visibility": structs.VisibleTypePublic},
builder.Eq{"visibility": structs.VisibleTypeLimited})),
),
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})),
)
cond = cond.And(visibilityCond)
if opts.AllPublic {
accessCond = accessCond.Or(builder.Eq{"is_private": false})
}
cond = cond.And(accessCond)
var accessCond = builder.NewCond()
if opts.Collaborate != util.OptionalBoolTrue {
accessCond = builder.Eq{"owner_id": opts.OwnerID}
}
if opts.Collaborate != util.OptionalBoolFalse {
collaborateCond := builder.And(
builder.Or(
builder.Expr("repository.id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
builder.In("id", builder.Select("`team_repo`.repo_id").
From("team_repo").
Where(builder.Eq{"`team_user`.uid": opts.OwnerID}).
Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id"))),
builder.Neq{"owner_id": opts.OwnerID})
if !opts.Private {
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
}
accessCond = accessCond.Or(collaborateCond)
}
if opts.AllPublic {
accessCond = accessCond.Or(builder.Eq{"is_private": false})
}
cond = cond.And(accessCond)
}
if opts.Keyword != "" {
+4 -4
View File
@@ -117,7 +117,7 @@ func TestSearchRepositoryByName(t *testing.T) {
count: 4},
{name: "PublicRepositoriesOfUserIncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15},
count: 4},
count: 5},
{name: "PublicRepositoriesOfUser2IncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18},
count: 1},
@@ -126,13 +126,13 @@ func TestSearchRepositoryByName(t *testing.T) {
count: 3},
{name: "PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true},
count: 8},
count: 9},
{name: "PublicAndPrivateRepositoriesOfUser2IncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18, Private: true},
count: 4},
{name: "PublicAndPrivateRepositoriesOfUser3IncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 20, Private: true},
count: 6},
count: 7},
{name: "PublicRepositoriesOfOrganization",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, Collaborate: util.OptionalBoolFalse},
count: 1},
@@ -150,7 +150,7 @@ func TestSearchRepositoryByName(t *testing.T) {
count: 21},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
count: 26},
count: 27},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
count: 15},
+14 -3
View File
@@ -20,6 +20,7 @@ import (
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"github.com/mcuadros/go-version"
)
// MirrorQueue holds an UniqueQueue object of the mirror
@@ -70,7 +71,17 @@ func (m *Mirror) ScheduleNextUpdate() {
}
func remoteAddress(repoPath string) (string, error) {
cmd := git.NewCommand("remote", "get-url", "origin")
var cmd *git.Command
binVersion, err := git.BinVersion()
if err != nil {
return "", err
}
if version.Compare(binVersion, "2.7", ">=") {
cmd = git.NewCommand("remote", "get-url", "origin")
} else {
cmd = git.NewCommand("config", "--get", "remote.origin.url")
}
result, err := cmd.RunInDir(repoPath)
if err != nil {
if strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
@@ -205,7 +216,7 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
_, stderr, err := process.GetManager().ExecDir(
timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath),
"git", gitArgs...)
git.GitExecutable, gitArgs...)
if err != nil {
// sanitize the output, since it may contain the remote address, which may
// contain a password
@@ -239,7 +250,7 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
if m.Repo.HasWiki() {
if _, stderr, err := process.GetManager().ExecDir(
timeout, wikiPath, fmt.Sprintf("Mirror.runSync: %s", wikiPath),
"git", "remote", "update", "--prune"); err != nil {
git.GitExecutable, "remote", "update", "--prune"); err != nil {
// sanitize the output, since it may contain the remote address, which may
// contain a password
message, err := sanitizeOutput(stderr, wikiPath)
+12 -4
View File
@@ -168,7 +168,17 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss
repo.mustOwner(e)
}
if repo.Owner.IsOrganization() && !HasOrgVisible(repo.Owner, user) {
var isCollaborator bool
if user != nil {
isCollaborator, err = repo.isCollaborator(e, user.ID)
if err != nil {
return perm, err
}
}
// Prevent strangers from checking out public repo of private orginization
// Allow user if they are collaborator of a repo within a private orginization but not a member of the orginization itself
if repo.Owner.IsOrganization() && !HasOrgVisible(repo.Owner, user) && !isCollaborator {
perm.AccessMode = AccessModeNone
return
}
@@ -207,9 +217,7 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss
perm.UnitsMode = make(map[UnitType]AccessMode)
// Collaborators on organization
if isCollaborator, err := repo.isCollaborator(e, user.ID); err != nil {
return perm, err
} else if isCollaborator {
if isCollaborator {
for _, u := range repo.Units {
perm.UnitsMode[u.Type] = perm.AccessMode
}
+12 -3
View File
@@ -4,7 +4,10 @@
package models
import "strings"
import (
"code.gitea.io/gitea/modules/log"
"strings"
)
// RepoRedirect represents that a repo name should be redirected to another
type RepoRedirect struct {
@@ -38,7 +41,10 @@ func NewRepoRedirect(ownerID, repoID int64, oldRepoName, newRepoName string) err
}
if err := deleteRepoRedirect(sess, ownerID, newRepoName); err != nil {
sess.Rollback()
errRollback := sess.Rollback()
if errRollback != nil {
log.Error("NewRepoRedirect sess.Rollback: %v", errRollback)
}
return err
}
@@ -47,7 +53,10 @@ func NewRepoRedirect(ownerID, repoID int64, oldRepoName, newRepoName string) err
LowerName: oldRepoName,
RedirectRepoID: repoID,
}); err != nil {
sess.Rollback()
errRollback := sess.Rollback()
if errRollback != nil {
log.Error("NewRepoRedirect sess.Rollback: %v", errRollback)
}
return err
}
return sess.Commit()
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"code.gitea.io/gitea/modules/git"
)
// GetTagsByPath returns repo tags by it's path
// GetTagsByPath returns repo tags by its path
func GetTagsByPath(path string) ([]*git.Tag, error) {
gitRepo, err := git.OpenRepository(path)
if err != nil {
+54 -1
View File
@@ -5,6 +5,11 @@
package models
import (
"bytes"
"crypto/md5"
"fmt"
"image"
"image/png"
"testing"
"code.gitea.io/gitea/modules/markup"
@@ -126,7 +131,7 @@ func TestForkRepository(t *testing.T) {
fork, err := ForkRepository(user, user, repo, "test", "test")
assert.Nil(t, fork)
assert.Error(t, err)
assert.True(t, IsErrRepoAlreadyExist(err))
assert.True(t, IsErrForkAlreadyExist(err))
}
func TestRepoAPIURL(t *testing.T) {
@@ -158,3 +163,51 @@ func TestTransferOwnership(t *testing.T) {
CheckConsistencyFor(t, &Repository{}, &User{}, &Team{})
}
func TestUploadAvatar(t *testing.T) {
// Generate image
myImage := image.NewRGBA(image.Rect(0, 0, 1, 1))
var buff bytes.Buffer
png.Encode(&buff, myImage)
assert.NoError(t, PrepareTestDatabase())
repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository)
err := repo.UploadAvatar(buff.Bytes())
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("%d-%x", 10, md5.Sum(buff.Bytes())), repo.Avatar)
}
func TestUploadBigAvatar(t *testing.T) {
// Generate BIG image
myImage := image.NewRGBA(image.Rect(0, 0, 5000, 1))
var buff bytes.Buffer
png.Encode(&buff, myImage)
assert.NoError(t, PrepareTestDatabase())
repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository)
err := repo.UploadAvatar(buff.Bytes())
assert.Error(t, err)
}
func TestDeleteAvatar(t *testing.T) {
// Generate image
myImage := image.NewRGBA(image.Rect(0, 0, 1, 1))
var buff bytes.Buffer
png.Encode(&buff, myImage)
assert.NoError(t, PrepareTestDatabase())
repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository)
err := repo.UploadAvatar(buff.Bytes())
assert.NoError(t, err)
err = repo.DeleteAvatar()
assert.NoError(t, err)
assert.Equal(t, "", repo.Avatar)
}
+1 -1
View File
@@ -10,8 +10,8 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"xorm.io/core"
)
// RepoUnit describes all units of a repository
+2 -2
View File
@@ -11,9 +11,9 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"github.com/go-xorm/builder"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"xorm.io/builder"
"xorm.io/core"
)
// ReviewType defines the sort of feedback a review gives
+36 -7
View File
@@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// 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.
@@ -24,9 +25,9 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
"golang.org/x/crypto/ssh"
"xorm.io/builder"
)
const (
@@ -142,7 +143,7 @@ func parseKeyString(content string) (string, error) {
if continuationLine || strings.ContainsAny(line, ":-") {
continuationLine = strings.HasSuffix(line, "\\")
} else {
keyContent = keyContent + line
keyContent += line
}
}
@@ -359,7 +360,7 @@ func checkKeyFingerprint(e Engine, fingerprint string) error {
return nil
}
func calcFingerprint(publicKeyContent string) (string, error) {
func calcFingerprintSSHKeygen(publicKeyContent string) (string, error) {
// Calculate fingerprint.
tmpPath, err := writeTmpKeyFile(publicKeyContent)
if err != nil {
@@ -375,6 +376,34 @@ func calcFingerprint(publicKeyContent string) (string, error) {
return strings.Split(stdout, " ")[1], nil
}
func calcFingerprintNative(publicKeyContent string) (string, error) {
// Calculate fingerprint.
pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKeyContent))
if err != nil {
return "", err
}
return ssh.FingerprintSHA256(pk), nil
}
func calcFingerprint(publicKeyContent string) (string, error) {
//Call the method based on configuration
var (
fnName, fp string
err error
)
if setting.SSH.StartBuiltinServer {
fnName = "calcFingerprintNative"
fp, err = calcFingerprintNative(publicKeyContent)
} else {
fnName = "calcFingerprintSSHKeygen"
fp, err = calcFingerprintSSHKeygen(publicKeyContent)
}
if err != nil {
return "", fmt.Errorf("%s: %v", fnName, err)
}
return fp, nil
}
func addKey(e Engine, key *PublicKey) (err error) {
if len(key.Fingerprint) == 0 {
key.Fingerprint, err = calcFingerprint(key.Content)
@@ -392,7 +421,7 @@ func addKey(e Engine, key *PublicKey) (err error) {
}
// AddPublicKey adds new public key to database and authorized_keys file.
func AddPublicKey(ownerID int64, name, content string, LoginSourceID int64) (*PublicKey, error) {
func AddPublicKey(ownerID int64, name, content string, loginSourceID int64) (*PublicKey, error) {
log.Trace(content)
fingerprint, err := calcFingerprint(content)
@@ -427,7 +456,7 @@ func AddPublicKey(ownerID int64, name, content string, LoginSourceID int64) (*Pu
Content: content,
Mode: AccessModeWrite,
Type: KeyTypeUser,
LoginSourceID: LoginSourceID,
LoginSourceID: loginSourceID,
}
if err = addKey(sess, key); err != nil {
return nil, fmt.Errorf("addKey: %v", err)
@@ -491,10 +520,10 @@ func ListPublicKeys(uid int64) ([]*PublicKey, error) {
}
// ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user and login source.
func ListPublicLdapSSHKeys(uid int64, LoginSourceID int64) ([]*PublicKey, error) {
func ListPublicLdapSSHKeys(uid int64, loginSourceID int64) ([]*PublicKey, error) {
keys := make([]*PublicKey, 0, 5)
return keys, x.
Where("owner_id = ? AND login_source_id = ?", uid, LoginSourceID).
Where("owner_id = ? AND login_source_id = ?", uid, loginSourceID).
Find(&keys)
}
+63 -21
View File
@@ -1,4 +1,5 @@
// Copyright 2016 The Gogs Authors. All rights reserved.
// 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.
@@ -14,31 +15,72 @@ import (
)
func init() {
setting.SetCustomPathAndConf("", "")
setting.SetCustomPathAndConf("", "", "")
setting.NewContext()
}
func Test_SSHParsePublicKey(t *testing.T) {
test := func(name, keyType string, length int, content string) {
keyTypeN, lengthN, err := SSHNativeParsePublicKey(content)
assert.NoError(t, err)
assert.Equal(t, keyType, keyTypeN)
assert.EqualValues(t, length, lengthN)
keyTypeK, lengthK, err := SSHKeyGenParsePublicKey(content)
if err != nil {
// Some servers do not support ecdsa format.
if !strings.Contains(err.Error(), "line 1 too long:") {
assert.Fail(t, "%v", err)
}
}
assert.Equal(t, keyType, keyTypeK)
assert.EqualValues(t, length, lengthK)
testCases := []struct {
name string
keyType string
length int
content string
}{
{"dsa-1024", "dsa", 1024, "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment"},
{"rsa-1024", "rsa", 1024, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
{"rsa-2048", "rsa", 2048, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
{"ecdsa-256", "ecdsa", 256, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
{"ecdsa-384", "ecdsa", 384, "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
}
test("dsa-1024", "dsa", 1024, "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment")
test("rsa-1024", "rsa", 1024, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n")
test("rsa-2048", "rsa", 2048, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment")
test("ecdsa-256", "ecdsa", 256, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment")
test("ecdsa-384", "ecdsa", 384, "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Run("Native", func(t *testing.T) {
keyTypeN, lengthN, err := SSHNativeParsePublicKey(tc.content)
assert.NoError(t, err)
assert.Equal(t, tc.keyType, keyTypeN)
assert.EqualValues(t, tc.length, lengthN)
})
t.Run("SSHKeygen", func(t *testing.T) {
keyTypeK, lengthK, err := SSHKeyGenParsePublicKey(tc.content)
if err != nil {
// Some servers do not support ecdsa format.
if !strings.Contains(err.Error(), "line 1 too long:") {
assert.Fail(t, "%v", err)
}
}
assert.Equal(t, tc.keyType, keyTypeK)
assert.EqualValues(t, tc.length, lengthK)
})
})
}
}
func Test_calcFingerprint(t *testing.T) {
testCases := []struct {
name string
fp string
content string
}{
{"dsa-1024", "SHA256:fSIHQlpKMDsGPVAXI8BPYfRp+e2sfvSt1sMrPsFiXrc", "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment"},
{"rsa-1024", "SHA256:vSnDkvRh/xM6kMxPidLgrUhq3mCN7CDaronCEm2joyQ", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
{"rsa-2048", "SHA256:ZHD//a1b9VuTq9XSunAeYjKeU1xDa2tBFZYrFr2Okkg", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
{"ecdsa-256", "SHA256:Bqx/xgWqRKLtkZ0Lr4iZpgb+5lYsFpSwXwVZbPwuTRw", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
{"ecdsa-384", "SHA256:4qfJOgJDtUd8BrEjyVNdI8IgjiZKouztVde43aDhe1E", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Run("Native", func(t *testing.T) {
fpN, err := calcFingerprintNative(tc.content)
assert.NoError(t, err)
assert.Equal(t, tc.fp, fpN)
})
t.Run("SSHKeygen", func(t *testing.T) {
fpK, err := calcFingerprintSSHKeygen(tc.content)
assert.NoError(t, err)
assert.Equal(t, tc.fp, fpK)
})
})
}
}
+2 -2
View File
@@ -36,11 +36,11 @@ func TestGetAccessTokenBySHA(t *testing.T) {
assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash)
assert.Equal(t, "e4efbf36", token.TokenLastEight)
token, err = GetAccessTokenBySHA("notahash")
_, err = GetAccessTokenBySHA("notahash")
assert.Error(t, err)
assert.True(t, IsErrAccessTokenNotExist(err))
token, err = GetAccessTokenBySHA("")
_, err = GetAccessTokenBySHA("")
assert.Error(t, err)
assert.True(t, IsErrAccessTokenEmpty(err))
}
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/go-xorm/builder"
"xorm.io/builder"
)
func init() {
+1 -1
View File
@@ -48,7 +48,7 @@ type U2FRegistrationList []*U2FRegistration
// ToRegistrations will convert all U2FRegistrations to u2f.Registrations
func (list U2FRegistrationList) ToRegistrations() []u2f.Registration {
regs := make([]u2f.Registration, len(list))
regs := make([]u2f.Registration, 0, len(list))
for _, reg := range list {
r, err := reg.Parse()
if err != nil {
+4 -4
View File
@@ -58,8 +58,8 @@ func (u UnitType) ColorFormat(s fmt.State) {
}
var (
// allRepUnitTypes contains all the unit types
allRepUnitTypes = []UnitType{
// AllRepoUnitTypes contains all the unit types
AllRepoUnitTypes = []UnitType{
UnitTypeCode,
UnitTypeIssues,
UnitTypePullRequests,
@@ -69,8 +69,8 @@ var (
UnitTypeExternalTracker,
}
// defaultRepoUnits contains the default unit types
defaultRepoUnits = []UnitType{
// DefaultRepoUnits contains the default unit types
DefaultRepoUnits = []UnitType{
UnitTypeCode,
UnitTypeIssues,
UnitTypePullRequests,
+1 -1
View File
@@ -18,10 +18,10 @@ import (
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"github.com/stretchr/testify/assert"
"gopkg.in/testfixtures.v2"
"xorm.io/core"
)
// NonexistentID an ID that will never exist
+4 -10
View File
@@ -7,7 +7,6 @@ package models
import (
"container/list"
"fmt"
"os/exec"
"strings"
"time"
@@ -84,7 +83,7 @@ func PushUpdate(branch string, opt PushUpdateOptions) error {
return nil
}
func pushUpdateDeleteTag(repo *Repository, gitRepo *git.Repository, tagName string) error {
func pushUpdateDeleteTag(repo *Repository, tagName string) error {
rel, err := GetRelease(repo.ID, tagName)
if err != nil {
if IsErrReleaseNotExist(err) {
@@ -193,9 +192,8 @@ func pushUpdate(opts PushUpdateOptions) (repo *Repository, err error) {
repoPath := RepoPath(opts.RepoUserName, opts.RepoName)
gitUpdate := exec.Command("git", "update-server-info")
gitUpdate.Dir = repoPath
if err = gitUpdate.Run(); err != nil {
_, err = git.NewCommand("update-server-info").RunInDir(repoPath)
if err != nil {
return nil, fmt.Errorf("Failed to call 'git update-server-info': %v", err)
}
@@ -223,7 +221,7 @@ func pushUpdate(opts PushUpdateOptions) (repo *Repository, err error) {
// If is tag reference
tagName := opts.RefFullName[len(git.TagPrefix):]
if isDelRef {
err = pushUpdateDeleteTag(repo, gitRepo, tagName)
err = pushUpdateDeleteTag(repo, tagName)
if err != nil {
return nil, fmt.Errorf("pushUpdateDeleteTag: %v", err)
}
@@ -263,10 +261,6 @@ func pushUpdate(opts PushUpdateOptions) (repo *Repository, err error) {
commits = ListToPushCommits(l)
}
if opts.RefFullName == git.BranchPrefix+repo.DefaultBranch {
UpdateRepoIndexer(repo)
}
if err := CommitRepoAction(CommitRepoActionOptions{
PusherName: opts.PusherName,
RepoOwnerID: owner.ID,
+85 -46
View File
@@ -6,7 +6,6 @@
package models
import (
"bytes"
"container/list"
"crypto/md5"
"crypto/sha256"
@@ -14,10 +13,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"image"
// Needed for jpeg support
_ "image/jpeg"
_ "image/jpeg" // Needed for jpeg support
"image/png"
"os"
"path/filepath"
@@ -36,12 +32,14 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"github.com/nfnt/resize"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
"golang.org/x/crypto/ssh"
"xorm.io/builder"
"xorm.io/core"
)
// UserType defines the user type
@@ -55,6 +53,13 @@ const (
UserTypeOrganization
)
const (
algoBcrypt = "bcrypt"
algoScrypt = "scrypt"
algoArgon2 = "argon2"
algoPbkdf2 = "pbkdf2"
)
const syncExternalUsers = "sync_external_users"
var (
@@ -87,6 +92,7 @@ type User struct {
Email string `xorm:"NOT NULL"`
KeepEmailPrivate bool
Passwd string `xorm:"NOT NULL"`
PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'pbkdf2'"`
// MustChangePassword is an attribute that determines if a user
// is to change his/her password after registration.
@@ -219,6 +225,8 @@ func (u *User) APIFormat() *api.User {
AvatarURL: u.AvatarLink(),
Language: u.Language,
IsAdmin: u.IsAdmin,
LastLogin: u.LastLoginUnix.AsTime(),
Created: u.CreatedUnix.AsTime(),
}
}
@@ -433,47 +441,57 @@ func (u *User) NewGitSig() *git.Signature {
}
}
func hashPassword(passwd, salt string) string {
tempPasswd := pbkdf2.Key([]byte(passwd), []byte(salt), 10000, 50, sha256.New)
func hashPassword(passwd, salt, algo string) string {
var tempPasswd []byte
switch algo {
case algoBcrypt:
tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost)
return string(tempPasswd)
case algoScrypt:
tempPasswd, _ = scrypt.Key([]byte(passwd), []byte(salt), 65536, 16, 2, 50)
case algoArgon2:
tempPasswd = argon2.IDKey([]byte(passwd), []byte(salt), 2, 65536, 8, 50)
case algoPbkdf2:
fallthrough
default:
tempPasswd = pbkdf2.Key([]byte(passwd), []byte(salt), 10000, 50, sha256.New)
}
return fmt.Sprintf("%x", tempPasswd)
}
// HashPassword hashes a password using PBKDF.
// HashPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO.
func (u *User) HashPassword(passwd string) {
u.Passwd = hashPassword(passwd, u.Salt)
u.PasswdHashAlgo = setting.PasswordHashAlgo
u.Passwd = hashPassword(passwd, u.Salt, setting.PasswordHashAlgo)
}
// ValidatePassword checks if given password matches the one belongs to the user.
func (u *User) ValidatePassword(passwd string) bool {
tempHash := hashPassword(passwd, u.Salt)
return subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1
tempHash := hashPassword(passwd, u.Salt, u.PasswdHashAlgo)
if u.PasswdHashAlgo != algoBcrypt && subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1 {
return true
}
if u.PasswdHashAlgo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil {
return true
}
return false
}
// IsPasswordSet checks if the password is set or left empty
func (u *User) IsPasswordSet() bool {
return !u.ValidatePassword("")
return len(u.Passwd) > 0
}
// UploadAvatar saves custom avatar for user.
// FIXME: split uploads to different subdirs in case we have massive users.
func (u *User) UploadAvatar(data []byte) error {
imgCfg, _, err := image.DecodeConfig(bytes.NewReader(data))
m, err := avatar.Prepare(data)
if err != nil {
return fmt.Errorf("DecodeConfig: %v", err)
return err
}
if imgCfg.Width > setting.AvatarMaxWidth {
return fmt.Errorf("Image width is to large: %d > %d", imgCfg.Width, setting.AvatarMaxWidth)
}
if imgCfg.Height > setting.AvatarMaxHeight {
return fmt.Errorf("Image height is to large: %d > %d", imgCfg.Height, setting.AvatarMaxHeight)
}
img, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
return fmt.Errorf("Decode: %v", err)
}
m := resize.Resize(avatar.AvatarSize, avatar.AvatarSize, img, resize.NearestNeighbor)
sess := x.NewSession()
defer sess.Close()
@@ -497,7 +515,7 @@ func (u *User) UploadAvatar(data []byte) error {
}
defer fw.Close()
if err = png.Encode(fw, m); err != nil {
if err = png.Encode(fw, *m); err != nil {
return fmt.Errorf("Encode: %v", err)
}
@@ -849,10 +867,9 @@ func CreateUser(u *User) (err error) {
return err
}
u.HashPassword(u.Passwd)
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
u.MaxRepoCreation = -1
u.Theme = setting.UI.DefaultTheme
u.AllowCreateOrganization = !setting.Admin.DisableRegularOrgCreation
if _, err = sess.Insert(u); err != nil {
return err
@@ -1091,7 +1108,10 @@ func deleteUser(e *xorm.Session, u *User) error {
if _, err = e.Delete(&PublicKey{OwnerID: u.ID}); err != nil {
return fmt.Errorf("deletePublicKeys: %v", err)
}
rewriteAllPublicKeys(e)
err = rewriteAllPublicKeys(e)
if err != nil {
return err
}
// ***** END: PublicKey *****
// ***** START: GPGPublicKey *****
@@ -1353,6 +1373,19 @@ func GetUserByEmail(email string) (*User, error) {
return GetUserByID(emailAddress.UID)
}
// Finally, if email address is the protected email address:
if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) {
username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress))
user := &User{LowerName: username}
has, err := x.Get(user)
if err != nil {
return nil, err
}
if has {
return user, nil
}
}
return nil, ErrUserNotExist{0, email, 0}
}
@@ -1407,8 +1440,7 @@ func (opts *SearchUserOptions) toConds() builder.Cond {
} else {
exprCond = builder.Expr("org_user.org_id = \"user\".id")
}
var accessCond = builder.NewCond()
accessCond = builder.Or(
accessCond := builder.Or(
builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.OwnerID}, builder.Eq{"visibility": structs.VisibleTypePrivate}))),
builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
cond = cond.And(accessCond)
@@ -1518,9 +1550,9 @@ func deleteKeysMarkedForDeletion(keys []string) (bool, error) {
}
// addLdapSSHPublicKeys add a users public keys. Returns true if there are changes.
func addLdapSSHPublicKeys(usr *User, s *LoginSource, SSHPublicKeys []string) bool {
func addLdapSSHPublicKeys(usr *User, s *LoginSource, sshPublicKeys []string) bool {
var sshKeysNeedUpdate bool
for _, sshKey := range SSHPublicKeys {
for _, sshKey := range sshPublicKeys {
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshKey))
if err == nil {
sshKeyName := fmt.Sprintf("%s-%s", s.Name, sshKey[0:40])
@@ -1542,7 +1574,7 @@ func addLdapSSHPublicKeys(usr *User, s *LoginSource, SSHPublicKeys []string) boo
}
// synchronizeLdapSSHPublicKeys updates a users public keys. Returns true if there are changes.
func synchronizeLdapSSHPublicKeys(usr *User, s *LoginSource, SSHPublicKeys []string) bool {
func synchronizeLdapSSHPublicKeys(usr *User, s *LoginSource, sshPublicKeys []string) bool {
var sshKeysNeedUpdate bool
log.Trace("synchronizeLdapSSHPublicKeys[%s]: Handling LDAP Public SSH Key synchronization for user %s", s.Name, usr.Name)
@@ -1560,7 +1592,7 @@ func synchronizeLdapSSHPublicKeys(usr *User, s *LoginSource, SSHPublicKeys []str
// Get Public Keys from LDAP and skip duplicate keys
var ldapKeys []string
for _, v := range SSHPublicKeys {
for _, v := range sshPublicKeys {
sshKeySplit := strings.Split(v, " ")
if len(sshKeySplit) > 1 {
ldapKey := strings.Join(sshKeySplit[:2], " ")
@@ -1639,10 +1671,14 @@ func SyncExternalUsers() {
var sshKeysNeedUpdate bool
// Find all users with this login type
var users []User
x.Where("login_type = ?", LoginLDAP).
var users []*User
err = x.Where("login_type = ?", LoginLDAP).
And("login_source = ?", s.ID).
Find(&users)
if err != nil {
log.Error("SyncExternalUsers: %v", err)
return
}
sr := s.LDAP().SearchEntries()
for _, su := range sr {
@@ -1658,7 +1694,7 @@ func SyncExternalUsers() {
// Search for existing user
for _, du := range users {
if du.LowerName == strings.ToLower(su.Username) {
usr = &du
usr = du
break
}
}
@@ -1700,7 +1736,7 @@ func SyncExternalUsers() {
// Check if user data has changed
if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) ||
strings.ToLower(usr.Email) != strings.ToLower(su.Mail) ||
!strings.EqualFold(usr.Email, su.Mail) ||
usr.FullName != fullName ||
!usr.IsActive {
@@ -1724,7 +1760,10 @@ func SyncExternalUsers() {
// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
if sshKeysNeedUpdate {
RewriteAllPublicKeys()
err = RewriteAllPublicKeys()
if err != nil {
log.Error("RewriteAllPublicKeys: %v", err)
}
}
// Deactivate users not present in LDAP
@@ -1741,7 +1780,7 @@ func SyncExternalUsers() {
log.Trace("SyncExternalUsers[%s]: Deactivating user %s", s.Name, usr.Name)
usr.IsActive = false
err = UpdateUserCols(&usr, "is_active")
err = UpdateUserCols(usr, "is_active")
if err != nil {
log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", s.Name, usr.Name, err)
}
+1 -1
View File
@@ -134,7 +134,7 @@ func (email *EmailAddress) Activate() error {
email.IsActivated = true
if _, err := sess.
Id(email.ID).
ID(email.ID).
Cols("is_activated").
Update(email); err != nil {
return err
+2 -2
View File
@@ -31,12 +31,12 @@ func TestGetUserOpenIDs(t *testing.T) {
func TestGetUserByOpenID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user, err := GetUserByOpenID("https://unknown")
_, err := GetUserByOpenID("https://unknown")
if assert.Error(t, err) {
assert.True(t, IsErrUserNotExist(err))
}
user, err = GetUserByOpenID("https://user1.domain1.tld")
user, err := GetUserByOpenID("https://user1.domain1.tld")
if assert.NoError(t, err) {
assert.Equal(t, user.ID, int64(1))
}
+22 -12
View File
@@ -147,21 +147,29 @@ func TestHashPasswordDeterministic(t *testing.T) {
b := make([]byte, 16)
rand.Read(b)
u := &User{Salt: string(b)}
for i := 0; i < 50; i++ {
// generate a random password
rand.Read(b)
pass := string(b)
algos := []string{"pbkdf2", "argon2", "scrypt", "bcrypt"}
for j := 0; j < len(algos); j++ {
u.PasswdHashAlgo = algos[j]
for i := 0; i < 50; i++ {
// generate a random password
rand.Read(b)
pass := string(b)
// save the current password in the user - hash it and store the result
u.HashPassword(pass)
r1 := u.Passwd
// save the current password in the user - hash it and store the result
u.HashPassword(pass)
r1 := u.Passwd
// run again
u.HashPassword(pass)
r2 := u.Passwd
// run again
u.HashPassword(pass)
r2 := u.Passwd
// assert equal (given the same salt+pass, the same result is produced)
assert.Equal(t, r1, r2)
// assert equal (given the same salt+pass, the same result is produced) except bcrypt
if u.PasswdHashAlgo == "bcrypt" {
assert.NotEqual(t, r1, r2)
} else {
assert.Equal(t, r1, r2)
}
}
}
}
@@ -261,6 +269,8 @@ func TestCreateUser_Issue5882(t *testing.T) {
{&User{Name: "GiteaBot2", Email: "GiteaBot2@gitea.io", Passwd: passwd, MustChangePassword: false}, true},
}
setting.Service.DefaultAllowCreateOrganization = true
for _, v := range tt {
setting.Admin.DisableRegularOrgCreation = v.disableOrgCreation
+95 -42
View File
@@ -13,11 +13,12 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"time"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -699,7 +700,10 @@ func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType,
log.Error("prepareWebhooks.JSONPayload: %v", err)
}
sig := hmac.New(sha256.New, []byte(w.Secret))
sig.Write(data)
_, err = sig.Write(data)
if err != nil {
log.Error("prepareWebhooks.sigWrite: %v", err)
}
signature = hex.EncodeToString(sig.Sum(nil))
}
@@ -753,49 +757,73 @@ func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payl
return nil
}
func (t *HookTask) deliver() {
func (t *HookTask) deliver() error {
t.IsDelivered = true
var req *http.Request
var err error
switch t.HTTPMethod {
case "":
log.Info("HTTP Method for webhook %d empty, setting to POST as default", t.ID)
fallthrough
case http.MethodPost:
switch t.ContentType {
case ContentTypeJSON:
req, err = http.NewRequest("POST", t.URL, strings.NewReader(t.PayloadContent))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
case ContentTypeForm:
var forms = url.Values{
"payload": []string{t.PayloadContent},
}
req, err = http.NewRequest("POST", t.URL, strings.NewReader(forms.Encode()))
if err != nil {
return err
}
}
case http.MethodGet:
u, err := url.Parse(t.URL)
if err != nil {
return err
}
vals := u.Query()
vals["payload"] = []string{t.PayloadContent}
u.RawQuery = vals.Encode()
req, err = http.NewRequest("GET", u.String(), nil)
if err != nil {
return err
}
default:
return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, t.HTTPMethod)
}
req.Header.Add("X-Gitea-Delivery", t.UUID)
req.Header.Add("X-Gitea-Event", string(t.EventType))
req.Header.Add("X-Gitea-Signature", t.Signature)
req.Header.Add("X-Gogs-Delivery", t.UUID)
req.Header.Add("X-Gogs-Event", string(t.EventType))
req.Header.Add("X-Gogs-Signature", t.Signature)
req.Header["X-GitHub-Delivery"] = []string{t.UUID}
req.Header["X-GitHub-Event"] = []string{string(t.EventType)}
// Record delivery information.
t.RequestInfo = &HookRequest{
Headers: map[string]string{},
}
for k, vals := range req.Header {
t.RequestInfo.Headers[k] = strings.Join(vals, ",")
}
t.ResponseInfo = &HookResponse{
Headers: map[string]string{},
}
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
var req *httplib.Request
if t.HTTPMethod == http.MethodPost {
req = httplib.Post(t.URL)
switch t.ContentType {
case ContentTypeJSON:
req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
case ContentTypeForm:
req.Param("payload", t.PayloadContent)
}
} else if t.HTTPMethod == http.MethodGet {
req = httplib.Get(t.URL).Param("payload", t.PayloadContent)
} else {
t.ResponseInfo.Body = fmt.Sprintf("Invalid http method: %v", t.HTTPMethod)
return
}
req = req.SetTimeout(timeout, timeout).
Header("X-Gitea-Delivery", t.UUID).
Header("X-Gitea-Event", string(t.EventType)).
Header("X-Gitea-Signature", t.Signature).
Header("X-Gogs-Delivery", t.UUID).
Header("X-Gogs-Event", string(t.EventType)).
Header("X-Gogs-Signature", t.Signature).
HeaderWithSensitiveCase("X-GitHub-Delivery", t.UUID).
HeaderWithSensitiveCase("X-GitHub-Event", string(t.EventType)).
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
// Record delivery information.
for k, vals := range req.Headers() {
t.RequestInfo.Headers[k] = strings.Join(vals, ",")
}
defer func() {
t.Delivered = time.Now().UnixNano()
if t.IsSucceed {
@@ -825,10 +853,10 @@ func (t *HookTask) deliver() {
}
}()
resp, err := req.Response()
resp, err := webhookHTTPClient.Do(req)
if err != nil {
t.ResponseInfo.Body = fmt.Sprintf("Delivery: %v", err)
return
return err
}
defer resp.Body.Close()
@@ -842,9 +870,10 @@ func (t *HookTask) deliver() {
p, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err)
return
return err
}
t.ResponseInfo.Body = string(p)
return nil
}
// DeliverHooks checks and delivers undelivered hooks.
@@ -859,7 +888,10 @@ func DeliverHooks() {
// Update hook task status.
for _, t := range tasks {
t.deliver()
if err = t.deliver(); err != nil {
log.Error("deliver: %v", err)
continue
}
}
// Start listening on new hook requests.
@@ -879,12 +911,33 @@ func DeliverHooks() {
continue
}
for _, t := range tasks {
t.deliver()
if err = t.deliver(); err != nil {
log.Error("deliver: %v", err)
}
}
}
}
var webhookHTTPClient *http.Client
// InitDeliverHooks starts the hooks delivery thread
func InitDeliverHooks() {
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
webhookHTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify},
Dial: func(netw, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(netw, addr, timeout)
if err != nil {
return nil, err
}
return conn, conn.SetDeadline(time.Now().Add(timeout))
},
},
}
go DeliverHooks()
}
+1 -1
View File
@@ -490,7 +490,7 @@ func getDiscordReleasePayload(p *api.ReleasePayload, meta *DiscordMeta) (*Discor
Embeds: []DiscordEmbed{
{
Title: title,
Description: fmt.Sprintf("%s", p.Release.Note),
Description: p.Release.Note,
URL: url,
Color: color,
Author: DiscordEmbedAuthor{
+10 -2
View File
@@ -115,7 +115,11 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con
if err != nil {
return err
}
defer RemoveTemporaryPath(basePath)
defer func() {
if err := RemoveTemporaryPath(basePath); err != nil {
log.Error("Merge: RemoveTemporaryPath: %s", err)
}
}()
cloneOpts := git.CloneRepoOptions{
Bare: true,
@@ -246,7 +250,11 @@ func (repo *Repository) DeleteWikiPage(doer *User, wikiName string) (err error)
if err != nil {
return err
}
defer RemoveTemporaryPath(basePath)
defer func() {
if err := RemoveTemporaryPath(basePath); err != nil {
log.Error("Merge: RemoveTemporaryPath: %s", err)
}
}()
if err := git.Clone(repo.WikiPath(), basePath, git.CloneRepoOptions{
Bare: true,