aboutsummaryrefslogtreecommitdiff
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/access.go14
-rw-r--r--models/action.go64
-rw-r--r--models/error.go68
-rw-r--r--models/issue.go1040
-rw-r--r--models/migrations/migrations.go155
-rw-r--r--models/models.go3
-rw-r--r--models/oauth2.go2
-rw-r--r--models/org.go30
-rw-r--r--models/repo.go168
-rw-r--r--models/update.go4
-rw-r--r--models/user.go98
11 files changed, 1191 insertions, 455 deletions
diff --git a/models/access.go b/models/access.go
index 54d0f099..8d698f15 100644
--- a/models/access.go
+++ b/models/access.go
@@ -37,11 +37,11 @@ func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) {
}
if u != nil {
- if u.Id == repo.OwnerId {
+ if u.Id == repo.OwnerID {
return ACCESS_MODE_OWNER, nil
}
- a := &Access{UserID: u.Id, RepoID: repo.Id}
+ a := &Access{UserID: u.Id, RepoID: repo.ID}
if has, err := e.Get(a); !has || err != nil {
return mode, err
}
@@ -77,7 +77,7 @@ func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
repos := make(map[*Repository]AccessMode, len(accesses))
for _, access := range accesses {
- repo, err := GetRepositoryById(access.RepoID)
+ repo, err := GetRepositoryByID(access.RepoID)
if err != nil {
if IsErrRepoNotExist(err) {
log.Error(4, "%v", err)
@@ -87,7 +87,7 @@ func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
}
if err = repo.GetOwner(); err != nil {
return nil, err
- } else if repo.OwnerId == u.Id {
+ } else if repo.OwnerID == u.Id {
continue
}
repos[repo] = access.Mode
@@ -121,13 +121,13 @@ func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode
}
newAccesses = append(newAccesses, Access{
UserID: userID,
- RepoID: repo.Id,
+ RepoID: repo.ID,
Mode: mode,
})
}
// Delete old accesses and insert new ones for repository.
- if _, err = e.Delete(&Access{RepoID: repo.Id}); err != nil {
+ if _, err = e.Delete(&Access{RepoID: repo.ID}); err != nil {
return fmt.Errorf("delete old accesses: %v", err)
} else if _, err = e.Insert(newAccesses); err != nil {
return fmt.Errorf("insert new accesses: %v", err)
@@ -193,7 +193,7 @@ func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err
// have relations with repository.
if t.IsOwnerTeam() {
t.Authorize = ACCESS_MODE_OWNER
- } else if !t.hasRepository(e, repo.Id) {
+ } else if !t.hasRepository(e, repo.ID) {
continue
}
diff --git a/models/action.go b/models/action.go
index 99cd1709..de7f93a9 100644
--- a/models/action.go
+++ b/models/action.go
@@ -124,7 +124,7 @@ func (a Action) GetIssueInfos() []string {
return strings.SplitN(a.Content, "|", 2)
}
-func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, commits []*base.PushCommit) error {
+func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, commits []*base.PushCommit) error {
for _, c := range commits {
for _, ref := range IssueReferenceKeywordsPat.FindAllString(c.Message, -1) {
ref := ref[strings.IndexByte(ref, byte(' '))+1:]
@@ -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(u, repo, issue, 0, 0, COMMENT_TYPE_COMMIT_REF, 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 == repo.ID {
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 = UpdateIssueUsersByStatus(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(u, repo, issue, 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 == repo.ID {
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 = UpdateIssueUsersByStatus(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(u, repo, issue, 0, 0, COMMENT_TYPE_REOPEN, "", nil); err != nil {
return err
}
}
@@ -280,8 +280,8 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
}
// CommitRepoAction adds new action for committing repository.
-func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
- repoId int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits, oldCommitId string, newCommitId string) error {
+func CommitRepoAction(userID, repoUserID int64, userName, actEmail string,
+ repoID int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits, oldCommitID string, newCommitID string) error {
opType := COMMIT_REPO
// Check it's tag push or branch.
@@ -292,40 +292,44 @@ 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/%s/compare/%s...%s", repoUserName, repoName, oldCommitId, newCommitId)
+ if !strings.HasPrefix(oldCommitID, "0000000") {
+ commit.CompareUrl = fmt.Sprintf("%s/%s/compare/%s...%s", repoUserName, repoName, oldCommitID, newCommitID)
}
bs, err := json.Marshal(commit)
if err != nil {
- return errors.New("json: " + err.Error())
+ return fmt.Errorf("Marshal: %v", err)
}
refName := git.RefEndName(refFullName)
// Change repository bare status and update last updated time.
- repo, err := GetRepositoryByName(repoUserId, repoName)
+ repo, err := GetRepositoryByName(repoUserID, repoName)
if err != nil {
- return errors.New("GetRepositoryByName: " + err.Error())
+ return fmt.Errorf("GetRepositoryByName: %v", err)
}
repo.IsBare = false
if err = UpdateRepository(repo, false); err != nil {
- return errors.New("UpdateRepository: " + err.Error())
+ return fmt.Errorf("UpdateRepository: %v", err)
}
- err = updateIssuesCommit(userId, repoId, repoUserName, repoName, commit.Commits)
+ u, err := GetUserByID(userID)
+ if err != nil {
+ return fmt.Errorf("GetUserByID: %v", err)
+ }
+ err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits)
if err != nil {
log.Debug("updateIssuesCommit: ", err)
}
if err = NotifyWatchers(&Action{
- ActUserID: userId,
+ ActUserID: u.Id,
ActUserName: userName,
ActEmail: actEmail,
OpType: opType,
Content: string(bs),
- RepoID: repoId,
+ RepoID: repo.ID,
RepoUserName: repoUserName,
RepoName: repoName,
RefName: refName,
@@ -340,7 +344,7 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
return errors.New("GetOwner: " + err.Error())
}
- ws, err := GetActiveWebhooksByRepoId(repoId)
+ ws, err := GetActiveWebhooksByRepoId(repo.ID)
if err != nil {
return errors.New("GetActiveWebhooksByRepoId: " + err.Error())
}
@@ -348,7 +352,7 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
// check if repo belongs to org and append additional webhooks
if repo.Owner.IsOrganization() {
// get hooks for org
- orgws, err := GetActiveWebhooksByOrgId(repo.OwnerId)
+ orgws, err := GetActiveWebhooksByOrgId(repo.OwnerID)
if err != nil {
return errors.New("GetActiveWebhooksByOrgId: " + err.Error())
}
@@ -388,7 +392,7 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
Ref: refFullName,
Commits: commits,
Repo: &PayloadRepo{
- Id: repo.Id,
+ Id: repo.ID,
Name: repo.LowerName,
Url: repoLink,
Description: repo.Description,
@@ -406,8 +410,8 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
Email: pusher_email,
UserName: userName,
},
- Before: oldCommitId,
- After: newCommitId,
+ Before: oldCommitID,
+ After: newCommitID,
CompareUrl: setting.AppUrl + commit.CompareUrl,
}
@@ -431,7 +435,7 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
}
if err = CreateHookTask(&HookTask{
- RepoID: repo.Id,
+ RepoID: repo.ID,
HookID: w.Id,
Type: w.HookTaskType,
Url: w.Url,
@@ -453,12 +457,12 @@ func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
ActUserName: u.Name,
ActEmail: u.Email,
OpType: CREATE_REPO,
- RepoID: repo.Id,
+ RepoID: repo.ID,
RepoUserName: repo.Owner.Name,
RepoName: repo.Name,
IsPrivate: repo.IsPrivate,
}); err != nil {
- return fmt.Errorf("notify watchers '%d/%s'", u.Id, repo.Id)
+ return fmt.Errorf("notify watchers '%d/%s'", u.Id, repo.ID)
}
log.Trace("action.NewRepoAction: %s/%s", u.Name, repo.Name)
@@ -476,19 +480,19 @@ func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repos
ActUserName: actUser.Name,
ActEmail: actUser.Email,
OpType: TRANSFER_REPO,
- RepoID: repo.Id,
+ RepoID: repo.ID,
RepoUserName: newOwner.Name,
RepoName: repo.Name,
IsPrivate: repo.IsPrivate,
Content: path.Join(oldOwner.LowerName, repo.LowerName),
}
if err = notifyWatchers(e, action); err != nil {
- return fmt.Errorf("notify watchers '%d/%s'", actUser.Id, repo.Id)
+ return fmt.Errorf("notify watchers '%d/%s'", actUser.Id, repo.ID)
}
// Remove watch for organization.
if repo.Owner.IsOrganization() {
- if err = watchRepo(e, repo.Owner.Id, repo.Id, false); err != nil {
+ if err = watchRepo(e, repo.Owner.Id, repo.ID, false); err != nil {
return fmt.Errorf("watch repository: %v", err)
}
}
diff --git a/models/error.go b/models/error.go
index 0e554e52..c3e48063 100644
--- a/models/error.go
+++ b/models/error.go
@@ -239,6 +239,48 @@ func (err ErrRepoAlreadyExist) Error() string {
return fmt.Sprintf("repository already exists [uname: %d, name: %s]", err.Uname, err.Name)
}
+// .___
+// | | ______ ________ __ ____
+// | |/ ___// ___/ | \_/ __ \
+// | |\___ \ \___ \| | /\ ___/
+// |___/____ >____ >____/ \___ >
+// \/ \/ \/
+
+type ErrIssueNotExist struct {
+ ID int64
+ RepoID int64
+ Index int64
+}
+
+func IsErrIssueNotExist(err error) bool {
+ _, ok := err.(ErrIssueNotExist)
+ return ok
+}
+
+func (err ErrIssueNotExist) Error() string {
+ return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %4]", err.ID, err.RepoID, err.Index)
+}
+
+// .____ ___. .__
+// | | _____ \_ |__ ____ | |
+// | | \__ \ | __ \_/ __ \| |
+// | |___ / __ \| \_\ \ ___/| |__
+// |_______ (____ /___ /\___ >____/
+// \/ \/ \/ \/
+
+type ErrLabelNotExist struct {
+ ID int64
+}
+
+func IsErrLabelNotExist(err error) bool {
+ _, ok := err.(ErrLabelNotExist)
+ return ok
+}
+
+func (err ErrLabelNotExist) Error() string {
+ return fmt.Sprintf("label does not exist [id: %d]", err.ID)
+}
+
// _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
@@ -247,7 +289,8 @@ func (err ErrRepoAlreadyExist) Error() string {
// \/ \/ \/ \/ \/
type ErrMilestoneNotExist struct {
- ID int64
+ ID int64
+ RepoID int64
}
func IsErrMilestoneNotExist(err error) bool {
@@ -256,5 +299,26 @@ func IsErrMilestoneNotExist(err error) bool {
}
func (err ErrMilestoneNotExist) Error() string {
- return fmt.Sprintf("milestone does not exist [id: %d]", err.ID)
+ return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID)
+}
+
+// _____ __ __ .__ __
+// / _ \_/ |__/ |______ ____ | |__ _____ ____ _____/ |_
+// / /_\ \ __\ __\__ \ _/ ___\| | \ / \_/ __ \ / \ __\
+// / | \ | | | / __ \\ \___| Y \ Y Y \ ___/| | \ |
+// \____|__ /__| |__| (____ /\___ >___| /__|_| /\___ >___| /__|
+// \/ \/ \/ \/ \/ \/ \/
+
+type ErrAttachmentNotExist struct {
+ ID int64
+ UUID string
+}
+
+func IsErrAttachmentNotExist(err error) bool {
+ _, ok := err.(ErrAttachmentNotExist)
+ return ok
+}
+
+func (err ErrAttachmentNotExist) Error() string {
+ return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
}
diff --git a/models/issue.go b/models/issue.go
index d423f056..7f16f936 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -7,8 +7,11 @@ package models
import (
"bytes"
"errors"
- "html/template"
+ "fmt"
+ "io"
+ "mime/multipart"
"os"
+ "path"
"strconv"
"strings"
"time"
@@ -16,15 +19,14 @@ import (
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
+ "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting"
+ gouuid "github.com/gogits/gogs/modules/uuid"
)
var (
- ErrIssueNotExist = errors.New("Issue does not exist")
- ErrLabelNotExist = errors.New("Label 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")
ErrMissingIssueNumber = errors.New("No issue number specified")
)
@@ -38,7 +40,6 @@ type Issue struct {
Repo *Repository `xorm:"-"`
PosterID int64
Poster *User `xorm:"-"`
- LabelIds string `xorm:"TEXT"`
Labels []*Label `xorm:"-"`
MilestoneID int64
Milestone *Milestone `xorm:"-"`
@@ -54,78 +55,196 @@ type Issue struct {
Deadline time.Time
Created time.Time `xorm:"CREATED"`
Updated time.Time `xorm:"UPDATED"`
+
+ Attachments []*Attachment `xorm:"-"`
+ Comments []*Comment `xorm:"-"`
+}
+
+// HashTag returns unique hash tag for issue.
+func (i *Issue) HashTag() string {
+ return "issue-" + com.ToStr(i.ID)
}
func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
var err error
switch colName {
+ case "id":
+ i.Attachments, err = GetAttachmentsByIssueID(i.ID)
+ if err != nil {
+ log.Error(3, "GetAttachmentsByIssueID[%d]: %v", i.ID, err)
+ }
+
+ i.Comments, err = GetCommentsByIssueID(i.ID)
+ if err != nil {
+ log.Error(3, "GetCommentsByIssueID[%d]: %v", i.ID, err)
+ }
+
case "milestone_id":
+ if i.MilestoneID == 0 {
+ return
+ }
+
i.Milestone, err = GetMilestoneByID(i.MilestoneID)
if err != nil {
- log.Error(3, "GetMilestoneById: %v", err)
+ log.Error(3, "GetMilestoneById[%d]: %v", i.ID, err)
+ }
+ case "assignee_id":
+ if i.AssigneeID == 0 {
+ return
+ }
+
+ i.Assignee, err = GetUserByID(i.AssigneeID)
+ if err != nil {
+ log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
}
}
}
+// IsPoster returns true if given user by ID is the poster.
+func (i *Issue) IsPoster(uid int64) bool {
+ return i.PosterID == uid
+}
+
func (i *Issue) GetPoster() (err error) {
- i.Poster, err = GetUserById(i.PosterID)
+ i.Poster, err = GetUserByID(i.PosterID)
if IsErrUserNotExist(err) {
- i.Poster = &User{Name: "FakeUser"}
+ i.Poster = &User{Name: "Someone"}
return nil
}
return err
}
-func (i *Issue) GetLabels() error {
- if len(i.LabelIds) < 3 {
+func (i *Issue) hasLabel(e Engine, labelID int64) bool {
+ return hasIssueLabel(e, i.ID, labelID)
+}
+
+// HasLabel returns true if issue has been labeled by given ID.
+func (i *Issue) HasLabel(labelID int64) bool {
+ return i.hasLabel(x, labelID)
+}
+
+func (i *Issue) addLabel(e Engine, labelID int64) error {
+ return newIssueLabel(e, i.ID, labelID)
+}
+
+// AddLabel adds new label to issue by given ID.
+func (i *Issue) AddLabel(labelID int64) error {
+ return i.addLabel(x, labelID)
+}
+
+func (i *Issue) getLabels(e Engine) (err error) {
+ if len(i.Labels) > 0 {
return nil
}
- strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$")
- i.Labels = make([]*Label, 0, len(strIds))
- for _, strId := range strIds {
- id := com.StrTo(strId).MustInt64()
- if id > 0 {
- l, err := GetLabelById(id)
- if err != nil {
- if err == ErrLabelNotExist {
- continue
- }
- return err
- }
- i.Labels = append(i.Labels, l)
- }
+ i.Labels, err = getLabelsByIssueID(e, i.ID)
+ if err != nil {
+ return fmt.Errorf("getLabelsByIssueID: %v", err)
}
return nil
}
+// GetLabels retrieves all labels of issue and assign to corresponding field.
+func (i *Issue) GetLabels() error {
+ return i.getLabels(x)
+}
+
+func (i *Issue) removeLabel(e Engine, labelID int64) error {
+ return deleteIssueLabel(e, i.ID, labelID)
+}
+
+// RemoveLabel removes a label from issue by given ID.
+func (i *Issue) RemoveLabel(labelID int64) error {
+ return i.removeLabel(x, labelID)
+}
+
func (i *Issue) GetAssignee() (err error) {
- if i.AssigneeID == 0 {
+ if i.AssigneeID == 0 || i.Assignee != nil {
return nil
}
- i.Assignee, err = GetUserById(i.AssigneeID)
+ i.Assignee, err = GetUserByID(i.AssigneeID)
if IsErrUserNotExist(err) {
return nil
}
return err
}
-func (i *Issue) Attachments() []*Attachment {
- a, _ := GetAttachmentsForIssue(i.ID)
- return a
+// ReadBy sets issue to be read by given user.
+func (i *Issue) ReadBy(uid int64) error {
+ return UpdateIssueUserByRead(uid, i.ID)
+}
+
+func (i *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (err error) {
+ if i.IsClosed == isClosed {
+ return nil
+ }
+ i.IsClosed = isClosed
+
+ if err = updateIssue(e, i); err != nil {
+ return err
+ } else if err = updateIssueUsersByStatus(e, i.ID, isClosed); err != nil {
+ return err
+ }
+
+ // Update labels.
+ if err = i.getLabels(e); err != nil {
+ return err
+ }
+ for idx := range i.Labels {
+ if i.IsClosed {
+ i.Labels[idx].NumClosedIssues++
+ } else {
+ i.Labels[idx].NumClosedIssues--
+ }
+ if err = updateLabel(e, i.Labels[idx]); err != nil {
+ return err
+ }
+ }
+
+ // Update milestone.
+ if err = changeMilestoneIssueStats(e, i); err != nil {
+ return err
+ }
+
+ // New action comment.
+ if _, err = createStatusComment(e, doer, i.Repo, i); err != nil {
+ return err
+ }
+
+ return nil
}
-func (i *Issue) AfterDelete() {
- _, err := DeleteAttachmentsByIssue(i.ID, true)
+// ChangeStatus changes issue status to open/closed.
+func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) {
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
- if err != nil {
- log.Info("Could not delete files for issue #%d: %s", i.ID, err)
+ if err = i.changeStatus(sess, doer, isClosed); err != nil {
+ return err
}
+
+ return sess.Commit()
}
-// CreateIssue creates new issue for repository.
-func NewIssue(issue *Issue) (err error) {
+// CreateIssue creates new issue with labels for repository.
+func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
+ // Check attachments.
+ attachments := make([]*Attachment, 0, len(uuids))
+ for _, uuid := range uuids {
+ attach, err := GetAttachmentByUUID(uuid)
+ if err != nil {
+ if IsErrAttachmentNotExist(err) {
+ continue
+ }
+ return fmt.Errorf("GetAttachmentByUUID[%s]: %v", uuid, err)
+ }
+ attachments = append(attachments, attach)
+ }
+
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
@@ -134,20 +253,51 @@ func NewIssue(issue *Issue) (err error) {
if _, err = sess.Insert(issue); err != nil {
return err
- } else if _, err = sess.Exec("UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?", issue.RepoID); err != nil {
+ } else if _, err = sess.Exec("UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?", issue.RepoID); err != nil {
return err
}
- if err = sess.Commit(); err != nil {
- return err
+ for _, id := range labelIDs {
+ if err = issue.addLabel(sess, id); err != nil {
+ return fmt.Errorf("addLabel: %v", err)
+ }
}
if issue.MilestoneID > 0 {
- // FIXES(280): Update milestone counter.
- return ChangeMilestoneAssign(0, issue.MilestoneID, issue)
+ if err = changeMilestoneAssign(sess, 0, issue); err != nil {
+ return err
+ }
+ }
+
+ if err = newIssueUsers(sess, repo, issue); err != nil {
+ return err
+ }
+
+ for i := range attachments {
+ attachments[i].IssueID = issue.ID
+ // No assign value could be 0, so ignore AllCols().
+ if _, err = sess.Id(attachments[i].ID).Update(attachments[i]); err != nil {
+ return fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
+ }
}
- return
+ // Notify watchers.
+ act := &Action{
+ ActUserID: issue.Poster.Id,
+ ActUserName: issue.Poster.Name,
+ ActEmail: issue.Poster.Email,
+ OpType: CREATE_ISSUE,
+ Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
+ RepoID: repo.ID,
+ RepoUserName: repo.Owner.Name,
+ RepoName: repo.Name,
+ IsPrivate: repo.IsPrivate,
+ }
+ if err = notifyWatchers(sess, act); err != nil {
+ return err
+ }
+
+ return sess.Commit()
}
// GetIssueByRef returns an Issue specified by a GFM reference.
@@ -170,35 +320,38 @@ func GetIssueByRef(ref string) (issue *Issue, err error) {
return
}
- return GetIssueByIndex(repo.Id, issueNumber)
+ return GetIssueByIndex(repo.ID, issueNumber)
}
// GetIssueByIndex returns issue by given index in repository.
-func GetIssueByIndex(rid, index int64) (*Issue, error) {
- issue := &Issue{RepoID: rid, Index: index}
+func GetIssueByIndex(repoID, index int64) (*Issue, error) {
+ issue := &Issue{
+ RepoID: repoID,
+ Index: index,
+ }
has, err := x.Get(issue)
if err != nil {
return nil, err
} else if !has {
- return nil, ErrIssueNotExist
+ return nil, ErrIssueNotExist{0, repoID, index}
}
return issue, nil
}
-// GetIssueById returns an issue by ID.
-func GetIssueById(id int64) (*Issue, error) {
- issue := &Issue{ID: id}
- has, err := x.Get(issue)
+// GetIssueByID returns an issue by given ID.
+func GetIssueByID(id int64) (*Issue, error) {
+ issue := new(Issue)
+ has, err := x.Id(id).Get(issue)
if err != nil {
return nil, err
} else if !has {
- return nil, ErrIssueNotExist
+ return nil, ErrIssueNotExist{id, 0, 0}
}
return issue, nil
}
// 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) {
+func Issues(uid, assigneeID, repoID, posterID, milestoneID int64, page int, isClosed, isMention bool, labels, sortType string) ([]*Issue, error) {
sess := x.Limit(setting.IssuePagingNum, (page-1)*setting.IssuePagingNum)
if repoID > 0 {
@@ -217,14 +370,6 @@ func Issues(uid, assigneeID, repoID, posterID, milestoneID int64, page int, isCl
sess.And("issue.milestone_id=?", milestoneID)
}
- if len(labelIds) > 0 {
- for _, label := range strings.Split(labelIds, ",") {
- if com.StrTo(label).MustInt() > 0 {
- sess.And("label_ids like ?", "%$"+label+"|%")
- }
- }
- }
-
switch sortType {
case "oldest":
sess.Asc("created")
@@ -242,10 +387,26 @@ func Issues(uid, assigneeID, repoID, posterID, milestoneID int64, page int, isCl
sess.Desc("created")
}
+ labelIDs := base.StringsToInt64s(strings.Split(labels, ","))
+ if len(labelIDs) > 0 {
+ validJoin := false
+ queryStr := "issue.id=issue_label.issue_id"
+ for _, id := range labelIDs {
+ if id == 0 {
+ continue
+ }
+ validJoin = true
+ queryStr += " AND issue_label.label_id=" + com.ToStr(id)
+ }
+ if validJoin {
+ sess.Join("INNER", "issue_label", queryStr)
+ }
+ }
+
if isMention {
- queryStr := "issue.id = issue_user.issue_id AND issue_user.is_mentioned=1"
+ queryStr := "issue.id=issue_user.issue_id AND issue_user.is_mentioned=1"
if uid > 0 {
- queryStr += " AND issue_user.uid = " + com.ToStr(uid)
+ queryStr += " AND issue_user.uid=" + com.ToStr(uid)
}
sess.Join("INNER", "issue_user", queryStr)
}
@@ -261,12 +422,6 @@ const (
IS_CLOSE
)
-// GetIssuesByLabel returns a list of issues by given label and repository.
-func GetIssuesByLabel(repoID, labelID int64) ([]*Issue, error) {
- issues := make([]*Issue, 0, 10)
- 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.
func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
count, _ := x.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue))
@@ -282,11 +437,11 @@ func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
// IssueUser represents an issue-user relation.
type IssueUser struct {
- Id int64
- Uid int64 `xorm:"INDEX"` // User ID.
- IssueId int64
- RepoId int64 `xorm:"INDEX"`
- MilestoneId int64
+ ID int64 `xorm:"pk autoincr"`
+ UID int64 `xorm:"uid INDEX"` // User ID.
+ IssueID int64
+ RepoID int64 `xorm:"INDEX"`
+ MilestoneID int64
IsRead bool
IsAssigned bool
IsMentioned bool
@@ -294,70 +449,72 @@ 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) error {
- users, err := repo.GetCollaborators()
+func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error {
+ users, err := repo.GetAssignees()
if err != nil {
return err
}
iu := &IssueUser{
- IssueId: issueID,
- RepoId: repo.Id,
+ IssueID: issue.ID,
+ RepoID: repo.ID,
}
+ // Poster can be anyone.
isNeedAddPoster := true
for _, u := range users {
- iu.Id = 0
- iu.Uid = u.Id
- iu.IsPoster = iu.Uid == posterID
+ iu.ID = 0
+ iu.UID = u.Id
+ iu.IsPoster = iu.UID == issue.PosterID
if isNeedAddPoster && iu.IsPoster {
isNeedAddPoster = false
}
- iu.IsAssigned = iu.Uid == assigneeID
- if _, err = x.Insert(iu); err != nil {
+ iu.IsAssigned = iu.UID == issue.AssigneeID
+ if _, err = e.Insert(iu); err != nil {
return err
}
}
if isNeedAddPoster {
- iu.Id = 0
- iu.Uid = posterID
+ iu.ID = 0
+ iu.UID = issue.PosterID
iu.IsPoster = true
- iu.IsAssigned = iu.Uid == assigneeID
- if _, err = x.Insert(iu); err != nil {
+ if _, err = e.Insert(iu); err != nil {
return err
}
}
+ return nil
+}
- // 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
- }
+// NewIssueUsers adds new issue-user relations for new issue of repository.
+func NewIssueUsers(repo *Repository, issue *Issue) (err error) {
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
}
- return nil
+ if err = newIssueUsers(sess, repo, issue); err != nil {
+ return err
+ }
+
+ return sess.Commit()
}
// PairsContains returns true when pairs list contains given issue.
func PairsContains(ius []*IssueUser, issueId, uid int64) int {
for i := range ius {
- if ius[i].IssueId == issueId &&
- ius[i].Uid == uid {
+ if ius[i].IssueID == issueId &&
+ ius[i].UID == uid {
return i
}
}
return -1
}
-// GetIssueUserPairs returns issue-user pairs by given repository and user.
-func GetIssueUserPairs(rid, uid int64, isClosed bool) ([]*IssueUser, error) {
+// GetIssueUsers returns issue-user pairs by given repository and user.
+func GetIssueUsers(rid, uid int64, isClosed bool) ([]*IssueUser, error) {
ius := make([]*IssueUser, 0, 10)
- err := x.Where("is_closed=?", isClosed).Find(&ius, &IssueUser{RepoId: rid, Uid: uid})
+ err := x.Where("is_closed=?", isClosed).Find(&ius, &IssueUser{RepoID: rid, UID: uid})
return ius, err
}
@@ -420,50 +577,58 @@ const (
FM_MENTION
)
+func parseCountResult(results []map[string][]byte) int64 {
+ if len(results) == 0 {
+ return 0
+ }
+ for _, result := range results[0] {
+ return com.StrTo(string(result)).MustInt64()
+ }
+ return 0
+}
+
// GetIssueStats returns issue statistic information by given conditions.
func GetIssueStats(repoID, uid, labelID, milestoneID int64, isShowClosed bool, filterMode int) *IssueStats {
stats := &IssueStats{}
- issue := new(Issue)
+ // issue := new(Issue)
- queryStr := "issue.repo_id=? AND issue.is_closed=?"
+ queryStr := "SELECT COUNT(*) FROM `issue` "
if labelID > 0 {
- queryStr += " AND issue.label_ids like '%$" + com.ToStr(labelID) + "|%'"
+ queryStr += "INNER JOIN `issue_label` ON `issue`.id=`issue_label`.issue_id AND `issue_label`.label_id=" + com.ToStr(labelID)
}
+
+ baseCond := " WHERE issue.repo_id=? AND issue.is_closed=?"
if milestoneID > 0 {
- queryStr += " AND milestone_id=" + com.ToStr(milestoneID)
+ baseCond += " AND issue.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
+ resutls, _ := x.Query(queryStr+baseCond, repoID, false)
+ stats.OpenCount = parseCountResult(resutls)
+ resutls, _ = x.Query(queryStr+baseCond, repoID, true)
+ stats.ClosedCount = parseCountResult(resutls)
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
+ baseCond += " AND assignee_id=?"
+ resutls, _ := x.Query(queryStr+baseCond, repoID, false, uid)
+ stats.OpenCount = parseCountResult(resutls)
+ resutls, _ = x.Query(queryStr+baseCond, repoID, true, uid)
+ stats.ClosedCount = parseCountResult(resutls)
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
+ baseCond += " AND poster_id=?"
+ resutls, _ := x.Query(queryStr+baseCond, repoID, false, uid)
+ stats.OpenCount = parseCountResult(resutls)
+ resutls, _ = x.Query(queryStr+baseCond, repoID, true, uid)
+ stats.ClosedCount = parseCountResult(resutls)
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
- }
-
- 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
+ queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id"
+ baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?"
+ resutls, _ := x.Query(queryStr+baseCond, repoID, false, uid, true)
+ stats.OpenCount = parseCountResult(resutls)
+ resutls, _ = x.Query(queryStr+baseCond, repoID, true, uid, true)
+ stats.ClosedCount = parseCountResult(resutls)
}
return stats
}
@@ -477,51 +642,64 @@ func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
return stats
}
+func updateIssue(e Engine, issue *Issue) error {
+ _, err := e.Id(issue.ID).AllCols().Update(issue)
+ return err
+}
+
// UpdateIssue updates information of issue.
func UpdateIssue(issue *Issue) error {
- _, err := x.Id(issue.ID).AllCols().Update(issue)
-
- if err != nil {
- return err
- }
+ return updateIssue(x, issue)
+}
+func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error {
+ _, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID)
return err
}
-// UpdateIssueUserByStatus updates issue-user pairs by issue status.
-func UpdateIssueUserPairsByStatus(iid int64, isClosed bool) error {
- rawSql := "UPDATE `issue_user` SET is_closed = ? WHERE issue_id = ?"
- _, err := x.Exec(rawSql, isClosed, iid)
- return err
+// UpdateIssueUsersByStatus updates issue-user relations by issue status.
+func UpdateIssueUsersByStatus(issueID int64, isClosed bool) error {
+ return updateIssueUsersByStatus(x, issueID, isClosed)
}
-// UpdateIssueUserPairByAssignee updates issue-user pair for assigning.
-func UpdateIssueUserPairByAssignee(aid, iid int64) error {
- rawSql := "UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?"
- if _, err := x.Exec(rawSql, false, iid); err != nil {
+func updateIssueUserByAssignee(e *xorm.Session, issueID, assigneeID int64) (err error) {
+ if _, err = e.Exec("UPDATE `issue_user` SET is_assigned=? WHERE issue_id=?", false, issueID); err != nil {
return err
}
// Assignee ID equals to 0 means clear assignee.
- if aid == 0 {
+ if assigneeID == 0 {
return nil
}
- rawSql = "UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?"
- _, err := x.Exec(rawSql, true, aid, iid)
+ _, err = e.Exec("UPDATE `issue_user` SET is_assigned=? WHERE uid=? AND issue_id=?", true, assigneeID, issueID)
return err
}
-// UpdateIssueUserPairByRead updates issue-user pair for reading.
-func UpdateIssueUserPairByRead(uid, iid int64) error {
- rawSql := "UPDATE `issue_user` SET is_read = ? WHERE uid = ? AND issue_id = ?"
- _, err := x.Exec(rawSql, true, uid, iid)
+// UpdateIssueUserByAssignee updates issue-user relation for assignee.
+func UpdateIssueUserByAssignee(issueID, assigneeID int64) (err error) {
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if err = updateIssueUserByAssignee(sess, issueID, assigneeID); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
+
+// UpdateIssueUserByRead updates issue-user relation for reading.
+func UpdateIssueUserByRead(uid, issueID int64) error {
+ _, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
return err
}
-// UpdateIssueUserPairsByMentions updates issue-user pairs by mentioning.
-func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
+// UpdateIssueUsersByMentions updates issue-user pairs by mentioning.
+func UpdateIssueUsersByMentions(uids []int64, iid int64) error {
for _, uid := range uids {
- iu := &IssueUser{Uid: uid, IssueId: iid}
+ iu := &IssueUser{UID: uid, IssueID: iid}
has, err := x.Get(iu)
if err != nil {
return err
@@ -529,7 +707,7 @@ func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
iu.IsMentioned = true
if has {
- _, err = x.Id(iu.Id).AllCols().Update(iu)
+ _, err = x.Id(iu.ID).AllCols().Update(iu)
} else {
_, err = x.Insert(iu)
}
@@ -550,7 +728,7 @@ func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
// Label represents a label of repository for issues.
type Label struct {
ID int64 `xorm:"pk autoincr"`
- RepoId int64 `xorm:"INDEX"`
+ RepoID int64 `xorm:"INDEX"`
Name string
Color string `xorm:"VARCHAR(7)"`
NumIssues int
@@ -570,10 +748,9 @@ func NewLabel(l *Label) error {
return err
}
-// GetLabelById returns a label by given ID.
-func GetLabelById(id int64) (*Label, error) {
+func getLabelByID(e Engine, id int64) (*Label, error) {
if id <= 0 {
- return nil, ErrLabelNotExist
+ return nil, ErrLabelNotExist{id}
}
l := &Label{ID: id}
@@ -581,58 +758,143 @@ func GetLabelById(id int64) (*Label, error) {
if err != nil {
return nil, err
} else if !has {
- return nil, ErrLabelNotExist
+ return nil, ErrLabelNotExist{l.ID}
}
return l, nil
}
-// GetLabels returns a list of labels of given repository ID.
-func GetLabels(repoId int64) ([]*Label, error) {
+// GetLabelByID returns a label by given ID.
+func GetLabelByID(id int64) (*Label, error) {
+ return getLabelByID(x, id)
+}
+
+// GetLabelsByRepoID returns all labels that belong to given repository by ID.
+func GetLabelsByRepoID(repoID int64) ([]*Label, error) {
labels := make([]*Label, 0, 10)
- err := x.Where("repo_id=?", repoId).Find(&labels)
- return labels, err
+ return labels, x.Where("repo_id=?", repoID).Find(&labels)
+}
+
+func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
+ issueLabels, err := getIssueLabels(e, issueID)
+ if err != nil {
+ return nil, fmt.Errorf("getIssueLabels: %v", err)
+ }
+
+ var label *Label
+ labels := make([]*Label, 0, len(issueLabels))
+ for idx := range issueLabels {
+ label, err = getLabelByID(e, issueLabels[idx].LabelID)
+ if err != nil && !IsErrLabelNotExist(err) {
+ return nil, fmt.Errorf("getLabelByID: %v", err)
+ }
+ labels = append(labels, label)
+ }
+ return labels, nil
+}
+
+// GetLabelsByIssueID returns all labels that belong to given issue by ID.
+func GetLabelsByIssueID(issueID int64) ([]*Label, error) {
+ return getLabelsByIssueID(x, issueID)
+}
+
+func updateLabel(e Engine, l *Label) error {
+ _, err := e.Id(l.ID).AllCols().Update(l)
+ return err
}
// UpdateLabel updates label information.
func UpdateLabel(l *Label) error {
- _, err := x.Id(l.ID).AllCols().Update(l)
- return err
+ return updateLabel(x, l)
}
// DeleteLabel delete a label of given repository.
func DeleteLabel(repoID, labelID int64) error {
- l, err := GetLabelById(labelID)
+ l, err := GetLabelByID(labelID)
if err != nil {
- if err == ErrLabelNotExist {
+ if IsErrLabelNotExist(err) {
return nil
}
return err
}
- issues, err := GetIssuesByLabel(repoID, labelID)
- if err != nil {
- return err
- }
-
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
- for _, issue := range issues {
- 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 {
+ if _, err = x.Where("label_id=?", labelID).Delete(new(IssueLabel)); err != nil {
+ return err
+ } else if _, err = sess.Delete(l); err != nil {
return err
}
return sess.Commit()
}
+// .___ .____ ___. .__
+// | | ______ ________ __ ____ | | _____ \_ |__ ____ | |
+// | |/ ___// ___/ | \_/ __ \| | \__ \ | __ \_/ __ \| |
+// | |\___ \ \___ \| | /\ ___/| |___ / __ \| \_\ \ ___/| |__
+// |___/____ >____ >____/ \___ >_______ (____ /___ /\___ >____/
+// \/ \/ \/ \/ \/ \/ \/
+
+// IssueLabel represetns an issue-lable relation.
+type IssueLabel struct {
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"UNIQUE(s)"`
+ LabelID int64 `xorm:"UNIQUE(s)"`
+}
+
+func hasIssueLabel(e Engine, issueID, labelID int64) bool {
+ has, _ := e.Where("issue_id=? AND label_id=?", issueID, labelID).Get(new(IssueLabel))
+ return has
+}
+
+// HasIssueLabel returns true if issue has been labeled.
+func HasIssueLabel(issueID, labelID int64) bool {
+ return hasIssueLabel(x, issueID, labelID)
+}
+
+func newIssueLabel(e Engine, issueID, labelID int64) error {
+ if issueID == 0 || labelID == 0 {
+ return nil
+ }
+
+ _, err := e.Insert(&IssueLabel{
+ IssueID: issueID,
+ LabelID: labelID,
+ })
+ return err
+}
+
+// NewIssueLabel creates a new issue-label relation.
+func NewIssueLabel(issueID, labelID int64) error {
+ return newIssueLabel(x, issueID, labelID)
+}
+
+func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) {
+ issueLabels := make([]*IssueLabel, 0, 10)
+ return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels)
+}
+
+// GetIssueLabels returns all issue-label relations of given issue by ID.
+func GetIssueLabels(issueID int64) ([]*IssueLabel, error) {
+ return getIssueLabels(x, issueID)
+}
+
+func deleteIssueLabel(e Engine, issueID, labelID int64) error {
+ _, err := e.Delete(&IssueLabel{
+ IssueID: issueID,
+ LabelID: labelID,
+ })
+ return err
+}
+
+// DeleteIssueLabel deletes issue-label relation.
+func DeleteIssueLabel(issueID, labelID int64) error {
+ return deleteIssueLabel(x, issueID, labelID)
+}
+
// _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
@@ -658,6 +920,14 @@ type Milestone struct {
ClosedDate time.Time
}
+func (m *Milestone) BeforeUpdate() {
+ if m.NumIssues > 0 {
+ m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
+ } else {
+ m.Completeness = 0
+ }
+}
+
func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
if colName == "deadline" {
if m.Deadline.Year() == 9999 {
@@ -694,14 +964,30 @@ func NewMilestone(m *Milestone) (err error) {
return sess.Commit()
}
+func getMilestoneByID(e Engine, 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{id, 0}
+ }
+ return m, nil
+}
+
// GetMilestoneByID returns the milestone of given ID.
func GetMilestoneByID(id int64) (*Milestone, error) {
- m := &Milestone{ID: id}
+ return getMilestoneByID(x, id)
+}
+
+// GetRepoMilestoneByID returns the milestone of given ID and repository.
+func GetRepoMilestoneByID(repoID, milestoneID int64) (*Milestone, error) {
+ m := &Milestone{ID: milestoneID, RepoID: repoID}
has, err := x.Get(m)
if err != nil {
return nil, err
} else if !has {
- return nil, ErrMilestoneNotExist{id}
+ return nil, ErrMilestoneNotExist{milestoneID, repoID}
}
return m, nil
}
@@ -760,7 +1046,7 @@ func MilestoneStats(repoID int64) (open int64, closed int64) {
// 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
}
@@ -776,22 +1062,20 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
return err
}
- 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 {
+ 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.
-func ChangeMilestoneIssueStats(issue *Issue) error {
+func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error {
if issue.MilestoneID == 0 {
return nil
}
- m, err := GetMilestoneByID(issue.MilestoneID)
+ m, err := getMilestoneByID(e, issue.MilestoneID)
if err != nil {
return err
}
@@ -804,21 +1088,28 @@ func ChangeMilestoneIssueStats(issue *Issue) error {
m.NumClosedIssues--
}
- m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
-
- return UpdateMilestone(m)
+ return updateMilestone(e, m)
}
-// ChangeMilestoneAssign changes assignment of milestone for issue.
-func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
+// ChangeMilestoneIssueStats updates the open/closed issues counter and progress
+// for the milestone associated witht the given issue.
+func ChangeMilestoneIssueStats(issue *Issue) (err error) {
sess := x.NewSession()
- defer sess.Close()
+ defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
+ if err = changeMilestoneIssueStats(sess, issue); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
+
+func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error {
if oldMid > 0 {
- m, err := GetMilestoneByID(oldMid)
+ m, err := getMilestoneByID(e, oldMid)
if err != nil {
return err
}
@@ -827,26 +1118,16 @@ func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
if issue.IsClosed {
m.NumClosedIssues--
}
- if m.NumIssues > 0 {
- m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
- } else {
- m.Completeness = 0
- }
- if _, err = sess.Id(m.ID).Cols("num_issues,num_completeness,num_closed_issues").Update(m); err != nil {
- sess.Rollback()
+ if err = updateMilestone(e, m); err != nil {
return err
- }
-
- rawSql := "UPDATE `issue_user` SET milestone_id = 0 WHERE issue_id = ?"
- if _, err = sess.Exec(rawSql, issue.ID); err != nil {
- sess.Rollback()
+ } else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id=0 WHERE issue_id=?", issue.ID); err != nil {
return err
}
}
- if mid > 0 {
- m, err := GetMilestoneByID(mid)
+ if issue.MilestoneID > 0 {
+ m, err := GetMilestoneByID(issue.MilestoneID)
if err != nil {
return err
}
@@ -860,19 +1141,28 @@ func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
return ErrWrongIssueCounter
}
- 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 {
- sess.Rollback()
+ if err = updateMilestone(e, m); err != nil {
return err
- }
-
- rawSql := "UPDATE `issue_user` SET milestone_id = ? WHERE issue_id = ?"
- if _, err = sess.Exec(rawSql, m.ID, issue.ID); err != nil {
- sess.Rollback()
+ } else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id=? WHERE issue_id=?", m.ID, issue.ID); err != nil {
return err
}
}
+ return nil
+}
+
+// ChangeMilestoneAssign changes assignment of milestone for issue.
+func ChangeMilestoneAssign(oldMid int64, issue *Issue) (err error) {
+ sess := x.NewSession()
+ defer sess.Close()
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if err = changeMilestoneAssign(sess, oldMid, issue); err != nil {
+ return err
+ }
+
return sess.Commit()
}
@@ -886,7 +1176,7 @@ func DeleteMilestoneByID(mid int64) error {
return err
}
- repo, err := GetRepositoryById(m.RepoID)
+ repo, err := GetRepositoryByID(m.RepoID)
if err != nil {
return err
}
@@ -901,9 +1191,9 @@ func DeleteMilestoneByID(mid int64) error {
return err
}
- 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 {
+ 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
}
@@ -932,173 +1222,279 @@ const (
COMMENT_TYPE_CLOSE
// References.
- COMMENT_TYPE_ISSUE
- // Reference from some commit (not part of a pull request)
- COMMENT_TYPE_COMMIT
- // Reference from some pull request
- COMMENT_TYPE_PULL
+ COMMENT_TYPE_ISSUE_REF
+ // Reference from a commit (not part of a pull request)
+ COMMENT_TYPE_COMMIT_REF
+ // Reference from a comment
+ COMMENT_TYPE_COMMENT_REF
+ // Reference from a pull request
+ COMMENT_TYPE_PULL_REF
+)
+
+type CommentTag int
+
+const (
+ COMMENT_TAG_NONE CommentTag = iota
+ COMMENT_TAG_POSTER
+ COMMENT_TAG_ADMIN
+ COMMENT_TAG_OWNER
)
// Comment represents a comment in commit and issue page.
type Comment struct {
- Id int64
- Type CommentType
- PosterId int64
- Poster *User `xorm:"-"`
- IssueId int64
- CommitId int64
- Line int64
- Content string `xorm:"TEXT"`
- Created time.Time `xorm:"CREATED"`
+ ID int64 `xorm:"pk autoincr"`
+ Type CommentType
+ PosterID int64
+ Poster *User `xorm:"-"`
+ IssueID int64 `xorm:"INDEX"`
+ CommitID int64
+ Line int64
+ Content string `xorm:"TEXT"`
+ RenderedContent string `xorm:"-"`
+ Created time.Time `xorm:"CREATED"`
+
+ Attachments []*Attachment `xorm:"-"`
+
+ // For view issue page.
+ ShowTag CommentTag `xorm:"-"`
}
-// 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 sessionRelease(sess)
- if err := sess.Begin(); err != nil {
- return nil, err
- }
+// HashTag returns unique hash tag for comment.
+func (c *Comment) HashTag() string {
+ return "issuecomment-" + com.ToStr(c.ID)
+}
+
+// EventTag returns unique event hash tag for comment.
+func (c *Comment) EventTag() string {
+ return "event-" + com.ToStr(c.ID)
+}
+
+func (c *Comment) AfterSet(colName string, _ xorm.Cell) {
+ var err error
+ switch colName {
+ case "id":
+ c.Attachments, err = GetAttachmentsByCommentID(c.ID)
+ if err != nil {
+ log.Error(3, "GetAttachmentsByCommentID[%d]: %v", c.ID, err)
+ }
- comment := &Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
- CommitId: commitId, Line: line, Content: content}
+ case "poster_id":
+ c.Poster, err = GetUserByID(c.PosterID)
+ if err != nil {
+ if IsErrUserNotExist(err) {
+ c.PosterID = -1
+ c.Poster = &User{Name: "someone"}
+ } else {
+ log.Error(3, "GetUserByID[%d]: %v", c.ID, err)
+ }
+ }
+ }
+}
- if _, err := sess.Insert(comment); err != nil {
+func createComment(e *xorm.Session, u *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, uuids []string) (_ *Comment, err error) {
+ comment := &Comment{
+ PosterID: u.Id,
+ Type: cmtType,
+ IssueID: issue.ID,
+ CommitID: commitID,
+ Line: line,
+ Content: content,
+ }
+ if _, err = e.Insert(comment); err != nil {
return nil, err
}
// Check comment type.
switch cmtType {
case COMMENT_TYPE_COMMENT:
- rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
- if _, err := sess.Exec(rawSql, issueId); err != nil {
+ if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", issue.ID); err != nil {
return nil, err
}
- if len(attachments) > 0 {
- rawSql = "UPDATE `attachment` SET comment_id = ? WHERE id IN (?)"
-
- astrs := make([]string, 0, len(attachments))
-
- for _, a := range attachments {
- astrs = append(astrs, strconv.FormatInt(a, 10))
+ // Check attachments.
+ attachments := make([]*Attachment, 0, len(uuids))
+ for _, uuid := range uuids {
+ attach, err := getAttachmentByUUID(e, uuid)
+ if err != nil {
+ if IsErrAttachmentNotExist(err) {
+ continue
+ }
+ return nil, fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err)
}
+ attachments = append(attachments, attach)
+ }
- if _, err := sess.Exec(rawSql, comment.Id, strings.Join(astrs, ",")); err != nil {
- return nil, err
+ for i := range attachments {
+ attachments[i].IssueID = issue.ID
+ attachments[i].CommentID = comment.ID
+ // No assign value could be 0, so ignore AllCols().
+ if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil {
+ return nil, fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
}
}
+
+ // Notify watchers.
+ act := &Action{
+ ActUserID: u.Id,
+ ActUserName: u.LowerName,
+ ActEmail: u.Email,
+ OpType: COMMENT_ISSUE,
+ Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
+ RepoID: repo.ID,
+ RepoUserName: repo.Owner.LowerName,
+ RepoName: repo.LowerName,
+ IsPrivate: repo.IsPrivate,
+ }
+ if err = notifyWatchers(e, act); err != nil {
+ 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 {
+ if _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", repo.ID); err != nil {
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 {
+ if _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", repo.ID); err != nil {
return nil, err
}
}
- return comment, sess.Commit()
+ return comment, nil
}
-// GetCommentById returns the comment with the given id
-func GetCommentById(commentId int64) (*Comment, error) {
- c := &Comment{Id: commentId}
- _, err := x.Get(c)
+func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) {
+ cmtType := COMMENT_TYPE_CLOSE
+ if !issue.IsClosed {
+ cmtType = COMMENT_TYPE_REOPEN
+ }
+ return createComment(e, doer, repo, issue, 0, 0, cmtType, "", nil)
+}
- return c, err
+// CreateComment creates comment of issue or commit.
+func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, attachments []string) (comment *Comment, err error) {
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return nil, err
+ }
+
+ comment, err = createComment(sess, doer, repo, issue, commitID, line, cmtType, content, attachments)
+ if err != nil {
+ return nil, err
+ }
+
+ return comment, sess.Commit()
}
-func (c *Comment) ContentHtml() template.HTML {
- return template.HTML(c.Content)
+// CreateIssueComment creates a plain issue comment.
+func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
+ return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, attachments)
}
-// GetIssueComments returns list of comment by given issue id.
-func GetIssueComments(issueId int64) ([]Comment, error) {
- comments := make([]Comment, 0, 10)
- err := x.Asc("created").Find(&comments, &Comment{IssueId: issueId})
- return comments, err
+// GetCommentById returns the comment with the given id
+func GetCommentById(id int64) (*Comment, error) {
+ c := new(Comment)
+ _, err := x.Id(id).Get(c)
+ return c, err
}
-// Attachments returns the attachments for this comment.
-func (c *Comment) Attachments() []*Attachment {
- a, _ := GetAttachmentsByComment(c.Id)
- return a
+// GetCommentsByIssueID returns all comments of issue by given ID.
+func GetCommentsByIssueID(issueID int64) ([]*Comment, error) {
+ comments := make([]*Comment, 0, 10)
+ return comments, x.Where("issue_id=?", issueID).Asc("created").Find(&comments)
}
func (c *Comment) AfterDelete() {
- _, err := DeleteAttachmentsByComment(c.Id, true)
+ _, err := DeleteAttachmentsByComment(c.ID, true)
if err != nil {
- log.Info("Could not delete files for comment %d on issue #%d: %s", c.Id, c.IssueId, err)
+ log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err)
}
}
+// Attachment represent a attachment of issue/comment/release.
type Attachment struct {
- Id int64
- IssueId int64
- CommentId int64
+ ID int64 `xorm:"pk autoincr"`
+ UUID string `xorm:"uuid UNIQUE"`
+ IssueID int64 `xorm:"INDEX"`
+ CommentID int64
+ ReleaseID int64 `xorm:"INDEX"`
Name string
- Path string `xorm:"TEXT"`
Created time.Time `xorm:"CREATED"`
}
-// CreateAttachment creates a new attachment inside the database and
-func CreateAttachment(issueId, commentId int64, name, path string) (*Attachment, error) {
- sess := x.NewSession()
- defer sess.Close()
+// AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
+func AttachmentLocalPath(uuid string) string {
+ return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid)
+}
+
+// LocalPath returns where attachment is stored in local file system.
+func (attach *Attachment) LocalPath() string {
+ return AttachmentLocalPath(attach.UUID)
+}
+// NewAttachment creates a new attachment object.
+func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) {
+ attach := &Attachment{
+ UUID: gouuid.NewV4().String(),
+ Name: name,
+ }
+
+ if err = os.MkdirAll(path.Dir(attach.LocalPath()), os.ModePerm); err != nil {
+ return nil, fmt.Errorf("MkdirAll: %v", err)
+ }
+
+ fw, err := os.Create(attach.LocalPath())
+ if err != nil {
+ return nil, fmt.Errorf("Create: %v", err)
+ }
+ defer fw.Close()
+
+ if _, err = fw.Write(buf); err != nil {
+ return nil, fmt.Errorf("Write: %v", err)
+ } else if _, err = io.Copy(fw, file); err != nil {
+ return nil, fmt.Errorf("Copy: %v", err)
+ }
+
+ sess := x.NewSession()
+ defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
return nil, err
}
- a := &Attachment{IssueId: issueId, CommentId: commentId, Name: name, Path: path}
-
- if _, err := sess.Insert(a); err != nil {
- sess.Rollback()
+ if _, err := sess.Insert(attach); err != nil {
return nil, err
}
- return a, sess.Commit()
+ return attach, sess.Commit()
}
-// Attachment returns the attachment by given ID.
-func GetAttachmentById(id int64) (*Attachment, error) {
- m := &Attachment{Id: id}
-
- has, err := x.Get(m)
-
+func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) {
+ attach := &Attachment{UUID: uuid}
+ has, err := x.Get(attach)
if err != nil {
return nil, err
+ } else if !has {
+ return nil, ErrAttachmentNotExist{0, uuid}
}
-
- if !has {
- return nil, ErrAttachmentNotExist
- }
-
- return m, nil
+ return attach, nil
}
-func GetAttachmentsForIssue(issueId int64) ([]*Attachment, error) {
- attachments := make([]*Attachment, 0, 10)
- err := x.Where("issue_id = ?", issueId).And("comment_id = 0").Find(&attachments)
- return attachments, err
+// GetAttachmentByUUID returns attachment by given UUID.
+func GetAttachmentByUUID(uuid string) (*Attachment, error) {
+ return getAttachmentByUUID(x, uuid)
}
-// GetAttachmentsByIssue returns a list of attachments for the given issue
-func GetAttachmentsByIssue(issueId int64) ([]*Attachment, error) {
+// GetAttachmentsByIssueID returns all attachments for given issue by ID.
+func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10)
- err := x.Where("issue_id = ?", issueId).And("comment_id > 0").Find(&attachments)
- return attachments, err
+ return attachments, x.Where("issue_id=? AND comment_id=0", issueID).Find(&attachments)
}
-// GetAttachmentsByComment returns a list of attachments for the given comment
-func GetAttachmentsByComment(commentId int64) ([]*Attachment, error) {
+// GetAttachmentsByCommentID returns all attachments if comment by given ID.
+func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10)
- err := x.Where("comment_id = ?", commentId).Find(&attachments)
- return attachments, err
+ return attachments, x.Where("comment_id=?", commentID).Find(&attachments)
}
// DeleteAttachment deletes the given attachment and optionally the associated file.
@@ -1111,12 +1507,12 @@ func DeleteAttachment(a *Attachment, remove bool) error {
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
for i, a := range attachments {
if remove {
- if err := os.Remove(a.Path); err != nil {
+ if err := os.Remove(a.LocalPath()); err != nil {
return i, err
}
}
- if _, err := x.Delete(a.Id); err != nil {
+ if _, err := x.Delete(a.ID); err != nil {
return i, err
}
}
@@ -1126,7 +1522,7 @@ func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) {
- attachments, err := GetAttachmentsByIssue(issueId)
+ attachments, err := GetAttachmentsByIssueID(issueId)
if err != nil {
return 0, err
@@ -1137,7 +1533,7 @@ func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) {
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) {
- attachments, err := GetAttachmentsByComment(commentId)
+ attachments, err := GetAttachmentsByCommentID(commentId)
if err != nil {
return 0, err
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index c7900743..f1d6c91a 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -5,8 +5,12 @@
package migrations
import (
+ "bytes"
"encoding/json"
"fmt"
+ "io/ioutil"
+ "os"
+ "path"
"strings"
"time"
@@ -16,6 +20,7 @@ import (
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting"
+ gouuid "github.com/gogits/gogs/modules/uuid"
)
const _MIN_DB_VER = 0
@@ -58,6 +63,8 @@ var migrations = []Migration{
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
+ NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
+ NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
}
// Migrate database to current version
@@ -96,6 +103,12 @@ func Migrate(x *xorm.Engine) error {
}
v := currentVersion.Version
+ if int(v-_MIN_DB_VER) > len(migrations) {
+ // User downgraded Gogs.
+ currentVersion.Version = int64(len(migrations) + _MIN_DB_VER)
+ _, err = x.Id(1).Update(currentVersion)
+ return err
+ }
for i, m := range migrations[v-_MIN_DB_VER:] {
log.Info("Migration: %s", m.Description())
if err = m.Migrate(x); err != nil {
@@ -130,6 +143,9 @@ func accessToCollaboration(x *xorm.Engine) (err error) {
results, err := x.Query("SELECT u.id AS `uid`, a.repo_name AS `repo`, a.mode AS `mode`, a.created as `created` FROM `access` a JOIN `user` u ON a.user_name=u.lower_name")
if err != nil {
+ if strings.Contains(err.Error(), "no such column") {
+ return nil
+ }
return err
}
@@ -278,6 +294,9 @@ func accessRefactor(x *xorm.Engine) (err error) {
results, err = x.Query("SELECT `id`,`authorize`,`repo_ids` FROM `team` WHERE org_id=? AND authorize>? ORDER BY `authorize` ASC", ownerID, int(minAccessLevel))
if err != nil {
+ if strings.Contains(err.Error(), "no such column") {
+ return nil
+ }
return fmt.Errorf("select teams from org: %v", err)
}
@@ -339,6 +358,9 @@ func teamToTeamRepo(x *xorm.Engine) error {
results, err := x.Query("SELECT `id`,`org_id`,`repo_ids` FROM `team`")
if err != nil {
+ if strings.Contains(err.Error(), "no such column") {
+ return nil
+ }
return fmt.Errorf("select teams: %v", err)
}
for _, team := range results {
@@ -369,7 +391,7 @@ func teamToTeamRepo(x *xorm.Engine) error {
}
if err = sess.Sync2(new(TeamRepo)); err != nil {
- return fmt.Errorf("sync: %v", err)
+ return fmt.Errorf("sync2: %v", err)
} else if _, err = sess.Insert(teamRepos); err != nil {
return fmt.Errorf("insert team-repos: %v", err)
}
@@ -453,3 +475,134 @@ func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
}
return sess.Commit()
}
+
+func issueToIssueLabel(x *xorm.Engine) error {
+ type IssueLabel struct {
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"UNIQUE(s)"`
+ LabelID int64 `xorm:"UNIQUE(s)"`
+ }
+
+ issueLabels := make([]*IssueLabel, 0, 50)
+ results, err := x.Query("SELECT `id`,`label_ids` FROM `issue`")
+ if err != nil {
+ if strings.Contains(err.Error(), "no such column") {
+ return nil
+ }
+ return fmt.Errorf("select issues: %v", err)
+ }
+ for _, issue := range results {
+ issueID := com.StrTo(issue["id"]).MustInt64()
+
+ // Just in case legacy code can have duplicated IDs for same label.
+ mark := make(map[int64]bool)
+ for _, idStr := range strings.Split(string(issue["label_ids"]), "|") {
+ labelID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
+ if labelID == 0 || mark[labelID] {
+ continue
+ }
+
+ mark[labelID] = true
+ issueLabels = append(issueLabels, &IssueLabel{
+ IssueID: issueID,
+ LabelID: labelID,
+ })
+ }
+ }
+
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if err = sess.Sync2(new(IssueLabel)); err != nil {
+ return fmt.Errorf("sync2: %v", err)
+ } else if _, err = sess.Insert(issueLabels); err != nil {
+ return fmt.Errorf("insert issue-labels: %v", err)
+ }
+
+ return sess.Commit()
+}
+
+func attachmentRefactor(x *xorm.Engine) error {
+ type Attachment struct {
+ ID int64 `xorm:"pk autoincr"`
+ UUID string `xorm:"uuid INDEX"`
+
+ // For rename purpose.
+ Path string `xorm:"-"`
+ NewPath string `xorm:"-"`
+ }
+
+ results, err := x.Query("SELECT * FROM `attachment`")
+ if err != nil {
+ return fmt.Errorf("select attachments: %v", err)
+ }
+
+ attachments := make([]*Attachment, 0, len(results))
+ for _, attach := range results {
+ if !com.IsExist(string(attach["path"])) {
+ // If the attachment is already missing, there is no point to update it.
+ continue
+ }
+ attachments = append(attachments, &Attachment{
+ ID: com.StrTo(attach["id"]).MustInt64(),
+ UUID: gouuid.NewV4().String(),
+ Path: string(attach["path"]),
+ })
+ }
+
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if err = sess.Sync2(new(Attachment)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
+ }
+
+ // Note: Roll back for rename can be a dead loop,
+ // so produces a backup file.
+ var buf bytes.Buffer
+ buf.WriteString("# old path -> new path\n")
+
+ // Update database first because this is where error happens the most often.
+ for _, attach := range attachments {
+ if _, err = sess.Id(attach.ID).Update(attach); err != nil {
+ return err
+ }
+
+ attach.NewPath = path.Join(setting.AttachmentPath, attach.UUID[0:1], attach.UUID[1:2], attach.UUID)
+ buf.WriteString(attach.Path)
+ buf.WriteString("\t")
+ buf.WriteString(attach.NewPath)
+ buf.WriteString("\n")
+ }
+
+ // Then rename attachments.
+ isSucceed := true
+ defer func() {
+ if isSucceed {
+ return
+ }
+
+ dumpPath := path.Join(setting.LogRootPath, "attachment_path.dump")
+ ioutil.WriteFile(dumpPath, buf.Bytes(), 0666)
+ fmt.Println("Fail to rename some attachments, old and new paths are saved into:", dumpPath)
+ }()
+ for _, attach := range attachments {
+ if err = os.MkdirAll(path.Dir(attach.NewPath), os.ModePerm); err != nil {
+ isSucceed = false
+ return err
+ }
+
+ if err = os.Rename(attach.Path, attach.NewPath); err != nil {
+ isSucceed = false
+ return err
+ }
+ }
+
+ return sess.Commit()
+}
diff --git a/models/models.go b/models/models.go
index 01b96c0f..e06d5cf8 100644
--- a/models/models.go
+++ b/models/models.go
@@ -57,7 +57,8 @@ func init() {
new(User), new(PublicKey), new(Oauth2), new(AccessToken),
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(Issue), new(Comment), new(Attachment), new(IssueUser),
+ new(Label), new(IssueLabel), new(Milestone),
new(Mirror), new(Release), new(LoginSource), new(Webhook),
new(UpdateTask), new(HookTask),
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
diff --git a/models/oauth2.go b/models/oauth2.go
index d19e248c..a15f7b6f 100644
--- a/models/oauth2.go
+++ b/models/oauth2.go
@@ -58,7 +58,7 @@ func GetOauth2(identity string) (oa *Oauth2, err error) {
} else if oa.Uid == -1 {
return oa, ErrOauth2NotAssociated
}
- oa.User, err = GetUserById(oa.Uid)
+ oa.User, err = GetUserByID(oa.Uid)
return oa, err
}
diff --git a/models/org.go b/models/org.go
index 3caed30b..b8d19456 100644
--- a/models/org.go
+++ b/models/org.go
@@ -66,7 +66,7 @@ func (org *User) GetMembers() error {
org.Members = make([]*User, len(ous))
for i, ou := range ous {
- org.Members[i], err = GetUserById(ou.Uid)
+ org.Members[i], err = GetUserByID(ou.Uid)
if err != nil {
return err
}
@@ -343,11 +343,11 @@ func RemoveOrgUser(orgId, uid int64) error {
return nil
}
- u, err := GetUserById(uid)
+ u, err := GetUserByID(uid)
if err != nil {
return fmt.Errorf("GetUserById: %v", err)
}
- org, err := GetUserById(orgId)
+ org, err := GetUserByID(orgId)
if err != nil {
return fmt.Errorf("get organization: %v", err)
} else if err = org.GetRepositories(); err != nil {
@@ -380,10 +380,10 @@ func RemoveOrgUser(orgId, uid int64) error {
// Delete all repository accesses.
access := &Access{UserID: u.Id}
for _, repo := range org.Repos {
- access.RepoID = repo.Id
+ access.RepoID = repo.ID
if _, err = sess.Delete(access); err != nil {
return err
- } else if err = watchRepo(sess, u.Id, repo.Id, false); err != nil {
+ } else if err = watchRepo(sess, u.Id, repo.ID, false); err != nil {
return err
}
}
@@ -443,7 +443,7 @@ func (t *Team) getRepositories(e Engine) (err error) {
t.Repos = make([]*Repository, 0, len(teamRepos))
for i := range teamRepos {
- repo, err := getRepositoryById(e, teamRepos[i].RepoID)
+ repo, err := getRepositoryByID(e, teamRepos[i].RepoID)
if err != nil {
return fmt.Errorf("getRepositoryById(%d): %v", teamRepos[i].RepoID, err)
}
@@ -487,7 +487,7 @@ func (t *Team) HasRepository(repoID int64) bool {
}
func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
- if err = addTeamRepo(e, t.OrgID, t.ID, repo.Id); err != nil {
+ if err = addTeamRepo(e, t.OrgID, t.ID, repo.ID); err != nil {
return err
}
@@ -504,7 +504,7 @@ func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
return fmt.Errorf("getMembers: %v", err)
}
for _, u := range t.Members {
- if err = watchRepo(e, u.Id, repo.Id, true); err != nil {
+ if err = watchRepo(e, u.Id, repo.ID, true); err != nil {
return fmt.Errorf("watchRepo: %v", err)
}
}
@@ -513,9 +513,9 @@ func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
// AddRepository adds new repository to team of organization.
func (t *Team) AddRepository(repo *Repository) (err error) {
- if repo.OwnerId != t.OrgID {
+ if repo.OwnerID != t.OrgID {
return errors.New("Repository does not belong to organization")
- } else if t.HasRepository(repo.Id) {
+ } else if t.HasRepository(repo.ID) {
return nil
}
@@ -533,7 +533,7 @@ func (t *Team) AddRepository(repo *Repository) (err error) {
}
func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) {
- if err = removeTeamRepo(e, t.ID, repo.Id); err != nil {
+ if err = removeTeamRepo(e, t.ID, repo.ID); err != nil {
return err
}
@@ -560,7 +560,7 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
continue
}
- if err = watchRepo(e, u.Id, repo.Id, false); err != nil {
+ if err = watchRepo(e, u.Id, repo.ID, false); err != nil {
return err
}
}
@@ -574,7 +574,7 @@ func (t *Team) RemoveRepository(repoID int64) error {
return nil
}
- repo, err := GetRepositoryById(repoID)
+ repo, err := GetRepositoryByID(repoID)
if err != nil {
return err
}
@@ -713,7 +713,7 @@ func DeleteTeam(t *Team) error {
}
// Get organization.
- org, err := GetUserById(t.OrgID)
+ org, err := GetUserByID(t.OrgID)
if err != nil {
return err
}
@@ -903,7 +903,7 @@ func removeTeamMember(e Engine, orgId, teamId, uid int64) error {
}
// Get organization.
- org, err := getUserById(e, orgId)
+ org, err := getUserByID(e, orgId)
if err != nil {
return err
}
diff --git a/models/repo.go b/models/repo.go
index 8135bc57..bad6f386 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -129,8 +129,8 @@ func NewRepoContext() {
// Repository represents a git repository.
type Repository struct {
- Id int64
- OwnerId int64 `xorm:"UNIQUE(s)"`
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"UNIQUE(s)"`
Owner *User `xorm:"-"`
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
Name string `xorm:"INDEX NOT NULL"`
@@ -159,8 +159,8 @@ type Repository struct {
*Mirror `xorm:"-"`
IsFork bool `xorm:"NOT NULL DEFAULT false"`
- ForkId int64
- ForkRepo *Repository `xorm:"-"`
+ ForkID int64
+ BaseRepo *Repository `xorm:"-"`
Created time.Time `xorm:"CREATED"`
Updated time.Time `xorm:"UPDATED"`
@@ -168,7 +168,7 @@ type Repository struct {
func (repo *Repository) getOwner(e Engine) (err error) {
if repo.Owner == nil {
- repo.Owner, err = getUserById(e, repo.OwnerId)
+ repo.Owner, err = getUserByID(e, repo.OwnerID)
}
return err
}
@@ -177,17 +177,54 @@ func (repo *Repository) GetOwner() (err error) {
return repo.getOwner(x)
}
+// GetAssignees returns all users that have write access of repository.
+func (repo *Repository) GetAssignees() (_ []*User, err error) {
+ if err = repo.GetOwner(); err != nil {
+ return nil, err
+ }
+
+ accesses := make([]*Access, 0, 10)
+ if err = x.Where("repo_id=? AND mode>=?", repo.ID, ACCESS_MODE_WRITE).Find(&accesses); err != nil {
+ return nil, err
+ }
+
+ users := make([]*User, 0, len(accesses)+1) // Just waste 1 unit does not matter.
+ if !repo.Owner.IsOrganization() {
+ users = append(users, repo.Owner)
+ }
+
+ var u *User
+ for i := range accesses {
+ u, err = GetUserByID(accesses[i].UserID)
+ if err != nil {
+ return nil, err
+ }
+ users = append(users, u)
+ }
+ return users, nil
+}
+
+// GetAssigneeByID returns the user that has write access of repository by given ID.
+func (repo *Repository) GetAssigneeByID(userID int64) (*User, error) {
+ return GetAssigneeByID(repo, userID)
+}
+
+// GetMilestoneByID returns the milestone belongs to repository by given ID.
+func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) {
+ return GetRepoMilestoneByID(repo.ID, milestoneID)
+}
+
func (repo *Repository) GetMirror() (err error) {
- repo.Mirror, err = GetMirror(repo.Id)
+ repo.Mirror, err = GetMirror(repo.ID)
return err
}
-func (repo *Repository) GetForkRepo() (err error) {
+func (repo *Repository) GetBaseRepo() (err error) {
if !repo.IsFork {
return nil
}
- repo.ForkRepo, err = GetRepositoryById(repo.ForkId)
+ repo.BaseRepo, err = GetRepositoryByID(repo.ForkID)
return err
}
@@ -210,8 +247,8 @@ func (repo *Repository) HasAccess(u *User) bool {
return has
}
-func (repo *Repository) IsOwnedBy(u *User) bool {
- return repo.OwnerId == u.Id
+func (repo *Repository) IsOwnedBy(userID int64) bool {
+ return repo.OwnerID == userID
}
// DescriptionHtml does special handles to description and return HTML string.
@@ -224,7 +261,7 @@ func (repo *Repository) DescriptionHtml() template.HTML {
func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) {
has, err := e.Get(&Repository{
- OwnerId: u.Id,
+ OwnerID: u.Id,
LowerName: strings.ToLower(repoName),
})
return has && com.IsDir(RepoPath(u.Name, repoName)), err
@@ -287,8 +324,8 @@ func IsUsableName(name string) error {
// Mirror represents a mirror information of repository.
type Mirror struct {
- Id int64
- RepoId int64
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64
RepoName string // <user name>/<repo name>
Interval int // Hour.
Updated time.Time `xorm:"UPDATED"`
@@ -296,7 +333,7 @@ type Mirror struct {
}
func getMirror(e Engine, repoId int64) (*Mirror, error) {
- m := &Mirror{RepoId: repoId}
+ m := &Mirror{RepoID: repoId}
has, err := e.Get(m)
if err != nil {
return nil, err
@@ -312,7 +349,7 @@ func GetMirror(repoId int64) (*Mirror, error) {
}
func updateMirror(e Engine, m *Mirror) error {
- _, err := e.Id(m.Id).Update(m)
+ _, err := e.Id(m.ID).Update(m)
return err
}
@@ -330,7 +367,7 @@ func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) er
}
if _, err = x.InsertOne(&Mirror{
- RepoId: repoId,
+ RepoID: repoId,
RepoName: strings.ToLower(userName + "/" + repoName),
Interval: 24,
NextUpdate: time.Now().Add(24 * time.Hour),
@@ -365,7 +402,7 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
repo.IsBare = false
if mirror {
- if err = MirrorRepository(repo.Id, u.Name, repo.Name, repoPath, url); err != nil {
+ if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, url); err != nil {
return repo, err
}
repo.IsMirror = true
@@ -517,7 +554,7 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, initRe
if len(fileName) == 0 {
// Re-fetch the repository from database before updating it (else it would
// override changes that were done earlier with sql)
- if repo, err = getRepositoryById(e, repo.Id); err != nil {
+ if repo, err = getRepositoryByID(e, repo.ID); err != nil {
return err
}
repo.IsBare = true
@@ -562,7 +599,7 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
}
}
- if err = watchRepo(e, u.Id, repo.Id, true); err != nil {
+ 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)
@@ -574,7 +611,7 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
// 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,
+ OwnerID: u.Id,
Owner: u,
Name: name,
LowerName: strings.ToLower(name),
@@ -630,12 +667,12 @@ func GetRepositoriesWithUsers(num, offset int) ([]*Repository, error) {
}
for _, repo := range repos {
- repo.Owner = &User{Id: repo.OwnerId}
+ repo.Owner = &User{Id: repo.OwnerID}
has, err := x.Get(repo.Owner)
if err != nil {
return nil, err
} else if !has {
- return nil, ErrUserNotExist{repo.OwnerId, ""}
+ return nil, ErrUserNotExist{repo.OwnerID, ""}
}
}
@@ -672,11 +709,11 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
// Note: we have to set value here to make sure recalculate accesses is based on
// new owner.
- repo.OwnerId = newOwner.Id
+ repo.OwnerID = newOwner.Id
repo.Owner = newOwner
// Update repository.
- if _, err := sess.Id(repo.Id).Update(repo); err != nil {
+ if _, err := sess.Id(repo.ID).Update(repo); err != nil {
return fmt.Errorf("update owner: %v", err)
}
@@ -687,7 +724,7 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
}
// Dummy object.
- collaboration := &Collaboration{RepoID: repo.Id}
+ collaboration := &Collaboration{RepoID: repo.ID}
for _, c := range collaborators {
collaboration.UserID = c.Id
if c.Id == newOwner.Id || newOwner.IsOrgMember(c.Id) {
@@ -703,7 +740,7 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
return fmt.Errorf("getTeams: %v", err)
}
for _, t := range owner.Teams {
- if !t.hasRepository(sess, repo.Id) {
+ if !t.hasRepository(sess, repo.ID) {
continue
}
@@ -713,7 +750,7 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
}
}
- if err = owner.removeOrgRepo(sess, repo.Id); err != nil {
+ if err = owner.removeOrgRepo(sess, repo.ID); err != nil {
return fmt.Errorf("removeOrgRepo: %v", err)
}
}
@@ -739,7 +776,7 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
return fmt.Errorf("decrease old owner repository count: %v", err)
}
- if err = watchRepo(sess, newOwner.Id, repo.Id, true); err != nil {
+ if err = watchRepo(sess, newOwner.Id, repo.ID, true); err != nil {
return fmt.Errorf("watchRepo: %v", err)
} else if err = transferRepoAction(sess, u, owner, newOwner, repo); err != nil {
return fmt.Errorf("transferRepoAction: %v", err)
@@ -747,7 +784,7 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
// Update mirror information.
if repo.IsMirror {
- mirror, err := getMirror(sess, repo.Id)
+ mirror, err := getMirror(sess, repo.ID)
if err != nil {
return fmt.Errorf("getMirror: %v", err)
}
@@ -794,7 +831,7 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
repo.Website = repo.Website[:255]
}
- if _, err = e.Id(repo.Id).AllCols().Update(repo); err != nil {
+ if _, err = e.Id(repo.ID).AllCols().Update(repo); err != nil {
return fmt.Errorf("update: %v", err)
}
@@ -831,7 +868,7 @@ func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) {
// DeleteRepository deletes a repository for a user or organization.
func DeleteRepository(uid, repoID int64, userName string) error {
- repo := &Repository{Id: repoID, OwnerId: uid}
+ repo := &Repository{ID: repoID, OwnerID: uid}
has, err := x.Get(repo)
if err != nil {
return err
@@ -840,7 +877,7 @@ func DeleteRepository(uid, repoID int64, userName string) error {
}
// In case is a organization.
- org, err := GetUserById(uid)
+ org, err := GetUserByID(uid)
if err != nil {
return err
}
@@ -866,17 +903,17 @@ func DeleteRepository(uid, repoID int64, userName string) error {
}
}
- if _, err = sess.Delete(&Repository{Id: repoID}); err != nil {
+ if _, err = sess.Delete(&Repository{ID: repoID}); err != nil {
return err
- } else if _, err = sess.Delete(&Access{RepoID: repo.Id}); err != nil {
+ } else if _, err = sess.Delete(&Access{RepoID: repo.ID}); err != nil {
return err
- } else if _, err = sess.Delete(&Action{RepoID: repo.Id}); err != nil {
+ } else if _, err = sess.Delete(&Action{RepoID: repo.ID}); err != nil {
return err
} else if _, err = sess.Delete(&Watch{RepoID: repoID}); err != nil {
return err
- } else if _, err = sess.Delete(&Mirror{RepoId: repoID}); err != nil {
+ } else if _, err = sess.Delete(&Mirror{RepoID: repoID}); err != nil {
return err
- } else if _, err = sess.Delete(&IssueUser{RepoId: repoID}); err != nil {
+ } else if _, err = sess.Delete(&IssueUser{RepoID: repoID}); err != nil {
return err
} else if _, err = sess.Delete(&Milestone{RepoID: repoID}); err != nil {
return err
@@ -886,13 +923,26 @@ func DeleteRepository(uid, repoID int64, userName string) error {
return err
}
- // Delete comments.
+ // Delete comments and attachments.
issues := make([]*Issue, 0, 25)
+ attachmentPaths := make([]string, 0, len(issues))
if err = sess.Where("repo_id=?", repoID).Find(&issues); err != nil {
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
+ }
+
+ attachments := make([]*Attachment, 0, 5)
+ if err = sess.Where("issue_id=?", issues[i].ID).Find(&attachments); err != nil {
+ return err
+ }
+ for j := range attachments {
+ attachmentPaths = append(attachmentPaths, attachments[j].LocalPath())
+ }
+
+ if _, err = sess.Delete(&Attachment{IssueID: issues[i].ID}); err != nil {
return err
}
}
@@ -902,7 +952,7 @@ func DeleteRepository(uid, repoID int64, userName string) error {
}
if repo.IsFork {
- if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkId); err != nil {
+ if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil {
return err
}
}
@@ -920,6 +970,13 @@ func DeleteRepository(uid, repoID int64, userName string) error {
}
}
+ // Remove attachment files.
+ for i := range attachmentPaths {
+ if err = os.Remove(attachmentPaths[i]); err != nil {
+ log.Warn("delete attachment: %v", err)
+ }
+ }
+
return sess.Commit()
}
@@ -946,7 +1003,7 @@ func GetRepositoryByRef(ref string) (*Repository, error) {
// GetRepositoryByName returns the repository by given name under user if exists.
func GetRepositoryByName(uid int64, repoName string) (*Repository, error) {
repo := &Repository{
- OwnerId: uid,
+ OwnerID: uid,
LowerName: strings.ToLower(repoName),
}
has, err := x.Get(repo)
@@ -958,7 +1015,7 @@ func GetRepositoryByName(uid int64, repoName string) (*Repository, error) {
return repo, err
}
-func getRepositoryById(e Engine, id int64) (*Repository, error) {
+func getRepositoryByID(e Engine, id int64) (*Repository, error) {
repo := new(Repository)
has, err := e.Id(id).Get(repo)
if err != nil {
@@ -969,9 +1026,9 @@ func getRepositoryById(e Engine, id int64) (*Repository, error) {
return repo, nil
}
-// GetRepositoryById returns the repository by given id if exists.
-func GetRepositoryById(id int64) (*Repository, error) {
- return getRepositoryById(x, id)
+// GetRepositoryByID returns the repository by given id if exists.
+func GetRepositoryByID(id int64) (*Repository, error) {
+ return getRepositoryByID(x, id)
}
// GetRepositories returns a list of repositories of given user.
@@ -982,8 +1039,7 @@ func GetRepositories(uid int64, private bool) ([]*Repository, error) {
sess.Where("is_private=?", false)
}
- err := sess.Find(&repos, &Repository{OwnerId: uid})
- return repos, err
+ return repos, sess.Find(&repos, &Repository{OwnerID: uid})
}
// GetRecentUpdatedRepositories returns the list of repositories that are recently updated.
@@ -993,8 +1049,8 @@ func GetRecentUpdatedRepositories(num int) (repos []*Repository, err error) {
}
// GetRepositoryCount returns the total number of repositories of user.
-func GetRepositoryCount(user *User) (int64, error) {
- return x.Count(&Repository{OwnerId: user.Id})
+func GetRepositoryCount(u *User) (int64, error) {
+ return x.Count(&Repository{OwnerID: u.Id})
}
type SearchOption struct {
@@ -1199,7 +1255,7 @@ type Collaboration struct {
// Add collaborator and accompanying access
func (repo *Repository) AddCollaborator(u *User) error {
collaboration := &Collaboration{
- RepoID: repo.Id,
+ RepoID: repo.ID,
UserID: u.Id,
}
@@ -1238,13 +1294,13 @@ func (repo *Repository) AddCollaborator(u *User) error {
func (repo *Repository) getCollaborators(e Engine) ([]*User, error) {
collaborations := make([]*Collaboration, 0)
- if err := e.Find(&collaborations, &Collaboration{RepoID: repo.Id}); err != nil {
+ if err := e.Find(&collaborations, &Collaboration{RepoID: repo.ID}); err != nil {
return nil, err
}
users := make([]*User, len(collaborations))
for i, c := range collaborations {
- user, err := getUserById(e, c.UserID)
+ user, err := getUserByID(e, c.UserID)
if err != nil {
return nil, err
}
@@ -1261,7 +1317,7 @@ func (repo *Repository) GetCollaborators() ([]*User, error) {
// Delete collaborator and accompanying access
func (repo *Repository) DeleteCollaborator(u *User) (err error) {
collaboration := &Collaboration{
- RepoID: repo.Id,
+ RepoID: repo.ID,
UserID: u.Id,
}
@@ -1430,14 +1486,14 @@ func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) {
func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) {
repo := &Repository{
- OwnerId: u.Id,
+ OwnerID: u.Id,
Owner: u,
Name: name,
LowerName: strings.ToLower(name),
Description: desc,
IsPrivate: oldRepo.IsPrivate,
IsFork: true,
- ForkId: oldRepo.Id,
+ ForkID: oldRepo.ID,
}
sess := x.NewSession()
@@ -1450,7 +1506,7 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
return nil, err
}
- if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.Id); err != nil {
+ if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil {
return nil, err
}
diff --git a/models/update.go b/models/update.go
index 33b7733e..f381d6fd 100644
--- a/models/update.go
+++ b/models/update.go
@@ -105,7 +105,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
commit := &base.PushCommits{}
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
- repos.Id, repoUserName, repoName, refName, commit, oldCommitId, newCommitId); err != nil {
+ repos.ID, repoUserName, repoName, refName, commit, oldCommitId, newCommitId); err != nil {
log.GitLogger.Fatal(4, "CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
}
return err
@@ -154,7 +154,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
}
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
- repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits, ""}, oldCommitId, newCommitId); err != nil {
+ repos.ID, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits, ""}, oldCommitId, newCommitId); err != nil {
return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
}
return nil
diff --git a/models/user.go b/models/user.go
index 6dd31536..f2151a70 100644
--- a/models/user.go
+++ b/models/user.go
@@ -13,7 +13,9 @@ import (
"fmt"
"image"
"image/jpeg"
+ _ "image/jpeg"
"os"
+ "path"
"path/filepath"
"strings"
"time"
@@ -116,11 +118,40 @@ func (u *User) HomeLink() string {
// AvatarLink returns user gravatar link.
func (u *User) AvatarLink() string {
+ defaultImgUrl := setting.AppSubUrl + "/img/avatar_default.jpg"
+ imgPath := path.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
switch {
case u.UseCustomAvatar:
+ if !com.IsExist(imgPath) {
+ return defaultImgUrl
+ }
return setting.AppSubUrl + "/avatars/" + com.ToStr(u.Id)
case setting.DisableGravatar, setting.OfflineMode:
- return setting.AppSubUrl + "/img/avatar_default.jpg"
+ if !com.IsExist(imgPath) {
+ img, err := avatar.RandomImage([]byte(u.Email))
+ if err != nil {
+ log.Error(3, "RandomImage: %v", err)
+ return defaultImgUrl
+ }
+ if err = os.MkdirAll(path.Dir(imgPath), os.ModePerm); err != nil {
+ log.Error(3, "MkdirAll: %v", err)
+ return defaultImgUrl
+ }
+ fw, err := os.Create(imgPath)
+ if err != nil {
+ log.Error(3, "Create: %v", err)
+ return defaultImgUrl
+ }
+ defer fw.Close()
+
+ if err = jpeg.Encode(fw, img, nil); err != nil {
+ log.Error(3, "Encode: %v", err)
+ return defaultImgUrl
+ }
+ log.Info("New random avatar created: %d", u.Id)
+ }
+
+ return setting.AppSubUrl + "/avatars/" + com.ToStr(u.Id)
case setting.Service.EnableCacheAvatar:
return setting.AppSubUrl + "/avatar/" + u.Avatar
}
@@ -163,7 +194,7 @@ func (u *User) UploadAvatar(data []byte) error {
if err != nil {
return err
}
- m := resize.Resize(200, 200, img, resize.NearestNeighbor)
+ m := resize.Resize(234, 234, img, resize.NearestNeighbor)
sess := x.NewSession()
defer sess.Close()
@@ -191,6 +222,25 @@ func (u *User) UploadAvatar(data []byte) error {
return sess.Commit()
}
+// IsAdminOfRepo returns true if user has admin or higher access of repository.
+func (u *User) IsAdminOfRepo(repo *Repository) bool {
+ if err := repo.GetOwner(); err != nil {
+ log.Error(3, "GetOwner: %v", err)
+ return false
+ }
+
+ if repo.Owner.IsOrganization() {
+ has, err := HasAccess(u, repo, ACCESS_MODE_ADMIN)
+ if err != nil {
+ log.Error(3, "HasAccess: %v", err)
+ return false
+ }
+ return has
+ }
+
+ return repo.IsOwnedBy(u.Id)
+}
+
// IsOrganization returns true if user is actually a organization.
func (u *User) IsOrganization() bool {
return u.Type == ORGANIZATION
@@ -226,7 +276,7 @@ func (u *User) GetOrganizations() error {
u.Orgs = make([]*User, len(ous))
for i, ou := range ous {
- u.Orgs[i], err = GetUserById(ou.OrgID)
+ u.Orgs[i], err = GetUserByID(ou.OrgID)
if err != nil {
return err
}
@@ -522,10 +572,12 @@ func DeleteUser(u *User) error {
return err
}
- // Delete user directory.
+ // Delete user data.
if err = os.RemoveAll(UserPath(u.Name)); err != nil {
return err
}
+ // Delete avatar.
+ os.Remove(u.CustomAvatarPath())
return sess.Commit()
}
@@ -555,7 +607,7 @@ func GetUserByKeyId(keyId int64) (*User, error) {
return user, nil
}
-func getUserById(e Engine, id int64) (*User, error) {
+func getUserByID(e Engine, id int64) (*User, error) {
u := new(User)
has, err := e.Id(id).Get(u)
if err != nil {
@@ -566,9 +618,20 @@ func getUserById(e Engine, id int64) (*User, error) {
return u, nil
}
-// GetUserById returns the user object by given ID if exists.
-func GetUserById(id int64) (*User, error) {
- return getUserById(x, id)
+// GetUserByID returns the user object by given ID if exists.
+func GetUserByID(id int64) (*User, error) {
+ return getUserByID(x, id)
+}
+
+// GetAssigneeByID returns the user with write access of repository by given ID.
+func GetAssigneeByID(repo *Repository, userID int64) (*User, error) {
+ has, err := HasAccess(&User{Id: userID}, repo, ACCESS_MODE_WRITE)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrUserNotExist{userID, ""}
+ }
+ return GetUserByID(userID)
}
// GetUserByName returns user by given name.
@@ -620,7 +683,7 @@ func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
return nil, err
}
- u, err := GetUserById(uid)
+ u, err := GetUserByID(uid)
if err != nil {
return nil, err
}
@@ -666,7 +729,7 @@ func (email *EmailAddress) Activate() error {
return err
}
- if user, err := GetUserById(email.Uid); err != nil {
+ if user, err := GetUserByID(email.Uid); err != nil {
return err
} else {
user.Rands = GetUserSalt()
@@ -793,7 +856,7 @@ func GetUserByEmail(email string) (*User, error) {
return nil, err
}
if has {
- return GetUserById(emailAddress.Uid)
+ return GetUserByID(emailAddress.Uid)
}
return nil, ErrUserNotExist{0, "email"}
@@ -869,18 +932,19 @@ func UnFollowUser(userId int64, unFollowId int64) (err error) {
}
func UpdateMentions(userNames []string, issueId int64) error {
+ for i := range userNames {
+ userNames[i] = strings.ToLower(userNames[i])
+ }
users := make([]*User, 0, len(userNames))
- if err := x.Where("name IN (?)", strings.Join(userNames, "\",\"")).OrderBy("name ASC").Find(&users); err != nil {
+ if err := x.Where("lower_name IN (?)", strings.Join(userNames, "\",\"")).OrderBy("lower_name ASC").Find(&users); err != nil {
return err
}
ids := make([]int64, 0, len(userNames))
-
for _, user := range users {
ids = append(ids, user.Id)
-
- if user.Type == INDIVIDUAL {
+ if !user.IsOrganization() {
continue
}
@@ -889,9 +953,7 @@ func UpdateMentions(userNames []string, issueId int64) error {
}
tempIds := make([]int64, 0, user.NumMembers)
-
orgUsers, err := GetOrgUsersByOrgId(user.Id)
-
if err != nil {
return err
}
@@ -903,7 +965,7 @@ func UpdateMentions(userNames []string, issueId int64) error {
ids = append(ids, tempIds...)
}
- if err := UpdateIssueUserPairsByMentions(ids, issueId); err != nil {
+ if err := UpdateIssueUsersByMentions(ids, issueId); err != nil {
return err
}