diff options
Diffstat (limited to 'models')
59 files changed, 0 insertions, 18646 deletions
diff --git a/models/access.go b/models/access.go deleted file mode 100644 index 661a57a5..00000000 --- a/models/access.go +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - - log "gopkg.in/clog.v1" - - "gogs.io/gogs/models/errors" -) - -type AccessMode int - -const ( - ACCESS_MODE_NONE AccessMode = iota // 0 - ACCESS_MODE_READ // 1 - ACCESS_MODE_WRITE // 2 - ACCESS_MODE_ADMIN // 3 - ACCESS_MODE_OWNER // 4 -) - -func (mode AccessMode) String() string { - switch mode { - case ACCESS_MODE_READ: - return "read" - case ACCESS_MODE_WRITE: - return "write" - case ACCESS_MODE_ADMIN: - return "admin" - case ACCESS_MODE_OWNER: - return "owner" - default: - return "none" - } -} - -// ParseAccessMode returns corresponding access mode to given permission string. -func ParseAccessMode(permission string) AccessMode { - switch permission { - case "write": - return ACCESS_MODE_WRITE - case "admin": - return ACCESS_MODE_ADMIN - default: - return ACCESS_MODE_READ - } -} - -// Access represents the highest access level of a user to the repository. The only access type -// that is not in this table is the real owner of a repository. In case of an organization -// repository, the members of the owners team are in this table. -type Access struct { - ID int64 - UserID int64 `xorm:"UNIQUE(s)"` - RepoID int64 `xorm:"UNIQUE(s)"` - Mode AccessMode -} - -func userAccessMode(e Engine, userID int64, repo *Repository) (AccessMode, error) { - mode := ACCESS_MODE_NONE - // Everyone has read access to public repository - if !repo.IsPrivate { - mode = ACCESS_MODE_READ - } - - if userID <= 0 { - return mode, nil - } - - if userID == repo.OwnerID { - return ACCESS_MODE_OWNER, nil - } - - access := &Access{ - UserID: userID, - RepoID: repo.ID, - } - if has, err := e.Get(access); !has || err != nil { - return mode, err - } - return access.Mode, nil -} - -// UserAccessMode returns the access mode of given user to the repository. -func UserAccessMode(userID int64, repo *Repository) (AccessMode, error) { - return userAccessMode(x, userID, repo) -} - -func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (bool, error) { - mode, err := userAccessMode(e, userID, repo) - return mode >= testMode, err -} - -// HasAccess returns true if someone has the request access level. User can be nil! -func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) { - return hasAccess(x, userID, repo, testMode) -} - -// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own. -func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) { - accesses := make([]*Access, 0, 10) - if err := x.Find(&accesses, &Access{UserID: u.ID}); err != nil { - return nil, err - } - - repos := make(map[*Repository]AccessMode, len(accesses)) - for _, access := range accesses { - repo, err := GetRepositoryByID(access.RepoID) - if err != nil { - if errors.IsRepoNotExist(err) { - log.Error(2, "GetRepositoryByID: %v", err) - continue - } - return nil, err - } - if repo.OwnerID == u.ID { - continue - } - repos[repo] = access.Mode - } - return repos, nil -} - -// GetAccessibleRepositories finds repositories which the user has access but does not own. -// If limit is smaller than 1 means returns all found results. -func (user *User) GetAccessibleRepositories(limit int) (repos []*Repository, _ error) { - sess := x.Where("owner_id !=? ", user.ID).Desc("updated_unix") - if limit > 0 { - sess.Limit(limit) - repos = make([]*Repository, 0, limit) - } else { - repos = make([]*Repository, 0, 10) - } - return repos, sess.Join("INNER", "access", "access.user_id = ? AND access.repo_id = repository.id", user.ID).Find(&repos) -} - -func maxAccessMode(modes ...AccessMode) AccessMode { - max := ACCESS_MODE_NONE - for _, mode := range modes { - if mode > max { - max = mode - } - } - return max -} - -// FIXME: do corss-comparison so reduce deletions and additions to the minimum? -func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) (err error) { - newAccesses := make([]Access, 0, len(accessMap)) - for userID, mode := range accessMap { - newAccesses = append(newAccesses, Access{ - UserID: userID, - RepoID: repo.ID, - Mode: mode, - }) - } - - // Delete old accesses and insert new ones for repository. - 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) - } - return nil -} - -// refreshCollaboratorAccesses retrieves repository collaborations with their access modes. -func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error { - collaborations, err := repo.getCollaborations(e) - if err != nil { - return fmt.Errorf("getCollaborations: %v", err) - } - for _, c := range collaborations { - accessMap[c.UserID] = c.Mode - } - return nil -} - -// recalculateTeamAccesses recalculates new accesses for teams of an organization -// except the team whose ID is given. It is used to assign a team ID when -// remove repository from that team. -func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err error) { - accessMap := make(map[int64]AccessMode, 20) - - if err = repo.getOwner(e); err != nil { - return err - } else if !repo.Owner.IsOrganization() { - return fmt.Errorf("owner is not an organization: %d", repo.OwnerID) - } - - if err = repo.refreshCollaboratorAccesses(e, accessMap); err != nil { - return fmt.Errorf("refreshCollaboratorAccesses: %v", err) - } - - if err = repo.Owner.getTeams(e); err != nil { - return err - } - - for _, t := range repo.Owner.Teams { - if t.ID == ignTeamID { - continue - } - - // Owner team gets owner access, and skip for teams that do not - // have relations with repository. - if t.IsOwnerTeam() { - t.Authorize = ACCESS_MODE_OWNER - } else if !t.hasRepository(e, repo.ID) { - continue - } - - if err = t.getMembers(e); err != nil { - return fmt.Errorf("getMembers '%d': %v", t.ID, err) - } - for _, m := range t.Members { - accessMap[m.ID] = maxAccessMode(accessMap[m.ID], t.Authorize) - } - } - - return repo.refreshAccesses(e, accessMap) -} - -func (repo *Repository) recalculateAccesses(e Engine) error { - if repo.Owner.IsOrganization() { - return repo.recalculateTeamAccesses(e, 0) - } - - accessMap := make(map[int64]AccessMode, 10) - if err := repo.refreshCollaboratorAccesses(e, accessMap); err != nil { - return fmt.Errorf("refreshCollaboratorAccesses: %v", err) - } - return repo.refreshAccesses(e, accessMap) -} - -// RecalculateAccesses recalculates all accesses for repository. -func (repo *Repository) RecalculateAccesses() error { - return repo.recalculateAccesses(x) -} diff --git a/models/action.go b/models/action.go deleted file mode 100644 index d8381e47..00000000 --- a/models/action.go +++ /dev/null @@ -1,767 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "path" - "regexp" - "strings" - "time" - "unicode" - - "github.com/unknwon/com" - "xorm.io/xorm" - "github.com/json-iterator/go" - log "gopkg.in/clog.v1" - - "github.com/gogs/git-module" - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/tool" -) - -type ActionType int - -// Note: To maintain backward compatibility only append to the end of list -const ( - ACTION_CREATE_REPO ActionType = iota + 1 // 1 - ACTION_RENAME_REPO // 2 - ACTION_STAR_REPO // 3 - ACTION_WATCH_REPO // 4 - ACTION_COMMIT_REPO // 5 - ACTION_CREATE_ISSUE // 6 - ACTION_CREATE_PULL_REQUEST // 7 - ACTION_TRANSFER_REPO // 8 - ACTION_PUSH_TAG // 9 - ACTION_COMMENT_ISSUE // 10 - ACTION_MERGE_PULL_REQUEST // 11 - ACTION_CLOSE_ISSUE // 12 - ACTION_REOPEN_ISSUE // 13 - ACTION_CLOSE_PULL_REQUEST // 14 - ACTION_REOPEN_PULL_REQUEST // 15 - ACTION_CREATE_BRANCH // 16 - ACTION_DELETE_BRANCH // 17 - ACTION_DELETE_TAG // 18 - ACTION_FORK_REPO // 19 - ACTION_MIRROR_SYNC_PUSH // 20 - ACTION_MIRROR_SYNC_CREATE // 21 - ACTION_MIRROR_SYNC_DELETE // 22 -) - -var ( - // Same as Github. See https://help.github.com/articles/closing-issues-via-commit-messages - IssueCloseKeywords = []string{"close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"} - IssueReopenKeywords = []string{"reopen", "reopens", "reopened"} - - IssueCloseKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(IssueCloseKeywords)) - IssueReopenKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(IssueReopenKeywords)) - IssueReferenceKeywordsPat = regexp.MustCompile(`(?i)(?:)(^| )\S+`) -) - -func assembleKeywordsPattern(words []string) string { - return fmt.Sprintf(`(?i)(?:%s) \S+`, strings.Join(words, "|")) -} - -// Action represents user operation type and other information to repository, -// it implemented interface base.Actioner so that can be used in template render. -type Action struct { - ID int64 - UserID int64 // Receiver user ID - OpType ActionType - ActUserID int64 // Doer user ID - ActUserName string // Doer user name - ActAvatar string `xorm:"-" json:"-"` - RepoID int64 `xorm:"INDEX"` - RepoUserName string - RepoName string - RefName string - IsPrivate bool `xorm:"NOT NULL DEFAULT false"` - Content string `xorm:"TEXT"` - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 -} - -func (a *Action) BeforeInsert() { - a.CreatedUnix = time.Now().Unix() -} - -func (a *Action) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - a.Created = time.Unix(a.CreatedUnix, 0).Local() - } -} - -func (a *Action) GetOpType() int { - return int(a.OpType) -} - -func (a *Action) GetActUserName() string { - return a.ActUserName -} - -func (a *Action) ShortActUserName() string { - return tool.EllipsisString(a.ActUserName, 20) -} - -func (a *Action) GetRepoUserName() string { - return a.RepoUserName -} - -func (a *Action) ShortRepoUserName() string { - return tool.EllipsisString(a.RepoUserName, 20) -} - -func (a *Action) GetRepoName() string { - return a.RepoName -} - -func (a *Action) ShortRepoName() string { - return tool.EllipsisString(a.RepoName, 33) -} - -func (a *Action) GetRepoPath() string { - return path.Join(a.RepoUserName, a.RepoName) -} - -func (a *Action) ShortRepoPath() string { - return path.Join(a.ShortRepoUserName(), a.ShortRepoName()) -} - -func (a *Action) GetRepoLink() string { - if len(setting.AppSubURL) > 0 { - return path.Join(setting.AppSubURL, a.GetRepoPath()) - } - return "/" + a.GetRepoPath() -} - -func (a *Action) GetBranch() string { - return a.RefName -} - -func (a *Action) GetContent() string { - return a.Content -} - -func (a *Action) GetCreate() time.Time { - return a.Created -} - -func (a *Action) GetIssueInfos() []string { - return strings.SplitN(a.Content, "|", 2) -} - -func (a *Action) GetIssueTitle() string { - index := com.StrTo(a.GetIssueInfos()[0]).MustInt64() - issue, err := GetIssueByIndex(a.RepoID, index) - if err != nil { - log.Error(4, "GetIssueByIndex: %v", err) - return "500 when get issue" - } - return issue.Title -} - -func (a *Action) GetIssueContent() string { - index := com.StrTo(a.GetIssueInfos()[0]).MustInt64() - issue, err := GetIssueByIndex(a.RepoID, index) - if err != nil { - log.Error(4, "GetIssueByIndex: %v", err) - return "500 when get issue" - } - return issue.Content -} - -func newRepoAction(e Engine, doer, owner *User, repo *Repository) (err error) { - opType := ACTION_CREATE_REPO - if repo.IsFork { - opType = ACTION_FORK_REPO - } - - return notifyWatchers(e, &Action{ - ActUserID: doer.ID, - ActUserName: doer.Name, - OpType: opType, - RepoID: repo.ID, - RepoUserName: repo.Owner.Name, - RepoName: repo.Name, - IsPrivate: repo.IsPrivate, - }) -} - -// NewRepoAction adds new action for creating repository. -func NewRepoAction(doer, owner *User, repo *Repository) (err error) { - return newRepoAction(x, doer, owner, repo) -} - -func renameRepoAction(e Engine, actUser *User, oldRepoName string, repo *Repository) (err error) { - if err = notifyWatchers(e, &Action{ - ActUserID: actUser.ID, - ActUserName: actUser.Name, - OpType: ACTION_RENAME_REPO, - RepoID: repo.ID, - RepoUserName: repo.Owner.Name, - RepoName: repo.Name, - IsPrivate: repo.IsPrivate, - Content: oldRepoName, - }); err != nil { - return fmt.Errorf("notify watchers: %v", err) - } - - log.Trace("action.renameRepoAction: %s/%s", actUser.Name, repo.Name) - return nil -} - -// RenameRepoAction adds new action for renaming a repository. -func RenameRepoAction(actUser *User, oldRepoName string, repo *Repository) error { - return renameRepoAction(x, actUser, oldRepoName, repo) -} - -func issueIndexTrimRight(c rune) bool { - return !unicode.IsDigit(c) -} - -type PushCommit struct { - Sha1 string - Message string - AuthorEmail string - AuthorName string - CommitterEmail string - CommitterName string - Timestamp time.Time -} - -type PushCommits struct { - Len int - Commits []*PushCommit - CompareURL string - - avatars map[string]string -} - -func NewPushCommits() *PushCommits { - return &PushCommits{ - avatars: make(map[string]string), - } -} - -func (pc *PushCommits) ToApiPayloadCommits(repoPath, repoURL string) ([]*api.PayloadCommit, error) { - commits := make([]*api.PayloadCommit, len(pc.Commits)) - for i, commit := range pc.Commits { - authorUsername := "" - author, err := GetUserByEmail(commit.AuthorEmail) - if err == nil { - authorUsername = author.Name - } else if !errors.IsUserNotExist(err) { - return nil, fmt.Errorf("GetUserByEmail: %v", err) - } - - committerUsername := "" - committer, err := GetUserByEmail(commit.CommitterEmail) - if err == nil { - committerUsername = committer.Name - } else if !errors.IsUserNotExist(err) { - return nil, fmt.Errorf("GetUserByEmail: %v", err) - } - - fileStatus, err := git.GetCommitFileStatus(repoPath, commit.Sha1) - if err != nil { - return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %v", commit.Sha1, err) - } - - commits[i] = &api.PayloadCommit{ - ID: commit.Sha1, - Message: commit.Message, - URL: fmt.Sprintf("%s/commit/%s", repoURL, commit.Sha1), - Author: &api.PayloadUser{ - Name: commit.AuthorName, - Email: commit.AuthorEmail, - UserName: authorUsername, - }, - Committer: &api.PayloadUser{ - Name: commit.CommitterName, - Email: commit.CommitterEmail, - UserName: committerUsername, - }, - Added: fileStatus.Added, - Removed: fileStatus.Removed, - Modified: fileStatus.Modified, - Timestamp: commit.Timestamp, - } - } - return commits, nil -} - -// AvatarLink tries to match user in database with e-mail -// in order to show custom avatar, and falls back to general avatar link. -func (push *PushCommits) AvatarLink(email string) string { - _, ok := push.avatars[email] - if !ok { - u, err := GetUserByEmail(email) - if err != nil { - push.avatars[email] = tool.AvatarLink(email) - if !errors.IsUserNotExist(err) { - log.Error(4, "GetUserByEmail: %v", err) - } - } else { - push.avatars[email] = u.RelAvatarLink() - } - } - - return push.avatars[email] -} - -// UpdateIssuesCommit checks if issues are manipulated by commit message. -func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) error { - // Commits are appended in the reverse order. - for i := len(commits) - 1; i >= 0; i-- { - c := commits[i] - - refMarked := make(map[int64]bool) - for _, ref := range IssueReferenceKeywordsPat.FindAllString(c.Message, -1) { - ref = ref[strings.IndexByte(ref, byte(' '))+1:] - ref = strings.TrimRightFunc(ref, issueIndexTrimRight) - - if len(ref) == 0 { - continue - } - - // Add repo name if missing - if ref[0] == '#' { - ref = fmt.Sprintf("%s%s", repo.FullName(), ref) - } else if !strings.Contains(ref, "/") { - // FIXME: We don't support User#ID syntax yet - // return ErrNotImplemented - continue - } - - issue, err := GetIssueByRef(ref) - if err != nil { - if errors.IsIssueNotExist(err) { - continue - } - return err - } - - if refMarked[issue.ID] { - continue - } - refMarked[issue.ID] = true - - msgLines := strings.Split(c.Message, "\n") - shortMsg := msgLines[0] - if len(msgLines) > 2 { - shortMsg += "..." - } - message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, repo.Link(), c.Sha1, shortMsg) - if err = CreateRefComment(doer, repo, issue, message, c.Sha1); err != nil { - return err - } - } - - refMarked = make(map[int64]bool) - // FIXME: can merge this one and next one to a common function. - for _, ref := range IssueCloseKeywordsPat.FindAllString(c.Message, -1) { - ref = ref[strings.IndexByte(ref, byte(' '))+1:] - ref = strings.TrimRightFunc(ref, issueIndexTrimRight) - - if len(ref) == 0 { - continue - } - - // Add repo name if missing - if ref[0] == '#' { - ref = fmt.Sprintf("%s%s", repo.FullName(), ref) - } else if !strings.Contains(ref, "/") { - // FIXME: We don't support User#ID syntax yet - continue - } - - issue, err := GetIssueByRef(ref) - if err != nil { - if errors.IsIssueNotExist(err) { - continue - } - return err - } - - if refMarked[issue.ID] { - continue - } - refMarked[issue.ID] = true - - if issue.RepoID != repo.ID || issue.IsClosed { - continue - } - - if err = issue.ChangeStatus(doer, repo, true); err != nil { - return err - } - } - - // It is conflict to have close and reopen at same time, so refsMarkd doesn't need to reinit here. - for _, ref := range IssueReopenKeywordsPat.FindAllString(c.Message, -1) { - ref = ref[strings.IndexByte(ref, byte(' '))+1:] - ref = strings.TrimRightFunc(ref, issueIndexTrimRight) - - if len(ref) == 0 { - continue - } - - // Add repo name if missing - if ref[0] == '#' { - ref = fmt.Sprintf("%s%s", repo.FullName(), ref) - } else if !strings.Contains(ref, "/") { - // We don't support User#ID syntax yet - // return ErrNotImplemented - continue - } - - issue, err := GetIssueByRef(ref) - if err != nil { - if errors.IsIssueNotExist(err) { - continue - } - return err - } - - if refMarked[issue.ID] { - continue - } - refMarked[issue.ID] = true - - if issue.RepoID != repo.ID || !issue.IsClosed { - continue - } - - if err = issue.ChangeStatus(doer, repo, false); err != nil { - return err - } - } - } - return nil -} - -type CommitRepoActionOptions struct { - PusherName string - RepoOwnerID int64 - RepoName string - RefFullName string - OldCommitID string - NewCommitID string - Commits *PushCommits -} - -// CommitRepoAction adds new commit actio to the repository, and prepare corresponding webhooks. -func CommitRepoAction(opts CommitRepoActionOptions) error { - pusher, err := GetUserByName(opts.PusherName) - if err != nil { - return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err) - } - - repo, err := GetRepositoryByName(opts.RepoOwnerID, opts.RepoName) - if err != nil { - return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err) - } - - // Change repository bare status and update last updated time. - repo.IsBare = false - if err = UpdateRepository(repo, false); err != nil { - return fmt.Errorf("UpdateRepository: %v", err) - } - - isNewRef := opts.OldCommitID == git.EMPTY_SHA - isDelRef := opts.NewCommitID == git.EMPTY_SHA - - opType := ACTION_COMMIT_REPO - // Check if it's tag push or branch. - if strings.HasPrefix(opts.RefFullName, git.TAG_PREFIX) { - opType = ACTION_PUSH_TAG - } else { - // if not the first commit, set the compare URL. - if !isNewRef && !isDelRef { - opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) - } - - // Only update issues via commits when internal issue tracker is enabled - if repo.EnableIssues && !repo.EnableExternalTracker { - if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits); err != nil { - log.Error(2, "UpdateIssuesCommit: %v", err) - } - } - } - - if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { - opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] - } - - data, err := jsoniter.Marshal(opts.Commits) - if err != nil { - return fmt.Errorf("Marshal: %v", err) - } - - refName := git.RefEndName(opts.RefFullName) - action := &Action{ - ActUserID: pusher.ID, - ActUserName: pusher.Name, - Content: string(data), - RepoID: repo.ID, - RepoUserName: repo.MustOwner().Name, - RepoName: repo.Name, - RefName: refName, - IsPrivate: repo.IsPrivate, - } - - apiRepo := repo.APIFormat(nil) - apiPusher := pusher.APIFormat() - switch opType { - case ACTION_COMMIT_REPO: // Push - if isDelRef { - if err = PrepareWebhooks(repo, HOOK_EVENT_DELETE, &api.DeletePayload{ - Ref: refName, - RefType: "branch", - PusherType: api.PUSHER_TYPE_USER, - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err) - } - - action.OpType = ACTION_DELETE_BRANCH - if err = NotifyWatchers(action); err != nil { - return fmt.Errorf("NotifyWatchers.(delete branch): %v", err) - } - - // Delete branch doesn't have anything to push or compare - return nil - } - - compareURL := setting.AppURL + opts.Commits.CompareURL - if isNewRef { - compareURL = "" - if err = PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{ - Ref: refName, - RefType: "branch", - DefaultBranch: repo.DefaultBranch, - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks.(new branch): %v", err) - } - - action.OpType = ACTION_CREATE_BRANCH - if err = NotifyWatchers(action); err != nil { - return fmt.Errorf("NotifyWatchers.(new branch): %v", err) - } - } - - commits, err := opts.Commits.ToApiPayloadCommits(repo.RepoPath(), repo.HTMLURL()) - if err != nil { - return fmt.Errorf("ToApiPayloadCommits: %v", err) - } - - if err = PrepareWebhooks(repo, HOOK_EVENT_PUSH, &api.PushPayload{ - Ref: opts.RefFullName, - Before: opts.OldCommitID, - After: opts.NewCommitID, - CompareURL: compareURL, - Commits: commits, - Repo: apiRepo, - Pusher: apiPusher, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks.(new commit): %v", err) - } - - action.OpType = ACTION_COMMIT_REPO - if err = NotifyWatchers(action); err != nil { - return fmt.Errorf("NotifyWatchers.(new commit): %v", err) - } - - case ACTION_PUSH_TAG: // Tag - if isDelRef { - if err = PrepareWebhooks(repo, HOOK_EVENT_DELETE, &api.DeletePayload{ - Ref: refName, - RefType: "tag", - PusherType: api.PUSHER_TYPE_USER, - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err) - } - - action.OpType = ACTION_DELETE_TAG - if err = NotifyWatchers(action); err != nil { - return fmt.Errorf("NotifyWatchers.(delete tag): %v", err) - } - return nil - } - - if err = PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{ - Ref: refName, - RefType: "tag", - Sha: opts.NewCommitID, - DefaultBranch: repo.DefaultBranch, - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks.(new tag): %v", err) - } - - action.OpType = ACTION_PUSH_TAG - if err = NotifyWatchers(action); err != nil { - return fmt.Errorf("NotifyWatchers.(new tag): %v", err) - } - } - - return nil -} - -func transferRepoAction(e Engine, doer, oldOwner *User, repo *Repository) (err error) { - if err = notifyWatchers(e, &Action{ - ActUserID: doer.ID, - ActUserName: doer.Name, - OpType: ACTION_TRANSFER_REPO, - RepoID: repo.ID, - RepoUserName: repo.Owner.Name, - RepoName: repo.Name, - IsPrivate: repo.IsPrivate, - Content: path.Join(oldOwner.Name, repo.Name), - }); err != nil { - return fmt.Errorf("notifyWatchers: %v", err) - } - - // Remove watch for organization. - if oldOwner.IsOrganization() { - if err = watchRepo(e, oldOwner.ID, repo.ID, false); err != nil { - return fmt.Errorf("watchRepo [false]: %v", err) - } - } - - return nil -} - -// TransferRepoAction adds new action for transferring repository, -// the Owner field of repository is assumed to be new owner. -func TransferRepoAction(doer, oldOwner *User, repo *Repository) error { - return transferRepoAction(x, doer, oldOwner, repo) -} - -func mergePullRequestAction(e Engine, doer *User, repo *Repository, issue *Issue) error { - return notifyWatchers(e, &Action{ - ActUserID: doer.ID, - ActUserName: doer.Name, - OpType: ACTION_MERGE_PULL_REQUEST, - Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title), - RepoID: repo.ID, - RepoUserName: repo.Owner.Name, - RepoName: repo.Name, - IsPrivate: repo.IsPrivate, - }) -} - -// MergePullRequestAction adds new action for merging pull request. -func MergePullRequestAction(actUser *User, repo *Repository, pull *Issue) error { - return mergePullRequestAction(x, actUser, repo, pull) -} - -func mirrorSyncAction(opType ActionType, repo *Repository, refName string, data []byte) error { - return NotifyWatchers(&Action{ - ActUserID: repo.OwnerID, - ActUserName: repo.MustOwner().Name, - OpType: opType, - Content: string(data), - RepoID: repo.ID, - RepoUserName: repo.MustOwner().Name, - RepoName: repo.Name, - RefName: refName, - IsPrivate: repo.IsPrivate, - }) -} - -type MirrorSyncPushActionOptions struct { - RefName string - OldCommitID string - NewCommitID string - Commits *PushCommits -} - -// MirrorSyncPushAction adds new action for mirror synchronization of pushed commits. -func MirrorSyncPushAction(repo *Repository, opts MirrorSyncPushActionOptions) error { - if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { - opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] - } - - apiCommits, err := opts.Commits.ToApiPayloadCommits(repo.RepoPath(), repo.HTMLURL()) - if err != nil { - return fmt.Errorf("ToApiPayloadCommits: %v", err) - } - - opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) - apiPusher := repo.MustOwner().APIFormat() - if err := PrepareWebhooks(repo, HOOK_EVENT_PUSH, &api.PushPayload{ - Ref: opts.RefName, - Before: opts.OldCommitID, - After: opts.NewCommitID, - CompareURL: setting.AppURL + opts.Commits.CompareURL, - Commits: apiCommits, - Repo: repo.APIFormat(nil), - Pusher: apiPusher, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks: %v", err) - } - - data, err := jsoniter.Marshal(opts.Commits) - if err != nil { - return err - } - - return mirrorSyncAction(ACTION_MIRROR_SYNC_PUSH, repo, opts.RefName, data) -} - -// MirrorSyncCreateAction adds new action for mirror synchronization of new reference. -func MirrorSyncCreateAction(repo *Repository, refName string) error { - return mirrorSyncAction(ACTION_MIRROR_SYNC_CREATE, repo, refName, nil) -} - -// MirrorSyncCreateAction adds new action for mirror synchronization of delete reference. -func MirrorSyncDeleteAction(repo *Repository, refName string) error { - return mirrorSyncAction(ACTION_MIRROR_SYNC_DELETE, repo, refName, nil) -} - -// GetFeeds returns action list of given user in given context. -// actorID is the user who's requesting, ctxUserID is the user/org that is requested. -// actorID can be -1 when isProfile is true or to skip the permission check. -func GetFeeds(ctxUser *User, actorID, afterID int64, isProfile bool) ([]*Action, error) { - actions := make([]*Action, 0, setting.UI.User.NewsFeedPagingNum) - sess := x.Limit(setting.UI.User.NewsFeedPagingNum).Where("user_id = ?", ctxUser.ID).Desc("id") - if afterID > 0 { - sess.And("id < ?", afterID) - } - if isProfile { - sess.And("is_private = ?", false).And("act_user_id = ?", ctxUser.ID) - } else if actorID != -1 && ctxUser.IsOrganization() { - // FIXME: only need to get IDs here, not all fields of repository. - repos, _, err := ctxUser.GetUserRepositories(actorID, 1, ctxUser.NumRepos) - if err != nil { - return nil, fmt.Errorf("GetUserRepositories: %v", err) - } - - var repoIDs []int64 - for _, repo := range repos { - repoIDs = append(repoIDs, repo.ID) - } - - if len(repoIDs) > 0 { - sess.In("repo_id", repoIDs) - } - } - - err := sess.Find(&actions) - return actions, err -} diff --git a/models/admin.go b/models/admin.go deleted file mode 100644 index 8aaa67a9..00000000 --- a/models/admin.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "os" - "strings" - "time" - - "github.com/unknwon/com" - "xorm.io/xorm" - log "gopkg.in/clog.v1" - - "gogs.io/gogs/pkg/tool" -) - -type NoticeType int - -const ( - NOTICE_REPOSITORY NoticeType = iota + 1 -) - -// Notice represents a system notice for admin. -type Notice struct { - ID int64 - Type NoticeType - Description string `xorm:"TEXT"` - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 -} - -func (n *Notice) BeforeInsert() { - n.CreatedUnix = time.Now().Unix() -} - -func (n *Notice) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - n.Created = time.Unix(n.CreatedUnix, 0).Local() - } -} - -// TrStr returns a translation format string. -func (n *Notice) TrStr() string { - return "admin.notices.type_" + com.ToStr(n.Type) -} - -// CreateNotice creates new system notice. -func CreateNotice(tp NoticeType, desc string) error { - // Prevent panic if database connection is not available at this point - if x == nil { - return fmt.Errorf("could not save notice due database connection not being available: %d %s", tp, desc) - } - - n := &Notice{ - Type: tp, - Description: desc, - } - _, err := x.Insert(n) - return err -} - -// CreateRepositoryNotice creates new system notice with type NOTICE_REPOSITORY. -func CreateRepositoryNotice(desc string) error { - return CreateNotice(NOTICE_REPOSITORY, desc) -} - -// RemoveAllWithNotice removes all directories in given path and -// creates a system notice when error occurs. -func RemoveAllWithNotice(title, path string) { - if err := os.RemoveAll(path); err != nil { - desc := fmt.Sprintf("%s [%s]: %v", title, path, err) - log.Warn(desc) - if err = CreateRepositoryNotice(desc); err != nil { - log.Error(2, "CreateRepositoryNotice: %v", err) - } - } -} - -// CountNotices returns number of notices. -func CountNotices() int64 { - count, _ := x.Count(new(Notice)) - return count -} - -// Notices returns number of notices in given page. -func Notices(page, pageSize int) ([]*Notice, error) { - notices := make([]*Notice, 0, pageSize) - return notices, x.Limit(pageSize, (page-1)*pageSize).Desc("id").Find(¬ices) -} - -// DeleteNotice deletes a system notice by given ID. -func DeleteNotice(id int64) error { - _, err := x.Id(id).Delete(new(Notice)) - return err -} - -// DeleteNotices deletes all notices with ID from start to end (inclusive). -func DeleteNotices(start, end int64) error { - sess := x.Where("id >= ?", start) - if end > 0 { - sess.And("id <= ?", end) - } - _, err := sess.Delete(new(Notice)) - return err -} - -// DeleteNoticesByIDs deletes notices by given IDs. -func DeleteNoticesByIDs(ids []int64) error { - if len(ids) == 0 { - return nil - } - _, err := x.Where("id IN (" + strings.Join(tool.Int64sToStrings(ids), ",") + ")").Delete(new(Notice)) - return err -} diff --git a/models/attachment.go b/models/attachment.go deleted file mode 100644 index 21718a73..00000000 --- a/models/attachment.go +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "io" - "mime/multipart" - "os" - "path" - "time" - - "xorm.io/xorm" - gouuid "github.com/satori/go.uuid" - - "gogs.io/gogs/pkg/setting" -) - -// Attachment represent a attachment of issue/comment/release. -type Attachment struct { - ID int64 - UUID string `xorm:"uuid UNIQUE"` - IssueID int64 `xorm:"INDEX"` - CommentID int64 - ReleaseID int64 `xorm:"INDEX"` - Name string - - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 -} - -func (a *Attachment) BeforeInsert() { - a.CreatedUnix = time.Now().Unix() -} - -func (a *Attachment) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - a.Created = time.Unix(a.CreatedUnix, 0).Local() - } -} - -// 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, - } - - localPath := attach.LocalPath() - if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil { - return nil, fmt.Errorf("MkdirAll: %v", err) - } - - fw, err := os.Create(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) - } - - if _, err := x.Insert(attach); err != nil { - return nil, err - } - - return attach, nil -} - -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} - } - return attach, nil -} - -func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) { - if len(uuids) == 0 { - return []*Attachment{}, nil - } - - // Silently drop invalid uuids. - attachments := make([]*Attachment, 0, len(uuids)) - return attachments, e.In("uuid", uuids).Find(&attachments) -} - -// GetAttachmentByUUID returns attachment by given UUID. -func GetAttachmentByUUID(uuid string) (*Attachment, error) { - return getAttachmentByUUID(x, uuid) -} - -func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) { - attachments := make([]*Attachment, 0, 5) - return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments) -} - -// GetAttachmentsByIssueID returns all attachments of an issue. -func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) { - return getAttachmentsByIssueID(x, issueID) -} - -func getAttachmentsByCommentID(e Engine, commentID int64) ([]*Attachment, error) { - attachments := make([]*Attachment, 0, 5) - return attachments, e.Where("comment_id=?", commentID).Find(&attachments) -} - -// GetAttachmentsByCommentID returns all attachments of a comment. -func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) { - return getAttachmentsByCommentID(x, commentID) -} - -func getAttachmentsByReleaseID(e Engine, releaseID int64) ([]*Attachment, error) { - attachments := make([]*Attachment, 0, 10) - return attachments, e.Where("release_id = ?", releaseID).Find(&attachments) -} - -// GetAttachmentsByReleaseID returns all attachments of a release. -func GetAttachmentsByReleaseID(releaseID int64) ([]*Attachment, error) { - return getAttachmentsByReleaseID(x, releaseID) -} - -// DeleteAttachment deletes the given attachment and optionally the associated file. -func DeleteAttachment(a *Attachment, remove bool) error { - _, err := DeleteAttachments([]*Attachment{a}, remove) - return err -} - -// DeleteAttachments deletes the given attachments and optionally the associated files. -func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) { - for i, a := range attachments { - if remove { - if err := os.Remove(a.LocalPath()); err != nil { - return i, err - } - } - - if _, err := x.Delete(a); err != nil { - return i, err - } - } - - return len(attachments), nil -} - -// DeleteAttachmentsByIssue deletes all attachments associated with the given issue. -func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) { - attachments, err := GetAttachmentsByIssueID(issueId) - if err != nil { - return 0, err - } - - return DeleteAttachments(attachments, remove) -} - -// DeleteAttachmentsByComment deletes all attachments associated with the given comment. -func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) { - attachments, err := GetAttachmentsByCommentID(commentId) - if err != nil { - return 0, err - } - - return DeleteAttachments(attachments, remove) -} diff --git a/models/comment.go b/models/comment.go deleted file mode 100644 index e3726ffe..00000000 --- a/models/comment.go +++ /dev/null @@ -1,534 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "strings" - "time" - - "github.com/unknwon/com" - "xorm.io/xorm" - log "gopkg.in/clog.v1" - - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/markup" -) - -// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference. -type CommentType int - -const ( - // Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0) - COMMENT_TYPE_COMMENT CommentType = iota - COMMENT_TYPE_REOPEN - COMMENT_TYPE_CLOSE - - // References. - 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_WRITER - COMMENT_TAG_OWNER -) - -// Comment represents a comment in commit and issue page. -type Comment struct { - ID int64 - Type CommentType - PosterID int64 - Poster *User `xorm:"-" json:"-"` - IssueID int64 `xorm:"INDEX"` - Issue *Issue `xorm:"-" json:"-"` - CommitID int64 - Line int64 - Content string `xorm:"TEXT"` - RenderedContent string `xorm:"-" json:"-"` - - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-"` - UpdatedUnix int64 - - // Reference issue in commit message - CommitSHA string `xorm:"VARCHAR(40)"` - - Attachments []*Attachment `xorm:"-" json:"-"` - - // For view issue page. - ShowTag CommentTag `xorm:"-" json:"-"` -} - -func (c *Comment) BeforeInsert() { - c.CreatedUnix = time.Now().Unix() - c.UpdatedUnix = c.CreatedUnix -} - -func (c *Comment) BeforeUpdate() { - c.UpdatedUnix = time.Now().Unix() -} - -func (c *Comment) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - c.Created = time.Unix(c.CreatedUnix, 0).Local() - case "updated_unix": - c.Updated = time.Unix(c.UpdatedUnix, 0).Local() - } -} - -func (c *Comment) loadAttributes(e Engine) (err error) { - if c.Poster == nil { - c.Poster, err = GetUserByID(c.PosterID) - if err != nil { - if errors.IsUserNotExist(err) { - c.PosterID = -1 - c.Poster = NewGhostUser() - } else { - return fmt.Errorf("getUserByID.(Poster) [%d]: %v", c.PosterID, err) - } - } - } - - if c.Issue == nil { - c.Issue, err = getRawIssueByID(e, c.IssueID) - if err != nil { - return fmt.Errorf("getIssueByID [%d]: %v", c.IssueID, err) - } - if c.Issue.Repo == nil { - c.Issue.Repo, err = getRepositoryByID(e, c.Issue.RepoID) - if err != nil { - return fmt.Errorf("getRepositoryByID [%d]: %v", c.Issue.RepoID, err) - } - } - } - - if c.Attachments == nil { - c.Attachments, err = getAttachmentsByCommentID(e, c.ID) - if err != nil { - return fmt.Errorf("getAttachmentsByCommentID [%d]: %v", c.ID, err) - } - } - - return nil -} - -func (c *Comment) LoadAttributes() error { - return c.loadAttributes(x) -} - -func (c *Comment) HTMLURL() string { - return fmt.Sprintf("%s#issuecomment-%d", c.Issue.HTMLURL(), c.ID) -} - -// This method assumes following fields have been assigned with valid values: -// Required - Poster, Issue -func (c *Comment) APIFormat() *api.Comment { - return &api.Comment{ - ID: c.ID, - HTMLURL: c.HTMLURL(), - Poster: c.Poster.APIFormat(), - Body: c.Content, - Created: c.Created, - Updated: c.Updated, - } -} - -func CommentHashTag(id int64) string { - return "issuecomment-" + com.ToStr(id) -} - -// HashTag returns unique hash tag for comment. -func (c *Comment) HashTag() string { - return CommentHashTag(c.ID) -} - -// EventTag returns unique event hash tag for comment. -func (c *Comment) EventTag() string { - return "event-" + com.ToStr(c.ID) -} - -// mailParticipants sends new comment emails to repository watchers -// and mentioned people. -func (cmt *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue) (err error) { - mentions := markup.FindAllMentions(cmt.Content) - if err = updateIssueMentions(e, cmt.IssueID, mentions); err != nil { - return fmt.Errorf("UpdateIssueMentions [%d]: %v", cmt.IssueID, err) - } - - switch opType { - case ACTION_COMMENT_ISSUE: - issue.Content = cmt.Content - case ACTION_CLOSE_ISSUE: - issue.Content = fmt.Sprintf("Closed #%d", issue.Index) - case ACTION_REOPEN_ISSUE: - issue.Content = fmt.Sprintf("Reopened #%d", issue.Index) - } - if err = mailIssueCommentToParticipants(issue, cmt.Poster, mentions); err != nil { - log.Error(2, "mailIssueCommentToParticipants: %v", err) - } - - return nil -} - -func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { - comment := &Comment{ - Type: opts.Type, - PosterID: opts.Doer.ID, - Poster: opts.Doer, - IssueID: opts.Issue.ID, - CommitID: opts.CommitID, - CommitSHA: opts.CommitSHA, - Line: opts.LineNum, - Content: opts.Content, - } - if _, err = e.Insert(comment); err != nil { - return nil, err - } - - // Compose comment action, could be plain comment, close or reopen issue/pull request. - // This object will be used to notify watchers in the end of function. - act := &Action{ - ActUserID: opts.Doer.ID, - ActUserName: opts.Doer.Name, - Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]), - RepoID: opts.Repo.ID, - RepoUserName: opts.Repo.Owner.Name, - RepoName: opts.Repo.Name, - IsPrivate: opts.Repo.IsPrivate, - } - - // Check comment type. - switch opts.Type { - case COMMENT_TYPE_COMMENT: - act.OpType = ACTION_COMMENT_ISSUE - - if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil { - return nil, err - } - - // Check attachments - attachments := make([]*Attachment, 0, len(opts.Attachments)) - for _, uuid := range opts.Attachments { - 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) - } - - for i := range attachments { - attachments[i].IssueID = opts.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) - } - } - - case COMMENT_TYPE_REOPEN: - act.OpType = ACTION_REOPEN_ISSUE - if opts.Issue.IsPull { - act.OpType = ACTION_REOPEN_PULL_REQUEST - } - - if opts.Issue.IsPull { - _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID) - } else { - _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID) - } - if err != nil { - return nil, err - } - - case COMMENT_TYPE_CLOSE: - act.OpType = ACTION_CLOSE_ISSUE - if opts.Issue.IsPull { - act.OpType = ACTION_CLOSE_PULL_REQUEST - } - - if opts.Issue.IsPull { - _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID) - } else { - _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID) - } - if err != nil { - return nil, err - } - } - - if _, err = e.Exec("UPDATE `issue` SET updated_unix = ? WHERE id = ?", time.Now().Unix(), opts.Issue.ID); err != nil { - return nil, fmt.Errorf("update issue 'updated_unix': %v", err) - } - - // Notify watchers for whatever action comes in, ignore if no action type. - if act.OpType > 0 { - if err = notifyWatchers(e, act); err != nil { - log.Error(2, "notifyWatchers: %v", err) - } - if err = comment.mailParticipants(e, act.OpType, opts.Issue); err != nil { - log.Error(2, "MailParticipants: %v", err) - } - } - - return comment, comment.loadAttributes(e) -} - -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, &CreateCommentOptions{ - Type: cmtType, - Doer: doer, - Repo: repo, - Issue: issue, - }) -} - -type CreateCommentOptions struct { - Type CommentType - Doer *User - Repo *Repository - Issue *Issue - - CommitID int64 - CommitSHA string - LineNum int64 - Content string - Attachments []string // UUIDs of attachments -} - -// CreateComment creates comment of issue or commit. -func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - comment, err = createComment(sess, opts) - if err != nil { - return nil, err - } - - return comment, sess.Commit() -} - -// CreateIssueComment creates a plain issue comment. -func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { - comment, err := CreateComment(&CreateCommentOptions{ - Type: COMMENT_TYPE_COMMENT, - Doer: doer, - Repo: repo, - Issue: issue, - Content: content, - Attachments: attachments, - }) - if err != nil { - return nil, fmt.Errorf("CreateComment: %v", err) - } - - comment.Issue = issue - if err = PrepareWebhooks(repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{ - Action: api.HOOK_ISSUE_COMMENT_CREATED, - Issue: issue.APIFormat(), - Comment: comment.APIFormat(), - Repository: repo.APIFormat(nil), - Sender: doer.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err) - } - - return comment, nil -} - -// CreateRefComment creates a commit reference comment to issue. -func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error { - if len(commitSHA) == 0 { - return fmt.Errorf("cannot create reference with empty commit SHA") - } - - // Check if same reference from same commit has already existed. - has, err := x.Get(&Comment{ - Type: COMMENT_TYPE_COMMIT_REF, - IssueID: issue.ID, - CommitSHA: commitSHA, - }) - if err != nil { - return fmt.Errorf("check reference comment: %v", err) - } else if has { - return nil - } - - _, err = CreateComment(&CreateCommentOptions{ - Type: COMMENT_TYPE_COMMIT_REF, - Doer: doer, - Repo: repo, - Issue: issue, - CommitSHA: commitSHA, - Content: content, - }) - return err -} - -// GetCommentByID returns the comment by given ID. -func GetCommentByID(id int64) (*Comment, error) { - c := new(Comment) - has, err := x.Id(id).Get(c) - if err != nil { - return nil, err - } else if !has { - return nil, ErrCommentNotExist{id, 0} - } - return c, c.LoadAttributes() -} - -// FIXME: use CommentList to improve performance. -func loadCommentsAttributes(e Engine, comments []*Comment) (err error) { - for i := range comments { - if err = comments[i].loadAttributes(e); err != nil { - return fmt.Errorf("loadAttributes [%d]: %v", comments[i].ID, err) - } - } - - return nil -} - -func getCommentsByIssueIDSince(e Engine, issueID, since int64) ([]*Comment, error) { - comments := make([]*Comment, 0, 10) - sess := e.Where("issue_id = ?", issueID).Asc("created_unix") - if since > 0 { - sess.And("updated_unix >= ?", since) - } - - if err := sess.Find(&comments); err != nil { - return nil, err - } - return comments, loadCommentsAttributes(e, comments) -} - -func getCommentsByRepoIDSince(e Engine, repoID, since int64) ([]*Comment, error) { - comments := make([]*Comment, 0, 10) - sess := e.Where("issue.repo_id = ?", repoID).Join("INNER", "issue", "issue.id = comment.issue_id").Asc("comment.created_unix") - if since > 0 { - sess.And("comment.updated_unix >= ?", since) - } - if err := sess.Find(&comments); err != nil { - return nil, err - } - return comments, loadCommentsAttributes(e, comments) -} - -func getCommentsByIssueID(e Engine, issueID int64) ([]*Comment, error) { - return getCommentsByIssueIDSince(e, issueID, -1) -} - -// GetCommentsByIssueID returns all comments of an issue. -func GetCommentsByIssueID(issueID int64) ([]*Comment, error) { - return getCommentsByIssueID(x, issueID) -} - -// GetCommentsByIssueIDSince returns a list of comments of an issue since a given time point. -func GetCommentsByIssueIDSince(issueID, since int64) ([]*Comment, error) { - return getCommentsByIssueIDSince(x, issueID, since) -} - -// GetCommentsByRepoIDSince returns a list of comments for all issues in a repo since a given time point. -func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) { - return getCommentsByRepoIDSince(x, repoID, since) -} - -// UpdateComment updates information of comment. -func UpdateComment(doer *User, c *Comment, oldContent string) (err error) { - if _, err = x.Id(c.ID).AllCols().Update(c); err != nil { - return err - } - - if err = c.Issue.LoadAttributes(); err != nil { - log.Error(2, "Issue.LoadAttributes [issue_id: %d]: %v", c.IssueID, err) - } else if err = PrepareWebhooks(c.Issue.Repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{ - Action: api.HOOK_ISSUE_COMMENT_EDITED, - Issue: c.Issue.APIFormat(), - Comment: c.APIFormat(), - Changes: &api.ChangesPayload{ - Body: &api.ChangesFromPayload{ - From: oldContent, - }, - }, - Repository: c.Issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", c.ID, err) - } - - return nil -} - -// DeleteCommentByID deletes the comment by given ID. -func DeleteCommentByID(doer *User, id int64) error { - comment, err := GetCommentByID(id) - if err != nil { - if IsErrCommentNotExist(err) { - return nil - } - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.ID(comment.ID).Delete(new(Comment)); err != nil { - return err - } - - if comment.Type == COMMENT_TYPE_COMMENT { - if _, err = sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil { - return err - } - } - - if err = sess.Commit(); err != nil { - return fmt.Errorf("commit: %v", err) - } - - _, err = DeleteAttachmentsByComment(comment.ID, true) - if err != nil { - log.Error(2, "Failed to delete attachments by comment[%d]: %v", comment.ID, err) - } - - if err = comment.Issue.LoadAttributes(); err != nil { - log.Error(2, "Issue.LoadAttributes [issue_id: %d]: %v", comment.IssueID, err) - } else if err = PrepareWebhooks(comment.Issue.Repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{ - Action: api.HOOK_ISSUE_COMMENT_DELETED, - Issue: comment.Issue.APIFormat(), - Comment: comment.APIFormat(), - Repository: comment.Issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err) - } - return nil -} diff --git a/models/error.go b/models/error.go deleted file mode 100644 index 63e06f6e..00000000 --- a/models/error.go +++ /dev/null @@ -1,575 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" -) - -type ErrNameReserved struct { - Name string -} - -func IsErrNameReserved(err error) bool { - _, ok := err.(ErrNameReserved) - return ok -} - -func (err ErrNameReserved) Error() string { - return fmt.Sprintf("name is reserved [name: %s]", err.Name) -} - -type ErrNamePatternNotAllowed struct { - Pattern string -} - -func IsErrNamePatternNotAllowed(err error) bool { - _, ok := err.(ErrNamePatternNotAllowed) - return ok -} - -func (err ErrNamePatternNotAllowed) Error() string { - return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern) -} - -// ____ ___ -// | | \______ ___________ -// | | / ___// __ \_ __ \ -// | | /\___ \\ ___/| | \/ -// |______//____ >\___ >__| -// \/ \/ - -type ErrUserAlreadyExist struct { - Name string -} - -func IsErrUserAlreadyExist(err error) bool { - _, ok := err.(ErrUserAlreadyExist) - return ok -} - -func (err ErrUserAlreadyExist) Error() string { - return fmt.Sprintf("user already exists [name: %s]", err.Name) -} - -type ErrEmailAlreadyUsed struct { - Email string -} - -func IsErrEmailAlreadyUsed(err error) bool { - _, ok := err.(ErrEmailAlreadyUsed) - return ok -} - -func (err ErrEmailAlreadyUsed) Error() string { - return fmt.Sprintf("e-mail has been used [email: %s]", err.Email) -} - -type ErrUserOwnRepos struct { - UID int64 -} - -func IsErrUserOwnRepos(err error) bool { - _, ok := err.(ErrUserOwnRepos) - return ok -} - -func (err ErrUserOwnRepos) Error() string { - return fmt.Sprintf("user still has ownership of repositories [uid: %d]", err.UID) -} - -type ErrUserHasOrgs struct { - UID int64 -} - -func IsErrUserHasOrgs(err error) bool { - _, ok := err.(ErrUserHasOrgs) - return ok -} - -func (err ErrUserHasOrgs) Error() string { - return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID) -} - -// __ __.__ __ .__ -// / \ / \__| | _|__| -// \ \/\/ / | |/ / | -// \ /| | <| | -// \__/\ / |__|__|_ \__| -// \/ \/ - -type ErrWikiAlreadyExist struct { - Title string -} - -func IsErrWikiAlreadyExist(err error) bool { - _, ok := err.(ErrWikiAlreadyExist) - return ok -} - -func (err ErrWikiAlreadyExist) Error() string { - return fmt.Sprintf("wiki page already exists [title: %s]", err.Title) -} - -// __________ ___. .__ .__ ____ __. -// \______ \__ _\_ |__ | | |__| ____ | |/ _|____ ___.__. -// | ___/ | \ __ \| | | |/ ___\ | <_/ __ < | | -// | | | | / \_\ \ |_| \ \___ | | \ ___/\___ | -// |____| |____/|___ /____/__|\___ > |____|__ \___ > ____| -// \/ \/ \/ \/\/ - -type ErrKeyUnableVerify struct { - Result string -} - -func IsErrKeyUnableVerify(err error) bool { - _, ok := err.(ErrKeyUnableVerify) - return ok -} - -func (err ErrKeyUnableVerify) Error() string { - return fmt.Sprintf("Unable to verify key content [result: %s]", err.Result) -} - -type ErrKeyNotExist struct { - ID int64 -} - -func IsErrKeyNotExist(err error) bool { - _, ok := err.(ErrKeyNotExist) - return ok -} - -func (err ErrKeyNotExist) Error() string { - return fmt.Sprintf("public key does not exist [id: %d]", err.ID) -} - -type ErrKeyAlreadyExist struct { - OwnerID int64 - Content string -} - -func IsErrKeyAlreadyExist(err error) bool { - _, ok := err.(ErrKeyAlreadyExist) - return ok -} - -func (err ErrKeyAlreadyExist) Error() string { - return fmt.Sprintf("public key already exists [owner_id: %d, content: %s]", err.OwnerID, err.Content) -} - -type ErrKeyNameAlreadyUsed struct { - OwnerID int64 - Name string -} - -func IsErrKeyNameAlreadyUsed(err error) bool { - _, ok := err.(ErrKeyNameAlreadyUsed) - return ok -} - -func (err ErrKeyNameAlreadyUsed) Error() string { - return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name) -} - -type ErrKeyAccessDenied struct { - UserID int64 - KeyID int64 - Note string -} - -func IsErrKeyAccessDenied(err error) bool { - _, ok := err.(ErrKeyAccessDenied) - return ok -} - -func (err ErrKeyAccessDenied) Error() string { - return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d, note: %s]", - err.UserID, err.KeyID, err.Note) -} - -type ErrDeployKeyNotExist struct { - ID int64 - KeyID int64 - RepoID int64 -} - -func IsErrDeployKeyNotExist(err error) bool { - _, ok := err.(ErrDeployKeyNotExist) - return ok -} - -func (err ErrDeployKeyNotExist) Error() string { - return fmt.Sprintf("Deploy key does not exist [id: %d, key_id: %d, repo_id: %d]", err.ID, err.KeyID, err.RepoID) -} - -type ErrDeployKeyAlreadyExist struct { - KeyID int64 - RepoID int64 -} - -func IsErrDeployKeyAlreadyExist(err error) bool { - _, ok := err.(ErrDeployKeyAlreadyExist) - return ok -} - -func (err ErrDeployKeyAlreadyExist) Error() string { - return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID) -} - -type ErrDeployKeyNameAlreadyUsed struct { - RepoID int64 - Name string -} - -func IsErrDeployKeyNameAlreadyUsed(err error) bool { - _, ok := err.(ErrDeployKeyNameAlreadyUsed) - return ok -} - -func (err ErrDeployKeyNameAlreadyUsed) Error() string { - return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name) -} - -// _____ ___________ __ -// / _ \ ____ ____ ____ ______ _____\__ ___/___ | | __ ____ ____ -// / /_\ \_/ ___\/ ___\/ __ \ / ___// ___/ | | / _ \| |/ // __ \ / \ -// / | \ \__\ \__\ ___/ \___ \ \___ \ | |( <_> ) <\ ___/| | \ -// \____|__ /\___ >___ >___ >____ >____ > |____| \____/|__|_ \\___ >___| / -// \/ \/ \/ \/ \/ \/ \/ \/ \/ - -type ErrAccessTokenNotExist struct { - SHA string -} - -func IsErrAccessTokenNotExist(err error) bool { - _, ok := err.(ErrAccessTokenNotExist) - return ok -} - -func (err ErrAccessTokenNotExist) Error() string { - return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA) -} - -type ErrAccessTokenEmpty struct { -} - -func IsErrAccessTokenEmpty(err error) bool { - _, ok := err.(ErrAccessTokenEmpty) - return ok -} - -func (err ErrAccessTokenEmpty) Error() string { - return fmt.Sprintf("access token is empty") -} - -// ________ .__ __ .__ -// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____ -// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \ -// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \ -// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| / -// \/ /_____/ \/ \/ \/ \/ \/ - -type ErrLastOrgOwner struct { - UID int64 -} - -func IsErrLastOrgOwner(err error) bool { - _, ok := err.(ErrLastOrgOwner) - return ok -} - -func (err ErrLastOrgOwner) Error() string { - return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID) -} - -// __________ .__ __ -// \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__. -// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | | -// | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ | -// |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____| -// \/ \/|__| \/ \/ - -type ErrRepoAlreadyExist struct { - Uname string - Name string -} - -func IsErrRepoAlreadyExist(err error) bool { - _, ok := err.(ErrRepoAlreadyExist) - return ok -} - -func (err ErrRepoAlreadyExist) Error() string { - return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name) -} - -type ErrInvalidCloneAddr struct { - IsURLError bool - IsInvalidPath bool - IsPermissionDenied bool -} - -func IsErrInvalidCloneAddr(err error) bool { - _, ok := err.(ErrInvalidCloneAddr) - return ok -} - -func (err ErrInvalidCloneAddr) Error() string { - return fmt.Sprintf("invalid clone address [is_url_error: %v, is_invalid_path: %v, is_permission_denied: %v]", - err.IsURLError, err.IsInvalidPath, err.IsPermissionDenied) -} - -type ErrUpdateTaskNotExist struct { - UUID string -} - -func IsErrUpdateTaskNotExist(err error) bool { - _, ok := err.(ErrUpdateTaskNotExist) - return ok -} - -func (err ErrUpdateTaskNotExist) Error() string { - return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID) -} - -type ErrReleaseAlreadyExist struct { - TagName string -} - -func IsErrReleaseAlreadyExist(err error) bool { - _, ok := err.(ErrReleaseAlreadyExist) - return ok -} - -func (err ErrReleaseAlreadyExist) Error() string { - return fmt.Sprintf("release tag already exist [tag_name: %s]", err.TagName) -} - -type ErrReleaseNotExist struct { - ID int64 - TagName string -} - -func IsErrReleaseNotExist(err error) bool { - _, ok := err.(ErrReleaseNotExist) - return ok -} - -func (err ErrReleaseNotExist) Error() string { - return fmt.Sprintf("release tag does not exist [id: %d, tag_name: %s]", err.ID, err.TagName) -} - -type ErrInvalidTagName struct { - TagName string -} - -func IsErrInvalidTagName(err error) bool { - _, ok := err.(ErrInvalidTagName) - return ok -} - -func (err ErrInvalidTagName) Error() string { - return fmt.Sprintf("release tag name is not valid [tag_name: %s]", err.TagName) -} - -type ErrRepoFileAlreadyExist struct { - FileName string -} - -func IsErrRepoFileAlreadyExist(err error) bool { - _, ok := err.(ErrRepoFileAlreadyExist) - return ok -} - -func (err ErrRepoFileAlreadyExist) Error() string { - return fmt.Sprintf("repository file already exists [file_name: %s]", err.FileName) -} - -// __________ .__ .__ __________ __ -// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_ -// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ -// | | | | / |_| |_| | \ ___< <_| | | /\ ___/ \___ \ | | -// |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__| -// \/ \/ |__| \/ \/ - -type ErrPullRequestNotExist struct { - ID int64 - IssueID int64 - HeadRepoID int64 - BaseRepoID int64 - HeadBarcnh string - BaseBranch string -} - -func IsErrPullRequestNotExist(err error) bool { - _, ok := err.(ErrPullRequestNotExist) - return ok -} - -func (err ErrPullRequestNotExist) Error() string { - return fmt.Sprintf("pull request does not exist [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]", - err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch) -} - -// _________ __ -// \_ ___ \ ____ _____ _____ ____ _____/ |_ -// / \ \/ / _ \ / \ / \_/ __ \ / \ __\ -// \ \___( <_> ) Y Y \ Y Y \ ___/| | \ | -// \______ /\____/|__|_| /__|_| /\___ >___| /__| -// \/ \/ \/ \/ \/ - -type ErrCommentNotExist struct { - ID int64 - IssueID int64 -} - -func IsErrCommentNotExist(err error) bool { - _, ok := err.(ErrCommentNotExist) - return ok -} - -func (err ErrCommentNotExist) Error() string { - return fmt.Sprintf("comment does not exist [id: %d, issue_id: %d]", err.ID, err.IssueID) -} - -// .____ ___. .__ -// | | _____ \_ |__ ____ | | -// | | \__ \ | __ \_/ __ \| | -// | |___ / __ \| \_\ \ ___/| |__ -// |_______ (____ /___ /\___ >____/ -// \/ \/ \/ \/ - -type ErrLabelNotExist struct { - LabelID int64 - RepoID int64 -} - -func IsErrLabelNotExist(err error) bool { - _, ok := err.(ErrLabelNotExist) - return ok -} - -func (err ErrLabelNotExist) Error() string { - return fmt.Sprintf("label does not exist [label_id: %d, repo_id: %d]", err.LabelID, err.RepoID) -} - -// _____ .__.__ __ -// / \ |__| | ____ _______/ |_ ____ ____ ____ -// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ -// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/ -// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ > -// \/ \/ \/ \/ \/ - -type ErrMilestoneNotExist struct { - ID int64 - RepoID int64 -} - -func IsErrMilestoneNotExist(err error) bool { - _, ok := err.(ErrMilestoneNotExist) - return ok -} - -func (err ErrMilestoneNotExist) Error() string { - return fmt.Sprintf("milestone does not exist [id: %d, 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) -} - -// .____ .__ _________ -// | | ____ ____ |__| ____ / _____/ ____ __ _________ ____ ____ -// | | / _ \ / ___\| |/ \ \_____ \ / _ \| | \_ __ \_/ ___\/ __ \ -// | |__( <_> ) /_/ > | | \ / ( <_> ) | /| | \/\ \__\ ___/ -// |_______ \____/\___ /|__|___| / /_______ /\____/|____/ |__| \___ >___ > -// \/ /_____/ \/ \/ \/ \/ - -type ErrLoginSourceAlreadyExist struct { - Name string -} - -func IsErrLoginSourceAlreadyExist(err error) bool { - _, ok := err.(ErrLoginSourceAlreadyExist) - return ok -} - -func (err ErrLoginSourceAlreadyExist) Error() string { - return fmt.Sprintf("login source already exists [name: %s]", err.Name) -} - -type ErrLoginSourceInUse struct { - ID int64 -} - -func IsErrLoginSourceInUse(err error) bool { - _, ok := err.(ErrLoginSourceInUse) - return ok -} - -func (err ErrLoginSourceInUse) Error() string { - return fmt.Sprintf("login source is still used by some users [id: %d]", err.ID) -} - -// ___________ -// \__ ___/___ _____ _____ -// | |_/ __ \\__ \ / \ -// | |\ ___/ / __ \| Y Y \ -// |____| \___ >____ /__|_| / -// \/ \/ \/ - -type ErrTeamAlreadyExist struct { - OrgID int64 - Name string -} - -func IsErrTeamAlreadyExist(err error) bool { - _, ok := err.(ErrTeamAlreadyExist) - return ok -} - -func (err ErrTeamAlreadyExist) Error() string { - return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name) -} - -// ____ ___ .__ .___ -// | | \______ | | _________ __| _/ -// | | /\____ \| | / _ \__ \ / __ | -// | | / | |_> > |_( <_> ) __ \_/ /_/ | -// |______/ | __/|____/\____(____ /\____ | -// |__| \/ \/ -// - -type ErrUploadNotExist struct { - ID int64 - UUID string -} - -func IsErrUploadNotExist(err error) bool { - _, ok := err.(ErrAttachmentNotExist) - return ok -} - -func (err ErrUploadNotExist) Error() string { - return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID) -} diff --git a/models/errors/errors.go b/models/errors/errors.go deleted file mode 100644 index cc231436..00000000 --- a/models/errors/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package errors - -import "errors" - -var InternalServerError = errors.New("internal server error") - -// New is a wrapper of real errors.New function. -func New(text string) error { - return errors.New(text) -} diff --git a/models/errors/issue.go b/models/errors/issue.go deleted file mode 100644 index 903cc977..00000000 --- a/models/errors/issue.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package errors - -import "fmt" - -type IssueNotExist struct { - ID int64 - RepoID int64 - Index int64 -} - -func IsIssueNotExist(err error) bool { - _, ok := err.(IssueNotExist) - return ok -} - -func (err IssueNotExist) Error() string { - return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index) -} - -type InvalidIssueReference struct { - Ref string -} - -func IsInvalidIssueReference(err error) bool { - _, ok := err.(InvalidIssueReference) - return ok -} - -func (err InvalidIssueReference) Error() string { - return fmt.Sprintf("invalid issue reference [ref: %s]", err.Ref) -} diff --git a/models/errors/login_source.go b/models/errors/login_source.go deleted file mode 100644 index dd18664e..00000000 --- a/models/errors/login_source.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package errors - -import "fmt" - -type LoginSourceNotExist struct { - ID int64 -} - -func IsLoginSourceNotExist(err error) bool { - _, ok := err.(LoginSourceNotExist) - return ok -} - -func (err LoginSourceNotExist) Error() string { - return fmt.Sprintf("login source does not exist [id: %d]", err.ID) -} - -type LoginSourceNotActivated struct { - SourceID int64 -} - -func IsLoginSourceNotActivated(err error) bool { - _, ok := err.(LoginSourceNotActivated) - return ok -} - -func (err LoginSourceNotActivated) Error() string { - return fmt.Sprintf("login source is not activated [source_id: %d]", err.SourceID) -} - -type InvalidLoginSourceType struct { - Type interface{} -} - -func IsInvalidLoginSourceType(err error) bool { - _, ok := err.(InvalidLoginSourceType) - return ok -} - -func (err InvalidLoginSourceType) Error() string { - return fmt.Sprintf("invalid login source type [type: %v]", err.Type) -} - -type LoginSourceMismatch struct { - Expect int64 - Actual int64 -} - -func IsLoginSourceMismatch(err error) bool { - _, ok := err.(LoginSourceMismatch) - return ok -} - -func (err LoginSourceMismatch) Error() string { - return fmt.Sprintf("login source mismatch [expect: %d, actual: %d]", err.Expect, err.Actual) -} diff --git a/models/errors/org.go b/models/errors/org.go deleted file mode 100644 index 56532746..00000000 --- a/models/errors/org.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package errors - -import "fmt" - -type TeamNotExist struct { - TeamID int64 - Name string -} - -func IsTeamNotExist(err error) bool { - _, ok := err.(TeamNotExist) - return ok -} - -func (err TeamNotExist) Error() string { - return fmt.Sprintf("team does not exist [team_id: %d, name: %s]", err.TeamID, err.Name) -} diff --git a/models/errors/repo.go b/models/errors/repo.go deleted file mode 100644 index c9894af9..00000000 --- a/models/errors/repo.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package errors - -import "fmt" - -type RepoNotExist struct { - ID int64 - UserID int64 - Name string -} - -func IsRepoNotExist(err error) bool { - _, ok := err.(RepoNotExist) - return ok -} - -func (err RepoNotExist) Error() string { - return fmt.Sprintf("repository does not exist [id: %d, user_id: %d, name: %s]", err.ID, err.UserID, err.Name) -} - -type ReachLimitOfRepo struct { - Limit int -} - -func IsReachLimitOfRepo(err error) bool { - _, ok := err.(ReachLimitOfRepo) - return ok -} - -func (err ReachLimitOfRepo) Error() string { - return fmt.Sprintf("user has reached maximum limit of repositories [limit: %d]", err.Limit) -} - -type InvalidRepoReference struct { - Ref string -} - -func IsInvalidRepoReference(err error) bool { - _, ok := err.(InvalidRepoReference) - return ok -} - -func (err InvalidRepoReference) Error() string { - return fmt.Sprintf("invalid repository reference [ref: %s]", err.Ref) -} - -type MirrorNotExist struct { - RepoID int64 -} - -func IsMirrorNotExist(err error) bool { - _, ok := err.(MirrorNotExist) - return ok -} - -func (err MirrorNotExist) Error() string { - return fmt.Sprintf("mirror does not exist [repo_id: %d]", err.RepoID) -} - -type BranchAlreadyExists struct { - Name string -} - -func IsBranchAlreadyExists(err error) bool { - _, ok := err.(BranchAlreadyExists) - return ok -} - -func (err BranchAlreadyExists) Error() string { - return fmt.Sprintf("branch already exists [name: %s]", err.Name) -} - -type ErrBranchNotExist struct { - Name string -} - -func IsErrBranchNotExist(err error) bool { - _, ok := err.(ErrBranchNotExist) - return ok -} - -func (err ErrBranchNotExist) Error() string { - return fmt.Sprintf("branch does not exist [name: %s]", err.Name) -} diff --git a/models/errors/token.go b/models/errors/token.go deleted file mode 100644 index d6a4577a..00000000 --- a/models/errors/token.go +++ /dev/null @@ -1,16 +0,0 @@ -package errors - -import "fmt" - -type AccessTokenNameAlreadyExist struct { - Name string -} - -func IsAccessTokenNameAlreadyExist(err error) bool { - _, ok := err.(AccessTokenNameAlreadyExist) - return ok -} - -func (err AccessTokenNameAlreadyExist) Error() string { - return fmt.Sprintf("access token already exist [name: %s]", err.Name) -} diff --git a/models/errors/two_factor.go b/models/errors/two_factor.go deleted file mode 100644 index 02cdcf5c..00000000 --- a/models/errors/two_factor.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package errors - -import "fmt" - -type TwoFactorNotFound struct { - UserID int64 -} - -func IsTwoFactorNotFound(err error) bool { - _, ok := err.(TwoFactorNotFound) - return ok -} - -func (err TwoFactorNotFound) Error() string { - return fmt.Sprintf("two-factor authentication does not found [user_id: %d]", err.UserID) -} - -type TwoFactorRecoveryCodeNotFound struct { - Code string -} - -func IsTwoFactorRecoveryCodeNotFound(err error) bool { - _, ok := err.(TwoFactorRecoveryCodeNotFound) - return ok -} - -func (err TwoFactorRecoveryCodeNotFound) Error() string { - return fmt.Sprintf("two-factor recovery code does not found [code: %s]", err.Code) -} diff --git a/models/errors/user.go b/models/errors/user.go deleted file mode 100644 index 526d4b2d..00000000 --- a/models/errors/user.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package errors - -import "fmt" - -type EmptyName struct{} - -func IsEmptyName(err error) bool { - _, ok := err.(EmptyName) - return ok -} - -func (err EmptyName) Error() string { - return "empty name" -} - -type UserNotExist struct { - UserID int64 - Name string -} - -func IsUserNotExist(err error) bool { - _, ok := err.(UserNotExist) - return ok -} - -func (err UserNotExist) Error() string { - return fmt.Sprintf("user does not exist [user_id: %d, name: %s]", err.UserID, err.Name) -} - -type UserNotKeyOwner struct { - KeyID int64 -} - -func IsUserNotKeyOwner(err error) bool { - _, ok := err.(UserNotKeyOwner) - return ok -} - -func (err UserNotKeyOwner) Error() string { - return fmt.Sprintf("user is not the owner of public key [key_id: %d]", err.KeyID) -} diff --git a/models/errors/user_mail.go b/models/errors/user_mail.go deleted file mode 100644 index fcdeb78c..00000000 --- a/models/errors/user_mail.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package errors - -import "fmt" - -type EmailNotFound struct { - Email string -} - -func IsEmailNotFound(err error) bool { - _, ok := err.(EmailNotFound) - return ok -} - -func (err EmailNotFound) Error() string { - return fmt.Sprintf("email is not found [email: %s]", err.Email) -} - -type EmailNotVerified struct { - Email string -} - -func IsEmailNotVerified(err error) bool { - _, ok := err.(EmailNotVerified) - return ok -} - -func (err EmailNotVerified) Error() string { - return fmt.Sprintf("email has not been verified [email: %s]", err.Email) -} diff --git a/models/errors/webhook.go b/models/errors/webhook.go deleted file mode 100644 index 76cf8cb4..00000000 --- a/models/errors/webhook.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package errors - -import "fmt" - -type WebhookNotExist struct { - ID int64 -} - -func IsWebhookNotExist(err error) bool { - _, ok := err.(WebhookNotExist) - return ok -} - -func (err WebhookNotExist) Error() string { - return fmt.Sprintf("webhook does not exist [id: %d]", err.ID) -} - -type HookTaskNotExist struct { - HookID int64 - UUID string -} - -func IsHookTaskNotExist(err error) bool { - _, ok := err.(HookTaskNotExist) - return ok -} - -func (err HookTaskNotExist) Error() string { - return fmt.Sprintf("hook task does not exist [hook_id: %d, uuid: %s]", err.HookID, err.UUID) -} diff --git a/models/git_diff.go b/models/git_diff.go deleted file mode 100644 index f6bdea2d..00000000 --- a/models/git_diff.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "bytes" - "fmt" - "html" - "html/template" - "io" - - "github.com/sergi/go-diff/diffmatchpatch" - "golang.org/x/net/html/charset" - "golang.org/x/text/transform" - - "github.com/gogs/git-module" - - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/template/highlight" - "gogs.io/gogs/pkg/tool" -) - -type DiffSection struct { - *git.DiffSection -} - -var ( - addedCodePrefix = []byte("<span class=\"added-code\">") - removedCodePrefix = []byte("<span class=\"removed-code\">") - codeTagSuffix = []byte("</span>") -) - -func diffToHTML(diffs []diffmatchpatch.Diff, lineType git.DiffLineType) template.HTML { - buf := bytes.NewBuffer(nil) - - // Reproduce signs which are cutted for inline diff before. - switch lineType { - case git.DIFF_LINE_ADD: - buf.WriteByte('+') - case git.DIFF_LINE_DEL: - buf.WriteByte('-') - } - - for i := range diffs { - switch { - case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == git.DIFF_LINE_ADD: - buf.Write(addedCodePrefix) - buf.WriteString(html.EscapeString(diffs[i].Text)) - buf.Write(codeTagSuffix) - case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == git.DIFF_LINE_DEL: - buf.Write(removedCodePrefix) - buf.WriteString(html.EscapeString(diffs[i].Text)) - buf.Write(codeTagSuffix) - case diffs[i].Type == diffmatchpatch.DiffEqual: - buf.WriteString(html.EscapeString(diffs[i].Text)) - } - } - - return template.HTML(buf.Bytes()) -} - -var diffMatchPatch = diffmatchpatch.New() - -func init() { - diffMatchPatch.DiffEditCost = 100 -} - -// ComputedInlineDiffFor computes inline diff for the given line. -func (diffSection *DiffSection) ComputedInlineDiffFor(diffLine *git.DiffLine) template.HTML { - if setting.Git.DisableDiffHighlight { - return template.HTML(html.EscapeString(diffLine.Content[1:])) - } - var ( - compareDiffLine *git.DiffLine - diff1 string - diff2 string - ) - - // try to find equivalent diff line. ignore, otherwise - switch diffLine.Type { - case git.DIFF_LINE_ADD: - compareDiffLine = diffSection.Line(git.DIFF_LINE_DEL, diffLine.RightIdx) - if compareDiffLine == nil { - return template.HTML(html.EscapeString(diffLine.Content)) - } - diff1 = compareDiffLine.Content - diff2 = diffLine.Content - case git.DIFF_LINE_DEL: - compareDiffLine = diffSection.Line(git.DIFF_LINE_ADD, diffLine.LeftIdx) - if compareDiffLine == nil { - return template.HTML(html.EscapeString(diffLine.Content)) - } - diff1 = diffLine.Content - diff2 = compareDiffLine.Content - default: - return template.HTML(html.EscapeString(diffLine.Content)) - } - - diffRecord := diffMatchPatch.DiffMain(diff1[1:], diff2[1:], true) - diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord) - - return diffToHTML(diffRecord, diffLine.Type) -} - -type DiffFile struct { - *git.DiffFile - Sections []*DiffSection -} - -func (diffFile *DiffFile) HighlightClass() string { - return highlight.FileNameToHighlightClass(diffFile.Name) -} - -type Diff struct { - *git.Diff - Files []*DiffFile -} - -func NewDiff(gitDiff *git.Diff) *Diff { - diff := &Diff{ - Diff: gitDiff, - Files: make([]*DiffFile, gitDiff.NumFiles()), - } - - // FIXME: detect encoding while parsing. - var buf bytes.Buffer - for i := range gitDiff.Files { - buf.Reset() - - diff.Files[i] = &DiffFile{ - DiffFile: gitDiff.Files[i], - Sections: make([]*DiffSection, gitDiff.Files[i].NumSections()), - } - - for j := range gitDiff.Files[i].Sections { - diff.Files[i].Sections[j] = &DiffSection{ - DiffSection: gitDiff.Files[i].Sections[j], - } - - for k := range diff.Files[i].Sections[j].Lines { - buf.WriteString(diff.Files[i].Sections[j].Lines[k].Content) - buf.WriteString("\n") - } - } - - charsetLabel, err := tool.DetectEncoding(buf.Bytes()) - if charsetLabel != "UTF-8" && err == nil { - encoding, _ := charset.Lookup(charsetLabel) - if encoding != nil { - d := encoding.NewDecoder() - for j := range diff.Files[i].Sections { - for k := range diff.Files[i].Sections[j].Lines { - if c, _, err := transform.String(d, diff.Files[i].Sections[j].Lines[k].Content); err == nil { - diff.Files[i].Sections[j].Lines[k].Content = c - } - } - } - } - } - } - - return diff -} - -func ParsePatch(maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) (*Diff, error) { - done := make(chan error) - var gitDiff *git.Diff - go func() { - gitDiff = git.ParsePatch(done, maxLines, maxLineCharacteres, maxFiles, reader) - }() - - if err := <-done; err != nil { - return nil, fmt.Errorf("ParsePatch: %v", err) - } - return NewDiff(gitDiff), nil -} - -func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) { - gitDiff, err := git.GetDiffRange(repoPath, beforeCommitID, afterCommitID, maxLines, maxLineCharacteres, maxFiles) - if err != nil { - return nil, fmt.Errorf("GetDiffRange: %v", err) - } - return NewDiff(gitDiff), nil -} - -func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) { - gitDiff, err := git.GetDiffCommit(repoPath, commitID, maxLines, maxLineCharacteres, maxFiles) - if err != nil { - return nil, fmt.Errorf("GetDiffCommit: %v", err) - } - return NewDiff(gitDiff), nil -} diff --git a/models/git_diff_test.go b/models/git_diff_test.go deleted file mode 100644 index 285e5646..00000000 --- a/models/git_diff_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "html/template" - "testing" - - "github.com/gogs/git-module" - dmp "github.com/sergi/go-diff/diffmatchpatch" -) - -func assertEqual(t *testing.T, s1 string, s2 template.HTML) { - if s1 != string(s2) { - t.Errorf("%s should be equal %s", s2, s1) - } -} - -func assertLineEqual(t *testing.T, d1 *git.DiffLine, d2 *git.DiffLine) { - if d1 != d2 { - t.Errorf("%v should be equal %v", d1, d2) - } -} - -func Test_diffToHTML(t *testing.T) { - assertEqual(t, "+foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{ - dmp.Diff{dmp.DiffEqual, "foo "}, - dmp.Diff{dmp.DiffInsert, "bar"}, - dmp.Diff{dmp.DiffDelete, " baz"}, - dmp.Diff{dmp.DiffEqual, " biz"}, - }, git.DIFF_LINE_ADD)) - - assertEqual(t, "-foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{ - dmp.Diff{dmp.DiffEqual, "foo "}, - dmp.Diff{dmp.DiffDelete, "bar"}, - dmp.Diff{dmp.DiffInsert, " baz"}, - dmp.Diff{dmp.DiffEqual, " biz"}, - }, git.DIFF_LINE_DEL)) -} diff --git a/models/issue.go b/models/issue.go deleted file mode 100644 index 06ded252..00000000 --- a/models/issue.go +++ /dev/null @@ -1,1440 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "strings" - "time" - - "github.com/unknwon/com" - "xorm.io/xorm" - log "gopkg.in/clog.v1" - - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/tool" -) - -var ( - ErrMissingIssueNumber = errors.New("No issue number specified") -) - -// Issue represents an issue or pull request of repository. -type Issue struct { - ID int64 - RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"` - Repo *Repository `xorm:"-" json:"-"` - Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository. - PosterID int64 - Poster *User `xorm:"-" json:"-"` - Title string `xorm:"name"` - Content string `xorm:"TEXT"` - RenderedContent string `xorm:"-" json:"-"` - Labels []*Label `xorm:"-" json:"-"` - MilestoneID int64 - Milestone *Milestone `xorm:"-" json:"-"` - Priority int - AssigneeID int64 - Assignee *User `xorm:"-" json:"-"` - IsClosed bool - IsRead bool `xorm:"-" json:"-"` - IsPull bool // Indicates whether is a pull request or not. - PullRequest *PullRequest `xorm:"-" json:"-"` - NumComments int - - Deadline time.Time `xorm:"-" json:"-"` - DeadlineUnix int64 - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-"` - UpdatedUnix int64 - - Attachments []*Attachment `xorm:"-" json:"-"` - Comments []*Comment `xorm:"-" json:"-"` -} - -func (issue *Issue) BeforeInsert() { - issue.CreatedUnix = time.Now().Unix() - issue.UpdatedUnix = issue.CreatedUnix -} - -func (issue *Issue) BeforeUpdate() { - issue.UpdatedUnix = time.Now().Unix() - issue.DeadlineUnix = issue.Deadline.Unix() -} - -func (issue *Issue) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "deadline_unix": - issue.Deadline = time.Unix(issue.DeadlineUnix, 0).Local() - case "created_unix": - issue.Created = time.Unix(issue.CreatedUnix, 0).Local() - case "updated_unix": - issue.Updated = time.Unix(issue.UpdatedUnix, 0).Local() - } -} - -func (issue *Issue) loadAttributes(e Engine) (err error) { - if issue.Repo == nil { - issue.Repo, err = getRepositoryByID(e, issue.RepoID) - if err != nil { - return fmt.Errorf("getRepositoryByID [%d]: %v", issue.RepoID, err) - } - } - - if issue.Poster == nil { - issue.Poster, err = getUserByID(e, issue.PosterID) - if err != nil { - if errors.IsUserNotExist(err) { - issue.PosterID = -1 - issue.Poster = NewGhostUser() - } else { - return fmt.Errorf("getUserByID.(Poster) [%d]: %v", issue.PosterID, err) - } - } - } - - if issue.Labels == nil { - issue.Labels, err = getLabelsByIssueID(e, issue.ID) - if err != nil { - return fmt.Errorf("getLabelsByIssueID [%d]: %v", issue.ID, err) - } - } - - if issue.Milestone == nil && issue.MilestoneID > 0 { - issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) - if err != nil { - return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err) - } - } - - if issue.Assignee == nil && issue.AssigneeID > 0 { - issue.Assignee, err = getUserByID(e, issue.AssigneeID) - if err != nil { - return fmt.Errorf("getUserByID.(assignee) [%d]: %v", issue.AssigneeID, err) - } - } - - if issue.IsPull && issue.PullRequest == nil { - // It is possible pull request is not yet created. - issue.PullRequest, err = getPullRequestByIssueID(e, issue.ID) - if err != nil && !IsErrPullRequestNotExist(err) { - return fmt.Errorf("getPullRequestByIssueID [%d]: %v", issue.ID, err) - } - } - - if issue.Attachments == nil { - issue.Attachments, err = getAttachmentsByIssueID(e, issue.ID) - if err != nil { - return fmt.Errorf("getAttachmentsByIssueID [%d]: %v", issue.ID, err) - } - } - - if issue.Comments == nil { - issue.Comments, err = getCommentsByIssueID(e, issue.ID) - if err != nil { - return fmt.Errorf("getCommentsByIssueID [%d]: %v", issue.ID, err) - } - } - - return nil -} - -func (issue *Issue) LoadAttributes() error { - return issue.loadAttributes(x) -} - -func (issue *Issue) HTMLURL() string { - var path string - if issue.IsPull { - path = "pulls" - } else { - path = "issues" - } - return fmt.Sprintf("%s/%s/%d", issue.Repo.HTMLURL(), path, issue.Index) -} - -// State returns string representation of issue status. -func (issue *Issue) State() api.StateType { - if issue.IsClosed { - return api.STATE_CLOSED - } - return api.STATE_OPEN -} - -// This method assumes some fields assigned with values: -// Required - Poster, Labels, -// Optional - Milestone, Assignee, PullRequest -func (issue *Issue) APIFormat() *api.Issue { - apiLabels := make([]*api.Label, len(issue.Labels)) - for i := range issue.Labels { - apiLabels[i] = issue.Labels[i].APIFormat() - } - - apiIssue := &api.Issue{ - ID: issue.ID, - Index: issue.Index, - Poster: issue.Poster.APIFormat(), - Title: issue.Title, - Body: issue.Content, - Labels: apiLabels, - State: issue.State(), - Comments: issue.NumComments, - Created: issue.Created, - Updated: issue.Updated, - } - - if issue.Milestone != nil { - apiIssue.Milestone = issue.Milestone.APIFormat() - } - if issue.Assignee != nil { - apiIssue.Assignee = issue.Assignee.APIFormat() - } - if issue.IsPull { - apiIssue.PullRequest = &api.PullRequestMeta{ - HasMerged: issue.PullRequest.HasMerged, - } - if issue.PullRequest.HasMerged { - apiIssue.PullRequest.Merged = &issue.PullRequest.Merged - } - } - - return apiIssue -} - -// HashTag returns unique hash tag for issue. -func (issue *Issue) HashTag() string { - return "issue-" + com.ToStr(issue.ID) -} - -// IsPoster returns true if given user by ID is the poster. -func (issue *Issue) IsPoster(uid int64) bool { - return issue.PosterID == uid -} - -func (issue *Issue) hasLabel(e Engine, labelID int64) bool { - return hasIssueLabel(e, issue.ID, labelID) -} - -// HasLabel returns true if issue has been labeled by given ID. -func (issue *Issue) HasLabel(labelID int64) bool { - return issue.hasLabel(x, labelID) -} - -func (issue *Issue) sendLabelUpdatedWebhook(doer *User) { - var err error - if issue.IsPull { - err = issue.PullRequest.LoadIssue() - if err != nil { - log.Error(2, "LoadIssue: %v", err) - return - } - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ - Action: api.HOOK_ISSUE_LABEL_UPDATED, - Index: issue.Index, - PullRequest: issue.PullRequest.APIFormat(), - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }) - } else { - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_ISSUES, &api.IssuesPayload{ - Action: api.HOOK_ISSUE_LABEL_UPDATED, - Index: issue.Index, - Issue: issue.APIFormat(), - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }) - } - if err != nil { - log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) - } -} - -func (issue *Issue) addLabel(e *xorm.Session, label *Label) error { - return newIssueLabel(e, issue, label) -} - -// AddLabel adds a new label to the issue. -func (issue *Issue) AddLabel(doer *User, label *Label) error { - if err := NewIssueLabel(issue, label); err != nil { - return err - } - - issue.sendLabelUpdatedWebhook(doer) - return nil -} - -func (issue *Issue) addLabels(e *xorm.Session, labels []*Label) error { - return newIssueLabels(e, issue, labels) -} - -// AddLabels adds a list of new labels to the issue. -func (issue *Issue) AddLabels(doer *User, labels []*Label) error { - if err := NewIssueLabels(issue, labels); err != nil { - return err - } - - issue.sendLabelUpdatedWebhook(doer) - return nil -} - -func (issue *Issue) getLabels(e Engine) (err error) { - if len(issue.Labels) > 0 { - return nil - } - - issue.Labels, err = getLabelsByIssueID(e, issue.ID) - if err != nil { - return fmt.Errorf("getLabelsByIssueID: %v", err) - } - return nil -} - -func (issue *Issue) removeLabel(e *xorm.Session, label *Label) error { - return deleteIssueLabel(e, issue, label) -} - -// RemoveLabel removes a label from issue by given ID. -func (issue *Issue) RemoveLabel(doer *User, label *Label) error { - if err := DeleteIssueLabel(issue, label); err != nil { - return err - } - - issue.sendLabelUpdatedWebhook(doer) - return nil -} - -func (issue *Issue) clearLabels(e *xorm.Session) (err error) { - if err = issue.getLabels(e); err != nil { - return fmt.Errorf("getLabels: %v", err) - } - - // NOTE: issue.removeLabel slices issue.Labels, so we need to create another slice to be unaffected. - labels := make([]*Label, len(issue.Labels)) - for i := range issue.Labels { - labels[i] = issue.Labels[i] - } - for i := range labels { - if err = issue.removeLabel(e, labels[i]); err != nil { - return fmt.Errorf("removeLabel: %v", err) - } - } - - return nil -} - -func (issue *Issue) ClearLabels(doer *User) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = issue.clearLabels(sess); err != nil { - return err - } - - if err = sess.Commit(); err != nil { - return fmt.Errorf("Commit: %v", err) - } - - if issue.IsPull { - err = issue.PullRequest.LoadIssue() - if err != nil { - log.Error(2, "LoadIssue: %v", err) - return - } - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ - Action: api.HOOK_ISSUE_LABEL_CLEARED, - Index: issue.Index, - PullRequest: issue.PullRequest.APIFormat(), - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }) - } else { - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_ISSUES, &api.IssuesPayload{ - Action: api.HOOK_ISSUE_LABEL_CLEARED, - Index: issue.Index, - Issue: issue.APIFormat(), - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }) - } - if err != nil { - log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) - } - - return nil -} - -// ReplaceLabels removes all current labels and add new labels to the issue. -func (issue *Issue) ReplaceLabels(labels []*Label) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = issue.clearLabels(sess); err != nil { - return fmt.Errorf("clearLabels: %v", err) - } else if err = issue.addLabels(sess, labels); err != nil { - return fmt.Errorf("addLabels: %v", err) - } - - return sess.Commit() -} - -func (issue *Issue) GetAssignee() (err error) { - if issue.AssigneeID == 0 || issue.Assignee != nil { - return nil - } - - issue.Assignee, err = GetUserByID(issue.AssigneeID) - if errors.IsUserNotExist(err) { - return nil - } - return err -} - -// ReadBy sets issue to be read by given user. -func (issue *Issue) ReadBy(uid int64) error { - return UpdateIssueUserByRead(uid, issue.ID) -} - -func updateIssueCols(e Engine, issue *Issue, cols ...string) error { - _, err := e.ID(issue.ID).Cols(cols...).Update(issue) - return err -} - -// UpdateIssueCols only updates values of specific columns for given issue. -func UpdateIssueCols(issue *Issue, cols ...string) error { - return updateIssueCols(x, issue, cols...) -} - -func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, isClosed bool) (err error) { - // Nothing should be performed if current status is same as target status - if issue.IsClosed == isClosed { - return nil - } - issue.IsClosed = isClosed - - if err = updateIssueCols(e, issue, "is_closed"); err != nil { - return err - } else if err = updateIssueUsersByStatus(e, issue.ID, isClosed); err != nil { - return err - } - - // Update issue count of labels - if err = issue.getLabels(e); err != nil { - return err - } - for idx := range issue.Labels { - if issue.IsClosed { - issue.Labels[idx].NumClosedIssues++ - } else { - issue.Labels[idx].NumClosedIssues-- - } - if err = updateLabel(e, issue.Labels[idx]); err != nil { - return err - } - } - - // Update issue count of milestone - if err = changeMilestoneIssueStats(e, issue); err != nil { - return err - } - - // New action comment - if _, err = createStatusComment(e, doer, repo, issue); err != nil { - return err - } - - return nil -} - -// ChangeStatus changes issue status to open or closed. -func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = issue.changeStatus(sess, doer, repo, isClosed); err != nil { - return err - } - - if err = sess.Commit(); err != nil { - return fmt.Errorf("Commit: %v", err) - } - - if issue.IsPull { - // Merge pull request calls issue.changeStatus so we need to handle separately. - issue.PullRequest.Issue = issue - apiPullRequest := &api.PullRequestPayload{ - Index: issue.Index, - PullRequest: issue.PullRequest.APIFormat(), - Repository: repo.APIFormat(nil), - Sender: doer.APIFormat(), - } - if isClosed { - apiPullRequest.Action = api.HOOK_ISSUE_CLOSED - } else { - apiPullRequest.Action = api.HOOK_ISSUE_REOPENED - } - err = PrepareWebhooks(repo, HOOK_EVENT_PULL_REQUEST, apiPullRequest) - } else { - apiIssues := &api.IssuesPayload{ - Index: issue.Index, - Issue: issue.APIFormat(), - Repository: repo.APIFormat(nil), - Sender: doer.APIFormat(), - } - if isClosed { - apiIssues.Action = api.HOOK_ISSUE_CLOSED - } else { - apiIssues.Action = api.HOOK_ISSUE_REOPENED - } - err = PrepareWebhooks(repo, HOOK_EVENT_ISSUES, apiIssues) - } - if err != nil { - log.Error(2, "PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err) - } - - return nil -} - -func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { - oldTitle := issue.Title - issue.Title = title - if err = UpdateIssueCols(issue, "name"); err != nil { - return fmt.Errorf("UpdateIssueCols: %v", err) - } - - if issue.IsPull { - issue.PullRequest.Issue = issue - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ - Action: api.HOOK_ISSUE_EDITED, - Index: issue.Index, - PullRequest: issue.PullRequest.APIFormat(), - Changes: &api.ChangesPayload{ - Title: &api.ChangesFromPayload{ - From: oldTitle, - }, - }, - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }) - } else { - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_ISSUES, &api.IssuesPayload{ - Action: api.HOOK_ISSUE_EDITED, - Index: issue.Index, - Issue: issue.APIFormat(), - Changes: &api.ChangesPayload{ - Title: &api.ChangesFromPayload{ - From: oldTitle, - }, - }, - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }) - } - if err != nil { - log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) - } - - return nil -} - -func (issue *Issue) ChangeContent(doer *User, content string) (err error) { - oldContent := issue.Content - issue.Content = content - if err = UpdateIssueCols(issue, "content"); err != nil { - return fmt.Errorf("UpdateIssueCols: %v", err) - } - - if issue.IsPull { - issue.PullRequest.Issue = issue - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ - Action: api.HOOK_ISSUE_EDITED, - Index: issue.Index, - PullRequest: issue.PullRequest.APIFormat(), - Changes: &api.ChangesPayload{ - Body: &api.ChangesFromPayload{ - From: oldContent, - }, - }, - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }) - } else { - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_ISSUES, &api.IssuesPayload{ - Action: api.HOOK_ISSUE_EDITED, - Index: issue.Index, - Issue: issue.APIFormat(), - Changes: &api.ChangesPayload{ - Body: &api.ChangesFromPayload{ - From: oldContent, - }, - }, - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }) - } - if err != nil { - log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) - } - - return nil -} - -func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) { - issue.AssigneeID = assigneeID - if err = UpdateIssueUserByAssignee(issue); err != nil { - return fmt.Errorf("UpdateIssueUserByAssignee: %v", err) - } - - issue.Assignee, err = GetUserByID(issue.AssigneeID) - if err != nil && !errors.IsUserNotExist(err) { - log.Error(4, "GetUserByID [assignee_id: %v]: %v", issue.AssigneeID, err) - return nil - } - - // Error not nil here means user does not exist, which is remove assignee. - isRemoveAssignee := err != nil - if issue.IsPull { - issue.PullRequest.Issue = issue - apiPullRequest := &api.PullRequestPayload{ - Index: issue.Index, - PullRequest: issue.PullRequest.APIFormat(), - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - } - if isRemoveAssignee { - apiPullRequest.Action = api.HOOK_ISSUE_UNASSIGNED - } else { - apiPullRequest.Action = api.HOOK_ISSUE_ASSIGNED - } - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, apiPullRequest) - } else { - apiIssues := &api.IssuesPayload{ - Index: issue.Index, - Issue: issue.APIFormat(), - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - } - if isRemoveAssignee { - apiIssues.Action = api.HOOK_ISSUE_UNASSIGNED - } else { - apiIssues.Action = api.HOOK_ISSUE_ASSIGNED - } - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_ISSUES, apiIssues) - } - if err != nil { - log.Error(4, "PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, isRemoveAssignee, err) - } - - return nil -} - -type NewIssueOptions struct { - Repo *Repository - Issue *Issue - LableIDs []int64 - Attachments []string // In UUID format. - IsPull bool -} - -func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) { - opts.Issue.Title = strings.TrimSpace(opts.Issue.Title) - opts.Issue.Index = opts.Repo.NextIssueIndex() - - if opts.Issue.MilestoneID > 0 { - milestone, err := getMilestoneByRepoID(e, opts.Issue.RepoID, opts.Issue.MilestoneID) - if err != nil && !IsErrMilestoneNotExist(err) { - return fmt.Errorf("getMilestoneByID: %v", err) - } - - // Assume milestone is invalid and drop silently. - opts.Issue.MilestoneID = 0 - if milestone != nil { - opts.Issue.MilestoneID = milestone.ID - opts.Issue.Milestone = milestone - if err = changeMilestoneAssign(e, opts.Issue, -1); err != nil { - return err - } - } - } - - if opts.Issue.AssigneeID > 0 { - assignee, err := getUserByID(e, opts.Issue.AssigneeID) - if err != nil && !errors.IsUserNotExist(err) { - return fmt.Errorf("getUserByID: %v", err) - } - - // Assume assignee is invalid and drop silently. - opts.Issue.AssigneeID = 0 - if assignee != nil { - valid, err := hasAccess(e, assignee.ID, opts.Repo, ACCESS_MODE_READ) - if err != nil { - return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assignee.ID, opts.Repo.ID, err) - } - if valid { - opts.Issue.AssigneeID = assignee.ID - opts.Issue.Assignee = assignee - } - } - } - - // Milestone and assignee validation should happen before insert actual object. - if _, err = e.Insert(opts.Issue); err != nil { - return err - } - - if opts.IsPull { - _, err = e.Exec("UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?", opts.Issue.RepoID) - } else { - _, err = e.Exec("UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?", opts.Issue.RepoID) - } - if err != nil { - return err - } - - if len(opts.LableIDs) > 0 { - // During the session, SQLite3 driver cannot handle retrieve objects after update something. - // So we have to get all needed labels first. - labels := make([]*Label, 0, len(opts.LableIDs)) - if err = e.In("id", opts.LableIDs).Find(&labels); err != nil { - return fmt.Errorf("find all labels [label_ids: %v]: %v", opts.LableIDs, err) - } - - for _, label := range labels { - // Silently drop invalid labels. - if label.RepoID != opts.Repo.ID { - continue - } - - if err = opts.Issue.addLabel(e, label); err != nil { - return fmt.Errorf("addLabel [id: %d]: %v", label.ID, err) - } - } - } - - if err = newIssueUsers(e, opts.Repo, opts.Issue); err != nil { - return err - } - - if len(opts.Attachments) > 0 { - attachments, err := getAttachmentsByUUIDs(e, opts.Attachments) - if err != nil { - return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", opts.Attachments, err) - } - - for i := 0; i < len(attachments); i++ { - attachments[i].IssueID = opts.Issue.ID - if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil { - return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err) - } - } - } - - return opts.Issue.loadAttributes(e) -} - -// NewIssue creates new issue with labels and attachments for repository. -func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = newIssue(sess, NewIssueOptions{ - Repo: repo, - Issue: issue, - LableIDs: labelIDs, - Attachments: uuids, - }); err != nil { - return fmt.Errorf("newIssue: %v", err) - } - - if err = sess.Commit(); err != nil { - return fmt.Errorf("Commit: %v", err) - } - - if err = NotifyWatchers(&Action{ - ActUserID: issue.Poster.ID, - ActUserName: issue.Poster.Name, - OpType: ACTION_CREATE_ISSUE, - Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title), - RepoID: repo.ID, - RepoUserName: repo.Owner.Name, - RepoName: repo.Name, - IsPrivate: repo.IsPrivate, - }); err != nil { - log.Error(2, "NotifyWatchers: %v", err) - } - if err = issue.MailParticipants(); err != nil { - log.Error(2, "MailParticipants: %v", err) - } - - if err = PrepareWebhooks(repo, HOOK_EVENT_ISSUES, &api.IssuesPayload{ - Action: api.HOOK_ISSUE_OPENED, - Index: issue.Index, - Issue: issue.APIFormat(), - Repository: repo.APIFormat(nil), - Sender: issue.Poster.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks: %v", err) - } - - return nil -} - -// GetIssueByRef returns an Issue specified by a GFM reference. -// See https://help.github.com/articles/writing-on-github#references for more information on the syntax. -func GetIssueByRef(ref string) (*Issue, error) { - n := strings.IndexByte(ref, byte('#')) - if n == -1 { - return nil, errors.InvalidIssueReference{ref} - } - - index := com.StrTo(ref[n+1:]).MustInt64() - if index == 0 { - return nil, errors.IssueNotExist{} - } - - repo, err := GetRepositoryByRef(ref[:n]) - if err != nil { - return nil, err - } - - issue, err := GetIssueByIndex(repo.ID, index) - if err != nil { - return nil, err - } - - return issue, issue.LoadAttributes() -} - -// GetIssueByIndex returns raw issue without loading attributes by index in a repository. -func GetRawIssueByIndex(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, errors.IssueNotExist{0, repoID, index} - } - return issue, nil -} - -// GetIssueByIndex returns issue by index in a repository. -func GetIssueByIndex(repoID, index int64) (*Issue, error) { - issue, err := GetRawIssueByIndex(repoID, index) - if err != nil { - return nil, err - } - return issue, issue.LoadAttributes() -} - -func getRawIssueByID(e Engine, id int64) (*Issue, error) { - issue := new(Issue) - has, err := e.ID(id).Get(issue) - if err != nil { - return nil, err - } else if !has { - return nil, errors.IssueNotExist{id, 0, 0} - } - return issue, nil -} - -func getIssueByID(e Engine, id int64) (*Issue, error) { - issue, err := getRawIssueByID(e, id) - if err != nil { - return nil, err - } - return issue, issue.loadAttributes(e) -} - -// GetIssueByID returns an issue by given ID. -func GetIssueByID(id int64) (*Issue, error) { - return getIssueByID(x, id) -} - -type IssuesOptions struct { - UserID int64 - AssigneeID int64 - RepoID int64 - PosterID int64 - MilestoneID int64 - RepoIDs []int64 - Page int - IsClosed bool - IsMention bool - IsPull bool - Labels string - SortType string -} - -// buildIssuesQuery returns nil if it foresees there won't be any value returned. -func buildIssuesQuery(opts *IssuesOptions) *xorm.Session { - sess := x.NewSession() - - if opts.Page <= 0 { - opts.Page = 1 - } - - if opts.RepoID > 0 { - sess.Where("issue.repo_id=?", opts.RepoID).And("issue.is_closed=?", opts.IsClosed) - } else if opts.RepoIDs != nil { - // In case repository IDs are provided but actually no repository has issue. - if len(opts.RepoIDs) == 0 { - return nil - } - sess.In("issue.repo_id", opts.RepoIDs).And("issue.is_closed=?", opts.IsClosed) - } else { - sess.Where("issue.is_closed=?", opts.IsClosed) - } - - if opts.AssigneeID > 0 { - sess.And("issue.assignee_id=?", opts.AssigneeID) - } else if opts.PosterID > 0 { - sess.And("issue.poster_id=?", opts.PosterID) - } - - if opts.MilestoneID > 0 { - sess.And("issue.milestone_id=?", opts.MilestoneID) - } - - sess.And("issue.is_pull=?", opts.IsPull) - - switch opts.SortType { - case "oldest": - sess.Asc("issue.created_unix") - case "recentupdate": - sess.Desc("issue.updated_unix") - case "leastupdate": - sess.Asc("issue.updated_unix") - case "mostcomment": - sess.Desc("issue.num_comments") - case "leastcomment": - sess.Asc("issue.num_comments") - case "priority": - sess.Desc("issue.priority") - default: - sess.Desc("issue.created_unix") - } - - if len(opts.Labels) > 0 && opts.Labels != "0" { - labelIDs := strings.Split(opts.Labels, ",") - if len(labelIDs) > 0 { - sess.Join("INNER", "issue_label", "issue.id = issue_label.issue_id").In("issue_label.label_id", labelIDs) - } - } - - if opts.IsMention { - sess.Join("INNER", "issue_user", "issue.id = issue_user.issue_id").And("issue_user.is_mentioned = ?", true) - - if opts.UserID > 0 { - sess.And("issue_user.uid = ?", opts.UserID) - } - } - - return sess -} - -// IssuesCount returns the number of issues by given conditions. -func IssuesCount(opts *IssuesOptions) (int64, error) { - sess := buildIssuesQuery(opts) - if sess == nil { - return 0, nil - } - - return sess.Count(&Issue{}) -} - -// Issues returns a list of issues by given conditions. -func Issues(opts *IssuesOptions) ([]*Issue, error) { - sess := buildIssuesQuery(opts) - if sess == nil { - return make([]*Issue, 0), nil - } - - sess.Limit(setting.UI.IssuePagingNum, (opts.Page-1)*setting.UI.IssuePagingNum) - - issues := make([]*Issue, 0, setting.UI.IssuePagingNum) - if err := sess.Find(&issues); err != nil { - return nil, fmt.Errorf("Find: %v", err) - } - - // FIXME: use IssueList to improve performance. - for i := range issues { - if err := issues[i].LoadAttributes(); err != nil { - return nil, fmt.Errorf("LoadAttributes [%d]: %v", issues[i].ID, err) - } - } - - return issues, nil -} - -// GetParticipantsByIssueID returns all users who are participated in comments of an issue. -func GetParticipantsByIssueID(issueID int64) ([]*User, error) { - userIDs := make([]int64, 0, 5) - if err := x.Table("comment").Cols("poster_id"). - Where("issue_id = ?", issueID). - Distinct("poster_id"). - Find(&userIDs); err != nil { - return nil, fmt.Errorf("get poster IDs: %v", err) - } - if len(userIDs) == 0 { - return nil, nil - } - - users := make([]*User, 0, len(userIDs)) - return users, x.In("id", userIDs).Find(&users) -} - -// .___ ____ ___ -// | | ______ ________ __ ____ | | \______ ___________ -// | |/ ___// ___/ | \_/ __ \| | / ___// __ \_ __ \ -// | |\___ \ \___ \| | /\ ___/| | /\___ \\ ___/| | \/ -// |___/____ >____ >____/ \___ >______//____ >\___ >__| -// \/ \/ \/ \/ \/ - -// 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 - IsRead bool - IsAssigned bool - IsMentioned bool - IsPoster bool - IsClosed bool -} - -func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error { - assignees, err := repo.getAssignees(e) - if err != nil { - return fmt.Errorf("getAssignees: %v", err) - } - - // Poster can be anyone, append later if not one of assignees. - isPosterAssignee := false - - // Leave a seat for poster itself to append later, but if poster is one of assignee - // and just waste 1 unit is cheaper than re-allocate memory once. - issueUsers := make([]*IssueUser, 0, len(assignees)+1) - for _, assignee := range assignees { - isPoster := assignee.ID == issue.PosterID - issueUsers = append(issueUsers, &IssueUser{ - IssueID: issue.ID, - RepoID: repo.ID, - UID: assignee.ID, - IsPoster: isPoster, - IsAssigned: assignee.ID == issue.AssigneeID, - }) - if !isPosterAssignee && isPoster { - isPosterAssignee = true - } - } - if !isPosterAssignee { - issueUsers = append(issueUsers, &IssueUser{ - IssueID: issue.ID, - RepoID: repo.ID, - UID: issue.PosterID, - IsPoster: true, - }) - } - - if _, err = e.Insert(issueUsers); err != nil { - return err - } - return nil -} - -// NewIssueUsers adds new issue-user relations for new issue of repository. -func NewIssueUsers(repo *Repository, issue *Issue) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - 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 { - return i - } - } - return -1 -} - -// 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}) - return ius, err -} - -// GetIssueUserPairsByRepoIds returns issue-user pairs by given repository IDs. -func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*IssueUser, error) { - if len(rids) == 0 { - return []*IssueUser{}, nil - } - - ius := make([]*IssueUser, 0, 10) - sess := x.Limit(20, (page-1)*20).Where("is_closed=?", isClosed).In("repo_id", rids) - err := sess.Find(&ius) - return ius, err -} - -// GetIssueUserPairsByMode returns issue-user pairs by given repository and user. -func GetIssueUserPairsByMode(userID, repoID int64, filterMode FilterMode, isClosed bool, page int) ([]*IssueUser, error) { - ius := make([]*IssueUser, 0, 10) - sess := x.Limit(20, (page-1)*20).Where("uid=?", userID).And("is_closed=?", isClosed) - if repoID > 0 { - sess.And("repo_id=?", repoID) - } - - switch filterMode { - case FILTER_MODE_ASSIGN: - sess.And("is_assigned=?", true) - case FILTER_MODE_CREATE: - sess.And("is_poster=?", true) - default: - return ius, nil - } - err := sess.Find(&ius) - return ius, err -} - -// updateIssueMentions extracts mentioned people from content and -// updates issue-user relations for them. -func updateIssueMentions(e Engine, issueID int64, mentions []string) error { - if len(mentions) == 0 { - return nil - } - - for i := range mentions { - mentions[i] = strings.ToLower(mentions[i]) - } - users := make([]*User, 0, len(mentions)) - - if err := e.In("lower_name", mentions).Asc("lower_name").Find(&users); err != nil { - return fmt.Errorf("find mentioned users: %v", err) - } - - ids := make([]int64, 0, len(mentions)) - for _, user := range users { - ids = append(ids, user.ID) - if !user.IsOrganization() || user.NumMembers == 0 { - continue - } - - memberIDs := make([]int64, 0, user.NumMembers) - orgUsers, err := getOrgUsersByOrgID(e, user.ID) - if err != nil { - return fmt.Errorf("getOrgUsersByOrgID [%d]: %v", user.ID, err) - } - - for _, orgUser := range orgUsers { - memberIDs = append(memberIDs, orgUser.ID) - } - - ids = append(ids, memberIDs...) - } - - if err := updateIssueUsersByMentions(e, issueID, ids); err != nil { - return fmt.Errorf("UpdateIssueUsersByMentions: %v", err) - } - - return nil -} - -// IssueStats represents issue statistic information. -type IssueStats struct { - OpenCount, ClosedCount int64 - YourReposCount int64 - AssignCount int64 - CreateCount int64 - MentionCount int64 -} - -type FilterMode string - -const ( - FILTER_MODE_YOUR_REPOS FilterMode = "your_repositories" - FILTER_MODE_ASSIGN FilterMode = "assigned" - FILTER_MODE_CREATE FilterMode = "created_by" - FILTER_MODE_MENTION FilterMode = "mentioned" -) - -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 -} - -type IssueStatsOptions struct { - RepoID int64 - UserID int64 - Labels string - MilestoneID int64 - AssigneeID int64 - FilterMode FilterMode - IsPull bool -} - -// GetIssueStats returns issue statistic information by given conditions. -func GetIssueStats(opts *IssueStatsOptions) *IssueStats { - stats := &IssueStats{} - - countSession := func(opts *IssueStatsOptions) *xorm.Session { - sess := x.Where("issue.repo_id = ?", opts.RepoID).And("is_pull = ?", opts.IsPull) - - if len(opts.Labels) > 0 && opts.Labels != "0" { - labelIDs := tool.StringsToInt64s(strings.Split(opts.Labels, ",")) - if len(labelIDs) > 0 { - sess.Join("INNER", "issue_label", "issue.id = issue_id").In("label_id", labelIDs) - } - } - - if opts.MilestoneID > 0 { - sess.And("issue.milestone_id = ?", opts.MilestoneID) - } - - if opts.AssigneeID > 0 { - sess.And("assignee_id = ?", opts.AssigneeID) - } - - return sess - } - - switch opts.FilterMode { - case FILTER_MODE_YOUR_REPOS, FILTER_MODE_ASSIGN: - stats.OpenCount, _ = countSession(opts). - And("is_closed = ?", false). - Count(new(Issue)) - - stats.ClosedCount, _ = countSession(opts). - And("is_closed = ?", true). - Count(new(Issue)) - case FILTER_MODE_CREATE: - stats.OpenCount, _ = countSession(opts). - And("poster_id = ?", opts.UserID). - And("is_closed = ?", false). - Count(new(Issue)) - - stats.ClosedCount, _ = countSession(opts). - And("poster_id = ?", opts.UserID). - And("is_closed = ?", true). - Count(new(Issue)) - case FILTER_MODE_MENTION: - stats.OpenCount, _ = countSession(opts). - Join("INNER", "issue_user", "issue.id = issue_user.issue_id"). - And("issue_user.uid = ?", opts.UserID). - And("issue_user.is_mentioned = ?", true). - And("issue.is_closed = ?", false). - Count(new(Issue)) - - stats.ClosedCount, _ = countSession(opts). - Join("INNER", "issue_user", "issue.id = issue_user.issue_id"). - And("issue_user.uid = ?", opts.UserID). - And("issue_user.is_mentioned = ?", true). - And("issue.is_closed = ?", true). - Count(new(Issue)) - } - return stats -} - -// GetUserIssueStats returns issue statistic information for dashboard by given conditions. -func GetUserIssueStats(repoID, userID int64, repoIDs []int64, filterMode FilterMode, isPull bool) *IssueStats { - stats := &IssueStats{} - hasAnyRepo := repoID > 0 || len(repoIDs) > 0 - countSession := func(isClosed, isPull bool, repoID int64, repoIDs []int64) *xorm.Session { - sess := x.Where("issue.is_closed = ?", isClosed).And("issue.is_pull = ?", isPull) - - if repoID > 0 { - sess.And("repo_id = ?", repoID) - } else if len(repoIDs) > 0 { - sess.In("repo_id", repoIDs) - } - - return sess - } - - stats.AssignCount, _ = countSession(false, isPull, repoID, nil). - And("assignee_id = ?", userID). - Count(new(Issue)) - - stats.CreateCount, _ = countSession(false, isPull, repoID, nil). - And("poster_id = ?", userID). - Count(new(Issue)) - - if hasAnyRepo { - stats.YourReposCount, _ = countSession(false, isPull, repoID, repoIDs). - Count(new(Issue)) - } - - switch filterMode { - case FILTER_MODE_YOUR_REPOS: - if !hasAnyRepo { - break - } - - stats.OpenCount, _ = countSession(false, isPull, repoID, repoIDs). - Count(new(Issue)) - stats.ClosedCount, _ = countSession(true, isPull, repoID, repoIDs). - Count(new(Issue)) - case FILTER_MODE_ASSIGN: - stats.OpenCount, _ = countSession(false, isPull, repoID, nil). - And("assignee_id = ?", userID). - Count(new(Issue)) - stats.ClosedCount, _ = countSession(true, isPull, repoID, nil). - And("assignee_id = ?", userID). - Count(new(Issue)) - case FILTER_MODE_CREATE: - stats.OpenCount, _ = countSession(false, isPull, repoID, nil). - And("poster_id = ?", userID). - Count(new(Issue)) - stats.ClosedCount, _ = countSession(true, isPull, repoID, nil). - And("poster_id = ?", userID). - Count(new(Issue)) - } - - return stats -} - -// GetRepoIssueStats returns number of open and closed repository issues by given filter mode. -func GetRepoIssueStats(repoID, userID int64, filterMode FilterMode, isPull bool) (numOpen int64, numClosed int64) { - countSession := func(isClosed, isPull bool, repoID int64) *xorm.Session { - sess := x.Where("issue.repo_id = ?", isClosed). - And("is_pull = ?", isPull). - And("repo_id = ?", repoID) - - return sess - } - - openCountSession := countSession(false, isPull, repoID) - closedCountSession := countSession(true, isPull, repoID) - - switch filterMode { - case FILTER_MODE_ASSIGN: - openCountSession.And("assignee_id = ?", userID) - closedCountSession.And("assignee_id = ?", userID) - case FILTER_MODE_CREATE: - openCountSession.And("poster_id = ?", userID) - closedCountSession.And("poster_id = ?", userID) - } - - openResult, _ := openCountSession.Count(new(Issue)) - closedResult, _ := closedCountSession.Count(new(Issue)) - - return openResult, closedResult -} - -func updateIssue(e Engine, issue *Issue) error { - _, err := e.ID(issue.ID).AllCols().Update(issue) - return err -} - -// UpdateIssue updates all fields of given issue. -func UpdateIssue(issue *Issue) error { - 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 -} - -// UpdateIssueUsersByStatus updates issue-user relations by issue status. -func UpdateIssueUsersByStatus(issueID int64, isClosed bool) error { - return updateIssueUsersByStatus(x, issueID, isClosed) -} - -func updateIssueUserByAssignee(e *xorm.Session, issue *Issue) (err error) { - if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?", false, issue.ID); err != nil { - return err - } - - // Assignee ID equals to 0 means clear assignee. - if issue.AssigneeID > 0 { - if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?", true, issue.AssigneeID, issue.ID); err != nil { - return err - } - } - - return updateIssue(e, issue) -} - -// UpdateIssueUserByAssignee updates issue-user relation for assignee. -func UpdateIssueUserByAssignee(issue *Issue) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = updateIssueUserByAssignee(sess, issue); 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 -} - -// updateIssueUsersByMentions updates issue-user pairs by mentioning. -func updateIssueUsersByMentions(e Engine, issueID int64, uids []int64) error { - for _, uid := range uids { - iu := &IssueUser{ - UID: uid, - IssueID: issueID, - } - has, err := e.Get(iu) - if err != nil { - return err - } - - iu.IsMentioned = true - if has { - _, err = e.ID(iu.ID).AllCols().Update(iu) - } else { - _, err = e.Insert(iu) - } - if err != nil { - return err - } - } - return nil -} diff --git a/models/issue_label.go b/models/issue_label.go deleted file mode 100644 index fb7f5662..00000000 --- a/models/issue_label.go +++ /dev/null @@ -1,374 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "html/template" - "regexp" - "strconv" - "strings" - - "xorm.io/xorm" - - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/pkg/tool" -) - -var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})") - -// GetLabelTemplateFile loads the label template file by given name, -// then parses and returns a list of name-color pairs. -func GetLabelTemplateFile(name string) ([][2]string, error) { - data, err := getRepoInitFile("label", name) - if err != nil { - return nil, fmt.Errorf("getRepoInitFile: %v", err) - } - - lines := strings.Split(string(data), "\n") - list := make([][2]string, 0, len(lines)) - for i := 0; i < len(lines); i++ { - line := strings.TrimSpace(lines[i]) - if len(line) == 0 { - continue - } - - fields := strings.SplitN(line, " ", 2) - if len(fields) != 2 { - return nil, fmt.Errorf("line is malformed: %s", line) - } - - if !labelColorPattern.MatchString(fields[0]) { - return nil, fmt.Errorf("bad HTML color code in line: %s", line) - } - - fields[1] = strings.TrimSpace(fields[1]) - list = append(list, [2]string{fields[1], fields[0]}) - } - - return list, nil -} - -// Label represents a label of repository for issues. -type Label struct { - ID int64 - RepoID int64 `xorm:"INDEX"` - Name string - Color string `xorm:"VARCHAR(7)"` - NumIssues int - NumClosedIssues int - NumOpenIssues int `xorm:"-" json:"-"` - IsChecked bool `xorm:"-" json:"-"` -} - -func (label *Label) APIFormat() *api.Label { - return &api.Label{ - ID: label.ID, - Name: label.Name, - Color: strings.TrimLeft(label.Color, "#"), - } -} - -// CalOpenIssues calculates the open issues of label. -func (label *Label) CalOpenIssues() { - label.NumOpenIssues = label.NumIssues - label.NumClosedIssues -} - -// ForegroundColor calculates the text color for labels based -// on their background color. -func (l *Label) ForegroundColor() template.CSS { - if strings.HasPrefix(l.Color, "#") { - if color, err := strconv.ParseUint(l.Color[1:], 16, 64); err == nil { - r := float32(0xFF & (color >> 16)) - g := float32(0xFF & (color >> 8)) - b := float32(0xFF & color) - luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 - - if luminance < 0.66 { - return template.CSS("#fff") - } - } - } - - // default to black - return template.CSS("#000") -} - -// NewLabels creates new label(s) for a repository. -func NewLabels(labels ...*Label) error { - _, err := x.Insert(labels) - return err -} - -// getLabelOfRepoByName returns a label by Name in given repository. -// If pass repoID as 0, then ORM will ignore limitation of repository -// and can return arbitrary label with any valid ID. -func getLabelOfRepoByName(e Engine, repoID int64, labelName string) (*Label, error) { - if len(labelName) <= 0 { - return nil, ErrLabelNotExist{0, repoID} - } - - l := &Label{ - Name: labelName, - RepoID: repoID, - } - has, err := x.Get(l) - if err != nil { - return nil, err - } else if !has { - return nil, ErrLabelNotExist{0, l.RepoID} - } - return l, nil -} - -// getLabelInRepoByID returns a label by ID in given repository. -// If pass repoID as 0, then ORM will ignore limitation of repository -// and can return arbitrary label with any valid ID. -func getLabelOfRepoByID(e Engine, repoID, labelID int64) (*Label, error) { - if labelID <= 0 { - return nil, ErrLabelNotExist{labelID, repoID} - } - - l := &Label{ - ID: labelID, - RepoID: repoID, - } - has, err := x.Get(l) - if err != nil { - return nil, err - } else if !has { - return nil, ErrLabelNotExist{l.ID, l.RepoID} - } - return l, nil -} - -// GetLabelByID returns a label by given ID. -func GetLabelByID(id int64) (*Label, error) { - return getLabelOfRepoByID(x, 0, id) -} - -// GetLabelOfRepoByID returns a label by ID in given repository. -func GetLabelOfRepoByID(repoID, labelID int64) (*Label, error) { - return getLabelOfRepoByID(x, repoID, labelID) -} - -// GetLabelOfRepoByName returns a label by name in given repository. -func GetLabelOfRepoByName(repoID int64, labelName string) (*Label, error) { - return getLabelOfRepoByName(x, repoID, labelName) -} - -// GetLabelsInRepoByIDs returns a list of labels by IDs in given repository, -// it silently ignores label IDs that are not belong to the repository. -func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) { - labels := make([]*Label, 0, len(labelIDs)) - return labels, x.Where("repo_id = ?", repoID).In("id", tool.Int64sToStrings(labelIDs)).Asc("name").Find(&labels) -} - -// GetLabelsByRepoID returns all labels that belong to given repository by ID. -func GetLabelsByRepoID(repoID int64) ([]*Label, error) { - labels := make([]*Label, 0, 10) - return labels, x.Where("repo_id = ?", repoID).Asc("name").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) - } else if len(issueLabels) == 0 { - return []*Label{}, nil - } - - labelIDs := make([]int64, len(issueLabels)) - for i := range issueLabels { - labelIDs[i] = issueLabels[i].LabelID - } - - labels := make([]*Label, 0, len(labelIDs)) - return labels, e.Where("id > 0").In("id", tool.Int64sToStrings(labelIDs)).Asc("name").Find(&labels) -} - -// 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 { - return updateLabel(x, l) -} - -// DeleteLabel delete a label of given repository. -func DeleteLabel(repoID, labelID int64) error { - _, err := GetLabelOfRepoByID(repoID, labelID) - if err != nil { - if IsErrLabelNotExist(err) { - return nil - } - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.ID(labelID).Delete(new(Label)); err != nil { - return err - } else if _, err = sess.Where("label_id = ?", labelID).Delete(new(IssueLabel)); err != nil { - return err - } - - return sess.Commit() -} - -// .___ .____ ___. .__ -// | | ______ ________ __ ____ | | _____ \_ |__ ____ | | -// | |/ ___// ___/ | \_/ __ \| | \__ \ | __ \_/ __ \| | -// | |\___ \ \___ \| | /\ ___/| |___ / __ \| \_\ \ ___/| |__ -// |___/____ >____ >____/ \___ >_______ (____ /___ /\___ >____/ -// \/ \/ \/ \/ \/ \/ \/ - -// IssueLabel represetns an issue-lable relation. -type IssueLabel struct { - ID int64 - 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 *xorm.Session, issue *Issue, label *Label) (err error) { - if _, err = e.Insert(&IssueLabel{ - IssueID: issue.ID, - LabelID: label.ID, - }); err != nil { - return err - } - - label.NumIssues++ - if issue.IsClosed { - label.NumClosedIssues++ - } - - if err = updateLabel(e, label); err != nil { - return fmt.Errorf("updateLabel: %v", err) - } - - issue.Labels = append(issue.Labels, label) - return nil -} - -// NewIssueLabel creates a new issue-label relation. -func NewIssueLabel(issue *Issue, label *Label) (err error) { - if HasIssueLabel(issue.ID, label.ID) { - return nil - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = newIssueLabel(sess, issue, label); err != nil { - return err - } - - return sess.Commit() -} - -func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label) (err error) { - for i := range labels { - if hasIssueLabel(e, issue.ID, labels[i].ID) { - continue - } - - if err = newIssueLabel(e, issue, labels[i]); err != nil { - return fmt.Errorf("newIssueLabel: %v", err) - } - } - - return nil -} - -// NewIssueLabels creates a list of issue-label relations. -func NewIssueLabels(issue *Issue, labels []*Label) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = newIssueLabels(sess, issue, labels); err != nil { - return err - } - - return sess.Commit() -} - -func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) { - issueLabels := make([]*IssueLabel, 0, 10) - return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels) -} - -// GetIssueLabels returns all issue-label relations of given issue by ID. -func GetIssueLabels(issueID int64) ([]*IssueLabel, error) { - return getIssueLabels(x, issueID) -} - -func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { - if _, err = e.Delete(&IssueLabel{ - IssueID: issue.ID, - LabelID: label.ID, - }); err != nil { - return err - } - - label.NumIssues-- - if issue.IsClosed { - label.NumClosedIssues-- - } - if err = updateLabel(e, label); err != nil { - return fmt.Errorf("updateLabel: %v", err) - } - - for i := range issue.Labels { - if issue.Labels[i].ID == label.ID { - issue.Labels = append(issue.Labels[:i], issue.Labels[i+1:]...) - break - } - } - return nil -} - -// DeleteIssueLabel deletes issue-label relation. -func DeleteIssueLabel(issue *Issue, label *Label) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = deleteIssueLabel(sess, issue, label); err != nil { - return err - } - - return sess.Commit() -} diff --git a/models/issue_mail.go b/models/issue_mail.go deleted file mode 100644 index 941fbced..00000000 --- a/models/issue_mail.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - - "github.com/unknwon/com" - log "gopkg.in/clog.v1" - - "gogs.io/gogs/pkg/mailer" - "gogs.io/gogs/pkg/markup" - "gogs.io/gogs/pkg/setting" -) - -func (issue *Issue) MailSubject() string { - return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Title, issue.Index) -} - -// mailerUser is a wrapper for satisfying mailer.User interface. -type mailerUser struct { - user *User -} - -func (this mailerUser) ID() int64 { - return this.user.ID -} - -func (this mailerUser) DisplayName() string { - return this.user.DisplayName() -} - -func (this mailerUser) Email() string { - return this.user.Email -} - -func (this mailerUser) GenerateActivateCode() string { - return this.user.GenerateActivateCode() -} - -func (this mailerUser) GenerateEmailActivateCode(email string) string { - return this.user.GenerateEmailActivateCode(email) -} - -func NewMailerUser(u *User) mailer.User { - return mailerUser{u} -} - -// mailerRepo is a wrapper for satisfying mailer.Repository interface. -type mailerRepo struct { - repo *Repository -} - -func (this mailerRepo) FullName() string { - return this.repo.FullName() -} - -func (this mailerRepo) HTMLURL() string { - return this.repo.HTMLURL() -} - -func (this mailerRepo) ComposeMetas() map[string]string { - return this.repo.ComposeMetas() -} - -func NewMailerRepo(repo *Repository) mailer.Repository { - return mailerRepo{repo} -} - -// mailerIssue is a wrapper for satisfying mailer.Issue interface. -type mailerIssue struct { - issue *Issue -} - -func (this mailerIssue) MailSubject() string { - return this.issue.MailSubject() -} - -func (this mailerIssue) Content() string { - return this.issue.Content -} - -func (this mailerIssue) HTMLURL() string { - return this.issue.HTMLURL() -} - -func NewMailerIssue(issue *Issue) mailer.Issue { - return mailerIssue{issue} -} - -// mailIssueCommentToParticipants can be used for both new issue creation and comment. -// This functions sends two list of emails: -// 1. Repository watchers, users who participated in comments and the assignee. -// 2. Users who are not in 1. but get mentioned in current issue/comment. -func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error { - if !setting.Service.EnableNotifyMail { - return nil - } - - watchers, err := GetWatchers(issue.RepoID) - if err != nil { - return fmt.Errorf("GetWatchers [repo_id: %d]: %v", issue.RepoID, err) - } - participants, err := GetParticipantsByIssueID(issue.ID) - if err != nil { - return fmt.Errorf("GetParticipantsByIssueID [issue_id: %d]: %v", issue.ID, err) - } - - // In case the issue poster is not watching the repository, - // even if we have duplicated in watchers, can be safely filtered out. - if issue.PosterID != doer.ID { - participants = append(participants, issue.Poster) - } - - tos := make([]string, 0, len(watchers)) // List of email addresses - names := make([]string, 0, len(watchers)) - for i := range watchers { - if watchers[i].UserID == doer.ID { - continue - } - - to, err := GetUserByID(watchers[i].UserID) - if err != nil { - return fmt.Errorf("GetUserByID [%d]: %v", watchers[i].UserID, err) - } - if to.IsOrganization() || !to.IsActive { - continue - } - - tos = append(tos, to.Email) - names = append(names, to.Name) - } - for i := range participants { - if participants[i].ID == doer.ID { - continue - } else if com.IsSliceContainsStr(names, participants[i].Name) { - continue - } - - tos = append(tos, participants[i].Email) - names = append(names, participants[i].Name) - } - if issue.Assignee != nil && issue.Assignee.ID != doer.ID { - if !com.IsSliceContainsStr(names, issue.Assignee.Name) { - tos = append(tos, issue.Assignee.Email) - names = append(names, issue.Assignee.Name) - } - } - mailer.SendIssueCommentMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos) - - // Mail mentioned people and exclude watchers. - names = append(names, doer.Name) - tos = make([]string, 0, len(mentions)) // list of user names. - for i := range mentions { - if com.IsSliceContainsStr(names, mentions[i]) { - continue - } - - tos = append(tos, mentions[i]) - } - mailer.SendIssueMentionMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), GetUserEmailsByNames(tos)) - return nil -} - -// MailParticipants sends new issue thread created emails to repository watchers -// and mentioned people. -func (issue *Issue) MailParticipants() (err error) { - mentions := markup.FindAllMentions(issue.Content) - if err = updateIssueMentions(x, issue.ID, mentions); err != nil { - return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err) - } - - if err = mailIssueCommentToParticipants(issue, issue.Poster, mentions); err != nil { - log.Error(2, "mailIssueCommentToParticipants: %v", err) - } - - return nil -} diff --git a/models/login_source.go b/models/login_source.go deleted file mode 100644 index 49601a77..00000000 --- a/models/login_source.go +++ /dev/null @@ -1,866 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -// FIXME: Put this file into its own package and separate into different files based on login sources. -package models - -import ( - "crypto/tls" - "fmt" - "net/smtp" - "net/textproto" - "os" - "path" - "strings" - "sync" - "time" - - "github.com/go-macaron/binding" - "github.com/json-iterator/go" - "github.com/unknwon/com" - log "gopkg.in/clog.v1" - "gopkg.in/ini.v1" - "xorm.io/core" - "xorm.io/xorm" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/auth/github" - "gogs.io/gogs/pkg/auth/ldap" - "gogs.io/gogs/pkg/auth/pam" - "gogs.io/gogs/pkg/setting" -) - -type LoginType int - -// Note: new type must append to the end of list to maintain compatibility. -const ( - LOGIN_NOTYPE LoginType = iota - LOGIN_PLAIN // 1 - LOGIN_LDAP // 2 - LOGIN_SMTP // 3 - LOGIN_PAM // 4 - LOGIN_DLDAP // 5 - LOGIN_GITHUB // 6 -) - -var LoginNames = map[LoginType]string{ - LOGIN_LDAP: "LDAP (via BindDN)", - LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind - LOGIN_SMTP: "SMTP", - LOGIN_PAM: "PAM", - LOGIN_GITHUB: "GitHub", -} - -var SecurityProtocolNames = map[ldap.SecurityProtocol]string{ - ldap.SECURITY_PROTOCOL_UNENCRYPTED: "Unencrypted", - ldap.SECURITY_PROTOCOL_LDAPS: "LDAPS", - ldap.SECURITY_PROTOCOL_START_TLS: "StartTLS", -} - -// Ensure structs implemented interface. -var ( - _ core.Conversion = &LDAPConfig{} - _ core.Conversion = &SMTPConfig{} - _ core.Conversion = &PAMConfig{} - _ core.Conversion = &GitHubConfig{} -) - -type LDAPConfig struct { - *ldap.Source `ini:"config"` -} - -func (cfg *LDAPConfig) FromDB(bs []byte) error { - return jsoniter.Unmarshal(bs, &cfg) -} - -func (cfg *LDAPConfig) ToDB() ([]byte, error) { - return jsoniter.Marshal(cfg) -} - -func (cfg *LDAPConfig) SecurityProtocolName() string { - return SecurityProtocolNames[cfg.SecurityProtocol] -} - -type SMTPConfig struct { - Auth string - Host string - Port int - AllowedDomains string `xorm:"TEXT"` - TLS bool `ini:"tls"` - SkipVerify bool -} - -func (cfg *SMTPConfig) FromDB(bs []byte) error { - return jsoniter.Unmarshal(bs, cfg) -} - -func (cfg *SMTPConfig) ToDB() ([]byte, error) { - return jsoniter.Marshal(cfg) -} - -type PAMConfig struct { - ServiceName string // PAM service (e.g. system-auth) -} - -func (cfg *PAMConfig) FromDB(bs []byte) error { - return jsoniter.Unmarshal(bs, &cfg) -} - -func (cfg *PAMConfig) ToDB() ([]byte, error) { - return jsoniter.Marshal(cfg) -} - -type GitHubConfig struct { - APIEndpoint string // GitHub service (e.g. https://api.github.com/) -} - -func (cfg *GitHubConfig) FromDB(bs []byte) error { - return jsoniter.Unmarshal(bs, &cfg) -} - -func (cfg *GitHubConfig) ToDB() ([]byte, error) { - return jsoniter.Marshal(cfg) -} - -// AuthSourceFile contains information of an authentication source file. -type AuthSourceFile struct { - abspath string - file *ini.File -} - -// SetGeneral sets new value to the given key in the general (default) section. -func (f *AuthSourceFile) SetGeneral(name, value string) { - f.file.Section("").Key(name).SetValue(value) -} - -// SetConfig sets new values to the "config" section. -func (f *AuthSourceFile) SetConfig(cfg core.Conversion) error { - return f.file.Section("config").ReflectFrom(cfg) -} - -// Save writes updates into file system. -func (f *AuthSourceFile) Save() error { - return f.file.SaveTo(f.abspath) -} - -// LoginSource represents an external way for authorizing users. -type LoginSource struct { - ID int64 - Type LoginType - Name string `xorm:"UNIQUE"` - IsActived bool `xorm:"NOT NULL DEFAULT false"` - IsDefault bool `xorm:"DEFAULT false"` - Cfg core.Conversion `xorm:"TEXT"` - - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-"` - UpdatedUnix int64 - - LocalFile *AuthSourceFile `xorm:"-" json:"-"` -} - -func (s *LoginSource) BeforeInsert() { - s.CreatedUnix = time.Now().Unix() - s.UpdatedUnix = s.CreatedUnix -} - -func (s *LoginSource) BeforeUpdate() { - s.UpdatedUnix = time.Now().Unix() -} - -// Cell2Int64 converts a xorm.Cell type to int64, -// and handles possible irregular cases. -func Cell2Int64(val xorm.Cell) int64 { - switch (*val).(type) { - case []uint8: - log.Trace("Cell2Int64 ([]uint8): %v", *val) - return com.StrTo(string((*val).([]uint8))).MustInt64() - } - return (*val).(int64) -} - -func (s *LoginSource) BeforeSet(colName string, val xorm.Cell) { - switch colName { - case "type": - switch LoginType(Cell2Int64(val)) { - case LOGIN_LDAP, LOGIN_DLDAP: - s.Cfg = new(LDAPConfig) - case LOGIN_SMTP: - s.Cfg = new(SMTPConfig) - case LOGIN_PAM: - s.Cfg = new(PAMConfig) - case LOGIN_GITHUB: - s.Cfg = new(GitHubConfig) - default: - panic("unrecognized login source type: " + com.ToStr(*val)) - } - } -} - -func (s *LoginSource) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - s.Created = time.Unix(s.CreatedUnix, 0).Local() - case "updated_unix": - s.Updated = time.Unix(s.UpdatedUnix, 0).Local() - } -} - -func (s *LoginSource) TypeName() string { - return LoginNames[s.Type] -} - -func (s *LoginSource) IsLDAP() bool { - return s.Type == LOGIN_LDAP -} - -func (s *LoginSource) IsDLDAP() bool { - return s.Type == LOGIN_DLDAP -} - -func (s *LoginSource) IsSMTP() bool { - return s.Type == LOGIN_SMTP -} - -func (s *LoginSource) IsPAM() bool { - return s.Type == LOGIN_PAM -} - -func (s *LoginSource) IsGitHub() bool { - return s.Type == LOGIN_GITHUB -} - -func (s *LoginSource) HasTLS() bool { - return ((s.IsLDAP() || s.IsDLDAP()) && - s.LDAP().SecurityProtocol > ldap.SECURITY_PROTOCOL_UNENCRYPTED) || - s.IsSMTP() -} - -func (s *LoginSource) UseTLS() bool { - switch s.Type { - case LOGIN_LDAP, LOGIN_DLDAP: - return s.LDAP().SecurityProtocol != ldap.SECURITY_PROTOCOL_UNENCRYPTED - case LOGIN_SMTP: - return s.SMTP().TLS - } - - return false -} - -func (s *LoginSource) SkipVerify() bool { - switch s.Type { - case LOGIN_LDAP, LOGIN_DLDAP: - return s.LDAP().SkipVerify - case LOGIN_SMTP: - return s.SMTP().SkipVerify - } - - return false -} - -func (s *LoginSource) LDAP() *LDAPConfig { - return s.Cfg.(*LDAPConfig) -} - -func (s *LoginSource) SMTP() *SMTPConfig { - return s.Cfg.(*SMTPConfig) -} - -func (s *LoginSource) PAM() *PAMConfig { - return s.Cfg.(*PAMConfig) -} - -func (s *LoginSource) GitHub() *GitHubConfig { - return s.Cfg.(*GitHubConfig) -} - -func CreateLoginSource(source *LoginSource) error { - has, err := x.Get(&LoginSource{Name: source.Name}) - if err != nil { - return err - } else if has { - return ErrLoginSourceAlreadyExist{source.Name} - } - - _, err = x.Insert(source) - if err != nil { - return err - } else if source.IsDefault { - return ResetNonDefaultLoginSources(source) - } - return nil -} - -// LoginSources returns all login sources defined. -func LoginSources() ([]*LoginSource, error) { - sources := make([]*LoginSource, 0, 2) - if err := x.Find(&sources); err != nil { - return nil, err - } - - return append(sources, localLoginSources.List()...), nil -} - -// ActivatedLoginSources returns login sources that are currently activated. -func ActivatedLoginSources() ([]*LoginSource, error) { - sources := make([]*LoginSource, 0, 2) - if err := x.Where("is_actived = ?", true).Find(&sources); err != nil { - return nil, fmt.Errorf("find activated login sources: %v", err) - } - return append(sources, localLoginSources.ActivatedList()...), nil -} - -// GetLoginSourceByID returns login source by given ID. -func GetLoginSourceByID(id int64) (*LoginSource, error) { - source := new(LoginSource) - has, err := x.Id(id).Get(source) - if err != nil { - return nil, err - } else if !has { - return localLoginSources.GetLoginSourceByID(id) - } - return source, nil -} - -// ResetNonDefaultLoginSources clean other default source flag -func ResetNonDefaultLoginSources(source *LoginSource) error { - // update changes to DB - if _, err := x.NotIn("id", []int64{source.ID}).Cols("is_default").Update(&LoginSource{IsDefault: false}); err != nil { - return err - } - // write changes to local authentications - for i := range localLoginSources.sources { - if localLoginSources.sources[i].LocalFile != nil && localLoginSources.sources[i].ID != source.ID { - localLoginSources.sources[i].LocalFile.SetGeneral("is_default", "false") - if err := localLoginSources.sources[i].LocalFile.SetConfig(source.Cfg); err != nil { - return fmt.Errorf("LocalFile.SetConfig: %v", err) - } else if err = localLoginSources.sources[i].LocalFile.Save(); err != nil { - return fmt.Errorf("LocalFile.Save: %v", err) - } - } - } - // flush memory so that web page can show the same behaviors - localLoginSources.UpdateLoginSource(source) - return nil -} - -// UpdateLoginSource updates information of login source to database or local file. -func UpdateLoginSource(source *LoginSource) error { - if source.LocalFile == nil { - if _, err := x.Id(source.ID).AllCols().Update(source); err != nil { - return err - } else { - return ResetNonDefaultLoginSources(source) - } - - } - - source.LocalFile.SetGeneral("name", source.Name) - source.LocalFile.SetGeneral("is_activated", com.ToStr(source.IsActived)) - source.LocalFile.SetGeneral("is_default", com.ToStr(source.IsDefault)) - if err := source.LocalFile.SetConfig(source.Cfg); err != nil { - return fmt.Errorf("LocalFile.SetConfig: %v", err) - } else if err = source.LocalFile.Save(); err != nil { - return fmt.Errorf("LocalFile.Save: %v", err) - } - return ResetNonDefaultLoginSources(source) -} - -func DeleteSource(source *LoginSource) error { - count, err := x.Count(&User{LoginSource: source.ID}) - if err != nil { - return err - } else if count > 0 { - return ErrLoginSourceInUse{source.ID} - } - _, err = x.Id(source.ID).Delete(new(LoginSource)) - return err -} - -// CountLoginSources returns total number of login sources. -func CountLoginSources() int64 { - count, _ := x.Count(new(LoginSource)) - return count + int64(localLoginSources.Len()) -} - -// LocalLoginSources contains authentication sources configured and loaded from local files. -// Calling its methods is thread-safe; otherwise, please maintain the mutex accordingly. -type LocalLoginSources struct { - sync.RWMutex - sources []*LoginSource -} - -func (s *LocalLoginSources) Len() int { - return len(s.sources) -} - -// List returns full clone of login sources. -func (s *LocalLoginSources) List() []*LoginSource { - s.RLock() - defer s.RUnlock() - - list := make([]*LoginSource, s.Len()) - for i := range s.sources { - list[i] = &LoginSource{} - *list[i] = *s.sources[i] - } - return list -} - -// ActivatedList returns clone of activated login sources. -func (s *LocalLoginSources) ActivatedList() []*LoginSource { - s.RLock() - defer s.RUnlock() - - list := make([]*LoginSource, 0, 2) - for i := range s.sources { - if !s.sources[i].IsActived { - continue - } - source := &LoginSource{} - *source = *s.sources[i] - list = append(list, source) - } - return list -} - -// GetLoginSourceByID returns a clone of login source by given ID. -func (s *LocalLoginSources) GetLoginSourceByID(id int64) (*LoginSource, error) { - s.RLock() - defer s.RUnlock() - - for i := range s.sources { - if s.sources[i].ID == id { - source := &LoginSource{} - *source = *s.sources[i] - return source, nil - } - } - - return nil, errors.LoginSourceNotExist{id} -} - -// UpdateLoginSource updates in-memory copy of the authentication source. -func (s *LocalLoginSources) UpdateLoginSource(source *LoginSource) { - s.Lock() - defer s.Unlock() - - source.Updated = time.Now() - for i := range s.sources { - if s.sources[i].ID == source.ID { - *s.sources[i] = *source - } else if source.IsDefault { - s.sources[i].IsDefault = false - } - } -} - -var localLoginSources = &LocalLoginSources{} - -// LoadAuthSources loads authentication sources from local files -// and converts them into login sources. -func LoadAuthSources() { - authdPath := path.Join(setting.CustomPath, "conf/auth.d") - if !com.IsDir(authdPath) { - return - } - - paths, err := com.GetFileListBySuffix(authdPath, ".conf") - if err != nil { - log.Fatal(2, "Failed to list authentication sources: %v", err) - } - - localLoginSources.sources = make([]*LoginSource, 0, len(paths)) - - for _, fpath := range paths { - authSource, err := ini.Load(fpath) - if err != nil { - log.Fatal(2, "Failed to load authentication source: %v", err) - } - authSource.NameMapper = ini.TitleUnderscore - - // Set general attributes - s := authSource.Section("") - loginSource := &LoginSource{ - ID: s.Key("id").MustInt64(), - Name: s.Key("name").String(), - IsActived: s.Key("is_activated").MustBool(), - IsDefault: s.Key("is_default").MustBool(), - LocalFile: &AuthSourceFile{ - abspath: fpath, - file: authSource, - }, - } - - fi, err := os.Stat(fpath) - if err != nil { - log.Fatal(2, "Failed to load authentication source: %v", err) - } - loginSource.Updated = fi.ModTime() - - // Parse authentication source file - authType := s.Key("type").String() - switch authType { - case "ldap_bind_dn": - loginSource.Type = LOGIN_LDAP - loginSource.Cfg = &LDAPConfig{} - case "ldap_simple_auth": - loginSource.Type = LOGIN_DLDAP - loginSource.Cfg = &LDAPConfig{} - case "smtp": - loginSource.Type = LOGIN_SMTP - loginSource.Cfg = &SMTPConfig{} - case "pam": - loginSource.Type = LOGIN_PAM - loginSource.Cfg = &PAMConfig{} - case "github": - loginSource.Type = LOGIN_GITHUB - loginSource.Cfg = &GitHubConfig{} - default: - log.Fatal(2, "Failed to load authentication source: unknown type '%s'", authType) - } - - if err = authSource.Section("config").MapTo(loginSource.Cfg); err != nil { - log.Fatal(2, "Failed to parse authentication source 'config': %v", err) - } - - localLoginSources.sources = append(localLoginSources.sources, loginSource) - } -} - -// .____ ________ _____ __________ -// | | \______ \ / _ \\______ \ -// | | | | \ / /_\ \| ___/ -// | |___ | ` \/ | \ | -// |_______ \/_______ /\____|__ /____| -// \/ \/ \/ - -func composeFullName(firstname, surname, username string) string { - switch { - case len(firstname) == 0 && len(surname) == 0: - return username - case len(firstname) == 0: - return surname - case len(surname) == 0: - return firstname - default: - return firstname + " " + surname - } -} - -// LoginViaLDAP queries if login/password is valid against the LDAP directory pool, -// and create a local user if success when enabled. -func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) { - username, fn, sn, mail, isAdmin, succeed := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LOGIN_DLDAP) - if !succeed { - // User not in LDAP, do nothing - return nil, errors.UserNotExist{0, login} - } - - if !autoRegister { - return user, nil - } - - // Fallback. - if len(username) == 0 { - username = login - } - // Validate username make sure it satisfies requirement. - if binding.AlphaDashDotPattern.MatchString(username) { - return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", username) - } - - if len(mail) == 0 { - mail = fmt.Sprintf("%s@localhost", username) - } - - user = &User{ - LowerName: strings.ToLower(username), - Name: username, - FullName: composeFullName(fn, sn, username), - Email: mail, - LoginType: source.Type, - LoginSource: source.ID, - LoginName: login, - IsActive: true, - IsAdmin: isAdmin, - } - - ok, err := IsUserExist(0, user.Name) - if err != nil { - return user, err - } - - if ok { - return user, UpdateUser(user) - } - - return user, CreateUser(user) -} - -// _________ __________________________ -// / _____/ / \__ ___/\______ \ -// \_____ \ / \ / \| | | ___/ -// / \/ Y \ | | | -// /_______ /\____|__ /____| |____| -// \/ \/ - -type smtpLoginAuth struct { - username, password string -} - -func (auth *smtpLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { - return "LOGIN", []byte(auth.username), nil -} - -func (auth *smtpLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) { - if more { - switch string(fromServer) { - case "Username:": - return []byte(auth.username), nil - case "Password:": - return []byte(auth.password), nil - } - } - return nil, nil -} - -const ( - SMTP_PLAIN = "PLAIN" - SMTP_LOGIN = "LOGIN" -) - -var SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN} - -func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error { - c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)) - if err != nil { - return err - } - defer c.Close() - - if err = c.Hello("gogs"); err != nil { - return err - } - - if cfg.TLS { - if ok, _ := c.Extension("STARTTLS"); ok { - if err = c.StartTLS(&tls.Config{ - InsecureSkipVerify: cfg.SkipVerify, - ServerName: cfg.Host, - }); err != nil { - return err - } - } else { - return errors.New("SMTP server unsupports TLS") - } - } - - if ok, _ := c.Extension("AUTH"); ok { - if err = c.Auth(a); err != nil { - return err - } - return nil - } - return errors.New("Unsupported SMTP authentication method") -} - -// LoginViaSMTP queries if login/password is valid against the SMTP, -// and create a local user if success when enabled. -func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) { - // Verify allowed domains. - if len(cfg.AllowedDomains) > 0 { - idx := strings.Index(login, "@") - if idx == -1 { - return nil, errors.UserNotExist{0, login} - } else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), login[idx+1:]) { - return nil, errors.UserNotExist{0, login} - } - } - - var auth smtp.Auth - if cfg.Auth == SMTP_PLAIN { - auth = smtp.PlainAuth("", login, password, cfg.Host) - } else if cfg.Auth == SMTP_LOGIN { - auth = &smtpLoginAuth{login, password} - } else { - return nil, errors.New("Unsupported SMTP authentication type") - } - - if err := SMTPAuth(auth, cfg); err != nil { - // Check standard error format first, - // then fallback to worse case. - tperr, ok := err.(*textproto.Error) - if (ok && tperr.Code == 535) || - strings.Contains(err.Error(), "Username and Password not accepted") { - return nil, errors.UserNotExist{0, login} - } - return nil, err - } - - if !autoRegister { - return user, nil - } - - username := login - idx := strings.Index(login, "@") - if idx > -1 { - username = login[:idx] - } - - user = &User{ - LowerName: strings.ToLower(username), - Name: strings.ToLower(username), - Email: login, - Passwd: password, - LoginType: LOGIN_SMTP, - LoginSource: sourceID, - LoginName: login, - IsActive: true, - } - return user, CreateUser(user) -} - -// __________ _____ _____ -// \______ \/ _ \ / \ -// | ___/ /_\ \ / \ / \ -// | | / | \/ Y \ -// |____| \____|__ /\____|__ / -// \/ \/ - -// LoginViaPAM queries if login/password is valid against the PAM, -// and create a local user if success when enabled. -func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) { - if err := pam.PAMAuth(cfg.ServiceName, login, password); err != nil { - if strings.Contains(err.Error(), "Authentication failure") { - return nil, errors.UserNotExist{0, login} - } - return nil, err - } - - if !autoRegister { - return user, nil - } - - user = &User{ - LowerName: strings.ToLower(login), - Name: login, - Email: login, - Passwd: password, - LoginType: LOGIN_PAM, - LoginSource: sourceID, - LoginName: login, - IsActive: true, - } - return user, CreateUser(user) -} - -//________.__ __ ___ ___ ___. -/// _____/|__|/ |_ / | \ __ _\_ |__ -/// \ ___| \ __\/ ~ \ | \ __ \ -//\ \_\ \ || | \ Y / | / \_\ \ -//\______ /__||__| \___|_ /|____/|___ / -//\/ \/ \/ - -func LoginViaGitHub(user *User, login, password string, sourceID int64, cfg *GitHubConfig, autoRegister bool) (*User, error) { - fullname, email, url, location, err := github.Authenticate(cfg.APIEndpoint, login, password) - if err != nil { - if strings.Contains(err.Error(), "401") { - return nil, errors.UserNotExist{0, login} - } - return nil, err - } - - if !autoRegister { - return user, nil - } - user = &User{ - LowerName: strings.ToLower(login), - Name: login, - FullName: fullname, - Email: email, - Website: url, - Passwd: password, - LoginType: LOGIN_GITHUB, - LoginSource: sourceID, - LoginName: login, - IsActive: true, - Location: location, - } - return user, CreateUser(user) -} - -func remoteUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) { - if !source.IsActived { - return nil, errors.LoginSourceNotActivated{source.ID} - } - - switch source.Type { - case LOGIN_LDAP, LOGIN_DLDAP: - return LoginViaLDAP(user, login, password, source, autoRegister) - case LOGIN_SMTP: - return LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister) - case LOGIN_PAM: - return LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister) - case LOGIN_GITHUB: - return LoginViaGitHub(user, login, password, source.ID, source.Cfg.(*GitHubConfig), autoRegister) - } - - return nil, errors.InvalidLoginSourceType{source.Type} -} - -// UserLogin validates user name and password via given login source ID. -// If the loginSourceID is negative, it will abort login process if user is not found. -func UserLogin(username, password string, loginSourceID int64) (*User, error) { - var user *User - if strings.Contains(username, "@") { - user = &User{Email: strings.ToLower(username)} - } else { - user = &User{LowerName: strings.ToLower(username)} - } - - hasUser, err := x.Get(user) - if err != nil { - return nil, fmt.Errorf("get user record: %v", err) - } - - if hasUser { - // Note: This check is unnecessary but to reduce user confusion at login page - // and make it more consistent at user's perspective. - if loginSourceID >= 0 && user.LoginSource != loginSourceID { - return nil, errors.LoginSourceMismatch{loginSourceID, user.LoginSource} - } - - // Validate password hash fetched from database for local accounts - if user.LoginType == LOGIN_NOTYPE || - user.LoginType == LOGIN_PLAIN { - if user.ValidatePassword(password) { - return user, nil - } - - return nil, errors.UserNotExist{user.ID, user.Name} - } - - // Remote login to the login source the user is associated with - source, err := GetLoginSourceByID(user.LoginSource) - if err != nil { - return nil, err - } - - return remoteUserLogin(user, user.LoginName, password, source, false) - } - - // Non-local login source is always greater than 0 - if loginSourceID <= 0 { - return nil, errors.UserNotExist{-1, username} - } - - source, err := GetLoginSourceByID(loginSourceID) - if err != nil { - return nil, err - } - - return remoteUserLogin(nil, username, password, source, true) -} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go deleted file mode 100644 index eb73a3b9..00000000 --- a/models/migrations/migrations.go +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "fmt" - "strings" - "time" - - "github.com/unknwon/com" - "xorm.io/xorm" - log "gopkg.in/clog.v1" - - "gogs.io/gogs/pkg/tool" -) - -const _MIN_DB_VER = 10 - -type Migration interface { - Description() string - Migrate(*xorm.Engine) error -} - -type migration struct { - description string - migrate func(*xorm.Engine) error -} - -func NewMigration(desc string, fn func(*xorm.Engine) error) Migration { - return &migration{desc, fn} -} - -func (m *migration) Description() string { - return m.description -} - -func (m *migration) Migrate(x *xorm.Engine) error { - return m.migrate(x) -} - -// The version table. Should have only one row with id==1 -type Version struct { - ID int64 - Version int64 -} - -// This is a sequence of migrations. Add new migrations to the bottom of the list. -// If you want to "retire" a migration, remove it from the top of the list and -// update _MIN_VER_DB accordingly -var migrations = []Migration{ - // v0 -> v4 : before 0.6.0 -> last support 0.7.33 - // v4 -> v10: before 0.7.0 -> last support 0.9.141 - NewMigration("generate rands and salt for organizations", generateOrgRandsAndSalt), // V10 -> V11:v0.8.5 - NewMigration("convert date to unix timestamp", convertDateToUnix), // V11 -> V12:v0.9.2 - NewMigration("convert LDAP UseSSL option to SecurityProtocol", ldapUseSSLToSecurityProtocol), // V12 -> V13:v0.9.37 - - // v13 -> v14:v0.9.87 - NewMigration("set comment updated with created", setCommentUpdatedWithCreated), - // v14 -> v15:v0.9.147 - NewMigration("generate and migrate Git hooks", generateAndMigrateGitHooks), - // v15 -> v16:v0.10.16 - NewMigration("update repository sizes", updateRepositorySizes), - // v16 -> v17:v0.10.31 - NewMigration("remove invalid protect branch whitelist", removeInvalidProtectBranchWhitelist), - // v17 -> v18:v0.11.48 - NewMigration("store long text in repository description field", updateRepositoryDescriptionField), - // v18 -> v19:v0.11.55 - NewMigration("clean unlinked webhook and hook_tasks", cleanUnlinkedWebhookAndHookTasks), -} - -// Migrate database to current version -func Migrate(x *xorm.Engine) error { - if err := x.Sync(new(Version)); err != nil { - return fmt.Errorf("sync: %v", err) - } - - currentVersion := &Version{ID: 1} - has, err := x.Get(currentVersion) - if err != nil { - return fmt.Errorf("get: %v", err) - } else if !has { - // If the version record does not exist we think - // it is a fresh installation and we can skip all migrations. - currentVersion.ID = 0 - currentVersion.Version = int64(_MIN_DB_VER + len(migrations)) - - if _, err = x.InsertOne(currentVersion); err != nil { - return fmt.Errorf("insert: %v", err) - } - } - - v := currentVersion.Version - if _MIN_DB_VER > v { - log.Fatal(0, ` -Hi there, thank you for using Gogs for so long! -However, Gogs has stopped supporting auto-migration from your previously installed version. -But the good news is, it's very easy to fix this problem! -You can migrate your older database using a previous release, then you can upgrade to the newest version. - -Please save following instructions to somewhere and start working: - -- If you were using below 0.6.0 (e.g. 0.5.x), download last supported archive from following link: - https://gogs.io/gogs/releases/tag/v0.7.33 -- If you were using below 0.7.0 (e.g. 0.6.x), download last supported archive from following link: - https://gogs.io/gogs/releases/tag/v0.9.141 - -Once finished downloading, - -1. Extract the archive and to upgrade steps as usual. -2. Run it once. To verify, you should see some migration traces. -3. Once it starts web server successfully, stop it. -4. Now it's time to put back the release archive you originally intent to upgrade. -5. Enjoy! - -In case you're stilling getting this notice, go through instructions again until it disappears.`) - return nil - } - - 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 { - return fmt.Errorf("do migrate: %v", err) - } - currentVersion.Version = v + int64(i) + 1 - if _, err = x.Id(1).Update(currentVersion); err != nil { - return err - } - } - return nil -} - -func generateOrgRandsAndSalt(x *xorm.Engine) (err error) { - type User struct { - ID int64 `xorm:"pk autoincr"` - Rands string `xorm:"VARCHAR(10)"` - Salt string `xorm:"VARCHAR(10)"` - } - - orgs := make([]*User, 0, 10) - if err = x.Where("type=1").And("rands=''").Find(&orgs); err != nil { - return fmt.Errorf("select all organizations: %v", err) - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - for _, org := range orgs { - if org.Rands, err = tool.RandomString(10); err != nil { - return err - } - if org.Salt, err = tool.RandomString(10); err != nil { - return err - } - if _, err = sess.ID(org.ID).Update(org); err != nil { - return err - } - } - - return sess.Commit() -} - -type TAction struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 -} - -func (t *TAction) TableName() string { return "action" } - -type TNotice struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 -} - -func (t *TNotice) TableName() string { return "notice" } - -type TComment struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 -} - -func (t *TComment) TableName() string { return "comment" } - -type TIssue struct { - ID int64 `xorm:"pk autoincr"` - DeadlineUnix int64 - CreatedUnix int64 - UpdatedUnix int64 -} - -func (t *TIssue) TableName() string { return "issue" } - -type TMilestone struct { - ID int64 `xorm:"pk autoincr"` - DeadlineUnix int64 - ClosedDateUnix int64 -} - -func (t *TMilestone) TableName() string { return "milestone" } - -type TAttachment struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 -} - -func (t *TAttachment) TableName() string { return "attachment" } - -type TLoginSource struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 - UpdatedUnix int64 -} - -func (t *TLoginSource) TableName() string { return "login_source" } - -type TPull struct { - ID int64 `xorm:"pk autoincr"` - MergedUnix int64 -} - -func (t *TPull) TableName() string { return "pull_request" } - -type TRelease struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 -} - -func (t *TRelease) TableName() string { return "release" } - -type TRepo struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 - UpdatedUnix int64 -} - -func (t *TRepo) TableName() string { return "repository" } - -type TMirror struct { - ID int64 `xorm:"pk autoincr"` - UpdatedUnix int64 - NextUpdateUnix int64 -} - -func (t *TMirror) TableName() string { return "mirror" } - -type TPublicKey struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 - UpdatedUnix int64 -} - -func (t *TPublicKey) TableName() string { return "public_key" } - -type TDeployKey struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 - UpdatedUnix int64 -} - -func (t *TDeployKey) TableName() string { return "deploy_key" } - -type TAccessToken struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 - UpdatedUnix int64 -} - -func (t *TAccessToken) TableName() string { return "access_token" } - -type TUser struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 - UpdatedUnix int64 -} - -func (t *TUser) TableName() string { return "user" } - -type TWebhook struct { - ID int64 `xorm:"pk autoincr"` - CreatedUnix int64 - UpdatedUnix int64 -} - -func (t *TWebhook) TableName() string { return "webhook" } - -func convertDateToUnix(x *xorm.Engine) (err error) { - log.Info("This migration could take up to minutes, please be patient.") - type Bean struct { - ID int64 `xorm:"pk autoincr"` - Created time.Time - Updated time.Time - Merged time.Time - Deadline time.Time - ClosedDate time.Time - NextUpdate time.Time - } - - var tables = []struct { - name string - cols []string - bean interface{} - }{ - {"action", []string{"created"}, new(TAction)}, - {"notice", []string{"created"}, new(TNotice)}, - {"comment", []string{"created"}, new(TComment)}, - {"issue", []string{"deadline", "created", "updated"}, new(TIssue)}, - {"milestone", []string{"deadline", "closed_date"}, new(TMilestone)}, - {"attachment", []string{"created"}, new(TAttachment)}, - {"login_source", []string{"created", "updated"}, new(TLoginSource)}, - {"pull_request", []string{"merged"}, new(TPull)}, - {"release", []string{"created"}, new(TRelease)}, - {"repository", []string{"created", "updated"}, new(TRepo)}, - {"mirror", []string{"updated", "next_update"}, new(TMirror)}, - {"public_key", []string{"created", "updated"}, new(TPublicKey)}, - {"deploy_key", []string{"created", "updated"}, new(TDeployKey)}, - {"access_token", []string{"created", "updated"}, new(TAccessToken)}, - {"user", []string{"created", "updated"}, new(TUser)}, - {"webhook", []string{"created", "updated"}, new(TWebhook)}, - } - - for _, table := range tables { - log.Info("Converting table: %s", table.name) - if err = x.Sync2(table.bean); err != nil { - return fmt.Errorf("Sync [table: %s]: %v", table.name, err) - } - - offset := 0 - for { - beans := make([]*Bean, 0, 100) - if err = x.Sql(fmt.Sprintf("SELECT * FROM `%s` ORDER BY id ASC LIMIT 100 OFFSET %d", - table.name, offset)).Find(&beans); err != nil { - return fmt.Errorf("select beans [table: %s, offset: %d]: %v", table.name, offset, err) - } - log.Trace("Table [%s]: offset: %d, beans: %d", table.name, offset, len(beans)) - if len(beans) == 0 { - break - } - offset += 100 - - baseSQL := "UPDATE `" + table.name + "` SET " - for _, bean := range beans { - valSQLs := make([]string, 0, len(table.cols)) - for _, col := range table.cols { - fieldSQL := "" - fieldSQL += col + "_unix = " - - switch col { - case "deadline": - if bean.Deadline.IsZero() { - continue - } - fieldSQL += com.ToStr(bean.Deadline.Unix()) - case "created": - fieldSQL += com.ToStr(bean.Created.Unix()) - case "updated": - fieldSQL += com.ToStr(bean.Updated.Unix()) - case "closed_date": - fieldSQL += com.ToStr(bean.ClosedDate.Unix()) - case "merged": - fieldSQL += com.ToStr(bean.Merged.Unix()) - case "next_update": - fieldSQL += com.ToStr(bean.NextUpdate.Unix()) - } - - valSQLs = append(valSQLs, fieldSQL) - } - - if len(valSQLs) == 0 { - continue - } - - if _, err = x.Exec(baseSQL + strings.Join(valSQLs, ",") + " WHERE id = " + com.ToStr(bean.ID)); err != nil { - return fmt.Errorf("update bean [table: %s, id: %d]: %v", table.name, bean.ID, err) - } - } - } - } - - return nil -} diff --git a/models/migrations/v13.go b/models/migrations/v13.go deleted file mode 100644 index 1097956e..00000000 --- a/models/migrations/v13.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "fmt" - "strings" - - "github.com/unknwon/com" - "xorm.io/xorm" - "github.com/json-iterator/go" -) - -func ldapUseSSLToSecurityProtocol(x *xorm.Engine) error { - results, err := x.Query("SELECT `id`,`cfg` FROM `login_source` WHERE `type` = 2 OR `type` = 5") - if err != nil { - if strings.Contains(err.Error(), "no such column") { - return nil - } - return fmt.Errorf("select LDAP login sources: %v", err) - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - for _, result := range results { - cfg := map[string]interface{}{} - if err = jsoniter.Unmarshal(result["cfg"], &cfg); err != nil { - return fmt.Errorf("unmarshal JSON config: %v", err) - } - if com.ToStr(cfg["UseSSL"]) == "true" { - cfg["SecurityProtocol"] = 1 // LDAPS - } - delete(cfg, "UseSSL") - - data, err := jsoniter.Marshal(&cfg) - if err != nil { - return fmt.Errorf("marshal JSON config: %v", err) - } - - if _, err = sess.Exec("UPDATE `login_source` SET `cfg`=? WHERE `id`=?", - string(data), com.StrTo(result["id"]).MustInt64()); err != nil { - return fmt.Errorf("update config column: %v", err) - } - } - return sess.Commit() -} diff --git a/models/migrations/v14.go b/models/migrations/v14.go deleted file mode 100644 index de8babed..00000000 --- a/models/migrations/v14.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "fmt" - - "xorm.io/xorm" -) - -func setCommentUpdatedWithCreated(x *xorm.Engine) (err error) { - type Comment struct { - UpdatedUnix int64 - } - - if err = x.Sync2(new(Comment)); err != nil { - return fmt.Errorf("Sync2: %v", err) - } else if _, err = x.Exec("UPDATE comment SET updated_unix = created_unix"); err != nil { - return fmt.Errorf("set update_unix: %v", err) - } - return nil -} diff --git a/models/migrations/v15.go b/models/migrations/v15.go deleted file mode 100644 index fb1214b6..00000000 --- a/models/migrations/v15.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/unknwon/com" - "xorm.io/xorm" - log "gopkg.in/clog.v1" - - "gogs.io/gogs/pkg/setting" -) - -func generateAndMigrateGitHooks(x *xorm.Engine) (err error) { - type Repository struct { - ID int64 - OwnerID int64 - Name string - } - type User struct { - ID int64 - Name string - } - var ( - hookNames = []string{"pre-receive", "update", "post-receive"} - hookTpls = []string{ - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - } - ) - - // Cleanup old update.log and http.log files. - filepath.Walk(setting.LogRootPath, func(path string, info os.FileInfo, err error) error { - if !info.IsDir() && - (strings.HasPrefix(filepath.Base(path), "update.log") || - strings.HasPrefix(filepath.Base(path), "http.log")) { - os.Remove(path) - } - return nil - }) - - return x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - if repo.Name == "." || repo.Name == ".." { - return nil - } - - user := new(User) - has, err := x.Where("id = ?", repo.OwnerID).Get(user) - if err != nil { - return fmt.Errorf("query owner of repository [repo_id: %d, owner_id: %d]: %v", repo.ID, repo.OwnerID, err) - } else if !has { - return nil - } - - repoBase := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) - repoPath := repoBase + ".git" - wikiPath := repoBase + ".wiki.git" - log.Trace("[%04d]: %s", idx, repoPath) - - // Note: we should not create hookDir here because update hook file should already exists inside this direcotry, - // if this directory does not exist, the current setup is not correct anyway. - hookDir := filepath.Join(repoPath, "hooks") - customHookDir := filepath.Join(repoPath, "custom_hooks") - wikiHookDir := filepath.Join(wikiPath, "hooks") - - for i, hookName := range hookNames { - oldHookPath := filepath.Join(hookDir, hookName) - newHookPath := filepath.Join(customHookDir, hookName) - - // Gogs didn't allow user to set custom update hook thus no migration for it. - // In case user runs this migration multiple times, and custom hook exists, - // we assume it's been migrated already. - if hookName != "update" && com.IsFile(oldHookPath) && !com.IsExist(customHookDir) { - os.MkdirAll(customHookDir, os.ModePerm) - if err = os.Rename(oldHookPath, newHookPath); err != nil { - return fmt.Errorf("move hook file to custom directory '%s' -> '%s': %v", oldHookPath, newHookPath, err) - } - } - - if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), os.ModePerm); err != nil { - return fmt.Errorf("write hook file '%s': %v", oldHookPath, err) - } - - if com.IsDir(wikiPath) { - os.MkdirAll(wikiHookDir, os.ModePerm) - wikiHookPath := filepath.Join(wikiHookDir, hookName) - if err = ioutil.WriteFile(wikiHookPath, []byte(hookTpls[i]), os.ModePerm); err != nil { - return fmt.Errorf("write wiki hook file '%s': %v", wikiHookPath, err) - } - } - } - return nil - }) -} diff --git a/models/migrations/v16.go b/models/migrations/v16.go deleted file mode 100644 index 389d1d62..00000000 --- a/models/migrations/v16.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "fmt" - "path/filepath" - "strings" - - "xorm.io/xorm" - log "gopkg.in/clog.v1" - - "github.com/gogs/git-module" - - "gogs.io/gogs/pkg/setting" -) - -func updateRepositorySizes(x *xorm.Engine) (err error) { - log.Info("This migration could take up to minutes, please be patient.") - type Repository struct { - ID int64 - OwnerID int64 - Name string - Size int64 - } - type User struct { - ID int64 - Name string - } - if err = x.Sync2(new(Repository)); err != nil { - return fmt.Errorf("Sync2: %v", err) - } - - // For the sake of SQLite3, we can't use x.Iterate here. - offset := 0 - for { - repos := make([]*Repository, 0, 10) - if err = x.Sql(fmt.Sprintf("SELECT * FROM `repository` ORDER BY id ASC LIMIT 10 OFFSET %d", offset)). - Find(&repos); err != nil { - return fmt.Errorf("select repos [offset: %d]: %v", offset, err) - } - log.Trace("Select [offset: %d, repos: %d]", offset, len(repos)) - if len(repos) == 0 { - break - } - offset += 10 - - for _, repo := range repos { - if repo.Name == "." || repo.Name == ".." { - continue - } - - user := new(User) - has, err := x.Where("id = ?", repo.OwnerID).Get(user) - if err != nil { - return fmt.Errorf("query owner of repository [repo_id: %d, owner_id: %d]: %v", repo.ID, repo.OwnerID, err) - } else if !has { - continue - } - - repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".git" - countObject, err := git.GetRepoSize(repoPath) - if err != nil { - log.Warn("GetRepoSize: %v", err) - continue - } - - repo.Size = countObject.Size + countObject.SizePack - if _, err = x.Id(repo.ID).Cols("size").Update(repo); err != nil { - return fmt.Errorf("update size: %v", err) - } - } - } - return nil -} diff --git a/models/migrations/v17.go b/models/migrations/v17.go deleted file mode 100644 index 279ddf25..00000000 --- a/models/migrations/v17.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "fmt" - - "xorm.io/xorm" -) - -func removeInvalidProtectBranchWhitelist(x *xorm.Engine) error { - exist, err := x.IsTableExist("protect_branch_whitelist") - if err != nil { - return fmt.Errorf("IsTableExist: %v", err) - } else if !exist { - return nil - } - _, err = x.Exec("DELETE FROM protect_branch_whitelist WHERE protect_branch_id = 0") - return err -} diff --git a/models/migrations/v18.go b/models/migrations/v18.go deleted file mode 100644 index b74a7ad2..00000000 --- a/models/migrations/v18.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "fmt" - - "xorm.io/xorm" - - "gogs.io/gogs/pkg/setting" -) - -func updateRepositoryDescriptionField(x *xorm.Engine) error { - exist, err := x.IsTableExist("repository") - if err != nil { - return fmt.Errorf("IsTableExist: %v", err) - } else if !exist { - return nil - } - switch { - case setting.UseMySQL: - _, err = x.Exec("ALTER TABLE `repository` MODIFY `description` VARCHAR(512);") - case setting.UseMSSQL: - _, err = x.Exec("ALTER TABLE `repository` ALTER COLUMN `description` VARCHAR(512);") - case setting.UsePostgreSQL: - _, err = x.Exec("ALTER TABLE `repository` ALTER COLUMN `description` TYPE VARCHAR(512);") - case setting.UseSQLite3: - // Sqlite3 uses TEXT type by default for any string type field. - // Keep this comment to mention that we don't missed any option. - } - return err -} diff --git a/models/migrations/v19.go b/models/migrations/v19.go deleted file mode 100644 index bae2e355..00000000 --- a/models/migrations/v19.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2018 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "xorm.io/xorm" -) - -func cleanUnlinkedWebhookAndHookTasks(x *xorm.Engine) error { - _, err := x.Exec(`DELETE FROM webhook WHERE repo_id NOT IN (SELECT id FROM repository);`) - if err != nil { - return err - } - _, err = x.Exec(`DELETE FROM hook_task WHERE repo_id NOT IN (SELECT id FROM repository);`) - return err -} diff --git a/models/milestone.go b/models/milestone.go deleted file mode 100644 index e30ca14f..00000000 --- a/models/milestone.go +++ /dev/null @@ -1,402 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "time" - - "xorm.io/xorm" - log "gopkg.in/clog.v1" - - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/pkg/setting" -) - -// Milestone represents a milestone of repository. -type Milestone struct { - ID int64 - RepoID int64 `xorm:"INDEX"` - Name string - Content string `xorm:"TEXT"` - RenderedContent string `xorm:"-" json:"-"` - IsClosed bool - NumIssues int - NumClosedIssues int - NumOpenIssues int `xorm:"-" json:"-"` - Completeness int // Percentage(1-100). - IsOverDue bool `xorm:"-" json:"-"` - - DeadlineString string `xorm:"-" json:"-"` - Deadline time.Time `xorm:"-" json:"-"` - DeadlineUnix int64 - ClosedDate time.Time `xorm:"-" json:"-"` - ClosedDateUnix int64 -} - -func (m *Milestone) BeforeInsert() { - m.DeadlineUnix = m.Deadline.Unix() -} - -func (m *Milestone) BeforeUpdate() { - if m.NumIssues > 0 { - m.Completeness = m.NumClosedIssues * 100 / m.NumIssues - } else { - m.Completeness = 0 - } - - m.DeadlineUnix = m.Deadline.Unix() - m.ClosedDateUnix = m.ClosedDate.Unix() -} - -func (m *Milestone) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "num_closed_issues": - m.NumOpenIssues = m.NumIssues - m.NumClosedIssues - - case "deadline_unix": - m.Deadline = time.Unix(m.DeadlineUnix, 0).Local() - if m.Deadline.Year() == 9999 { - return - } - - m.DeadlineString = m.Deadline.Format("2006-01-02") - if time.Now().Local().After(m.Deadline) { - m.IsOverDue = true - } - - case "closed_date_unix": - m.ClosedDate = time.Unix(m.ClosedDateUnix, 0).Local() - } -} - -// State returns string representation of milestone status. -func (m *Milestone) State() api.StateType { - if m.IsClosed { - return api.STATE_CLOSED - } - return api.STATE_OPEN -} - -func (m *Milestone) ChangeStatus(isClosed bool) error { - return ChangeMilestoneStatus(m, isClosed) -} - -func (m *Milestone) APIFormat() *api.Milestone { - apiMilestone := &api.Milestone{ - ID: m.ID, - State: m.State(), - Title: m.Name, - Description: m.Content, - OpenIssues: m.NumOpenIssues, - ClosedIssues: m.NumClosedIssues, - } - if m.IsClosed { - apiMilestone.Closed = &m.ClosedDate - } - if m.Deadline.Year() < 9999 { - apiMilestone.Deadline = &m.Deadline - } - return apiMilestone -} - -func (m *Milestone) CountIssues(isClosed, includePulls bool) int64 { - sess := x.Where("milestone_id = ?", m.ID).And("is_closed = ?", isClosed) - if !includePulls { - sess.And("is_pull = ?", false) - } - count, _ := sess.Count(new(Issue)) - return count -} - -// NewMilestone creates new milestone of repository. -func NewMilestone(m *Milestone) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Insert(m); err != nil { - return err - } - - if _, err = sess.Exec("UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID); err != nil { - return err - } - return sess.Commit() -} - -func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) { - m := &Milestone{ - ID: id, - RepoID: repoID, - } - has, err := e.Get(m) - if err != nil { - return nil, err - } else if !has { - return nil, ErrMilestoneNotExist{id, repoID} - } - return m, nil -} - -// GetWebhookByRepoID returns the milestone in a repository. -func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) { - return getMilestoneByRepoID(x, repoID, id) -} - -// GetMilestonesByRepoID returns all milestones of a repository. -func GetMilestonesByRepoID(repoID int64) ([]*Milestone, error) { - miles := make([]*Milestone, 0, 10) - return miles, x.Where("repo_id = ?", repoID).Find(&miles) -} - -// GetMilestones returns a list of milestones of given repository and status. -func GetMilestones(repoID int64, page int, isClosed bool) ([]*Milestone, error) { - miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) - sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed) - if page > 0 { - sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum) - } - return miles, sess.Find(&miles) -} - -func updateMilestone(e Engine, m *Milestone) error { - _, err := e.ID(m.ID).AllCols().Update(m) - return err -} - -// UpdateMilestone updates information of given milestone. -func UpdateMilestone(m *Milestone) error { - return updateMilestone(x, m) -} - -func countRepoMilestones(e Engine, repoID int64) int64 { - count, _ := e.Where("repo_id=?", repoID).Count(new(Milestone)) - return count -} - -// CountRepoMilestones returns number of milestones in given repository. -func CountRepoMilestones(repoID int64) int64 { - return countRepoMilestones(x, repoID) -} - -func countRepoClosedMilestones(e Engine, repoID int64) int64 { - closed, _ := e.Where("repo_id=? AND is_closed=?", repoID, true).Count(new(Milestone)) - return closed -} - -// CountRepoClosedMilestones returns number of closed milestones in given repository. -func CountRepoClosedMilestones(repoID int64) int64 { - return countRepoClosedMilestones(x, repoID) -} - -// MilestoneStats returns number of open and closed milestones of given repository. -func MilestoneStats(repoID int64) (open int64, closed int64) { - open, _ = x.Where("repo_id=? AND is_closed=?", repoID, false).Count(new(Milestone)) - return open, CountRepoClosedMilestones(repoID) -} - -// ChangeMilestoneStatus changes the milestone open/closed status. -// If milestone passes with changed values, those values will be -// updated to database as well. -func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { - repo, err := GetRepositoryByID(m.RepoID) - if err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - m.IsClosed = isClosed - if err = updateMilestone(sess, m); err != nil { - 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 { - return err - } - return sess.Commit() -} - -func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error { - if issue.MilestoneID == 0 { - return nil - } - - m, err := getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) - if err != nil { - return err - } - - if issue.IsClosed { - m.NumOpenIssues-- - m.NumClosedIssues++ - } else { - m.NumOpenIssues++ - m.NumClosedIssues-- - } - - return updateMilestone(e, m) -} - -// ChangeMilestoneIssueStats updates the open/closed issues counter and progress -// for the milestone associated with the given issue. -func ChangeMilestoneIssueStats(issue *Issue) (err error) { - sess := x.NewSession() - defer sess.Close() - 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, issue *Issue, oldMilestoneID int64) error { - if oldMilestoneID > 0 { - m, err := getMilestoneByRepoID(e, issue.RepoID, oldMilestoneID) - if err != nil { - return err - } - - m.NumIssues-- - if issue.IsClosed { - m.NumClosedIssues-- - } - - if err = updateMilestone(e, m); err != nil { - return err - } else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id = 0 WHERE issue_id = ?", issue.ID); err != nil { - return err - } - - issue.Milestone = nil - } - - if issue.MilestoneID > 0 { - m, err := getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) - if err != nil { - return err - } - - m.NumIssues++ - if issue.IsClosed { - m.NumClosedIssues++ - } - - if err = updateMilestone(e, m); err != nil { - return err - } else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id = ? WHERE issue_id = ?", m.ID, issue.ID); err != nil { - return err - } - - issue.Milestone = m - } - - return updateIssue(e, issue) -} - -// ChangeMilestoneAssign changes assignment of milestone for issue. -func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = changeMilestoneAssign(sess, issue, oldMilestoneID); err != nil { - return err - } - - if err = sess.Commit(); err != nil { - return fmt.Errorf("Commit: %v", err) - } - - var hookAction api.HookIssueAction - if issue.MilestoneID > 0 { - hookAction = api.HOOK_ISSUE_MILESTONED - } else { - hookAction = api.HOOK_ISSUE_DEMILESTONED - } - - if issue.IsPull { - err = issue.PullRequest.LoadIssue() - if err != nil { - log.Error(2, "LoadIssue: %v", err) - return - } - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ - Action: hookAction, - Index: issue.Index, - PullRequest: issue.PullRequest.APIFormat(), - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }) - } else { - err = PrepareWebhooks(issue.Repo, HOOK_EVENT_ISSUES, &api.IssuesPayload{ - Action: hookAction, - Index: issue.Index, - Issue: issue.APIFormat(), - Repository: issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }) - } - if err != nil { - log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) - } - - return nil -} - -// DeleteMilestoneOfRepoByID deletes a milestone from a repository. -func DeleteMilestoneOfRepoByID(repoID, id int64) error { - m, err := GetMilestoneByRepoID(repoID, id) - if err != nil { - if IsErrMilestoneNotExist(err) { - return nil - } - return err - } - - repo, err := GetRepositoryByID(m.RepoID) - if err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.ID(m.ID).Delete(new(Milestone)); err != nil { - 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 { - return err - } - - if _, err = sess.Exec("UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil { - return err - } else if _, err = sess.Exec("UPDATE `issue_user` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil { - return err - } - return sess.Commit() -} diff --git a/models/mirror.go b/models/mirror.go deleted file mode 100644 index d4113f20..00000000 --- a/models/mirror.go +++ /dev/null @@ -1,498 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "container/list" - "fmt" - "net/url" - "strings" - "time" - - "github.com/unknwon/com" - "xorm.io/xorm" - log "gopkg.in/clog.v1" - "gopkg.in/ini.v1" - - "github.com/gogs/git-module" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/process" - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/sync" -) - -var MirrorQueue = sync.NewUniqueQueue(setting.Repository.MirrorQueueLength) - -// Mirror represents mirror information of a repository. -type Mirror struct { - ID int64 - RepoID int64 - Repo *Repository `xorm:"-" json:"-"` - Interval int // Hour. - EnablePrune bool `xorm:"NOT NULL DEFAULT true"` - - // Last and next sync time of Git data from upstream - LastSync time.Time `xorm:"-" json:"-"` - LastSyncUnix int64 `xorm:"updated_unix"` - NextSync time.Time `xorm:"-" json:"-"` - NextSyncUnix int64 `xorm:"next_update_unix"` - - address string `xorm:"-" json:"-"` -} - -func (m *Mirror) BeforeInsert() { - m.NextSyncUnix = m.NextSync.Unix() -} - -func (m *Mirror) BeforeUpdate() { - m.LastSyncUnix = m.LastSync.Unix() - m.NextSyncUnix = m.NextSync.Unix() -} - -func (m *Mirror) AfterSet(colName string, _ xorm.Cell) { - var err error - switch colName { - case "repo_id": - m.Repo, err = GetRepositoryByID(m.RepoID) - if err != nil { - log.Error(3, "GetRepositoryByID [%d]: %v", m.ID, err) - } - case "updated_unix": - m.LastSync = time.Unix(m.LastSyncUnix, 0).Local() - case "next_update_unix": - m.NextSync = time.Unix(m.NextSyncUnix, 0).Local() - } -} - -// ScheduleNextSync calculates and sets next sync time based on repostiroy mirror setting. -func (m *Mirror) ScheduleNextSync() { - m.NextSync = time.Now().Add(time.Duration(m.Interval) * time.Hour) -} - -// findPasswordInMirrorAddress returns start (inclusive) and end index (exclusive) -// of password portion of credentials in given mirror address. -// It returns a boolean value to indicate whether password portion is found. -func findPasswordInMirrorAddress(addr string) (start int, end int, found bool) { - // Find end of credentials (start of path) - end = strings.LastIndex(addr, "@") - if end == -1 { - return -1, -1, false - } - - // Find delimiter of credentials (end of username) - start = strings.Index(addr, "://") - if start == -1 { - return -1, -1, false - } - start += 3 - delim := strings.Index(addr[start:], ":") - if delim == -1 { - return -1, -1, false - } - delim += 1 - - if start+delim >= end { - return -1, -1, false // No password portion presented - } - - return start + delim, end, true -} - -// unescapeMirrorCredentials returns mirror address with unescaped credentials. -func unescapeMirrorCredentials(addr string) string { - start, end, found := findPasswordInMirrorAddress(addr) - if !found { - return addr - } - - password, _ := url.QueryUnescape(addr[start:end]) - return addr[:start] + password + addr[end:] -} - -func (m *Mirror) readAddress() { - if len(m.address) > 0 { - return - } - - cfg, err := ini.Load(m.Repo.GitConfigPath()) - if err != nil { - log.Error(2, "Load: %v", err) - return - } - m.address = cfg.Section("remote \"origin\"").Key("url").Value() -} - -// HandleMirrorCredentials replaces user credentials from HTTP/HTTPS URL -// with placeholder <credentials>. -// It returns original string if protocol is not HTTP/HTTPS. -func HandleMirrorCredentials(url string, mosaics bool) string { - i := strings.Index(url, "@") - if i == -1 { - return url - } - start := strings.Index(url, "://") - if start == -1 { - return url - } - if mosaics { - return url[:start+3] + "<credentials>" + url[i:] - } - return url[:start+3] + url[i+1:] -} - -// Address returns mirror address from Git repository config without credentials. -func (m *Mirror) Address() string { - m.readAddress() - return HandleMirrorCredentials(m.address, false) -} - -// MosaicsAddress returns mirror address from Git repository config with credentials under mosaics. -func (m *Mirror) MosaicsAddress() string { - m.readAddress() - return HandleMirrorCredentials(m.address, true) -} - -// RawAddress returns raw mirror address directly from Git repository config. -func (m *Mirror) RawAddress() string { - m.readAddress() - return m.address -} - -// FullAddress returns mirror address from Git repository config with unescaped credentials. -func (m *Mirror) FullAddress() string { - m.readAddress() - return unescapeMirrorCredentials(m.address) -} - -// escapeCredentials returns mirror address with escaped credentials. -func escapeMirrorCredentials(addr string) string { - start, end, found := findPasswordInMirrorAddress(addr) - if !found { - return addr - } - - return addr[:start] + url.QueryEscape(addr[start:end]) + addr[end:] -} - -// SaveAddress writes new address to Git repository config. -func (m *Mirror) SaveAddress(addr string) error { - configPath := m.Repo.GitConfigPath() - cfg, err := ini.Load(configPath) - if err != nil { - return fmt.Errorf("Load: %v", err) - } - - cfg.Section(`remote "origin"`).Key("url").SetValue(escapeMirrorCredentials(addr)) - return cfg.SaveToIndent(configPath, "\t") -} - -const GIT_SHORT_EMPTY_SHA = "0000000" - -// mirrorSyncResult contains information of a updated reference. -// If the oldCommitID is "0000000", it means a new reference, the value of newCommitID is empty. -// If the newCommitID is "0000000", it means the reference is deleted, the value of oldCommitID is empty. -type mirrorSyncResult struct { - refName string - oldCommitID string - newCommitID string -} - -// parseRemoteUpdateOutput detects create, update and delete operations of references from upstream. -func parseRemoteUpdateOutput(output string) []*mirrorSyncResult { - results := make([]*mirrorSyncResult, 0, 3) - lines := strings.Split(output, "\n") - for i := range lines { - // Make sure reference name is presented before continue - idx := strings.Index(lines[i], "-> ") - if idx == -1 { - continue - } - - refName := lines[i][idx+3:] - switch { - case strings.HasPrefix(lines[i], " * "): // New reference - results = append(results, &mirrorSyncResult{ - refName: refName, - oldCommitID: GIT_SHORT_EMPTY_SHA, - }) - case strings.HasPrefix(lines[i], " - "): // Delete reference - results = append(results, &mirrorSyncResult{ - refName: refName, - newCommitID: GIT_SHORT_EMPTY_SHA, - }) - case strings.HasPrefix(lines[i], " "): // New commits of a reference - delimIdx := strings.Index(lines[i][3:], " ") - if delimIdx == -1 { - log.Error(2, "SHA delimiter not found: %q", lines[i]) - continue - } - shas := strings.Split(lines[i][3:delimIdx+3], "..") - if len(shas) != 2 { - log.Error(2, "Expect two SHAs but not what found: %q", lines[i]) - continue - } - results = append(results, &mirrorSyncResult{ - refName: refName, - oldCommitID: shas[0], - newCommitID: shas[1], - }) - - default: - log.Warn("parseRemoteUpdateOutput: unexpected update line %q", lines[i]) - } - } - return results -} - -// runSync returns true if sync finished without error. -func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) { - repoPath := m.Repo.RepoPath() - wikiPath := m.Repo.WikiPath() - timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second - - // Do a fast-fail testing against on repository URL to ensure it is accessible under - // good condition to prevent long blocking on URL resolution without syncing anything. - if !git.IsRepoURLAccessible(git.NetworkOptions{ - URL: m.RawAddress(), - Timeout: 10 * time.Second, - }) { - desc := fmt.Sprintf("Source URL of mirror repository '%s' is not accessible: %s", m.Repo.FullName(), m.MosaicsAddress()) - if err := CreateRepositoryNotice(desc); err != nil { - log.Error(2, "CreateRepositoryNotice: %v", err) - } - return nil, false - } - - gitArgs := []string{"remote", "update"} - if m.EnablePrune { - gitArgs = append(gitArgs, "--prune") - } - _, stderr, err := process.ExecDir( - timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath), - "git", gitArgs...) - if err != nil { - desc := fmt.Sprintf("Fail to update mirror repository '%s': %s", repoPath, stderr) - log.Error(2, desc) - if err = CreateRepositoryNotice(desc); err != nil { - log.Error(2, "CreateRepositoryNotice: %v", err) - } - return nil, false - } - output := stderr - - if err := m.Repo.UpdateSize(); err != nil { - log.Error(2, "UpdateSize [repo_id: %d]: %v", m.Repo.ID, err) - } - - if m.Repo.HasWiki() { - // Even if wiki sync failed, we still want results from the main repository - if _, stderr, err := process.ExecDir( - timeout, wikiPath, fmt.Sprintf("Mirror.runSync: %s", wikiPath), - "git", "remote", "update", "--prune"); err != nil { - desc := fmt.Sprintf("Fail to update mirror wiki repository '%s': %s", wikiPath, stderr) - log.Error(2, desc) - if err = CreateRepositoryNotice(desc); err != nil { - log.Error(2, "CreateRepositoryNotice: %v", err) - } - } - } - - return parseRemoteUpdateOutput(output), true -} - -func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) { - m := &Mirror{RepoID: repoID} - has, err := e.Get(m) - if err != nil { - return nil, err - } else if !has { - return nil, errors.MirrorNotExist{repoID} - } - return m, nil -} - -// GetMirrorByRepoID returns mirror information of a repository. -func GetMirrorByRepoID(repoID int64) (*Mirror, error) { - return getMirrorByRepoID(x, repoID) -} - -func updateMirror(e Engine, m *Mirror) error { - _, err := e.ID(m.ID).AllCols().Update(m) - return err -} - -func UpdateMirror(m *Mirror) error { - return updateMirror(x, m) -} - -func DeleteMirrorByRepoID(repoID int64) error { - _, err := x.Delete(&Mirror{RepoID: repoID}) - return err -} - -// MirrorUpdate checks and updates mirror repositories. -func MirrorUpdate() { - if taskStatusTable.IsRunning(_MIRROR_UPDATE) { - return - } - taskStatusTable.Start(_MIRROR_UPDATE) - defer taskStatusTable.Stop(_MIRROR_UPDATE) - - log.Trace("Doing: MirrorUpdate") - - if err := x.Where("next_update_unix<=?", time.Now().Unix()).Iterate(new(Mirror), func(idx int, bean interface{}) error { - m := bean.(*Mirror) - if m.Repo == nil { - log.Error(2, "Disconnected mirror repository found: %d", m.ID) - return nil - } - - MirrorQueue.Add(m.RepoID) - return nil - }); err != nil { - log.Error(2, "MirrorUpdate: %v", err) - } -} - -// SyncMirrors checks and syncs mirrors. -// TODO: sync more mirrors at same time. -func SyncMirrors() { - // Start listening on new sync requests. - for repoID := range MirrorQueue.Queue() { - log.Trace("SyncMirrors [repo_id: %s]", repoID) - MirrorQueue.Remove(repoID) - - m, err := GetMirrorByRepoID(com.StrTo(repoID).MustInt64()) - if err != nil { - log.Error(2, "GetMirrorByRepoID [%d]: %v", m.RepoID, err) - continue - } - - results, ok := m.runSync() - if !ok { - continue - } - - m.ScheduleNextSync() - if err = UpdateMirror(m); err != nil { - log.Error(2, "UpdateMirror [%d]: %v", m.RepoID, err) - continue - } - - // TODO: - // - Create "Mirror Sync" webhook event - // - Create mirror sync (create, push and delete) events and trigger the "mirror sync" webhooks - - var gitRepo *git.Repository - if len(results) == 0 { - log.Trace("SyncMirrors [repo_id: %d]: no commits fetched", m.RepoID) - } else { - gitRepo, err = git.OpenRepository(m.Repo.RepoPath()) - if err != nil { - log.Error(2, "OpenRepository [%d]: %v", m.RepoID, err) - continue - } - } - - for _, result := range results { - // Discard GitHub pull requests, i.e. refs/pull/* - if strings.HasPrefix(result.refName, "refs/pull/") { - continue - } - - // Delete reference - if result.newCommitID == GIT_SHORT_EMPTY_SHA { - if err = MirrorSyncDeleteAction(m.Repo, result.refName); err != nil { - log.Error(2, "MirrorSyncDeleteAction [repo_id: %d]: %v", m.RepoID, err) - } - continue - } - - // New reference - isNewRef := false - if result.oldCommitID == GIT_SHORT_EMPTY_SHA { - if err = MirrorSyncCreateAction(m.Repo, result.refName); err != nil { - log.Error(2, "MirrorSyncCreateAction [repo_id: %d]: %v", m.RepoID, err) - continue - } - isNewRef = true - } - - // Push commits - var commits *list.List - var oldCommitID string - var newCommitID string - if !isNewRef { - oldCommitID, err = git.GetFullCommitID(gitRepo.Path, result.oldCommitID) - if err != nil { - log.Error(2, "GetFullCommitID [%d]: %v", m.RepoID, err) - continue - } - newCommitID, err = git.GetFullCommitID(gitRepo.Path, result.newCommitID) - if err != nil { - log.Error(2, "GetFullCommitID [%d]: %v", m.RepoID, err) - continue - } - commits, err = gitRepo.CommitsBetweenIDs(newCommitID, oldCommitID) - if err != nil { - log.Error(2, "CommitsBetweenIDs [repo_id: %d, new_commit_id: %s, old_commit_id: %s]: %v", m.RepoID, newCommitID, oldCommitID, err) - continue - } - } else { - refNewCommitID, err := gitRepo.GetBranchCommitID(result.refName) - if err != nil { - log.Error(2, "GetFullCommitID [%d]: %v", m.RepoID, err) - continue - } - if newCommit, err := gitRepo.GetCommit(refNewCommitID); err != nil { - log.Error(2, "GetCommit [repo_id: %d, commit_id: %s]: %v", m.RepoID, refNewCommitID, err) - continue - } else { - // TODO: Get the commits for the new ref until the closest ancestor branch like Github does - commits, err = newCommit.CommitsBeforeLimit(10) - if err != nil { - log.Error(2, "CommitsBeforeLimit [repo_id: %d, commit_id: %s]: %v", m.RepoID, refNewCommitID, err) - } - oldCommitID = git.EMPTY_SHA - newCommitID = refNewCommitID - } - } - if err = MirrorSyncPushAction(m.Repo, MirrorSyncPushActionOptions{ - RefName: result.refName, - OldCommitID: oldCommitID, - NewCommitID: newCommitID, - Commits: ListToPushCommits(commits), - }); err != nil { - log.Error(2, "MirrorSyncPushAction [repo_id: %d]: %v", m.RepoID, err) - continue - } - } - - if _, err = x.Exec("UPDATE mirror SET updated_unix = ? WHERE repo_id = ?", time.Now().Unix(), m.RepoID); err != nil { - log.Error(2, "Update 'mirror.updated_unix' [%d]: %v", m.RepoID, err) - continue - } - - // Get latest commit date and compare to current repository updated time, - // update if latest commit date is newer. - commitDate, err := git.GetLatestCommitDate(m.Repo.RepoPath(), "") - if err != nil { - log.Error(2, "GetLatestCommitDate [%d]: %v", m.RepoID, err) - continue - } else if commitDate.Before(m.Repo.Updated) { - continue - } - - if _, err = x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", commitDate.Unix(), m.RepoID); err != nil { - log.Error(2, "Update 'repository.updated_unix' [%d]: %v", m.RepoID, err) - continue - } - } -} - -func InitSyncMirrors() { - go SyncMirrors() -} diff --git a/models/mirror_test.go b/models/mirror_test.go deleted file mode 100644 index d6e86502..00000000 --- a/models/mirror_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -func Test_parseRemoteUpdateOutput(t *testing.T) { - Convey("Parse mirror remote update output", t, func() { - testCases := []struct { - output string - results []*mirrorSyncResult - }{ - { - ` -From https://try.gogs.io/unknwon/upsteam - * [new branch] develop -> develop - b0bb24f..1d85a4f master -> master - - [deleted] (none) -> bugfix -`, - []*mirrorSyncResult{ - {"develop", GIT_SHORT_EMPTY_SHA, ""}, - {"master", "b0bb24f", "1d85a4f"}, - {"bugfix", "", GIT_SHORT_EMPTY_SHA}, - }, - }, - } - - for _, tc := range testCases { - results := parseRemoteUpdateOutput(tc.output) - So(len(results), ShouldEqual, len(tc.results)) - - for i := range tc.results { - So(tc.results[i].refName, ShouldEqual, results[i].refName) - So(tc.results[i].oldCommitID, ShouldEqual, results[i].oldCommitID) - So(tc.results[i].newCommitID, ShouldEqual, results[i].newCommitID) - } - } - }) -} - -func Test_findPasswordInMirrorAddress(t *testing.T) { - Convey("Find password portion in mirror address", t, func() { - testCases := []struct { - addr string - start, end int - found bool - password string - }{ - {"http://localhost:3000/user/repo.git", -1, -1, false, ""}, - {"http://user@localhost:3000/user/repo.git", -1, -1, false, ""}, - {"http://user:@localhost:3000/user/repo.git", -1, -1, false, ""}, - {"http://user:password@localhost:3000/user/repo.git", 12, 20, true, "password"}, - {"http://username:my%3Asecure%3Bpassword@localhost:3000/user/repo.git", 16, 38, true, "my%3Asecure%3Bpassword"}, - {"http://username:my%40secure%23password@localhost:3000/user/repo.git", 16, 38, true, "my%40secure%23password"}, - {"http://username:@@localhost:3000/user/repo.git", 16, 17, true, "@"}, - } - - for _, tc := range testCases { - start, end, found := findPasswordInMirrorAddress(tc.addr) - So(start, ShouldEqual, tc.start) - So(end, ShouldEqual, tc.end) - So(found, ShouldEqual, tc.found) - if found { - So(tc.addr[start:end], ShouldEqual, tc.password) - } - } - }) -} - -func Test_unescapeMirrorCredentials(t *testing.T) { - Convey("Escape credentials in mirror address", t, func() { - testCases := []string{ - "http://localhost:3000/user/repo.git", "http://localhost:3000/user/repo.git", - "http://user@localhost:3000/user/repo.git", "http://user@localhost:3000/user/repo.git", - "http://user:@localhost:3000/user/repo.git", "http://user:@localhost:3000/user/repo.git", - "http://user:password@localhost:3000/user/repo.git", "http://user:password@localhost:3000/user/repo.git", - "http://user:my%3Asecure%3Bpassword@localhost:3000/user/repo.git", "http://user:my:secure;password@localhost:3000/user/repo.git", - "http://user:my%40secure%23password@localhost:3000/user/repo.git", "http://user:my@secure#password@localhost:3000/user/repo.git", - } - - for i := 0; i < len(testCases); i += 2 { - So(unescapeMirrorCredentials(testCases[i]), ShouldEqual, testCases[i+1]) - } - }) -} - -func Test_escapeMirrorCredentials(t *testing.T) { - Convey("Escape credentials in mirror address", t, func() { - testCases := []string{ - "http://localhost:3000/user/repo.git", "http://localhost:3000/user/repo.git", - "http://user@localhost:3000/user/repo.git", "http://user@localhost:3000/user/repo.git", - "http://user:@localhost:3000/user/repo.git", "http://user:@localhost:3000/user/repo.git", - "http://user:password@localhost:3000/user/repo.git", "http://user:password@localhost:3000/user/repo.git", - "http://user:my:secure;password@localhost:3000/user/repo.git", "http://user:my%3Asecure%3Bpassword@localhost:3000/user/repo.git", - "http://user:my@secure#password@localhost:3000/user/repo.git", "http://user:my%40secure%23password@localhost:3000/user/repo.git", - } - - for i := 0; i < len(testCases); i += 2 { - So(escapeMirrorCredentials(testCases[i]), ShouldEqual, testCases[i+1]) - } - }) -} diff --git a/models/models.go b/models/models.go deleted file mode 100644 index 2bfb8800..00000000 --- a/models/models.go +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "bufio" - "database/sql" - "errors" - "fmt" - "net/url" - "os" - "path" - "strings" - "time" - - _ "github.com/denisenkom/go-mssqldb" - _ "github.com/go-sql-driver/mysql" - "xorm.io/core" - "xorm.io/xorm" - "github.com/json-iterator/go" - _ "github.com/lib/pq" - "github.com/unknwon/com" - log "gopkg.in/clog.v1" - - "gogs.io/gogs/models/migrations" - "gogs.io/gogs/pkg/setting" -) - -// Engine represents a XORM engine or session. -type Engine interface { - Delete(interface{}) (int64, error) - Exec(...interface{}) (sql.Result, error) - Find(interface{}, ...interface{}) error - Get(interface{}) (bool, error) - ID(interface{}) *xorm.Session - In(string, ...interface{}) *xorm.Session - Insert(...interface{}) (int64, error) - InsertOne(interface{}) (int64, error) - Iterate(interface{}, xorm.IterFunc) error - Sql(string, ...interface{}) *xorm.Session - Table(interface{}) *xorm.Session - Where(interface{}, ...interface{}) *xorm.Session -} - -var ( - x *xorm.Engine - tables []interface{} - HasEngine bool - - DbCfg struct { - Type, Host, Name, User, Passwd, Path, SSLMode string - } - - EnableSQLite3 bool -) - -func init() { - tables = append(tables, - new(User), new(PublicKey), new(AccessToken), new(TwoFactor), new(TwoFactorRecoveryCode), - new(Repository), new(DeployKey), new(Collaboration), new(Access), new(Upload), - new(Watch), new(Star), new(Follow), new(Action), - new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser), - new(Label), new(IssueLabel), new(Milestone), - new(Mirror), new(Release), new(LoginSource), new(Webhook), new(HookTask), - new(ProtectBranch), new(ProtectBranchWhitelist), - new(Team), new(OrgUser), new(TeamUser), new(TeamRepo), - new(Notice), new(EmailAddress)) - - gonicNames := []string{"SSL"} - for _, name := range gonicNames { - core.LintGonicMapper[name] = true - } -} - -func LoadConfigs() { - sec := setting.Cfg.Section("database") - DbCfg.Type = sec.Key("DB_TYPE").String() - switch DbCfg.Type { - case "sqlite3": - setting.UseSQLite3 = true - case "mysql": - setting.UseMySQL = true - case "postgres": - setting.UsePostgreSQL = true - case "mssql": - setting.UseMSSQL = true - } - DbCfg.Host = sec.Key("HOST").String() - DbCfg.Name = sec.Key("NAME").String() - DbCfg.User = sec.Key("USER").String() - if len(DbCfg.Passwd) == 0 { - DbCfg.Passwd = sec.Key("PASSWD").String() - } - DbCfg.SSLMode = sec.Key("SSL_MODE").String() - DbCfg.Path = sec.Key("PATH").MustString("data/gogs.db") -} - -// parsePostgreSQLHostPort parses given input in various forms defined in -// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING -// and returns proper host and port number. -func parsePostgreSQLHostPort(info string) (string, string) { - host, port := "127.0.0.1", "5432" - if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") { - idx := strings.LastIndex(info, ":") - host = info[:idx] - port = info[idx+1:] - } else if len(info) > 0 { - host = info - } - return host, port -} - -func parseMSSQLHostPort(info string) (string, string) { - host, port := "127.0.0.1", "1433" - if strings.Contains(info, ":") { - host = strings.Split(info, ":")[0] - port = strings.Split(info, ":")[1] - } else if strings.Contains(info, ",") { - host = strings.Split(info, ",")[0] - port = strings.TrimSpace(strings.Split(info, ",")[1]) - } else if len(info) > 0 { - host = info - } - return host, port -} - -func getEngine() (*xorm.Engine, error) { - connStr := "" - var Param string = "?" - if strings.Contains(DbCfg.Name, Param) { - Param = "&" - } - switch DbCfg.Type { - case "mysql": - if DbCfg.Host[0] == '/' { // looks like a unix socket - connStr = fmt.Sprintf("%s:%s@unix(%s)/%s%scharset=utf8mb4&parseTime=true", - DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param) - } else { - connStr = fmt.Sprintf("%s:%s@tcp(%s)/%s%scharset=utf8mb4&parseTime=true", - DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param) - } - var engineParams = map[string]string{"rowFormat": "DYNAMIC"} - return xorm.NewEngineWithParams(DbCfg.Type, connStr, engineParams) - case "postgres": - host, port := parsePostgreSQLHostPort(DbCfg.Host) - if host[0] == '/' { // looks like a unix socket - connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s", - url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), port, DbCfg.Name, Param, DbCfg.SSLMode, host) - } else { - connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s", - url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), host, port, DbCfg.Name, Param, DbCfg.SSLMode) - } - case "mssql": - host, port := parseMSSQLHostPort(DbCfg.Host) - connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, DbCfg.Name, DbCfg.User, DbCfg.Passwd) - case "sqlite3": - if !EnableSQLite3 { - return nil, errors.New("this binary version does not build support for SQLite3") - } - if err := os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm); err != nil { - return nil, fmt.Errorf("create directories: %v", err) - } - connStr = "file:" + DbCfg.Path + "?cache=shared&mode=rwc" - default: - return nil, fmt.Errorf("unknown database type: %s", DbCfg.Type) - } - return xorm.NewEngine(DbCfg.Type, connStr) -} - -func NewTestEngine(x *xorm.Engine) (err error) { - x, err = getEngine() - if err != nil { - return fmt.Errorf("connect to database: %v", err) - } - - x.SetMapper(core.GonicMapper{}) - return x.StoreEngine("InnoDB").Sync2(tables...) -} - -func SetEngine() (err error) { - x, err = getEngine() - if err != nil { - return fmt.Errorf("connect to database: %v", err) - } - - x.SetMapper(core.GonicMapper{}) - - // WARNING: for serv command, MUST remove the output to os.stdout, - // so use log file to instead print to stdout. - sec := setting.Cfg.Section("log.xorm") - logger, err := log.NewFileWriter(path.Join(setting.LogRootPath, "xorm.log"), - log.FileRotationConfig{ - Rotate: sec.Key("ROTATE").MustBool(true), - Daily: sec.Key("ROTATE_DAILY").MustBool(true), - MaxSize: sec.Key("MAX_SIZE").MustInt64(100) * 1024 * 1024, - MaxDays: sec.Key("MAX_DAYS").MustInt64(3), - }) - if err != nil { - return fmt.Errorf("create 'xorm.log': %v", err) - } - - // To prevent mystery "MySQL: invalid connection" error, - // see https://gogs.io/gogs/issues/5532. - x.SetMaxIdleConns(0) - x.SetConnMaxLifetime(time.Second) - - if setting.ProdMode { - x.SetLogger(xorm.NewSimpleLogger3(logger, xorm.DEFAULT_LOG_PREFIX, xorm.DEFAULT_LOG_FLAG, core.LOG_WARNING)) - } else { - x.SetLogger(xorm.NewSimpleLogger(logger)) - } - x.ShowSQL(true) - return nil -} - -func NewEngine() (err error) { - if err = SetEngine(); err != nil { - return err - } - - if err = migrations.Migrate(x); err != nil { - return fmt.Errorf("migrate: %v", err) - } - - if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil { - return fmt.Errorf("sync structs to database tables: %v\n", err) - } - - return nil -} - -type Statistic struct { - Counter struct { - User, Org, PublicKey, - Repo, Watch, Star, Action, Access, - Issue, Comment, Oauth, Follow, - Mirror, Release, LoginSource, Webhook, - Milestone, Label, HookTask, - Team, UpdateTask, Attachment int64 - } -} - -func GetStatistic() (stats Statistic) { - stats.Counter.User = CountUsers() - stats.Counter.Org = CountOrganizations() - stats.Counter.PublicKey, _ = x.Count(new(PublicKey)) - stats.Counter.Repo = CountRepositories(true) - stats.Counter.Watch, _ = x.Count(new(Watch)) - stats.Counter.Star, _ = x.Count(new(Star)) - stats.Counter.Action, _ = x.Count(new(Action)) - stats.Counter.Access, _ = x.Count(new(Access)) - stats.Counter.Issue, _ = x.Count(new(Issue)) - stats.Counter.Comment, _ = x.Count(new(Comment)) - stats.Counter.Oauth = 0 - stats.Counter.Follow, _ = x.Count(new(Follow)) - stats.Counter.Mirror, _ = x.Count(new(Mirror)) - stats.Counter.Release, _ = x.Count(new(Release)) - stats.Counter.LoginSource = CountLoginSources() - stats.Counter.Webhook, _ = x.Count(new(Webhook)) - stats.Counter.Milestone, _ = x.Count(new(Milestone)) - stats.Counter.Label, _ = x.Count(new(Label)) - stats.Counter.HookTask, _ = x.Count(new(HookTask)) - stats.Counter.Team, _ = x.Count(new(Team)) - stats.Counter.Attachment, _ = x.Count(new(Attachment)) - return -} - -func Ping() error { - return x.Ping() -} - -// The version table. Should have only one row with id==1 -type Version struct { - ID int64 - Version int64 -} - -// DumpDatabase dumps all data from database to file system in JSON format. -func DumpDatabase(dirPath string) (err error) { - os.MkdirAll(dirPath, os.ModePerm) - // Purposely create a local variable to not modify global variable - tables := append(tables, new(Version)) - for _, table := range tables { - tableName := strings.TrimPrefix(fmt.Sprintf("%T", table), "*models.") - tableFile := path.Join(dirPath, tableName+".json") - f, err := os.Create(tableFile) - if err != nil { - return fmt.Errorf("create JSON file: %v", err) - } - - if err = x.Asc("id").Iterate(table, func(idx int, bean interface{}) (err error) { - return jsoniter.NewEncoder(f).Encode(bean) - }); err != nil { - f.Close() - return fmt.Errorf("dump table '%s': %v", tableName, err) - } - f.Close() - } - return nil -} - -// ImportDatabase imports data from backup archive. -func ImportDatabase(dirPath string, verbose bool) (err error) { - snakeMapper := core.SnakeMapper{} - - skipInsertProcessors := map[string]bool{ - "mirror": true, - "milestone": true, - } - - // Purposely create a local variable to not modify global variable - tables := append(tables, new(Version)) - for _, table := range tables { - tableName := strings.TrimPrefix(fmt.Sprintf("%T", table), "*models.") - tableFile := path.Join(dirPath, tableName+".json") - if !com.IsExist(tableFile) { - continue - } - - if verbose { - log.Trace("Importing table '%s'...", tableName) - } - - if err = x.DropTables(table); err != nil { - return fmt.Errorf("drop table '%s': %v", tableName, err) - } else if err = x.Sync2(table); err != nil { - return fmt.Errorf("sync table '%s': %v", tableName, err) - } - - f, err := os.Open(tableFile) - if err != nil { - return fmt.Errorf("open JSON file: %v", err) - } - rawTableName := x.TableName(table) - _, isInsertProcessor := table.(xorm.BeforeInsertProcessor) - scanner := bufio.NewScanner(f) - for scanner.Scan() { - switch bean := table.(type) { - case *LoginSource: - meta := make(map[string]interface{}) - if err = jsoniter.Unmarshal(scanner.Bytes(), &meta); err != nil { - return fmt.Errorf("unmarshal to map: %v", err) - } - - tp := LoginType(com.StrTo(com.ToStr(meta["Type"])).MustInt64()) - switch tp { - case LOGIN_LDAP, LOGIN_DLDAP: - bean.Cfg = new(LDAPConfig) - case LOGIN_SMTP: - bean.Cfg = new(SMTPConfig) - case LOGIN_PAM: - bean.Cfg = new(PAMConfig) - case LOGIN_GITHUB: - bean.Cfg = new(GitHubConfig) - default: - return fmt.Errorf("unrecognized login source type:: %v", tp) - } - table = bean - } - - if err = jsoniter.Unmarshal(scanner.Bytes(), table); err != nil { - return fmt.Errorf("unmarshal to struct: %v", err) - } - - if _, err = x.Insert(table); err != nil { - return fmt.Errorf("insert strcut: %v", err) - } - - meta := make(map[string]interface{}) - if err = jsoniter.Unmarshal(scanner.Bytes(), &meta); err != nil { - log.Error(2, "Failed to unmarshal to map: %v", err) - } - - // Reset created_unix back to the date save in archive because Insert method updates its value - if isInsertProcessor && !skipInsertProcessors[rawTableName] { - if _, err = x.Exec("UPDATE "+rawTableName+" SET created_unix=? WHERE id=?", meta["CreatedUnix"], meta["ID"]); err != nil { - log.Error(2, "Failed to reset 'created_unix': %v", err) - } - } - - switch rawTableName { - case "milestone": - if _, err = x.Exec("UPDATE "+rawTableName+" SET deadline_unix=?, closed_date_unix=? WHERE id=?", meta["DeadlineUnix"], meta["ClosedDateUnix"], meta["ID"]); err != nil { - log.Error(2, "Failed to reset 'milestone.deadline_unix', 'milestone.closed_date_unix': %v", err) - } - } - } - - // PostgreSQL needs manually reset table sequence for auto increment keys - if setting.UsePostgreSQL { - rawTableName := snakeMapper.Obj2Table(tableName) - seqName := rawTableName + "_id_seq" - if _, err = x.Exec(fmt.Sprintf(`SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM "%s"), 1), false);`, seqName, rawTableName)); err != nil { - return fmt.Errorf("reset table '%s' sequence: %v", rawTableName, err) - } - } - } - return nil -} diff --git a/models/models_sqlite.go b/models/models_sqlite.go deleted file mode 100644 index c77e5ae5..00000000 --- a/models/models_sqlite.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build sqlite - -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - _ "github.com/mattn/go-sqlite3" -) - -func init() { - EnableSQLite3 = true -} diff --git a/models/models_test.go b/models/models_test.go deleted file mode 100644 index f68590c5..00000000 --- a/models/models_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -func Test_parsePostgreSQLHostPort(t *testing.T) { - testSuites := []struct { - input string - host, port string - }{ - {"127.0.0.1:1234", "127.0.0.1", "1234"}, - {"127.0.0.1", "127.0.0.1", "5432"}, - {"[::1]:1234", "[::1]", "1234"}, - {"[::1]", "[::1]", "5432"}, - {"/tmp/pg.sock:1234", "/tmp/pg.sock", "1234"}, - {"/tmp/pg.sock", "/tmp/pg.sock", "5432"}, - } - - Convey("Parse PostgreSQL host and port", t, func() { - for _, suite := range testSuites { - host, port := parsePostgreSQLHostPort(suite.input) - So(host, ShouldEqual, suite.host) - So(port, ShouldEqual, suite.port) - } - }) -} diff --git a/models/org.go b/models/org.go deleted file mode 100644 index df280c42..00000000 --- a/models/org.go +++ /dev/null @@ -1,563 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "errors" - "fmt" - "os" - "strings" - - "github.com/go-xorm/builder" - "xorm.io/xorm" -) - -var ( - ErrOrgNotExist = errors.New("Organization does not exist") -) - -// IsOwnedBy returns true if given user is in the owner team. -func (org *User) IsOwnedBy(userID int64) bool { - return IsOrganizationOwner(org.ID, userID) -} - -// IsOrgMember returns true if given user is member of organization. -func (org *User) IsOrgMember(uid int64) bool { - return org.IsOrganization() && IsOrganizationMember(org.ID, uid) -} - -func (org *User) getTeam(e Engine, name string) (*Team, error) { - return getTeamOfOrgByName(e, org.ID, name) -} - -// GetTeamOfOrgByName returns named team of organization. -func (org *User) GetTeam(name string) (*Team, error) { - return org.getTeam(x, name) -} - -func (org *User) getOwnerTeam(e Engine) (*Team, error) { - return org.getTeam(e, OWNER_TEAM) -} - -// GetOwnerTeam returns owner team of organization. -func (org *User) GetOwnerTeam() (*Team, error) { - return org.getOwnerTeam(x) -} - -func (org *User) getTeams(e Engine) (err error) { - org.Teams, err = getTeamsByOrgID(e, org.ID) - return err -} - -// GetTeams returns all teams that belong to organization. -func (org *User) GetTeams() error { - return org.getTeams(x) -} - -// TeamsHaveAccessToRepo returns all teamsthat have given access level to the repository. -func (org *User) TeamsHaveAccessToRepo(repoID int64, mode AccessMode) ([]*Team, error) { - return GetTeamsHaveAccessToRepo(org.ID, repoID, mode) -} - -// GetMembers returns all members of organization. -func (org *User) GetMembers() error { - ous, err := GetOrgUsersByOrgID(org.ID) - if err != nil { - return err - } - - org.Members = make([]*User, len(ous)) - for i, ou := range ous { - org.Members[i], err = GetUserByID(ou.Uid) - if err != nil { - return err - } - } - return nil -} - -// AddMember adds new member to organization. -func (org *User) AddMember(uid int64) error { - return AddOrgUser(org.ID, uid) -} - -// RemoveMember removes member from organization. -func (org *User) RemoveMember(uid int64) error { - return RemoveOrgUser(org.ID, uid) -} - -func (org *User) removeOrgRepo(e Engine, repoID int64) error { - return removeOrgRepo(e, org.ID, repoID) -} - -// RemoveOrgRepo removes all team-repository relations of organization. -func (org *User) RemoveOrgRepo(repoID int64) error { - return org.removeOrgRepo(x, repoID) -} - -// CreateOrganization creates record of a new organization. -func CreateOrganization(org, owner *User) (err error) { - if err = IsUsableUsername(org.Name); err != nil { - return err - } - - isExist, err := IsUserExist(0, org.Name) - if err != nil { - return err - } else if isExist { - return ErrUserAlreadyExist{org.Name} - } - - org.LowerName = strings.ToLower(org.Name) - if org.Rands, err = GetUserSalt(); err != nil { - return err - } - if org.Salt, err = GetUserSalt(); err != nil { - return err - } - org.UseCustomAvatar = true - org.MaxRepoCreation = -1 - org.NumTeams = 1 - org.NumMembers = 1 - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Insert(org); err != nil { - return fmt.Errorf("insert organization: %v", err) - } - org.GenerateRandomAvatar() - - // Add initial creator to organization and owner team. - if _, err = sess.Insert(&OrgUser{ - Uid: owner.ID, - OrgID: org.ID, - IsOwner: true, - NumTeams: 1, - }); err != nil { - return fmt.Errorf("insert org-user relation: %v", err) - } - - // Create default owner team. - t := &Team{ - OrgID: org.ID, - LowerName: strings.ToLower(OWNER_TEAM), - Name: OWNER_TEAM, - Authorize: ACCESS_MODE_OWNER, - NumMembers: 1, - } - if _, err = sess.Insert(t); err != nil { - return fmt.Errorf("insert owner team: %v", err) - } - - if _, err = sess.Insert(&TeamUser{ - UID: owner.ID, - OrgID: org.ID, - TeamID: t.ID, - }); err != nil { - return fmt.Errorf("insert team-user relation: %v", err) - } - - if err = os.MkdirAll(UserPath(org.Name), os.ModePerm); err != nil { - return fmt.Errorf("create directory: %v", err) - } - - return sess.Commit() -} - -// GetOrgByName returns organization by given name. -func GetOrgByName(name string) (*User, error) { - if len(name) == 0 { - return nil, ErrOrgNotExist - } - u := &User{ - LowerName: strings.ToLower(name), - Type: USER_TYPE_ORGANIZATION, - } - has, err := x.Get(u) - if err != nil { - return nil, err - } else if !has { - return nil, ErrOrgNotExist - } - return u, nil -} - -// CountOrganizations returns number of organizations. -func CountOrganizations() int64 { - count, _ := x.Where("type=1").Count(new(User)) - return count -} - -// Organizations returns number of organizations in given page. -func Organizations(page, pageSize int) ([]*User, error) { - orgs := make([]*User, 0, pageSize) - return orgs, x.Limit(pageSize, (page-1)*pageSize).Where("type=1").Asc("id").Find(&orgs) -} - -// DeleteOrganization completely and permanently deletes everything of organization. -func DeleteOrganization(org *User) (err error) { - if err := DeleteUser(org); err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = deleteBeans(sess, - &Team{OrgID: org.ID}, - &OrgUser{OrgID: org.ID}, - &TeamUser{OrgID: org.ID}, - ); err != nil { - return fmt.Errorf("deleteBeans: %v", err) - } - - if err = deleteUser(sess, org); err != nil { - return fmt.Errorf("deleteUser: %v", err) - } - - return sess.Commit() -} - -// ________ ____ ___ -// \_____ \_______ ____ | | \______ ___________ -// / | \_ __ \/ ___\| | / ___// __ \_ __ \ -// / | \ | \/ /_/ > | /\___ \\ ___/| | \/ -// \_______ /__| \___ /|______//____ >\___ >__| -// \/ /_____/ \/ \/ - -// OrgUser represents an organization-user relation. -type OrgUser struct { - ID int64 - Uid int64 `xorm:"INDEX UNIQUE(s)"` - OrgID int64 `xorm:"INDEX UNIQUE(s)"` - IsPublic bool - IsOwner bool - NumTeams int -} - -// IsOrganizationOwner returns true if given user is in the owner team. -func IsOrganizationOwner(orgID, userID int64) bool { - has, _ := x.Where("is_owner = ?", true).And("uid = ?", userID).And("org_id = ?", orgID).Get(new(OrgUser)) - return has -} - -// IsOrganizationMember returns true if given user is member of organization. -func IsOrganizationMember(orgId, uid int64) bool { - has, _ := x.Where("uid=?", uid).And("org_id=?", orgId).Get(new(OrgUser)) - return has -} - -// IsPublicMembership returns true if given user public his/her membership. -func IsPublicMembership(orgId, uid int64) bool { - has, _ := x.Where("uid=?", uid).And("org_id=?", orgId).And("is_public=?", true).Get(new(OrgUser)) - return has -} - -func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, error) { - orgs := make([]*User, 0, 10) - if !showAll { - sess.And("`org_user`.is_public=?", true) - } - return orgs, sess.And("`org_user`.uid=?", userID). - Join("INNER", "`org_user`", "`org_user`.org_id=`user`.id").Find(&orgs) -} - -// GetOrgsByUserID returns a list of organizations that the given user ID -// has joined. -func GetOrgsByUserID(userID int64, showAll bool) ([]*User, error) { - return getOrgsByUserID(x.NewSession(), userID, showAll) -} - -// GetOrgsByUserIDDesc returns a list of organizations that the given user ID -// has joined, ordered descending by the given condition. -func GetOrgsByUserIDDesc(userID int64, desc string, showAll bool) ([]*User, error) { - return getOrgsByUserID(x.NewSession().Desc(desc), userID, showAll) -} - -func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) { - orgs := make([]*User, 0, 10) - return orgs, sess.Where("`org_user`.uid=?", userID).And("`org_user`.is_owner=?", true). - Join("INNER", "`org_user`", "`org_user`.org_id=`user`.id").Find(&orgs) -} - -// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID. -func GetOwnedOrgsByUserID(userID int64) ([]*User, error) { - sess := x.NewSession() - return getOwnedOrgsByUserID(sess, userID) -} - -// GetOwnedOrganizationsByUserIDDesc returns a list of organizations are owned by -// given user ID, ordered descending by the given condition. -func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) { - sess := x.NewSession() - return getOwnedOrgsByUserID(sess.Desc(desc), userID) -} - -// GetOrgIDsByUserID returns a list of organization IDs that user belongs to. -// The showPrivate indicates whether to include private memberships. -func GetOrgIDsByUserID(userID int64, showPrivate bool) ([]int64, error) { - orgIDs := make([]int64, 0, 5) - sess := x.Table("org_user").Where("uid = ?", userID) - if !showPrivate { - sess.And("is_public = ?", true) - } - return orgIDs, sess.Distinct("org_id").Find(&orgIDs) -} - -func getOrgUsersByOrgID(e Engine, orgID int64) ([]*OrgUser, error) { - orgUsers := make([]*OrgUser, 0, 10) - return orgUsers, e.Where("org_id=?", orgID).Find(&orgUsers) -} - -// GetOrgUsersByOrgID returns all organization-user relations by organization ID. -func GetOrgUsersByOrgID(orgID int64) ([]*OrgUser, error) { - return getOrgUsersByOrgID(x, orgID) -} - -// ChangeOrgUserStatus changes public or private membership status. -func ChangeOrgUserStatus(orgID, uid int64, public bool) error { - ou := new(OrgUser) - has, err := x.Where("uid=?", uid).And("org_id=?", orgID).Get(ou) - if err != nil { - return err - } else if !has { - return nil - } - - ou.IsPublic = public - _, err = x.Id(ou.ID).AllCols().Update(ou) - return err -} - -// AddOrgUser adds new user to given organization. -func AddOrgUser(orgID, uid int64) error { - if IsOrganizationMember(orgID, uid) { - return nil - } - - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - - ou := &OrgUser{ - Uid: uid, - OrgID: orgID, - } - - if _, err := sess.Insert(ou); err != nil { - sess.Rollback() - return err - } else if _, err = sess.Exec("UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil { - sess.Rollback() - return err - } - - return sess.Commit() -} - -// RemoveOrgUser removes user from given organization. -func RemoveOrgUser(orgID, userID int64) error { - ou := new(OrgUser) - - has, err := x.Where("uid=?", userID).And("org_id=?", orgID).Get(ou) - if err != nil { - return fmt.Errorf("get org-user: %v", err) - } else if !has { - return nil - } - - user, err := GetUserByID(userID) - if err != nil { - return fmt.Errorf("GetUserByID [%d]: %v", userID, err) - } - org, err := GetUserByID(orgID) - if err != nil { - return fmt.Errorf("GetUserByID [%d]: %v", orgID, err) - } - - // FIXME: only need to get IDs here, not all fields of repository. - repos, _, err := org.GetUserRepositories(user.ID, 1, org.NumRepos) - if err != nil { - return fmt.Errorf("GetUserRepositories [%d]: %v", user.ID, err) - } - - // Check if the user to delete is the last member in owner team. - if IsOrganizationOwner(orgID, userID) { - t, err := org.GetOwnerTeam() - if err != nil { - return err - } - if t.NumMembers == 1 { - return ErrLastOrgOwner{UID: userID} - } - } - - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - - if _, err := sess.ID(ou.ID).Delete(ou); err != nil { - return err - } else if _, err = sess.Exec("UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil { - return err - } - - // Delete all repository accesses and unwatch them. - repoIDs := make([]int64, len(repos)) - for i := range repos { - repoIDs = append(repoIDs, repos[i].ID) - if err = watchRepo(sess, user.ID, repos[i].ID, false); err != nil { - return err - } - } - - if len(repoIDs) > 0 { - if _, err = sess.Where("user_id = ?", user.ID).In("repo_id", repoIDs).Delete(new(Access)); err != nil { - return err - } - } - - // Delete member in his/her teams. - teams, err := getUserTeams(sess, org.ID, user.ID) - if err != nil { - return err - } - for _, t := range teams { - if err = removeTeamMember(sess, org.ID, t.ID, user.ID); err != nil { - return err - } - } - - return sess.Commit() -} - -func removeOrgRepo(e Engine, orgID, repoID int64) error { - _, err := e.Delete(&TeamRepo{ - OrgID: orgID, - RepoID: repoID, - }) - return err -} - -// RemoveOrgRepo removes all team-repository relations of given organization. -func RemoveOrgRepo(orgID, repoID int64) error { - return removeOrgRepo(x, orgID, repoID) -} - -func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team, error) { - teams := make([]*Team, 0, org.NumTeams) - return teams, e.Where("team_user.org_id = ?", org.ID). - And("team_user.uid = ?", userID). - Join("INNER", "team_user", "team_user.team_id = team.id"). - Cols(cols...).Find(&teams) -} - -// GetUserTeamIDs returns of all team IDs of the organization that user is memeber of. -func (org *User) GetUserTeamIDs(userID int64) ([]int64, error) { - teams, err := org.getUserTeams(x, userID, "team.id") - if err != nil { - return nil, fmt.Errorf("getUserTeams [%d]: %v", userID, err) - } - - teamIDs := make([]int64, len(teams)) - for i := range teams { - teamIDs[i] = teams[i].ID - } - return teamIDs, nil -} - -// GetTeams returns all teams that belong to organization, -// and that the user has joined. -func (org *User) GetUserTeams(userID int64) ([]*Team, error) { - return org.getUserTeams(x, userID) -} - -// GetUserRepositories returns a range of repositories in organization which the user has access to, -// and total number of records based on given condition. -func (org *User) GetUserRepositories(userID int64, page, pageSize int) ([]*Repository, int64, error) { - teamIDs, err := org.GetUserTeamIDs(userID) - if err != nil { - return nil, 0, fmt.Errorf("GetUserTeamIDs: %v", err) - } - if len(teamIDs) == 0 { - // user has no team but "IN ()" is invalid SQL - teamIDs = []int64{-1} // there is no team with id=-1 - } - - var teamRepoIDs []int64 - if err = x.Table("team_repo").In("team_id", teamIDs).Distinct("repo_id").Find(&teamRepoIDs); err != nil { - return nil, 0, fmt.Errorf("get team repository IDs: %v", err) - } - if len(teamRepoIDs) == 0 { - // team has no repo but "IN ()" is invalid SQL - teamRepoIDs = []int64{-1} // there is no repo with id=-1 - } - - if page <= 0 { - page = 1 - } - repos := make([]*Repository, 0, pageSize) - if err = x.Where("owner_id = ?", org.ID). - And("is_private = ?", false). - Or(builder.In("id", teamRepoIDs)). - Desc("updated_unix"). - Limit(pageSize, (page-1)*pageSize). - Find(&repos); err != nil { - return nil, 0, fmt.Errorf("get user repositories: %v", err) - } - - repoCount, err := x.Where("owner_id = ?", org.ID). - And("is_private = ?", false). - Or(builder.In("id", teamRepoIDs)). - Count(new(Repository)) - if err != nil { - return nil, 0, fmt.Errorf("count user repositories: %v", err) - } - - return repos, repoCount, nil -} - -// GetUserMirrorRepositories returns mirror repositories of the organization which the user has access to. -func (org *User) GetUserMirrorRepositories(userID int64) ([]*Repository, error) { - teamIDs, err := org.GetUserTeamIDs(userID) - if err != nil { - return nil, fmt.Errorf("GetUserTeamIDs: %v", err) - } - if len(teamIDs) == 0 { - teamIDs = []int64{-1} - } - - var teamRepoIDs []int64 - err = x.Table("team_repo").In("team_id", teamIDs).Distinct("repo_id").Find(&teamRepoIDs) - if err != nil { - return nil, fmt.Errorf("get team repository ids: %v", err) - } - if len(teamRepoIDs) == 0 { - // team has no repo but "IN ()" is invalid SQL - teamRepoIDs = []int64{-1} // there is no repo with id=-1 - } - - repos := make([]*Repository, 0, 10) - if err = x.Where("owner_id = ?", org.ID). - And("is_private = ?", false). - Or(builder.In("id", teamRepoIDs)). - And("is_mirror = ?", true). // Don't move up because it's an independent condition - Desc("updated_unix"). - Find(&repos); err != nil { - return nil, fmt.Errorf("get user repositories: %v", err) - } - return repos, nil -} diff --git a/models/org_team.go b/models/org_team.go deleted file mode 100644 index 5fc77dbe..00000000 --- a/models/org_team.go +++ /dev/null @@ -1,666 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "strings" - - "xorm.io/xorm" - - "gogs.io/gogs/models/errors" -) - -const OWNER_TEAM = "Owners" - -// Team represents a organization team. -type Team struct { - ID int64 - OrgID int64 `xorm:"INDEX"` - LowerName string - Name string - Description string - Authorize AccessMode - Repos []*Repository `xorm:"-" json:"-"` - Members []*User `xorm:"-" json:"-"` - NumRepos int - NumMembers int -} - -func (t *Team) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "num_repos": - // LEGACY [1.0]: this is backward compatibility bug fix for https://gogs.io/gogs/issues/3671 - if t.NumRepos < 0 { - t.NumRepos = 0 - } - } -} - -// IsOwnerTeam returns true if team is owner team. -func (t *Team) IsOwnerTeam() bool { - return t.Name == OWNER_TEAM -} - -// HasWriteAccess returns true if team has at least write level access mode. -func (t *Team) HasWriteAccess() bool { - return t.Authorize >= ACCESS_MODE_WRITE -} - -// IsTeamMember returns true if given user is a member of team. -func (t *Team) IsMember(userID int64) bool { - return IsTeamMember(t.OrgID, t.ID, userID) -} - -func (t *Team) getRepositories(e Engine) (err error) { - teamRepos := make([]*TeamRepo, 0, t.NumRepos) - if err = x.Where("team_id=?", t.ID).Find(&teamRepos); err != nil { - return fmt.Errorf("get team-repos: %v", err) - } - - t.Repos = make([]*Repository, 0, len(teamRepos)) - for i := range teamRepos { - repo, err := getRepositoryByID(e, teamRepos[i].RepoID) - if err != nil { - return fmt.Errorf("getRepositoryById(%d): %v", teamRepos[i].RepoID, err) - } - t.Repos = append(t.Repos, repo) - } - return nil -} - -// GetRepositories returns all repositories in team of organization. -func (t *Team) GetRepositories() error { - return t.getRepositories(x) -} - -func (t *Team) getMembers(e Engine) (err error) { - t.Members, err = getTeamMembers(e, t.ID) - return err -} - -// GetMembers returns all members in team of organization. -func (t *Team) GetMembers() (err error) { - return t.getMembers(x) -} - -// AddMember adds new membership of the team to the organization, -// the user will have membership to the organization automatically when needed. -func (t *Team) AddMember(uid int64) error { - return AddTeamMember(t.OrgID, t.ID, uid) -} - -// RemoveMember removes member from team of organization. -func (t *Team) RemoveMember(uid int64) error { - return RemoveTeamMember(t.OrgID, t.ID, uid) -} - -func (t *Team) hasRepository(e Engine, repoID int64) bool { - return hasTeamRepo(e, t.OrgID, t.ID, repoID) -} - -// HasRepository returns true if given repository belong to team. -func (t *Team) HasRepository(repoID int64) bool { - return t.hasRepository(x, repoID) -} - -func (t *Team) addRepository(e Engine, repo *Repository) (err error) { - if err = addTeamRepo(e, t.OrgID, t.ID, repo.ID); err != nil { - return err - } - - t.NumRepos++ - if _, err = e.ID(t.ID).AllCols().Update(t); err != nil { - return fmt.Errorf("update team: %v", err) - } - - if err = repo.recalculateTeamAccesses(e, 0); err != nil { - return fmt.Errorf("recalculateAccesses: %v", err) - } - - if err = t.getMembers(e); err != nil { - return fmt.Errorf("getMembers: %v", err) - } - for _, u := range t.Members { - if err = watchRepo(e, u.ID, repo.ID, true); err != nil { - return fmt.Errorf("watchRepo: %v", err) - } - } - return nil -} - -// AddRepository adds new repository to team of organization. -func (t *Team) AddRepository(repo *Repository) (err error) { - if repo.OwnerID != t.OrgID { - return errors.New("Repository does not belong to organization") - } else if t.HasRepository(repo.ID) { - return nil - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = t.addRepository(sess, repo); err != nil { - return err - } - - return sess.Commit() -} - -func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) { - if err = removeTeamRepo(e, t.ID, repo.ID); err != nil { - return err - } - - t.NumRepos-- - if _, err = e.ID(t.ID).AllCols().Update(t); err != nil { - return err - } - - // Don't need to recalculate when delete a repository from organization. - if recalculate { - if err = repo.recalculateTeamAccesses(e, t.ID); err != nil { - return err - } - } - - if err = t.getMembers(e); err != nil { - return fmt.Errorf("get team members: %v", err) - } - for _, member := range t.Members { - has, err := hasAccess(e, member.ID, repo, ACCESS_MODE_READ) - if err != nil { - return err - } else if has { - continue - } - - if err = watchRepo(e, member.ID, repo.ID, false); err != nil { - return err - } - } - - return nil -} - -// RemoveRepository removes repository from team of organization. -func (t *Team) RemoveRepository(repoID int64) error { - if !t.HasRepository(repoID) { - return nil - } - - repo, err := GetRepositoryByID(repoID) - if err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = t.removeRepository(sess, repo, true); err != nil { - return err - } - - return sess.Commit() -} - -var reservedTeamNames = []string{"new"} - -// IsUsableTeamName return an error if given name is a reserved name or pattern. -func IsUsableTeamName(name string) error { - return isUsableName(reservedTeamNames, nil, name) -} - -// NewTeam creates a record of new team. -// It's caller's responsibility to assign organization ID. -func NewTeam(t *Team) error { - if len(t.Name) == 0 { - return errors.New("empty team name") - } else if t.OrgID == 0 { - return errors.New("OrgID is not assigned") - } - - if err := IsUsableTeamName(t.Name); err != nil { - return err - } - - has, err := x.Id(t.OrgID).Get(new(User)) - if err != nil { - return err - } else if !has { - return ErrOrgNotExist - } - - t.LowerName = strings.ToLower(t.Name) - has, err = x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).Get(new(Team)) - if err != nil { - return err - } else if has { - return ErrTeamAlreadyExist{t.OrgID, t.LowerName} - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Insert(t); err != nil { - sess.Rollback() - return err - } - - // Update organization number of teams. - if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil { - sess.Rollback() - return err - } - return sess.Commit() -} - -func getTeamOfOrgByName(e Engine, orgID int64, name string) (*Team, error) { - t := &Team{ - OrgID: orgID, - LowerName: strings.ToLower(name), - } - has, err := e.Get(t) - if err != nil { - return nil, err - } else if !has { - return nil, errors.TeamNotExist{0, name} - } - return t, nil -} - -// GetTeamOfOrgByName returns team by given team name and organization. -func GetTeamOfOrgByName(orgID int64, name string) (*Team, error) { - return getTeamOfOrgByName(x, orgID, name) -} - -func getTeamByID(e Engine, teamID int64) (*Team, error) { - t := new(Team) - has, err := e.ID(teamID).Get(t) - if err != nil { - return nil, err - } else if !has { - return nil, errors.TeamNotExist{teamID, ""} - } - return t, nil -} - -// GetTeamByID returns team by given ID. -func GetTeamByID(teamID int64) (*Team, error) { - return getTeamByID(x, teamID) -} - -func getTeamsByOrgID(e Engine, orgID int64) ([]*Team, error) { - teams := make([]*Team, 0, 3) - return teams, e.Where("org_id = ?", orgID).Find(&teams) -} - -// GetTeamsByOrgID returns all teams belong to given organization. -func GetTeamsByOrgID(orgID int64) ([]*Team, error) { - return getTeamsByOrgID(x, orgID) -} - -// UpdateTeam updates information of team. -func UpdateTeam(t *Team, authChanged bool) (err error) { - if len(t.Name) == 0 { - return errors.New("empty team name") - } - - if len(t.Description) > 255 { - t.Description = t.Description[:255] - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - t.LowerName = strings.ToLower(t.Name) - has, err := x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).And("id!=?", t.ID).Get(new(Team)) - if err != nil { - return err - } else if has { - return ErrTeamAlreadyExist{t.OrgID, t.LowerName} - } - - if _, err = sess.ID(t.ID).AllCols().Update(t); err != nil { - return fmt.Errorf("update: %v", err) - } - - // Update access for team members if needed. - if authChanged { - if err = t.getRepositories(sess); err != nil { - return fmt.Errorf("getRepositories:%v", err) - } - - for _, repo := range t.Repos { - if err = repo.recalculateTeamAccesses(sess, 0); err != nil { - return fmt.Errorf("recalculateTeamAccesses: %v", err) - } - } - } - - return sess.Commit() -} - -// DeleteTeam deletes given team. -// It's caller's responsibility to assign organization ID. -func DeleteTeam(t *Team) error { - if err := t.GetRepositories(); err != nil { - return err - } - - // Get organization. - org, err := GetUserByID(t.OrgID) - if err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - // Delete all accesses. - for _, repo := range t.Repos { - if err = repo.recalculateTeamAccesses(sess, t.ID); err != nil { - return err - } - } - - // Delete team-user. - if _, err = sess.Where("org_id=?", org.ID).Where("team_id=?", t.ID).Delete(new(TeamUser)); err != nil { - return err - } - - // Delete team. - if _, err = sess.ID(t.ID).Delete(new(Team)); err != nil { - return err - } - // Update organization number of teams. - if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil { - return err - } - - return sess.Commit() -} - -// ___________ ____ ___ -// \__ ___/___ _____ _____ | | \______ ___________ -// | |_/ __ \\__ \ / \| | / ___// __ \_ __ \ -// | |\ ___/ / __ \| Y Y \ | /\___ \\ ___/| | \/ -// |____| \___ >____ /__|_| /______//____ >\___ >__| -// \/ \/ \/ \/ \/ - -// TeamUser represents an team-user relation. -type TeamUser struct { - ID int64 - OrgID int64 `xorm:"INDEX"` - TeamID int64 `xorm:"UNIQUE(s)"` - UID int64 `xorm:"UNIQUE(s)"` -} - -func isTeamMember(e Engine, orgID, teamID, uid int64) bool { - has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("uid=?", uid).Get(new(TeamUser)) - return has -} - -// IsTeamMember returns true if given user is a member of team. -func IsTeamMember(orgID, teamID, uid int64) bool { - return isTeamMember(x, orgID, teamID, uid) -} - -func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) { - teamUsers := make([]*TeamUser, 0, 10) - if err = e.Sql("SELECT `id`, `org_id`, `team_id`, `uid` FROM `team_user` WHERE team_id = ?", teamID). - Find(&teamUsers); err != nil { - return nil, fmt.Errorf("get team-users: %v", err) - } - members := make([]*User, 0, len(teamUsers)) - for i := range teamUsers { - member := new(User) - if _, err = e.ID(teamUsers[i].UID).Get(member); err != nil { - return nil, fmt.Errorf("get user '%d': %v", teamUsers[i].UID, err) - } - members = append(members, member) - } - return members, nil -} - -// GetTeamMembers returns all members in given team of organization. -func GetTeamMembers(teamID int64) ([]*User, error) { - return getTeamMembers(x, teamID) -} - -func getUserTeams(e Engine, orgID, userID int64) ([]*Team, error) { - teamUsers := make([]*TeamUser, 0, 5) - if err := e.Where("uid = ?", userID).And("org_id = ?", orgID).Find(&teamUsers); err != nil { - return nil, err - } - - teamIDs := make([]int64, len(teamUsers)+1) - for i := range teamUsers { - teamIDs[i] = teamUsers[i].TeamID - } - teamIDs[len(teamUsers)] = -1 - - teams := make([]*Team, 0, len(teamIDs)) - return teams, e.Where("org_id = ?", orgID).In("id", teamIDs).Find(&teams) -} - -// GetUserTeams returns all teams that user belongs to in given organization. -func GetUserTeams(orgID, userID int64) ([]*Team, error) { - return getUserTeams(x, orgID, userID) -} - -// AddTeamMember adds new membership of given team to given organization, -// the user will have membership to given organization automatically when needed. -func AddTeamMember(orgID, teamID, userID int64) error { - if IsTeamMember(orgID, teamID, userID) { - return nil - } - - if err := AddOrgUser(orgID, userID); err != nil { - return err - } - - // Get team and its repositories. - t, err := GetTeamByID(teamID) - if err != nil { - return err - } - t.NumMembers++ - - if err = t.GetRepositories(); err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - tu := &TeamUser{ - UID: userID, - OrgID: orgID, - TeamID: teamID, - } - if _, err = sess.Insert(tu); err != nil { - return err - } else if _, err = sess.ID(t.ID).Update(t); err != nil { - return err - } - - // Give access to team repositories. - for _, repo := range t.Repos { - if err = repo.recalculateTeamAccesses(sess, 0); err != nil { - return err - } - } - - // We make sure it exists before. - ou := new(OrgUser) - if _, err = sess.Where("uid = ?", userID).And("org_id = ?", orgID).Get(ou); err != nil { - return err - } - ou.NumTeams++ - if t.IsOwnerTeam() { - ou.IsOwner = true - } - if _, err = sess.ID(ou.ID).AllCols().Update(ou); err != nil { - return err - } - - return sess.Commit() -} - -func removeTeamMember(e Engine, orgID, teamID, uid int64) error { - if !isTeamMember(e, orgID, teamID, uid) { - return nil - } - - // Get team and its repositories. - t, err := getTeamByID(e, teamID) - if err != nil { - return err - } - - // Check if the user to delete is the last member in owner team. - if t.IsOwnerTeam() && t.NumMembers == 1 { - return ErrLastOrgOwner{UID: uid} - } - - t.NumMembers-- - - if err = t.getRepositories(e); err != nil { - return err - } - - // Get organization. - org, err := getUserByID(e, orgID) - if err != nil { - return err - } - - tu := &TeamUser{ - UID: uid, - OrgID: orgID, - TeamID: teamID, - } - if _, err := e.Delete(tu); err != nil { - return err - } else if _, err = e.ID(t.ID).AllCols().Update(t); err != nil { - return err - } - - // Delete access to team repositories. - for _, repo := range t.Repos { - if err = repo.recalculateTeamAccesses(e, 0); err != nil { - return err - } - } - - // This must exist. - ou := new(OrgUser) - _, err = e.Where("uid = ?", uid).And("org_id = ?", org.ID).Get(ou) - if err != nil { - return err - } - ou.NumTeams-- - if t.IsOwnerTeam() { - ou.IsOwner = false - } - if _, err = e.ID(ou.ID).AllCols().Update(ou); err != nil { - return err - } - return nil -} - -// RemoveTeamMember removes member from given team of given organization. -func RemoveTeamMember(orgID, teamID, uid int64) error { - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - if err := removeTeamMember(sess, orgID, teamID, uid); err != nil { - return err - } - return sess.Commit() -} - -// ___________ __________ -// \__ ___/___ _____ _____\______ \ ____ ______ ____ -// | |_/ __ \\__ \ / \| _// __ \\____ \ / _ \ -// | |\ ___/ / __ \| Y Y \ | \ ___/| |_> > <_> ) -// |____| \___ >____ /__|_| /____|_ /\___ > __/ \____/ -// \/ \/ \/ \/ \/|__| - -// TeamRepo represents an team-repository relation. -type TeamRepo struct { - ID int64 - OrgID int64 `xorm:"INDEX"` - TeamID int64 `xorm:"UNIQUE(s)"` - RepoID int64 `xorm:"UNIQUE(s)"` -} - -func hasTeamRepo(e Engine, orgID, teamID, repoID int64) bool { - has, _ := e.Where("org_id = ?", orgID).And("team_id = ?", teamID).And("repo_id = ?", repoID).Get(new(TeamRepo)) - return has -} - -// HasTeamRepo returns true if given team has access to the repository of the organization. -func HasTeamRepo(orgID, teamID, repoID int64) bool { - return hasTeamRepo(x, orgID, teamID, repoID) -} - -func addTeamRepo(e Engine, orgID, teamID, repoID int64) error { - _, err := e.InsertOne(&TeamRepo{ - OrgID: orgID, - TeamID: teamID, - RepoID: repoID, - }) - return err -} - -// AddTeamRepo adds new repository relation to team. -func AddTeamRepo(orgID, teamID, repoID int64) error { - return addTeamRepo(x, orgID, teamID, repoID) -} - -func removeTeamRepo(e Engine, teamID, repoID int64) error { - _, err := e.Delete(&TeamRepo{ - TeamID: teamID, - RepoID: repoID, - }) - return err -} - -// RemoveTeamRepo deletes repository relation to team. -func RemoveTeamRepo(teamID, repoID int64) error { - return removeTeamRepo(x, teamID, repoID) -} - -// GetTeamsHaveAccessToRepo returns all teams in an organization that have given access level to the repository. -func GetTeamsHaveAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, error) { - teams := make([]*Team, 0, 5) - return teams, x.Where("team.authorize >= ?", mode). - Join("INNER", "team_repo", "team_repo.team_id = team.id"). - And("team_repo.org_id = ?", orgID). - And("team_repo.repo_id = ?", repoID). - Find(&teams) -} diff --git a/models/pull.go b/models/pull.go deleted file mode 100644 index edb37c22..00000000 --- a/models/pull.go +++ /dev/null @@ -1,851 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "os" - "path" - "strings" - "time" - - "github.com/unknwon/com" - "xorm.io/xorm" - log "gopkg.in/clog.v1" - - "github.com/gogs/git-module" - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/process" - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/sync" -) - -var PullRequestQueue = sync.NewUniqueQueue(setting.Repository.PullRequestQueueLength) - -type PullRequestType int - -const ( - PULL_REQUEST_GOGS PullRequestType = iota - PLLL_ERQUEST_GIT -) - -type PullRequestStatus int - -const ( - PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota - PULL_REQUEST_STATUS_CHECKING - PULL_REQUEST_STATUS_MERGEABLE -) - -// PullRequest represents relation between pull request and repositories. -type PullRequest struct { - ID int64 - Type PullRequestType - Status PullRequestStatus - - IssueID int64 `xorm:"INDEX"` - Issue *Issue `xorm:"-" json:"-"` - Index int64 - - HeadRepoID int64 - HeadRepo *Repository `xorm:"-" json:"-"` - BaseRepoID int64 - BaseRepo *Repository `xorm:"-" json:"-"` - HeadUserName string - HeadBranch string - BaseBranch string - MergeBase string `xorm:"VARCHAR(40)"` - - HasMerged bool - MergedCommitID string `xorm:"VARCHAR(40)"` - MergerID int64 - Merger *User `xorm:"-" json:"-"` - Merged time.Time `xorm:"-" json:"-"` - MergedUnix int64 -} - -func (pr *PullRequest) BeforeUpdate() { - pr.MergedUnix = pr.Merged.Unix() -} - -// Note: don't try to get Issue because will end up recursive querying. -func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "merged_unix": - if !pr.HasMerged { - return - } - - pr.Merged = time.Unix(pr.MergedUnix, 0).Local() - } -} - -// Note: don't try to get Issue because will end up recursive querying. -func (pr *PullRequest) loadAttributes(e Engine) (err error) { - if pr.HeadRepo == nil { - pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID) - if err != nil && !errors.IsRepoNotExist(err) { - return fmt.Errorf("getRepositoryByID.(HeadRepo) [%d]: %v", pr.HeadRepoID, err) - } - } - - if pr.BaseRepo == nil { - pr.BaseRepo, err = getRepositoryByID(e, pr.BaseRepoID) - if err != nil { - return fmt.Errorf("getRepositoryByID.(BaseRepo) [%d]: %v", pr.BaseRepoID, err) - } - } - - if pr.HasMerged && pr.Merger == nil { - pr.Merger, err = getUserByID(e, pr.MergerID) - if errors.IsUserNotExist(err) { - pr.MergerID = -1 - pr.Merger = NewGhostUser() - } else if err != nil { - return fmt.Errorf("getUserByID [%d]: %v", pr.MergerID, err) - } - } - - return nil -} - -func (pr *PullRequest) LoadAttributes() error { - return pr.loadAttributes(x) -} - -func (pr *PullRequest) LoadIssue() (err error) { - if pr.Issue != nil { - return nil - } - - pr.Issue, err = GetIssueByID(pr.IssueID) - return err -} - -// This method assumes following fields have been assigned with valid values: -// Required - Issue, BaseRepo -// Optional - HeadRepo, Merger -func (pr *PullRequest) APIFormat() *api.PullRequest { - // In case of head repo has been deleted. - var apiHeadRepo *api.Repository - if pr.HeadRepo == nil { - apiHeadRepo = &api.Repository{ - Name: "deleted", - } - } else { - apiHeadRepo = pr.HeadRepo.APIFormat(nil) - } - - apiIssue := pr.Issue.APIFormat() - apiPullRequest := &api.PullRequest{ - ID: pr.ID, - Index: pr.Index, - Poster: apiIssue.Poster, - Title: apiIssue.Title, - Body: apiIssue.Body, - Labels: apiIssue.Labels, - Milestone: apiIssue.Milestone, - Assignee: apiIssue.Assignee, - State: apiIssue.State, - Comments: apiIssue.Comments, - HeadBranch: pr.HeadBranch, - HeadRepo: apiHeadRepo, - BaseBranch: pr.BaseBranch, - BaseRepo: pr.BaseRepo.APIFormat(nil), - HTMLURL: pr.Issue.HTMLURL(), - HasMerged: pr.HasMerged, - } - - if pr.Status != PULL_REQUEST_STATUS_CHECKING { - mergeable := pr.Status != PULL_REQUEST_STATUS_CONFLICT - apiPullRequest.Mergeable = &mergeable - } - if pr.HasMerged { - apiPullRequest.Merged = &pr.Merged - apiPullRequest.MergedCommitID = &pr.MergedCommitID - apiPullRequest.MergedBy = pr.Merger.APIFormat() - } - - return apiPullRequest -} - -// IsChecking returns true if this pull request is still checking conflict. -func (pr *PullRequest) IsChecking() bool { - return pr.Status == PULL_REQUEST_STATUS_CHECKING -} - -// CanAutoMerge returns true if this pull request can be merged automatically. -func (pr *PullRequest) CanAutoMerge() bool { - return pr.Status == PULL_REQUEST_STATUS_MERGEABLE -} - -// MergeStyle represents the approach to merge commits into base branch. -type MergeStyle string - -const ( - MERGE_STYLE_REGULAR MergeStyle = "create_merge_commit" - MERGE_STYLE_REBASE MergeStyle = "rebase_before_merging" -) - -// Merge merges pull request to base repository. -// FIXME: add repoWorkingPull make sure two merges does not happen at same time. -func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle MergeStyle, commitDescription string) (err error) { - defer func() { - go HookQueue.Add(pr.BaseRepo.ID) - go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false) - }() - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = pr.Issue.changeStatus(sess, doer, pr.Issue.Repo, true); err != nil { - return fmt.Errorf("Issue.changeStatus: %v", err) - } - - headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name) - headGitRepo, err := git.OpenRepository(headRepoPath) - if err != nil { - return fmt.Errorf("OpenRepository: %v", err) - } - - // Create temporary directory to store temporary copy of the base repository, - // and clean it up when operation finished regardless of succeed or not. - tmpBasePath := path.Join(setting.AppDataPath, "tmp/repos", com.ToStr(time.Now().Nanosecond())+".git") - os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm) - defer os.RemoveAll(path.Dir(tmpBasePath)) - - // Clone the base repository to the defined temporary directory, - // and checks out to base branch directly. - var stderr string - if _, stderr, err = process.ExecTimeout(5*time.Minute, - fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath), - "git", "clone", "-b", pr.BaseBranch, baseGitRepo.Path, tmpBasePath); err != nil { - return fmt.Errorf("git clone: %s", stderr) - } - - // Add remote which points to the head repository. - if _, stderr, err = process.ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath), - "git", "remote", "add", "head_repo", headRepoPath); err != nil { - return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) - } - - // Fetch information from head repository to the temporary copy. - if _, stderr, err = process.ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath), - "git", "fetch", "head_repo"); err != nil { - return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) - } - - remoteHeadBranch := "head_repo/" + pr.HeadBranch - - // Check if merge style is allowed, reset to default style if not - if mergeStyle == MERGE_STYLE_REBASE && !pr.BaseRepo.PullsAllowRebase { - mergeStyle = MERGE_STYLE_REGULAR - } - - switch mergeStyle { - case MERGE_STYLE_REGULAR: // Create merge commit - - // Merge changes from head branch. - if _, stderr, err = process.ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath), - "git", "merge", "--no-ff", "--no-commit", remoteHeadBranch); err != nil { - return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr) - } - - // Create a merge commit for the base branch. - sig := doer.NewGitSig() - if _, stderr, err = process.ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), - "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), - "-m", fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch), - "-m", commitDescription); err != nil { - return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr) - } - - case MERGE_STYLE_REBASE: // Rebase before merging - - // Rebase head branch based on base branch, this creates a non-branch commit state. - if _, stderr, err = process.ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), - "git", "rebase", "--quiet", pr.BaseBranch, remoteHeadBranch); err != nil { - return fmt.Errorf("git rebase [%s on %s]: %s", remoteHeadBranch, pr.BaseBranch, stderr) - } - - // Name non-branch commit state to a new temporary branch in order to save changes. - tmpBranch := com.ToStr(time.Now().UnixNano(), 10) - if _, stderr, err = process.ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), - "git", "checkout", "-b", tmpBranch); err != nil { - return fmt.Errorf("git checkout '%s': %s", tmpBranch, stderr) - } - - // Check out the base branch to be operated on. - if _, stderr, err = process.ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), - "git", "checkout", pr.BaseBranch); err != nil { - return fmt.Errorf("git checkout '%s': %s", pr.BaseBranch, stderr) - } - - // Merge changes from temporary branch to the base branch. - if _, stderr, err = process.ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), - "git", "merge", tmpBranch); err != nil { - return fmt.Errorf("git merge [%s]: %v - %s", tmpBasePath, err, stderr) - } - - default: - return fmt.Errorf("unknown merge style: %s", mergeStyle) - } - - // Push changes on base branch to upstream. - if _, stderr, err = process.ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath), - "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil { - return fmt.Errorf("git push: %s", stderr) - } - - pr.MergedCommitID, err = headGitRepo.GetBranchCommitID(pr.HeadBranch) - if err != nil { - return fmt.Errorf("GetBranchCommit: %v", err) - } - - pr.HasMerged = true - pr.Merged = time.Now() - pr.MergerID = doer.ID - if _, err = sess.ID(pr.ID).AllCols().Update(pr); err != nil { - return fmt.Errorf("update pull request: %v", err) - } - - if err = sess.Commit(); err != nil { - return fmt.Errorf("Commit: %v", err) - } - - if err = MergePullRequestAction(doer, pr.Issue.Repo, pr.Issue); err != nil { - log.Error(2, "MergePullRequestAction [%d]: %v", pr.ID, err) - } - - // Reload pull request information. - if err = pr.LoadAttributes(); err != nil { - log.Error(2, "LoadAttributes: %v", err) - return nil - } - if err = PrepareWebhooks(pr.Issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ - Action: api.HOOK_ISSUE_CLOSED, - Index: pr.Index, - PullRequest: pr.APIFormat(), - Repository: pr.Issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks: %v", err) - return nil - } - - l, err := headGitRepo.CommitsBetweenIDs(pr.MergedCommitID, pr.MergeBase) - if err != nil { - log.Error(2, "CommitsBetweenIDs: %v", err) - return nil - } - - // It is possible that head branch is not fully sync with base branch for merge commits, - // so we need to get latest head commit and append merge commit manully - // to avoid strange diff commits produced. - mergeCommit, err := baseGitRepo.GetBranchCommit(pr.BaseBranch) - if err != nil { - log.Error(2, "GetBranchCommit: %v", err) - return nil - } - if mergeStyle == MERGE_STYLE_REGULAR { - l.PushFront(mergeCommit) - } - - commits, err := ListToPushCommits(l).ToApiPayloadCommits(pr.BaseRepo.RepoPath(), pr.BaseRepo.HTMLURL()) - if err != nil { - log.Error(2, "ToApiPayloadCommits: %v", err) - return nil - } - - p := &api.PushPayload{ - Ref: git.BRANCH_PREFIX + pr.BaseBranch, - Before: pr.MergeBase, - After: mergeCommit.ID.String(), - CompareURL: setting.AppURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID), - Commits: commits, - Repo: pr.BaseRepo.APIFormat(nil), - Pusher: pr.HeadRepo.MustOwner().APIFormat(), - Sender: doer.APIFormat(), - } - if err = PrepareWebhooks(pr.BaseRepo, HOOK_EVENT_PUSH, p); err != nil { - log.Error(2, "PrepareWebhooks: %v", err) - return nil - } - return nil -} - -// testPatch checks if patch can be merged to base repository without conflit. -// FIXME: make a mechanism to clean up stable local copies. -func (pr *PullRequest) testPatch() (err error) { - if pr.BaseRepo == nil { - pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID) - if err != nil { - return fmt.Errorf("GetRepositoryByID: %v", err) - } - } - - patchPath, err := pr.BaseRepo.PatchPath(pr.Index) - if err != nil { - return fmt.Errorf("BaseRepo.PatchPath: %v", err) - } - - // Fast fail if patch does not exist, this assumes data is cruppted. - if !com.IsFile(patchPath) { - log.Trace("PullRequest[%d].testPatch: ignored cruppted data", pr.ID) - return nil - } - - repoWorkingPool.CheckIn(com.ToStr(pr.BaseRepoID)) - defer repoWorkingPool.CheckOut(com.ToStr(pr.BaseRepoID)) - - log.Trace("PullRequest[%d].testPatch (patchPath): %s", pr.ID, patchPath) - - if err := pr.BaseRepo.UpdateLocalCopyBranch(pr.BaseBranch); err != nil { - return fmt.Errorf("UpdateLocalCopy [%d]: %v", pr.BaseRepoID, err) - } - - args := []string{"apply", "--check"} - if pr.BaseRepo.PullsIgnoreWhitespace { - args = append(args, "--ignore-whitespace") - } - args = append(args, patchPath) - - pr.Status = PULL_REQUEST_STATUS_CHECKING - _, stderr, err := process.ExecDir(-1, pr.BaseRepo.LocalCopyPath(), - fmt.Sprintf("testPatch (git apply --check): %d", pr.BaseRepo.ID), - "git", args...) - if err != nil { - log.Trace("PullRequest[%d].testPatch (apply): has conflit\n%s", pr.ID, stderr) - pr.Status = PULL_REQUEST_STATUS_CONFLICT - return nil - } - return nil -} - -// NewPullRequest creates new pull request with labels for repository. -func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = newIssue(sess, NewIssueOptions{ - Repo: repo, - Issue: pull, - LableIDs: labelIDs, - Attachments: uuids, - IsPull: true, - }); err != nil { - return fmt.Errorf("newIssue: %v", err) - } - - pr.Index = pull.Index - if err = repo.SavePatch(pr.Index, patch); err != nil { - return fmt.Errorf("SavePatch: %v", err) - } - - pr.BaseRepo = repo - if err = pr.testPatch(); err != nil { - return fmt.Errorf("testPatch: %v", err) - } - // No conflict appears after test means mergeable. - if pr.Status == PULL_REQUEST_STATUS_CHECKING { - pr.Status = PULL_REQUEST_STATUS_MERGEABLE - } - - pr.IssueID = pull.ID - if _, err = sess.Insert(pr); err != nil { - return fmt.Errorf("insert pull repo: %v", err) - } - - if err = sess.Commit(); err != nil { - return fmt.Errorf("Commit: %v", err) - } - - if err = NotifyWatchers(&Action{ - ActUserID: pull.Poster.ID, - ActUserName: pull.Poster.Name, - OpType: ACTION_CREATE_PULL_REQUEST, - Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title), - RepoID: repo.ID, - RepoUserName: repo.Owner.Name, - RepoName: repo.Name, - IsPrivate: repo.IsPrivate, - }); err != nil { - log.Error(2, "NotifyWatchers: %v", err) - } - if err = pull.MailParticipants(); err != nil { - log.Error(2, "MailParticipants: %v", err) - } - - pr.Issue = pull - pull.PullRequest = pr - if err = PrepareWebhooks(repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ - Action: api.HOOK_ISSUE_OPENED, - Index: pull.Index, - PullRequest: pr.APIFormat(), - Repository: repo.APIFormat(nil), - Sender: pull.Poster.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks: %v", err) - } - - return nil -} - -// GetUnmergedPullRequest returnss a pull request that is open and has not been merged -// by given head/base and repo/branch. -func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) { - pr := new(PullRequest) - has, err := x.Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?", - headRepoID, headBranch, baseRepoID, baseBranch, false, false). - Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr) - if err != nil { - return nil, err - } else if !has { - return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch} - } - - return pr, nil -} - -// GetUnmergedPullRequestsByHeadInfo returnss all pull requests that are open and has not been merged -// by given head information (repo and branch). -func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) { - prs := make([]*PullRequest, 0, 2) - return prs, x.Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ?", - repoID, branch, false, false). - Join("INNER", "issue", "issue.id = pull_request.issue_id").Find(&prs) -} - -// GetUnmergedPullRequestsByBaseInfo returnss all pull requests that are open and has not been merged -// by given base information (repo and branch). -func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) { - prs := make([]*PullRequest, 0, 2) - return prs, x.Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?", - repoID, branch, false, false). - Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs) -} - -func getPullRequestByID(e Engine, id int64) (*PullRequest, error) { - pr := new(PullRequest) - has, err := e.ID(id).Get(pr) - if err != nil { - return nil, err - } else if !has { - return nil, ErrPullRequestNotExist{id, 0, 0, 0, "", ""} - } - return pr, pr.loadAttributes(e) -} - -// GetPullRequestByID returns a pull request by given ID. -func GetPullRequestByID(id int64) (*PullRequest, error) { - return getPullRequestByID(x, id) -} - -func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) { - pr := &PullRequest{ - IssueID: issueID, - } - has, err := e.Get(pr) - if err != nil { - return nil, err - } else if !has { - return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""} - } - return pr, pr.loadAttributes(e) -} - -// GetPullRequestByIssueID returns pull request by given issue ID. -func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) { - return getPullRequestByIssueID(x, issueID) -} - -// Update updates all fields of pull request. -func (pr *PullRequest) Update() error { - _, err := x.Id(pr.ID).AllCols().Update(pr) - return err -} - -// Update updates specific fields of pull request. -func (pr *PullRequest) UpdateCols(cols ...string) error { - _, err := x.Id(pr.ID).Cols(cols...).Update(pr) - return err -} - -// UpdatePatch generates and saves a new patch. -func (pr *PullRequest) UpdatePatch() (err error) { - if pr.HeadRepo == nil { - log.Trace("PullRequest[%d].UpdatePatch: ignored cruppted data", pr.ID) - return nil - } - - headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath()) - if err != nil { - return fmt.Errorf("OpenRepository: %v", err) - } - - // Add a temporary remote. - tmpRemote := com.ToStr(time.Now().UnixNano()) - if err = headGitRepo.AddRemote(tmpRemote, RepoPath(pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name), true); err != nil { - return fmt.Errorf("AddRemote: %v", err) - } - defer func() { - headGitRepo.RemoveRemote(tmpRemote) - }() - remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch - pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch) - if err != nil { - return fmt.Errorf("GetMergeBase: %v", err) - } else if err = pr.Update(); err != nil { - return fmt.Errorf("Update: %v", err) - } - - patch, err := headGitRepo.GetPatch(pr.MergeBase, pr.HeadBranch) - if err != nil { - return fmt.Errorf("GetPatch: %v", err) - } - - if err = pr.BaseRepo.SavePatch(pr.Index, patch); err != nil { - return fmt.Errorf("BaseRepo.SavePatch: %v", err) - } - - return nil -} - -// PushToBaseRepo pushes commits from branches of head repository to -// corresponding branches of base repository. -// FIXME: Only push branches that are actually updates? -func (pr *PullRequest) PushToBaseRepo() (err error) { - log.Trace("PushToBaseRepo[%d]: pushing commits to base repo 'refs/pull/%d/head'", pr.BaseRepoID, pr.Index) - - headRepoPath := pr.HeadRepo.RepoPath() - headGitRepo, err := git.OpenRepository(headRepoPath) - if err != nil { - return fmt.Errorf("OpenRepository: %v", err) - } - - tmpRemoteName := fmt.Sprintf("tmp-pull-%d", pr.ID) - if err = headGitRepo.AddRemote(tmpRemoteName, pr.BaseRepo.RepoPath(), false); err != nil { - return fmt.Errorf("headGitRepo.AddRemote: %v", err) - } - // Make sure to remove the remote even if the push fails - defer headGitRepo.RemoveRemote(tmpRemoteName) - - headFile := fmt.Sprintf("refs/pull/%d/head", pr.Index) - - // Remove head in case there is a conflict. - os.Remove(path.Join(pr.BaseRepo.RepoPath(), headFile)) - - if err = git.Push(headRepoPath, tmpRemoteName, fmt.Sprintf("%s:%s", pr.HeadBranch, headFile)); err != nil { - return fmt.Errorf("Push: %v", err) - } - - return nil -} - -// AddToTaskQueue adds itself to pull request test task queue. -func (pr *PullRequest) AddToTaskQueue() { - go PullRequestQueue.AddFunc(pr.ID, func() { - pr.Status = PULL_REQUEST_STATUS_CHECKING - if err := pr.UpdateCols("status"); err != nil { - log.Error(3, "AddToTaskQueue.UpdateCols[%d].(add to queue): %v", pr.ID, err) - } - }) -} - -type PullRequestList []*PullRequest - -func (prs PullRequestList) loadAttributes(e Engine) (err error) { - if len(prs) == 0 { - return nil - } - - // Load issues - set := make(map[int64]*Issue) - for i := range prs { - set[prs[i].IssueID] = nil - } - issueIDs := make([]int64, 0, len(prs)) - for issueID := range set { - issueIDs = append(issueIDs, issueID) - } - issues := make([]*Issue, 0, len(issueIDs)) - if err = e.Where("id > 0").In("id", issueIDs).Find(&issues); err != nil { - return fmt.Errorf("find issues: %v", err) - } - for i := range issues { - set[issues[i].ID] = issues[i] - } - for i := range prs { - prs[i].Issue = set[prs[i].IssueID] - } - - // Load attributes - for i := range prs { - if err = prs[i].loadAttributes(e); err != nil { - return fmt.Errorf("loadAttributes [%d]: %v", prs[i].ID, err) - } - } - - return nil -} - -func (prs PullRequestList) LoadAttributes() error { - return prs.loadAttributes(x) -} - -func addHeadRepoTasks(prs []*PullRequest) { - for _, pr := range prs { - log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID) - if err := pr.UpdatePatch(); err != nil { - log.Error(4, "UpdatePatch: %v", err) - continue - } else if err := pr.PushToBaseRepo(); err != nil { - log.Error(4, "PushToBaseRepo: %v", err) - continue - } - - pr.AddToTaskQueue() - } -} - -// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch, -// and generate new patch for testing as needed. -func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool) { - log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch) - prs, err := GetUnmergedPullRequestsByHeadInfo(repoID, branch) - if err != nil { - log.Error(2, "Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err) - return - } - - if isSync { - if err = PullRequestList(prs).LoadAttributes(); err != nil { - log.Error(2, "PullRequestList.LoadAttributes: %v", err) - } - - if err == nil { - for _, pr := range prs { - pr.Issue.PullRequest = pr - if err = pr.Issue.LoadAttributes(); err != nil { - log.Error(2, "LoadAttributes: %v", err) - continue - } - if err = PrepareWebhooks(pr.Issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ - Action: api.HOOK_ISSUE_SYNCHRONIZED, - Index: pr.Issue.Index, - PullRequest: pr.Issue.PullRequest.APIFormat(), - Repository: pr.Issue.Repo.APIFormat(nil), - Sender: doer.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks [pull_id: %v]: %v", pr.ID, err) - continue - } - } - } - } - - addHeadRepoTasks(prs) - - log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch) - prs, err = GetUnmergedPullRequestsByBaseInfo(repoID, branch) - if err != nil { - log.Error(2, "Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err) - return - } - for _, pr := range prs { - pr.AddToTaskQueue() - } -} - -func ChangeUsernameInPullRequests(oldUserName, newUserName string) error { - pr := PullRequest{ - HeadUserName: strings.ToLower(newUserName), - } - _, err := x.Cols("head_user_name").Where("head_user_name = ?", strings.ToLower(oldUserName)).Update(pr) - return err -} - -// checkAndUpdateStatus checks if pull request is possible to levaing checking status, -// and set to be either conflict or mergeable. -func (pr *PullRequest) checkAndUpdateStatus() { - // Status is not changed to conflict means mergeable. - if pr.Status == PULL_REQUEST_STATUS_CHECKING { - pr.Status = PULL_REQUEST_STATUS_MERGEABLE - } - - // Make sure there is no waiting test to process before levaing the checking status. - if !PullRequestQueue.Exist(pr.ID) { - if err := pr.UpdateCols("status"); err != nil { - log.Error(4, "Update[%d]: %v", pr.ID, err) - } - } -} - -// TestPullRequests checks and tests untested patches of pull requests. -// TODO: test more pull requests at same time. -func TestPullRequests() { - prs := make([]*PullRequest, 0, 10) - x.Iterate(PullRequest{ - Status: PULL_REQUEST_STATUS_CHECKING, - }, - func(idx int, bean interface{}) error { - pr := bean.(*PullRequest) - - if err := pr.LoadAttributes(); err != nil { - log.Error(3, "LoadAttributes: %v", err) - return nil - } - - if err := pr.testPatch(); err != nil { - log.Error(3, "testPatch: %v", err) - return nil - } - prs = append(prs, pr) - return nil - }) - - // Update pull request status. - for _, pr := range prs { - pr.checkAndUpdateStatus() - } - - // Start listening on new test requests. - for prID := range PullRequestQueue.Queue() { - log.Trace("TestPullRequests[%v]: processing test task", prID) - PullRequestQueue.Remove(prID) - - pr, err := GetPullRequestByID(com.StrTo(prID).MustInt64()) - if err != nil { - log.Error(4, "GetPullRequestByID[%s]: %v", prID, err) - continue - } else if err = pr.testPatch(); err != nil { - log.Error(4, "testPatch[%d]: %v", pr.ID, err) - continue - } - - pr.checkAndUpdateStatus() - } -} - -func InitTestPullRequests() { - go TestPullRequests() -} diff --git a/models/release.go b/models/release.go deleted file mode 100644 index 26103734..00000000 --- a/models/release.go +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "sort" - "strings" - "time" - - "xorm.io/xorm" - log "gopkg.in/clog.v1" - - "github.com/gogs/git-module" - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/process" -) - -// Release represents a release of repository. -type Release struct { - ID int64 - RepoID int64 - Repo *Repository `xorm:"-" json:"-"` - PublisherID int64 - Publisher *User `xorm:"-" json:"-"` - TagName string - LowerTagName string - Target string - Title string - Sha1 string `xorm:"VARCHAR(40)"` - NumCommits int64 - NumCommitsBehind int64 `xorm:"-" json:"-"` - Note string `xorm:"TEXT"` - IsDraft bool `xorm:"NOT NULL DEFAULT false"` - IsPrerelease bool - - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 - - Attachments []*Attachment `xorm:"-" json:"-"` -} - -func (r *Release) BeforeInsert() { - if r.CreatedUnix == 0 { - r.CreatedUnix = time.Now().Unix() - } -} - -func (r *Release) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - r.Created = time.Unix(r.CreatedUnix, 0).Local() - } -} - -func (r *Release) loadAttributes(e Engine) (err error) { - if r.Repo == nil { - r.Repo, err = getRepositoryByID(e, r.RepoID) - if err != nil { - return fmt.Errorf("getRepositoryByID [repo_id: %d]: %v", r.RepoID, err) - } - } - - if r.Publisher == nil { - r.Publisher, err = getUserByID(e, r.PublisherID) - if err != nil { - if errors.IsUserNotExist(err) { - r.PublisherID = -1 - r.Publisher = NewGhostUser() - } else { - return fmt.Errorf("getUserByID.(Publisher) [publisher_id: %d]: %v", r.PublisherID, err) - } - } - } - - if r.Attachments == nil { - r.Attachments, err = getAttachmentsByReleaseID(e, r.ID) - if err != nil { - return fmt.Errorf("getAttachmentsByReleaseID [%d]: %v", r.ID, err) - } - } - - return nil -} - -func (r *Release) LoadAttributes() error { - return r.loadAttributes(x) -} - -// This method assumes some fields assigned with values: -// Required - Publisher -func (r *Release) APIFormat() *api.Release { - return &api.Release{ - ID: r.ID, - TagName: r.TagName, - TargetCommitish: r.Target, - Name: r.Title, - Body: r.Note, - Draft: r.IsDraft, - Prerelease: r.IsPrerelease, - Author: r.Publisher.APIFormat(), - Created: r.Created, - } -} - -// IsReleaseExist returns true if release with given tag name already exists. -func IsReleaseExist(repoID int64, tagName string) (bool, error) { - if len(tagName) == 0 { - return false, nil - } - - return x.Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}) -} - -func createTag(gitRepo *git.Repository, r *Release) error { - // Only actual create when publish. - if !r.IsDraft { - if !gitRepo.IsTagExist(r.TagName) { - commit, err := gitRepo.GetBranchCommit(r.Target) - if err != nil { - return fmt.Errorf("GetBranchCommit: %v", err) - } - - // Trim '--' prefix to prevent command line argument vulnerability. - r.TagName = strings.TrimPrefix(r.TagName, "--") - if err = gitRepo.CreateTag(r.TagName, commit.ID.String()); err != nil { - if strings.Contains(err.Error(), "is not a valid tag name") { - return ErrInvalidTagName{r.TagName} - } - return err - } - } else { - commit, err := gitRepo.GetTagCommit(r.TagName) - if err != nil { - return fmt.Errorf("GetTagCommit: %v", err) - } - - r.Sha1 = commit.ID.String() - r.NumCommits, err = commit.CommitsCount() - if err != nil { - return fmt.Errorf("CommitsCount: %v", err) - } - } - } - return nil -} - -func (r *Release) preparePublishWebhooks() { - if err := PrepareWebhooks(r.Repo, HOOK_EVENT_RELEASE, &api.ReleasePayload{ - Action: api.HOOK_RELEASE_PUBLISHED, - Release: r.APIFormat(), - Repository: r.Repo.APIFormat(nil), - Sender: r.Publisher.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks: %v", err) - } -} - -// NewRelease creates a new release with attachments for repository. -func NewRelease(gitRepo *git.Repository, r *Release, uuids []string) error { - isExist, err := IsReleaseExist(r.RepoID, r.TagName) - if err != nil { - return err - } else if isExist { - return ErrReleaseAlreadyExist{r.TagName} - } - - if err = createTag(gitRepo, r); err != nil { - return err - } - r.LowerTagName = strings.ToLower(r.TagName) - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Insert(r); err != nil { - return fmt.Errorf("Insert: %v", err) - } - - if len(uuids) > 0 { - if _, err = sess.In("uuid", uuids).Cols("release_id").Update(&Attachment{ReleaseID: r.ID}); err != nil { - return fmt.Errorf("link attachments: %v", err) - } - } - - if err = sess.Commit(); err != nil { - return fmt.Errorf("Commit: %v", err) - } - - // Only send webhook when actually published, skip drafts - if r.IsDraft { - return nil - } - r, err = GetReleaseByID(r.ID) - if err != nil { - return fmt.Errorf("GetReleaseByID: %v", err) - } - r.preparePublishWebhooks() - return nil -} - -// GetRelease returns release by given ID. -func GetRelease(repoID int64, tagName string) (*Release, error) { - isExist, err := IsReleaseExist(repoID, tagName) - if err != nil { - return nil, err - } else if !isExist { - return nil, ErrReleaseNotExist{0, tagName} - } - - r := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)} - if _, err = x.Get(r); err != nil { - return nil, fmt.Errorf("Get: %v", err) - } - - return r, r.LoadAttributes() -} - -// GetReleaseByID returns release with given ID. -func GetReleaseByID(id int64) (*Release, error) { - r := new(Release) - has, err := x.Id(id).Get(r) - if err != nil { - return nil, err - } else if !has { - return nil, ErrReleaseNotExist{id, ""} - } - - return r, r.LoadAttributes() -} - -// GetPublishedReleasesByRepoID returns a list of published releases of repository. -// If matches is not empty, only published releases in matches will be returned. -// In any case, drafts won't be returned by this function. -func GetPublishedReleasesByRepoID(repoID int64, matches ...string) ([]*Release, error) { - sess := x.Where("repo_id = ?", repoID).And("is_draft = ?", false).Desc("created_unix") - if len(matches) > 0 { - sess.In("tag_name", matches) - } - releases := make([]*Release, 0, 5) - return releases, sess.Find(&releases, new(Release)) -} - -// GetDraftReleasesByRepoID returns all draft releases of repository. -func GetDraftReleasesByRepoID(repoID int64) ([]*Release, error) { - releases := make([]*Release, 0) - return releases, x.Where("repo_id = ?", repoID).And("is_draft = ?", true).Find(&releases) -} - -type ReleaseSorter struct { - releases []*Release -} - -func (rs *ReleaseSorter) Len() int { - return len(rs.releases) -} - -func (rs *ReleaseSorter) Less(i, j int) bool { - diffNum := rs.releases[i].NumCommits - rs.releases[j].NumCommits - if diffNum != 0 { - return diffNum > 0 - } - return rs.releases[i].Created.After(rs.releases[j].Created) -} - -func (rs *ReleaseSorter) Swap(i, j int) { - rs.releases[i], rs.releases[j] = rs.releases[j], rs.releases[i] -} - -// SortReleases sorts releases by number of commits and created time. -func SortReleases(rels []*Release) { - sorter := &ReleaseSorter{releases: rels} - sort.Sort(sorter) -} - -// UpdateRelease updates information of a release. -func UpdateRelease(doer *User, gitRepo *git.Repository, r *Release, isPublish bool, uuids []string) (err error) { - if err = createTag(gitRepo, r); err != nil { - return fmt.Errorf("createTag: %v", err) - } - - r.PublisherID = doer.ID - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - if _, err = sess.ID(r.ID).AllCols().Update(r); err != nil { - return fmt.Errorf("Update: %v", err) - } - - // Unlink all current attachments and link back later if still valid - if _, err = sess.Exec("UPDATE attachment SET release_id = 0 WHERE release_id = ?", r.ID); err != nil { - return fmt.Errorf("unlink current attachments: %v", err) - } - - if len(uuids) > 0 { - if _, err = sess.In("uuid", uuids).Cols("release_id").Update(&Attachment{ReleaseID: r.ID}); err != nil { - return fmt.Errorf("link attachments: %v", err) - } - } - - if err = sess.Commit(); err != nil { - return fmt.Errorf("Commit: %v", err) - } - - if !isPublish { - return nil - } - r.Publisher = doer - r.preparePublishWebhooks() - return nil -} - -// DeleteReleaseOfRepoByID deletes a release and corresponding Git tag by given ID. -func DeleteReleaseOfRepoByID(repoID, id int64) error { - rel, err := GetReleaseByID(id) - if err != nil { - return fmt.Errorf("GetReleaseByID: %v", err) - } - - // Mark sure the delete operation againsts same repository. - if repoID != rel.RepoID { - return nil - } - - repo, err := GetRepositoryByID(rel.RepoID) - if err != nil { - return fmt.Errorf("GetRepositoryByID: %v", err) - } - - _, stderr, err := process.ExecDir(-1, repo.RepoPath(), - fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID), - "git", "tag", "-d", rel.TagName) - if err != nil && !strings.Contains(stderr, "not found") { - return fmt.Errorf("git tag -d: %v - %s", err, stderr) - } - - if _, err = x.Id(rel.ID).Delete(new(Release)); err != nil { - return fmt.Errorf("Delete: %v", err) - } - - return nil -} diff --git a/models/repo.go b/models/repo.go deleted file mode 100644 index f456934d..00000000 --- a/models/repo.go +++ /dev/null @@ -1,2458 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "bytes" - "fmt" - "image" - _ "image/jpeg" - "image/png" - "io/ioutil" - "os" - "os/exec" - "path" - "path/filepath" - "sort" - "strings" - "time" - - "github.com/unknwon/cae/zip" - "github.com/unknwon/com" - "xorm.io/xorm" - "github.com/mcuadros/go-version" - "github.com/nfnt/resize" - log "gopkg.in/clog.v1" - "gopkg.in/ini.v1" - - git "github.com/gogs/git-module" - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/avatar" - "gogs.io/gogs/pkg/bindata" - "gogs.io/gogs/pkg/markup" - "gogs.io/gogs/pkg/process" - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/sync" -) - -// REPO_AVATAR_URL_PREFIX is used to identify a URL is to access repository avatar. -const REPO_AVATAR_URL_PREFIX = "repo-avatars" - -var repoWorkingPool = sync.NewExclusivePool() - -var ( - Gitignores, Licenses, Readmes, LabelTemplates []string - - // Maximum items per page in forks, watchers and stars of a repo - ItemsPerPage = 40 -) - -func LoadRepoConfig() { - // Load .gitignore and license files and readme templates. - types := []string{"gitignore", "license", "readme", "label"} - typeFiles := make([][]string, 4) - for i, t := range types { - files, err := bindata.AssetDir("conf/" + t) - if err != nil { - log.Fatal(4, "Fail to get %s files: %v", t, err) - } - customPath := path.Join(setting.CustomPath, "conf", t) - if com.IsDir(customPath) { - customFiles, err := com.StatDir(customPath) - if err != nil { - log.Fatal(4, "Fail to get custom %s files: %v", t, err) - } - - for _, f := range customFiles { - if !com.IsSliceContainsStr(files, f) { - files = append(files, f) - } - } - } - typeFiles[i] = files - } - - Gitignores = typeFiles[0] - Licenses = typeFiles[1] - Readmes = typeFiles[2] - LabelTemplates = typeFiles[3] - sort.Strings(Gitignores) - sort.Strings(Licenses) - sort.Strings(Readmes) - sort.Strings(LabelTemplates) - - // Filter out invalid names and promote preferred licenses. - sortedLicenses := make([]string, 0, len(Licenses)) - for _, name := range setting.Repository.PreferredLicenses { - if com.IsSliceContainsStr(Licenses, name) { - sortedLicenses = append(sortedLicenses, name) - } - } - for _, name := range Licenses { - if !com.IsSliceContainsStr(setting.Repository.PreferredLicenses, name) { - sortedLicenses = append(sortedLicenses, name) - } - } - Licenses = sortedLicenses -} - -func NewRepoContext() { - zip.Verbose = false - - // Check Git installation. - if _, err := exec.LookPath("git"); err != nil { - log.Fatal(4, "Fail to test 'git' command: %v (forgotten install?)", err) - } - - // Check Git version. - var err error - setting.Git.Version, err = git.BinVersion() - if err != nil { - log.Fatal(4, "Fail to get Git version: %v", err) - } - - log.Info("Git Version: %s", setting.Git.Version) - if version.Compare("1.7.1", setting.Git.Version, ">") { - log.Fatal(4, "Gogs requires Git version greater or equal to 1.7.1") - } - git.HookDir = "custom_hooks" - git.HookSampleDir = "hooks" - git.DefaultCommitsPageSize = setting.UI.User.CommitsPagingNum - - // Git requires setting user.name and user.email in order to commit changes. - for configKey, defaultValue := range map[string]string{"user.name": "Gogs", "user.email": "gogs@fake.local"} { - if stdout, stderr, err := process.Exec("NewRepoContext(get setting)", "git", "config", "--get", configKey); err != nil || strings.TrimSpace(stdout) == "" { - // ExitError indicates this config is not set - if _, ok := err.(*exec.ExitError); ok || strings.TrimSpace(stdout) == "" { - if _, stderr, gerr := process.Exec("NewRepoContext(set "+configKey+")", "git", "config", "--global", configKey, defaultValue); gerr != nil { - log.Fatal(4, "Fail to set git %s(%s): %s", configKey, gerr, stderr) - } - log.Info("Git config %s set to %s", configKey, defaultValue) - } else { - log.Fatal(4, "Fail to get git %s(%s): %s", configKey, err, stderr) - } - } - } - - // Set git some configurations. - if _, stderr, err := process.Exec("NewRepoContext(git config --global core.quotepath false)", - "git", "config", "--global", "core.quotepath", "false"); err != nil { - log.Fatal(4, "Fail to execute 'git config --global core.quotepath false': %s", stderr) - } - - RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp")) -} - -// Repository contains information of a repository. -type Repository struct { - ID int64 - OwnerID int64 `xorm:"UNIQUE(s)"` - Owner *User `xorm:"-" json:"-"` - LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` - Name string `xorm:"INDEX NOT NULL"` - Description string `xorm:"VARCHAR(512)"` - Website string - DefaultBranch string - Size int64 `xorm:"NOT NULL DEFAULT 0"` - UseCustomAvatar bool - - // Counters - NumWatches int - NumStars int - NumForks int - NumIssues int - NumClosedIssues int - NumOpenIssues int `xorm:"-" json:"-"` - NumPulls int - NumClosedPulls int - NumOpenPulls int `xorm:"-" json:"-"` - NumMilestones int `xorm:"NOT NULL DEFAULT 0"` - NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` - NumOpenMilestones int `xorm:"-" json:"-"` - NumTags int `xorm:"-" json:"-"` - - IsPrivate bool - IsBare bool - - IsMirror bool - *Mirror `xorm:"-" json:"-"` - - // Advanced settings - EnableWiki bool `xorm:"NOT NULL DEFAULT true"` - AllowPublicWiki bool - EnableExternalWiki bool - ExternalWikiURL string - EnableIssues bool `xorm:"NOT NULL DEFAULT true"` - AllowPublicIssues bool - EnableExternalTracker bool - ExternalTrackerURL string - ExternalTrackerFormat string - ExternalTrackerStyle string - ExternalMetas map[string]string `xorm:"-" json:"-"` - EnablePulls bool `xorm:"NOT NULL DEFAULT true"` - PullsIgnoreWhitespace bool `xorm:"NOT NULL DEFAULT false"` - PullsAllowRebase bool `xorm:"NOT NULL DEFAULT false"` - - IsFork bool `xorm:"NOT NULL DEFAULT false"` - ForkID int64 - BaseRepo *Repository `xorm:"-" json:"-"` - - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-"` - UpdatedUnix int64 -} - -func (repo *Repository) BeforeInsert() { - repo.CreatedUnix = time.Now().Unix() - repo.UpdatedUnix = repo.CreatedUnix -} - -func (repo *Repository) BeforeUpdate() { - repo.UpdatedUnix = time.Now().Unix() -} - -func (repo *Repository) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "default_branch": - // FIXME: use models migration to solve all at once. - if len(repo.DefaultBranch) == 0 { - repo.DefaultBranch = "master" - } - case "num_closed_issues": - repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues - case "num_closed_pulls": - repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls - case "num_closed_milestones": - repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones - case "external_tracker_style": - if len(repo.ExternalTrackerStyle) == 0 { - repo.ExternalTrackerStyle = markup.ISSUE_NAME_STYLE_NUMERIC - } - case "created_unix": - repo.Created = time.Unix(repo.CreatedUnix, 0).Local() - case "updated_unix": - repo.Updated = time.Unix(repo.UpdatedUnix, 0) - } -} - -func (repo *Repository) loadAttributes(e Engine) (err error) { - if repo.Owner == nil { - repo.Owner, err = getUserByID(e, repo.OwnerID) - if err != nil { - return fmt.Errorf("getUserByID [%d]: %v", repo.OwnerID, err) - } - } - - if repo.IsFork && repo.BaseRepo == nil { - repo.BaseRepo, err = getRepositoryByID(e, repo.ForkID) - if err != nil { - if errors.IsRepoNotExist(err) { - repo.IsFork = false - repo.ForkID = 0 - } else { - return fmt.Errorf("getRepositoryByID [%d]: %v", repo.ForkID, err) - } - } - } - - return nil -} - -func (repo *Repository) LoadAttributes() error { - return repo.loadAttributes(x) -} - -// IsPartialPublic returns true if repository is public or allow public access to wiki or issues. -func (repo *Repository) IsPartialPublic() bool { - return !repo.IsPrivate || repo.AllowPublicWiki || repo.AllowPublicIssues -} - -func (repo *Repository) CanGuestViewWiki() bool { - return repo.EnableWiki && !repo.EnableExternalWiki && repo.AllowPublicWiki -} - -func (repo *Repository) CanGuestViewIssues() bool { - return repo.EnableIssues && !repo.EnableExternalTracker && repo.AllowPublicIssues -} - -// MustOwner always returns a valid *User object to avoid conceptually impossible error handling. -// It creates a fake object that contains error deftail when error occurs. -func (repo *Repository) MustOwner() *User { - return repo.mustOwner(x) -} - -func (repo *Repository) FullName() string { - return repo.MustOwner().Name + "/" + repo.Name -} - -func (repo *Repository) HTMLURL() string { - return setting.AppURL + repo.FullName() -} - -// CustomAvatarPath returns repository custom avatar file path. -func (repo *Repository) CustomAvatarPath() string { - return filepath.Join(setting.RepositoryAvatarUploadPath, com.ToStr(repo.ID)) -} - -// RelAvatarLink returns relative avatar link to the site domain, -// which includes app sub-url as prefix. -// Since Gravatar support not needed here - just check for image path. -func (repo *Repository) RelAvatarLink() string { - defaultImgUrl := "" - if !com.IsExist(repo.CustomAvatarPath()) { - return defaultImgUrl - } - return fmt.Sprintf("%s/%s/%d", setting.AppSubURL, REPO_AVATAR_URL_PREFIX, repo.ID) -} - -// AvatarLink returns repository avatar absolute link. -func (repo *Repository) AvatarLink() string { - link := repo.RelAvatarLink() - if link[0] == '/' && link[1] != '/' { - return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:] - } - return link -} - -// UploadAvatar saves custom avatar for repository. -// FIXME: split uploads to different subdirs in case we have massive number of repositories. -func (repo *Repository) UploadAvatar(data []byte) error { - img, _, err := image.Decode(bytes.NewReader(data)) - if err != nil { - return fmt.Errorf("decode image: %v", err) - } - - os.MkdirAll(setting.RepositoryAvatarUploadPath, os.ModePerm) - fw, err := os.Create(repo.CustomAvatarPath()) - if err != nil { - return fmt.Errorf("create custom avatar directory: %v", err) - } - defer fw.Close() - - m := resize.Resize(avatar.AVATAR_SIZE, avatar.AVATAR_SIZE, img, resize.NearestNeighbor) - if err = png.Encode(fw, m); err != nil { - return fmt.Errorf("encode image: %v", err) - } - - return nil -} - -// DeleteAvatar deletes the repository custom avatar. -func (repo *Repository) DeleteAvatar() error { - log.Trace("DeleteAvatar [%d]: %s", repo.ID, repo.CustomAvatarPath()) - if err := os.Remove(repo.CustomAvatarPath()); err != nil { - return err - } - - repo.UseCustomAvatar = false - return UpdateRepository(repo, false) -} - -// This method assumes following fields have been assigned with valid values: -// Required - BaseRepo (if fork) -// Arguments that are allowed to be nil: permission -func (repo *Repository) APIFormat(permission *api.Permission, user ...*User) *api.Repository { - cloneLink := repo.CloneLink() - apiRepo := &api.Repository{ - ID: repo.ID, - Owner: repo.Owner.APIFormat(), - Name: repo.Name, - FullName: repo.FullName(), - Description: repo.Description, - Private: repo.IsPrivate, - Fork: repo.IsFork, - Empty: repo.IsBare, - Mirror: repo.IsMirror, - Size: repo.Size, - HTMLURL: repo.HTMLURL(), - SSHURL: cloneLink.SSH, - CloneURL: cloneLink.HTTPS, - Website: repo.Website, - Stars: repo.NumStars, - Forks: repo.NumForks, - Watchers: repo.NumWatches, - OpenIssues: repo.NumOpenIssues, - DefaultBranch: repo.DefaultBranch, - Created: repo.Created, - Updated: repo.Updated, - Permissions: permission, - // Reserved for go-gogs-client change - // AvatarUrl: repo.AvatarLink(), - } - if repo.IsFork { - p := &api.Permission{Pull: true} - if len(user) != 0 { - p.Admin = user[0].IsAdminOfRepo(repo) - p.Push = user[0].IsWriterOfRepo(repo) - } - apiRepo.Parent = repo.BaseRepo.APIFormat(p) - } - return apiRepo -} - -func (repo *Repository) getOwner(e Engine) (err error) { - if repo.Owner != nil { - return nil - } - - repo.Owner, err = getUserByID(e, repo.OwnerID) - return err -} - -func (repo *Repository) GetOwner() error { - return repo.getOwner(x) -} - -func (repo *Repository) mustOwner(e Engine) *User { - if err := repo.getOwner(e); err != nil { - return &User{ - Name: "error", - FullName: err.Error(), - } - } - - return repo.Owner -} - -func (repo *Repository) UpdateSize() error { - countObject, err := git.GetRepoSize(repo.RepoPath()) - if err != nil { - return fmt.Errorf("GetRepoSize: %v", err) - } - - repo.Size = countObject.Size + countObject.SizePack - if _, err = x.Id(repo.ID).Cols("size").Update(repo); err != nil { - return fmt.Errorf("update size: %v", err) - } - return nil -} - -// ComposeMetas composes a map of metas for rendering external issue tracker URL. -func (repo *Repository) ComposeMetas() map[string]string { - if !repo.EnableExternalTracker { - return nil - } else if repo.ExternalMetas == nil { - repo.ExternalMetas = map[string]string{ - "format": repo.ExternalTrackerFormat, - "user": repo.MustOwner().Name, - "repo": repo.Name, - } - switch repo.ExternalTrackerStyle { - case markup.ISSUE_NAME_STYLE_ALPHANUMERIC: - repo.ExternalMetas["style"] = markup.ISSUE_NAME_STYLE_ALPHANUMERIC - default: - repo.ExternalMetas["style"] = markup.ISSUE_NAME_STYLE_NUMERIC - } - - } - return repo.ExternalMetas -} - -// DeleteWiki removes the actual and local copy of repository wiki. -func (repo *Repository) DeleteWiki() { - wikiPaths := []string{repo.WikiPath(), repo.LocalWikiPath()} - for _, wikiPath := range wikiPaths { - RemoveAllWithNotice("Delete repository wiki", wikiPath) - } -} - -// getUsersWithAccesMode returns users that have at least given access mode to the repository. -func (repo *Repository) getUsersWithAccesMode(e Engine, mode AccessMode) (_ []*User, err error) { - if err = repo.getOwner(e); err != nil { - return nil, err - } - - accesses := make([]*Access, 0, 10) - if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { - return nil, err - } - - // Leave a seat for owner itself to append later, but if owner is an organization - // and just waste 1 unit is cheaper than re-allocate memory once. - users := make([]*User, 0, len(accesses)+1) - if len(accesses) > 0 { - userIDs := make([]int64, len(accesses)) - for i := 0; i < len(accesses); i++ { - userIDs[i] = accesses[i].UserID - } - - if err = e.In("id", userIDs).Find(&users); err != nil { - return nil, err - } - } - if !repo.Owner.IsOrganization() { - users = append(users, repo.Owner) - } - - return users, nil -} - -// getAssignees returns a list of users who can be assigned to issues in this repository. -func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { - return repo.getUsersWithAccesMode(e, ACCESS_MODE_READ) -} - -// GetAssignees returns all users that have read access and can be assigned to issues -// of the repository, -func (repo *Repository) GetAssignees() (_ []*User, err error) { - return repo.getAssignees(x) -} - -// 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) -} - -// GetWriters returns all users that have write access to the repository. -func (repo *Repository) GetWriters() (_ []*User, err error) { - return repo.getUsersWithAccesMode(x, ACCESS_MODE_WRITE) -} - -// GetMilestoneByID returns the milestone belongs to repository by given ID. -func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) { - return GetMilestoneByRepoID(repo.ID, milestoneID) -} - -// IssueStats returns number of open and closed repository issues by given filter mode. -func (repo *Repository) IssueStats(userID int64, filterMode FilterMode, isPull bool) (int64, int64) { - return GetRepoIssueStats(repo.ID, userID, filterMode, isPull) -} - -func (repo *Repository) GetMirror() (err error) { - repo.Mirror, err = GetMirrorByRepoID(repo.ID) - return err -} - -func (repo *Repository) repoPath(e Engine) string { - return RepoPath(repo.mustOwner(e).Name, repo.Name) -} - -func (repo *Repository) RepoPath() string { - return repo.repoPath(x) -} - -func (repo *Repository) GitConfigPath() string { - return filepath.Join(repo.RepoPath(), "config") -} - -func (repo *Repository) RelLink() string { - return "/" + repo.FullName() -} - -func (repo *Repository) Link() string { - return setting.AppSubURL + "/" + repo.FullName() -} - -func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { - return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) -} - -func (repo *Repository) HasAccess(userID int64) bool { - has, _ := HasAccess(userID, repo, ACCESS_MODE_READ) - return has -} - -func (repo *Repository) IsOwnedBy(userID int64) bool { - return repo.OwnerID == userID -} - -// CanBeForked returns true if repository meets the requirements of being forked. -func (repo *Repository) CanBeForked() bool { - return !repo.IsBare -} - -// CanEnablePulls returns true if repository meets the requirements of accepting pulls. -func (repo *Repository) CanEnablePulls() bool { - return !repo.IsMirror && !repo.IsBare -} - -// AllowPulls returns true if repository meets the requirements of accepting pulls and has them enabled. -func (repo *Repository) AllowsPulls() bool { - return repo.CanEnablePulls() && repo.EnablePulls -} - -func (repo *Repository) IsBranchRequirePullRequest(name string) bool { - return IsBranchOfRepoRequirePullRequest(repo.ID, name) -} - -// CanEnableEditor returns true if repository meets the requirements of web editor. -func (repo *Repository) CanEnableEditor() bool { - return !repo.IsMirror -} - -// FIXME: should have a mutex to prevent producing same index for two issues that are created -// closely enough. -func (repo *Repository) NextIssueIndex() int64 { - return int64(repo.NumIssues+repo.NumPulls) + 1 -} - -func (repo *Repository) LocalCopyPath() string { - return path.Join(setting.AppDataPath, "tmp/local-repo", com.ToStr(repo.ID)) -} - -// UpdateLocalCopy fetches latest changes of given branch from repoPath to localPath. -// It creates a new clone if local copy does not exist, but does not checks out to a -// specific branch if the local copy belongs to a wiki. -// For existing local copy, it checks out to target branch by default, and safe to -// assume subsequent operations are against target branch when caller has confidence -// about no race condition. -func UpdateLocalCopyBranch(repoPath, localPath, branch string, isWiki bool) (err error) { - if !com.IsExist(localPath) { - // Checkout to a specific branch fails when wiki is an empty repository. - if isWiki { - branch = "" - } - if err = git.Clone(repoPath, localPath, git.CloneRepoOptions{ - Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second, - Branch: branch, - }); err != nil { - return fmt.Errorf("git clone %s: %v", branch, err) - } - } else { - if err = git.Fetch(localPath, git.FetchRemoteOptions{ - Prune: true, - }); err != nil { - return fmt.Errorf("git fetch: %v", err) - } - if err = git.Checkout(localPath, git.CheckoutOptions{ - Branch: branch, - }); err != nil { - return fmt.Errorf("git checkout %s: %v", branch, err) - } - - // Reset to align with remote in case of force push. - if err = git.ResetHEAD(localPath, true, "origin/"+branch); err != nil { - return fmt.Errorf("git reset --hard origin/%s: %v", branch, err) - } - } - return nil -} - -// UpdateLocalCopyBranch makes sure local copy of repository in given branch is up-to-date. -func (repo *Repository) UpdateLocalCopyBranch(branch string) error { - return UpdateLocalCopyBranch(repo.RepoPath(), repo.LocalCopyPath(), branch, false) -} - -// PatchPath returns corresponding patch file path of repository by given issue ID. -func (repo *Repository) PatchPath(index int64) (string, error) { - if err := repo.GetOwner(); err != nil { - return "", err - } - - return filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "pulls", com.ToStr(index)+".patch"), nil -} - -// SavePatch saves patch data to corresponding location by given issue ID. -func (repo *Repository) SavePatch(index int64, patch []byte) error { - patchPath, err := repo.PatchPath(index) - if err != nil { - return fmt.Errorf("PatchPath: %v", err) - } - - os.MkdirAll(filepath.Dir(patchPath), os.ModePerm) - if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil { - return fmt.Errorf("WriteFile: %v", err) - } - - return nil -} - -func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { - has, err := e.Get(&Repository{ - OwnerID: u.ID, - LowerName: strings.ToLower(repoName), - }) - return has && com.IsDir(RepoPath(u.Name, repoName)), err -} - -// IsRepositoryExist returns true if the repository with given name under user has already existed. -func IsRepositoryExist(u *User, repoName string) (bool, error) { - return isRepositoryExist(x, u, repoName) -} - -// CloneLink represents different types of clone URLs of repository. -type CloneLink struct { - SSH string - HTTPS string - Git string -} - -// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name. -func ComposeHTTPSCloneURL(owner, repo string) string { - return fmt.Sprintf("%s%s/%s.git", setting.AppURL, owner, repo) -} - -func (repo *Repository) cloneLink(isWiki bool) *CloneLink { - repoName := repo.Name - if isWiki { - repoName += ".wiki" - } - - repo.Owner = repo.MustOwner() - cl := new(CloneLink) - if setting.SSH.Port != 22 { - cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSH.Domain, setting.SSH.Port, repo.Owner.Name, repoName) - } else { - cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSH.Domain, repo.Owner.Name, repoName) - } - cl.HTTPS = ComposeHTTPSCloneURL(repo.Owner.Name, repoName) - return cl -} - -// CloneLink returns clone URLs of repository. -func (repo *Repository) CloneLink() (cl *CloneLink) { - return repo.cloneLink(false) -} - -type MigrateRepoOptions struct { - Name string - Description string - IsPrivate bool - IsMirror bool - RemoteAddr string -} - -/* - GitHub, GitLab, Gogs: *.wiki.git - BitBucket: *.git/wiki -*/ -var commonWikiURLSuffixes = []string{".wiki.git", ".git/wiki"} - -// wikiRemoteURL returns accessible repository URL for wiki if exists. -// Otherwise, it returns an empty string. -func wikiRemoteURL(remote string) string { - remote = strings.TrimSuffix(remote, ".git") - for _, suffix := range commonWikiURLSuffixes { - wikiURL := remote + suffix - if git.IsRepoURLAccessible(git.NetworkOptions{ - URL: wikiURL, - }) { - return wikiURL - } - } - return "" -} - -// MigrateRepository migrates a existing repository from other project hosting. -func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository, error) { - repo, err := CreateRepository(doer, owner, CreateRepoOptions{ - Name: opts.Name, - Description: opts.Description, - IsPrivate: opts.IsPrivate, - IsMirror: opts.IsMirror, - }) - if err != nil { - return nil, err - } - - repoPath := RepoPath(owner.Name, opts.Name) - wikiPath := WikiPath(owner.Name, opts.Name) - - if owner.IsOrganization() { - t, err := owner.GetOwnerTeam() - if err != nil { - return nil, err - } - repo.NumWatches = t.NumMembers - } else { - repo.NumWatches = 1 - } - - migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second - - RemoveAllWithNotice("Repository path erase before creation", repoPath) - if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneRepoOptions{ - Mirror: true, - Quiet: true, - Timeout: migrateTimeout, - }); err != nil { - return repo, fmt.Errorf("Clone: %v", err) - } - - wikiRemotePath := wikiRemoteURL(opts.RemoteAddr) - if len(wikiRemotePath) > 0 { - RemoveAllWithNotice("Repository wiki path erase before creation", wikiPath) - if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{ - Mirror: true, - Quiet: true, - Timeout: migrateTimeout, - }); err != nil { - log.Trace("Fail to clone wiki: %v", err) - RemoveAllWithNotice("Delete repository wiki for initialization failure", wikiPath) - } - } - - // Check if repository is empty. - _, stderr, err := com.ExecCmdDir(repoPath, "git", "log", "-1") - if err != nil { - if strings.Contains(stderr, "fatal: bad default revision 'HEAD'") { - repo.IsBare = true - } else { - return repo, fmt.Errorf("check bare: %v - %s", err, stderr) - } - } - - if !repo.IsBare { - // Try to get HEAD branch and set it as default branch. - gitRepo, err := git.OpenRepository(repoPath) - if err != nil { - return repo, fmt.Errorf("OpenRepository: %v", err) - } - headBranch, err := gitRepo.GetHEADBranch() - if err != nil { - return repo, fmt.Errorf("GetHEADBranch: %v", err) - } - if headBranch != nil { - repo.DefaultBranch = headBranch.Name - } - - if err = repo.UpdateSize(); err != nil { - log.Error(2, "UpdateSize [repo_id: %d]: %v", repo.ID, err) - } - } - - if opts.IsMirror { - if _, err = x.InsertOne(&Mirror{ - RepoID: repo.ID, - Interval: setting.Mirror.DefaultInterval, - EnablePrune: true, - NextSync: time.Now().Add(time.Duration(setting.Mirror.DefaultInterval) * time.Hour), - }); err != nil { - return repo, fmt.Errorf("InsertOne: %v", err) - } - - repo.IsMirror = true - return repo, UpdateRepository(repo, false) - } - - return CleanUpMigrateInfo(repo) -} - -// cleanUpMigrateGitConfig removes mirror info which prevents "push --all". -// This also removes possible user credentials. -func cleanUpMigrateGitConfig(configPath string) error { - cfg, err := ini.Load(configPath) - if err != nil { - return fmt.Errorf("open config file: %v", err) - } - cfg.DeleteSection("remote \"origin\"") - if err = cfg.SaveToIndent(configPath, "\t"); err != nil { - return fmt.Errorf("save config file: %v", err) - } - return nil -} - -var hooksTpls = map[string]string{ - "pre-receive": "#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", - "update": "#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", - "post-receive": "#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", -} - -func createDelegateHooks(repoPath string) (err error) { - for _, name := range git.HookNames { - hookPath := filepath.Join(repoPath, "hooks", name) - if err = ioutil.WriteFile(hookPath, - []byte(fmt.Sprintf(hooksTpls[name], setting.ScriptType, setting.AppPath, setting.CustomConf)), - os.ModePerm); err != nil { - return fmt.Errorf("create delegate hook '%s': %v", hookPath, err) - } - } - return nil -} - -// Finish migrating repository and/or wiki with things that don't need to be done for mirrors. -func CleanUpMigrateInfo(repo *Repository) (*Repository, error) { - repoPath := repo.RepoPath() - if err := createDelegateHooks(repoPath); err != nil { - return repo, fmt.Errorf("createDelegateHooks: %v", err) - } - if repo.HasWiki() { - if err := createDelegateHooks(repo.WikiPath()); err != nil { - return repo, fmt.Errorf("createDelegateHooks.(wiki): %v", err) - } - } - - if err := cleanUpMigrateGitConfig(repo.GitConfigPath()); err != nil { - return repo, fmt.Errorf("cleanUpMigrateGitConfig: %v", err) - } - if repo.HasWiki() { - if err := cleanUpMigrateGitConfig(path.Join(repo.WikiPath(), "config")); err != nil { - return repo, fmt.Errorf("cleanUpMigrateGitConfig.(wiki): %v", err) - } - } - - return repo, UpdateRepository(repo, false) -} - -// initRepoCommit temporarily changes with work directory. -func initRepoCommit(tmpPath string, sig *git.Signature) (err error) { - var stderr string - if _, stderr, err = process.ExecDir(-1, - tmpPath, fmt.Sprintf("initRepoCommit (git add): %s", tmpPath), - "git", "add", "--all"); err != nil { - return fmt.Errorf("git add: %s", stderr) - } - - if _, stderr, err = process.ExecDir(-1, - tmpPath, fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath), - "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), - "-m", "Initial commit"); err != nil { - return fmt.Errorf("git commit: %s", stderr) - } - - if _, stderr, err = process.ExecDir(-1, - tmpPath, fmt.Sprintf("initRepoCommit (git push): %s", tmpPath), - "git", "push", "origin", "master"); err != nil { - return fmt.Errorf("git push: %s", stderr) - } - return nil -} - -type CreateRepoOptions struct { - Name string - Description string - Gitignores string - License string - Readme string - IsPrivate bool - IsMirror bool - AutoInit bool -} - -func getRepoInitFile(tp, name string) ([]byte, error) { - relPath := path.Join("conf", tp, strings.TrimLeft(path.Clean("/"+name), "/")) - - // Use custom file when available. - customPath := path.Join(setting.CustomPath, relPath) - if com.IsFile(customPath) { - return ioutil.ReadFile(customPath) - } - return bindata.Asset(relPath) -} - -func prepareRepoCommit(repo *Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { - // Clone to temprory path and do the init commit. - _, stderr, err := process.Exec( - fmt.Sprintf("initRepository(git clone): %s", repoPath), "git", "clone", repoPath, tmpDir) - if err != nil { - return fmt.Errorf("git clone: %v - %s", err, stderr) - } - - // README - data, err := getRepoInitFile("readme", opts.Readme) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", opts.Readme, err) - } - - cloneLink := repo.CloneLink() - match := map[string]string{ - "Name": repo.Name, - "Description": repo.Description, - "CloneURL.SSH": cloneLink.SSH, - "CloneURL.HTTPS": cloneLink.HTTPS, - } - if err = ioutil.WriteFile(filepath.Join(tmpDir, "README.md"), - []byte(com.Expand(string(data), match)), 0644); err != nil { - return fmt.Errorf("write README.md: %v", err) - } - - // .gitignore - if len(opts.Gitignores) > 0 { - var buf bytes.Buffer - names := strings.Split(opts.Gitignores, ",") - for _, name := range names { - data, err = getRepoInitFile("gitignore", name) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", name, err) - } - buf.WriteString("# ---> " + name + "\n") - buf.Write(data) - buf.WriteString("\n") - } - - if buf.Len() > 0 { - if err = ioutil.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0644); err != nil { - return fmt.Errorf("write .gitignore: %v", err) - } - } - } - - // LICENSE - if len(opts.License) > 0 { - data, err = getRepoInitFile("license", opts.License) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", opts.License, err) - } - - if err = ioutil.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0644); err != nil { - return fmt.Errorf("write LICENSE: %v", err) - } - } - - return nil -} - -// initRepository performs initial commit with chosen setup files on behave of doer. -func initRepository(e Engine, repoPath string, doer *User, repo *Repository, opts CreateRepoOptions) (err error) { - // Somehow the directory could exist. - if com.IsExist(repoPath) { - return fmt.Errorf("initRepository: path already exists: %s", repoPath) - } - - // Init bare new repository. - if err = git.InitRepository(repoPath, true); err != nil { - return fmt.Errorf("InitRepository: %v", err) - } else if err = createDelegateHooks(repoPath); err != nil { - return fmt.Errorf("createDelegateHooks: %v", err) - } - - tmpDir := filepath.Join(os.TempDir(), "gogs-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond())) - - // Initialize repository according to user's choice. - if opts.AutoInit { - os.MkdirAll(tmpDir, os.ModePerm) - defer RemoveAllWithNotice("Delete repository for auto-initialization", tmpDir) - - if err = prepareRepoCommit(repo, tmpDir, repoPath, opts); err != nil { - return fmt.Errorf("prepareRepoCommit: %v", err) - } - - // Apply changes and commit. - if err = initRepoCommit(tmpDir, doer.NewGitSig()); err != nil { - return fmt.Errorf("initRepoCommit: %v", err) - } - } - - // 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 { - return fmt.Errorf("getRepositoryByID: %v", err) - } - - if !opts.AutoInit { - repo.IsBare = true - } - - repo.DefaultBranch = "master" - if err = updateRepository(e, repo, false); err != nil { - return fmt.Errorf("updateRepository: %v", err) - } - - return nil -} - -var ( - reservedRepoNames = []string{".", ".."} - reservedRepoPatterns = []string{"*.git", "*.wiki"} -) - -// IsUsableRepoName return an error if given name is a reserved name or pattern. -func IsUsableRepoName(name string) error { - return isUsableName(reservedRepoNames, reservedRepoPatterns, name) -} - -func createRepository(e *xorm.Session, doer, owner *User, repo *Repository) (err error) { - if err = IsUsableRepoName(repo.Name); err != nil { - return err - } - - has, err := isRepositoryExist(e, owner, repo.Name) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{owner.Name, repo.Name} - } - - if _, err = e.Insert(repo); err != nil { - return err - } - - owner.NumRepos++ - // Remember visibility preference. - owner.LastRepoVisibility = repo.IsPrivate - if err = updateUser(e, owner); err != nil { - return fmt.Errorf("updateUser: %v", err) - } - - // Give access to all members in owner team. - if owner.IsOrganization() { - t, err := owner.getOwnerTeam(e) - if err != nil { - return fmt.Errorf("getOwnerTeam: %v", err) - } else if err = t.addRepository(e, repo); err != nil { - return fmt.Errorf("addRepository: %v", err) - } - } else { - // Organization automatically called this in addRepository method. - if err = repo.recalculateAccesses(e); err != nil { - return fmt.Errorf("recalculateAccesses: %v", err) - } - } - - if err = watchRepo(e, owner.ID, repo.ID, true); err != nil { - return fmt.Errorf("watchRepo: %v", err) - } else if err = newRepoAction(e, doer, owner, repo); err != nil { - return fmt.Errorf("newRepoAction: %v", err) - } - - return repo.loadAttributes(e) -} - -// CreateRepository creates a repository for given user or organization. -func CreateRepository(doer, owner *User, opts CreateRepoOptions) (_ *Repository, err error) { - if !owner.CanCreateRepo() { - return nil, errors.ReachLimitOfRepo{owner.RepoCreationNum()} - } - - repo := &Repository{ - OwnerID: owner.ID, - Owner: owner, - Name: opts.Name, - LowerName: strings.ToLower(opts.Name), - Description: opts.Description, - IsPrivate: opts.IsPrivate, - EnableWiki: true, - EnableIssues: true, - EnablePulls: true, - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - if err = createRepository(sess, doer, owner, repo); err != nil { - return nil, err - } - - // No need for init mirror. - if !opts.IsMirror { - repoPath := RepoPath(owner.Name, repo.Name) - if err = initRepository(sess, repoPath, doer, repo, opts); err != nil { - RemoveAllWithNotice("Delete repository for initialization failure", repoPath) - return nil, fmt.Errorf("initRepository: %v", err) - } - - _, stderr, err := process.ExecDir(-1, - repoPath, fmt.Sprintf("CreateRepository 'git update-server-info': %s", repoPath), - "git", "update-server-info") - if err != nil { - return nil, fmt.Errorf("CreateRepository 'git update-server-info': %s", stderr) - } - } - - return repo, sess.Commit() -} - -func countRepositories(userID int64, private bool) int64 { - sess := x.Where("id > 0") - - if userID > 0 { - sess.And("owner_id = ?", userID) - } - if !private { - sess.And("is_private=?", false) - } - - count, err := sess.Count(new(Repository)) - if err != nil { - log.Error(4, "countRepositories: %v", err) - } - return count -} - -// CountRepositories returns number of repositories. -// Argument private only takes effect when it is false, -// set it true to count all repositories. -func CountRepositories(private bool) int64 { - return countRepositories(-1, private) -} - -// CountUserRepositories returns number of repositories user owns. -// Argument private only takes effect when it is false, -// set it true to count all repositories. -func CountUserRepositories(userID int64, private bool) int64 { - return countRepositories(userID, private) -} - -func Repositories(page, pageSize int) (_ []*Repository, err error) { - repos := make([]*Repository, 0, pageSize) - return repos, x.Limit(pageSize, (page-1)*pageSize).Asc("id").Find(&repos) -} - -// RepositoriesWithUsers returns number of repos in given page. -func RepositoriesWithUsers(page, pageSize int) (_ []*Repository, err error) { - repos, err := Repositories(page, pageSize) - if err != nil { - return nil, fmt.Errorf("Repositories: %v", err) - } - - for i := range repos { - if err = repos[i].GetOwner(); err != nil { - return nil, err - } - } - - return repos, nil -} - -// FilterRepositoryWithIssues selects repositories that are using interal issue tracker -// and has disabled external tracker from given set. -// It returns nil if result set is empty. -func FilterRepositoryWithIssues(repoIDs []int64) ([]int64, error) { - if len(repoIDs) == 0 { - return nil, nil - } - - repos := make([]*Repository, 0, len(repoIDs)) - if err := x.Where("enable_issues=?", true). - And("enable_external_tracker=?", false). - In("id", repoIDs). - Cols("id"). - Find(&repos); err != nil { - return nil, fmt.Errorf("filter valid repositories %v: %v", repoIDs, err) - } - - if len(repos) == 0 { - return nil, nil - } - - repoIDs = make([]int64, len(repos)) - for i := range repos { - repoIDs[i] = repos[i].ID - } - return repoIDs, nil -} - -// RepoPath returns repository path by given user and repository name. -func RepoPath(userName, repoName string) string { - return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") -} - -// TransferOwnership transfers all corresponding setting from old user to new one. -func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error { - newOwner, err := GetUserByName(newOwnerName) - if err != nil { - return fmt.Errorf("get new owner '%s': %v", newOwnerName, err) - } - - // Check if new owner has repository with same name. - has, err := IsRepositoryExist(newOwner, repo.Name) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{newOwnerName, repo.Name} - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return fmt.Errorf("sess.Begin: %v", err) - } - - owner := repo.Owner - - // Note: we have to set value here to make sure recalculate accesses is based on - // new owner. - repo.OwnerID = newOwner.ID - repo.Owner = newOwner - - // Update repository. - if _, err := sess.ID(repo.ID).Update(repo); err != nil { - return fmt.Errorf("update owner: %v", err) - } - - // Remove redundant collaborators. - collaborators, err := repo.getCollaborators(sess) - if err != nil { - return fmt.Errorf("getCollaborators: %v", err) - } - - // Dummy object. - collaboration := &Collaboration{RepoID: repo.ID} - for _, c := range collaborators { - collaboration.UserID = c.ID - if c.ID == newOwner.ID || newOwner.IsOrgMember(c.ID) { - if _, err = sess.Delete(collaboration); err != nil { - return fmt.Errorf("remove collaborator '%d': %v", c.ID, err) - } - } - } - - // Remove old team-repository relations. - if owner.IsOrganization() { - if err = owner.getTeams(sess); err != nil { - return fmt.Errorf("getTeams: %v", err) - } - for _, t := range owner.Teams { - if !t.hasRepository(sess, repo.ID) { - continue - } - - t.NumRepos-- - if _, err := sess.ID(t.ID).AllCols().Update(t); err != nil { - return fmt.Errorf("decrease team repository count '%d': %v", t.ID, err) - } - } - - if err = owner.removeOrgRepo(sess, repo.ID); err != nil { - return fmt.Errorf("removeOrgRepo: %v", err) - } - } - - if newOwner.IsOrganization() { - t, err := newOwner.getOwnerTeam(sess) - if err != nil { - return fmt.Errorf("getOwnerTeam: %v", err) - } else if err = t.addRepository(sess, repo); err != nil { - return fmt.Errorf("add to owner team: %v", err) - } - } else { - // Organization called this in addRepository method. - if err = repo.recalculateAccesses(sess); err != nil { - return fmt.Errorf("recalculateAccesses: %v", err) - } - } - - // Update repository count. - if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil { - return fmt.Errorf("increase new owner repository count: %v", err) - } else if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", owner.ID); err != nil { - return fmt.Errorf("decrease old owner repository count: %v", err) - } - - if err = watchRepo(sess, newOwner.ID, repo.ID, true); err != nil { - return fmt.Errorf("watchRepo: %v", err) - } else if err = transferRepoAction(sess, doer, owner, repo); err != nil { - return fmt.Errorf("transferRepoAction: %v", err) - } - - // Rename remote repository to new path and delete local copy. - os.MkdirAll(UserPath(newOwner.Name), os.ModePerm) - if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { - return fmt.Errorf("rename repository directory: %v", err) - } - RemoveAllWithNotice("Delete repository local copy", repo.LocalCopyPath()) - - // Rename remote wiki repository to new path and delete local copy. - wikiPath := WikiPath(owner.Name, repo.Name) - if com.IsExist(wikiPath) { - RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) - if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { - return fmt.Errorf("rename repository wiki: %v", err) - } - } - - return sess.Commit() -} - -// ChangeRepositoryName changes all corresponding setting from old repository name to new one. -func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) { - oldRepoName = strings.ToLower(oldRepoName) - newRepoName = strings.ToLower(newRepoName) - if err = IsUsableRepoName(newRepoName); err != nil { - return err - } - - has, err := IsRepositoryExist(u, newRepoName) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{u.Name, newRepoName} - } - - repo, err := GetRepositoryByName(u.ID, oldRepoName) - if err != nil { - return fmt.Errorf("GetRepositoryByName: %v", err) - } - - // Change repository directory name - if err = os.Rename(repo.RepoPath(), RepoPath(u.Name, newRepoName)); err != nil { - return fmt.Errorf("rename repository directory: %v", err) - } - - wikiPath := repo.WikiPath() - if com.IsExist(wikiPath) { - if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil { - return fmt.Errorf("rename repository wiki: %v", err) - } - RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) - } - - RemoveAllWithNotice("Delete repository local copy", repo.LocalCopyPath()) - return nil -} - -func getRepositoriesByForkID(e Engine, forkID int64) ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - return repos, e.Where("fork_id=?", forkID).Find(&repos) -} - -// GetRepositoriesByForkID returns all repositories with given fork ID. -func GetRepositoriesByForkID(forkID int64) ([]*Repository, error) { - return getRepositoriesByForkID(x, forkID) -} - -func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) { - repo.LowerName = strings.ToLower(repo.Name) - - if len(repo.Description) > 512 { - repo.Description = repo.Description[:512] - } - if len(repo.Website) > 255 { - repo.Website = repo.Website[:255] - } - - if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { - return fmt.Errorf("update: %v", err) - } - - if visibilityChanged { - if err = repo.getOwner(e); err != nil { - return fmt.Errorf("getOwner: %v", err) - } - if repo.Owner.IsOrganization() { - // Organization repository need to recalculate access table when visivility is changed - if err = repo.recalculateTeamAccesses(e, 0); err != nil { - return fmt.Errorf("recalculateTeamAccesses: %v", err) - } - } - - // Create/Remove git-daemon-export-ok for git-daemon - daemonExportFile := path.Join(repo.RepoPath(), "git-daemon-export-ok") - if repo.IsPrivate && com.IsExist(daemonExportFile) { - if err = os.Remove(daemonExportFile); err != nil { - log.Error(4, "Failed to remove %s: %v", daemonExportFile, err) - } - } else if !repo.IsPrivate && !com.IsExist(daemonExportFile) { - if f, err := os.Create(daemonExportFile); err != nil { - log.Error(4, "Failed to create %s: %v", daemonExportFile, err) - } else { - f.Close() - } - } - - forkRepos, err := getRepositoriesByForkID(e, repo.ID) - if err != nil { - return fmt.Errorf("getRepositoriesByForkID: %v", err) - } - for i := range forkRepos { - forkRepos[i].IsPrivate = repo.IsPrivate - if err = updateRepository(e, forkRepos[i], true); err != nil { - return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) - } - } - - // Change visibility of generated actions - if _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{IsPrivate: repo.IsPrivate}); err != nil { - return fmt.Errorf("change action visibility of repository: %v", err) - } - } - - return nil -} - -func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = updateRepository(x, repo, visibilityChanged); err != nil { - return fmt.Errorf("updateRepository: %v", err) - } - - return sess.Commit() -} - -// DeleteRepository deletes a repository for a user or organization. -func DeleteRepository(uid, repoID int64) error { - repo := &Repository{ID: repoID, OwnerID: uid} - has, err := x.Get(repo) - if err != nil { - return err - } else if !has { - return errors.RepoNotExist{repoID, uid, ""} - } - - // In case is a organization. - org, err := GetUserByID(uid) - if err != nil { - return err - } - if org.IsOrganization() { - if err = org.GetTeams(); err != nil { - return err - } - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if org.IsOrganization() { - for _, t := range org.Teams { - if !t.hasRepository(sess, repoID) { - continue - } else if err = t.removeRepository(sess, repo, false); err != nil { - return err - } - } - } - - if err = deleteBeans(sess, - &Repository{ID: repoID}, - &Access{RepoID: repo.ID}, - &Action{RepoID: repo.ID}, - &Watch{RepoID: repoID}, - &Star{RepoID: repoID}, - &Mirror{RepoID: repoID}, - &IssueUser{RepoID: repoID}, - &Milestone{RepoID: repoID}, - &Release{RepoID: repoID}, - &Collaboration{RepoID: repoID}, - &PullRequest{BaseRepoID: repoID}, - &ProtectBranch{RepoID: repoID}, - &ProtectBranchWhitelist{RepoID: repoID}, - &Webhook{RepoID: repoID}, - &HookTask{RepoID: repoID}, - ); err != nil { - return fmt.Errorf("deleteBeans: %v", err) - } - - // 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 { - 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 - } - } - - if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil { - return err - } - - if repo.IsFork { - if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { - return fmt.Errorf("decrease fork count: %v", err) - } - } - - if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil { - return err - } - - if err = sess.Commit(); err != nil { - return fmt.Errorf("Commit: %v", err) - } - - // Remove repository files. - repoPath := repo.RepoPath() - RemoveAllWithNotice("Delete repository files", repoPath) - - repo.DeleteWiki() - - // Remove attachment files. - for i := range attachmentPaths { - RemoveAllWithNotice("Delete attachment", attachmentPaths[i]) - } - - if repo.NumForks > 0 { - if _, err = x.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil { - log.Error(4, "reset 'fork_id' and 'is_fork': %v", err) - } - } - - return nil -} - -// GetRepositoryByRef returns a Repository specified by a GFM reference. -// See https://help.github.com/articles/writing-on-github#references for more information on the syntax. -func GetRepositoryByRef(ref string) (*Repository, error) { - n := strings.IndexByte(ref, byte('/')) - if n < 2 { - return nil, errors.InvalidRepoReference{ref} - } - - userName, repoName := ref[:n], ref[n+1:] - user, err := GetUserByName(userName) - if err != nil { - return nil, err - } - - return GetRepositoryByName(user.ID, repoName) -} - -// GetRepositoryByName returns the repository by given name under user if exists. -func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { - repo := &Repository{ - OwnerID: ownerID, - LowerName: strings.ToLower(name), - } - has, err := x.Get(repo) - if err != nil { - return nil, err - } else if !has { - return nil, errors.RepoNotExist{0, ownerID, name} - } - return repo, repo.LoadAttributes() -} - -func getRepositoryByID(e Engine, id int64) (*Repository, error) { - repo := new(Repository) - has, err := e.ID(id).Get(repo) - if err != nil { - return nil, err - } else if !has { - return nil, errors.RepoNotExist{id, 0, ""} - } - return repo, repo.loadAttributes(e) -} - -// GetRepositoryByID returns the repository by given id if exists. -func GetRepositoryByID(id int64) (*Repository, error) { - return getRepositoryByID(x, id) -} - -type UserRepoOptions struct { - UserID int64 - Private bool - Page int - PageSize int -} - -// GetUserRepositories returns a list of repositories of given user. -func GetUserRepositories(opts *UserRepoOptions) ([]*Repository, error) { - sess := x.Where("owner_id=?", opts.UserID).Desc("updated_unix") - if !opts.Private { - sess.And("is_private=?", false) - } - - if opts.Page <= 0 { - opts.Page = 1 - } - sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) - - repos := make([]*Repository, 0, opts.PageSize) - return repos, sess.Find(&repos) -} - -// GetUserRepositories returns a list of mirror repositories of given user. -func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - return repos, x.Where("owner_id = ?", userID).And("is_mirror = ?", true).Find(&repos) -} - -// GetRecentUpdatedRepositories returns the list of repositories that are recently updated. -func GetRecentUpdatedRepositories(page, pageSize int) (repos []*Repository, err error) { - return repos, x.Limit(pageSize, (page-1)*pageSize). - Where("is_private=?", false).Limit(pageSize).Desc("updated_unix").Find(&repos) -} - -// GetUserAndCollaborativeRepositories returns list of repositories the user owns and collaborates. -func GetUserAndCollaborativeRepositories(userID int64) ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - if err := x.Alias("repo"). - Join("INNER", "collaboration", "collaboration.repo_id = repo.id"). - Where("collaboration.user_id = ?", userID). - Find(&repos); err != nil { - return nil, fmt.Errorf("select collaborative repositories: %v", err) - } - - ownRepos := make([]*Repository, 0, 10) - if err := x.Where("owner_id = ?", userID).Find(&ownRepos); err != nil { - return nil, fmt.Errorf("select own repositories: %v", err) - } - - return append(repos, ownRepos...), nil -} - -func getRepositoryCount(e Engine, u *User) (int64, error) { - return x.Count(&Repository{OwnerID: u.ID}) -} - -// GetRepositoryCount returns the total number of repositories of user. -func GetRepositoryCount(u *User) (int64, error) { - return getRepositoryCount(x, u) -} - -type SearchRepoOptions struct { - Keyword string - OwnerID int64 - UserID int64 // When set results will contain all public/private repositories user has access to - OrderBy string - Private bool // Include private repositories in results - Page int - PageSize int // Can be smaller than or equal to setting.ExplorePagingNum -} - -// SearchRepositoryByName takes keyword and part of repository name to search, -// it returns results in given range and number of total results. -func SearchRepositoryByName(opts *SearchRepoOptions) (repos []*Repository, count int64, err error) { - if opts.Page <= 0 { - opts.Page = 1 - } - - repos = make([]*Repository, 0, opts.PageSize) - sess := x.Alias("repo") - // Attempt to find repositories that opts.UserID has access to, - // this does not include other people's private repositories even if opts.UserID is an admin. - if !opts.Private && opts.UserID > 0 { - sess.Join("LEFT", "access", "access.repo_id = repo.id"). - Where("repo.owner_id = ? OR access.user_id = ? OR repo.is_private = ? OR (repo.is_private = ? AND (repo.allow_public_wiki = ? OR repo.allow_public_issues = ?))", opts.UserID, opts.UserID, false, true, true, true) - } else { - // Only return public repositories if opts.Private is not set - if !opts.Private { - sess.And("repo.is_private = ? OR (repo.is_private = ? AND (repo.allow_public_wiki = ? OR repo.allow_public_issues = ?))", false, true, true, true) - } - } - if len(opts.Keyword) > 0 { - sess.And("repo.lower_name LIKE ? OR repo.description LIKE ?", "%"+strings.ToLower(opts.Keyword)+"%", "%"+strings.ToLower(opts.Keyword)+"%") - } - if opts.OwnerID > 0 { - sess.And("repo.owner_id = ?", opts.OwnerID) - } - - // We need all fields (repo.*) in final list but only ID (repo.id) is good enough for counting. - count, err = sess.Clone().Distinct("repo.id").Count(new(Repository)) - if err != nil { - return nil, 0, fmt.Errorf("Count: %v", err) - } - - if len(opts.OrderBy) > 0 { - sess.OrderBy("repo." + opts.OrderBy) - } - return repos, count, sess.Distinct("repo.*").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&repos) -} - -func DeleteOldRepositoryArchives() { - if taskStatusTable.IsRunning(_CLEAN_OLD_ARCHIVES) { - return - } - taskStatusTable.Start(_CLEAN_OLD_ARCHIVES) - defer taskStatusTable.Stop(_CLEAN_OLD_ARCHIVES) - - log.Trace("Doing: DeleteOldRepositoryArchives") - - formats := []string{"zip", "targz"} - oldestTime := time.Now().Add(-setting.Cron.RepoArchiveCleanup.OlderThan) - if err := x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - basePath := filepath.Join(repo.RepoPath(), "archives") - for _, format := range formats { - dirPath := filepath.Join(basePath, format) - if !com.IsDir(dirPath) { - continue - } - - dir, err := os.Open(dirPath) - if err != nil { - log.Error(3, "Fail to open directory '%s': %v", dirPath, err) - continue - } - - fis, err := dir.Readdir(0) - dir.Close() - if err != nil { - log.Error(3, "Fail to read directory '%s': %v", dirPath, err) - continue - } - - for _, fi := range fis { - if fi.IsDir() || fi.ModTime().After(oldestTime) { - continue - } - - archivePath := filepath.Join(dirPath, fi.Name()) - if err = os.Remove(archivePath); err != nil { - desc := fmt.Sprintf("Fail to health delete archive '%s': %v", archivePath, err) - log.Warn(desc) - if err = CreateRepositoryNotice(desc); err != nil { - log.Error(3, "CreateRepositoryNotice: %v", err) - } - } - } - } - - return nil - }); err != nil { - log.Error(2, "DeleteOldRepositoryArchives: %v", err) - } -} - -// DeleteRepositoryArchives deletes all repositories' archives. -func DeleteRepositoryArchives() error { - if taskStatusTable.IsRunning(_CLEAN_OLD_ARCHIVES) { - return nil - } - taskStatusTable.Start(_CLEAN_OLD_ARCHIVES) - defer taskStatusTable.Stop(_CLEAN_OLD_ARCHIVES) - - return x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives")) - }) -} - -func gatherMissingRepoRecords() ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - if err := x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - if !com.IsDir(repo.RepoPath()) { - repos = append(repos, repo) - } - return nil - }); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil { - return nil, fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - return repos, nil -} - -// DeleteMissingRepositories deletes all repository records that lost Git files. -func DeleteMissingRepositories() error { - repos, err := gatherMissingRepoRecords() - if err != nil { - return fmt.Errorf("gatherMissingRepoRecords: %v", err) - } - - if len(repos) == 0 { - return nil - } - - for _, repo := range repos { - log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID) - if err := DeleteRepository(repo.OwnerID, repo.ID); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil { - return fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - } - return nil -} - -// ReinitMissingRepositories reinitializes all repository records that lost Git files. -func ReinitMissingRepositories() error { - repos, err := gatherMissingRepoRecords() - if err != nil { - return fmt.Errorf("gatherMissingRepoRecords: %v", err) - } - - if len(repos) == 0 { - return nil - } - - for _, repo := range repos { - log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) - if err := git.InitRepository(repo.RepoPath(), true); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil { - return fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - } - return nil -} - -// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks -// to make sure the binary and custom conf path are up-to-date. -func SyncRepositoryHooks() error { - return x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - if err := createDelegateHooks(repo.RepoPath()); err != nil { - return err - } - - if repo.HasWiki() { - return createDelegateHooks(repo.WikiPath()) - } - return nil - }) -} - -// Prevent duplicate running tasks. -var taskStatusTable = sync.NewStatusTable() - -const ( - _MIRROR_UPDATE = "mirror_update" - _GIT_FSCK = "git_fsck" - _CHECK_REPO_STATS = "check_repos_stats" - _CLEAN_OLD_ARCHIVES = "clean_old_archives" -) - -// GitFsck calls 'git fsck' to check repository health. -func GitFsck() { - if taskStatusTable.IsRunning(_GIT_FSCK) { - return - } - taskStatusTable.Start(_GIT_FSCK) - defer taskStatusTable.Stop(_GIT_FSCK) - - log.Trace("Doing: GitFsck") - - if err := x.Where("id>0").Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - repoPath := repo.RepoPath() - if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil { - desc := fmt.Sprintf("Failed to perform health check on repository '%s': %v", repoPath, err) - log.Warn(desc) - if err = CreateRepositoryNotice(desc); err != nil { - log.Error(3, "CreateRepositoryNotice: %v", err) - } - } - return nil - }); err != nil { - log.Error(2, "GitFsck: %v", err) - } -} - -func GitGcRepos() error { - args := append([]string{"gc"}, setting.Git.GCArgs...) - return x.Where("id > 0").Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - if err := repo.GetOwner(); err != nil { - return err - } - _, stderr, err := process.ExecDir( - time.Duration(setting.Git.Timeout.GC)*time.Second, - RepoPath(repo.Owner.Name, repo.Name), "Repository garbage collection", - "git", args...) - if err != nil { - return fmt.Errorf("%v: %v", err, stderr) - } - return nil - }) -} - -type repoChecker struct { - querySQL, correctSQL string - desc string -} - -func repoStatsCheck(checker *repoChecker) { - results, err := x.Query(checker.querySQL) - if err != nil { - log.Error(2, "Select %s: %v", checker.desc, err) - return - } - for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating %s: %d", checker.desc, id) - _, err = x.Exec(checker.correctSQL, id, id) - if err != nil { - log.Error(2, "Update %s[%d]: %v", checker.desc, id, err) - } - } -} - -func CheckRepoStats() { - if taskStatusTable.IsRunning(_CHECK_REPO_STATS) { - return - } - taskStatusTable.Start(_CHECK_REPO_STATS) - defer taskStatusTable.Stop(_CHECK_REPO_STATS) - - log.Trace("Doing: CheckRepoStats") - - checkers := []*repoChecker{ - // Repository.NumWatches - { - "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id)", - "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=?) WHERE id=?", - "repository count 'num_watches'", - }, - // Repository.NumStars - { - "SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)", - "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", - "repository count 'num_stars'", - }, - // Label.NumIssues - { - "SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)", - "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", - "label count 'num_issues'", - }, - // User.NumRepos - { - "SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)", - "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", - "user count 'num_repos'", - }, - // Issue.NumComments - { - "SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)", - "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", - "issue count 'num_comments'", - }, - } - for i := range checkers { - repoStatsCheck(checkers[i]) - } - - // ***** START: Repository.NumClosedIssues ***** - desc := "repository count 'num_closed_issues'" - results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false) - if err != nil { - log.Error(2, "Select %s: %v", desc, err) - } else { - for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating %s: %d", desc, id) - _, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id) - if err != nil { - log.Error(2, "Update %s[%d]: %v", desc, id, err) - } - } - } - // ***** END: Repository.NumClosedIssues ***** - - // FIXME: use checker when stop supporting old fork repo format. - // ***** START: Repository.NumForks ***** - results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") - if err != nil { - log.Error(2, "Select repository count 'num_forks': %v", err) - } else { - for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating repository count 'num_forks': %d", id) - - repo, err := GetRepositoryByID(id) - if err != nil { - log.Error(2, "GetRepositoryByID[%d]: %v", id, err) - continue - } - - rawResult, err := x.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) - if err != nil { - log.Error(2, "Select count of forks[%d]: %v", repo.ID, err) - continue - } - repo.NumForks = int(parseCountResult(rawResult)) - - if err = UpdateRepository(repo, false); err != nil { - log.Error(2, "UpdateRepository[%d]: %v", id, err) - continue - } - } - } - // ***** END: Repository.NumForks ***** -} - -type RepositoryList []*Repository - -func (repos RepositoryList) loadAttributes(e Engine) error { - if len(repos) == 0 { - return nil - } - - // Load owners - userSet := make(map[int64]*User) - for i := range repos { - userSet[repos[i].OwnerID] = nil - } - userIDs := make([]int64, 0, len(userSet)) - for userID := range userSet { - userIDs = append(userIDs, userID) - } - users := make([]*User, 0, len(userIDs)) - if err := e.Where("id > 0").In("id", userIDs).Find(&users); err != nil { - return fmt.Errorf("find users: %v", err) - } - for i := range users { - userSet[users[i].ID] = users[i] - } - for i := range repos { - repos[i].Owner = userSet[repos[i].OwnerID] - } - - // Load base repositories - repoSet := make(map[int64]*Repository) - for i := range repos { - if repos[i].IsFork { - repoSet[repos[i].ForkID] = nil - } - } - baseIDs := make([]int64, 0, len(repoSet)) - for baseID := range repoSet { - baseIDs = append(baseIDs, baseID) - } - baseRepos := make([]*Repository, 0, len(baseIDs)) - if err := e.Where("id > 0").In("id", baseIDs).Find(&baseRepos); err != nil { - return fmt.Errorf("find base repositories: %v", err) - } - for i := range baseRepos { - repoSet[baseRepos[i].ID] = baseRepos[i] - } - for i := range repos { - if repos[i].IsFork { - repos[i].BaseRepo = repoSet[repos[i].ForkID] - } - } - - return nil -} - -func (repos RepositoryList) LoadAttributes() error { - return repos.loadAttributes(x) -} - -type MirrorRepositoryList []*Repository - -func (repos MirrorRepositoryList) loadAttributes(e Engine) error { - if len(repos) == 0 { - return nil - } - - // Load mirrors. - repoIDs := make([]int64, 0, len(repos)) - for i := range repos { - if !repos[i].IsMirror { - continue - } - - repoIDs = append(repoIDs, repos[i].ID) - } - mirrors := make([]*Mirror, 0, len(repoIDs)) - if err := e.Where("id > 0").In("repo_id", repoIDs).Find(&mirrors); err != nil { - return fmt.Errorf("find mirrors: %v", err) - } - - set := make(map[int64]*Mirror) - for i := range mirrors { - set[mirrors[i].RepoID] = mirrors[i] - } - for i := range repos { - repos[i].Mirror = set[repos[i].ID] - } - return nil -} - -func (repos MirrorRepositoryList) LoadAttributes() error { - return repos.loadAttributes(x) -} - -// __ __ __ .__ -// / \ / \_____ _/ |_ ____ | |__ -// \ \/\/ /\__ \\ __\/ ___\| | \ -// \ / / __ \| | \ \___| Y \ -// \__/\ / (____ /__| \___ >___| / -// \/ \/ \/ \/ - -// Watch is connection request for receiving repository notification. -type Watch struct { - ID int64 - UserID int64 `xorm:"UNIQUE(watch)"` - RepoID int64 `xorm:"UNIQUE(watch)"` -} - -func isWatching(e Engine, userID, repoID int64) bool { - has, _ := e.Get(&Watch{0, userID, repoID}) - return has -} - -// IsWatching checks if user has watched given repository. -func IsWatching(userID, repoID int64) bool { - return isWatching(x, userID, repoID) -} - -func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) { - if watch { - if isWatching(e, userID, repoID) { - return nil - } - if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil { - return err - } - _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID) - } else { - if !isWatching(e, userID, repoID) { - return nil - } - if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil { - return err - } - _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID) - } - return err -} - -// Watch or unwatch repository. -func WatchRepo(userID, repoID int64, watch bool) (err error) { - return watchRepo(x, userID, repoID, watch) -} - -func getWatchers(e Engine, repoID int64) ([]*Watch, error) { - watches := make([]*Watch, 0, 10) - return watches, e.Find(&watches, &Watch{RepoID: repoID}) -} - -// GetWatchers returns all watchers of given repository. -func GetWatchers(repoID int64) ([]*Watch, error) { - return getWatchers(x, repoID) -} - -// Repository.GetWatchers returns range of users watching given repository. -func (repo *Repository) GetWatchers(page int) ([]*User, error) { - users := make([]*User, 0, ItemsPerPage) - sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("watch.repo_id=?", repo.ID) - if setting.UsePostgreSQL { - sess = sess.Join("LEFT", "watch", `"user".id=watch.user_id`) - } else { - sess = sess.Join("LEFT", "watch", "user.id=watch.user_id") - } - return users, sess.Find(&users) -} - -func notifyWatchers(e Engine, act *Action) error { - // Add feeds for user self and all watchers. - watchers, err := getWatchers(e, act.RepoID) - if err != nil { - return fmt.Errorf("getWatchers: %v", err) - } - - // Reset ID to reuse Action object - act.ID = 0 - - // Add feed for actioner. - act.UserID = act.ActUserID - if _, err = e.Insert(act); err != nil { - return fmt.Errorf("insert new action: %v", err) - } - - for i := range watchers { - if act.ActUserID == watchers[i].UserID { - continue - } - - act.ID = 0 - act.UserID = watchers[i].UserID - if _, err = e.Insert(act); err != nil { - return fmt.Errorf("insert new action: %v", err) - } - } - return nil -} - -// NotifyWatchers creates batch of actions for every watcher. -func NotifyWatchers(act *Action) error { - return notifyWatchers(x, act) -} - -// _________ __ -// / _____// |______ _______ -// \_____ \\ __\__ \\_ __ \ -// / \| | / __ \| | \/ -// /_______ /|__| (____ /__| -// \/ \/ - -type Star struct { - ID int64 - UID int64 `xorm:"UNIQUE(s)"` - RepoID int64 `xorm:"UNIQUE(s)"` -} - -// Star or unstar repository. -func StarRepo(userID, repoID int64, star bool) (err error) { - if star { - if IsStaring(userID, repoID) { - return nil - } - if _, err = x.Insert(&Star{UID: userID, RepoID: repoID}); err != nil { - return err - } else if _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repoID); err != nil { - return err - } - _, err = x.Exec("UPDATE `user` SET num_stars = num_stars + 1 WHERE id = ?", userID) - } else { - if !IsStaring(userID, repoID) { - return nil - } - if _, err = x.Delete(&Star{0, userID, repoID}); err != nil { - return err - } else if _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoID); err != nil { - return err - } - _, err = x.Exec("UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", userID) - } - return err -} - -// IsStaring checks if user has starred given repository. -func IsStaring(userID, repoID int64) bool { - has, _ := x.Get(&Star{0, userID, repoID}) - return has -} - -func (repo *Repository) GetStargazers(page int) ([]*User, error) { - users := make([]*User, 0, ItemsPerPage) - sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("star.repo_id=?", repo.ID) - if setting.UsePostgreSQL { - sess = sess.Join("LEFT", "star", `"user".id=star.uid`) - } else { - sess = sess.Join("LEFT", "star", "user.id=star.uid") - } - return users, sess.Find(&users) -} - -// ___________ __ -// \_ _____/__________| | __ -// | __)/ _ \_ __ \ |/ / -// | \( <_> ) | \/ < -// \___ / \____/|__| |__|_ \ -// \/ \/ - -// HasForkedRepo checks if given user has already forked a repository. -// When user has already forked, it returns true along with the repository. -func HasForkedRepo(ownerID, repoID int64) (*Repository, bool, error) { - repo := new(Repository) - has, err := x.Where("owner_id = ? AND fork_id = ?", ownerID, repoID).Get(repo) - if err != nil { - return nil, false, err - } else if !has { - return nil, false, nil - } - return repo, true, repo.LoadAttributes() -} - -// ForkRepository creates a fork of target repository under another user domain. -func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string) (_ *Repository, err error) { - if !owner.CanCreateRepo() { - return nil, errors.ReachLimitOfRepo{owner.RepoCreationNum()} - } - - repo := &Repository{ - OwnerID: owner.ID, - Owner: owner, - Name: name, - LowerName: strings.ToLower(name), - Description: desc, - DefaultBranch: baseRepo.DefaultBranch, - IsPrivate: baseRepo.IsPrivate, - IsFork: true, - ForkID: baseRepo.ID, - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - if err = createRepository(sess, doer, owner, repo); err != nil { - return nil, err - } else if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", baseRepo.ID); err != nil { - return nil, err - } - - repoPath := repo.repoPath(sess) - RemoveAllWithNotice("Repository path erase before creation", repoPath) - - _, stderr, err := process.ExecTimeout(10*time.Minute, - fmt.Sprintf("ForkRepository 'git clone': %s/%s", owner.Name, repo.Name), - "git", "clone", "--bare", baseRepo.RepoPath(), repoPath) - if err != nil { - return nil, fmt.Errorf("git clone: %v", stderr) - } - - _, stderr, err = process.ExecDir(-1, - repoPath, fmt.Sprintf("ForkRepository 'git update-server-info': %s", repoPath), - "git", "update-server-info") - if err != nil { - return nil, fmt.Errorf("git update-server-info: %v", err) - } - - if err = createDelegateHooks(repoPath); err != nil { - return nil, fmt.Errorf("createDelegateHooks: %v", err) - } - - if err = sess.Commit(); err != nil { - return nil, fmt.Errorf("Commit: %v", err) - } - - if err = repo.UpdateSize(); err != nil { - log.Error(2, "UpdateSize [repo_id: %d]: %v", repo.ID, err) - } - if err = PrepareWebhooks(baseRepo, HOOK_EVENT_FORK, &api.ForkPayload{ - Forkee: repo.APIFormat(nil), - Repo: baseRepo.APIFormat(nil), - Sender: doer.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks [repo_id: %d]: %v", baseRepo.ID, err) - } - return repo, nil -} - -func (repo *Repository) GetForks() ([]*Repository, error) { - forks := make([]*Repository, 0, repo.NumForks) - if err := x.Find(&forks, &Repository{ForkID: repo.ID}); err != nil { - return nil, err - } - - for _, fork := range forks { - fork.BaseRepo = repo - } - return forks, nil -} - -// __________ .__ -// \______ \____________ ____ ____ | |__ -// | | _/\_ __ \__ \ / \_/ ___\| | \ -// | | \ | | \// __ \| | \ \___| Y \ -// |______ / |__| (____ /___| /\___ >___| / -// \/ \/ \/ \/ \/ -// - -func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName string) (err error) { - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - - localPath := repo.LocalCopyPath() - - if err = discardLocalRepoBranchChanges(localPath, oldBranchName); err != nil { - return fmt.Errorf("discardLocalRepoChanges: %v", err) - } else if err = repo.UpdateLocalCopyBranch(oldBranchName); err != nil { - return fmt.Errorf("UpdateLocalCopyBranch: %v", err) - } - - if err = repo.CheckoutNewBranch(oldBranchName, branchName); err != nil { - return fmt.Errorf("CreateNewBranch: %v", err) - } - - if err = git.Push(localPath, "origin", branchName); err != nil { - return fmt.Errorf("Push: %v", err) - } - - return nil -} diff --git a/models/repo_branch.go b/models/repo_branch.go deleted file mode 100644 index 99fb9f04..00000000 --- a/models/repo_branch.go +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "strings" - - "github.com/unknwon/com" - "github.com/gogs/git-module" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/tool" -) - -type Branch struct { - RepoPath string - Name string - - IsProtected bool - Commit *git.Commit -} - -func GetBranchesByPath(path string) ([]*Branch, error) { - gitRepo, err := git.OpenRepository(path) - if err != nil { - return nil, err - } - - brs, err := gitRepo.GetBranches() - if err != nil { - return nil, err - } - - branches := make([]*Branch, len(brs)) - for i := range brs { - branches[i] = &Branch{ - RepoPath: path, - Name: brs[i], - } - } - return branches, nil -} - -func (repo *Repository) GetBranch(br string) (*Branch, error) { - if !git.IsBranchExist(repo.RepoPath(), br) { - return nil, errors.ErrBranchNotExist{br} - } - return &Branch{ - RepoPath: repo.RepoPath(), - Name: br, - }, nil -} - -func (repo *Repository) GetBranches() ([]*Branch, error) { - return GetBranchesByPath(repo.RepoPath()) -} - -func (br *Branch) GetCommit() (*git.Commit, error) { - gitRepo, err := git.OpenRepository(br.RepoPath) - if err != nil { - return nil, err - } - return gitRepo.GetBranchCommit(br.Name) -} - -type ProtectBranchWhitelist struct { - ID int64 - ProtectBranchID int64 - RepoID int64 `xorm:"UNIQUE(protect_branch_whitelist)"` - Name string `xorm:"UNIQUE(protect_branch_whitelist)"` - UserID int64 `xorm:"UNIQUE(protect_branch_whitelist)"` -} - -// IsUserInProtectBranchWhitelist returns true if given user is in the whitelist of a branch in a repository. -func IsUserInProtectBranchWhitelist(repoID, userID int64, branch string) bool { - has, err := x.Where("repo_id = ?", repoID).And("user_id = ?", userID).And("name = ?", branch).Get(new(ProtectBranchWhitelist)) - return has && err == nil -} - -// ProtectBranch contains options of a protected branch. -type ProtectBranch struct { - ID int64 - RepoID int64 `xorm:"UNIQUE(protect_branch)"` - Name string `xorm:"UNIQUE(protect_branch)"` - Protected bool - RequirePullRequest bool - EnableWhitelist bool - WhitelistUserIDs string `xorm:"TEXT"` - WhitelistTeamIDs string `xorm:"TEXT"` -} - -// GetProtectBranchOfRepoByName returns *ProtectBranch by branch name in given repostiory. -func GetProtectBranchOfRepoByName(repoID int64, name string) (*ProtectBranch, error) { - protectBranch := &ProtectBranch{ - RepoID: repoID, - Name: name, - } - has, err := x.Get(protectBranch) - if err != nil { - return nil, err - } else if !has { - return nil, errors.ErrBranchNotExist{name} - } - return protectBranch, nil -} - -// IsBranchOfRepoRequirePullRequest returns true if branch requires pull request in given repository. -func IsBranchOfRepoRequirePullRequest(repoID int64, name string) bool { - protectBranch, err := GetProtectBranchOfRepoByName(repoID, name) - if err != nil { - return false - } - return protectBranch.Protected && protectBranch.RequirePullRequest -} - -// UpdateProtectBranch saves branch protection options. -// If ID is 0, it creates a new record. Otherwise, updates existing record. -func UpdateProtectBranch(protectBranch *ProtectBranch) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if protectBranch.ID == 0 { - if _, err = sess.Insert(protectBranch); err != nil { - return fmt.Errorf("Insert: %v", err) - } - } - - if _, err = sess.ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil { - return fmt.Errorf("Update: %v", err) - } - - return sess.Commit() -} - -// UpdateOrgProtectBranch saves branch protection options of organizational repository. -// If ID is 0, it creates a new record. Otherwise, updates existing record. -// This function also performs check if whitelist user and team's IDs have been changed -// to avoid unnecessary whitelist delete and regenerate. -func UpdateOrgProtectBranch(repo *Repository, protectBranch *ProtectBranch, whitelistUserIDs, whitelistTeamIDs string) (err error) { - if err = repo.GetOwner(); err != nil { - return fmt.Errorf("GetOwner: %v", err) - } else if !repo.Owner.IsOrganization() { - return fmt.Errorf("expect repository owner to be an organization") - } - - hasUsersChanged := false - validUserIDs := tool.StringsToInt64s(strings.Split(protectBranch.WhitelistUserIDs, ",")) - if protectBranch.WhitelistUserIDs != whitelistUserIDs { - hasUsersChanged = true - userIDs := tool.StringsToInt64s(strings.Split(whitelistUserIDs, ",")) - validUserIDs = make([]int64, 0, len(userIDs)) - for _, userID := range userIDs { - has, err := HasAccess(userID, repo, ACCESS_MODE_WRITE) - if err != nil { - return fmt.Errorf("HasAccess [user_id: %d, repo_id: %d]: %v", userID, protectBranch.RepoID, err) - } else if !has { - continue // Drop invalid user ID - } - - validUserIDs = append(validUserIDs, userID) - } - - protectBranch.WhitelistUserIDs = strings.Join(tool.Int64sToStrings(validUserIDs), ",") - } - - hasTeamsChanged := false - validTeamIDs := tool.StringsToInt64s(strings.Split(protectBranch.WhitelistTeamIDs, ",")) - if protectBranch.WhitelistTeamIDs != whitelistTeamIDs { - hasTeamsChanged = true - teamIDs := tool.StringsToInt64s(strings.Split(whitelistTeamIDs, ",")) - teams, err := GetTeamsHaveAccessToRepo(repo.OwnerID, repo.ID, ACCESS_MODE_WRITE) - if err != nil { - return fmt.Errorf("GetTeamsHaveAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err) - } - validTeamIDs = make([]int64, 0, len(teams)) - for i := range teams { - if teams[i].HasWriteAccess() && com.IsSliceContainsInt64(teamIDs, teams[i].ID) { - validTeamIDs = append(validTeamIDs, teams[i].ID) - } - } - - protectBranch.WhitelistTeamIDs = strings.Join(tool.Int64sToStrings(validTeamIDs), ",") - } - - // Make sure protectBranch.ID is not 0 for whitelists - if protectBranch.ID == 0 { - if _, err = x.Insert(protectBranch); err != nil { - return fmt.Errorf("Insert: %v", err) - } - } - - // Merge users and members of teams - var whitelists []*ProtectBranchWhitelist - if hasUsersChanged || hasTeamsChanged { - mergedUserIDs := make(map[int64]bool) - for _, userID := range validUserIDs { - // Empty whitelist users can cause an ID with 0 - if userID != 0 { - mergedUserIDs[userID] = true - } - } - - for _, teamID := range validTeamIDs { - members, err := GetTeamMembers(teamID) - if err != nil { - return fmt.Errorf("GetTeamMembers [team_id: %d]: %v", teamID, err) - } - - for i := range members { - mergedUserIDs[members[i].ID] = true - } - } - - whitelists = make([]*ProtectBranchWhitelist, 0, len(mergedUserIDs)) - for userID := range mergedUserIDs { - whitelists = append(whitelists, &ProtectBranchWhitelist{ - ProtectBranchID: protectBranch.ID, - RepoID: repo.ID, - Name: protectBranch.Name, - UserID: userID, - }) - } - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil { - return fmt.Errorf("Update: %v", err) - } - - // Refresh whitelists - if hasUsersChanged || hasTeamsChanged { - if _, err = sess.Delete(&ProtectBranchWhitelist{ProtectBranchID: protectBranch.ID}); err != nil { - return fmt.Errorf("delete old protect branch whitelists: %v", err) - } else if _, err = sess.Insert(whitelists); err != nil { - return fmt.Errorf("insert new protect branch whitelists: %v", err) - } - } - - return sess.Commit() -} - -// GetProtectBranchesByRepoID returns a list of *ProtectBranch in given repostiory. -func GetProtectBranchesByRepoID(repoID int64) ([]*ProtectBranch, error) { - protectBranches := make([]*ProtectBranch, 0, 2) - return protectBranches, x.Where("repo_id = ? and protected = ?", repoID, true).Asc("name").Find(&protectBranches) -} diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go deleted file mode 100644 index 189d0c3f..00000000 --- a/models/repo_collaboration.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - - log "gopkg.in/clog.v1" - - api "github.com/gogs/go-gogs-client" -) - -// Collaboration represent the relation between an individual and a repository. -type Collaboration struct { - ID int64 - RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` - UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` - Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"` -} - -func (c *Collaboration) ModeI18nKey() string { - switch c.Mode { - case ACCESS_MODE_READ: - return "repo.settings.collaboration.read" - case ACCESS_MODE_WRITE: - return "repo.settings.collaboration.write" - case ACCESS_MODE_ADMIN: - return "repo.settings.collaboration.admin" - default: - return "repo.settings.collaboration.undefined" - } -} - -// IsCollaborator returns true if the user is a collaborator of the repository. -func IsCollaborator(repoID, userID int64) bool { - collaboration := &Collaboration{ - RepoID: repoID, - UserID: userID, - } - has, err := x.Get(collaboration) - if err != nil { - log.Error(2, "get collaboration [repo_id: %d, user_id: %d]: %v", repoID, userID, err) - return false - } - return has -} - -func (repo *Repository) IsCollaborator(userID int64) bool { - return IsCollaborator(repo.ID, userID) -} - -// AddCollaborator adds new collaboration to a repository with default access mode. -func (repo *Repository) AddCollaborator(u *User) error { - collaboration := &Collaboration{ - RepoID: repo.ID, - UserID: u.ID, - } - - has, err := x.Get(collaboration) - if err != nil { - return err - } else if has { - return nil - } - collaboration.Mode = ACCESS_MODE_WRITE - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Insert(collaboration); err != nil { - return err - } else if err = repo.recalculateAccesses(sess); err != nil { - return fmt.Errorf("recalculateAccesses [repo_id: %v]: %v", repo.ID, err) - } - - return sess.Commit() -} - -func (repo *Repository) getCollaborations(e Engine) ([]*Collaboration, error) { - collaborations := make([]*Collaboration, 0) - return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repo.ID}) -} - -// Collaborator represents a user with collaboration details. -type Collaborator struct { - *User - Collaboration *Collaboration -} - -func (c *Collaborator) APIFormat() *api.Collaborator { - return &api.Collaborator{ - User: c.User.APIFormat(), - Permissions: api.Permission{ - Admin: c.Collaboration.Mode >= ACCESS_MODE_ADMIN, - Push: c.Collaboration.Mode >= ACCESS_MODE_WRITE, - Pull: c.Collaboration.Mode >= ACCESS_MODE_READ, - }, - } -} - -func (repo *Repository) getCollaborators(e Engine) ([]*Collaborator, error) { - collaborations, err := repo.getCollaborations(e) - if err != nil { - return nil, fmt.Errorf("getCollaborations: %v", err) - } - - collaborators := make([]*Collaborator, len(collaborations)) - for i, c := range collaborations { - user, err := getUserByID(e, c.UserID) - if err != nil { - return nil, err - } - collaborators[i] = &Collaborator{ - User: user, - Collaboration: c, - } - } - return collaborators, nil -} - -// GetCollaborators returns the collaborators for a repository -func (repo *Repository) GetCollaborators() ([]*Collaborator, error) { - return repo.getCollaborators(x) -} - -// ChangeCollaborationAccessMode sets new access mode for the collaboration. -func (repo *Repository) ChangeCollaborationAccessMode(userID int64, mode AccessMode) error { - // Discard invalid input - if mode <= ACCESS_MODE_NONE || mode > ACCESS_MODE_OWNER { - return nil - } - - collaboration := &Collaboration{ - RepoID: repo.ID, - UserID: userID, - } - has, err := x.Get(collaboration) - if err != nil { - return fmt.Errorf("get collaboration: %v", err) - } else if !has { - return nil - } - - if collaboration.Mode == mode { - return nil - } - collaboration.Mode = mode - - // If it's an organizational repository, merge with team access level for highest permission - if repo.Owner.IsOrganization() { - teams, err := GetUserTeams(repo.OwnerID, userID) - if err != nil { - return fmt.Errorf("GetUserTeams: [org_id: %d, user_id: %d]: %v", repo.OwnerID, userID, err) - } - for i := range teams { - if mode < teams[i].Authorize { - mode = teams[i].Authorize - } - } - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.ID(collaboration.ID).AllCols().Update(collaboration); err != nil { - return fmt.Errorf("update collaboration: %v", err) - } - - access := &Access{ - UserID: userID, - RepoID: repo.ID, - } - has, err = sess.Get(access) - if err != nil { - return fmt.Errorf("get access record: %v", err) - } - if has { - _, err = sess.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, userID, repo.ID) - } else { - access.Mode = mode - _, err = sess.Insert(access) - } - if err != nil { - return fmt.Errorf("update/insert access table: %v", err) - } - - return sess.Commit() -} - -// DeleteCollaboration removes collaboration relation between the user and repository. -func DeleteCollaboration(repo *Repository, userID int64) (err error) { - if !IsCollaborator(repo.ID, userID) { - return nil - } - - collaboration := &Collaboration{ - RepoID: repo.ID, - UserID: userID, - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if has, err := sess.Delete(collaboration); err != nil || has == 0 { - return err - } else if err = repo.recalculateAccesses(sess); err != nil { - return err - } - - return sess.Commit() -} - -func (repo *Repository) DeleteCollaboration(userID int64) error { - return DeleteCollaboration(repo, userID) -} diff --git a/models/repo_editor.go b/models/repo_editor.go deleted file mode 100644 index 19eb9597..00000000 --- a/models/repo_editor.go +++ /dev/null @@ -1,518 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "io" - "io/ioutil" - "mime/multipart" - "os" - "os/exec" - "path" - "path/filepath" - "strings" - "time" - - "github.com/unknwon/com" - gouuid "github.com/satori/go.uuid" - - "github.com/gogs/git-module" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/process" - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/tool" -) - -const ( - ENV_AUTH_USER_ID = "GOGS_AUTH_USER_ID" - ENV_AUTH_USER_NAME = "GOGS_AUTH_USER_NAME" - ENV_AUTH_USER_EMAIL = "GOGS_AUTH_USER_EMAIL" - ENV_REPO_OWNER_NAME = "GOGS_REPO_OWNER_NAME" - ENV_REPO_OWNER_SALT_MD5 = "GOGS_REPO_OWNER_SALT_MD5" - ENV_REPO_ID = "GOGS_REPO_ID" - ENV_REPO_NAME = "GOGS_REPO_NAME" - ENV_REPO_CUSTOM_HOOKS_PATH = "GOGS_REPO_CUSTOM_HOOKS_PATH" -) - -type ComposeHookEnvsOptions struct { - AuthUser *User - OwnerName string - OwnerSalt string - RepoID int64 - RepoName string - RepoPath string -} - -func ComposeHookEnvs(opts ComposeHookEnvsOptions) []string { - envs := []string{ - "SSH_ORIGINAL_COMMAND=1", - ENV_AUTH_USER_ID + "=" + com.ToStr(opts.AuthUser.ID), - ENV_AUTH_USER_NAME + "=" + opts.AuthUser.Name, - ENV_AUTH_USER_EMAIL + "=" + opts.AuthUser.Email, - ENV_REPO_OWNER_NAME + "=" + opts.OwnerName, - ENV_REPO_OWNER_SALT_MD5 + "=" + tool.MD5(opts.OwnerSalt), - ENV_REPO_ID + "=" + com.ToStr(opts.RepoID), - ENV_REPO_NAME + "=" + opts.RepoName, - ENV_REPO_CUSTOM_HOOKS_PATH + "=" + path.Join(opts.RepoPath, "custom_hooks"), - } - return envs -} - -// ___________ .___.__ __ ___________.__.__ -// \_ _____/ __| _/|__|/ |_ \_ _____/|__| | ____ -// | __)_ / __ | | \ __\ | __) | | | _/ __ \ -// | \/ /_/ | | || | | \ | | |_\ ___/ -// /_______ /\____ | |__||__| \___ / |__|____/\___ > -// \/ \/ \/ \/ - -// discardLocalRepoBranchChanges discards local commits/changes of -// given branch to make sure it is even to remote branch. -func discardLocalRepoBranchChanges(localPath, branch string) error { - if !com.IsExist(localPath) { - return nil - } - // No need to check if nothing in the repository. - if !git.IsBranchExist(localPath, branch) { - return nil - } - - refName := "origin/" + branch - if err := git.ResetHEAD(localPath, true, refName); err != nil { - return fmt.Errorf("git reset --hard %s: %v", refName, err) - } - return nil -} - -func (repo *Repository) DiscardLocalRepoBranchChanges(branch string) error { - return discardLocalRepoBranchChanges(repo.LocalCopyPath(), branch) -} - -// checkoutNewBranch checks out to a new branch from the a branch name. -func checkoutNewBranch(repoPath, localPath, oldBranch, newBranch string) error { - if err := git.Checkout(localPath, git.CheckoutOptions{ - Timeout: time.Duration(setting.Git.Timeout.Pull) * time.Second, - Branch: newBranch, - OldBranch: oldBranch, - }); err != nil { - return fmt.Errorf("git checkout -b %s %s: %v", newBranch, oldBranch, err) - } - return nil -} - -func (repo *Repository) CheckoutNewBranch(oldBranch, newBranch string) error { - return checkoutNewBranch(repo.RepoPath(), repo.LocalCopyPath(), oldBranch, newBranch) -} - -type UpdateRepoFileOptions struct { - LastCommitID string - OldBranch string - NewBranch string - OldTreeName string - NewTreeName string - Message string - Content string - IsNewFile bool -} - -// UpdateRepoFile adds or updates a file in repository. -func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (err error) { - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - - if err = repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil { - return fmt.Errorf("discard local repo branch[%s] changes: %v", opts.OldBranch, err) - } else if err = repo.UpdateLocalCopyBranch(opts.OldBranch); err != nil { - return fmt.Errorf("update local copy branch[%s]: %v", opts.OldBranch, err) - } - - repoPath := repo.RepoPath() - localPath := repo.LocalCopyPath() - - if opts.OldBranch != opts.NewBranch { - // Directly return error if new branch already exists in the server - if git.IsBranchExist(repoPath, opts.NewBranch) { - return errors.BranchAlreadyExists{opts.NewBranch} - } - - // Otherwise, delete branch from local copy in case out of sync - if git.IsBranchExist(localPath, opts.NewBranch) { - if err = git.DeleteBranch(localPath, opts.NewBranch, git.DeleteBranchOptions{ - Force: true, - }); err != nil { - return fmt.Errorf("delete branch[%s]: %v", opts.NewBranch, err) - } - } - - if err := repo.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil { - return fmt.Errorf("checkout new branch[%s] from old branch[%s]: %v", opts.NewBranch, opts.OldBranch, err) - } - } - - oldFilePath := path.Join(localPath, opts.OldTreeName) - filePath := path.Join(localPath, opts.NewTreeName) - os.MkdirAll(path.Dir(filePath), os.ModePerm) - - // If it's meant to be a new file, make sure it doesn't exist. - if opts.IsNewFile { - if com.IsExist(filePath) { - return ErrRepoFileAlreadyExist{filePath} - } - } - - // Ignore move step if it's a new file under a directory. - // Otherwise, move the file when name changed. - if com.IsFile(oldFilePath) && opts.OldTreeName != opts.NewTreeName { - if err = git.MoveFile(localPath, opts.OldTreeName, opts.NewTreeName); err != nil { - return fmt.Errorf("git mv %q %q: %v", opts.OldTreeName, opts.NewTreeName, err) - } - } - - if err = ioutil.WriteFile(filePath, []byte(opts.Content), 0666); err != nil { - return fmt.Errorf("write file: %v", err) - } - - if err = git.AddChanges(localPath, true); err != nil { - return fmt.Errorf("git add --all: %v", err) - } else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ - Committer: doer.NewGitSig(), - Message: opts.Message, - }); err != nil { - return fmt.Errorf("commit changes on %q: %v", localPath, err) - } else if err = git.PushWithEnvs(localPath, "origin", opts.NewBranch, - ComposeHookEnvs(ComposeHookEnvsOptions{ - AuthUser: doer, - OwnerName: repo.MustOwner().Name, - OwnerSalt: repo.MustOwner().Salt, - RepoID: repo.ID, - RepoName: repo.Name, - RepoPath: repo.RepoPath(), - })); err != nil { - return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err) - } - return nil -} - -// GetDiffPreview produces and returns diff result of a file which is not yet committed. -func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *Diff, err error) { - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - - if err = repo.DiscardLocalRepoBranchChanges(branch); err != nil { - return nil, fmt.Errorf("discard local repo branch[%s] changes: %v", branch, err) - } else if err = repo.UpdateLocalCopyBranch(branch); err != nil { - return nil, fmt.Errorf("update local copy branch[%s]: %v", branch, err) - } - - localPath := repo.LocalCopyPath() - filePath := path.Join(localPath, treePath) - os.MkdirAll(filepath.Dir(filePath), os.ModePerm) - if err = ioutil.WriteFile(filePath, []byte(content), 0666); err != nil { - return nil, fmt.Errorf("write file: %v", err) - } - - cmd := exec.Command("git", "diff", treePath) - cmd.Dir = localPath - cmd.Stderr = os.Stderr - - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, fmt.Errorf("get stdout pipe: %v", err) - } - - if err = cmd.Start(); err != nil { - return nil, fmt.Errorf("start: %v", err) - } - - pid := process.Add(fmt.Sprintf("GetDiffPreview [repo_path: %s]", repo.RepoPath()), cmd) - defer process.Remove(pid) - - diff, err = ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdout) - if err != nil { - return nil, fmt.Errorf("parse path: %v", err) - } - - if err = cmd.Wait(); err != nil { - return nil, fmt.Errorf("wait: %v", err) - } - - return diff, nil -} - -// ________ .__ __ ___________.__.__ -// \______ \ ____ | | _____/ |_ ____ \_ _____/|__| | ____ -// | | \_/ __ \| | _/ __ \ __\/ __ \ | __) | | | _/ __ \ -// | ` \ ___/| |_\ ___/| | \ ___/ | \ | | |_\ ___/ -// /_______ /\___ >____/\___ >__| \___ > \___ / |__|____/\___ > -// \/ \/ \/ \/ \/ \/ -// - -type DeleteRepoFileOptions struct { - LastCommitID string - OldBranch string - NewBranch string - TreePath string - Message string -} - -func (repo *Repository) DeleteRepoFile(doer *User, opts DeleteRepoFileOptions) (err error) { - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - - if err = repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil { - return fmt.Errorf("discard local repo branch[%s] changes: %v", opts.OldBranch, err) - } else if err = repo.UpdateLocalCopyBranch(opts.OldBranch); err != nil { - return fmt.Errorf("update local copy branch[%s]: %v", opts.OldBranch, err) - } - - if opts.OldBranch != opts.NewBranch { - if err := repo.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil { - return fmt.Errorf("checkout new branch[%s] from old branch[%s]: %v", opts.NewBranch, opts.OldBranch, err) - } - } - - localPath := repo.LocalCopyPath() - if err = os.Remove(path.Join(localPath, opts.TreePath)); err != nil { - return fmt.Errorf("remove file %q: %v", opts.TreePath, err) - } - - if err = git.AddChanges(localPath, true); err != nil { - return fmt.Errorf("git add --all: %v", err) - } else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ - Committer: doer.NewGitSig(), - Message: opts.Message, - }); err != nil { - return fmt.Errorf("commit changes to %q: %v", localPath, err) - } else if err = git.PushWithEnvs(localPath, "origin", opts.NewBranch, - ComposeHookEnvs(ComposeHookEnvsOptions{ - AuthUser: doer, - OwnerName: repo.MustOwner().Name, - OwnerSalt: repo.MustOwner().Salt, - RepoID: repo.ID, - RepoName: repo.Name, - RepoPath: repo.RepoPath(), - })); err != nil { - return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err) - } - return nil -} - -// ____ ___ .__ .___ ___________.___.__ -// | | \______ | | _________ __| _/ \_ _____/| | | ____ ______ -// | | /\____ \| | / _ \__ \ / __ | | __) | | | _/ __ \ / ___/ -// | | / | |_> > |_( <_> ) __ \_/ /_/ | | \ | | |_\ ___/ \___ \ -// |______/ | __/|____/\____(____ /\____ | \___ / |___|____/\___ >____ > -// |__| \/ \/ \/ \/ \/ -// - -// Upload represent a uploaded file to a repo to be deleted when moved -type Upload struct { - ID int64 - UUID string `xorm:"uuid UNIQUE"` - Name string -} - -// UploadLocalPath returns where uploads is stored in local file system based on given UUID. -func UploadLocalPath(uuid string) string { - return path.Join(setting.Repository.Upload.TempPath, uuid[0:1], uuid[1:2], uuid) -} - -// LocalPath returns where uploads are temporarily stored in local file system. -func (upload *Upload) LocalPath() string { - return UploadLocalPath(upload.UUID) -} - -// NewUpload creates a new upload object. -func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err error) { - if tool.IsMaliciousPath(name) { - return nil, fmt.Errorf("malicious path detected: %s", name) - } - - upload := &Upload{ - UUID: gouuid.NewV4().String(), - Name: name, - } - - localPath := upload.LocalPath() - if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil { - return nil, fmt.Errorf("mkdir all: %v", err) - } - - fw, err := os.Create(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) - } - - if _, err := x.Insert(upload); err != nil { - return nil, err - } - - return upload, nil -} - -func GetUploadByUUID(uuid string) (*Upload, error) { - upload := &Upload{UUID: uuid} - has, err := x.Get(upload) - if err != nil { - return nil, err - } else if !has { - return nil, ErrUploadNotExist{0, uuid} - } - return upload, nil -} - -func GetUploadsByUUIDs(uuids []string) ([]*Upload, error) { - if len(uuids) == 0 { - return []*Upload{}, nil - } - - // Silently drop invalid uuids. - uploads := make([]*Upload, 0, len(uuids)) - return uploads, x.In("uuid", uuids).Find(&uploads) -} - -func DeleteUploads(uploads ...*Upload) (err error) { - if len(uploads) == 0 { - return nil - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - ids := make([]int64, len(uploads)) - for i := 0; i < len(uploads); i++ { - ids[i] = uploads[i].ID - } - if _, err = sess.In("id", ids).Delete(new(Upload)); err != nil { - return fmt.Errorf("delete uploads: %v", err) - } - - for _, upload := range uploads { - localPath := upload.LocalPath() - if !com.IsFile(localPath) { - continue - } - - if err := os.Remove(localPath); err != nil { - return fmt.Errorf("remove upload: %v", err) - } - } - - return sess.Commit() -} - -func DeleteUpload(u *Upload) error { - return DeleteUploads(u) -} - -func DeleteUploadByUUID(uuid string) error { - upload, err := GetUploadByUUID(uuid) - if err != nil { - if IsErrUploadNotExist(err) { - return nil - } - return fmt.Errorf("get upload by UUID[%s]: %v", uuid, err) - } - - if err := DeleteUpload(upload); err != nil { - return fmt.Errorf("delete upload: %v", err) - } - - return nil -} - -type UploadRepoFileOptions struct { - LastCommitID string - OldBranch string - NewBranch string - TreePath string - Message string - Files []string // In UUID format -} - -// isRepositoryGitPath returns true if given path is or resides inside ".git" path of the repository. -func isRepositoryGitPath(path string) bool { - return strings.HasSuffix(path, ".git") || strings.Contains(path, ".git"+string(os.PathSeparator)) -} - -func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) (err error) { - if len(opts.Files) == 0 { - return nil - } - - uploads, err := GetUploadsByUUIDs(opts.Files) - if err != nil { - return fmt.Errorf("get uploads by UUIDs[%v]: %v", opts.Files, err) - } - - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - - if err = repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil { - return fmt.Errorf("discard local repo branch[%s] changes: %v", opts.OldBranch, err) - } else if err = repo.UpdateLocalCopyBranch(opts.OldBranch); err != nil { - return fmt.Errorf("update local copy branch[%s]: %v", opts.OldBranch, err) - } - - if opts.OldBranch != opts.NewBranch { - if err = repo.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil { - return fmt.Errorf("checkout new branch[%s] from old branch[%s]: %v", opts.NewBranch, opts.OldBranch, err) - } - } - - localPath := repo.LocalCopyPath() - dirPath := path.Join(localPath, opts.TreePath) - os.MkdirAll(dirPath, os.ModePerm) - - // Copy uploaded files into repository - for _, upload := range uploads { - tmpPath := upload.LocalPath() - if !com.IsFile(tmpPath) { - continue - } - - // Prevent copying files into .git directory, see https://gogs.io/gogs/issues/5558. - if isRepositoryGitPath(upload.Name) { - continue - } - - targetPath := path.Join(dirPath, upload.Name) - if err = com.Copy(tmpPath, targetPath); err != nil { - return fmt.Errorf("copy: %v", err) - } - } - - if err = git.AddChanges(localPath, true); err != nil { - return fmt.Errorf("git add --all: %v", err) - } else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ - Committer: doer.NewGitSig(), - Message: opts.Message, - }); err != nil { - return fmt.Errorf("commit changes on %q: %v", localPath, err) - } else if err = git.PushWithEnvs(localPath, "origin", opts.NewBranch, - ComposeHookEnvs(ComposeHookEnvsOptions{ - AuthUser: doer, - OwnerName: repo.MustOwner().Name, - OwnerSalt: repo.MustOwner().Salt, - RepoID: repo.ID, - RepoName: repo.Name, - RepoPath: repo.RepoPath(), - })); err != nil { - return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err) - } - - return DeleteUploads(uploads...) -} diff --git a/models/repo_editor_test.go b/models/repo_editor_test.go deleted file mode 100644 index 396382e3..00000000 --- a/models/repo_editor_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "os" - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -func Test_isRepositoryGitPath(t *testing.T) { - Convey("Check if path is or resides inside '.git'", t, func() { - sep := string(os.PathSeparator) - testCases := []struct { - path string - expect bool - }{ - {"." + sep + ".git", true}, - {"." + sep + ".git" + sep + "", true}, - {"." + sep + ".git" + sep + "hooks" + sep + "pre-commit", true}, - {".git" + sep + "hooks", true}, - {"dir" + sep + ".git", true}, - - {".gitignore", false}, - {"dir" + sep + ".gitkeep", false}, - } - for _, tc := range testCases { - So(isRepositoryGitPath(tc.path), ShouldEqual, tc.expect) - } - }) -} diff --git a/models/repo_test.go b/models/repo_test.go deleted file mode 100644 index 3d852e07..00000000 --- a/models/repo_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package models_test - -import ( - "testing" - - . "github.com/smartystreets/goconvey/convey" - - . "gogs.io/gogs/models" - "gogs.io/gogs/pkg/markup" -) - -func TestRepo(t *testing.T) { - Convey("The metas map", t, func() { - var repo = new(Repository) - repo.Name = "testrepo" - repo.Owner = new(User) - repo.Owner.Name = "testuser" - repo.ExternalTrackerFormat = "https://someurl.com/{user}/{repo}/{issue}" - - Convey("When no external tracker is configured", func() { - Convey("It should be nil", func() { - repo.EnableExternalTracker = false - So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil)) - }) - Convey("It should be nil even if other settings are present", func() { - repo.EnableExternalTracker = false - repo.ExternalTrackerFormat = "http://someurl.com/{user}/{repo}/{issue}" - repo.ExternalTrackerStyle = markup.ISSUE_NAME_STYLE_NUMERIC - So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil)) - }) - }) - - Convey("When an external issue tracker is configured", func() { - repo.EnableExternalTracker = true - Convey("It should default to numeric issue style", func() { - metas := repo.ComposeMetas() - So(metas["style"], ShouldEqual, markup.ISSUE_NAME_STYLE_NUMERIC) - }) - Convey("It should pass through numeric issue style setting", func() { - repo.ExternalTrackerStyle = markup.ISSUE_NAME_STYLE_NUMERIC - metas := repo.ComposeMetas() - So(metas["style"], ShouldEqual, markup.ISSUE_NAME_STYLE_NUMERIC) - }) - Convey("It should pass through alphanumeric issue style setting", func() { - repo.ExternalTrackerStyle = markup.ISSUE_NAME_STYLE_ALPHANUMERIC - metas := repo.ComposeMetas() - So(metas["style"], ShouldEqual, markup.ISSUE_NAME_STYLE_ALPHANUMERIC) - }) - Convey("It should contain the user name", func() { - metas := repo.ComposeMetas() - So(metas["user"], ShouldEqual, "testuser") - }) - Convey("It should contain the repo name", func() { - metas := repo.ComposeMetas() - So(metas["repo"], ShouldEqual, "testrepo") - }) - Convey("It should contain the URL format", func() { - metas := repo.ComposeMetas() - So(metas["format"], ShouldEqual, "https://someurl.com/{user}/{repo}/{issue}") - }) - }) - }) -} diff --git a/models/ssh_key.go b/models/ssh_key.go deleted file mode 100644 index 3f94475e..00000000 --- a/models/ssh_key.go +++ /dev/null @@ -1,771 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "encoding/base64" - "encoding/binary" - "errors" - "fmt" - "io/ioutil" - "math/big" - "os" - "path" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/unknwon/com" - "xorm.io/xorm" - "golang.org/x/crypto/ssh" - log "gopkg.in/clog.v1" - - "gogs.io/gogs/pkg/process" - "gogs.io/gogs/pkg/setting" -) - -const ( - _TPL_PUBLICK_KEY = `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n" -) - -var sshOpLocker sync.Mutex - -type KeyType int - -const ( - KEY_TYPE_USER = iota + 1 - KEY_TYPE_DEPLOY -) - -// PublicKey represents a user or deploy SSH public key. -type PublicKey struct { - ID int64 - OwnerID int64 `xorm:"INDEX NOT NULL"` - Name string `xorm:"NOT NULL"` - Fingerprint string `xorm:"NOT NULL"` - Content string `xorm:"TEXT NOT NULL"` - Mode AccessMode `xorm:"NOT NULL DEFAULT 2"` - Type KeyType `xorm:"NOT NULL DEFAULT 1"` - - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-"` // Note: Updated must below Created for AfterSet. - UpdatedUnix int64 - HasRecentActivity bool `xorm:"-" json:"-"` - HasUsed bool `xorm:"-" json:"-"` -} - -func (k *PublicKey) BeforeInsert() { - k.CreatedUnix = time.Now().Unix() -} - -func (k *PublicKey) BeforeUpdate() { - k.UpdatedUnix = time.Now().Unix() -} - -func (k *PublicKey) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - k.Created = time.Unix(k.CreatedUnix, 0).Local() - case "updated_unix": - k.Updated = time.Unix(k.UpdatedUnix, 0).Local() - k.HasUsed = k.Updated.After(k.Created) - k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now()) - } -} - -// OmitEmail returns content of public key without email address. -func (k *PublicKey) OmitEmail() string { - return strings.Join(strings.Split(k.Content, " ")[:2], " ") -} - -// AuthorizedString returns formatted public key string for authorized_keys file. -func (k *PublicKey) AuthorizedString() string { - return fmt.Sprintf(_TPL_PUBLICK_KEY, setting.AppPath, k.ID, setting.CustomConf, k.Content) -} - -// IsDeployKey returns true if the public key is used as deploy key. -func (k *PublicKey) IsDeployKey() bool { - return k.Type == KEY_TYPE_DEPLOY -} - -func extractTypeFromBase64Key(key string) (string, error) { - b, err := base64.StdEncoding.DecodeString(key) - if err != nil || len(b) < 4 { - return "", fmt.Errorf("invalid key format: %v", err) - } - - keyLength := int(binary.BigEndian.Uint32(b)) - if len(b) < 4+keyLength { - return "", fmt.Errorf("invalid key format: not enough length %d", keyLength) - } - - return string(b[4 : 4+keyLength]), nil -} - -// parseKeyString parses any key string in OpenSSH or SSH2 format to clean OpenSSH string (RFC4253). -func parseKeyString(content string) (string, error) { - // Transform all legal line endings to a single "\n" - - // Replace all windows full new lines ("\r\n") - content = strings.Replace(content, "\r\n", "\n", -1) - - // Replace all windows half new lines ("\r"), if it happen not to match replace above - content = strings.Replace(content, "\r", "\n", -1) - - // Replace ending new line as its may cause unwanted behaviour (extra line means not a single line key | OpenSSH key) - content = strings.TrimRight(content, "\n") - - // split lines - lines := strings.Split(content, "\n") - - var keyType, keyContent, keyComment string - - if len(lines) == 1 { - // Parse OpenSSH format. - parts := strings.SplitN(lines[0], " ", 3) - switch len(parts) { - case 0: - return "", errors.New("empty key") - case 1: - keyContent = parts[0] - case 2: - keyType = parts[0] - keyContent = parts[1] - default: - keyType = parts[0] - keyContent = parts[1] - keyComment = parts[2] - } - - // If keyType is not given, extract it from content. If given, validate it. - t, err := extractTypeFromBase64Key(keyContent) - if err != nil { - return "", fmt.Errorf("extractTypeFromBase64Key: %v", err) - } - if len(keyType) == 0 { - keyType = t - } else if keyType != t { - return "", fmt.Errorf("key type and content does not match: %s - %s", keyType, t) - } - } else { - // Parse SSH2 file format. - continuationLine := false - - for _, line := range lines { - // Skip lines that: - // 1) are a continuation of the previous line, - // 2) contain ":" as that are comment lines - // 3) contain "-" as that are begin and end tags - if continuationLine || strings.ContainsAny(line, ":-") { - continuationLine = strings.HasSuffix(line, "\\") - } else { - keyContent = keyContent + line - } - } - - t, err := extractTypeFromBase64Key(keyContent) - if err != nil { - return "", fmt.Errorf("extractTypeFromBase64Key: %v", err) - } - keyType = t - } - return keyType + " " + keyContent + " " + keyComment, nil -} - -// writeTmpKeyFile writes key content to a temporary file -// and returns the name of that file, along with any possible errors. -func writeTmpKeyFile(content string) (string, error) { - tmpFile, err := ioutil.TempFile(setting.SSH.KeyTestPath, "gogs_keytest") - if err != nil { - return "", fmt.Errorf("TempFile: %v", err) - } - defer tmpFile.Close() - - if _, err = tmpFile.WriteString(content); err != nil { - return "", fmt.Errorf("WriteString: %v", err) - } - return tmpFile.Name(), nil -} - -// SSHKeyGenParsePublicKey extracts key type and length using ssh-keygen. -func SSHKeyGenParsePublicKey(key string) (string, int, error) { - tmpName, err := writeTmpKeyFile(key) - if err != nil { - return "", 0, fmt.Errorf("writeTmpKeyFile: %v", err) - } - defer os.Remove(tmpName) - - stdout, stderr, err := process.Exec("SSHKeyGenParsePublicKey", setting.SSH.KeygenPath, "-lf", tmpName) - if err != nil { - return "", 0, fmt.Errorf("fail to parse public key: %s - %s", err, stderr) - } - if strings.Contains(stdout, "is not a public key file") { - return "", 0, ErrKeyUnableVerify{stdout} - } - - fields := strings.Split(stdout, " ") - if len(fields) < 4 { - return "", 0, fmt.Errorf("invalid public key line: %s", stdout) - } - - keyType := strings.Trim(fields[len(fields)-1], "()\r\n") - return strings.ToLower(keyType), com.StrTo(fields[0]).MustInt(), nil -} - -// SSHNativeParsePublicKey extracts the key type and length using the golang SSH library. -func SSHNativeParsePublicKey(keyLine string) (string, int, error) { - fields := strings.Fields(keyLine) - if len(fields) < 2 { - return "", 0, fmt.Errorf("not enough fields in public key line: %s", string(keyLine)) - } - - raw, err := base64.StdEncoding.DecodeString(fields[1]) - if err != nil { - return "", 0, err - } - - pkey, err := ssh.ParsePublicKey(raw) - if err != nil { - if strings.Contains(err.Error(), "ssh: unknown key algorithm") { - return "", 0, ErrKeyUnableVerify{err.Error()} - } - return "", 0, fmt.Errorf("ParsePublicKey: %v", err) - } - - // The ssh library can parse the key, so next we find out what key exactly we have. - switch pkey.Type() { - case ssh.KeyAlgoDSA: - rawPub := struct { - Name string - P, Q, G, Y *big.Int - }{} - if err := ssh.Unmarshal(pkey.Marshal(), &rawPub); err != nil { - return "", 0, err - } - // as per https://bugzilla.mindrot.org/show_bug.cgi?id=1647 we should never - // see dsa keys != 1024 bit, but as it seems to work, we will not check here - return "dsa", rawPub.P.BitLen(), nil // use P as per crypto/dsa/dsa.go (is L) - case ssh.KeyAlgoRSA: - rawPub := struct { - Name string - E *big.Int - N *big.Int - }{} - if err := ssh.Unmarshal(pkey.Marshal(), &rawPub); err != nil { - return "", 0, err - } - return "rsa", rawPub.N.BitLen(), nil // use N as per crypto/rsa/rsa.go (is bits) - case ssh.KeyAlgoECDSA256: - return "ecdsa", 256, nil - case ssh.KeyAlgoECDSA384: - return "ecdsa", 384, nil - case ssh.KeyAlgoECDSA521: - return "ecdsa", 521, nil - case ssh.KeyAlgoED25519: - return "ed25519", 256, nil - } - return "", 0, fmt.Errorf("unsupported key length detection for type: %s", pkey.Type()) -} - -// CheckPublicKeyString checks if the given public key string is recognized by SSH. -// It returns the actual public key line on success. -func CheckPublicKeyString(content string) (_ string, err error) { - if setting.SSH.Disabled { - return "", errors.New("SSH is disabled") - } - - content, err = parseKeyString(content) - if err != nil { - return "", err - } - - content = strings.TrimRight(content, "\n\r") - if strings.ContainsAny(content, "\n\r") { - return "", errors.New("only a single line with a single key please") - } - - // Remove any unnecessary whitespace - content = strings.TrimSpace(content) - - if !setting.SSH.MinimumKeySizeCheck { - return content, nil - } - - var ( - fnName string - keyType string - length int - ) - if setting.SSH.StartBuiltinServer { - fnName = "SSHNativeParsePublicKey" - keyType, length, err = SSHNativeParsePublicKey(content) - } else { - fnName = "SSHKeyGenParsePublicKey" - keyType, length, err = SSHKeyGenParsePublicKey(content) - } - if err != nil { - return "", fmt.Errorf("%s: %v", fnName, err) - } - log.Trace("Key info [native: %v]: %s-%d", setting.SSH.StartBuiltinServer, keyType, length) - - if minLen, found := setting.SSH.MinimumKeySizes[keyType]; found && length >= minLen { - return content, nil - } else if found && length < minLen { - return "", fmt.Errorf("key length is not enough: got %d, needs %d", length, minLen) - } - return "", fmt.Errorf("key type is not allowed: %s", keyType) -} - -// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file. -func appendAuthorizedKeysToFile(keys ...*PublicKey) error { - sshOpLocker.Lock() - defer sshOpLocker.Unlock() - - fpath := filepath.Join(setting.SSH.RootPath, "authorized_keys") - f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) - if err != nil { - return err - } - defer f.Close() - - // Note: chmod command does not support in Windows. - if !setting.IsWindows { - fi, err := f.Stat() - if err != nil { - return err - } - - // .ssh directory should have mode 700, and authorized_keys file should have mode 600. - if fi.Mode().Perm() > 0600 { - log.Error(4, "authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String()) - if err = f.Chmod(0600); err != nil { - return err - } - } - } - - for _, key := range keys { - if _, err = f.WriteString(key.AuthorizedString()); err != nil { - return err - } - } - return nil -} - -// checkKeyContent onlys checks if key content has been used as public key, -// it is OK to use same key as deploy key for multiple repositories/users. -func checkKeyContent(content string) error { - has, err := x.Get(&PublicKey{ - Content: content, - Type: KEY_TYPE_USER, - }) - if err != nil { - return err - } else if has { - return ErrKeyAlreadyExist{0, content} - } - return nil -} - -func addKey(e Engine, key *PublicKey) (err error) { - // Calculate fingerprint. - tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), - "id_rsa.pub"), "\\", "/", -1) - os.MkdirAll(path.Dir(tmpPath), os.ModePerm) - if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil { - return err - } - - stdout, stderr, err := process.Exec("AddPublicKey", setting.SSH.KeygenPath, "-lf", tmpPath) - if err != nil { - return fmt.Errorf("fail to parse public key: %s - %s", err, stderr) - } else if len(stdout) < 2 { - return errors.New("not enough output for calculating fingerprint: " + stdout) - } - key.Fingerprint = strings.Split(stdout, " ")[1] - - // Save SSH key. - if _, err = e.Insert(key); err != nil { - return err - } - - // Don't need to rewrite this file if builtin SSH server is enabled. - if setting.SSH.StartBuiltinServer { - return nil - } - return appendAuthorizedKeysToFile(key) -} - -// AddPublicKey adds new public key to database and authorized_keys file. -func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) { - log.Trace(content) - if err := checkKeyContent(content); err != nil { - return nil, err - } - - // Key name of same user cannot be duplicated. - has, err := x.Where("owner_id = ? AND name = ?", ownerID, name).Get(new(PublicKey)) - if err != nil { - return nil, err - } else if has { - return nil, ErrKeyNameAlreadyUsed{ownerID, name} - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - key := &PublicKey{ - OwnerID: ownerID, - Name: name, - Content: content, - Mode: ACCESS_MODE_WRITE, - Type: KEY_TYPE_USER, - } - if err = addKey(sess, key); err != nil { - return nil, fmt.Errorf("addKey: %v", err) - } - - return key, sess.Commit() -} - -// GetPublicKeyByID returns public key by given ID. -func GetPublicKeyByID(keyID int64) (*PublicKey, error) { - key := new(PublicKey) - has, err := x.Id(keyID).Get(key) - if err != nil { - return nil, err - } else if !has { - return nil, ErrKeyNotExist{keyID} - } - return key, nil -} - -// SearchPublicKeyByContent searches content as prefix (leak e-mail part) -// and returns public key found. -func SearchPublicKeyByContent(content string) (*PublicKey, error) { - key := new(PublicKey) - has, err := x.Where("content like ?", content+"%").Get(key) - if err != nil { - return nil, err - } else if !has { - return nil, ErrKeyNotExist{} - } - return key, nil -} - -// ListPublicKeys returns a list of public keys belongs to given user. -func ListPublicKeys(uid int64) ([]*PublicKey, error) { - keys := make([]*PublicKey, 0, 5) - return keys, x.Where("owner_id = ?", uid).Find(&keys) -} - -// UpdatePublicKey updates given public key. -func UpdatePublicKey(key *PublicKey) error { - _, err := x.Id(key.ID).AllCols().Update(key) - return err -} - -// deletePublicKeys does the actual key deletion but does not update authorized_keys file. -func deletePublicKeys(e *xorm.Session, keyIDs ...int64) error { - if len(keyIDs) == 0 { - return nil - } - - _, err := e.In("id", keyIDs).Delete(new(PublicKey)) - return err -} - -// DeletePublicKey deletes SSH key information both in database and authorized_keys file. -func DeletePublicKey(doer *User, id int64) (err error) { - key, err := GetPublicKeyByID(id) - if err != nil { - if IsErrKeyNotExist(err) { - return nil - } - return fmt.Errorf("GetPublicKeyByID: %v", err) - } - - // Check if user has access to delete this key. - if !doer.IsAdmin && doer.ID != key.OwnerID { - return ErrKeyAccessDenied{doer.ID, key.ID, "public"} - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = deletePublicKeys(sess, id); err != nil { - return err - } - - if err = sess.Commit(); err != nil { - return err - } - - return RewriteAuthorizedKeys() -} - -// RewriteAuthorizedKeys removes any authorized key and rewrite all keys from database again. -// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function -// outsite any session scope independently. -func RewriteAuthorizedKeys() error { - sshOpLocker.Lock() - defer sshOpLocker.Unlock() - - log.Trace("Doing: RewriteAuthorizedKeys") - - os.MkdirAll(setting.SSH.RootPath, os.ModePerm) - fpath := filepath.Join(setting.SSH.RootPath, "authorized_keys") - tmpPath := fpath + ".tmp" - f, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer os.Remove(tmpPath) - - err = x.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) { - _, err = f.WriteString((bean.(*PublicKey)).AuthorizedString()) - return err - }) - f.Close() - if err != nil { - return err - } - - if com.IsExist(fpath) { - if err = os.Remove(fpath); err != nil { - return err - } - } - if err = os.Rename(tmpPath, fpath); err != nil { - return err - } - - return nil -} - -// ________ .__ ____ __. -// \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__. -// | | \_/ __ \\____ \| | / _ < | || <_/ __ < | | -// | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ | -// /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____| -// \/ \/|__| \/ \/ \/\/ - -// DeployKey represents deploy key information and its relation with repository. -type DeployKey struct { - ID int64 - KeyID int64 `xorm:"UNIQUE(s) INDEX"` - RepoID int64 `xorm:"UNIQUE(s) INDEX"` - Name string - Fingerprint string - Content string `xorm:"-" json:"-"` - - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-"` // Note: Updated must below Created for AfterSet. - UpdatedUnix int64 - HasRecentActivity bool `xorm:"-" json:"-"` - HasUsed bool `xorm:"-" json:"-"` -} - -func (k *DeployKey) BeforeInsert() { - k.CreatedUnix = time.Now().Unix() -} - -func (k *DeployKey) BeforeUpdate() { - k.UpdatedUnix = time.Now().Unix() -} - -func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - k.Created = time.Unix(k.CreatedUnix, 0).Local() - case "updated_unix": - k.Updated = time.Unix(k.UpdatedUnix, 0).Local() - k.HasUsed = k.Updated.After(k.Created) - k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now()) - } -} - -// GetContent gets associated public key content. -func (k *DeployKey) GetContent() error { - pkey, err := GetPublicKeyByID(k.KeyID) - if err != nil { - return err - } - k.Content = pkey.Content - return nil -} - -func checkDeployKey(e Engine, keyID, repoID int64, name string) error { - // Note: We want error detail, not just true or false here. - has, err := e.Where("key_id = ? AND repo_id = ?", keyID, repoID).Get(new(DeployKey)) - if err != nil { - return err - } else if has { - return ErrDeployKeyAlreadyExist{keyID, repoID} - } - - has, err = e.Where("repo_id = ? AND name = ?", repoID, name).Get(new(DeployKey)) - if err != nil { - return err - } else if has { - return ErrDeployKeyNameAlreadyUsed{repoID, name} - } - - return nil -} - -// addDeployKey adds new key-repo relation. -func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) { - if err := checkDeployKey(e, keyID, repoID, name); err != nil { - return nil, err - } - - key := &DeployKey{ - KeyID: keyID, - RepoID: repoID, - Name: name, - Fingerprint: fingerprint, - } - _, err := e.Insert(key) - return key, err -} - -// HasDeployKey returns true if public key is a deploy key of given repository. -func HasDeployKey(keyID, repoID int64) bool { - has, _ := x.Where("key_id = ? AND repo_id = ?", keyID, repoID).Get(new(DeployKey)) - return has -} - -// AddDeployKey add new deploy key to database and authorized_keys file. -func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) { - if err := checkKeyContent(content); err != nil { - return nil, err - } - - pkey := &PublicKey{ - Content: content, - Mode: ACCESS_MODE_READ, - Type: KEY_TYPE_DEPLOY, - } - has, err := x.Get(pkey) - if err != nil { - return nil, err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - // First time use this deploy key. - if !has { - if err = addKey(sess, pkey); err != nil { - return nil, fmt.Errorf("addKey: %v", err) - } - } - - key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint) - if err != nil { - return nil, fmt.Errorf("addDeployKey: %v", err) - } - - return key, sess.Commit() -} - -// GetDeployKeyByID returns deploy key by given ID. -func GetDeployKeyByID(id int64) (*DeployKey, error) { - key := new(DeployKey) - has, err := x.Id(id).Get(key) - if err != nil { - return nil, err - } else if !has { - return nil, ErrDeployKeyNotExist{id, 0, 0} - } - return key, nil -} - -// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID. -func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) { - key := &DeployKey{ - KeyID: keyID, - RepoID: repoID, - } - has, err := x.Get(key) - if err != nil { - return nil, err - } else if !has { - return nil, ErrDeployKeyNotExist{0, keyID, repoID} - } - return key, nil -} - -// UpdateDeployKey updates deploy key information. -func UpdateDeployKey(key *DeployKey) error { - _, err := x.Id(key.ID).AllCols().Update(key) - return err -} - -// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed. -func DeleteDeployKey(doer *User, id int64) error { - key, err := GetDeployKeyByID(id) - if err != nil { - if IsErrDeployKeyNotExist(err) { - return nil - } - return fmt.Errorf("GetDeployKeyByID: %v", err) - } - - // Check if user has access to delete this key. - if !doer.IsAdmin { - repo, err := GetRepositoryByID(key.RepoID) - if err != nil { - return fmt.Errorf("GetRepositoryByID: %v", err) - } - yes, err := HasAccess(doer.ID, repo, ACCESS_MODE_ADMIN) - if err != nil { - return fmt.Errorf("HasAccess: %v", err) - } else if !yes { - return ErrKeyAccessDenied{doer.ID, key.ID, "deploy"} - } - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.ID(key.ID).Delete(new(DeployKey)); err != nil { - return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err) - } - - // Check if this is the last reference to same key content. - has, err := sess.Where("key_id = ?", key.KeyID).Get(new(DeployKey)) - if err != nil { - return err - } else if !has { - if err = deletePublicKeys(sess, key.KeyID); err != nil { - return err - } - } - - return sess.Commit() -} - -// ListDeployKeys returns all deploy keys by given repository ID. -func ListDeployKeys(repoID int64) ([]*DeployKey, error) { - keys := make([]*DeployKey, 0, 5) - return keys, x.Where("repo_id = ?", repoID).Find(&keys) -} diff --git a/models/ssh_key_test.go b/models/ssh_key_test.go deleted file mode 100644 index 407d83e2..00000000 --- a/models/ssh_key_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "strings" - "testing" - - . "github.com/smartystreets/goconvey/convey" - - "gogs.io/gogs/pkg/setting" -) - -func init() { - setting.NewContext() -} - -func Test_SSHParsePublicKey(t *testing.T) { - testKeys := map[string]struct { - typeName string - length int - content string - }{ - "dsa-1024": {"dsa", 1024, "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment"}, - "rsa-1024": {"rsa", 1024, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"}, - "rsa-2048": {"rsa", 2048, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"}, - "ecdsa-256": {"ecdsa", 256, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"}, - "ecdsa-384": {"ecdsa", 384, "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"}, - // "ecdsa-521": {"ecdsa", 521, "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACGt3UG3EzRwNOI17QR84l6PgiAcvCE7v6aXPj/SC6UWKg4EL8vW9ZBcdYL9wzs4FZXh4MOV8jAzu3KRWNTwb4k2wFNUpGOt7l28MztFFEtH5BDDrtAJSPENPy8pvPLMfnPg5NhvWycqIBzNcHipem5wSJFN5PdpNOC2xMrPWKNqj+ZjQ== nocomment"}, - } - - Convey("Parse public keys in both native and ssh-keygen", t, func() { - for name, key := range testKeys { - fmt.Println("\nTesting key:", name) - - keyTypeN, lengthN, errN := SSHNativeParsePublicKey(key.content) - So(errN, ShouldBeNil) - So(keyTypeN, ShouldEqual, key.typeName) - So(lengthN, ShouldEqual, key.length) - - keyTypeK, lengthK, errK := SSHKeyGenParsePublicKey(key.content) - if errK != nil { - // Some server just does not support ecdsa format. - if strings.Contains(errK.Error(), "line 1 too long:") { - continue - } - So(errK, ShouldBeNil) - } - So(keyTypeK, ShouldEqual, key.typeName) - So(lengthK, ShouldEqual, key.length) - } - }) -} diff --git a/models/token.go b/models/token.go deleted file mode 100644 index 93f90ed3..00000000 --- a/models/token.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "time" - - "xorm.io/xorm" - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/tool" - gouuid "github.com/satori/go.uuid" -) - -// AccessToken represents a personal access token. -type AccessToken struct { - ID int64 - UID int64 `xorm:"INDEX"` - Name string - Sha1 string `xorm:"UNIQUE VARCHAR(40)"` - - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-"` // Note: Updated must below Created for AfterSet. - UpdatedUnix int64 - HasRecentActivity bool `xorm:"-" json:"-"` - HasUsed bool `xorm:"-" json:"-"` -} - -func (t *AccessToken) BeforeInsert() { - t.CreatedUnix = time.Now().Unix() -} - -func (t *AccessToken) BeforeUpdate() { - t.UpdatedUnix = time.Now().Unix() -} - -func (t *AccessToken) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - t.Created = time.Unix(t.CreatedUnix, 0).Local() - case "updated_unix": - t.Updated = time.Unix(t.UpdatedUnix, 0).Local() - t.HasUsed = t.Updated.After(t.Created) - t.HasRecentActivity = t.Updated.Add(7 * 24 * time.Hour).After(time.Now()) - } -} - -// NewAccessToken creates new access token. -func NewAccessToken(t *AccessToken) error { - t.Sha1 = tool.SHA1(gouuid.NewV4().String()) - has, err := x.Get(&AccessToken{ - UID: t.UID, - Name: t.Name, - }) - if err != nil { - return err - } else if has { - return errors.AccessTokenNameAlreadyExist{t.Name} - } - - _, err = x.Insert(t) - return err -} - -// GetAccessTokenBySHA returns access token by given sha1. -func GetAccessTokenBySHA(sha string) (*AccessToken, error) { - if sha == "" { - return nil, ErrAccessTokenEmpty{} - } - t := &AccessToken{Sha1: sha} - has, err := x.Get(t) - if err != nil { - return nil, err - } else if !has { - return nil, ErrAccessTokenNotExist{sha} - } - return t, nil -} - -// ListAccessTokens returns a list of access tokens belongs to given user. -func ListAccessTokens(uid int64) ([]*AccessToken, error) { - tokens := make([]*AccessToken, 0, 5) - return tokens, x.Where("uid=?", uid).Desc("id").Find(&tokens) -} - -// UpdateAccessToken updates information of access token. -func UpdateAccessToken(t *AccessToken) error { - _, err := x.Id(t.ID).AllCols().Update(t) - return err -} - -// DeleteAccessTokenOfUserByID deletes access token by given ID. -func DeleteAccessTokenOfUserByID(userID, id int64) error { - _, err := x.Delete(&AccessToken{ - ID: id, - UID: userID, - }) - return err -} diff --git a/models/two_factor.go b/models/two_factor.go deleted file mode 100644 index 35e9e87e..00000000 --- a/models/two_factor.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "encoding/base64" - "fmt" - "strings" - "time" - - "github.com/unknwon/com" - "xorm.io/xorm" - "github.com/pquerna/otp/totp" - log "gopkg.in/clog.v1" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/tool" -) - -// TwoFactor represents a two-factor authentication token. -type TwoFactor struct { - ID int64 - UserID int64 `xorm:"UNIQUE"` - Secret string - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 -} - -func (t *TwoFactor) BeforeInsert() { - t.CreatedUnix = time.Now().Unix() -} - -func (t *TwoFactor) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - t.Created = time.Unix(t.CreatedUnix, 0).Local() - } -} - -// ValidateTOTP returns true if given passcode is valid for two-factor authentication token. -// It also returns possible validation error. -func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) { - secret, err := base64.StdEncoding.DecodeString(t.Secret) - if err != nil { - return false, fmt.Errorf("DecodeString: %v", err) - } - decryptSecret, err := com.AESGCMDecrypt(tool.MD5Bytes(setting.SecretKey), secret) - if err != nil { - return false, fmt.Errorf("AESGCMDecrypt: %v", err) - } - return totp.Validate(passcode, string(decryptSecret)), nil -} - -// IsUserEnabledTwoFactor returns true if user has enabled two-factor authentication. -func IsUserEnabledTwoFactor(userID int64) bool { - has, err := x.Where("user_id = ?", userID).Get(new(TwoFactor)) - if err != nil { - log.Error(2, "IsUserEnabledTwoFactor [user_id: %d]: %v", userID, err) - } - return has -} - -func generateRecoveryCodes(userID int64) ([]*TwoFactorRecoveryCode, error) { - recoveryCodes := make([]*TwoFactorRecoveryCode, 10) - for i := 0; i < 10; i++ { - code, err := tool.RandomString(10) - if err != nil { - return nil, fmt.Errorf("RandomString: %v", err) - } - recoveryCodes[i] = &TwoFactorRecoveryCode{ - UserID: userID, - Code: strings.ToLower(code[:5] + "-" + code[5:]), - } - } - return recoveryCodes, nil -} - -// NewTwoFactor creates a new two-factor authentication token and recovery codes for given user. -func NewTwoFactor(userID int64, secret string) error { - t := &TwoFactor{ - UserID: userID, - } - - // Encrypt secret - encryptSecret, err := com.AESGCMEncrypt(tool.MD5Bytes(setting.SecretKey), []byte(secret)) - if err != nil { - return fmt.Errorf("AESGCMEncrypt: %v", err) - } - t.Secret = base64.StdEncoding.EncodeToString(encryptSecret) - - recoveryCodes, err := generateRecoveryCodes(userID) - if err != nil { - return fmt.Errorf("generateRecoveryCodes: %v", err) - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Insert(t); err != nil { - return fmt.Errorf("insert two-factor: %v", err) - } else if _, err = sess.Insert(recoveryCodes); err != nil { - return fmt.Errorf("insert recovery codes: %v", err) - } - - return sess.Commit() -} - -// GetTwoFactorByUserID returns two-factor authentication token of given user. -func GetTwoFactorByUserID(userID int64) (*TwoFactor, error) { - t := new(TwoFactor) - has, err := x.Where("user_id = ?", userID).Get(t) - if err != nil { - return nil, err - } else if !has { - return nil, errors.TwoFactorNotFound{userID} - } - - return t, nil -} - -// DeleteTwoFactor removes two-factor authentication token and recovery codes of given user. -func DeleteTwoFactor(userID int64) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Where("user_id = ?", userID).Delete(new(TwoFactor)); err != nil { - return fmt.Errorf("delete two-factor: %v", err) - } else if err = deleteRecoveryCodesByUserID(sess, userID); err != nil { - return fmt.Errorf("deleteRecoveryCodesByUserID: %v", err) - } - - return sess.Commit() -} - -// TwoFactorRecoveryCode represents a two-factor authentication recovery code. -type TwoFactorRecoveryCode struct { - ID int64 - UserID int64 - Code string `xorm:"VARCHAR(11)"` - IsUsed bool -} - -// GetRecoveryCodesByUserID returns all recovery codes of given user. -func GetRecoveryCodesByUserID(userID int64) ([]*TwoFactorRecoveryCode, error) { - recoveryCodes := make([]*TwoFactorRecoveryCode, 0, 10) - return recoveryCodes, x.Where("user_id = ?", userID).Find(&recoveryCodes) -} - -func deleteRecoveryCodesByUserID(e Engine, userID int64) error { - _, err := e.Where("user_id = ?", userID).Delete(new(TwoFactorRecoveryCode)) - return err -} - -// RegenerateRecoveryCodes regenerates new set of recovery codes for given user. -func RegenerateRecoveryCodes(userID int64) error { - recoveryCodes, err := generateRecoveryCodes(userID) - if err != nil { - return fmt.Errorf("generateRecoveryCodes: %v", err) - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = deleteRecoveryCodesByUserID(sess, userID); err != nil { - return fmt.Errorf("deleteRecoveryCodesByUserID: %v", err) - } else if _, err = sess.Insert(recoveryCodes); err != nil { - return fmt.Errorf("insert new recovery codes: %v", err) - } - - return sess.Commit() -} - -// UseRecoveryCode validates recovery code of given user and marks it is used if valid. -func UseRecoveryCode(userID int64, code string) error { - recoveryCode := new(TwoFactorRecoveryCode) - has, err := x.Where("code = ?", code).And("is_used = ?", false).Get(recoveryCode) - if err != nil { - return fmt.Errorf("get unused code: %v", err) - } else if !has { - return errors.TwoFactorRecoveryCodeNotFound{code} - } - - recoveryCode.IsUsed = true - if _, err = x.Id(recoveryCode.ID).Cols("is_used").Update(recoveryCode); err != nil { - return fmt.Errorf("mark code as used: %v", err) - } - - return nil -} diff --git a/models/update.go b/models/update.go deleted file mode 100644 index db01392c..00000000 --- a/models/update.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "container/list" - "fmt" - "os/exec" - "strings" - - git "github.com/gogs/git-module" -) - -// CommitToPushCommit transforms a git.Commit to PushCommit type. -func CommitToPushCommit(commit *git.Commit) *PushCommit { - return &PushCommit{ - Sha1: commit.ID.String(), - Message: commit.Message(), - AuthorEmail: commit.Author.Email, - AuthorName: commit.Author.Name, - CommitterEmail: commit.Committer.Email, - CommitterName: commit.Committer.Name, - Timestamp: commit.Committer.When, - } -} - -func ListToPushCommits(l *list.List) *PushCommits { - if l == nil { - return &PushCommits{} - } - - commits := make([]*PushCommit, 0) - var actEmail string - for e := l.Front(); e != nil; e = e.Next() { - commit := e.Value.(*git.Commit) - if actEmail == "" { - actEmail = commit.Committer.Email - } - commits = append(commits, CommitToPushCommit(commit)) - } - return &PushCommits{l.Len(), commits, "", nil} -} - -type PushUpdateOptions struct { - OldCommitID string - NewCommitID string - RefFullName string - PusherID int64 - PusherName string - RepoUserName string - RepoName string -} - -// PushUpdate must be called for any push actions in order to -// generates necessary push action history feeds. -func PushUpdate(opts PushUpdateOptions) (err error) { - isNewRef := opts.OldCommitID == git.EMPTY_SHA - isDelRef := opts.NewCommitID == git.EMPTY_SHA - if isNewRef && isDelRef { - return fmt.Errorf("Old and new revisions are both %s", git.EMPTY_SHA) - } - - repoPath := RepoPath(opts.RepoUserName, opts.RepoName) - - gitUpdate := exec.Command("git", "update-server-info") - gitUpdate.Dir = repoPath - if err = gitUpdate.Run(); err != nil { - return fmt.Errorf("Fail to call 'git update-server-info': %v", err) - } - - gitRepo, err := git.OpenRepository(repoPath) - if err != nil { - return fmt.Errorf("OpenRepository: %v", err) - } - - owner, err := GetUserByName(opts.RepoUserName) - if err != nil { - return fmt.Errorf("GetUserByName: %v", err) - } - - repo, err := GetRepositoryByName(owner.ID, opts.RepoName) - if err != nil { - return fmt.Errorf("GetRepositoryByName: %v", err) - } - - if err = repo.UpdateSize(); err != nil { - return fmt.Errorf("UpdateSize: %v", err) - } - - // Push tags - if strings.HasPrefix(opts.RefFullName, git.TAG_PREFIX) { - if err := CommitRepoAction(CommitRepoActionOptions{ - PusherName: opts.PusherName, - RepoOwnerID: owner.ID, - RepoName: repo.Name, - RefFullName: opts.RefFullName, - OldCommitID: opts.OldCommitID, - NewCommitID: opts.NewCommitID, - Commits: &PushCommits{}, - }); err != nil { - return fmt.Errorf("CommitRepoAction.(tag): %v", err) - } - return nil - } - - var l *list.List - // Skip read parent commits when delete branch - if !isDelRef { - // Push new branch - newCommit, err := gitRepo.GetCommit(opts.NewCommitID) - if err != nil { - return fmt.Errorf("GetCommit [commit_id: %s]: %v", opts.NewCommitID, err) - } - - if isNewRef { - l, err = newCommit.CommitsBeforeLimit(10) - if err != nil { - return fmt.Errorf("CommitsBeforeLimit [commit_id: %s]: %v", newCommit.ID, err) - } - } else { - l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID) - if err != nil { - return fmt.Errorf("CommitsBeforeUntil [commit_id: %s]: %v", opts.OldCommitID, err) - } - } - } - - if err := CommitRepoAction(CommitRepoActionOptions{ - PusherName: opts.PusherName, - RepoOwnerID: owner.ID, - RepoName: repo.Name, - RefFullName: opts.RefFullName, - OldCommitID: opts.OldCommitID, - NewCommitID: opts.NewCommitID, - Commits: ListToPushCommits(l), - }); err != nil { - return fmt.Errorf("CommitRepoAction.(branch): %v", err) - } - return nil -} diff --git a/models/user.go b/models/user.go deleted file mode 100644 index 26f7bc0c..00000000 --- a/models/user.go +++ /dev/null @@ -1,1146 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "bytes" - "container/list" - "crypto/sha256" - "crypto/subtle" - "encoding/hex" - "fmt" - "image" - _ "image/jpeg" - "image/png" - "os" - "path/filepath" - "strings" - "time" - "unicode/utf8" - - "github.com/unknwon/com" - "xorm.io/xorm" - "github.com/nfnt/resize" - "golang.org/x/crypto/pbkdf2" - log "gopkg.in/clog.v1" - - "github.com/gogs/git-module" - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/avatar" - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/tool" -) - -// USER_AVATAR_URL_PREFIX is used to identify a URL is to access user avatar. -const USER_AVATAR_URL_PREFIX = "avatars" - -type UserType int - -const ( - USER_TYPE_INDIVIDUAL UserType = iota // Historic reason to make it starts at 0. - USER_TYPE_ORGANIZATION -) - -// User represents the object of individual and member of organization. -type User struct { - ID int64 - LowerName string `xorm:"UNIQUE NOT NULL"` - Name string `xorm:"UNIQUE NOT NULL"` - FullName string - // Email is the primary email address (to be used for communication) - Email string `xorm:"NOT NULL"` - Passwd string `xorm:"NOT NULL"` - LoginType LoginType - LoginSource int64 `xorm:"NOT NULL DEFAULT 0"` - LoginName string - Type UserType - OwnedOrgs []*User `xorm:"-" json:"-"` - Orgs []*User `xorm:"-" json:"-"` - Repos []*Repository `xorm:"-" json:"-"` - Location string - Website string - Rands string `xorm:"VARCHAR(10)"` - Salt string `xorm:"VARCHAR(10)"` - - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-"` - UpdatedUnix int64 - - // Remember visibility choice for convenience, true for private - LastRepoVisibility bool - // Maximum repository creation limit, -1 means use gloabl default - MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1"` - - // Permissions - IsActive bool // Activate primary email - IsAdmin bool - AllowGitHook bool - AllowImportLocal bool // Allow migrate repository by local path - ProhibitLogin bool - - // Avatar - Avatar string `xorm:"VARCHAR(2048) NOT NULL"` - AvatarEmail string `xorm:"NOT NULL"` - UseCustomAvatar bool - - // Counters - NumFollowers int - NumFollowing int `xorm:"NOT NULL DEFAULT 0"` - NumStars int - NumRepos int - - // For organization - Description string - NumTeams int - NumMembers int - Teams []*Team `xorm:"-" json:"-"` - Members []*User `xorm:"-" json:"-"` -} - -func (u *User) BeforeInsert() { - u.CreatedUnix = time.Now().Unix() - u.UpdatedUnix = u.CreatedUnix -} - -func (u *User) BeforeUpdate() { - if u.MaxRepoCreation < -1 { - u.MaxRepoCreation = -1 - } - u.UpdatedUnix = time.Now().Unix() -} - -func (u *User) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - u.Created = time.Unix(u.CreatedUnix, 0).Local() - case "updated_unix": - u.Updated = time.Unix(u.UpdatedUnix, 0).Local() - } -} - -// IDStr returns string representation of user's ID. -func (u *User) IDStr() string { - return com.ToStr(u.ID) -} - -func (u *User) APIFormat() *api.User { - return &api.User{ - ID: u.ID, - UserName: u.Name, - Login: u.Name, - FullName: u.FullName, - Email: u.Email, - AvatarUrl: u.AvatarLink(), - } -} - -// returns true if user login type is LOGIN_PLAIN. -func (u *User) IsLocal() bool { - return u.LoginType <= LOGIN_PLAIN -} - -// HasForkedRepo checks if user has already forked a repository with given ID. -func (u *User) HasForkedRepo(repoID int64) bool { - _, has, _ := HasForkedRepo(u.ID, repoID) - return has -} - -func (u *User) RepoCreationNum() int { - if u.MaxRepoCreation <= -1 { - return setting.Repository.MaxCreationLimit - } - return u.MaxRepoCreation -} - -func (u *User) CanCreateRepo() bool { - if u.MaxRepoCreation <= -1 { - if setting.Repository.MaxCreationLimit <= -1 { - return true - } - return u.NumRepos < setting.Repository.MaxCreationLimit - } - return u.NumRepos < u.MaxRepoCreation -} - -func (u *User) CanCreateOrganization() bool { - return !setting.Admin.DisableRegularOrgCreation || u.IsAdmin -} - -// CanEditGitHook returns true if user can edit Git hooks. -func (u *User) CanEditGitHook() bool { - return u.IsAdmin || u.AllowGitHook -} - -// CanImportLocal returns true if user can migrate repository by local path. -func (u *User) CanImportLocal() bool { - return setting.Repository.EnableLocalPathMigration && (u.IsAdmin || u.AllowImportLocal) -} - -// DashboardLink returns the user dashboard page link. -func (u *User) DashboardLink() string { - if u.IsOrganization() { - return setting.AppSubURL + "/org/" + u.Name + "/dashboard/" - } - return setting.AppSubURL + "/" -} - -// HomeLink returns the user or organization home page link. -func (u *User) HomeLink() string { - return setting.AppSubURL + "/" + u.Name -} - -func (u *User) HTMLURL() string { - return setting.AppURL + u.Name -} - -// GenerateEmailActivateCode generates an activate code based on user information and given e-mail. -func (u *User) GenerateEmailActivateCode(email string) string { - code := tool.CreateTimeLimitCode( - com.ToStr(u.ID)+email+u.LowerName+u.Passwd+u.Rands, - setting.Service.ActiveCodeLives, nil) - - // Add tail hex username - code += hex.EncodeToString([]byte(u.LowerName)) - return code -} - -// GenerateActivateCode generates an activate code based on user information. -func (u *User) GenerateActivateCode() string { - return u.GenerateEmailActivateCode(u.Email) -} - -// CustomAvatarPath returns user custom avatar file path. -func (u *User) CustomAvatarPath() string { - return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.ID)) -} - -// GenerateRandomAvatar generates a random avatar for user. -func (u *User) GenerateRandomAvatar() error { - seed := u.Email - if len(seed) == 0 { - seed = u.Name - } - - img, err := avatar.RandomImage([]byte(seed)) - if err != nil { - return fmt.Errorf("RandomImage: %v", err) - } - if err = os.MkdirAll(filepath.Dir(u.CustomAvatarPath()), os.ModePerm); err != nil { - return fmt.Errorf("MkdirAll: %v", err) - } - fw, err := os.Create(u.CustomAvatarPath()) - if err != nil { - return fmt.Errorf("Create: %v", err) - } - defer fw.Close() - - if err = png.Encode(fw, img); err != nil { - return fmt.Errorf("Encode: %v", err) - } - - log.Info("New random avatar created: %d", u.ID) - return nil -} - -// RelAvatarLink returns relative avatar link to the site domain, -// which includes app sub-url as prefix. However, it is possible -// to return full URL if user enables Gravatar-like service. -func (u *User) RelAvatarLink() string { - defaultImgUrl := setting.AppSubURL + "/img/avatar_default.png" - if u.ID == -1 { - return defaultImgUrl - } - - switch { - case u.UseCustomAvatar: - if !com.IsExist(u.CustomAvatarPath()) { - return defaultImgUrl - } - return fmt.Sprintf("%s/%s/%d", setting.AppSubURL, USER_AVATAR_URL_PREFIX, u.ID) - case setting.DisableGravatar, setting.OfflineMode: - if !com.IsExist(u.CustomAvatarPath()) { - if err := u.GenerateRandomAvatar(); err != nil { - log.Error(3, "GenerateRandomAvatar: %v", err) - } - } - - return fmt.Sprintf("%s/%s/%d", setting.AppSubURL, USER_AVATAR_URL_PREFIX, u.ID) - } - return tool.AvatarLink(u.AvatarEmail) -} - -// AvatarLink returns user avatar absolute link. -func (u *User) AvatarLink() string { - link := u.RelAvatarLink() - if link[0] == '/' && link[1] != '/' { - return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:] - } - return link -} - -// User.GetFollwoers returns range of user's followers. -func (u *User) GetFollowers(page int) ([]*User, error) { - users := make([]*User, 0, ItemsPerPage) - sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.follow_id=?", u.ID) - if setting.UsePostgreSQL { - sess = sess.Join("LEFT", "follow", `"user".id=follow.user_id`) - } else { - sess = sess.Join("LEFT", "follow", "user.id=follow.user_id") - } - return users, sess.Find(&users) -} - -func (u *User) IsFollowing(followID int64) bool { - return IsFollowing(u.ID, followID) -} - -// GetFollowing returns range of user's following. -func (u *User) GetFollowing(page int) ([]*User, error) { - users := make([]*User, 0, ItemsPerPage) - sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.user_id=?", u.ID) - if setting.UsePostgreSQL { - sess = sess.Join("LEFT", "follow", `"user".id=follow.follow_id`) - } else { - sess = sess.Join("LEFT", "follow", "user.id=follow.follow_id") - } - return users, sess.Find(&users) -} - -// NewGitSig generates and returns the signature of given user. -func (u *User) NewGitSig() *git.Signature { - return &git.Signature{ - Name: u.DisplayName(), - Email: u.Email, - When: time.Now(), - } -} - -// EncodePasswd encodes password to safe format. -func (u *User) EncodePasswd() { - newPasswd := pbkdf2.Key([]byte(u.Passwd), []byte(u.Salt), 10000, 50, sha256.New) - u.Passwd = fmt.Sprintf("%x", newPasswd) -} - -// ValidatePassword checks if given password matches the one belongs to the user. -func (u *User) ValidatePassword(passwd string) bool { - newUser := &User{Passwd: passwd, Salt: u.Salt} - newUser.EncodePasswd() - return subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(newUser.Passwd)) == 1 -} - -// UploadAvatar saves custom avatar for user. -// FIXME: split uploads to different subdirs in case we have massive number of users. -func (u *User) UploadAvatar(data []byte) error { - img, _, err := image.Decode(bytes.NewReader(data)) - if err != nil { - return fmt.Errorf("decode image: %v", err) - } - - os.MkdirAll(setting.AvatarUploadPath, os.ModePerm) - fw, err := os.Create(u.CustomAvatarPath()) - if err != nil { - return fmt.Errorf("create custom avatar directory: %v", err) - } - defer fw.Close() - - m := resize.Resize(avatar.AVATAR_SIZE, avatar.AVATAR_SIZE, img, resize.NearestNeighbor) - if err = png.Encode(fw, m); err != nil { - return fmt.Errorf("encode image: %v", err) - } - - return nil -} - -// DeleteAvatar deletes the user's custom avatar. -func (u *User) DeleteAvatar() error { - log.Trace("DeleteAvatar [%d]: %s", u.ID, u.CustomAvatarPath()) - if err := os.Remove(u.CustomAvatarPath()); err != nil { - return err - } - - u.UseCustomAvatar = false - return UpdateUser(u) -} - -// IsAdminOfRepo returns true if user has admin or higher access of repository. -func (u *User) IsAdminOfRepo(repo *Repository) bool { - has, err := HasAccess(u.ID, repo, ACCESS_MODE_ADMIN) - if err != nil { - log.Error(2, "HasAccess: %v", err) - } - return has -} - -// IsWriterOfRepo returns true if user has write access to given repository. -func (u *User) IsWriterOfRepo(repo *Repository) bool { - has, err := HasAccess(u.ID, repo, ACCESS_MODE_WRITE) - if err != nil { - log.Error(2, "HasAccess: %v", err) - } - return has -} - -// IsOrganization returns true if user is actually a organization. -func (u *User) IsOrganization() bool { - return u.Type == USER_TYPE_ORGANIZATION -} - -// IsUserOrgOwner returns true if user is in the owner team of given organization. -func (u *User) IsUserOrgOwner(orgId int64) bool { - return IsOrganizationOwner(orgId, u.ID) -} - -// IsPublicMember returns true if user public his/her membership in give organization. -func (u *User) IsPublicMember(orgId int64) bool { - return IsPublicMembership(orgId, u.ID) -} - -// IsEnabledTwoFactor returns true if user has enabled two-factor authentication. -func (u *User) IsEnabledTwoFactor() bool { - return IsUserEnabledTwoFactor(u.ID) -} - -func (u *User) getOrganizationCount(e Engine) (int64, error) { - return e.Where("uid=?", u.ID).Count(new(OrgUser)) -} - -// GetOrganizationCount returns count of membership of organization of user. -func (u *User) GetOrganizationCount() (int64, error) { - return u.getOrganizationCount(x) -} - -// GetRepositories returns repositories that user owns, including private repositories. -func (u *User) GetRepositories(page, pageSize int) (err error) { - u.Repos, err = GetUserRepositories(&UserRepoOptions{ - UserID: u.ID, - Private: true, - Page: page, - PageSize: pageSize, - }) - return err -} - -// GetRepositories returns mirror repositories that user owns, including private repositories. -func (u *User) GetMirrorRepositories() ([]*Repository, error) { - return GetUserMirrorRepositories(u.ID) -} - -// GetOwnedOrganizations returns all organizations that user owns. -func (u *User) GetOwnedOrganizations() (err error) { - u.OwnedOrgs, err = GetOwnedOrgsByUserID(u.ID) - return err -} - -// GetOrganizations returns all organizations that user belongs to. -func (u *User) GetOrganizations(showPrivate bool) error { - orgIDs, err := GetOrgIDsByUserID(u.ID, showPrivate) - if err != nil { - return fmt.Errorf("GetOrgIDsByUserID: %v", err) - } - if len(orgIDs) == 0 { - return nil - } - - u.Orgs = make([]*User, 0, len(orgIDs)) - if err = x.Where("type = ?", USER_TYPE_ORGANIZATION).In("id", orgIDs).Find(&u.Orgs); err != nil { - return err - } - return nil -} - -// DisplayName returns full name if it's not empty, -// returns username otherwise. -func (u *User) DisplayName() string { - if len(u.FullName) > 0 { - return u.FullName - } - return u.Name -} - -func (u *User) ShortName(length int) string { - return tool.EllipsisString(u.Name, length) -} - -// IsMailable checks if a user is elegible -// to receive emails. -func (u *User) IsMailable() bool { - return u.IsActive -} - -// IsUserExist checks if given user name exist, -// the user name should be noncased unique. -// If uid is presented, then check will rule out that one, -// it is used when update a user name in settings page. -func IsUserExist(uid int64, name string) (bool, error) { - if len(name) == 0 { - return false, nil - } - return x.Where("id != ?", uid).Get(&User{LowerName: strings.ToLower(name)}) -} - -// GetUserSalt returns a ramdom user salt token. -func GetUserSalt() (string, error) { - return tool.RandomString(10) -} - -// NewGhostUser creates and returns a fake user for someone who has deleted his/her account. -func NewGhostUser() *User { - return &User{ - ID: -1, - Name: "Ghost", - LowerName: "ghost", - } -} - -var ( - reservedUsernames = []string{"explore", "create", "assets", "css", "img", "js", "less", "plugins", "debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new", ".", ".."} - reservedUserPatterns = []string{"*.keys"} -) - -// isUsableName checks if name is reserved or pattern of name is not allowed -// based on given reserved names and patterns. -// Names are exact match, patterns can be prefix or suffix match with placeholder '*'. -func isUsableName(names, patterns []string, name string) error { - name = strings.TrimSpace(strings.ToLower(name)) - if utf8.RuneCountInString(name) == 0 { - return errors.EmptyName{} - } - - for i := range names { - if name == names[i] { - return ErrNameReserved{name} - } - } - - for _, pat := range patterns { - if pat[0] == '*' && strings.HasSuffix(name, pat[1:]) || - (pat[len(pat)-1] == '*' && strings.HasPrefix(name, pat[:len(pat)-1])) { - return ErrNamePatternNotAllowed{pat} - } - } - - return nil -} - -func IsUsableUsername(name string) error { - return isUsableName(reservedUsernames, reservedUserPatterns, name) -} - -// CreateUser creates record of a new user. -func CreateUser(u *User) (err error) { - if err = IsUsableUsername(u.Name); err != nil { - return err - } - - isExist, err := IsUserExist(0, u.Name) - if err != nil { - return err - } else if isExist { - return ErrUserAlreadyExist{u.Name} - } - - u.Email = strings.ToLower(u.Email) - isExist, err = IsEmailUsed(u.Email) - if err != nil { - return err - } else if isExist { - return ErrEmailAlreadyUsed{u.Email} - } - - u.LowerName = strings.ToLower(u.Name) - u.AvatarEmail = u.Email - u.Avatar = tool.HashEmail(u.AvatarEmail) - if u.Rands, err = GetUserSalt(); err != nil { - return err - } - if u.Salt, err = GetUserSalt(); err != nil { - return err - } - u.EncodePasswd() - u.MaxRepoCreation = -1 - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Insert(u); err != nil { - return err - } else if err = os.MkdirAll(UserPath(u.Name), os.ModePerm); err != nil { - return err - } - - return sess.Commit() -} - -func countUsers(e Engine) int64 { - count, _ := e.Where("type=0").Count(new(User)) - return count -} - -// CountUsers returns number of users. -func CountUsers() int64 { - return countUsers(x) -} - -// Users returns number of users in given page. -func Users(page, pageSize int) ([]*User, error) { - users := make([]*User, 0, pageSize) - return users, x.Limit(pageSize, (page-1)*pageSize).Where("type=0").Asc("id").Find(&users) -} - -// parseUserFromCode returns user by username encoded in code. -// It returns nil if code or username is invalid. -func parseUserFromCode(code string) (user *User) { - if len(code) <= tool.TIME_LIMIT_CODE_LENGTH { - return nil - } - - // Use tail hex username to query user - hexStr := code[tool.TIME_LIMIT_CODE_LENGTH:] - if b, err := hex.DecodeString(hexStr); err == nil { - if user, err = GetUserByName(string(b)); user != nil { - return user - } else if !errors.IsUserNotExist(err) { - log.Error(2, "GetUserByName: %v", err) - } - } - - return nil -} - -// verify active code when active account -func VerifyUserActiveCode(code string) (user *User) { - minutes := setting.Service.ActiveCodeLives - - if user = parseUserFromCode(code); user != nil { - // time limit code - prefix := code[:tool.TIME_LIMIT_CODE_LENGTH] - data := com.ToStr(user.ID) + user.Email + user.LowerName + user.Passwd + user.Rands - - if tool.VerifyTimeLimitCode(data, minutes, prefix) { - return user - } - } - return nil -} - -// verify active code when active account -func VerifyActiveEmailCode(code, email string) *EmailAddress { - minutes := setting.Service.ActiveCodeLives - - if user := parseUserFromCode(code); user != nil { - // time limit code - prefix := code[:tool.TIME_LIMIT_CODE_LENGTH] - data := com.ToStr(user.ID) + email + user.LowerName + user.Passwd + user.Rands - - if tool.VerifyTimeLimitCode(data, minutes, prefix) { - emailAddress := &EmailAddress{Email: email} - if has, _ := x.Get(emailAddress); has { - return emailAddress - } - } - } - return nil -} - -// ChangeUserName changes all corresponding setting from old user name to new one. -func ChangeUserName(u *User, newUserName string) (err error) { - if err = IsUsableUsername(newUserName); err != nil { - return err - } - - isExist, err := IsUserExist(0, newUserName) - if err != nil { - return err - } else if isExist { - return ErrUserAlreadyExist{newUserName} - } - - if err = ChangeUsernameInPullRequests(u.Name, newUserName); err != nil { - return fmt.Errorf("ChangeUsernameInPullRequests: %v", err) - } - - // Delete all local copies of repository wiki that user owns. - if err = x.Where("owner_id=?", u.ID).Iterate(new(Repository), func(idx int, bean interface{}) error { - repo := bean.(*Repository) - RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) - return nil - }); err != nil { - return fmt.Errorf("Delete repository wiki local copy: %v", err) - } - - // Rename or create user base directory - baseDir := UserPath(u.Name) - newBaseDir := UserPath(newUserName) - if com.IsExist(baseDir) { - return os.Rename(baseDir, newBaseDir) - } - return os.MkdirAll(newBaseDir, os.ModePerm) -} - -func updateUser(e Engine, u *User) error { - // Organization does not need email - if !u.IsOrganization() { - u.Email = strings.ToLower(u.Email) - has, err := e.Where("id!=?", u.ID).And("type=?", u.Type).And("email=?", u.Email).Get(new(User)) - if err != nil { - return err - } else if has { - return ErrEmailAlreadyUsed{u.Email} - } - - if len(u.AvatarEmail) == 0 { - u.AvatarEmail = u.Email - } - u.Avatar = tool.HashEmail(u.AvatarEmail) - } - - u.LowerName = strings.ToLower(u.Name) - u.Location = tool.TruncateString(u.Location, 255) - u.Website = tool.TruncateString(u.Website, 255) - u.Description = tool.TruncateString(u.Description, 255) - - _, err := e.ID(u.ID).AllCols().Update(u) - return err -} - -// UpdateUser updates user's information. -func UpdateUser(u *User) error { - return updateUser(x, u) -} - -// deleteBeans deletes all given beans, beans should contain delete conditions. -func deleteBeans(e Engine, beans ...interface{}) (err error) { - for i := range beans { - if _, err = e.Delete(beans[i]); err != nil { - return err - } - } - return nil -} - -// FIXME: need some kind of mechanism to record failure. HINT: system notice -func deleteUser(e *xorm.Session, u *User) error { - // Note: A user owns any repository or belongs to any organization - // cannot perform delete operation. - - // Check ownership of repository. - count, err := getRepositoryCount(e, u) - if err != nil { - return fmt.Errorf("GetRepositoryCount: %v", err) - } else if count > 0 { - return ErrUserOwnRepos{UID: u.ID} - } - - // Check membership of organization. - count, err = u.getOrganizationCount(e) - if err != nil { - return fmt.Errorf("GetOrganizationCount: %v", err) - } else if count > 0 { - return ErrUserHasOrgs{UID: u.ID} - } - - // ***** START: Watch ***** - watches := make([]*Watch, 0, 10) - if err = e.Find(&watches, &Watch{UserID: u.ID}); err != nil { - return fmt.Errorf("get all watches: %v", err) - } - for i := range watches { - if _, err = e.Exec("UPDATE `repository` SET num_watches=num_watches-1 WHERE id=?", watches[i].RepoID); err != nil { - return fmt.Errorf("decrease repository watch number[%d]: %v", watches[i].RepoID, err) - } - } - // ***** END: Watch ***** - - // ***** START: Star ***** - stars := make([]*Star, 0, 10) - if err = e.Find(&stars, &Star{UID: u.ID}); err != nil { - return fmt.Errorf("get all stars: %v", err) - } - for i := range stars { - if _, err = e.Exec("UPDATE `repository` SET num_stars=num_stars-1 WHERE id=?", stars[i].RepoID); err != nil { - return fmt.Errorf("decrease repository star number[%d]: %v", stars[i].RepoID, err) - } - } - // ***** END: Star ***** - - // ***** START: Follow ***** - followers := make([]*Follow, 0, 10) - if err = e.Find(&followers, &Follow{UserID: u.ID}); err != nil { - return fmt.Errorf("get all followers: %v", err) - } - for i := range followers { - if _, err = e.Exec("UPDATE `user` SET num_followers=num_followers-1 WHERE id=?", followers[i].UserID); err != nil { - return fmt.Errorf("decrease user follower number[%d]: %v", followers[i].UserID, err) - } - } - // ***** END: Follow ***** - - if err = deleteBeans(e, - &AccessToken{UID: u.ID}, - &Collaboration{UserID: u.ID}, - &Access{UserID: u.ID}, - &Watch{UserID: u.ID}, - &Star{UID: u.ID}, - &Follow{FollowID: u.ID}, - &Action{UserID: u.ID}, - &IssueUser{UID: u.ID}, - &EmailAddress{UID: u.ID}, - ); err != nil { - return fmt.Errorf("deleteBeans: %v", err) - } - - // ***** START: PublicKey ***** - keys := make([]*PublicKey, 0, 10) - if err = e.Find(&keys, &PublicKey{OwnerID: u.ID}); err != nil { - return fmt.Errorf("get all public keys: %v", err) - } - - keyIDs := make([]int64, len(keys)) - for i := range keys { - keyIDs[i] = keys[i].ID - } - if err = deletePublicKeys(e, keyIDs...); err != nil { - return fmt.Errorf("deletePublicKeys: %v", err) - } - // ***** END: PublicKey ***** - - // Clear assignee. - if _, err = e.Exec("UPDATE `issue` SET assignee_id=0 WHERE assignee_id=?", u.ID); err != nil { - return fmt.Errorf("clear assignee: %v", err) - } - - if _, err = e.Id(u.ID).Delete(new(User)); err != nil { - return fmt.Errorf("Delete: %v", err) - } - - // FIXME: system notice - // Note: There are something just cannot be roll back, - // so just keep error logs of those operations. - - os.RemoveAll(UserPath(u.Name)) - os.Remove(u.CustomAvatarPath()) - - return nil -} - -// DeleteUser completely and permanently deletes everything of a user, -// but issues/comments/pulls will be kept and shown as someone has been deleted. -func DeleteUser(u *User) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = deleteUser(sess, u); err != nil { - // Note: don't wrapper error here. - return err - } - - if err = sess.Commit(); err != nil { - return err - } - - return RewriteAuthorizedKeys() -} - -// DeleteInactivateUsers deletes all inactivate users and email addresses. -func DeleteInactivateUsers() (err error) { - users := make([]*User, 0, 10) - if err = x.Where("is_active = ?", false).Find(&users); err != nil { - return fmt.Errorf("get all inactive users: %v", err) - } - // FIXME: should only update authorized_keys file once after all deletions. - for _, u := range users { - if err = DeleteUser(u); err != nil { - // Ignore users that were set inactive by admin. - if IsErrUserOwnRepos(err) || IsErrUserHasOrgs(err) { - continue - } - return err - } - } - - _, err = x.Where("is_activated = ?", false).Delete(new(EmailAddress)) - return err -} - -// UserPath returns the path absolute path of user repositories. -func UserPath(userName string) string { - return filepath.Join(setting.RepoRootPath, strings.ToLower(userName)) -} - -func GetUserByKeyID(keyID int64) (*User, error) { - user := new(User) - has, err := x.SQL("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyID).Get(user) - if err != nil { - return nil, err - } else if !has { - return nil, errors.UserNotKeyOwner{keyID} - } - return user, nil -} - -func getUserByID(e Engine, id int64) (*User, error) { - u := new(User) - has, err := e.ID(id).Get(u) - if err != nil { - return nil, err - } else if !has { - return nil, errors.UserNotExist{id, ""} - } - return u, nil -} - -// 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(userID, repo, ACCESS_MODE_READ) - if err != nil { - return nil, err - } else if !has { - return nil, errors.UserNotExist{userID, ""} - } - return GetUserByID(userID) -} - -// GetUserByName returns a user by given name. -func GetUserByName(name string) (*User, error) { - if len(name) == 0 { - return nil, errors.UserNotExist{0, name} - } - u := &User{LowerName: strings.ToLower(name)} - has, err := x.Get(u) - if err != nil { - return nil, err - } else if !has { - return nil, errors.UserNotExist{0, name} - } - return u, nil -} - -// GetUserEmailsByNames returns a list of e-mails corresponds to names. -func GetUserEmailsByNames(names []string) []string { - mails := make([]string, 0, len(names)) - for _, name := range names { - u, err := GetUserByName(name) - if err != nil { - continue - } - if u.IsMailable() { - mails = append(mails, u.Email) - } - } - return mails -} - -// GetUserIDsByNames returns a slice of ids corresponds to names. -func GetUserIDsByNames(names []string) []int64 { - ids := make([]int64, 0, len(names)) - for _, name := range names { - u, err := GetUserByName(name) - if err != nil { - continue - } - ids = append(ids, u.ID) - } - return ids -} - -// UserCommit represents a commit with validation of user. -type UserCommit struct { - User *User - *git.Commit -} - -// ValidateCommitWithEmail chceck if author's e-mail of commit is corresponsind to a user. -func ValidateCommitWithEmail(c *git.Commit) *User { - u, err := GetUserByEmail(c.Author.Email) - if err != nil { - return nil - } - return u -} - -// ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users. -func ValidateCommitsWithEmails(oldCommits *list.List) *list.List { - var ( - u *User - emails = map[string]*User{} - newCommits = list.New() - e = oldCommits.Front() - ) - for e != nil { - c := e.Value.(*git.Commit) - - if v, ok := emails[c.Author.Email]; !ok { - u, _ = GetUserByEmail(c.Author.Email) - emails[c.Author.Email] = u - } else { - u = v - } - - newCommits.PushBack(UserCommit{ - User: u, - Commit: c, - }) - e = e.Next() - } - return newCommits -} - -// GetUserByEmail returns the user object by given e-mail if exists. -func GetUserByEmail(email string) (*User, error) { - if len(email) == 0 { - return nil, errors.UserNotExist{0, "email"} - } - - email = strings.ToLower(email) - // First try to find the user by primary email - user := &User{Email: email} - has, err := x.Get(user) - if err != nil { - return nil, err - } - if has { - return user, nil - } - - // Otherwise, check in alternative list for activated email addresses - emailAddress := &EmailAddress{Email: email, IsActivated: true} - has, err = x.Get(emailAddress) - if err != nil { - return nil, err - } - if has { - return GetUserByID(emailAddress.UID) - } - - return nil, errors.UserNotExist{0, email} -} - -type SearchUserOptions struct { - Keyword string - Type UserType - OrderBy string - Page int - PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum -} - -// SearchUserByName takes keyword and part of user name to search, -// it returns results in given range and number of total results. -func SearchUserByName(opts *SearchUserOptions) (users []*User, _ int64, _ error) { - if len(opts.Keyword) == 0 { - return users, 0, nil - } - opts.Keyword = strings.ToLower(opts.Keyword) - - if opts.PageSize <= 0 || opts.PageSize > setting.UI.ExplorePagingNum { - opts.PageSize = setting.UI.ExplorePagingNum - } - if opts.Page <= 0 { - opts.Page = 1 - } - - searchQuery := "%" + opts.Keyword + "%" - users = make([]*User, 0, opts.PageSize) - // Append conditions - sess := x.Where("LOWER(lower_name) LIKE ?", searchQuery). - Or("LOWER(full_name) LIKE ?", searchQuery). - And("type = ?", opts.Type) - - var countSess xorm.Session - countSess = *sess - count, err := countSess.Count(new(User)) - if err != nil { - return nil, 0, fmt.Errorf("Count: %v", err) - } - - if len(opts.OrderBy) > 0 { - sess.OrderBy(opts.OrderBy) - } - return users, count, sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&users) -} - -// ___________ .__ .__ -// \_ _____/___ | | | | ______ _ __ -// | __)/ _ \| | | | / _ \ \/ \/ / -// | \( <_> ) |_| |_( <_> ) / -// \___ / \____/|____/____/\____/ \/\_/ -// \/ - -// Follow represents relations of user and his/her followers. -type Follow struct { - ID int64 - UserID int64 `xorm:"UNIQUE(follow)"` - FollowID int64 `xorm:"UNIQUE(follow)"` -} - -func IsFollowing(userID, followID int64) bool { - has, _ := x.Get(&Follow{UserID: userID, FollowID: followID}) - return has -} - -// FollowUser marks someone be another's follower. -func FollowUser(userID, followID int64) (err error) { - if userID == followID || IsFollowing(userID, followID) { - return nil - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Insert(&Follow{UserID: userID, FollowID: followID}); err != nil { - return err - } - - if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil { - return err - } - - if _, err = sess.Exec("UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil { - return err - } - return sess.Commit() -} - -// UnfollowUser unmarks someone be another's follower. -func UnfollowUser(userID, followID int64) (err error) { - if userID == followID || !IsFollowing(userID, followID) { - return nil - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Delete(&Follow{UserID: userID, FollowID: followID}); err != nil { - return err - } - - if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil { - return err - } - - if _, err = sess.Exec("UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil { - return err - } - return sess.Commit() -} diff --git a/models/user_cache.go b/models/user_cache.go deleted file mode 100644 index 45c3721b..00000000 --- a/models/user_cache.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -// MailResendCacheKey returns key used for cache mail resend. -func (u *User) MailResendCacheKey() string { - return "MailResend_" + u.IDStr() -} - -// TwoFactorCacheKey returns key used for cache two factor passcode. -// e.g. TwoFactor_1_012664 -func (u *User) TwoFactorCacheKey(passcode string) string { - return "TwoFactor_" + u.IDStr() + "_" + passcode -} diff --git a/models/user_mail.go b/models/user_mail.go deleted file mode 100644 index d036790c..00000000 --- a/models/user_mail.go +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "strings" - - "gogs.io/gogs/models/errors" -) - -// EmailAdresses is the list of all email addresses of a user. Can contain the -// primary email address, but is not obligatory. -type EmailAddress struct { - ID int64 - UID int64 `xorm:"INDEX NOT NULL"` - Email string `xorm:"UNIQUE NOT NULL"` - IsActivated bool - IsPrimary bool `xorm:"-" json:"-"` -} - -// GetEmailAddresses returns all email addresses belongs to given user. -func GetEmailAddresses(uid int64) ([]*EmailAddress, error) { - emails := make([]*EmailAddress, 0, 5) - if err := x.Where("uid=?", uid).Find(&emails); err != nil { - return nil, err - } - - u, err := GetUserByID(uid) - if err != nil { - return nil, err - } - - isPrimaryFound := false - for _, email := range emails { - if email.Email == u.Email { - isPrimaryFound = true - email.IsPrimary = true - } else { - email.IsPrimary = false - } - } - - // We alway want the primary email address displayed, even if it's not in - // the emailaddress table (yet). - if !isPrimaryFound { - emails = append(emails, &EmailAddress{ - Email: u.Email, - IsActivated: true, - IsPrimary: true, - }) - } - return emails, nil -} - -func isEmailUsed(e Engine, email string) (bool, error) { - if len(email) == 0 { - return true, nil - } - - has, err := e.Get(&EmailAddress{Email: email}) - if err != nil { - return false, err - } else if has { - return true, nil - } - - // We need to check primary email of users as well. - return e.Where("type=?", USER_TYPE_INDIVIDUAL).And("email=?", email).Get(new(User)) -} - -// IsEmailUsed returns true if the email has been used. -func IsEmailUsed(email string) (bool, error) { - return isEmailUsed(x, email) -} - -func addEmailAddress(e Engine, email *EmailAddress) error { - email.Email = strings.ToLower(strings.TrimSpace(email.Email)) - used, err := isEmailUsed(e, email.Email) - if err != nil { - return err - } else if used { - return ErrEmailAlreadyUsed{email.Email} - } - - _, err = e.Insert(email) - return err -} - -func AddEmailAddress(email *EmailAddress) error { - return addEmailAddress(x, email) -} - -func AddEmailAddresses(emails []*EmailAddress) error { - if len(emails) == 0 { - return nil - } - - // Check if any of them has been used - for i := range emails { - emails[i].Email = strings.ToLower(strings.TrimSpace(emails[i].Email)) - used, err := IsEmailUsed(emails[i].Email) - if err != nil { - return err - } else if used { - return ErrEmailAlreadyUsed{emails[i].Email} - } - } - - if _, err := x.Insert(emails); err != nil { - return fmt.Errorf("Insert: %v", err) - } - - return nil -} - -func (email *EmailAddress) Activate() error { - user, err := GetUserByID(email.UID) - if err != nil { - return err - } - if user.Rands, err = GetUserSalt(); err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - email.IsActivated = true - if _, err := sess.ID(email.ID).AllCols().Update(email); err != nil { - return err - } else if err = updateUser(sess, user); err != nil { - return err - } - - return sess.Commit() -} - -func DeleteEmailAddress(email *EmailAddress) (err error) { - if email.ID > 0 { - _, err = x.Id(email.ID).Delete(new(EmailAddress)) - } else { - _, err = x.Where("email=?", email.Email).Delete(new(EmailAddress)) - } - return err -} - -func DeleteEmailAddresses(emails []*EmailAddress) (err error) { - for i := range emails { - if err = DeleteEmailAddress(emails[i]); err != nil { - return err - } - } - - return nil -} - -func MakeEmailPrimary(email *EmailAddress) error { - has, err := x.Get(email) - if err != nil { - return err - } else if !has { - return errors.EmailNotFound{email.Email} - } - - if !email.IsActivated { - return errors.EmailNotVerified{email.Email} - } - - user := &User{ID: email.UID} - has, err = x.Get(user) - if err != nil { - return err - } else if !has { - return errors.UserNotExist{email.UID, ""} - } - - // Make sure the former primary email doesn't disappear. - formerPrimaryEmail := &EmailAddress{Email: user.Email} - has, err = x.Get(formerPrimaryEmail) - if err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if !has { - formerPrimaryEmail.UID = user.ID - formerPrimaryEmail.IsActivated = user.IsActive - if _, err = sess.Insert(formerPrimaryEmail); err != nil { - return err - } - } - - user.Email = email.Email - if _, err = sess.ID(user.ID).AllCols().Update(user); err != nil { - return err - } - - return sess.Commit() -} diff --git a/models/webhook.go b/models/webhook.go deleted file mode 100644 index 6f39ab7c..00000000 --- a/models/webhook.go +++ /dev/null @@ -1,771 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "crypto/hmac" - "crypto/sha256" - "crypto/tls" - "encoding/hex" - "fmt" - "io/ioutil" - "strings" - "time" - - "xorm.io/xorm" - "github.com/json-iterator/go" - gouuid "github.com/satori/go.uuid" - log "gopkg.in/clog.v1" - - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/models/errors" - "gogs.io/gogs/pkg/httplib" - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/sync" -) - -var HookQueue = sync.NewUniqueQueue(setting.Webhook.QueueLength) - -type HookContentType int - -const ( - JSON HookContentType = iota + 1 - FORM -) - -var hookContentTypes = map[string]HookContentType{ - "json": JSON, - "form": FORM, -} - -// ToHookContentType returns HookContentType by given name. -func ToHookContentType(name string) HookContentType { - return hookContentTypes[name] -} - -func (t HookContentType) Name() string { - switch t { - case JSON: - return "json" - case FORM: - return "form" - } - return "" -} - -// IsValidHookContentType returns true if given name is a valid hook content type. -func IsValidHookContentType(name string) bool { - _, ok := hookContentTypes[name] - return ok -} - -type HookEvents struct { - Create bool `json:"create"` - Delete bool `json:"delete"` - Fork bool `json:"fork"` - Push bool `json:"push"` - Issues bool `json:"issues"` - PullRequest bool `json:"pull_request"` - IssueComment bool `json:"issue_comment"` - Release bool `json:"release"` -} - -// HookEvent represents events that will delivery hook. -type HookEvent struct { - PushOnly bool `json:"push_only"` - SendEverything bool `json:"send_everything"` - ChooseEvents bool `json:"choose_events"` - - HookEvents `json:"events"` -} - -type HookStatus int - -const ( - HOOK_STATUS_NONE = iota - HOOK_STATUS_SUCCEED - HOOK_STATUS_FAILED -) - -// Webhook represents a web hook object. -type Webhook struct { - ID int64 - RepoID int64 - OrgID int64 - URL string `xorm:"url TEXT"` - ContentType HookContentType - Secret string `xorm:"TEXT"` - Events string `xorm:"TEXT"` - *HookEvent `xorm:"-"` // LEGACY [1.0]: Cannot ignore JSON here, it breaks old backup archive - IsSSL bool `xorm:"is_ssl"` - IsActive bool - HookTaskType HookTaskType - Meta string `xorm:"TEXT"` // store hook-specific attributes - LastStatus HookStatus // Last delivery status - - Created time.Time `xorm:"-" json:"-"` - CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-"` - UpdatedUnix int64 -} - -func (w *Webhook) BeforeInsert() { - w.CreatedUnix = time.Now().Unix() - w.UpdatedUnix = w.CreatedUnix -} - -func (w *Webhook) BeforeUpdate() { - w.UpdatedUnix = time.Now().Unix() -} - -func (w *Webhook) AfterSet(colName string, _ xorm.Cell) { - var err error - switch colName { - case "events": - w.HookEvent = &HookEvent{} - if err = jsoniter.Unmarshal([]byte(w.Events), w.HookEvent); err != nil { - log.Error(3, "Unmarshal [%d]: %v", w.ID, err) - } - case "created_unix": - w.Created = time.Unix(w.CreatedUnix, 0).Local() - case "updated_unix": - w.Updated = time.Unix(w.UpdatedUnix, 0).Local() - } -} - -func (w *Webhook) GetSlackHook() *SlackMeta { - s := &SlackMeta{} - if err := jsoniter.Unmarshal([]byte(w.Meta), s); err != nil { - log.Error(2, "GetSlackHook [%d]: %v", w.ID, err) - } - return s -} - -// History returns history of webhook by given conditions. -func (w *Webhook) History(page int) ([]*HookTask, error) { - return HookTasks(w.ID, page) -} - -// UpdateEvent handles conversion from HookEvent to Events. -func (w *Webhook) UpdateEvent() error { - data, err := jsoniter.Marshal(w.HookEvent) - w.Events = string(data) - return err -} - -// HasCreateEvent returns true if hook enabled create event. -func (w *Webhook) HasCreateEvent() bool { - return w.SendEverything || - (w.ChooseEvents && w.HookEvents.Create) -} - -// HasDeleteEvent returns true if hook enabled delete event. -func (w *Webhook) HasDeleteEvent() bool { - return w.SendEverything || - (w.ChooseEvents && w.HookEvents.Delete) -} - -// HasForkEvent returns true if hook enabled fork event. -func (w *Webhook) HasForkEvent() bool { - return w.SendEverything || - (w.ChooseEvents && w.HookEvents.Fork) -} - -// HasPushEvent returns true if hook enabled push event. -func (w *Webhook) HasPushEvent() bool { - return w.PushOnly || w.SendEverything || - (w.ChooseEvents && w.HookEvents.Push) -} - -// HasIssuesEvent returns true if hook enabled issues event. -func (w *Webhook) HasIssuesEvent() bool { - return w.SendEverything || - (w.ChooseEvents && w.HookEvents.Issues) -} - -// HasPullRequestEvent returns true if hook enabled pull request event. -func (w *Webhook) HasPullRequestEvent() bool { - return w.SendEverything || - (w.ChooseEvents && w.HookEvents.PullRequest) -} - -// HasIssueCommentEvent returns true if hook enabled issue comment event. -func (w *Webhook) HasIssueCommentEvent() bool { - return w.SendEverything || - (w.ChooseEvents && w.HookEvents.IssueComment) -} - -// HasReleaseEvent returns true if hook enabled release event. -func (w *Webhook) HasReleaseEvent() bool { - return w.SendEverything || - (w.ChooseEvents && w.HookEvents.Release) -} - -type eventChecker struct { - checker func() bool - typ HookEventType -} - -func (w *Webhook) EventsArray() []string { - events := make([]string, 0, 8) - eventCheckers := []eventChecker{ - {w.HasCreateEvent, HOOK_EVENT_CREATE}, - {w.HasDeleteEvent, HOOK_EVENT_DELETE}, - {w.HasForkEvent, HOOK_EVENT_FORK}, - {w.HasPushEvent, HOOK_EVENT_PUSH}, - {w.HasIssuesEvent, HOOK_EVENT_ISSUES}, - {w.HasPullRequestEvent, HOOK_EVENT_PULL_REQUEST}, - {w.HasIssueCommentEvent, HOOK_EVENT_ISSUE_COMMENT}, - {w.HasReleaseEvent, HOOK_EVENT_RELEASE}, - } - for _, c := range eventCheckers { - if c.checker() { - events = append(events, string(c.typ)) - } - } - return events -} - -// CreateWebhook creates a new web hook. -func CreateWebhook(w *Webhook) error { - _, err := x.Insert(w) - return err -} - -// getWebhook uses argument bean as query condition, -// ID must be specified and do not assign unnecessary fields. -func getWebhook(bean *Webhook) (*Webhook, error) { - has, err := x.Get(bean) - if err != nil { - return nil, err - } else if !has { - return nil, errors.WebhookNotExist{bean.ID} - } - return bean, nil -} - -// GetWebhookByID returns webhook by given ID. -// Use this function with caution of accessing unauthorized webhook, -// which means should only be used in non-user interactive functions. -func GetWebhookByID(id int64) (*Webhook, error) { - return getWebhook(&Webhook{ - ID: id, - }) -} - -// GetWebhookOfRepoByID returns webhook of repository by given ID. -func GetWebhookOfRepoByID(repoID, id int64) (*Webhook, error) { - return getWebhook(&Webhook{ - ID: id, - RepoID: repoID, - }) -} - -// GetWebhookByOrgID returns webhook of organization by given ID. -func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) { - return getWebhook(&Webhook{ - ID: id, - OrgID: orgID, - }) -} - -// getActiveWebhooksByRepoID returns all active webhooks of repository. -func getActiveWebhooksByRepoID(e Engine, repoID int64) ([]*Webhook, error) { - webhooks := make([]*Webhook, 0, 5) - return webhooks, e.Where("repo_id = ?", repoID).And("is_active = ?", true).Find(&webhooks) -} - -// GetWebhooksByRepoID returns all webhooks of a repository. -func GetWebhooksByRepoID(repoID int64) ([]*Webhook, error) { - webhooks := make([]*Webhook, 0, 5) - return webhooks, x.Find(&webhooks, &Webhook{RepoID: repoID}) -} - -// UpdateWebhook updates information of webhook. -func UpdateWebhook(w *Webhook) error { - _, err := x.Id(w.ID).AllCols().Update(w) - return err -} - -// deleteWebhook uses argument bean as query condition, -// ID must be specified and do not assign unnecessary fields. -func deleteWebhook(bean *Webhook) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Delete(bean); err != nil { - return err - } else if _, err = sess.Delete(&HookTask{HookID: bean.ID}); err != nil { - return err - } - - return sess.Commit() -} - -// DeleteWebhookOfRepoByID deletes webhook of repository by given ID. -func DeleteWebhookOfRepoByID(repoID, id int64) error { - return deleteWebhook(&Webhook{ - ID: id, - RepoID: repoID, - }) -} - -// DeleteWebhookOfOrgByID deletes webhook of organization by given ID. -func DeleteWebhookOfOrgByID(orgID, id int64) error { - return deleteWebhook(&Webhook{ - ID: id, - OrgID: orgID, - }) -} - -// GetWebhooksByOrgID returns all webhooks for an organization. -func GetWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) { - err = x.Find(&ws, &Webhook{OrgID: orgID}) - return ws, err -} - -// getActiveWebhooksByOrgID returns all active webhooks for an organization. -func getActiveWebhooksByOrgID(e Engine, orgID int64) ([]*Webhook, error) { - ws := make([]*Webhook, 0, 3) - return ws, e.Where("org_id=?", orgID).And("is_active=?", true).Find(&ws) -} - -// ___ ___ __ ___________ __ -// / | \ ____ ____ | | _\__ ___/____ _____| | __ -// / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ / -// \ Y ( <_> | <_> ) < | | / __ \_\___ \| < -// \___|_ / \____/ \____/|__|_ \ |____| (____ /____ >__|_ \ -// \/ \/ \/ \/ \/ - -type HookTaskType int - -const ( - GOGS HookTaskType = iota + 1 - SLACK - DISCORD - DINGTALK -) - -var hookTaskTypes = map[string]HookTaskType{ - "gogs": GOGS, - "slack": SLACK, - "discord": DISCORD, - "dingtalk": DINGTALK, -} - -// ToHookTaskType returns HookTaskType by given name. -func ToHookTaskType(name string) HookTaskType { - return hookTaskTypes[name] -} - -func (t HookTaskType) Name() string { - switch t { - case GOGS: - return "gogs" - case SLACK: - return "slack" - case DISCORD: - return "discord" - case DINGTALK: - return "dingtalk" - } - return "" -} - -// IsValidHookTaskType returns true if given name is a valid hook task type. -func IsValidHookTaskType(name string) bool { - _, ok := hookTaskTypes[name] - return ok -} - -type HookEventType string - -const ( - HOOK_EVENT_CREATE HookEventType = "create" - HOOK_EVENT_DELETE HookEventType = "delete" - HOOK_EVENT_FORK HookEventType = "fork" - HOOK_EVENT_PUSH HookEventType = "push" - HOOK_EVENT_ISSUES HookEventType = "issues" - HOOK_EVENT_PULL_REQUEST HookEventType = "pull_request" - HOOK_EVENT_ISSUE_COMMENT HookEventType = "issue_comment" - HOOK_EVENT_RELEASE HookEventType = "release" -) - -// HookRequest represents hook task request information. -type HookRequest struct { - Headers map[string]string `json:"headers"` -} - -// HookResponse represents hook task response information. -type HookResponse struct { - Status int `json:"status"` - Headers map[string]string `json:"headers"` - Body string `json:"body"` -} - -// HookTask represents a hook task. -type HookTask struct { - ID int64 - RepoID int64 `xorm:"INDEX"` - HookID int64 - UUID string - Type HookTaskType - URL string `xorm:"TEXT"` - Signature string `xorm:"TEXT"` - api.Payloader `xorm:"-" json:"-"` - PayloadContent string `xorm:"TEXT"` - ContentType HookContentType - EventType HookEventType - IsSSL bool - IsDelivered bool - Delivered int64 - DeliveredString string `xorm:"-" json:"-"` - - // History info. - IsSucceed bool - RequestContent string `xorm:"TEXT"` - RequestInfo *HookRequest `xorm:"-" json:"-"` - ResponseContent string `xorm:"TEXT"` - ResponseInfo *HookResponse `xorm:"-" json:"-"` -} - -func (t *HookTask) BeforeUpdate() { - if t.RequestInfo != nil { - t.RequestContent = t.MarshalJSON(t.RequestInfo) - } - if t.ResponseInfo != nil { - t.ResponseContent = t.MarshalJSON(t.ResponseInfo) - } -} - -func (t *HookTask) AfterSet(colName string, _ xorm.Cell) { - var err error - switch colName { - case "delivered": - t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST") - - case "request_content": - if len(t.RequestContent) == 0 { - return - } - - t.RequestInfo = &HookRequest{} - if err = jsoniter.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil { - log.Error(3, "Unmarshal[%d]: %v", t.ID, err) - } - - case "response_content": - if len(t.ResponseContent) == 0 { - return - } - - t.ResponseInfo = &HookResponse{} - if err = jsoniter.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil { - log.Error(3, "Unmarshal [%d]: %v", t.ID, err) - } - } -} - -func (t *HookTask) MarshalJSON(v interface{}) string { - p, err := jsoniter.Marshal(v) - if err != nil { - log.Error(3, "Marshal [%d]: %v", t.ID, err) - } - return string(p) -} - -// HookTasks returns a list of hook tasks by given conditions. -func HookTasks(hookID int64, page int) ([]*HookTask, error) { - tasks := make([]*HookTask, 0, setting.Webhook.PagingNum) - return tasks, x.Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).Where("hook_id=?", hookID).Desc("id").Find(&tasks) -} - -// createHookTask creates a new hook task, -// it handles conversion from Payload to PayloadContent. -func createHookTask(e Engine, t *HookTask) error { - data, err := t.Payloader.JSONPayload() - if err != nil { - return err - } - t.UUID = gouuid.NewV4().String() - t.PayloadContent = string(data) - _, err = e.Insert(t) - return err -} - -// GetHookTaskOfWebhookByUUID returns hook task of given webhook by UUID. -func GetHookTaskOfWebhookByUUID(webhookID int64, uuid string) (*HookTask, error) { - hookTask := &HookTask{ - HookID: webhookID, - UUID: uuid, - } - has, err := x.Get(hookTask) - if err != nil { - return nil, err - } else if !has { - return nil, errors.HookTaskNotExist{webhookID, uuid} - } - return hookTask, nil -} - -// UpdateHookTask updates information of hook task. -func UpdateHookTask(t *HookTask) error { - _, err := x.Id(t.ID).AllCols().Update(t) - return err -} - -// prepareHookTasks adds list of webhooks to task queue. -func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Payloader, webhooks []*Webhook) (err error) { - if len(webhooks) == 0 { - return nil - } - - var payloader api.Payloader - for _, w := range webhooks { - switch event { - case HOOK_EVENT_CREATE: - if !w.HasCreateEvent() { - continue - } - case HOOK_EVENT_DELETE: - if !w.HasDeleteEvent() { - continue - } - case HOOK_EVENT_FORK: - if !w.HasForkEvent() { - continue - } - case HOOK_EVENT_PUSH: - if !w.HasPushEvent() { - continue - } - case HOOK_EVENT_ISSUES: - if !w.HasIssuesEvent() { - continue - } - case HOOK_EVENT_PULL_REQUEST: - if !w.HasPullRequestEvent() { - continue - } - case HOOK_EVENT_ISSUE_COMMENT: - if !w.HasIssueCommentEvent() { - continue - } - case HOOK_EVENT_RELEASE: - if !w.HasReleaseEvent() { - continue - } - } - - // Use separate objects so modifcations won't be made on payload on non-Gogs type hooks. - switch w.HookTaskType { - case SLACK: - payloader, err = GetSlackPayload(p, event, w.Meta) - if err != nil { - return fmt.Errorf("GetSlackPayload: %v", err) - } - case DISCORD: - payloader, err = GetDiscordPayload(p, event, w.Meta) - if err != nil { - return fmt.Errorf("GetDiscordPayload: %v", err) - } - case DINGTALK: - payloader, err = GetDingtalkPayload(p, event) - if err != nil { - return fmt.Errorf("GetDingtalkPayload: %v", err) - } - default: - payloader = p - } - - var signature string - if len(w.Secret) > 0 { - data, err := payloader.JSONPayload() - if err != nil { - log.Error(2, "prepareWebhooks.JSONPayload: %v", err) - } - sig := hmac.New(sha256.New, []byte(w.Secret)) - sig.Write(data) - signature = hex.EncodeToString(sig.Sum(nil)) - } - - if err = createHookTask(e, &HookTask{ - RepoID: repo.ID, - HookID: w.ID, - Type: w.HookTaskType, - URL: w.URL, - Signature: signature, - Payloader: payloader, - ContentType: w.ContentType, - EventType: event, - IsSSL: w.IsSSL, - }); err != nil { - return fmt.Errorf("createHookTask: %v", err) - } - } - - // It's safe to fail when the whole function is called during hook execution - // because resource released after exit. Also, there is no process started to - // consume this input during hook execution. - go HookQueue.Add(repo.ID) - return nil -} - -func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payloader) error { - webhooks, err := getActiveWebhooksByRepoID(e, repo.ID) - if err != nil { - return fmt.Errorf("getActiveWebhooksByRepoID [%d]: %v", repo.ID, err) - } - - // check if repo belongs to org and append additional webhooks - if repo.mustOwner(e).IsOrganization() { - // get hooks for org - orgws, err := getActiveWebhooksByOrgID(e, repo.OwnerID) - if err != nil { - return fmt.Errorf("getActiveWebhooksByOrgID [%d]: %v", repo.OwnerID, err) - } - webhooks = append(webhooks, orgws...) - } - return prepareHookTasks(e, repo, event, p, webhooks) -} - -// PrepareWebhooks adds all active webhooks to task queue. -func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error { - return prepareWebhooks(x, repo, event, p) -} - -// TestWebhook adds the test webhook matches the ID to task queue. -func TestWebhook(repo *Repository, event HookEventType, p api.Payloader, webhookID int64) error { - webhook, err := GetWebhookOfRepoByID(repo.ID, webhookID) - if err != nil { - return fmt.Errorf("GetWebhookOfRepoByID [repo_id: %d, id: %d]: %v", repo.ID, webhookID, err) - } - return prepareHookTasks(x, repo, event, p, []*Webhook{webhook}) -} - -func (t *HookTask) deliver() { - t.IsDelivered = true - - timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second - req := httplib.Post(t.URL).SetTimeout(timeout, timeout). - Header("X-Github-Delivery", t.UUID). - Header("X-Github-Event", string(t.EventType)). - Header("X-Gogs-Delivery", t.UUID). - Header("X-Gogs-Signature", t.Signature). - Header("X-Gogs-Event", string(t.EventType)). - SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify}) - - switch t.ContentType { - case JSON: - req = req.Header("Content-Type", "application/json").Body(t.PayloadContent) - case FORM: - req.Param("payload", t.PayloadContent) - } - - // Record delivery information. - t.RequestInfo = &HookRequest{ - Headers: map[string]string{}, - } - for k, vals := range req.Headers() { - t.RequestInfo.Headers[k] = strings.Join(vals, ",") - } - - t.ResponseInfo = &HookResponse{ - Headers: map[string]string{}, - } - - defer func() { - t.Delivered = time.Now().UnixNano() - if t.IsSucceed { - log.Trace("Hook delivered: %s", t.UUID) - } else { - log.Trace("Hook delivery failed: %s", t.UUID) - } - - // Update webhook last delivery status. - w, err := GetWebhookByID(t.HookID) - if err != nil { - log.Error(3, "GetWebhookByID: %v", err) - return - } - if t.IsSucceed { - w.LastStatus = HOOK_STATUS_SUCCEED - } else { - w.LastStatus = HOOK_STATUS_FAILED - } - if err = UpdateWebhook(w); err != nil { - log.Error(3, "UpdateWebhook: %v", err) - return - } - }() - - resp, err := req.Response() - if err != nil { - t.ResponseInfo.Body = fmt.Sprintf("Delivery: %v", err) - return - } - defer resp.Body.Close() - - // Status code is 20x can be seen as succeed. - t.IsSucceed = resp.StatusCode/100 == 2 - t.ResponseInfo.Status = resp.StatusCode - for k, vals := range resp.Header { - t.ResponseInfo.Headers[k] = strings.Join(vals, ",") - } - - p, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err) - return - } - t.ResponseInfo.Body = string(p) -} - -// DeliverHooks checks and delivers undelivered hooks. -// TODO: shoot more hooks at same time. -func DeliverHooks() { - tasks := make([]*HookTask, 0, 10) - x.Where("is_delivered = ?", false).Iterate(new(HookTask), - func(idx int, bean interface{}) error { - t := bean.(*HookTask) - t.deliver() - tasks = append(tasks, t) - return nil - }) - - // Update hook task status. - for _, t := range tasks { - if err := UpdateHookTask(t); err != nil { - log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err) - } - } - - // Start listening on new hook requests. - for repoID := range HookQueue.Queue() { - log.Trace("DeliverHooks [repo_id: %v]", repoID) - HookQueue.Remove(repoID) - - tasks = make([]*HookTask, 0, 5) - if err := x.Where("repo_id = ?", repoID).And("is_delivered = ?", false).Find(&tasks); err != nil { - log.Error(4, "Get repository [%s] hook tasks: %v", repoID, err) - continue - } - for _, t := range tasks { - t.deliver() - if err := UpdateHookTask(t); err != nil { - log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err) - continue - } - } - } -} - -func InitDeliverHooks() { - go DeliverHooks() -} diff --git a/models/webhook_dingtalk.go b/models/webhook_dingtalk.go deleted file mode 100644 index 99b623cc..00000000 --- a/models/webhook_dingtalk.go +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "strings" - - "github.com/json-iterator/go" - - "github.com/gogs/git-module" - api "github.com/gogs/go-gogs-client" -) - -const ( - DingtalkNotificationTitle = "Gogs Notification" -) - -//Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=257&articleId=105735&docType=1 -type DingtalkActionCard struct { - Title string `json:"title"` - Text string `json:"text"` - HideAvatar string `json:"hideAvatar"` - BtnOrientation string `json:"btnOrientation"` - SingleTitle string `json:"singleTitle"` - SingleURL string `json:"singleURL"` -} - -//Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=257&articleId=105735&docType=1 -type DingtalkAtObject struct { - AtMobiles []string `json:"atMobiles"` - IsAtAll bool `json:"isAtAll"` -} - -//Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=257&articleId=105735&docType=1 -type DingtalkPayload struct { - MsgType string `json:"msgtype"` - At DingtalkAtObject `json:"at"` - ActionCard DingtalkActionCard `json:"actionCard"` -} - -func (p *DingtalkPayload) JSONPayload() ([]byte, error) { - data, err := jsoniter.MarshalIndent(p, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -func NewDingtalkActionCard(singleTitle, singleURL string) DingtalkActionCard { - return DingtalkActionCard{ - Title: DingtalkNotificationTitle, - SingleURL: singleURL, - SingleTitle: singleTitle, - } -} - -//TODO: add content -func GetDingtalkPayload(p api.Payloader, event HookEventType) (payload *DingtalkPayload, err error) { - switch event { - case HOOK_EVENT_CREATE: - payload, err = getDingtalkCreatePayload(p.(*api.CreatePayload)) - case HOOK_EVENT_DELETE: - payload, err = getDingtalkDeletePayload(p.(*api.DeletePayload)) - case HOOK_EVENT_FORK: - payload, err = getDingtalkForkPayload(p.(*api.ForkPayload)) - case HOOK_EVENT_PUSH: - payload, err = getDingtalkPushPayload(p.(*api.PushPayload)) - case HOOK_EVENT_ISSUES: - payload, err = getDingtalkIssuesPayload(p.(*api.IssuesPayload)) - case HOOK_EVENT_ISSUE_COMMENT: - payload, err = getDingtalkIssueCommentPayload(p.(*api.IssueCommentPayload)) - case HOOK_EVENT_PULL_REQUEST: - payload, err = getDingtalkPullRequestPayload(p.(*api.PullRequestPayload)) - case HOOK_EVENT_RELEASE: - payload, err = getDingtalkReleasePayload(p.(*api.ReleasePayload)) - } - - if err != nil { - return nil, fmt.Errorf("event '%s': %v", event, err) - } - - return payload, nil -} - -func getDingtalkCreatePayload(p *api.CreatePayload) (*DingtalkPayload, error) { - refName := git.RefEndName(p.Ref) - refType := strings.Title(p.RefType) - - actionCard := NewDingtalkActionCard("View "+refType, p.Repo.HTMLURL+"/src/"+refName) - - actionCard.Text += "# New " + refType + " Create Event" - actionCard.Text += "\n- Repo: **" + MarkdownLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) + "**" - actionCard.Text += "\n- New " + refType + ": **" + MarkdownLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName) + "**" - - return &DingtalkPayload{MsgType: "actionCard", ActionCard: actionCard}, nil -} - -func getDingtalkDeletePayload(p *api.DeletePayload) (*DingtalkPayload, error) { - refName := git.RefEndName(p.Ref) - refType := strings.Title(p.RefType) - - actionCard := NewDingtalkActionCard("View Repo", p.Repo.HTMLURL) - - actionCard.Text += "# " + refType + " Delete Event" - actionCard.Text += "\n- Repo: **" + MarkdownLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) + "**" - actionCard.Text += "\n- " + refType + ": **" + refName + "**" - - return &DingtalkPayload{MsgType: "actionCard", ActionCard: actionCard}, nil -} - -func getDingtalkForkPayload(p *api.ForkPayload) (*DingtalkPayload, error) { - actionCard := NewDingtalkActionCard("View Forkee", p.Forkee.HTMLURL) - - actionCard.Text += "# Repo Fork Event" - actionCard.Text += "\n- From Repo: **" + MarkdownLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) + "**" - actionCard.Text += "\n- To Repo: **" + MarkdownLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) + "**" - - return &DingtalkPayload{MsgType: "actionCard", ActionCard: actionCard}, nil -} - -func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) { - refName := git.RefEndName(p.Ref) - - pusher := p.Pusher.FullName - if pusher == "" { - pusher = p.Pusher.UserName - } - - var detail string - for i, commit := range p.Commits { - msg := strings.Split(commit.Message, "\n")[0] - commitLink := MarkdownLinkFormatter(commit.URL, commit.ID[:7]) - detail += fmt.Sprintf("> %d. %s %s - %s\n", i, commitLink, commit.Author.Name, msg) - } - - actionCard := NewDingtalkActionCard("View Changes", p.CompareURL) - - actionCard.Text += "# Repo Push Event" - actionCard.Text += "\n- Repo: **" + MarkdownLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) + "**" - actionCard.Text += "\n- Ref: **" + MarkdownLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName) + "**" - actionCard.Text += "\n- Pusher: **" + pusher + "**" - actionCard.Text += "\n## " + fmt.Sprintf("Total %d commits(s)", len(p.Commits)) - actionCard.Text += "\n" + detail - - return &DingtalkPayload{MsgType: "actionCard", ActionCard: actionCard}, nil -} - -func getDingtalkIssuesPayload(p *api.IssuesPayload) (*DingtalkPayload, error) { - issueName := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title) - issueURL := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index) - - actionCard := NewDingtalkActionCard("View Issue", issueURL) - - actionCard.Text += "# Issue Event " + strings.Title(string(p.Action)) - actionCard.Text += "\n- Issue: **" + MarkdownLinkFormatter(issueURL, issueName) + "**" - - if p.Action == api.HOOK_ISSUE_ASSIGNED { - actionCard.Text += "\n- New Assignee: **" + p.Issue.Assignee.UserName + "**" - } else if p.Action == api.HOOK_ISSUE_MILESTONED { - actionCard.Text += "\n- New Milestone: **" + p.Issue.Milestone.Title + "**" - } else if p.Action == api.HOOK_ISSUE_LABEL_UPDATED { - if len(p.Issue.Labels) > 0 { - labels := make([]string, len(p.Issue.Labels)) - for i, label := range p.Issue.Labels { - labels[i] = "**" + label.Name + "**" - } - actionCard.Text += "\n- Labels: " + strings.Join(labels, ",") - } else { - actionCard.Text += "\n- Labels: **empty**" - } - } - - if p.Issue.Body != "" { - actionCard.Text += "\n> " + p.Issue.Body - } - - return &DingtalkPayload{MsgType: "actionCard", ActionCard: actionCard}, nil -} - -func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayload, error) { - issueName := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title) - commentURL := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index) - if p.Action != api.HOOK_ISSUE_COMMENT_DELETED { - commentURL += "#" + CommentHashTag(p.Comment.ID) - } - - issueURL := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index) - - actionCard := NewDingtalkActionCard("View Issue Comment", commentURL) - - actionCard.Text += "# Issue Comment " + strings.Title(string(p.Action)) - actionCard.Text += "\n- Issue: " + MarkdownLinkFormatter(issueURL, issueName) - actionCard.Text += "\n- Comment content: " - actionCard.Text += "\n> " + p.Comment.Body - - return &DingtalkPayload{MsgType: "actionCard", ActionCard: actionCard}, nil -} - -func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, error) { - title := "# Pull Request " + strings.Title(string(p.Action)) - if p.Action == api.HOOK_ISSUE_CLOSED && p.PullRequest.HasMerged { - title = "# Pull Request Merged" - } - - pullRequestURL := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) - - content := "- PR: " + MarkdownLinkFormatter(pullRequestURL, fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)) - if p.Action == api.HOOK_ISSUE_ASSIGNED { - content += "\n- New Assignee: **" + p.PullRequest.Assignee.UserName + "**" - } else if p.Action == api.HOOK_ISSUE_MILESTONED { - content += "\n- New Milestone: *" + p.PullRequest.Milestone.Title + "*" - } else if p.Action == api.HOOK_ISSUE_LABEL_UPDATED { - labels := make([]string, len(p.PullRequest.Labels)) - for i, label := range p.PullRequest.Labels { - labels[i] = "**" + label.Name + "**" - } - content += "\n- New Labels: " + strings.Join(labels, ",") - } - - actionCard := NewDingtalkActionCard("View Pull Request", pullRequestURL) - actionCard.Text += title + "\n" + content - - if p.Action == api.HOOK_ISSUE_OPENED || p.Action == api.HOOK_ISSUE_EDITED { - actionCard.Text += "\n> " + p.PullRequest.Body - } - - return &DingtalkPayload{MsgType: "actionCard", ActionCard: actionCard}, nil -} - -func getDingtalkReleasePayload(p *api.ReleasePayload) (*DingtalkPayload, error) { - releaseURL := p.Repository.HTMLURL + "/src/" + p.Release.TagName - - author := p.Release.Author.FullName - if author == "" { - author = p.Release.Author.UserName - } - - actionCard := NewDingtalkActionCard("View Release", releaseURL) - - actionCard.Text += "# New Release Published" - actionCard.Text += "\n- Repo: " + MarkdownLinkFormatter(p.Repository.HTMLURL, p.Repository.Name) - actionCard.Text += "\n- Tag: " + MarkdownLinkFormatter(releaseURL, p.Release.TagName) - actionCard.Text += "\n- Author: " + author - actionCard.Text += fmt.Sprintf("\n- Draft?: %t", p.Release.Draft) - actionCard.Text += fmt.Sprintf("\n- Pre Release?: %t", p.Release.Prerelease) - actionCard.Text += "\n- Title: " + p.Release.Name - - if p.Release.Body != "" { - actionCard.Text += "\n- Note: " + p.Release.Body - } - - return &DingtalkPayload{MsgType: "actionCard", ActionCard: actionCard}, nil -} - -//Format link addr and title into markdown style -func MarkdownLinkFormatter(link, text string) string { - return "[" + text + "](" + link + ")" -} diff --git a/models/webhook_discord.go b/models/webhook_discord.go deleted file mode 100644 index e7cd9f9f..00000000 --- a/models/webhook_discord.go +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright 2017 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "strconv" - "strings" - - "github.com/json-iterator/go" - - "github.com/gogs/git-module" - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/pkg/setting" -) - -type DiscordEmbedFooterObject struct { - Text string `json:"text"` -} - -type DiscordEmbedAuthorObject struct { - Name string `json:"name"` - URL string `json:"url"` - IconURL string `json:"icon_url"` -} - -type DiscordEmbedFieldObject struct { - Name string `json:"name"` - Value string `json:"value"` -} - -type DiscordEmbedObject struct { - Title string `json:"title"` - Description string `json:"description"` - URL string `json:"url"` - Color int `json:"color"` - Footer *DiscordEmbedFooterObject `json:"footer"` - Author *DiscordEmbedAuthorObject `json:"author"` - Fields []*DiscordEmbedFieldObject `json:"fields"` -} - -type DiscordPayload struct { - Content string `json:"content"` - Username string `json:"username"` - AvatarURL string `json:"avatar_url"` - Embeds []*DiscordEmbedObject `json:"embeds"` -} - -func (p *DiscordPayload) JSONPayload() ([]byte, error) { - data, err := jsoniter.MarshalIndent(p, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -func DiscordTextFormatter(s string) string { - return strings.Split(s, "\n")[0] -} - -func DiscordLinkFormatter(url string, text string) string { - return fmt.Sprintf("[%s](%s)", text, url) -} - -func DiscordSHALinkFormatter(url string, text string) string { - return fmt.Sprintf("[`%s`](%s)", text, url) -} - -// getDiscordCreatePayload composes Discord payload for create new branch or tag. -func getDiscordCreatePayload(p *api.CreatePayload) (*DiscordPayload, error) { - refName := git.RefEndName(p.Ref) - repoLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) - refLink := DiscordLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName) - content := fmt.Sprintf("Created new %s: %s/%s", p.RefType, repoLink, refLink) - return &DiscordPayload{ - Embeds: []*DiscordEmbedObject{{ - Description: content, - URL: setting.AppURL + p.Sender.UserName, - Author: &DiscordEmbedAuthorObject{ - Name: p.Sender.UserName, - IconURL: p.Sender.AvatarUrl, - }, - }}, - }, nil -} - -// getDiscordDeletePayload composes Discord payload for delete a branch or tag. -func getDiscordDeletePayload(p *api.DeletePayload) (*DiscordPayload, error) { - refName := git.RefEndName(p.Ref) - repoLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) - content := fmt.Sprintf("Deleted %s: %s/%s", p.RefType, repoLink, refName) - return &DiscordPayload{ - Embeds: []*DiscordEmbedObject{{ - Description: content, - URL: setting.AppURL + p.Sender.UserName, - Author: &DiscordEmbedAuthorObject{ - Name: p.Sender.UserName, - IconURL: p.Sender.AvatarUrl, - }, - }}, - }, nil -} - -// getDiscordForkPayload composes Discord payload for forked by a repository. -func getDiscordForkPayload(p *api.ForkPayload) (*DiscordPayload, error) { - baseLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) - forkLink := DiscordLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) - content := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) - return &DiscordPayload{ - Embeds: []*DiscordEmbedObject{{ - Description: content, - URL: setting.AppURL + p.Sender.UserName, - Author: &DiscordEmbedAuthorObject{ - Name: p.Sender.UserName, - IconURL: p.Sender.AvatarUrl, - }, - }}, - }, nil -} - -func getDiscordPushPayload(p *api.PushPayload, slack *SlackMeta) (*DiscordPayload, error) { - // n new commits - var ( - branchName = git.RefEndName(p.Ref) - commitDesc string - commitString string - ) - - if len(p.Commits) == 1 { - commitDesc = "1 new commit" - } else { - commitDesc = fmt.Sprintf("%d new commits", len(p.Commits)) - } - - if len(p.CompareURL) > 0 { - commitString = DiscordLinkFormatter(p.CompareURL, commitDesc) - } else { - commitString = commitDesc - } - - repoLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) - branchLink := DiscordLinkFormatter(p.Repo.HTMLURL+"/src/"+branchName, branchName) - content := fmt.Sprintf("Pushed %s to %s/%s\n", commitString, repoLink, branchLink) - - // for each commit, generate attachment text - for i, commit := range p.Commits { - content += fmt.Sprintf("%s %s - %s", DiscordSHALinkFormatter(commit.URL, commit.ID[:7]), DiscordTextFormatter(commit.Message), commit.Author.Name) - // add linebreak to each commit but the last - if i < len(p.Commits)-1 { - content += "\n" - } - } - - color, _ := strconv.ParseInt(strings.TrimLeft(slack.Color, "#"), 16, 32) - return &DiscordPayload{ - Username: slack.Username, - AvatarURL: slack.IconURL, - Embeds: []*DiscordEmbedObject{{ - Description: content, - URL: setting.AppURL + p.Sender.UserName, - Color: int(color), - Author: &DiscordEmbedAuthorObject{ - Name: p.Sender.UserName, - IconURL: p.Sender.AvatarUrl, - }, - }}, - }, nil -} - -func getDiscordIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) (*DiscordPayload, error) { - title := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title) - url := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index) - content := "" - fields := make([]*DiscordEmbedFieldObject, 0, 1) - switch p.Action { - case api.HOOK_ISSUE_OPENED: - title = "New issue: " + title - content = p.Issue.Body - case api.HOOK_ISSUE_CLOSED: - title = "Issue closed: " + title - case api.HOOK_ISSUE_REOPENED: - title = "Issue re-opened: " + title - case api.HOOK_ISSUE_EDITED: - title = "Issue edited: " + title - content = p.Issue.Body - case api.HOOK_ISSUE_ASSIGNED: - title = "Issue assigned: " + title - fields = []*DiscordEmbedFieldObject{{ - Name: "New Assignee", - Value: p.Issue.Assignee.UserName, - }} - case api.HOOK_ISSUE_UNASSIGNED: - title = "Issue unassigned: " + title - case api.HOOK_ISSUE_LABEL_UPDATED: - title = "Issue labels updated: " + title - labels := make([]string, len(p.Issue.Labels)) - for i := range p.Issue.Labels { - labels[i] = p.Issue.Labels[i].Name - } - if len(labels) == 0 { - labels = []string{"<empty>"} - } - fields = []*DiscordEmbedFieldObject{{ - Name: "Labels", - Value: strings.Join(labels, ", "), - }} - case api.HOOK_ISSUE_LABEL_CLEARED: - title = "Issue labels cleared: " + title - case api.HOOK_ISSUE_SYNCHRONIZED: - title = "Issue synchronized: " + title - case api.HOOK_ISSUE_MILESTONED: - title = "Issue milestoned: " + title - fields = []*DiscordEmbedFieldObject{{ - Name: "New Milestone", - Value: p.Issue.Milestone.Title, - }} - case api.HOOK_ISSUE_DEMILESTONED: - title = "Issue demilestoned: " + title - } - - color, _ := strconv.ParseInt(strings.TrimLeft(slack.Color, "#"), 16, 32) - return &DiscordPayload{ - Username: slack.Username, - AvatarURL: slack.IconURL, - Embeds: []*DiscordEmbedObject{{ - Title: title, - Description: content, - URL: url, - Color: int(color), - Footer: &DiscordEmbedFooterObject{ - Text: p.Repository.FullName, - }, - Author: &DiscordEmbedAuthorObject{ - Name: p.Sender.UserName, - IconURL: p.Sender.AvatarUrl, - }, - Fields: fields, - }}, - }, nil -} - -func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*DiscordPayload, error) { - title := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title) - url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)) - content := "" - fields := make([]*DiscordEmbedFieldObject, 0, 1) - switch p.Action { - case api.HOOK_ISSUE_COMMENT_CREATED: - title = "New comment: " + title - content = p.Comment.Body - case api.HOOK_ISSUE_COMMENT_EDITED: - title = "Comment edited: " + title - content = p.Comment.Body - case api.HOOK_ISSUE_COMMENT_DELETED: - title = "Comment deleted: " + title - url = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index) - content = p.Comment.Body - } - - color, _ := strconv.ParseInt(strings.TrimLeft(slack.Color, "#"), 16, 32) - return &DiscordPayload{ - Username: slack.Username, - AvatarURL: slack.IconURL, - Embeds: []*DiscordEmbedObject{{ - Title: title, - Description: content, - URL: url, - Color: int(color), - Footer: &DiscordEmbedFooterObject{ - Text: p.Repository.FullName, - }, - Author: &DiscordEmbedAuthorObject{ - Name: p.Sender.UserName, - IconURL: p.Sender.AvatarUrl, - }, - Fields: fields, - }}, - }, nil -} - -func getDiscordPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*DiscordPayload, error) { - title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) - url := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) - content := "" - fields := make([]*DiscordEmbedFieldObject, 0, 1) - switch p.Action { - case api.HOOK_ISSUE_OPENED: - title = "New pull request: " + title - content = p.PullRequest.Body - case api.HOOK_ISSUE_CLOSED: - if p.PullRequest.HasMerged { - title = "Pull request merged: " + title - } else { - title = "Pull request closed: " + title - } - case api.HOOK_ISSUE_REOPENED: - title = "Pull request re-opened: " + title - case api.HOOK_ISSUE_EDITED: - title = "Pull request edited: " + title - content = p.PullRequest.Body - case api.HOOK_ISSUE_ASSIGNED: - title = "Pull request assigned: " + title - fields = []*DiscordEmbedFieldObject{{ - Name: "New Assignee", - Value: p.PullRequest.Assignee.UserName, - }} - case api.HOOK_ISSUE_UNASSIGNED: - title = "Pull request unassigned: " + title - case api.HOOK_ISSUE_LABEL_UPDATED: - title = "Pull request labels updated: " + title - labels := make([]string, len(p.PullRequest.Labels)) - for i := range p.PullRequest.Labels { - labels[i] = p.PullRequest.Labels[i].Name - } - fields = []*DiscordEmbedFieldObject{{ - Name: "Labels", - Value: strings.Join(labels, ", "), - }} - case api.HOOK_ISSUE_LABEL_CLEARED: - title = "Pull request labels cleared: " + title - case api.HOOK_ISSUE_SYNCHRONIZED: - title = "Pull request synchronized: " + title - case api.HOOK_ISSUE_MILESTONED: - title = "Pull request milestoned: " + title - fields = []*DiscordEmbedFieldObject{{ - Name: "New Milestone", - Value: p.PullRequest.Milestone.Title, - }} - case api.HOOK_ISSUE_DEMILESTONED: - title = "Pull request demilestoned: " + title - } - - color, _ := strconv.ParseInt(strings.TrimLeft(slack.Color, "#"), 16, 32) - return &DiscordPayload{ - Username: slack.Username, - AvatarURL: slack.IconURL, - Embeds: []*DiscordEmbedObject{{ - Title: title, - Description: content, - URL: url, - Color: int(color), - Footer: &DiscordEmbedFooterObject{ - Text: p.Repository.FullName, - }, - Author: &DiscordEmbedAuthorObject{ - Name: p.Sender.UserName, - IconURL: p.Sender.AvatarUrl, - }, - Fields: fields, - }}, - }, nil -} - -func getDiscordReleasePayload(p *api.ReleasePayload) (*DiscordPayload, error) { - repoLink := DiscordLinkFormatter(p.Repository.HTMLURL, p.Repository.Name) - refLink := DiscordLinkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName) - content := fmt.Sprintf("Published new release %s of %s", refLink, repoLink) - return &DiscordPayload{ - Embeds: []*DiscordEmbedObject{{ - Description: content, - URL: setting.AppURL + p.Sender.UserName, - Author: &DiscordEmbedAuthorObject{ - Name: p.Sender.UserName, - IconURL: p.Sender.AvatarUrl, - }, - }}, - }, nil -} - -func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (payload *DiscordPayload, err error) { - slack := &SlackMeta{} - if err := jsoniter.Unmarshal([]byte(meta), &slack); err != nil { - return nil, fmt.Errorf("jsoniter.Unmarshal: %v", err) - } - - switch event { - case HOOK_EVENT_CREATE: - payload, err = getDiscordCreatePayload(p.(*api.CreatePayload)) - case HOOK_EVENT_DELETE: - payload, err = getDiscordDeletePayload(p.(*api.DeletePayload)) - case HOOK_EVENT_FORK: - payload, err = getDiscordForkPayload(p.(*api.ForkPayload)) - case HOOK_EVENT_PUSH: - payload, err = getDiscordPushPayload(p.(*api.PushPayload), slack) - case HOOK_EVENT_ISSUES: - payload, err = getDiscordIssuesPayload(p.(*api.IssuesPayload), slack) - case HOOK_EVENT_ISSUE_COMMENT: - payload, err = getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), slack) - case HOOK_EVENT_PULL_REQUEST: - payload, err = getDiscordPullRequestPayload(p.(*api.PullRequestPayload), slack) - case HOOK_EVENT_RELEASE: - payload, err = getDiscordReleasePayload(p.(*api.ReleasePayload)) - } - if err != nil { - return nil, fmt.Errorf("event '%s': %v", event, err) - } - - payload.Username = slack.Username - payload.AvatarURL = slack.IconURL - if len(payload.Embeds) > 0 { - color, _ := strconv.ParseInt(strings.TrimLeft(slack.Color, "#"), 16, 32) - payload.Embeds[0].Color = int(color) - } - - return payload, nil -} diff --git a/models/webhook_slack.go b/models/webhook_slack.go deleted file mode 100644 index 824191be..00000000 --- a/models/webhook_slack.go +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "strings" - - "github.com/json-iterator/go" - - "github.com/gogs/git-module" - api "github.com/gogs/go-gogs-client" - - "gogs.io/gogs/pkg/setting" -) - -type SlackMeta struct { - Channel string `json:"channel"` - Username string `json:"username"` - IconURL string `json:"icon_url"` - Color string `json:"color"` -} - -type SlackAttachment struct { - Fallback string `json:"fallback"` - Color string `json:"color"` - Title string `json:"title"` - Text string `json:"text"` -} - -type SlackPayload struct { - Channel string `json:"channel"` - Text string `json:"text"` - Username string `json:"username"` - IconURL string `json:"icon_url"` - UnfurlLinks int `json:"unfurl_links"` - LinkNames int `json:"link_names"` - Attachments []*SlackAttachment `json:"attachments"` -} - -func (p *SlackPayload) JSONPayload() ([]byte, error) { - data, err := jsoniter.MarshalIndent(p, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -// see: https://api.slack.com/docs/formatting -func SlackTextFormatter(s string) string { - // replace & < > - s = strings.Replace(s, "&", "&", -1) - s = strings.Replace(s, "<", "<", -1) - s = strings.Replace(s, ">", ">", -1) - return s -} - -func SlackShortTextFormatter(s string) string { - s = strings.Split(s, "\n")[0] - // replace & < > - s = strings.Replace(s, "&", "&", -1) - s = strings.Replace(s, "<", "<", -1) - s = strings.Replace(s, ">", ">", -1) - return s -} - -func SlackLinkFormatter(url string, text string) string { - return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text)) -} - -// getSlackCreatePayload composes Slack payload for create new branch or tag. -func getSlackCreatePayload(p *api.CreatePayload) (*SlackPayload, error) { - refName := git.RefEndName(p.Ref) - repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) - refLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName) - text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName) - return &SlackPayload{ - Text: text, - }, nil -} - -// getSlackDeletePayload composes Slack payload for delete a branch or tag. -func getSlackDeletePayload(p *api.DeletePayload) (*SlackPayload, error) { - refName := git.RefEndName(p.Ref) - repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) - text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName) - return &SlackPayload{ - Text: text, - }, nil -} - -// getSlackForkPayload composes Slack payload for forked by a repository. -func getSlackForkPayload(p *api.ForkPayload) (*SlackPayload, error) { - baseLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) - forkLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) - text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) - return &SlackPayload{ - Text: text, - }, nil -} - -func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) { - // n new commits - var ( - branchName = git.RefEndName(p.Ref) - commitDesc string - commitString string - ) - - if len(p.Commits) == 1 { - commitDesc = "1 new commit" - } else { - commitDesc = fmt.Sprintf("%d new commits", len(p.Commits)) - } - if len(p.CompareURL) > 0 { - commitString = SlackLinkFormatter(p.CompareURL, commitDesc) - } else { - commitString = commitDesc - } - - repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) - branchLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+branchName, branchName) - text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.UserName) - - var attachmentText string - // for each commit, generate attachment text - for i, commit := range p.Commits { - attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.URL, commit.ID[:7]), SlackShortTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name)) - // add linebreak to each commit but the last - if i < len(p.Commits)-1 { - attachmentText += "\n" - } - } - - return &SlackPayload{ - Channel: slack.Channel, - Text: text, - Username: slack.Username, - IconURL: slack.IconURL, - Attachments: []*SlackAttachment{{ - Color: slack.Color, - Text: attachmentText, - }}, - }, nil -} - -func getSlackIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) (*SlackPayload, error) { - senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index), - fmt.Sprintf("#%d %s", p.Index, p.Issue.Title)) - var text, title, attachmentText string - switch p.Action { - case api.HOOK_ISSUE_OPENED: - text = fmt.Sprintf("[%s] New issue created by %s", p.Repository.FullName, senderLink) - title = titleLink - attachmentText = SlackTextFormatter(p.Issue.Body) - case api.HOOK_ISSUE_CLOSED: - text = fmt.Sprintf("[%s] Issue closed: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_REOPENED: - text = fmt.Sprintf("[%s] Issue re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_EDITED: - text = fmt.Sprintf("[%s] Issue edited: %s by %s", p.Repository.FullName, titleLink, senderLink) - attachmentText = SlackTextFormatter(p.Issue.Body) - case api.HOOK_ISSUE_ASSIGNED: - text = fmt.Sprintf("[%s] Issue assigned to %s: %s by %s", p.Repository.FullName, - SlackLinkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName), - titleLink, senderLink) - case api.HOOK_ISSUE_UNASSIGNED: - text = fmt.Sprintf("[%s] Issue unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_LABEL_UPDATED: - text = fmt.Sprintf("[%s] Issue labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_LABEL_CLEARED: - text = fmt.Sprintf("[%s] Issue labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_MILESTONED: - text = fmt.Sprintf("[%s] Issue milestoned: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_DEMILESTONED: - text = fmt.Sprintf("[%s] Issue demilestoned: %s by %s", p.Repository.FullName, titleLink, senderLink) - } - - return &SlackPayload{ - Channel: slack.Channel, - Text: text, - Username: slack.Username, - IconURL: slack.IconURL, - Attachments: []*SlackAttachment{{ - Color: slack.Color, - Title: title, - Text: attachmentText, - }}, - }, nil -} - -func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) { - senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)), - fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)) - var text, title, attachmentText string - switch p.Action { - case api.HOOK_ISSUE_COMMENT_CREATED: - text = fmt.Sprintf("[%s] New comment created by %s", p.Repository.FullName, senderLink) - title = titleLink - attachmentText = SlackTextFormatter(p.Comment.Body) - case api.HOOK_ISSUE_COMMENT_EDITED: - text = fmt.Sprintf("[%s] Comment edited by %s", p.Repository.FullName, senderLink) - title = titleLink - attachmentText = SlackTextFormatter(p.Comment.Body) - case api.HOOK_ISSUE_COMMENT_DELETED: - text = fmt.Sprintf("[%s] Comment deleted by %s", p.Repository.FullName, senderLink) - title = SlackLinkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index), - fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)) - attachmentText = SlackTextFormatter(p.Comment.Body) - } - - return &SlackPayload{ - Channel: slack.Channel, - Text: text, - Username: slack.Username, - IconURL: slack.IconURL, - Attachments: []*SlackAttachment{{ - Color: slack.Color, - Title: title, - Text: attachmentText, - }}, - }, nil -} - -func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) { - senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index), - fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)) - var text, title, attachmentText string - switch p.Action { - case api.HOOK_ISSUE_OPENED: - text = fmt.Sprintf("[%s] Pull request submitted by %s", p.Repository.FullName, senderLink) - title = titleLink - attachmentText = SlackTextFormatter(p.PullRequest.Body) - case api.HOOK_ISSUE_CLOSED: - if p.PullRequest.HasMerged { - text = fmt.Sprintf("[%s] Pull request merged: %s by %s", p.Repository.FullName, titleLink, senderLink) - } else { - text = fmt.Sprintf("[%s] Pull request closed: %s by %s", p.Repository.FullName, titleLink, senderLink) - } - case api.HOOK_ISSUE_REOPENED: - text = fmt.Sprintf("[%s] Pull request re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_EDITED: - text = fmt.Sprintf("[%s] Pull request edited: %s by %s", p.Repository.FullName, titleLink, senderLink) - attachmentText = SlackTextFormatter(p.PullRequest.Body) - case api.HOOK_ISSUE_ASSIGNED: - text = fmt.Sprintf("[%s] Pull request assigned to %s: %s by %s", p.Repository.FullName, - SlackLinkFormatter(setting.AppURL+p.PullRequest.Assignee.UserName, p.PullRequest.Assignee.UserName), - titleLink, senderLink) - case api.HOOK_ISSUE_UNASSIGNED: - text = fmt.Sprintf("[%s] Pull request unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_LABEL_UPDATED: - text = fmt.Sprintf("[%s] Pull request labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_LABEL_CLEARED: - text = fmt.Sprintf("[%s] Pull request labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_SYNCHRONIZED: - text = fmt.Sprintf("[%s] Pull request synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_MILESTONED: - text = fmt.Sprintf("[%s] Pull request milestoned: %s by %s", p.Repository.FullName, titleLink, senderLink) - case api.HOOK_ISSUE_DEMILESTONED: - text = fmt.Sprintf("[%s] Pull request demilestoned: %s by %s", p.Repository.FullName, titleLink, senderLink) - } - - return &SlackPayload{ - Channel: slack.Channel, - Text: text, - Username: slack.Username, - IconURL: slack.IconURL, - Attachments: []*SlackAttachment{{ - Color: slack.Color, - Title: title, - Text: attachmentText, - }}, - }, nil -} - -func getSlackReleasePayload(p *api.ReleasePayload) (*SlackPayload, error) { - repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.Name) - refLink := SlackLinkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName) - text := fmt.Sprintf("[%s] new release %s published by %s", repoLink, refLink, p.Sender.UserName) - return &SlackPayload{ - Text: text, - }, nil -} - -func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (payload *SlackPayload, err error) { - slack := &SlackMeta{} - if err := jsoniter.Unmarshal([]byte(meta), &slack); err != nil { - return nil, fmt.Errorf("Unmarshal: %v", err) - } - - switch event { - case HOOK_EVENT_CREATE: - payload, err = getSlackCreatePayload(p.(*api.CreatePayload)) - case HOOK_EVENT_DELETE: - payload, err = getSlackDeletePayload(p.(*api.DeletePayload)) - case HOOK_EVENT_FORK: - payload, err = getSlackForkPayload(p.(*api.ForkPayload)) - case HOOK_EVENT_PUSH: - payload, err = getSlackPushPayload(p.(*api.PushPayload), slack) - case HOOK_EVENT_ISSUES: - payload, err = getSlackIssuesPayload(p.(*api.IssuesPayload), slack) - case HOOK_EVENT_ISSUE_COMMENT: - payload, err = getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack) - case HOOK_EVENT_PULL_REQUEST: - payload, err = getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack) - case HOOK_EVENT_RELEASE: - payload, err = getSlackReleasePayload(p.(*api.ReleasePayload)) - } - if err != nil { - return nil, fmt.Errorf("event '%s': %v", event, err) - } - - payload.Channel = slack.Channel - payload.Username = slack.Username - payload.IconURL = slack.IconURL - if len(payload.Attachments) > 0 { - payload.Attachments[0].Color = slack.Color - } - - return payload, nil -} diff --git a/models/wiki.go b/models/wiki.go deleted file mode 100644 index ca391c05..00000000 --- a/models/wiki.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "fmt" - "io/ioutil" - "net/url" - "os" - "path" - "path/filepath" - "strings" - - "github.com/unknwon/com" - - "github.com/gogs/git-module" - - "gogs.io/gogs/pkg/setting" - "gogs.io/gogs/pkg/sync" -) - -var wikiWorkingPool = sync.NewExclusivePool() - -// ToWikiPageURL formats a string to corresponding wiki URL name. -func ToWikiPageURL(name string) string { - return url.QueryEscape(name) -} - -// ToWikiPageName formats a URL back to corresponding wiki page name, -// and removes leading characters './' to prevent changing files -// that are not belong to wiki repository. -func ToWikiPageName(urlString string) string { - name, _ := url.QueryUnescape(urlString) - return strings.Replace(strings.TrimLeft(path.Clean("/"+name), "/"), "/", " ", -1) -} - -// WikiCloneLink returns clone URLs of repository wiki. -func (repo *Repository) WikiCloneLink() (cl *CloneLink) { - return repo.cloneLink(true) -} - -// WikiPath returns wiki data path by given user and repository name. -func WikiPath(userName, repoName string) string { - return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".wiki.git") -} - -func (repo *Repository) WikiPath() string { - return WikiPath(repo.MustOwner().Name, repo.Name) -} - -// HasWiki returns true if repository has wiki. -func (repo *Repository) HasWiki() bool { - return com.IsDir(repo.WikiPath()) -} - -// InitWiki initializes a wiki for repository, -// it does nothing when repository already has wiki. -func (repo *Repository) InitWiki() error { - if repo.HasWiki() { - return nil - } - - if err := git.InitRepository(repo.WikiPath(), true); err != nil { - return fmt.Errorf("InitRepository: %v", err) - } else if err = createDelegateHooks(repo.WikiPath()); err != nil { - return fmt.Errorf("createDelegateHooks: %v", err) - } - return nil -} - -func (repo *Repository) LocalWikiPath() string { - return path.Join(setting.AppDataPath, "tmp/local-wiki", com.ToStr(repo.ID)) -} - -// UpdateLocalWiki makes sure the local copy of repository wiki is up-to-date. -func (repo *Repository) UpdateLocalWiki() error { - return UpdateLocalCopyBranch(repo.WikiPath(), repo.LocalWikiPath(), "master", true) -} - -func discardLocalWikiChanges(localPath string) error { - return discardLocalRepoBranchChanges(localPath, "master") -} - -// updateWikiPage adds new page to repository wiki. -func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, message string, isNew bool) (err error) { - wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) - - if err = repo.InitWiki(); err != nil { - return fmt.Errorf("InitWiki: %v", err) - } - - localPath := repo.LocalWikiPath() - if err = discardLocalWikiChanges(localPath); err != nil { - return fmt.Errorf("discardLocalWikiChanges: %v", err) - } else if err = repo.UpdateLocalWiki(); err != nil { - return fmt.Errorf("UpdateLocalWiki: %v", err) - } - - title = ToWikiPageName(title) - filename := path.Join(localPath, title+".md") - - // If not a new file, show perform update not create. - if isNew { - if com.IsExist(filename) { - return ErrWikiAlreadyExist{filename} - } - } else { - os.Remove(path.Join(localPath, oldTitle+".md")) - } - - // SECURITY: if new file is a symlink to non-exist critical file, - // attack content can be written to the target file (e.g. authorized_keys2) - // as a new page operation. - // So we want to make sure the symlink is removed before write anything. - // The new file we created will be in normal text format. - os.Remove(filename) - - if err = ioutil.WriteFile(filename, []byte(content), 0666); err != nil { - return fmt.Errorf("WriteFile: %v", err) - } - - if len(message) == 0 { - message = "Update page '" + title + "'" - } - if err = git.AddChanges(localPath, true); err != nil { - return fmt.Errorf("AddChanges: %v", err) - } else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ - Committer: doer.NewGitSig(), - Message: message, - }); err != nil { - return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, "origin", "master"); err != nil { - return fmt.Errorf("Push: %v", err) - } - - return nil -} - -func (repo *Repository) AddWikiPage(doer *User, title, content, message string) error { - return repo.updateWikiPage(doer, "", title, content, message, true) -} - -func (repo *Repository) EditWikiPage(doer *User, oldTitle, title, content, message string) error { - return repo.updateWikiPage(doer, oldTitle, title, content, message, false) -} - -func (repo *Repository) DeleteWikiPage(doer *User, title string) (err error) { - wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) - - localPath := repo.LocalWikiPath() - if err = discardLocalWikiChanges(localPath); err != nil { - return fmt.Errorf("discardLocalWikiChanges: %v", err) - } else if err = repo.UpdateLocalWiki(); err != nil { - return fmt.Errorf("UpdateLocalWiki: %v", err) - } - - title = ToWikiPageName(title) - filename := path.Join(localPath, title+".md") - os.Remove(filename) - - message := "Delete page '" + title + "'" - - if err = git.AddChanges(localPath, true); err != nil { - return fmt.Errorf("AddChanges: %v", err) - } else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ - Committer: doer.NewGitSig(), - Message: message, - }); err != nil { - return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, "origin", "master"); err != nil { - return fmt.Errorf("Push: %v", err) - } - - return nil -} |