mirror of
https://github.com/go-gitea/gitea
synced 2025-12-07 13:28:25 +00:00
Merge branch 'master' into fix-6409
This commit is contained in:
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
@@ -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++ {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -9,3 +9,9 @@
|
||||
repo_id: 4
|
||||
user_id: 4
|
||||
mode: 2 # write
|
||||
|
||||
-
|
||||
id: 3
|
||||
repo_id: 40
|
||||
user_id: 4
|
||||
mode: 2 # write
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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 != "" {
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/go-xorm/builder"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
+1
-1
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user