aboutsummaryrefslogtreecommitdiff
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/access.go241
-rw-r--r--models/action.go767
-rw-r--r--models/admin.go118
-rw-r--r--models/attachment.go183
-rw-r--r--models/comment.go534
-rw-r--r--models/error.go575
-rw-r--r--models/errors/errors.go14
-rw-r--r--models/errors/issue.go35
-rw-r--r--models/errors/login_source.go60
-rw-r--r--models/errors/org.go21
-rw-r--r--models/errors/repo.go87
-rw-r--r--models/errors/token.go16
-rw-r--r--models/errors/two_factor.go33
-rw-r--r--models/errors/user.go45
-rw-r--r--models/errors/user_mail.go33
-rw-r--r--models/errors/webhook.go34
-rw-r--r--models/git_diff.go194
-rw-r--r--models/git_diff_test.go41
-rw-r--r--models/issue.go1440
-rw-r--r--models/issue_label.go374
-rw-r--r--models/issue_mail.go180
-rw-r--r--models/login_source.go866
-rw-r--r--models/migrations/migrations.go390
-rw-r--r--models/migrations/v13.go52
-rw-r--r--models/migrations/v14.go24
-rw-r--r--models/migrations/v15.go104
-rw-r--r--models/migrations/v16.go77
-rw-r--r--models/migrations/v17.go22
-rw-r--r--models/migrations/v18.go34
-rw-r--r--models/migrations/v19.go18
-rw-r--r--models/milestone.go402
-rw-r--r--models/mirror.go498
-rw-r--r--models/mirror_test.go108
-rw-r--r--models/models.go401
-rw-r--r--models/models_sqlite.go15
-rw-r--r--models/models_test.go33
-rw-r--r--models/org.go563
-rw-r--r--models/org_team.go666
-rw-r--r--models/pull.go851
-rw-r--r--models/release.go352
-rw-r--r--models/repo.go2458
-rw-r--r--models/repo_branch.go257
-rw-r--r--models/repo_collaboration.go226
-rw-r--r--models/repo_editor.go518
-rw-r--r--models/repo_editor_test.go34
-rw-r--r--models/repo_test.go63
-rw-r--r--models/ssh_key.go771
-rw-r--r--models/ssh_key_test.go56
-rw-r--r--models/token.go101
-rw-r--r--models/two_factor.go201
-rw-r--r--models/update.go142
-rw-r--r--models/user.go1146
-rw-r--r--models/user_cache.go16
-rw-r--r--models/user_mail.go210
-rw-r--r--models/webhook.go771
-rw-r--r--models/webhook_dingtalk.go261
-rw-r--r--models/webhook_discord.go409
-rw-r--r--models/webhook_slack.go326
-rw-r--r--models/wiki.go179
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(&notices)
-}
-
-// 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, "&", "&amp;", -1)
- s = strings.Replace(s, "<", "&lt;", -1)
- s = strings.Replace(s, ">", "&gt;", -1)
- return s
-}
-
-func SlackShortTextFormatter(s string) string {
- s = strings.Split(s, "\n")[0]
- // replace & < >
- s = strings.Replace(s, "&", "&amp;", -1)
- s = strings.Replace(s, "<", "&lt;", -1)
- s = strings.Replace(s, ">", "&gt;", -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
-}