aboutsummaryrefslogtreecommitdiff
path: root/models
diff options
context:
space:
mode:
authorDon Bowman <don.waterloo@gmail.com>2015-08-12 21:10:00 +0000
committerDon Bowman <don.waterloo@gmail.com>2015-08-12 21:10:00 +0000
commit1cb46ede1acf4f8527e64fcae7e92672cad764b2 (patch)
treefabb54ee5f040be2a4ee5c95f87cb3e9fbf7bdea /models
parent9e6bd31d76aa6d6495a2144466af78773f34d07c (diff)
parentaede5cdb04fdbf74d9c602062fdece9f408e90f4 (diff)
Merge branch 'master' of https://github.com/gogits/gogs
Conflicts: routers/repo/download.go
Diffstat (limited to 'models')
-rw-r--r--models/access.go26
-rw-r--r--models/action.go36
-rw-r--r--models/error.go176
-rw-r--r--models/git_diff.go47
-rw-r--r--models/issue.go406
-rw-r--r--models/login.go71
-rw-r--r--models/migrations/migrations.go74
-rw-r--r--models/models.go14
-rw-r--r--models/org.go38
-rw-r--r--models/publickey.go337
-rw-r--r--models/repo.go265
-rw-r--r--models/user.go82
-rw-r--r--models/webhook.go172
13 files changed, 1231 insertions, 513 deletions
diff --git a/models/access.go b/models/access.go
index dd856afb..54d0f099 100644
--- a/models/access.go
+++ b/models/access.go
@@ -145,6 +145,25 @@ func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int6
for _, c := range collaborators {
accessMap[c.Id] = ACCESS_MODE_WRITE
}
+
+ // Adds team members access.
+ if repo.Owner.IsOrganization() {
+ if err = repo.Owner.GetTeams(); err != nil {
+ return fmt.Errorf("GetTeams: %v", err)
+ }
+ for _, t := range repo.Owner.Teams {
+ if err = t.GetMembers(); err != nil {
+ return fmt.Errorf("GetMembers: %v", err)
+ }
+ for _, m := range t.Members {
+ if t.IsOwnerTeam() {
+ accessMap[m.Id] = ACCESS_MODE_OWNER
+ } else {
+ accessMap[m.Id] = maxAccessMode(accessMap[m.Id], t.Authorize)
+ }
+ }
+ }
+ }
return nil
}
@@ -154,13 +173,12 @@ func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int6
func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err error) {
accessMap := make(map[int64]AccessMode, 20)
- if err = repo.refreshCollaboratorAccesses(e, accessMap); err != nil {
- return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
- }
-
if err = repo.getOwner(e); err != nil {
return err
}
+ if err = repo.refreshCollaboratorAccesses(e, accessMap); err != nil {
+ return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
+ }
if repo.Owner.IsOrganization() {
if err = repo.Owner.getTeams(e); err != nil {
return err
diff --git a/models/action.go b/models/action.go
index d3393728..99cd1709 100644
--- a/models/action.go
+++ b/models/action.go
@@ -153,7 +153,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
url := fmt.Sprintf("%s/%s/%s/commit/%s", setting.AppSubUrl, repoUserName, repoName, c.Sha1)
message := fmt.Sprintf(`<a href="%s">%s</a>`, url, c.Message)
- if _, err = CreateComment(userId, issue.RepoId, issue.Id, 0, 0, COMMENT_TYPE_COMMIT, message, nil); err != nil {
+ if _, err = CreateComment(userId, issue.RepoID, issue.ID, 0, 0, COMMENT_TYPE_COMMIT, message, nil); err != nil {
return err
}
}
@@ -183,7 +183,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
return err
}
- if issue.RepoId == repoId {
+ if issue.RepoID == repoId {
if issue.IsClosed {
continue
}
@@ -202,7 +202,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
if err = UpdateIssue(issue); err != nil {
return err
- } else if err = UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil {
+ } else if err = UpdateIssueUserPairsByStatus(issue.ID, issue.IsClosed); err != nil {
return err
}
@@ -211,7 +211,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
}
// If commit happened in the referenced repository, it means the issue can be closed.
- if _, err = CreateComment(userId, repoId, issue.Id, 0, 0, COMMENT_TYPE_CLOSE, "", nil); err != nil {
+ if _, err = CreateComment(userId, repoId, issue.ID, 0, 0, COMMENT_TYPE_CLOSE, "", nil); err != nil {
return err
}
}
@@ -242,7 +242,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
return err
}
- if issue.RepoId == repoId {
+ if issue.RepoID == repoId {
if !issue.IsClosed {
continue
}
@@ -261,7 +261,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
if err = UpdateIssue(issue); err != nil {
return err
- } else if err = UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil {
+ } else if err = UpdateIssueUserPairsByStatus(issue.ID, issue.IsClosed); err != nil {
return err
}
@@ -270,7 +270,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
}
// If commit happened in the referenced repository, it means the issue can be closed.
- if _, err = CreateComment(userId, repoId, issue.Id, 0, 0, COMMENT_TYPE_REOPEN, "", nil); err != nil {
+ if _, err = CreateComment(userId, repoId, issue.ID, 0, 0, COMMENT_TYPE_REOPEN, "", nil); err != nil {
return err
}
}
@@ -293,12 +293,12 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
// if not the first commit, set the compareUrl
if !strings.HasPrefix(oldCommitId, "0000000") {
- commit.CompareUrl = fmt.Sprintf("%s/compare/%s...%s", repoLink, oldCommitId, newCommitId)
+ commit.CompareUrl = fmt.Sprintf("%s/%s/compare/%s...%s", repoUserName, repoName, oldCommitId, newCommitId)
}
bs, err := json.Marshal(commit)
if err != nil {
- return errors.New("action.CommitRepoAction(json): " + err.Error())
+ return errors.New("json: " + err.Error())
}
refName := git.RefEndName(refFullName)
@@ -306,17 +306,17 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
// Change repository bare status and update last updated time.
repo, err := GetRepositoryByName(repoUserId, repoName)
if err != nil {
- return errors.New("action.CommitRepoAction(GetRepositoryByName): " + err.Error())
+ return errors.New("GetRepositoryByName: " + err.Error())
}
repo.IsBare = false
if err = UpdateRepository(repo, false); err != nil {
- return errors.New("action.CommitRepoAction(UpdateRepository): " + err.Error())
+ return errors.New("UpdateRepository: " + err.Error())
}
err = updateIssuesCommit(userId, repoId, repoUserName, repoName, commit.Commits)
if err != nil {
- log.Debug("action.CommitRepoAction(updateIssuesCommit): ", err)
+ log.Debug("updateIssuesCommit: ", err)
}
if err = NotifyWatchers(&Action{
@@ -331,18 +331,18 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
RefName: refName,
IsPrivate: repo.IsPrivate,
}); err != nil {
- return errors.New("action.CommitRepoAction(NotifyWatchers): " + err.Error())
+ return errors.New("NotifyWatchers: " + err.Error())
}
// New push event hook.
if err := repo.GetOwner(); err != nil {
- return errors.New("action.CommitRepoAction(GetOwner): " + err.Error())
+ return errors.New("GetOwner: " + err.Error())
}
ws, err := GetActiveWebhooksByRepoId(repoId)
if err != nil {
- return errors.New("action.CommitRepoAction(GetActiveWebhooksByRepoId): " + err.Error())
+ return errors.New("GetActiveWebhooksByRepoId: " + err.Error())
}
// check if repo belongs to org and append additional webhooks
@@ -350,7 +350,7 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
// get hooks for org
orgws, err := GetActiveWebhooksByOrgId(repo.OwnerId)
if err != nil {
- return errors.New("action.CommitRepoAction(GetActiveWebhooksByOrgId): " + err.Error())
+ return errors.New("GetActiveWebhooksByOrgId: " + err.Error())
}
ws = append(ws, orgws...)
}
@@ -408,7 +408,7 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
},
Before: oldCommitId,
After: newCommitId,
- CompareUrl: commit.CompareUrl,
+ CompareUrl: setting.AppUrl + commit.CompareUrl,
}
for _, w := range ws {
@@ -431,6 +431,8 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
}
if err = CreateHookTask(&HookTask{
+ RepoID: repo.Id,
+ HookID: w.Id,
Type: w.HookTaskType,
Url: w.Url,
BasePayload: payload,
diff --git a/models/error.go b/models/error.go
index a434b8d6..0e554e52 100644
--- a/models/error.go
+++ b/models/error.go
@@ -8,6 +8,32 @@ import (
"fmt"
)
+type ErrNameReserved struct {
+ Name string
+}
+
+func IsErrNameReserved(err error) bool {
+ _, ok := err.(ErrNameReserved)
+ return ok
+}
+
+func (err ErrNameReserved) Error() string {
+ return fmt.Sprintf("name is reserved: [name: %s]", err.Name)
+}
+
+type ErrNamePatternNotAllowed struct {
+ Pattern string
+}
+
+func IsErrNamePatternNotAllowed(err error) bool {
+ _, ok := err.(ErrNamePatternNotAllowed)
+ return ok
+}
+
+func (err ErrNamePatternNotAllowed) Error() string {
+ return fmt.Sprintf("name pattern is not allowed: [pattern: %s]", err.Pattern)
+}
+
// ____ ___
// | | \______ ___________
// | | / ___// __ \_ __ \
@@ -15,6 +41,46 @@ import (
// |______//____ >\___ >__|
// \/ \/
+type ErrUserAlreadyExist struct {
+ Name string
+}
+
+func IsErrUserAlreadyExist(err error) bool {
+ _, ok := err.(ErrUserAlreadyExist)
+ return ok
+}
+
+func (err ErrUserAlreadyExist) Error() string {
+ return fmt.Sprintf("user already exists: [name: %s]", err.Name)
+}
+
+type ErrUserNotExist struct {
+ UID int64
+ Name string
+}
+
+func IsErrUserNotExist(err error) bool {
+ _, ok := err.(ErrUserNotExist)
+ return ok
+}
+
+func (err ErrUserNotExist) Error() string {
+ return fmt.Sprintf("user does not exist: [uid: %d, name: %s]", err.UID, err.Name)
+}
+
+type ErrEmailAlreadyUsed struct {
+ Email string
+}
+
+func IsErrEmailAlreadyUsed(err error) bool {
+ _, ok := err.(ErrEmailAlreadyUsed)
+ return ok
+}
+
+func (err ErrEmailAlreadyUsed) Error() string {
+ return fmt.Sprintf("e-mail has been used: [email: %s]", err.Email)
+}
+
type ErrUserOwnRepos struct {
UID int64
}
@@ -41,6 +107,82 @@ func (err ErrUserHasOrgs) Error() string {
return fmt.Sprintf("user still has membership of organizations: [uid: %d]", err.UID)
}
+// __________ ___. .__ .__ ____ __.
+// \______ \__ _\_ |__ | | |__| ____ | |/ _|____ ___.__.
+// | ___/ | \ __ \| | | |/ ___\ | <_/ __ < | |
+// | | | | / \_\ \ |_| \ \___ | | \ ___/\___ |
+// |____| |____/|___ /____/__|\___ > |____|__ \___ > ____|
+// \/ \/ \/ \/\/
+
+type ErrKeyNotExist struct {
+ ID int64
+}
+
+func IsErrKeyNotExist(err error) bool {
+ _, ok := err.(ErrKeyNotExist)
+ return ok
+}
+
+func (err ErrKeyNotExist) Error() string {
+ return fmt.Sprintf("public key does not exist: [id: %d]", err.ID)
+}
+
+type ErrKeyAlreadyExist struct {
+ OwnerID int64
+ Content string
+}
+
+func IsErrKeyAlreadyExist(err error) bool {
+ _, ok := err.(ErrKeyAlreadyExist)
+ return ok
+}
+
+func (err ErrKeyAlreadyExist) Error() string {
+ return fmt.Sprintf("public key already exists: [owner_id: %d, content: %s]", err.OwnerID, err.Content)
+}
+
+type ErrKeyNameAlreadyUsed struct {
+ OwnerID int64
+ Name string
+}
+
+func IsErrKeyNameAlreadyUsed(err error) bool {
+ _, ok := err.(ErrKeyNameAlreadyUsed)
+ return ok
+}
+
+func (err ErrKeyNameAlreadyUsed) Error() string {
+ return fmt.Sprintf("public key already exists: [owner_id: %d, name: %s]", err.OwnerID, err.Name)
+}
+
+type ErrDeployKeyAlreadyExist struct {
+ KeyID int64
+ RepoID int64
+}
+
+func IsErrDeployKeyAlreadyExist(err error) bool {
+ _, ok := err.(ErrDeployKeyAlreadyExist)
+ return ok
+}
+
+func (err ErrDeployKeyAlreadyExist) Error() string {
+ return fmt.Sprintf("public key already exists: [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
+}
+
+type ErrDeployKeyNameAlreadyUsed struct {
+ RepoID int64
+ Name string
+}
+
+func IsErrDeployKeyNameAlreadyUsed(err error) bool {
+ _, ok := err.(ErrDeployKeyNameAlreadyUsed)
+ return ok
+}
+
+func (err ErrDeployKeyNameAlreadyUsed) Error() string {
+ return fmt.Sprintf("public key already exists: [repo_id: %d, name: %s]", err.RepoID, err.Name)
+}
+
// ________ .__ __ .__
// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
@@ -82,3 +224,37 @@ func IsErrRepoNotExist(err error) bool {
func (err ErrRepoNotExist) Error() string {
return fmt.Sprintf("repository does not exist [id: %d, uid: %d, name: %s]", err.ID, err.UID, err.Name)
}
+
+type ErrRepoAlreadyExist struct {
+ Uname string
+ Name string
+}
+
+func IsErrRepoAlreadyExist(err error) bool {
+ _, ok := err.(ErrRepoAlreadyExist)
+ return ok
+}
+
+func (err ErrRepoAlreadyExist) Error() string {
+ return fmt.Sprintf("repository already exists [uname: %d, name: %s]", err.Uname, err.Name)
+}
+
+// _____ .__.__ __
+// / \ |__| | ____ _______/ |_ ____ ____ ____
+// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
+// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
+// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
+// \/ \/ \/ \/ \/
+
+type ErrMilestoneNotExist struct {
+ ID int64
+}
+
+func IsErrMilestoneNotExist(err error) bool {
+ _, ok := err.(ErrMilestoneNotExist)
+ return ok
+}
+
+func (err ErrMilestoneNotExist) Error() string {
+ return fmt.Sprintf("milestone does not exist [id: %d]", err.ID)
+}
diff --git a/models/git_diff.go b/models/git_diff.go
index 9d34ed62..9681b3f4 100644
--- a/models/git_diff.go
+++ b/models/git_diff.go
@@ -87,7 +87,7 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
leftLine, rightLine int
isTooLong bool
- // FIXME: use first 30 lines to detect file encoding. Should use cache in the future.
+ // FIXME: Should use cache in the future.
buf bytes.Buffer
)
@@ -106,16 +106,10 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
i = i + 1
- // FIXME: use first 30 lines to detect file encoding.
- if i <= 30 {
- buf.WriteString(line)
- }
-
// Diff data too large, we only show the first about maxlines lines
if i == maxlines {
isTooLong = true
log.Warn("Diff data too large")
- //return &Diff{}, nil
}
switch {
@@ -127,7 +121,7 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
continue
case line[0] == '@':
if isTooLong {
- return diff, nil
+ break
}
curSection = &DiffSection{}
@@ -137,9 +131,14 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
curSection.Lines = append(curSection.Lines, diffLine)
// Parse line number.
- ranges := strings.Split(ss[len(ss)-2][1:], " ")
+ ranges := strings.Split(ss[1][1:], " ")
leftLine, _ = com.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
- rightLine, _ = com.StrTo(strings.Split(ranges[1], ",")[0]).Int()
+ if len(ranges) > 1 {
+ rightLine, _ = com.StrTo(strings.Split(ranges[1], ",")[0]).Int()
+ } else {
+ log.Warn("Parse line number failed: %v", line)
+ rightLine = leftLine
+ }
continue
case line[0] == '+':
curFile.Addition++
@@ -164,11 +163,11 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
// Get new file.
if strings.HasPrefix(line, DIFF_HEAD) {
if isTooLong {
- return diff, nil
+ break
}
- fs := strings.Split(line[len(DIFF_HEAD):], " ")
- a := fs[0]
+ beg := len(DIFF_HEAD)
+ a := line[beg : (len(line)-beg)/2+beg]
curFile = &DiffFile{
Name: a[strings.Index(a, "/")+1:],
@@ -201,14 +200,19 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
}
}
- // FIXME: use first 30 lines to detect file encoding.
- charsetLabel, err := base.DetectEncoding(buf.Bytes())
- if charsetLabel != "utf8" && err == nil {
- encoding, _ := charset.Lookup(charsetLabel)
-
- if encoding != nil {
- d := encoding.NewDecoder()
- for _, f := range diff.Files {
+ for _, f := range diff.Files {
+ buf.Reset()
+ for _, sec := range f.Sections {
+ for _, l := range sec.Lines {
+ buf.WriteString(l.Content)
+ buf.WriteString("\n")
+ }
+ }
+ charsetLabel, err := base.DetectEncoding(buf.Bytes())
+ if charsetLabel != "UTF-8" && err == nil {
+ encoding, _ := charset.Lookup(charsetLabel)
+ if encoding != nil {
+ d := encoding.NewDecoder()
for _, sec := range f.Sections {
for _, l := range sec.Lines {
if c, _, err := transform.String(d, l.Content); err == nil {
@@ -219,7 +223,6 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
}
}
}
-
return diff, nil
}
diff --git a/models/issue.go b/models/issue.go
index 2b80e576..d423f056 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -17,12 +17,12 @@ import (
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/log"
+ "github.com/gogits/gogs/modules/setting"
)
var (
ErrIssueNotExist = errors.New("Issue does not exist")
ErrLabelNotExist = errors.New("Label does not exist")
- ErrMilestoneNotExist = errors.New("Milestone does not exist")
ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone")
ErrAttachmentNotExist = errors.New("Attachment does not exist")
ErrAttachmentNotLinked = errors.New("Attachment does not belong to this issue")
@@ -31,17 +31,18 @@ var (
// Issue represents an issue or pull request of repository.
type Issue struct {
- Id int64
- RepoId int64 `xorm:"INDEX"`
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX"`
Index int64 // Index in one repository.
Name string
Repo *Repository `xorm:"-"`
- PosterId int64
+ PosterID int64
Poster *User `xorm:"-"`
LabelIds string `xorm:"TEXT"`
Labels []*Label `xorm:"-"`
- MilestoneId int64
- AssigneeId int64
+ MilestoneID int64
+ Milestone *Milestone `xorm:"-"`
+ AssigneeID int64
Assignee *User `xorm:"-"`
IsRead bool `xorm:"-"`
IsPull bool // Indicates whether is a pull request or not.
@@ -55,9 +56,20 @@ type Issue struct {
Updated time.Time `xorm:"UPDATED"`
}
+func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
+ var err error
+ switch colName {
+ case "milestone_id":
+ i.Milestone, err = GetMilestoneByID(i.MilestoneID)
+ if err != nil {
+ log.Error(3, "GetMilestoneById: %v", err)
+ }
+ }
+}
+
func (i *Issue) GetPoster() (err error) {
- i.Poster, err = GetUserById(i.PosterId)
- if err == ErrUserNotExist {
+ i.Poster, err = GetUserById(i.PosterID)
+ if IsErrUserNotExist(err) {
i.Poster = &User{Name: "FakeUser"}
return nil
}
@@ -72,7 +84,7 @@ func (i *Issue) GetLabels() error {
strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$")
i.Labels = make([]*Label, 0, len(strIds))
for _, strId := range strIds {
- id, _ := com.StrTo(strId).Int64()
+ id := com.StrTo(strId).MustInt64()
if id > 0 {
l, err := GetLabelById(id)
if err != nil {
@@ -88,45 +100,41 @@ func (i *Issue) GetLabels() error {
}
func (i *Issue) GetAssignee() (err error) {
- if i.AssigneeId == 0 {
+ if i.AssigneeID == 0 {
return nil
}
- i.Assignee, err = GetUserById(i.AssigneeId)
- if err == ErrUserNotExist {
+
+ i.Assignee, err = GetUserById(i.AssigneeID)
+ if IsErrUserNotExist(err) {
return nil
}
return err
}
func (i *Issue) Attachments() []*Attachment {
- a, _ := GetAttachmentsForIssue(i.Id)
+ a, _ := GetAttachmentsForIssue(i.ID)
return a
}
func (i *Issue) AfterDelete() {
- _, err := DeleteAttachmentsByIssue(i.Id, true)
+ _, err := DeleteAttachmentsByIssue(i.ID, true)
if err != nil {
- log.Info("Could not delete files for issue #%d: %s", i.Id, err)
+ log.Info("Could not delete files for issue #%d: %s", i.ID, err)
}
}
// CreateIssue creates new issue for repository.
func NewIssue(issue *Issue) (err error) {
sess := x.NewSession()
- defer sess.Close()
+ defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Insert(issue); err != nil {
- sess.Rollback()
return err
- }
-
- rawSql := "UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?"
- if _, err = sess.Exec(rawSql, issue.RepoId); err != nil {
- sess.Rollback()
+ } else if _, err = sess.Exec("UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?", issue.RepoID); err != nil {
return err
}
@@ -134,9 +142,9 @@ func NewIssue(issue *Issue) (err error) {
return err
}
- if issue.MilestoneId > 0 {
+ if issue.MilestoneID > 0 {
// FIXES(280): Update milestone counter.
- return ChangeMilestoneAssign(0, issue.MilestoneId, issue)
+ return ChangeMilestoneAssign(0, issue.MilestoneID, issue)
}
return
@@ -167,7 +175,7 @@ func GetIssueByRef(ref string) (issue *Issue, err error) {
// GetIssueByIndex returns issue by given index in repository.
func GetIssueByIndex(rid, index int64) (*Issue, error) {
- issue := &Issue{RepoId: rid, Index: index}
+ issue := &Issue{RepoID: rid, Index: index}
has, err := x.Get(issue)
if err != nil {
return nil, err
@@ -179,7 +187,7 @@ func GetIssueByIndex(rid, index int64) (*Issue, error) {
// GetIssueById returns an issue by ID.
func GetIssueById(id int64) (*Issue, error) {
- issue := &Issue{Id: id}
+ issue := &Issue{ID: id}
has, err := x.Get(issue)
if err != nil {
return nil, err
@@ -189,31 +197,30 @@ func GetIssueById(id int64) (*Issue, error) {
return issue, nil
}
-// GetIssues returns a list of issues by given conditions.
-func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sortType string) ([]Issue, error) {
- sess := x.Limit(20, (page-1)*20)
+// Issues returns a list of issues by given conditions.
+func Issues(uid, assigneeID, repoID, posterID, milestoneID int64, page int, isClosed, isMention bool, labelIds, sortType string) ([]*Issue, error) {
+ sess := x.Limit(setting.IssuePagingNum, (page-1)*setting.IssuePagingNum)
- if rid > 0 {
- sess.Where("repo_id=?", rid).And("is_closed=?", isClosed)
+ if repoID > 0 {
+ sess.Where("issue.repo_id=?", repoID).And("issue.is_closed=?", isClosed)
} else {
- sess.Where("is_closed=?", isClosed)
+ sess.Where("issue.is_closed=?", isClosed)
}
- if uid > 0 {
- sess.And("assignee_id=?", uid)
- } else if pid > 0 {
- sess.And("poster_id=?", pid)
+ if assigneeID > 0 {
+ sess.And("issue.assignee_id=?", assigneeID)
+ } else if posterID > 0 {
+ sess.And("issue.poster_id=?", posterID)
}
- if mid > 0 {
- sess.And("milestone_id=?", mid)
+ if milestoneID > 0 {
+ sess.And("issue.milestone_id=?", milestoneID)
}
if len(labelIds) > 0 {
for _, label := range strings.Split(labelIds, ",") {
- // Prevent SQL inject.
if com.StrTo(label).MustInt() > 0 {
- sess.And("label_ids like '%$" + label + "|%'")
+ sess.And("label_ids like ?", "%$"+label+"|%")
}
}
}
@@ -235,9 +242,16 @@ func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sort
sess.Desc("created")
}
- var issues []Issue
- err := sess.Find(&issues)
- return issues, err
+ if isMention {
+ queryStr := "issue.id = issue_user.issue_id AND issue_user.is_mentioned=1"
+ if uid > 0 {
+ queryStr += " AND issue_user.uid = " + com.ToStr(uid)
+ }
+ sess.Join("INNER", "issue_user", queryStr)
+ }
+
+ issues := make([]*Issue, 0, setting.IssuePagingNum)
+ return issues, sess.Find(&issues)
}
type IssueStatus int
@@ -248,10 +262,9 @@ const (
)
// GetIssuesByLabel returns a list of issues by given label and repository.
-func GetIssuesByLabel(repoId int64, label string) ([]*Issue, error) {
+func GetIssuesByLabel(repoID, labelID int64) ([]*Issue, error) {
issues := make([]*Issue, 0, 10)
- err := x.Where("repo_id=?", repoId).And("label_ids like '%$" + label + "|%'").Find(&issues)
- return issues, err
+ return issues, x.Where("repo_id=?", repoID).And("label_ids like '%$" + com.ToStr(labelID) + "|%'").Find(&issues)
}
// GetIssueCountByPoster returns number of issues of repository by poster.
@@ -281,8 +294,9 @@ type IssueUser struct {
IsClosed bool
}
+// FIXME: organization
// NewIssueUserPairs adds new issue-user pairs for new issue of repository.
-func NewIssueUserPairs(repo *Repository, issueID, orgID, posterID, assigneeID int64) (err error) {
+func NewIssueUserPairs(repo *Repository, issueID, orgID, posterID, assigneeID int64) error {
users, err := repo.GetCollaborators()
if err != nil {
return err
@@ -295,6 +309,7 @@ func NewIssueUserPairs(repo *Repository, issueID, orgID, posterID, assigneeID in
isNeedAddPoster := true
for _, u := range users {
+ iu.Id = 0
iu.Uid = u.Id
iu.IsPoster = iu.Uid == posterID
if isNeedAddPoster && iu.IsPoster {
@@ -306,6 +321,7 @@ func NewIssueUserPairs(repo *Repository, issueID, orgID, posterID, assigneeID in
}
}
if isNeedAddPoster {
+ iu.Id = 0
iu.Uid = posterID
iu.IsPoster = true
iu.IsAssigned = iu.Uid == assigneeID
@@ -314,13 +330,24 @@ func NewIssueUserPairs(repo *Repository, issueID, orgID, posterID, assigneeID in
}
}
+ // Add owner's as well.
+ if repo.OwnerId != posterID {
+ iu.Id = 0
+ iu.Uid = repo.OwnerId
+ iu.IsAssigned = iu.Uid == assigneeID
+ if _, err = x.Insert(iu); err != nil {
+ return err
+ }
+ }
+
return nil
}
// PairsContains returns true when pairs list contains given issue.
-func PairsContains(ius []*IssueUser, issueId int64) int {
+func PairsContains(ius []*IssueUser, issueId, uid int64) int {
for i := range ius {
- if ius[i].IssueId == issueId {
+ if ius[i].IssueId == issueId &&
+ ius[i].Uid == uid {
return i
}
}
@@ -387,53 +414,57 @@ type IssueStats struct {
// Filter modes.
const (
- FM_ASSIGN = iota + 1
+ FM_ALL = iota
+ FM_ASSIGN
FM_CREATE
FM_MENTION
)
// GetIssueStats returns issue statistic information by given conditions.
-func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStats {
+func GetIssueStats(repoID, uid, labelID, milestoneID int64, isShowClosed bool, filterMode int) *IssueStats {
stats := &IssueStats{}
issue := new(Issue)
- tmpSess := &xorm.Session{}
-
- sess := x.Where("repo_id=?", rid)
- *tmpSess = *sess
- stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
- *tmpSess = *sess
- stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
- if isShowClosed {
- stats.AllCount = stats.ClosedCount
- } else {
- stats.AllCount = stats.OpenCount
- }
-
- if filterMode != FM_MENTION {
- sess = x.Where("repo_id=?", rid)
- switch filterMode {
- case FM_ASSIGN:
- sess.And("assignee_id=?", uid)
- case FM_CREATE:
- sess.And("poster_id=?", uid)
- default:
- goto nofilter
+
+ queryStr := "issue.repo_id=? AND issue.is_closed=?"
+ if labelID > 0 {
+ queryStr += " AND issue.label_ids like '%$" + com.ToStr(labelID) + "|%'"
+ }
+ if milestoneID > 0 {
+ queryStr += " AND milestone_id=" + com.ToStr(milestoneID)
+ }
+ switch filterMode {
+ case FM_ALL:
+ stats.OpenCount, _ = x.Where(queryStr, repoID, false).Count(issue)
+ stats.ClosedCount, _ = x.Where(queryStr, repoID, true).Count(issue)
+ return stats
+
+ case FM_ASSIGN:
+ queryStr += " AND assignee_id=?"
+ stats.OpenCount, _ = x.Where(queryStr, repoID, false, uid).Count(issue)
+ stats.ClosedCount, _ = x.Where(queryStr, repoID, true, uid).Count(issue)
+ return stats
+
+ case FM_CREATE:
+ queryStr += " AND poster_id=?"
+ stats.OpenCount, _ = x.Where(queryStr, repoID, false, uid).Count(issue)
+ stats.ClosedCount, _ = x.Where(queryStr, repoID, true, uid).Count(issue)
+ return stats
+
+ case FM_MENTION:
+ queryStr += " AND uid=? AND is_mentioned=?"
+ if labelID > 0 {
+ stats.OpenCount, _ = x.Where(queryStr, repoID, false, uid, true).
+ Join("INNER", "issue", "issue.id = issue_id").Count(new(IssueUser))
+ stats.ClosedCount, _ = x.Where(queryStr, repoID, true, uid, true).
+ Join("INNER", "issue", "issue.id = issue_id").Count(new(IssueUser))
+ return stats
}
- *tmpSess = *sess
- stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
- *tmpSess = *sess
- stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
- } else {
- sess := x.Where("repo_id=?", rid).And("uid=?", uid).And("is_mentioned=?", true)
- *tmpSess = *sess
- stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssueUser))
- *tmpSess = *sess
- stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssueUser))
- }
-nofilter:
- stats.AssignCount, _ = x.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("assignee_id=?", uid).Count(issue)
- stats.CreateCount, _ = x.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("poster_id=?", uid).Count(issue)
- stats.MentionCount, _ = x.Where("repo_id=?", rid).And("uid=?", uid).And("is_closed=?", isShowClosed).And("is_mentioned=?", true).Count(new(IssueUser))
+
+ queryStr = strings.Replace(queryStr, "issue.", "", 2)
+ stats.OpenCount, _ = x.Where(queryStr, repoID, false, uid, true).Count(new(IssueUser))
+ stats.ClosedCount, _ = x.Where(queryStr, repoID, true, uid, true).Count(new(IssueUser))
+ return stats
+ }
return stats
}
@@ -448,7 +479,7 @@ func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
// UpdateIssue updates information of issue.
func UpdateIssue(issue *Issue) error {
- _, err := x.Id(issue.Id).AllCols().Update(issue)
+ _, err := x.Id(issue.ID).AllCols().Update(issue)
if err != nil {
return err
@@ -518,7 +549,7 @@ func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
// Label represents a label of repository for issues.
type Label struct {
- Id int64
+ ID int64 `xorm:"pk autoincr"`
RepoId int64 `xorm:"INDEX"`
Name string
Color string `xorm:"VARCHAR(7)"`
@@ -545,7 +576,7 @@ func GetLabelById(id int64) (*Label, error) {
return nil, ErrLabelNotExist
}
- l := &Label{Id: id}
+ l := &Label{ID: id}
has, err := x.Get(l)
if err != nil {
return nil, err
@@ -564,14 +595,13 @@ func GetLabels(repoId int64) ([]*Label, error) {
// UpdateLabel updates label information.
func UpdateLabel(l *Label) error {
- _, err := x.Id(l.Id).AllCols().Update(l)
+ _, err := x.Id(l.ID).AllCols().Update(l)
return err
}
// DeleteLabel delete a label of given repository.
-func DeleteLabel(repoId int64, strId string) error {
- id, _ := com.StrTo(strId).Int64()
- l, err := GetLabelById(id)
+func DeleteLabel(repoID, labelID int64) error {
+ l, err := GetLabelById(labelID)
if err != nil {
if err == ErrLabelNotExist {
return nil
@@ -579,27 +609,25 @@ func DeleteLabel(repoId int64, strId string) error {
return err
}
- issues, err := GetIssuesByLabel(repoId, strId)
+ issues, err := GetIssuesByLabel(repoID, labelID)
if err != nil {
return err
}
sess := x.NewSession()
- defer sess.Close()
+ defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
for _, issue := range issues {
- issue.LabelIds = strings.Replace(issue.LabelIds, "$"+strId+"|", "", -1)
- if _, err = sess.Id(issue.Id).AllCols().Update(issue); err != nil {
- sess.Rollback()
+ issue.LabelIds = strings.Replace(issue.LabelIds, "$"+com.ToStr(labelID)+"|", "", -1)
+ if _, err = sess.Id(issue.ID).AllCols().Update(issue); err != nil {
return err
}
}
if _, err = sess.Delete(l); err != nil {
- sess.Rollback()
return err
}
return sess.Commit()
@@ -614,9 +642,8 @@ func DeleteLabel(repoId int64, strId string) error {
// Milestone represents a milestone of repository.
type Milestone struct {
- Id int64
- RepoId int64 `xorm:"INDEX"`
- Index int64
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX"`
Name string
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
@@ -627,9 +654,23 @@ type Milestone struct {
Completeness int // Percentage(1-100).
Deadline time.Time
DeadlineString string `xorm:"-"`
+ IsOverDue bool `xorm:"-"`
ClosedDate time.Time
}
+func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
+ if colName == "deadline" {
+ if m.Deadline.Year() == 9999 {
+ return
+ }
+
+ m.DeadlineString = m.Deadline.Format("2006-01-02")
+ if time.Now().After(m.Deadline) {
+ m.IsOverDue = true
+ }
+ }
+}
+
// CalOpenIssues calculates the open issues of milestone.
func (m *Milestone) CalOpenIssues() {
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
@@ -638,101 +679,119 @@ func (m *Milestone) CalOpenIssues() {
// NewMilestone creates new milestone of repository.
func NewMilestone(m *Milestone) (err error) {
sess := x.NewSession()
- defer sess.Close()
+ defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Insert(m); err != nil {
- sess.Rollback()
return err
}
- rawSql := "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?"
- if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
- sess.Rollback()
+ if _, err = sess.Exec("UPDATE `repository` SET num_milestones=num_milestones+1 WHERE id=?", m.RepoID); err != nil {
return err
}
return sess.Commit()
}
-// GetMilestoneById returns the milestone by given ID.
-func GetMilestoneById(id int64) (*Milestone, error) {
- m := &Milestone{Id: id}
+// GetMilestoneByID returns the milestone of given ID.
+func GetMilestoneByID(id int64) (*Milestone, error) {
+ m := &Milestone{ID: id}
has, err := x.Get(m)
if err != nil {
return nil, err
} else if !has {
- return nil, ErrMilestoneNotExist
+ return nil, ErrMilestoneNotExist{id}
}
return m, nil
}
-// GetMilestoneByIndex returns the milestone of given repository and index.
-func GetMilestoneByIndex(repoId, idx int64) (*Milestone, error) {
- m := &Milestone{RepoId: repoId, Index: idx}
- has, err := x.Get(m)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrMilestoneNotExist
- }
- return m, nil
+// GetAllRepoMilestones returns all milestones of given repository.
+func GetAllRepoMilestones(repoID int64) ([]*Milestone, error) {
+ miles := make([]*Milestone, 0, 10)
+ return miles, x.Where("repo_id=?", repoID).Find(&miles)
}
// GetMilestones returns a list of milestones of given repository and status.
-func GetMilestones(repoId int64, isClosed bool) ([]*Milestone, error) {
- miles := make([]*Milestone, 0, 10)
- err := x.Where("repo_id=?", repoId).And("is_closed=?", isClosed).Find(&miles)
- return miles, err
+func GetMilestones(repoID int64, page int, isClosed bool) ([]*Milestone, error) {
+ miles := make([]*Milestone, 0, setting.IssuePagingNum)
+ sess := x.Where("repo_id=? AND is_closed=?", repoID, isClosed)
+ if page > 0 {
+ sess = sess.Limit(setting.IssuePagingNum, (page-1)*setting.IssuePagingNum)
+ }
+ return miles, sess.Find(&miles)
+}
+
+func updateMilestone(e Engine, m *Milestone) error {
+ _, err := e.Id(m.ID).AllCols().Update(m)
+ return err
}
// UpdateMilestone updates information of given milestone.
func UpdateMilestone(m *Milestone) error {
- _, err := x.Id(m.Id).Update(m)
- return err
+ return updateMilestone(x, m)
+}
+
+func countRepoMilestones(e Engine, repoID int64) int64 {
+ count, _ := e.Where("repo_id=?", repoID).Count(new(Milestone))
+ return count
+}
+
+// CountRepoMilestones returns number of milestones in given repository.
+func CountRepoMilestones(repoID int64) int64 {
+ return countRepoMilestones(x, repoID)
+}
+
+func countRepoClosedMilestones(e Engine, repoID int64) int64 {
+ closed, _ := e.Where("repo_id=? AND is_closed=?", repoID, true).Count(new(Milestone))
+ return closed
+}
+
+// CountRepoClosedMilestones returns number of closed milestones in given repository.
+func CountRepoClosedMilestones(repoID int64) int64 {
+ return countRepoClosedMilestones(x, repoID)
+}
+
+// MilestoneStats returns number of open and closed milestones of given repository.
+func MilestoneStats(repoID int64) (open int64, closed int64) {
+ open, _ = x.Where("repo_id=? AND is_closed=?", repoID, false).Count(new(Milestone))
+ return open, CountRepoClosedMilestones(repoID)
}
// ChangeMilestoneStatus changes the milestone open/closed status.
func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
- repo, err := GetRepositoryById(m.RepoId)
+ repo, err := GetRepositoryById(m.RepoID)
if err != nil {
return err
}
sess := x.NewSession()
- defer sess.Close()
+ defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
m.IsClosed = isClosed
- if _, err = sess.Id(m.Id).AllCols().Update(m); err != nil {
- sess.Rollback()
+ if err = updateMilestone(sess, m); err != nil {
return err
}
- if isClosed {
- repo.NumClosedMilestones++
- } else {
- repo.NumClosedMilestones--
- }
- if _, err = sess.Id(repo.Id).Update(repo); err != nil {
- sess.Rollback()
+ repo.NumMilestones = int(countRepoMilestones(sess, repo.Id))
+ repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.Id))
+ if _, err = sess.Id(repo.Id).AllCols().Update(repo); err != nil {
return err
}
return sess.Commit()
}
-// ChangeMilestoneIssueStats updates the open/closed issues counter and progress for the
-// milestone associated witht the given issue.
+// ChangeMilestoneIssueStats updates the open/closed issues counter and progress
+// for the milestone associated witht the given issue.
func ChangeMilestoneIssueStats(issue *Issue) error {
- if issue.MilestoneId == 0 {
+ if issue.MilestoneID == 0 {
return nil
}
- m, err := GetMilestoneById(issue.MilestoneId)
-
+ m, err := GetMilestoneByID(issue.MilestoneID)
if err != nil {
return err
}
@@ -759,7 +818,7 @@ func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
}
if oldMid > 0 {
- m, err := GetMilestoneById(oldMid)
+ m, err := GetMilestoneByID(oldMid)
if err != nil {
return err
}
@@ -774,20 +833,20 @@ func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
m.Completeness = 0
}
- if _, err = sess.Id(m.Id).Cols("num_issues,num_completeness,num_closed_issues").Update(m); err != nil {
+ if _, err = sess.Id(m.ID).Cols("num_issues,num_completeness,num_closed_issues").Update(m); err != nil {
sess.Rollback()
return err
}
rawSql := "UPDATE `issue_user` SET milestone_id = 0 WHERE issue_id = ?"
- if _, err = sess.Exec(rawSql, issue.Id); err != nil {
+ if _, err = sess.Exec(rawSql, issue.ID); err != nil {
sess.Rollback()
return err
}
}
if mid > 0 {
- m, err := GetMilestoneById(mid)
+ m, err := GetMilestoneByID(mid)
if err != nil {
return err
}
@@ -802,13 +861,13 @@ func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
}
m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
- if _, err = sess.Id(m.Id).Cols("num_issues,num_completeness,num_closed_issues").Update(m); err != nil {
+ if _, err = sess.Id(m.ID).Cols("num_issues,num_completeness,num_closed_issues").Update(m); err != nil {
sess.Rollback()
return err
}
rawSql := "UPDATE `issue_user` SET milestone_id = ? WHERE issue_id = ?"
- if _, err = sess.Exec(rawSql, m.Id, issue.Id); err != nil {
+ if _, err = sess.Exec(rawSql, m.ID, issue.ID); err != nil {
sess.Rollback()
return err
}
@@ -817,34 +876,40 @@ func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
return sess.Commit()
}
-// DeleteMilestone deletes a milestone.
-func DeleteMilestone(m *Milestone) (err error) {
- sess := x.NewSession()
- defer sess.Close()
- if err = sess.Begin(); err != nil {
+// DeleteMilestoneByID deletes a milestone by given ID.
+func DeleteMilestoneByID(mid int64) error {
+ m, err := GetMilestoneByID(mid)
+ if err != nil {
+ if IsErrMilestoneNotExist(err) {
+ return nil
+ }
return err
}
- if _, err = sess.Delete(m); err != nil {
- sess.Rollback()
+ repo, err := GetRepositoryById(m.RepoID)
+ if err != nil {
return err
}
- rawSql := "UPDATE `repository` SET num_milestones = num_milestones - 1 WHERE id = ?"
- if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
- sess.Rollback()
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
return err
}
- rawSql = "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?"
- if _, err = sess.Exec(rawSql, m.Id); err != nil {
- sess.Rollback()
+ if _, err = sess.Id(m.ID).Delete(m); err != nil {
return err
}
- rawSql = "UPDATE `issue_user` SET milestone_id = 0 WHERE milestone_id = ?"
- if _, err = sess.Exec(rawSql, m.Id); err != nil {
- sess.Rollback()
+ repo.NumMilestones = int(countRepoMilestones(sess, repo.Id))
+ repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.Id))
+ if _, err = sess.Id(repo.Id).AllCols().Update(repo); err != nil {
+ return err
+ }
+
+ if _, err = sess.Exec("UPDATE `issue` SET milestone_id=0 WHERE milestone_id=?", m.ID); err != nil {
+ return err
+ } else if _, err = sess.Exec("UPDATE `issue_user` SET milestone_id=0 WHERE milestone_id=?", m.ID); err != nil {
return err
}
return sess.Commit()
@@ -890,7 +955,7 @@ type Comment struct {
// CreateComment creates comment of issue or commit.
func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType CommentType, content string, attachments []int64) (*Comment, error) {
sess := x.NewSession()
- defer sess.Close()
+ defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
return nil, err
}
@@ -899,7 +964,6 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
CommitId: commitId, Line: line, Content: content}
if _, err := sess.Insert(comment); err != nil {
- sess.Rollback()
return nil, err
}
@@ -908,7 +972,6 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
case COMMENT_TYPE_COMMENT:
rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, issueId); err != nil {
- sess.Rollback()
return nil, err
}
@@ -922,20 +985,17 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
}
if _, err := sess.Exec(rawSql, comment.Id, strings.Join(astrs, ",")); err != nil {
- sess.Rollback()
return nil, err
}
}
case COMMENT_TYPE_REOPEN:
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, repoId); err != nil {
- sess.Rollback()
return nil, err
}
case COMMENT_TYPE_CLOSE:
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, repoId); err != nil {
- sess.Rollback()
return nil, err
}
}
diff --git a/models/login.go b/models/login.go
index 916e2731..82cea35f 100644
--- a/models/login.go
+++ b/models/login.go
@@ -17,6 +17,7 @@ import (
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/auth/ldap"
+ "github.com/gogits/gogs/modules/auth/pam"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/uuid"
)
@@ -28,6 +29,7 @@ const (
PLAIN
LDAP
SMTP
+ PAM
)
var (
@@ -39,12 +41,14 @@ var (
var LoginTypes = map[LoginType]string{
LDAP: "LDAP",
SMTP: "SMTP",
+ PAM: "PAM",
}
// Ensure structs implemented interface.
var (
_ core.Conversion = &LDAPConfig{}
_ core.Conversion = &SMTPConfig{}
+ _ core.Conversion = &PAMConfig{}
)
type LDAPConfig struct {
@@ -74,6 +78,18 @@ func (cfg *SMTPConfig) ToDB() ([]byte, error) {
return json.Marshal(cfg)
}
+type PAMConfig struct {
+ ServiceName string // pam service (e.g. system-auth)
+}
+
+func (cfg *PAMConfig) FromDB(bs []byte) error {
+ return json.Unmarshal(bs, &cfg)
+}
+
+func (cfg *PAMConfig) ToDB() ([]byte, error) {
+ return json.Marshal(cfg)
+}
+
type LoginSource struct {
Id int64
Type LoginType
@@ -97,6 +113,10 @@ func (source *LoginSource) SMTP() *SMTPConfig {
return source.Cfg.(*SMTPConfig)
}
+func (source *LoginSource) PAM() *PAMConfig {
+ return source.Cfg.(*PAMConfig)
+}
+
func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
if colName == "type" {
ty := (*val).(int64)
@@ -105,6 +125,8 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
source.Cfg = new(LDAPConfig)
case SMTP:
source.Cfg = new(SMTPConfig)
+ case PAM:
+ source.Cfg = new(PAMConfig)
}
}
}
@@ -169,8 +191,8 @@ func UserSignIn(uname, passwd string) (*User, error) {
// For plain login, user must exist to reach this line.
// Now verify password.
if u.LoginType == PLAIN {
- if !u.ValidtePassword(passwd) {
- return nil, ErrUserNotExist
+ if !u.ValidatePassword(passwd) {
+ return nil, ErrUserNotExist{u.Id, u.Name}
}
return u, nil
}
@@ -197,10 +219,17 @@ func UserSignIn(uname, passwd string) (*User, error) {
return u, nil
}
log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err)
+ } else if source.Type == PAM {
+ u, err := LoginUserPAMSource(nil, uname, passwd,
+ source.Id, source.Cfg.(*PAMConfig), true)
+ if err == nil {
+ return u, nil
+ }
+ log.Warn("Fail to login(%s) by PAM(%s): %v", uname, source.Name, err)
}
}
- return nil, ErrUserNotExist
+ return nil, ErrUserNotExist{u.Id, u.Name}
}
var source LoginSource
@@ -218,6 +247,8 @@ func UserSignIn(uname, passwd string) (*User, error) {
return LoginUserLdapSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*LDAPConfig), false)
case SMTP:
return LoginUserSMTPSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*SMTPConfig), false)
+ case PAM:
+ return LoginUserPAMSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*PAMConfig), false)
}
return nil, ErrUnsupportedLoginType
}
@@ -230,7 +261,7 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP
name, fn, sn, mail, logged := cfg.Ldapsource.SearchEntry(name, passwd)
if !logged {
// User not in LDAP, do nothing
- return nil, ErrUserNotExist
+ return nil, ErrUserNotExist{u.Id, u.Name}
}
if !autoRegister {
return u, nil
@@ -331,7 +362,7 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP
if err := SmtpAuth(cfg.Host, cfg.Port, auth, cfg.TLS); err != nil {
if strings.Contains(err.Error(), "Username and Password not accepted") {
- return nil, ErrUserNotExist
+ return nil, ErrUserNotExist{u.Id, u.Name}
}
return nil, err
}
@@ -359,3 +390,33 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP
err := CreateUser(u)
return u, err
}
+
+// Query if name/passwd can login against PAM
+// Create a local user if success
+// Return the same LoginUserPlain semantic
+func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
+ if err := pam.PAMAuth(cfg.ServiceName, name, passwd); err != nil {
+ if strings.Contains(err.Error(), "Authentication failure") {
+ return nil, ErrUserNotExist{u.Id, u.Name}
+ }
+ return nil, err
+ }
+
+ if !autoRegister {
+ return u, nil
+ }
+
+ // fake a local user creation
+ u = &User{
+ LowerName: strings.ToLower(name),
+ Name: strings.ToLower(name),
+ LoginType: PAM,
+ LoginSource: sourceId,
+ LoginName: name,
+ IsActive: true,
+ Passwd: passwd,
+ Email: name,
+ }
+ err := CreateUser(u)
+ return u, err
+}
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 7ad0cbdd..c7900743 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -5,6 +5,7 @@
package migrations
import (
+ "encoding/json"
"fmt"
"strings"
"time"
@@ -51,11 +52,12 @@ type Version struct {
// If you want to "retire" a migration, remove it from the top of the list and
// update _MIN_VER_DB accordingly
var migrations = []Migration{
- NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1:v0.5.13
- NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2:v0.5.13
- NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3:v0.5.13
- NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4:v0.5.13
- NewMigration("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
+ NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1:v0.5.13
+ NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2:v0.5.13
+ NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3:v0.5.13
+ NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4:v0.5.13
+ NewMigration("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
+ NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
}
// Migrate database to current version
@@ -389,3 +391,65 @@ func fixLocaleFileLoadPanic(_ *xorm.Engine) error {
setting.Langs = strings.Split(strings.Replace(strings.Join(setting.Langs, ","), "fr-CA", "fr-FR", 1), ",")
return nil
}
+
+func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
+ type PushCommit struct {
+ Sha1 string
+ Message string
+ AuthorEmail string
+ AuthorName string
+ }
+
+ type PushCommits struct {
+ Len int
+ Commits []*PushCommit
+ CompareUrl string
+ }
+
+ type Action struct {
+ ID int64 `xorm:"pk autoincr"`
+ Content string `xorm:"TEXT"`
+ }
+
+ results, err := x.Query("SELECT `id`,`content` FROM `action` WHERE `op_type`=?", 5)
+ if err != nil {
+ return fmt.Errorf("select commit actions: %v", err)
+ }
+
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ var pushCommits *PushCommits
+ for _, action := range results {
+ actID := com.StrTo(string(action["id"])).MustInt64()
+ if actID == 0 {
+ continue
+ }
+
+ pushCommits = new(PushCommits)
+ if err = json.Unmarshal(action["content"], pushCommits); err != nil {
+ return fmt.Errorf("unmarshal action content[%s]: %v", actID, err)
+ }
+
+ infos := strings.Split(pushCommits.CompareUrl, "/")
+ if len(infos) <= 4 {
+ continue
+ }
+ pushCommits.CompareUrl = strings.Join(infos[len(infos)-4:], "/")
+
+ p, err := json.Marshal(pushCommits)
+ if err != nil {
+ return fmt.Errorf("marshal action content[%s]: %v", actID, err)
+ }
+
+ if _, err = sess.Id(actID).Update(&Action{
+ Content: string(p),
+ }); err != nil {
+ return fmt.Errorf("update action[%s]: %v", actID, err)
+ }
+ }
+ return sess.Commit()
+}
diff --git a/models/models.go b/models/models.go
index b7986fed..01b96c0f 100644
--- a/models/models.go
+++ b/models/models.go
@@ -55,7 +55,7 @@ var (
func init() {
tables = append(tables,
new(User), new(PublicKey), new(Oauth2), new(AccessToken),
- new(Repository), new(Collaboration), new(Access),
+ new(Repository), new(DeployKey), new(Collaboration), new(Access),
new(Watch), new(Star), new(Follow), new(Action),
new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone),
new(Mirror), new(Release), new(LoginSource), new(Webhook),
@@ -90,10 +90,10 @@ func getEngine() (*xorm.Engine, error) {
switch DbCfg.Type {
case "mysql":
if DbCfg.Host[0] == '/' { // looks like a unix socket
- cnnstr = fmt.Sprintf("%s:%s@unix(%s)/%s?charset=utf8",
+ cnnstr = fmt.Sprintf("%s:%s@unix(%s)/%s?charset=utf8&parseTime=true",
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name)
} else {
- cnnstr = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8",
+ cnnstr = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true",
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name)
}
case "postgres":
@@ -105,7 +105,7 @@ func getEngine() (*xorm.Engine, error) {
if len(fields) > 1 && len(strings.TrimSpace(fields[1])) > 0 {
port = fields[1]
}
- cnnstr = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s",
+ cnnstr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s",
DbCfg.User, DbCfg.Passwd, host, port, DbCfg.Name, DbCfg.SSLMode)
case "sqlite3":
if !EnableSQLite3 {
@@ -122,7 +122,7 @@ func getEngine() (*xorm.Engine, error) {
func NewTestEngine(x *xorm.Engine) (err error) {
x, err = getEngine()
if err != nil {
- return fmt.Errorf("connect to database: %v", err)
+ return fmt.Errorf("Connect to database: %v", err)
}
x.SetMapper(core.GonicMapper{})
@@ -132,7 +132,7 @@ func NewTestEngine(x *xorm.Engine) (err error) {
func SetEngine() (err error) {
x, err = getEngine()
if err != nil {
- return fmt.Errorf("connect to database: %v", err)
+ return fmt.Errorf("Fail to connect to database: %v", err)
}
x.SetMapper(core.GonicMapper{})
@@ -144,7 +144,7 @@ func SetEngine() (err error) {
f, err := os.Create(logPath)
if err != nil {
- return fmt.Errorf("models.init(fail to create xorm.log): %v", err)
+ return fmt.Errorf("Fail to create xorm.log: %v", err)
}
x.SetLogger(xorm.NewSimpleLogger(f))
diff --git a/models/org.go b/models/org.go
index 32f135cb..3caed30b 100644
--- a/models/org.go
+++ b/models/org.go
@@ -105,23 +105,23 @@ func IsOrgEmailUsed(email string) (bool, error) {
}
// CreateOrganization creates record of a new organization.
-func CreateOrganization(org, owner *User) (*User, error) {
- if !IsLegalName(org.Name) {
- return nil, ErrUserNameIllegal
+func CreateOrganization(org, owner *User) (err error) {
+ if err = IsUsableName(org.Name); err != nil {
+ return err
}
isExist, err := IsUserExist(0, org.Name)
if err != nil {
- return nil, err
+ return err
} else if isExist {
- return nil, ErrUserAlreadyExist
+ return ErrUserAlreadyExist{org.Name}
}
isExist, err = IsOrgEmailUsed(org.Email)
if err != nil {
- return nil, err
+ return err
} else if isExist {
- return nil, ErrEmailAlreadyUsed
+ return ErrEmailAlreadyUsed{org.Email}
}
org.LowerName = strings.ToLower(org.Name)
@@ -135,11 +135,11 @@ func CreateOrganization(org, owner *User) (*User, error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
- return nil, err
+ return err
}
if _, err = sess.Insert(org); err != nil {
- return nil, err
+ return fmt.Errorf("insert organization: %v", err)
}
// Create default owner team.
@@ -151,7 +151,7 @@ func CreateOrganization(org, owner *User) (*User, error) {
NumMembers: 1,
}
if _, err = sess.Insert(t); err != nil {
- return nil, err
+ return fmt.Errorf("insert owner team: %v", err)
}
// Add initial creator to organization and owner team.
@@ -162,7 +162,7 @@ func CreateOrganization(org, owner *User) (*User, error) {
NumTeams: 1,
}
if _, err = sess.Insert(ou); err != nil {
- return nil, err
+ return fmt.Errorf("insert org-user relation: %v", err)
}
tu := &TeamUser{
@@ -171,14 +171,14 @@ func CreateOrganization(org, owner *User) (*User, error) {
TeamID: t.ID,
}
if _, err = sess.Insert(tu); err != nil {
- return nil, err
+ return fmt.Errorf("insert team-user relation: %v", err)
}
if err = os.MkdirAll(UserPath(org.Name), os.ModePerm); err != nil {
- return nil, err
+ return fmt.Errorf("create directory: %v", err)
}
- return org, sess.Commit()
+ return sess.Commit()
}
// GetOrgByName returns organization by given name.
@@ -594,9 +594,9 @@ func (t *Team) RemoveRepository(repoID int64) error {
// NewTeam creates a record of new team.
// It's caller's responsibility to assign organization ID.
-func NewTeam(t *Team) error {
- if !IsLegalName(t.Name) {
- return ErrTeamNameIllegal
+func NewTeam(t *Team) (err error) {
+ if err = IsUsableName(t.Name); err != nil {
+ return err
}
has, err := x.Id(t.OrgID).Get(new(User))
@@ -670,8 +670,8 @@ func GetTeamById(teamId int64) (*Team, error) {
// UpdateTeam updates information of team.
func UpdateTeam(t *Team, authChanged bool) (err error) {
- if !IsLegalName(t.Name) {
- return ErrTeamNameIllegal
+ if err = IsUsableName(t.Name); err != nil {
+ return err
}
if len(t.Description) > 255 {
diff --git a/models/publickey.go b/models/publickey.go
index 0db9f333..70da8057 100644
--- a/models/publickey.go
+++ b/models/publickey.go
@@ -21,6 +21,7 @@ import (
"time"
"github.com/Unknwon/com"
+ "github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/process"
@@ -33,8 +34,6 @@ const (
)
var (
- ErrKeyAlreadyExist = errors.New("Public key already exists")
- ErrKeyNotExist = errors.New("Public key does not exist")
ErrKeyUnableVerify = errors.New("Unable to verify public key")
)
@@ -78,17 +77,34 @@ func init() {
}
}
-// PublicKey represents a SSH key.
+type KeyType int
+
+const (
+ KEY_TYPE_USER = iota + 1
+ KEY_TYPE_DEPLOY
+)
+
+// PublicKey represents a SSH or deploy key.
type PublicKey struct {
- Id int64
- OwnerId int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
- Name string `xorm:"UNIQUE(s) NOT NULL"`
- Fingerprint string `xorm:"INDEX NOT NULL"`
- Content string `xorm:"TEXT NOT NULL"`
- Created time.Time `xorm:"CREATED"`
- Updated time.Time
- HasRecentActivity bool `xorm:"-"`
- HasUsed bool `xorm:"-"`
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"INDEX NOT NULL"`
+ Name string `xorm:"NOT NULL"`
+ Fingerprint string `xorm:"NOT NULL"`
+ Content string `xorm:"TEXT NOT NULL"`
+ Mode AccessMode `xorm:"NOT NULL DEFAULT 2"`
+ Type KeyType `xorm:"NOT NULL DEFAULT 1"`
+ Created time.Time `xorm:"CREATED"`
+ Updated time.Time // Note: Updated must below Created for AfterSet.
+ HasRecentActivity bool `xorm:"-"`
+ HasUsed bool `xorm:"-"`
+}
+
+func (k *PublicKey) AfterSet(colName string, _ xorm.Cell) {
+ switch colName {
+ case "created":
+ k.HasUsed = k.Updated.After(k.Created)
+ k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now())
+ }
}
// OmitEmail returns content of public key but without e-mail address.
@@ -98,7 +114,7 @@ func (k *PublicKey) OmitEmail() string {
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
func (key *PublicKey) GetAuthorizedString() string {
- return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.Id, setting.CustomConf, key.Content)
+ return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.ID, setting.CustomConf, key.Content)
}
var minimumKeySizes = map[string]int{
@@ -126,8 +142,8 @@ func extractTypeFromBase64Key(key string) (string, error) {
return string(b[4 : 4+keyLength]), nil
}
-// Parse any key string in openssh or ssh2 format to clean openssh string (rfc4253)
-func ParseKeyString(content string) (string, error) {
+// parseKeyString parses any key string in openssh or ssh2 format to clean openssh string (rfc4253)
+func parseKeyString(content string) (string, error) {
// Transform all legal line endings to a single "\n"
s := strings.Replace(strings.Replace(strings.TrimSpace(content), "\r\n", "\n", -1), "\r", "\n", -1)
@@ -190,16 +206,21 @@ func ParseKeyString(content string) (string, error) {
}
// CheckPublicKeyString checks if the given public key string is recognized by SSH.
-func CheckPublicKeyString(content string) (bool, error) {
+func CheckPublicKeyString(content string) (_ string, err error) {
+ content, err = parseKeyString(content)
+ if err != nil {
+ return "", err
+ }
+
content = strings.TrimRight(content, "\n\r")
if strings.ContainsAny(content, "\n\r") {
- return false, errors.New("only a single line with a single key please")
+ return "", errors.New("only a single line with a single key please")
}
// write the key to a file…
tmpFile, err := ioutil.TempFile(os.TempDir(), "keytest")
if err != nil {
- return false, err
+ return "", err
}
tmpPath := tmpFile.Name()
defer os.Remove(tmpPath)
@@ -209,37 +230,36 @@ func CheckPublicKeyString(content string) (bool, error) {
// Check if ssh-keygen recognizes its contents.
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-l", "-f", tmpPath)
if err != nil {
- return false, errors.New("ssh-keygen -l -f: " + stderr)
+ return "", errors.New("ssh-keygen -l -f: " + stderr)
} else if len(stdout) < 2 {
- return false, errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
+ return "", errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
}
// The ssh-keygen in Windows does not print key type, so no need go further.
if setting.IsWindows {
- return true, nil
+ return content, nil
}
- fmt.Println(stdout)
sshKeygenOutput := strings.Split(stdout, " ")
if len(sshKeygenOutput) < 4 {
- return false, ErrKeyUnableVerify
+ return content, ErrKeyUnableVerify
}
// Check if key type and key size match.
if !setting.Service.DisableMinimumKeySizeCheck {
keySize := com.StrTo(sshKeygenOutput[0]).MustInt()
if keySize == 0 {
- return false, errors.New("cannot get key size of the given key")
+ return "", errors.New("cannot get key size of the given key")
}
keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1])
if minimumKeySize := minimumKeySizes[keyType]; minimumKeySize == 0 {
- return false, errors.New("sorry, unrecognized public key type")
+ return "", errors.New("sorry, unrecognized public key type")
} else if keySize < minimumKeySize {
- return false, fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
+ return "", fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
}
}
- return true, nil
+ return content, nil
}
// saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
@@ -278,20 +298,23 @@ func saveAuthorizedKeyFile(keys ...*PublicKey) error {
return nil
}
-// AddPublicKey adds new public key to database and authorized_keys file.
-func AddPublicKey(key *PublicKey) (err error) {
- has, err := x.Get(key)
+func checkKeyContent(content string) error {
+ // Same key can only be added once.
+ has, err := x.Where("content=?", content).Get(new(PublicKey))
if err != nil {
return err
} else if has {
- return ErrKeyAlreadyExist
+ return ErrKeyAlreadyExist{0, content}
}
+ return nil
+}
+func addKey(e Engine, key *PublicKey) (err error) {
// Calculate fingerprint.
tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
"id_rsa.pub"), "\\", "/", -1)
os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
- if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil {
+ if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil {
return err
}
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-l", "-f", tmpPath)
@@ -301,32 +324,56 @@ func AddPublicKey(key *PublicKey) (err error) {
return errors.New("not enough output for calculating fingerprint: " + stdout)
}
key.Fingerprint = strings.Split(stdout, " ")[1]
- if has, err := x.Get(&PublicKey{Fingerprint: key.Fingerprint}); err == nil && has {
- return ErrKeyAlreadyExist
- }
// Save SSH key.
- if _, err = x.Insert(key); err != nil {
+ if _, err = e.Insert(key); err != nil {
return err
- } else if err = saveAuthorizedKeyFile(key); err != nil {
- // Roll back.
- if _, err2 := x.Delete(key); err2 != nil {
- return err2
- }
+ }
+ return saveAuthorizedKeyFile(key)
+}
+
+// AddPublicKey adds new public key to database and authorized_keys file.
+func AddPublicKey(ownerID int64, name, content string) (err error) {
+ if err = checkKeyContent(content); err != nil {
return err
}
- return nil
+ // Key name of same user cannot be duplicated.
+ has, err := x.Where("owner_id=? AND name=?", ownerID, name).Get(new(PublicKey))
+ if err != nil {
+ return err
+ } else if has {
+ return ErrKeyNameAlreadyUsed{ownerID, name}
+ }
+
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ key := &PublicKey{
+ OwnerID: ownerID,
+ Name: name,
+ Content: content,
+ Mode: ACCESS_MODE_WRITE,
+ Type: KEY_TYPE_USER,
+ }
+ if err = addKey(sess, key); err != nil {
+ return fmt.Errorf("addKey: %v", err)
+ }
+
+ return sess.Commit()
}
-// GetPublicKeyById returns public key by given ID.
-func GetPublicKeyById(keyId int64) (*PublicKey, error) {
+// GetPublicKeyByID returns public key by given ID.
+func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
key := new(PublicKey)
- has, err := x.Id(keyId).Get(key)
+ has, err := x.Id(keyID).Get(key)
if err != nil {
return nil, err
} else if !has {
- return nil, ErrKeyNotExist
+ return nil, ErrKeyNotExist{keyID}
}
return key, nil
}
@@ -334,16 +381,7 @@ func GetPublicKeyById(keyId int64) (*PublicKey, error) {
// ListPublicKeys returns a list of public keys belongs to given user.
func ListPublicKeys(uid int64) ([]*PublicKey, error) {
keys := make([]*PublicKey, 0, 5)
- err := x.Where("owner_id=?", uid).Find(&keys)
- if err != nil {
- return nil, err
- }
-
- for _, key := range keys {
- key.HasUsed = key.Updated.After(key.Created)
- key.HasRecentActivity = key.Updated.Add(7 * 24 * time.Hour).After(time.Now())
- }
- return keys, nil
+ return keys, x.Where("owner_id=?", uid).Find(&keys)
}
// rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
@@ -364,7 +402,7 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
defer fw.Close()
isFound := false
- keyword := fmt.Sprintf("key-%d", key.Id)
+ keyword := fmt.Sprintf("key-%d", key.ID)
buf := bufio.NewReader(fr)
for {
line, errRead := buf.ReadString('\n')
@@ -401,20 +439,19 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
// UpdatePublicKey updates given public key.
func UpdatePublicKey(key *PublicKey) error {
- _, err := x.Id(key.Id).AllCols().Update(key)
+ _, err := x.Id(key.ID).AllCols().Update(key)
return err
}
-// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
-func DeletePublicKey(key *PublicKey) error {
- has, err := x.Get(key)
+func deletePublicKey(e *xorm.Session, key *PublicKey) error {
+ has, err := e.Get(key)
if err != nil {
return err
} else if !has {
- return ErrKeyNotExist
+ return nil
}
- if _, err = x.Delete(key); err != nil {
+ if _, err = e.Id(key.ID).Delete(key); err != nil {
return err
}
@@ -428,6 +465,21 @@ func DeletePublicKey(key *PublicKey) error {
return os.Rename(tmpPath, fpath)
}
+// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
+func DeletePublicKey(key *PublicKey) (err error) {
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if err = deletePublicKey(sess, key); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
+
// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
func RewriteAllPublicKeys() error {
sshOpLocker.Lock()
@@ -461,3 +513,162 @@ func RewriteAllPublicKeys() error {
return nil
}
+
+// ________ .__ ____ __.
+// \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
+// | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
+// | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
+// /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
+// \/ \/|__| \/ \/ \/\/
+
+// DeployKey represents deploy key information and its relation with repository.
+type DeployKey struct {
+ ID int64 `xorm:"pk autoincr"`
+ KeyID int64 `xorm:"UNIQUE(s) INDEX"`
+ RepoID int64 `xorm:"UNIQUE(s) INDEX"`
+ Name string
+ Fingerprint string
+ Created time.Time `xorm:"CREATED"`
+ Updated time.Time // Note: Updated must below Created for AfterSet.
+ HasRecentActivity bool `xorm:"-"`
+ HasUsed bool `xorm:"-"`
+}
+
+func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) {
+ switch colName {
+ case "created":
+ k.HasUsed = k.Updated.After(k.Created)
+ k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now())
+ }
+}
+
+func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
+ // Note: We want error detail, not just true or false here.
+ has, err := e.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
+ if err != nil {
+ return err
+ } else if has {
+ return ErrDeployKeyAlreadyExist{keyID, repoID}
+ }
+
+ has, err = e.Where("repo_id=? AND name=?", repoID, name).Get(new(DeployKey))
+ if err != nil {
+ return err
+ } else if has {
+ return ErrDeployKeyNameAlreadyUsed{repoID, name}
+ }
+
+ return nil
+}
+
+// addDeployKey adds new key-repo relation.
+func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (err error) {
+ if err = checkDeployKey(e, keyID, repoID, name); err != nil {
+ return err
+ }
+
+ _, err = e.Insert(&DeployKey{
+ KeyID: keyID,
+ RepoID: repoID,
+ Name: name,
+ Fingerprint: fingerprint,
+ })
+ return err
+}
+
+// HasDeployKey returns true if public key is a deploy key of given repository.
+func HasDeployKey(keyID, repoID int64) bool {
+ has, _ := x.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
+ return has
+}
+
+// AddDeployKey add new deploy key to database and authorized_keys file.
+func AddDeployKey(repoID int64, name, content string) (err error) {
+ if err = checkKeyContent(content); err != nil {
+ return err
+ }
+
+ key := &PublicKey{
+ Content: content,
+ Mode: ACCESS_MODE_READ,
+ Type: KEY_TYPE_DEPLOY,
+ }
+ has, err := x.Get(key)
+ if err != nil {
+ return err
+ }
+
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ // First time use this deploy key.
+ if !has {
+ if err = addKey(sess, key); err != nil {
+ return nil
+ }
+ }
+
+ if err = addDeployKey(sess, key.ID, repoID, name, key.Fingerprint); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
+
+// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
+func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
+ key := &DeployKey{
+ KeyID: keyID,
+ RepoID: repoID,
+ }
+ _, err := x.Get(key)
+ return key, err
+}
+
+// UpdateDeployKey updates deploy key information.
+func UpdateDeployKey(key *DeployKey) error {
+ _, err := x.Id(key.ID).AllCols().Update(key)
+ return err
+}
+
+// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
+func DeleteDeployKey(id int64) error {
+ key := &DeployKey{ID: id}
+ has, err := x.Id(key.ID).Get(key)
+ if err != nil {
+ return err
+ } else if !has {
+ return nil
+ }
+
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if _, err = sess.Id(key.ID).Delete(key); err != nil {
+ return fmt.Errorf("delete deploy key[%d]: %v", key.ID, err)
+ }
+
+ // Check if this is the last reference to same key content.
+ has, err = sess.Where("key_id=?", key.KeyID).Get(new(DeployKey))
+ if err != nil {
+ return err
+ } else if !has {
+ if err = deletePublicKey(sess, &PublicKey{ID: key.KeyID}); err != nil {
+ return err
+ }
+ }
+
+ return sess.Commit()
+}
+
+// ListDeployKeys returns all deploy keys by given repository ID.
+func ListDeployKeys(repoID int64) ([]*DeployKey, error) {
+ keys := make([]*DeployKey, 0, 5)
+ return keys, x.Where("repo_id=?", repoID).Find(&keys)
+}
diff --git a/models/repo.go b/models/repo.go
index 91bb0d71..8135bc57 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -21,6 +21,7 @@ import (
"github.com/Unknwon/cae/zip"
"github.com/Unknwon/com"
+ "github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/bindata"
@@ -35,12 +36,11 @@ const (
)
var (
- ErrRepoAlreadyExist = errors.New("Repository already exist")
ErrRepoFileNotExist = errors.New("Repository file does not exist")
- ErrRepoNameIllegal = errors.New("Repository name contains illegal characters")
ErrRepoFileNotLoaded = errors.New("Repository file not loaded")
ErrMirrorNotExist = errors.New("Mirror does not exist")
ErrInvalidReference = errors.New("Invalid reference specified")
+ ErrNameEmpty = errors.New("Name is empty")
)
var (
@@ -222,13 +222,17 @@ func (repo *Repository) DescriptionHtml() template.HTML {
return template.HTML(DescPattern.ReplaceAllStringFunc(base.Sanitizer.Sanitize(repo.Description), sanitize))
}
-// IsRepositoryExist returns true if the repository with given name under user has already existed.
-func IsRepositoryExist(u *User, repoName string) bool {
- has, _ := x.Get(&Repository{
+func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) {
+ has, err := e.Get(&Repository{
OwnerId: u.Id,
LowerName: strings.ToLower(repoName),
})
- return has && com.IsDir(RepoPath(u.Name, repoName))
+ return has && com.IsDir(RepoPath(u.Name, repoName)), err
+}
+
+// IsRepositoryExist returns true if the repository with given name under user has already existed.
+func IsRepositoryExist(u *User, repoName string) (bool, error) {
+ return isRepositoryExist(x, u, repoName)
}
// CloneLink represents different types of clone URLs of repository.
@@ -243,34 +247,42 @@ func (repo *Repository) CloneLink() (cl CloneLink, err error) {
if err = repo.GetOwner(); err != nil {
return cl, err
}
+
if setting.SSHPort != 22 {
- cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.Domain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName)
+ cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName)
} else {
- cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.Domain, repo.Owner.LowerName, repo.LowerName)
+ cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.LowerName, repo.LowerName)
}
cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.LowerName, repo.LowerName)
return cl, nil
}
var (
- illegalEquals = []string{"debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new"}
- illegalSuffixs = []string{".git", ".keys"}
+ reservedNames = []string{"debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new"}
+ reservedPatterns = []string{"*.git", "*.keys"}
)
-// IsLegalName returns false if name contains illegal characters.
-func IsLegalName(repoName string) bool {
- repoName = strings.ToLower(repoName)
- for _, char := range illegalEquals {
- if repoName == char {
- return false
+// IsUsableName checks if name is reserved or pattern of name is not allowed.
+func IsUsableName(name string) error {
+ name = strings.TrimSpace(strings.ToLower(name))
+ if utf8.RuneCountInString(name) == 0 {
+ return ErrNameEmpty
+ }
+
+ for i := range reservedNames {
+ if name == reservedNames[i] {
+ return ErrNameReserved{name}
}
}
- for _, char := range illegalSuffixs {
- if strings.HasSuffix(repoName, char) {
- return false
+
+ for _, pat := range reservedPatterns {
+ if pat[0] == '*' && strings.HasSuffix(name, pat[1:]) ||
+ (pat[len(pat)-1] == '*' && strings.HasPrefix(name, pat[:len(pat)-1])) {
+ return ErrNamePatternNotAllowed{pat}
}
}
- return true
+
+ return nil
}
// Mirror represents a mirror information of repository.
@@ -365,13 +377,22 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
// FIXME: this command could for both migrate and mirror
_, stderr, err := process.ExecTimeout(10*time.Minute,
fmt.Sprintf("MigrateRepository: %s", repoPath),
- "git", "clone", "--mirror", "--bare", url, repoPath)
+ "git", "clone", "--mirror", "--bare", "--quiet", url, repoPath)
if err != nil {
- return repo, fmt.Errorf("git clone --mirror --bare: %v", stderr)
+ return repo, fmt.Errorf("git clone --mirror --bare --quiet: %v", stderr)
} else if err = createUpdateHook(repoPath); err != nil {
return repo, fmt.Errorf("create update hook: %v", err)
}
+ // Check if repository has master branch, if so set it to default branch.
+ gitRepo, err := git.OpenRepository(repoPath)
+ if err != nil {
+ return repo, fmt.Errorf("open git repository: %v", err)
+ }
+ if gitRepo.IsBranchExist("master") {
+ repo.DefaultBranch = "master"
+ }
+
return repo, UpdateRepository(repo, false)
}
@@ -406,13 +427,18 @@ func createUpdateHook(repoPath string) error {
// InitRepository initializes README and .gitignore if needed.
func initRepository(e Engine, repoPath string, u *User, repo *Repository, initReadme bool, repoLang, license string) error {
+ // Somehow the directory could exist.
+ if com.IsExist(repoPath) {
+ return fmt.Errorf("initRepository: path already exists: %s", repoPath)
+ }
+
// Init bare new repository.
os.MkdirAll(repoPath, os.ModePerm)
_, stderr, err := process.ExecDir(-1, repoPath,
fmt.Sprintf("initRepository(git init --bare): %s", repoPath),
"git", "init", "--bare")
if err != nil {
- return errors.New("git init --bare: " + stderr)
+ return fmt.Errorf("git init --bare: %s", err)
}
if err := createUpdateHook(repoPath); err != nil {
@@ -434,6 +460,7 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, initRe
// Clone to temprory path and do the init commit.
tmpDir := filepath.Join(os.TempDir(), com.ToStr(time.Now().Nanosecond()))
os.MkdirAll(tmpDir, os.ModePerm)
+ defer os.RemoveAll(tmpDir)
_, stderr, err = process.Exec(
fmt.Sprintf("initRepository(git clone): %s", repoPath),
@@ -464,7 +491,7 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, initRe
}
} else if com.IsSliceContainsStr(Gitignores, repoLang) {
if err = ioutil.WriteFile(targetPath,
- bindata.MustAsset(path.Join("conf/gitignore", repoLang)), os.ModePerm); err != nil {
+ bindata.MustAsset(path.Join("conf/gitignore", repoLang)), 0644); err != nil {
return fmt.Errorf("generate gitignore: %v", err)
}
} else {
@@ -480,7 +507,7 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, initRe
}
} else if com.IsSliceContainsStr(Licenses, license) {
if err = ioutil.WriteFile(targetPath,
- bindata.MustAsset(path.Join("conf/license", license)), os.ModePerm); err != nil {
+ bindata.MustAsset(path.Join("conf/license", license)), 0644); err != nil {
return fmt.Errorf("generate license: %v", err)
}
} else {
@@ -502,16 +529,50 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, initRe
return initRepoCommit(tmpDir, u.NewGitSig())
}
-// CreateRepository creates a repository for given user or organization.
-func CreateRepository(u *User, name, desc, lang, license string, isPrivate, isMirror, initReadme bool) (_ *Repository, err error) {
- if !IsLegalName(name) {
- return nil, ErrRepoNameIllegal
+func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
+ if err = IsUsableName(repo.Name); err != nil {
+ return err
}
- if IsRepositoryExist(u, name) {
- return nil, ErrRepoAlreadyExist
+ has, err := isRepositoryExist(e, u, repo.Name)
+ if err != nil {
+ return fmt.Errorf("IsRepositoryExist: %v", err)
+ } else if has {
+ return ErrRepoAlreadyExist{u.Name, repo.Name}
+ }
+
+ if _, err = e.Insert(repo); err != nil {
+ return err
+ } else if _, err = e.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", u.Id); err != nil {
+ return err
}
+ // Give access to all members in owner team.
+ if u.IsOrganization() {
+ t, err := u.getOwnerTeam(e)
+ if err != nil {
+ return fmt.Errorf("getOwnerTeam: %v", err)
+ } else if err = t.addRepository(e, repo); err != nil {
+ return fmt.Errorf("addRepository: %v", err)
+ }
+ } else {
+ // Organization automatically called this in addRepository method.
+ if err = repo.recalculateAccesses(e); err != nil {
+ return fmt.Errorf("recalculateAccesses: %v", err)
+ }
+ }
+
+ if err = watchRepo(e, u.Id, repo.Id, true); err != nil {
+ return fmt.Errorf("watchRepo: %v", err)
+ } else if err = newRepoAction(e, u, repo); err != nil {
+ return fmt.Errorf("newRepoAction: %v", err)
+ }
+
+ return nil
+}
+
+// CreateRepository creates a repository for given user or organization.
+func CreateRepository(u *User, name, desc, lang, license string, isPrivate, isMirror, initReadme bool) (_ *Repository, err error) {
repo := &Repository{
OwnerId: u.Id,
Owner: u,
@@ -527,35 +588,10 @@ func CreateRepository(u *User, name, desc, lang, license string, isPrivate, isMi
return nil, err
}
- if _, err = sess.Insert(repo); err != nil {
- return nil, err
- } else if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil {
+ if err = createRepository(sess, u, repo); err != nil {
return nil, err
}
- // TODO fix code for mirrors?
-
- // Give access to all members in owner team.
- if u.IsOrganization() {
- t, err := u.getOwnerTeam(sess)
- if err != nil {
- return nil, fmt.Errorf("getOwnerTeam: %v", err)
- } else if err = t.addRepository(sess, repo); err != nil {
- return nil, fmt.Errorf("addRepository: %v", err)
- }
- } else {
- // Organization called this in addRepository method.
- if err = repo.recalculateAccesses(sess); err != nil {
- return nil, fmt.Errorf("recalculateAccesses: %v", err)
- }
- }
-
- if err = watchRepo(sess, u.Id, repo.Id, true); err != nil {
- return nil, fmt.Errorf("watchRepo: %v", err)
- } else if err = newRepoAction(sess, u, repo); err != nil {
- return nil, fmt.Errorf("newRepoAction: %v", err)
- }
-
// No need for init mirror.
if !isMirror {
repoPath := RepoPath(u.Name, repo.Name)
@@ -599,7 +635,7 @@ func GetRepositoriesWithUsers(num, offset int) ([]*Repository, error) {
if err != nil {
return nil, err
} else if !has {
- return nil, ErrUserNotExist
+ return nil, ErrUserNotExist{repo.OwnerId, ""}
}
}
@@ -619,8 +655,11 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
}
// Check if new owner has repository with same name.
- if IsRepositoryExist(newOwner, repo.Name) {
- return ErrRepoAlreadyExist
+ has, err := IsRepositoryExist(newOwner, repo.Name)
+ if err != nil {
+ return fmt.Errorf("IsRepositoryExist: %v", err)
+ } else if has {
+ return ErrRepoAlreadyExist{newOwnerName, repo.Name}
}
sess := x.NewSession()
@@ -727,16 +766,22 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
}
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
-func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) {
- userName = strings.ToLower(userName)
+func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) {
oldRepoName = strings.ToLower(oldRepoName)
newRepoName = strings.ToLower(newRepoName)
- if !IsLegalName(newRepoName) {
- return ErrRepoNameIllegal
+ if err = IsUsableName(newRepoName); err != nil {
+ return err
+ }
+
+ has, err := IsRepositoryExist(u, newRepoName)
+ if err != nil {
+ return fmt.Errorf("IsRepositoryExist: %v", err)
+ } else if has {
+ return ErrRepoAlreadyExist{u.Name, newRepoName}
}
// Change repository directory name.
- return os.Rename(RepoPath(userName, oldRepoName), RepoPath(userName, newRepoName))
+ return os.Rename(RepoPath(u.LowerName, oldRepoName), RepoPath(u.LowerName, newRepoName))
}
func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) {
@@ -833,7 +878,7 @@ func DeleteRepository(uid, repoID int64, userName string) error {
return err
} else if _, err = sess.Delete(&IssueUser{RepoId: repoID}); err != nil {
return err
- } else if _, err = sess.Delete(&Milestone{RepoId: repoID}); err != nil {
+ } else if _, err = sess.Delete(&Milestone{RepoID: repoID}); err != nil {
return err
} else if _, err = sess.Delete(&Release{RepoId: repoID}); err != nil {
return err
@@ -847,12 +892,12 @@ func DeleteRepository(uid, repoID int64, userName string) error {
return err
}
for i := range issues {
- if _, err = sess.Delete(&Comment{IssueId: issues[i].Id}); err != nil {
+ if _, err = sess.Delete(&Comment{IssueId: issues[i].ID}); err != nil {
return err
}
}
- if _, err = sess.Delete(&Issue{RepoId: repoID}); err != nil {
+ if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil {
return err
}
@@ -1008,6 +1053,7 @@ var (
// Prevent duplicate tasks.
isMirrorUpdating = false
isGitFscking = false
+ isCheckingRepos = false
)
// MirrorUpdate checks and updates mirror repositories.
@@ -1029,7 +1075,7 @@ func MirrorUpdate() {
repoPath := filepath.Join(setting.RepoRootPath, m.RepoName+".git")
if _, stderr, err := process.ExecDir(10*time.Minute,
repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath),
- "git", "remote", "update"); err != nil {
+ "git", "remote", "update", "--prune"); err != nil {
desc := fmt.Sprintf("Fail to update mirror repository(%s): %s", repoPath, stderr)
log.Error(4, desc)
if err = CreateRepositoryNotice(desc); err != nil {
@@ -1099,6 +1145,42 @@ func GitGcRepos() error {
})
}
+func CheckRepoStats() {
+ if isCheckingRepos {
+ return
+ }
+ isCheckingRepos = true
+ defer func() { isCheckingRepos = false }()
+
+ // Check count watchers
+ results_watch, err := x.Query("SELECT r.id FROM `repository` r WHERE r.num_watches!=(SELECT count(*) FROM `watch` WHERE repo_id=r.id)")
+ if err != nil {
+ log.Error(4, "select repository check 'watch': %v", err)
+ }
+ for _, repo_id := range results_watch {
+ log.Info("updating repository count 'watch'")
+ repoID := com.StrTo(repo_id["id"]).MustInt64()
+ _, err := x.Exec("UPDATE `repository` SET num_watches=(SELECT count(*) FROM `watch` WHERE repo_id=?) WHERE id=?", repoID, repoID)
+ if err != nil {
+ log.Error(4, "update repository check 'watch', repo %v: %v", repo_id, err)
+ }
+ }
+
+ // Check count stars
+ results_star, err := x.Query("SELECT s.id FROM `repository` s WHERE s.num_stars!=(SELECT count(*) FROM `star` WHERE repo_id=s.id)")
+ if err != nil {
+ log.Error(4, "select repository check 'star': %v", err)
+ }
+ for _, repo_id := range results_star {
+ log.Info("updating repository count 'star'")
+ repoID := com.StrTo(repo_id["id"]).MustInt64()
+ _, err := x.Exec("UPDATE `repository` SET .num_stars=(SELECT count(*) FROM `star` WHERE repo_id=?) WHERE id=?", repoID, repoID)
+ if err != nil {
+ log.Error(4, "update repository check 'star', repo %v: %v", repo_id, err)
+ }
+ }
+}
+
// _________ .__ .__ ___. __ .__
// \_ ___ \ ____ | | | | _____ \_ |__ ________________ _/ |_|__| ____ ____
// / \ \/ / _ \| | | | \__ \ | __ \ / _ \_ __ \__ \\ __\ |/ _ \ / \
@@ -1339,19 +1421,14 @@ func IsStaring(uid, repoId int64) bool {
// \___ / \____/|__| |__|_ \
// \/ \/
-func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) {
- if IsRepositoryExist(u, name) {
- return nil, ErrRepoAlreadyExist
- }
-
- // In case the old repository is a fork.
- if oldRepo.IsFork {
- oldRepo, err = GetRepositoryById(oldRepo.ForkId)
- if err != nil {
- return nil, err
- }
- }
+// HasForkedRepo checks if given user has already forked a repository with given ID.
+func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) {
+ repo := new(Repository)
+ has, _ := x.Where("owner_id=? AND fork_id=?", ownerID, repoID).Get(repo)
+ return repo, has
+}
+func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) {
repo := &Repository{
OwnerId: u.Id,
Owner: u,
@@ -1369,32 +1446,8 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
return nil, err
}
- if _, err = sess.Insert(repo); err != nil {
- return nil, err
- }
-
- if err = repo.recalculateAccesses(sess); err != nil {
+ if err = createRepository(sess, u, repo); err != nil {
return nil, err
- } else if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil {
- return nil, err
- }
-
- if u.IsOrganization() {
- // Update owner team info and count.
- t, err := u.getOwnerTeam(sess)
- if err != nil {
- return nil, fmt.Errorf("getOwnerTeam: %v", err)
- } else if err = t.addRepository(sess, repo); err != nil {
- return nil, fmt.Errorf("addRepository: %v", err)
- }
- } else {
- if err = watchRepo(sess, u.Id, repo.Id, true); err != nil {
- return nil, fmt.Errorf("watchRepo: %v", err)
- }
- }
-
- if err = newRepoAction(sess, u, repo); err != nil {
- return nil, fmt.Errorf("newRepoAction: %v", err)
}
if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.Id); err != nil {
diff --git a/models/user.go b/models/user.go
index dcfd0dc5..6dd31536 100644
--- a/models/user.go
+++ b/models/user.go
@@ -36,10 +36,7 @@ const (
)
var (
- ErrUserAlreadyExist = errors.New("User already exist")
- ErrUserNotExist = errors.New("User does not exist")
ErrUserNotKeyOwner = errors.New("User does not the owner of public key")
- ErrEmailAlreadyUsed = errors.New("E-mail already used")
ErrEmailNotExist = errors.New("E-mail does not exist")
ErrEmailNotActivated = errors.New("E-mail address has not been activated")
ErrUserNameIllegal = errors.New("User name contains illegal characters")
@@ -145,8 +142,8 @@ func (u *User) EncodePasswd() {
u.Passwd = fmt.Sprintf("%x", newPasswd)
}
-// ValidtePassword checks if given password matches the one belongs to the user.
-func (u *User) ValidtePassword(passwd string) bool {
+// ValidatePassword checks if given password matches the one belongs to the user.
+func (u *User) ValidatePassword(passwd string) bool {
newUser := &User{Passwd: passwd, Salt: u.Salt}
newUser.EncodePasswd()
return u.Passwd == newUser.Passwd
@@ -261,6 +258,8 @@ func IsEmailUsed(email string) (bool, error) {
if len(email) == 0 {
return false, nil
}
+
+ email = strings.ToLower(email)
if has, err := x.Get(&EmailAddress{Email: email}); has || err != nil {
return has, err
}
@@ -273,23 +272,23 @@ func GetUserSalt() string {
}
// CreateUser creates record of a new user.
-func CreateUser(u *User) error {
- if !IsLegalName(u.Name) {
- return ErrUserNameIllegal
+func CreateUser(u *User) (err error) {
+ if err = IsUsableName(u.Name); err != nil {
+ return err
}
isExist, err := IsUserExist(0, u.Name)
if err != nil {
return err
} else if isExist {
- return ErrUserAlreadyExist
+ return ErrUserAlreadyExist{u.Name}
}
isExist, err = IsEmailUsed(u.Email)
if err != nil {
return err
} else if isExist {
- return ErrEmailAlreadyUsed
+ return ErrEmailAlreadyUsed{u.Email}
}
u.LowerName = strings.ToLower(u.Name)
@@ -315,19 +314,23 @@ func CreateUser(u *User) error {
return err
}
- // Auto-set admin for user whose ID is 1.
- if u.Id == 1 {
+ // Auto-set admin for the first user.
+ if CountUsers() == 1 {
u.IsAdmin = true
u.IsActive = true
- _, err = x.Id(u.Id).UseBool().Update(u)
+ _, err = x.Id(u.Id).AllCols().Update(u)
}
return err
}
+func countUsers(e Engine) int64 {
+ count, _ := e.Where("type=0").Count(new(User))
+ return count
+}
+
// CountUsers returns number of users.
func CountUsers() int64 {
- count, _ := x.Where("type=0").Count(new(User))
- return count
+ return countUsers(x)
}
// GetUsers returns given number of user objects with offset.
@@ -392,8 +395,15 @@ func VerifyActiveEmailCode(code, email string) *EmailAddress {
// ChangeUserName changes all corresponding setting from old user name to new one.
func ChangeUserName(u *User, newUserName string) (err error) {
- if !IsLegalName(newUserName) {
- return ErrUserNameIllegal
+ if err = IsUsableName(newUserName); err != nil {
+ return err
+ }
+
+ isExist, err := IsUserExist(0, newUserName)
+ if err != nil {
+ return err
+ } else if isExist {
+ return ErrUserAlreadyExist{newUserName}
}
return os.Rename(UserPath(u.LowerName), UserPath(newUserName))
@@ -401,11 +411,12 @@ func ChangeUserName(u *User, newUserName string) (err error) {
// UpdateUser updates user's information.
func UpdateUser(u *User) error {
+ u.Email = strings.ToLower(u.Email)
has, err := x.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
if err != nil {
return err
} else if has {
- return ErrEmailAlreadyUsed
+ return ErrEmailAlreadyUsed{u.Email}
}
u.LowerName = strings.ToLower(u.Name)
@@ -498,7 +509,7 @@ func DeleteUser(u *User) error {
// Delete all SSH keys.
keys := make([]*PublicKey, 0, 10)
- if err = sess.Find(&keys, &PublicKey{OwnerId: u.Id}); err != nil {
+ if err = sess.Find(&keys, &PublicKey{OwnerID: u.Id}); err != nil {
return err
}
for _, key := range keys {
@@ -550,7 +561,7 @@ func getUserById(e Engine, id int64) (*User, error) {
if err != nil {
return nil, err
} else if !has {
- return nil, ErrUserNotExist
+ return nil, ErrUserNotExist{id, ""}
}
return u, nil
}
@@ -563,14 +574,14 @@ func GetUserById(id int64) (*User, error) {
// GetUserByName returns user by given name.
func GetUserByName(name string) (*User, error) {
if len(name) == 0 {
- return nil, ErrUserNotExist
+ return nil, ErrUserNotExist{0, name}
}
u := &User{LowerName: strings.ToLower(name)}
has, err := x.Get(u)
if err != nil {
return nil, err
} else if !has {
- return nil, ErrUserNotExist
+ return nil, ErrUserNotExist{0, name}
}
return u, nil
}
@@ -637,11 +648,12 @@ func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
}
func AddEmailAddress(email *EmailAddress) error {
+ email.Email = strings.ToLower(email.Email)
used, err := IsEmailUsed(email.Email)
if err != nil {
return err
} else if used {
- return ErrEmailAlreadyUsed
+ return ErrEmailAlreadyUsed{email.Email}
}
_, err = x.Insert(email)
@@ -670,7 +682,7 @@ func DeleteEmailAddress(email *EmailAddress) error {
return ErrEmailNotExist
}
- if _, err = x.Delete(email); err != nil {
+ if _, err = x.Id(email.Id).Delete(email); err != nil {
return err
}
@@ -695,7 +707,7 @@ func MakeEmailPrimary(email *EmailAddress) error {
if err != nil {
return err
} else if !has {
- return ErrUserNotExist
+ return ErrUserNotExist{email.Uid, ""}
}
// Make sure the former primary email doesn't disappear
@@ -732,13 +744,15 @@ func ValidateCommitWithEmail(c *git.Commit) *User {
// ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users.
func ValidateCommitsWithEmails(oldCommits *list.List) *list.List {
- emails := map[string]*User{}
- newCommits := list.New()
- e := oldCommits.Front()
+ var (
+ u *User
+ emails = map[string]*User{}
+ newCommits = list.New()
+ e = oldCommits.Front()
+ )
for e != nil {
c := e.Value.(*git.Commit)
- var u *User
if v, ok := emails[c.Author.Email]; !ok {
u, _ = GetUserByEmail(c.Author.Email)
emails[c.Author.Email] = u
@@ -758,10 +772,12 @@ func ValidateCommitsWithEmails(oldCommits *list.List) *list.List {
// GetUserByEmail returns the user object by given e-mail if exists.
func GetUserByEmail(email string) (*User, error) {
if len(email) == 0 {
- return nil, ErrUserNotExist
+ return nil, ErrUserNotExist{0, "email"}
}
+
+ email = strings.ToLower(email)
// First try to find the user by primary email
- user := &User{Email: strings.ToLower(email)}
+ user := &User{Email: email}
has, err := x.Get(user)
if err != nil {
return nil, err
@@ -771,7 +787,7 @@ func GetUserByEmail(email string) (*User, error) {
}
// Otherwise, check in alternative list for activated email addresses
- emailAddress := &EmailAddress{Email: strings.ToLower(email), IsActivated: true}
+ emailAddress := &EmailAddress{Email: email, IsActivated: true}
has, err = x.Get(emailAddress)
if err != nil {
return nil, err
@@ -780,7 +796,7 @@ func GetUserByEmail(email string) (*User, error) {
return GetUserById(emailAddress.Uid)
}
- return nil, ErrUserNotExist
+ return nil, ErrUserNotExist{0, "email"}
}
// SearchUserByName returns given number of users whose name contains keyword.
diff --git a/models/webhook.go b/models/webhook.go
index bfa52b99..18cda748 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -9,6 +9,7 @@ import (
"encoding/json"
"errors"
"io/ioutil"
+ "sync"
"time"
"github.com/gogits/gogs/modules/httplib"
@@ -259,7 +260,9 @@ func (p Payload) GetJSONPayload() ([]byte, error) {
// HookTask represents a hook task.
type HookTask struct {
- Id int64
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX"`
+ HookID int64
Uuid string
Type HookTaskType
Url string
@@ -269,6 +272,7 @@ type HookTask struct {
EventType HookEventType
IsSsl bool
IsDelivered bool
+ Delivered int64
IsSucceed bool
}
@@ -287,87 +291,137 @@ func CreateHookTask(t *HookTask) error {
// UpdateHookTask updates information of hook task.
func UpdateHookTask(t *HookTask) error {
- _, err := x.Id(t.Id).AllCols().Update(t)
+ _, err := x.Id(t.ID).AllCols().Update(t)
return err
}
-var (
- // Prevent duplicate deliveries.
- // This happens with massive hook tasks cannot finish delivering
- // before next shooting starts.
- isShooting = false
-)
+type hookQueue struct {
+ // Make sure one repository only occur once in the queue.
+ lock sync.Mutex
+ repoIDs map[int64]bool
-// DeliverHooks checks and delivers undelivered hooks.
-// FIXME: maybe can use goroutine to shoot a number of them at same time?
-func DeliverHooks() {
- if isShooting {
+ queue chan int64
+}
+
+func (q *hookQueue) removeRepoID(id int64) {
+ q.lock.Lock()
+ defer q.lock.Unlock()
+ delete(q.repoIDs, id)
+}
+
+func (q *hookQueue) addRepoID(id int64) {
+ q.lock.Lock()
+ if q.repoIDs[id] {
+ q.lock.Unlock()
return
}
- isShooting = true
- defer func() { isShooting = false }()
+ q.repoIDs[id] = true
+ q.lock.Unlock()
+ q.queue <- id
+}
- tasks := make([]*HookTask, 0, 10)
+// AddRepoID adds repository ID to hook delivery queue.
+func (q *hookQueue) AddRepoID(id int64) {
+ go q.addRepoID(id)
+}
+
+var HookQueue *hookQueue
+
+func deliverHook(t *HookTask) {
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
- x.Where("is_delivered=?", false).Iterate(new(HookTask),
- func(idx int, bean interface{}) error {
- t := bean.(*HookTask)
- req := httplib.Post(t.Url).SetTimeout(timeout, timeout).
- Header("X-Gogs-Delivery", t.Uuid).
- Header("X-Gogs-Event", string(t.EventType)).
- SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
-
- switch t.ContentType {
- case JSON:
- req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
- case FORM:
- req.Param("payload", t.PayloadContent)
- }
+ req := httplib.Post(t.Url).SetTimeout(timeout, timeout).
+ Header("X-Gogs-Delivery", t.Uuid).
+ Header("X-Gogs-Event", string(t.EventType)).
+ SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
- t.IsDelivered = true
+ switch t.ContentType {
+ case JSON:
+ req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
+ case FORM:
+ req.Param("payload", t.PayloadContent)
+ }
- // FIXME: record response.
- switch t.Type {
- case GOGS:
- {
- if _, err := req.Response(); err != nil {
- log.Error(5, "Delivery: %v", err)
+ t.IsDelivered = true
+
+ // FIXME: record response.
+ switch t.Type {
+ case GOGS:
+ {
+ if resp, err := req.Response(); err != nil {
+ log.Error(5, "Delivery: %v", err)
+ } else {
+ resp.Body.Close()
+ t.IsSucceed = true
+ }
+ }
+ case SLACK:
+ {
+ if resp, err := req.Response(); err != nil {
+ log.Error(5, "Delivery: %v", err)
+ } else {
+ defer resp.Body.Close()
+ contents, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ log.Error(5, "%s", err)
+ } else {
+ if string(contents) != "ok" {
+ log.Error(5, "slack failed with: %s", string(contents))
} else {
t.IsSucceed = true
}
}
- case SLACK:
- {
- if res, err := req.Response(); err != nil {
- log.Error(5, "Delivery: %v", err)
- } else {
- defer res.Body.Close()
- contents, err := ioutil.ReadAll(res.Body)
- if err != nil {
- log.Error(5, "%s", err)
- } else {
- if string(contents) != "ok" {
- log.Error(5, "slack failed with: %s", string(contents))
- } else {
- t.IsSucceed = true
- }
- }
- }
- }
}
+ }
+ }
- tasks = append(tasks, t)
+ t.Delivered = time.Now().UTC().UnixNano()
+ if t.IsSucceed {
+ log.Trace("Hook delivered(%s): %s", t.Uuid, t.PayloadContent)
+ }
+}
- if t.IsSucceed {
- log.Trace("Hook delivered(%s): %s", t.Uuid, t.PayloadContent)
- }
+// DeliverHooks checks and delivers undelivered hooks.
+func DeliverHooks() {
+ tasks := make([]*HookTask, 0, 10)
+ x.Where("is_delivered=?", false).Iterate(new(HookTask),
+ func(idx int, bean interface{}) error {
+ t := bean.(*HookTask)
+ deliverHook(t)
+ tasks = append(tasks, t)
return nil
})
// Update hook task status.
for _, t := range tasks {
if err := UpdateHookTask(t); err != nil {
- log.Error(4, "UpdateHookTask(%d): %v", t.Id, err)
+ log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)
+ }
+ }
+
+ HookQueue = &hookQueue{
+ lock: sync.Mutex{},
+ repoIDs: make(map[int64]bool),
+ queue: make(chan int64, setting.Webhook.QueueLength),
+ }
+
+ // Start listening on new hook requests.
+ for repoID := range HookQueue.queue {
+ HookQueue.removeRepoID(repoID)
+
+ tasks = make([]*HookTask, 0, 5)
+ if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
+ log.Error(4, "Get repository(%d) hook tasks: %v", repoID, err)
+ continue
+ }
+ for _, t := range tasks {
+ deliverHook(t)
+ if err := UpdateHookTask(t); err != nil {
+ log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)
+ }
}
}
}
+
+func InitDeliverHooks() {
+ go DeliverHooks()
+}