Browse Source

Merge branch 'access' of github.com:gogits/gogs into dev

Unknwon 10 years ago
parent
commit
b0b11fd7b1

+ 1 - 1
.bra.toml

@@ -1,6 +1,6 @@
 [run]
 init_cmds = [
-	["grep", "-rn", "FIXME", "."],
+	#["grep", "-rn", "FIXME", "."],
 	["./gogs", "web"]
 ]
 watch_all = true

+ 3 - 3
README.md

@@ -7,13 +7,13 @@ Gogs (Go Git Service) is a painless self-hosted Git service written in Go.
 
 ![Demo](http://gogs.qiniudn.com/gogs_demo.gif)
 
-##### Current version: 0.5.13 Beta
+##### Current version: 0.5.16 Beta
 
 ### NOTICES
 
 - Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) has been reset in **Jan 28, 2015** and will reset multiple times after. Please do **NOT** put your important data on the site.
 - The demo site [try.gogs.io](https://try.gogs.io) is running under `dev` branch.
-- You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing a issue or making a Pull Request.
+- You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing an issue or making a Pull Request.
 - If you think there are vulnerabilities in the project, please talk privately to **[email protected]**.  Thanks!
 
 #### Other language version
@@ -57,7 +57,7 @@ The goal of this project is to make the easiest, fastest, and most painless way
 ## System Requirements
 
 - A cheap Raspberry Pi is powerful enough for basic functionality.
-- At least 4 CPU cores and 1GB RAM would be the baseline for teamwork.
+- At least 2 CPU cores and 1GB RAM would be the baseline for teamwork.
 
 ## Installation
 

+ 3 - 3
README_ZH.md

@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。
 
 ![Demo](http://gogs.qiniudn.com/gogs_demo.gif)
 
-##### 当前版本:0.5.13 Beta
+##### 当前版本:0.5.16 Beta
 
 ## 开发目的
 
@@ -44,7 +44,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
 ## 系统要求
 
 - 最低的系统硬件要求为一个廉价的树莓派
-- 如果用于团队项目,建议使用 4 核 CPU 及 1GB 内存
+- 如果用于团队项目,建议使用 2 核 CPU 及 1GB 内存
 
 ## 安装部署
 
@@ -75,4 +75,4 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
 
 ## 授权许可
 
-本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。
+本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。

+ 32 - 27
cmd/serve.go

@@ -8,7 +8,6 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
-	"path"
 	"path/filepath"
 	"strings"
 	"time"
@@ -43,7 +42,7 @@ func setup(logPath string) {
 
 	models.LoadModelsConfig()
 
-	if models.UseSQLite3 {
+	if setting.UseSQLite3 {
 		workDir, _ := setting.WorkDir()
 		os.Chdir(workDir)
 	}
@@ -67,33 +66,33 @@ func parseCmd(cmd string) (string, string) {
 }
 
 var (
-	COMMANDS_READONLY = map[string]models.AccessType{
-		"git-upload-pack":    models.WRITABLE,
-		"git upload-pack":    models.WRITABLE,
-		"git-upload-archive": models.WRITABLE,
+	COMMANDS_READONLY = map[string]models.AccessMode{
+		"git-upload-pack":    models.ACCESS_MODE_WRITE,
+		"git upload-pack":    models.ACCESS_MODE_WRITE,
+		"git-upload-archive": models.ACCESS_MODE_WRITE,
 	}
 
-	COMMANDS_WRITE = map[string]models.AccessType{
-		"git-receive-pack": models.READABLE,
-		"git receive-pack": models.READABLE,
+	COMMANDS_WRITE = map[string]models.AccessMode{
+		"git-receive-pack": models.ACCESS_MODE_READ,
+		"git receive-pack": models.ACCESS_MODE_READ,
 	}
 )
 
-func In(b string, sl map[string]models.AccessType) bool {
+func In(b string, sl map[string]models.AccessMode) bool {
 	_, e := sl[b]
 	return e
 }
 
-func runServ(k *cli.Context) {
-	if k.IsSet("config") {
-		setting.CustomConf = k.String("config")
+func runServ(c *cli.Context) {
+	if c.IsSet("config") {
+		setting.CustomConf = c.String("config")
 	}
 	setup("serv.log")
 
-	if len(k.Args()) < 1 {
+	if len(c.Args()) < 1 {
 		log.GitLogger.Fatal(2, "Not enough arguments")
 	}
-	keys := strings.Split(k.Args()[0], "-")
+	keys := strings.Split(c.Args()[0], "-")
 	if len(keys) != 2 {
 		println("Gogs: auth file format error")
 		log.GitLogger.Fatal(2, "Invalid auth file format: %s", os.Args[2])
@@ -117,6 +116,7 @@ func runServ(k *cli.Context) {
 	cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
 	if cmd == "" {
 		println("Hi", user.Name, "! You've successfully authenticated, but Gogs does not provide shell access.")
+		println("If this is what you do not expect, please log in with password and setup Gogs under another user.")
 		return
 	}
 
@@ -144,9 +144,19 @@ func runServ(k *cli.Context) {
 	}
 
 	// Access check.
+	repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
+	if err != nil {
+		if err == models.ErrRepoNotExist {
+			println("Gogs: given repository does not exist")
+			log.GitLogger.Fatal(2, "Repository does not exist: %s/%s", repoUser.Name, repoName)
+		}
+		println("Gogs: internal error:", err.Error())
+		log.GitLogger.Fatal(2, "Fail to get repository: %v", err)
+	}
+
 	switch {
 	case isWrite:
-		has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.WRITABLE)
+		has, err := models.HasAccess(user, repo, models.ACCESS_MODE_WRITE)
 		if err != nil {
 			println("Gogs: internal error:", err.Error())
 			log.GitLogger.Fatal(2, "Fail to check write access:", err)
@@ -154,22 +164,17 @@ func runServ(k *cli.Context) {
 			println("You have no right to write this repository")
 			log.GitLogger.Fatal(2, "User %s has no right to write repository %s", user.Name, repoPath)
 		}
-	case isRead:
-		repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
-		if err != nil {
-			if err == models.ErrRepoNotExist {
-				println("Gogs: given repository does not exist")
-				log.GitLogger.Fatal(2, "Repository does not exist: %s/%s", repoUser.Name, repoName)
-			}
-			println("Gogs: internal error:", err.Error())
-			log.GitLogger.Fatal(2, "Fail to get repository: %v", err)
-		}
 
+		if repo.IsMirror {
+			println("You can't write to a mirror repository")
+			log.GitLogger.Fatal(2, "User %s tried to write to a mirror repository %s", user.Name, repoPath)
+		}
+	case isRead:
 		if !repo.IsPrivate {
 			break
 		}
 
-		has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.READABLE)
+		has, err := models.HasAccess(user, repo, models.ACCESS_MODE_READ)
 		if err != nil {
 			println("Gogs: internal error:", err.Error())
 			log.GitLogger.Fatal(2, "Fail to check read access:", err)

+ 2 - 2
cmd/web.go

@@ -318,7 +318,7 @@ func runWeb(ctx *cli.Context) {
 		m.Get("/template/*", dev.TemplatePreview)
 	}
 
-	reqTrueOwner := middleware.RequireTrueOwner()
+	reqAdmin := middleware.RequireAdmin()
 
 	// Organization.
 	m.Group("/org", func() {
@@ -393,7 +393,7 @@ func runWeb(ctx *cli.Context) {
 				m.Post("/:name", repo.GitHooksEditPost)
 			}, middleware.GitHookService())
 		})
-	}, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner)
+	}, reqSignIn, middleware.RepoAssignment(true), reqAdmin)
 
 	m.Group("/:username/:reponame", func() {
 		m.Get("/action/:action", repo.Action)

+ 1 - 1
gogs.go

@@ -17,7 +17,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.5.14.0222 Beta"
+const APP_VER = "0.5.16.0228 Beta"
 
 func init() {
 	runtime.GOMAXPROCS(runtime.NumCPU())

+ 164 - 50
models/access.go

@@ -5,76 +5,190 @@
 package models
 
 import (
-	"strings"
-	"time"
-
-	"github.com/go-xorm/xorm"
+	"fmt"
 )
 
-type AccessType int
+type AccessMode int
 
 const (
-	READABLE AccessType = iota + 1
-	WRITABLE
+	ACCESS_MODE_NONE AccessMode = iota
+	ACCESS_MODE_READ
+	ACCESS_MODE_WRITE
+	ACCESS_MODE_ADMIN
+	ACCESS_MODE_OWNER
 )
 
-// Access represents the accessibility of user to repository.
+// 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
-	UserName string     `xorm:"UNIQUE(s)"`
-	RepoName string     `xorm:"UNIQUE(s)"` // <user name>/<repo name>
-	Mode     AccessType `xorm:"UNIQUE(s)"`
-	Created  time.Time  `xorm:"CREATED"`
+	ID     int64 `xorm:"pk autoincr"`
+	UserID int64 `xorm:"UNIQUE(s)"`
+	RepoID int64 `xorm:"UNIQUE(s)"`
+	Mode   AccessMode
+}
+
+func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) {
+	mode := ACCESS_MODE_NONE
+	if !repo.IsPrivate {
+		mode = ACCESS_MODE_READ
+	}
+
+	if u != nil {
+		if u.Id == repo.OwnerId {
+			return ACCESS_MODE_OWNER, nil
+		}
+
+		a := &Access{UserID: u.Id, RepoID: repo.Id}
+		if has, err := e.Get(a); !has || err != nil {
+			return mode, err
+		}
+		return a.Mode, nil
+	}
+
+	return mode, nil
 }
 
-// AddAccess adds new access record.
-func AddAccess(access *Access) error {
-	access.UserName = strings.ToLower(access.UserName)
-	access.RepoName = strings.ToLower(access.RepoName)
-	_, err := x.Insert(access)
-	return err
+// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
+// user does not have access. User can be nil!
+func AccessLevel(u *User, repo *Repository) (AccessMode, error) {
+	return accessLevel(x, u, repo)
 }
 
-// UpdateAccess updates access information.
-func UpdateAccess(access *Access) error {
-	access.UserName = strings.ToLower(access.UserName)
-	access.RepoName = strings.ToLower(access.RepoName)
-	_, err := x.Id(access.Id).Update(access)
-	return err
+func hasAccess(e Engine, u *User, repo *Repository, testMode AccessMode) (bool, error) {
+	mode, err := accessLevel(e, u, repo)
+	return testMode <= mode, err
 }
 
-// DeleteAccess deletes access record.
-func DeleteAccess(access *Access) error {
-	_, err := x.Delete(access)
-	return err
+// HasAccess returns true if someone has the request access level. User can be nil!
+func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) {
+	return hasAccess(x, u, repo, testMode)
 }
 
-// UpdateAccess updates access information with session for rolling back.
-func UpdateAccessWithSession(sess *xorm.Session, access *Access) error {
-	if _, err := sess.Id(access.Id).Update(access); err != nil {
-		sess.Rollback()
-		return err
+// GetAccessibleRepositories finds all repositories where a user has access to,
+// besides his own.
+func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
+	accesses := make([]*Access, 0, 10)
+	if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
+		return nil, err
 	}
-	return nil
+
+	repos := make(map[*Repository]AccessMode, len(accesses))
+	for _, access := range accesses {
+		repo, err := GetRepositoryById(access.RepoID)
+		if err != nil {
+			return nil, err
+		}
+		if err = repo.GetOwner(); err != nil {
+			return nil, err
+		} else if repo.OwnerId == u.Id {
+			continue
+		}
+		repos[repo] = access.Mode
+	}
+
+	// FIXME: should we generate an ordered list here? Random looks weird.
+	return repos, nil
+}
+
+func maxAccessMode(modes ...AccessMode) AccessMode {
+	max := ACCESS_MODE_NONE
+	for _, mode := range modes {
+		if mode > max {
+			max = mode
+		}
+	}
+	return max
 }
 
-// HasAccess returns true if someone can read or write to given repository.
-// The repoName should be in format <username>/<reponame>.
-func HasAccess(uname, repoName string, mode AccessType) (bool, error) {
-	if len(repoName) == 0 {
-		return false, nil
+// FIXME: do corss-comparison so reduce deletions and additions to the minimum?
+func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) (err error) {
+	minMode := ACCESS_MODE_READ
+	if !repo.IsPrivate {
+		minMode = ACCESS_MODE_WRITE
 	}
-	access := &Access{
-		UserName: strings.ToLower(uname),
-		RepoName: strings.ToLower(repoName),
+
+	newAccesses := make([]Access, 0, len(accessMap))
+	for userID, mode := range accessMap {
+		if mode < minMode {
+			continue
+		}
+		newAccesses = append(newAccesses, Access{
+			UserID: userID,
+			RepoID: repo.Id,
+			Mode:   mode,
+		})
 	}
-	has, err := x.Get(access)
+
+	// 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
+}
+
+// FIXME: should be able to have read-only access.
+// Give all collaborators write access.
+func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error {
+	collaborators, err := repo.getCollaborators(e)
 	if err != nil {
-		return false, err
-	} else if !has {
-		return false, nil
-	} else if mode > access.Mode {
-		return false, nil
+		return fmt.Errorf("getCollaborators: %v", err)
+	}
+	for _, c := range collaborators {
+		accessMap[c.Id] = ACCESS_MODE_WRITE
+	}
+	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.refreshCollaboratorAccesses(e, accessMap); err != nil {
+		return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
+	}
+
+	if err = repo.getOwner(e); err != nil {
+		return err
+	}
+	if repo.Owner.IsOrganization() {
+		if err = repo.Owner.getTeams(e); err != nil {
+			return err
+		}
+
+		for _, t := range repo.Owner.Teams {
+			if t.ID == ignTeamID {
+				continue
+			}
+			if t.IsOwnerTeam() {
+				t.Authorize = ACCESS_MODE_OWNER
+			}
+
+			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 {
+	accessMap := make(map[int64]AccessMode, 20)
+	if err := repo.refreshCollaboratorAccesses(e, accessMap); err != nil {
+		return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
 	}
-	return true, nil
+	return repo.refreshAccesses(e, accessMap)
+}
+
+// RecalculateAccesses recalculates all accesses for repository.
+func (r *Repository) RecalculateAccesses() error {
+	return r.recalculateAccesses(x)
 }

+ 33 - 21
models/action.go

@@ -434,46 +434,58 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
 	return nil
 }
 
-// NewRepoAction adds new action for creating repository.
-func NewRepoAction(u *User, repo *Repository) (err error) {
-	if err = NotifyWatchers(&Action{ActUserId: u.Id, ActUserName: u.Name, ActEmail: u.Email,
-		OpType: CREATE_REPO, RepoId: repo.Id, RepoUserName: repo.Owner.Name, RepoName: repo.Name,
-		IsPrivate: repo.IsPrivate}); err != nil {
-		log.Error(4, "NotifyWatchers: %d/%s", u.Id, repo.Name)
-		return err
+func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
+	if err = notifyWatchers(e, &Action{
+		ActUserId:    u.Id,
+		ActUserName:  u.Name,
+		ActEmail:     u.Email,
+		OpType:       CREATE_REPO,
+		RepoId:       repo.Id,
+		RepoUserName: repo.Owner.Name,
+		RepoName:     repo.Name,
+		IsPrivate:    repo.IsPrivate}); err != nil {
+		return fmt.Errorf("notify watchers '%d/%s'", u.Id, repo.Id)
 	}
 
 	log.Trace("action.NewRepoAction: %s/%s", u.Name, repo.Name)
 	return err
 }
 
-// TransferRepoAction adds new action for transferring repository.
-func TransferRepoAction(u, newUser *User, repo *Repository) (err error) {
+// NewRepoAction adds new action for creating repository.
+func NewRepoAction(u *User, repo *Repository) (err error) {
+	return newRepoAction(x, u, repo)
+}
+
+func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repository) (err error) {
 	action := &Action{
-		ActUserId:    u.Id,
-		ActUserName:  u.Name,
-		ActEmail:     u.Email,
+		ActUserId:    actUser.Id,
+		ActUserName:  actUser.Name,
+		ActEmail:     actUser.Email,
 		OpType:       TRANSFER_REPO,
 		RepoId:       repo.Id,
-		RepoUserName: newUser.Name,
+		RepoUserName: newOwner.Name,
 		RepoName:     repo.Name,
 		IsPrivate:    repo.IsPrivate,
-		Content:      path.Join(repo.Owner.LowerName, repo.LowerName),
+		Content:      path.Join(oldOwner.LowerName, repo.LowerName),
 	}
-	if err = NotifyWatchers(action); err != nil {
-		log.Error(4, "NotifyWatchers: %d/%s", u.Id, repo.Name)
-		return err
+	if err = notifyWatchers(e, action); err != nil {
+		return fmt.Errorf("notify watchers '%d/%s'", actUser.Id, repo.Id)
 	}
 
 	// Remove watch for organization.
 	if repo.Owner.IsOrganization() {
-		if err = WatchRepo(repo.Owner.Id, repo.Id, false); err != nil {
-			log.Error(4, "WatchRepo", err)
+		if err = watchRepo(e, repo.Owner.Id, repo.Id, false); err != nil {
+			return fmt.Errorf("watch repository: %v", err)
 		}
 	}
 
-	log.Trace("action.TransferRepoAction: %s/%s", u.Name, repo.Name)
-	return err
+	log.Trace("action.TransferRepoAction: %s/%s", actUser.Name, repo.Name)
+	return nil
+}
+
+// TransferRepoAction adds new action for transferring repository.
+func TransferRepoAction(actUser, oldOwner, newOwner *User, repo *Repository) (err error) {
+	return transferRepoAction(x, actUser, oldOwner, newOwner, repo)
 }
 
 // GetFeeds returns action list of given user in given context.

+ 12 - 9
models/issue.go

@@ -282,30 +282,33 @@ type IssueUser struct {
 }
 
 // NewIssueUserPairs adds new issue-user pairs for new issue of repository.
-func NewIssueUserPairs(rid, iid, oid, pid, aid int64, repoName string) (err error) {
-	iu := &IssueUser{IssueId: iid, RepoId: rid}
-
-	us, err := GetCollaborators(repoName)
+func NewIssueUserPairs(repo *Repository, issueID, orgID, posterID, assigneeID int64) (err error) {
+	users, err := repo.GetCollaborators()
 	if err != nil {
 		return err
 	}
 
+	iu := &IssueUser{
+		IssueId: issueID,
+		RepoId:  repo.Id,
+	}
+
 	isNeedAddPoster := true
-	for _, u := range us {
+	for _, u := range users {
 		iu.Uid = u.Id
-		iu.IsPoster = iu.Uid == pid
+		iu.IsPoster = iu.Uid == posterID
 		if isNeedAddPoster && iu.IsPoster {
 			isNeedAddPoster = false
 		}
-		iu.IsAssigned = iu.Uid == aid
+		iu.IsAssigned = iu.Uid == assigneeID
 		if _, err = x.Insert(iu); err != nil {
 			return err
 		}
 	}
 	if isNeedAddPoster {
-		iu.Uid = pid
+		iu.Uid = posterID
 		iu.IsPoster = true
-		iu.IsAssigned = iu.Uid == aid
+		iu.IsAssigned = iu.Uid == assigneeID
 		if _, err = x.Insert(iu); err != nil {
 			return err
 		}

+ 331 - 13
models/migrations/migrations.go

@@ -1,12 +1,44 @@
+// 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 (
-	"errors"
+	"fmt"
+	"strings"
+	"time"
 
+	"github.com/Unknwon/com"
 	"github.com/go-xorm/xorm"
+
+	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/setting"
 )
 
-type migration func(*xorm.Engine) error
+const _MIN_DB_VER = 0
+
+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 {
@@ -15,30 +47,55 @@ type Version struct {
 }
 
 // This is a sequence of migrations. Add new migrations to the bottom of the list.
-// If you want to "retire" a migration, replace it with "expiredMigration"
-var migrations = []migration{}
+// If you want to "retire" a migration, remove it from the top of the list and
+// update _MIN_VER_DB accordingly
+var migrations = []Migration{
+	NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1
+	NewMigration("make authorize 4 if team is owners", ownerTeamUpdate),       // V1 -> V2
+	NewMigration("refactor access table to use id's", accessRefactor),         // V2 -> V3
+	NewMigration("generate team-repo from team", teamToTeamRepo),              // V3 -> V4
+}
 
 // Migrate database to current version
 func Migrate(x *xorm.Engine) error {
 	if err := x.Sync(new(Version)); err != nil {
-		return err
+		return fmt.Errorf("sync: %v", err)
 	}
 
 	currentVersion := &Version{Id: 1}
 	has, err := x.Get(currentVersion)
 	if err != nil {
-		return err
+		return fmt.Errorf("get: %v", err)
 	} else if !has {
-		if _, err = x.InsertOne(currentVersion); err != nil {
+		// If the user table does not exist it is a fresh installation and we
+		// can skip all migrations.
+		needsMigration, err := x.IsTableExist("user")
+		if err != nil {
 			return err
 		}
+		if needsMigration {
+			isEmpty, err := x.IsTableEmpty("user")
+			if err != nil {
+				return err
+			}
+			// If the user table is empty it is a fresh installation and we can
+			// skip all migrations.
+			needsMigration = !isEmpty
+		}
+		if !needsMigration {
+			currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
+		}
+
+		if _, err = x.InsertOne(currentVersion); err != nil {
+			return fmt.Errorf("insert: %v", err)
+		}
 	}
 
 	v := currentVersion.Version
-
-	for i, migration := range migrations[v:] {
-		if err = migration(x); err != nil {
-			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 {
@@ -48,6 +105,267 @@ func Migrate(x *xorm.Engine) error {
 	return nil
 }
 
-func expiredMigration(x *xorm.Engine) error {
-	return errors.New("You are migrating from a too old gogs version")
+func sessionRelease(sess *xorm.Session) {
+	if !sess.IsCommitedOrRollbacked {
+		sess.Rollback()
+	}
+	sess.Close()
+}
+
+func accessToCollaboration(x *xorm.Engine) (err error) {
+	type Collaboration struct {
+		ID      int64 `xorm:"pk autoincr"`
+		RepoID  int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+		UserID  int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
+		Created time.Time
+	}
+
+	if err = x.Sync(new(Collaboration)); err != nil {
+		return fmt.Errorf("sync: %v", err)
+	}
+
+	results, err := x.Query("SELECT u.id AS `uid`, a.repo_name AS `repo`, a.mode AS `mode`, a.created as `created` FROM `access` a JOIN `user` u ON a.user_name=u.lower_name")
+	if err != nil {
+		return err
+	}
+
+	sess := x.NewSession()
+	defer sessionRelease(sess)
+	if err = sess.Begin(); err != nil {
+		return err
+	}
+
+	offset := strings.Split(time.Now().String(), " ")[2]
+	for _, result := range results {
+		mode := com.StrTo(result["mode"]).MustInt64()
+		// Collaborators must have write access.
+		if mode < 2 {
+			continue
+		}
+
+		userID := com.StrTo(result["uid"]).MustInt64()
+		repoRefName := string(result["repo"])
+
+		var created time.Time
+		switch {
+		case setting.UseSQLite3:
+			created, _ = time.Parse(time.RFC3339, string(result["created"]))
+		case setting.UseMySQL:
+			created, _ = time.Parse("2006-01-02 15:04:05-0700", string(result["created"])+offset)
+		case setting.UsePostgreSQL:
+			created, _ = time.Parse("2006-01-02T15:04:05Z-0700", string(result["created"])+offset)
+		}
+
+		// find owner of repository
+		parts := strings.SplitN(repoRefName, "/", 2)
+		ownerName := parts[0]
+		repoName := parts[1]
+
+		results, err := sess.Query("SELECT u.id as `uid`, ou.uid as `memberid` FROM `user` u LEFT JOIN org_user ou ON ou.org_id=u.id WHERE u.lower_name=?", ownerName)
+		if err != nil {
+			return err
+		}
+		if len(results) < 1 {
+			continue
+		}
+
+		ownerID := com.StrTo(results[0]["uid"]).MustInt64()
+		if ownerID == userID {
+			continue
+		}
+
+		// test if user is member of owning organization
+		isMember := false
+		for _, member := range results {
+			memberID := com.StrTo(member["memberid"]).MustInt64()
+			// We can skip all cases that a user is member of the owning organization
+			if memberID == userID {
+				isMember = true
+			}
+		}
+		if isMember {
+			continue
+		}
+
+		results, err = sess.Query("SELECT id FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName)
+		if err != nil {
+			return err
+		} else if len(results) < 1 {
+			continue
+		}
+
+		collaboration := &Collaboration{
+			UserID: userID,
+			RepoID: com.StrTo(results[0]["id"]).MustInt64(),
+		}
+		has, err := sess.Get(collaboration)
+		if err != nil {
+			return err
+		} else if has {
+			continue
+		}
+
+		collaboration.Created = created
+		if _, err = sess.InsertOne(collaboration); err != nil {
+			return err
+		}
+	}
+
+	return sess.Commit()
+}
+
+func ownerTeamUpdate(x *xorm.Engine) (err error) {
+	if _, err := x.Exec("UPDATE `team` SET authorize=4 WHERE lower_name=?", "owners"); err != nil {
+		return fmt.Errorf("update owner team table: %v", err)
+	}
+	return nil
+}
+
+func accessRefactor(x *xorm.Engine) (err error) {
+	type (
+		AccessMode int
+		Access     struct {
+			ID     int64 `xorm:"pk autoincr"`
+			UserID int64 `xorm:"UNIQUE(s)"`
+			RepoID int64 `xorm:"UNIQUE(s)"`
+			Mode   AccessMode
+		}
+		UserRepo struct {
+			UserID int64
+			RepoID int64
+		}
+	)
+
+	// We consiously don't start a session yet as we make only reads for now, no writes
+
+	accessMap := make(map[UserRepo]AccessMode, 50)
+
+	results, err := x.Query("SELECT r.id AS `repo_id`, r.is_private AS `is_private`, r.owner_id AS `owner_id`, u.type AS `owner_type` FROM `repository` r LEFT JOIN `user` u ON r.owner_id=u.id")
+	if err != nil {
+		return fmt.Errorf("select repositories: %v", err)
+	}
+	for _, repo := range results {
+		repoID := com.StrTo(repo["repo_id"]).MustInt64()
+		isPrivate := com.StrTo(repo["is_private"]).MustInt() > 0
+		ownerID := com.StrTo(repo["owner_id"]).MustInt64()
+		ownerIsOrganization := com.StrTo(repo["owner_type"]).MustInt() > 0
+
+		results, err := x.Query("SELECT `user_id` FROM `collaboration` WHERE repo_id=?", repoID)
+		if err != nil {
+			return fmt.Errorf("select collaborators: %v", err)
+		}
+		for _, user := range results {
+			userID := com.StrTo(user["user_id"]).MustInt64()
+			accessMap[UserRepo{userID, repoID}] = 2 // WRITE ACCESS
+		}
+
+		if !ownerIsOrganization {
+			continue
+		}
+
+		// The minimum level to add a new access record,
+		// because public repository has implicit open access.
+		minAccessLevel := AccessMode(0)
+		if !isPrivate {
+			minAccessLevel = 1
+		}
+
+		repoString := "$" + string(repo["repo_id"]) + "|"
+
+		results, err = x.Query("SELECT `id`,`authorize`,`repo_ids` FROM `team` WHERE org_id=? AND authorize>? ORDER BY `authorize` ASC", ownerID, int(minAccessLevel))
+		if err != nil {
+			return fmt.Errorf("select teams from org: %v", err)
+		}
+
+		for _, team := range results {
+			if !strings.Contains(string(team["repo_ids"]), repoString) {
+				continue
+			}
+			teamID := com.StrTo(team["id"]).MustInt64()
+			mode := AccessMode(com.StrTo(team["authorize"]).MustInt())
+
+			results, err := x.Query("SELECT `uid` FROM `team_user` WHERE team_id=?", teamID)
+			if err != nil {
+				return fmt.Errorf("select users from team: %v", err)
+			}
+			for _, user := range results {
+				userID := com.StrTo(user["user_id"]).MustInt64()
+				accessMap[UserRepo{userID, repoID}] = mode
+			}
+		}
+	}
+
+	// Drop table can't be in a session (at least not in sqlite)
+	if _, err = x.Exec("DROP TABLE `access`"); err != nil {
+		return fmt.Errorf("drop access table: %v", err)
+	}
+
+	// Now we start writing so we make a session
+	sess := x.NewSession()
+	defer sessionRelease(sess)
+	if err = sess.Begin(); err != nil {
+		return err
+	}
+
+	if err = sess.Sync2(new(Access)); err != nil {
+		return fmt.Errorf("sync: %v", err)
+	}
+
+	accesses := make([]*Access, 0, len(accessMap))
+	for ur, mode := range accessMap {
+		accesses = append(accesses, &Access{UserID: ur.UserID, RepoID: ur.RepoID, Mode: mode})
+	}
+
+	if _, err = sess.Insert(accesses); err != nil {
+		return fmt.Errorf("insert accesses: %v", err)
+	}
+
+	return sess.Commit()
+}
+
+func teamToTeamRepo(x *xorm.Engine) error {
+	type TeamRepo struct {
+		ID     int64 `xorm:"pk autoincr"`
+		OrgID  int64 `xorm:"INDEX"`
+		TeamID int64 `xorm:"UNIQUE(s)"`
+		RepoID int64 `xorm:"UNIQUE(s)"`
+	}
+
+	teamRepos := make([]*TeamRepo, 0, 50)
+
+	results, err := x.Query("SELECT `id`,`org_id`,`repo_ids` FROM `team`")
+	if err != nil {
+		return fmt.Errorf("select teams: %v", err)
+	}
+	for _, team := range results {
+		orgID := com.StrTo(team["org_id"]).MustInt64()
+		teamID := com.StrTo(team["id"]).MustInt64()
+
+		for _, idStr := range strings.Split(string(team["repo_ids"]), "|") {
+			repoID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
+			if repoID == 0 {
+				continue
+			}
+
+			teamRepos = append(teamRepos, &TeamRepo{
+				OrgID:  orgID,
+				TeamID: teamID,
+				RepoID: repoID,
+			})
+		}
+	}
+
+	sess := x.NewSession()
+	defer sessionRelease(sess)
+	if err = sess.Begin(); err != nil {
+		return err
+	}
+
+	if err = sess.Sync2(new(TeamRepo)); err != nil {
+		return fmt.Errorf("sync: %v", err)
+	} else if _, err = sess.Insert(teamRepos); err != nil {
+		return fmt.Errorf("insert team-repos: %v", err)
+	}
+
+	return sess.Commit()
 }

+ 31 - 10
models/models.go

@@ -12,10 +12,11 @@ import (
 	"strings"
 
 	_ "github.com/go-sql-driver/mysql"
+	"github.com/go-xorm/core"
 	"github.com/go-xorm/xorm"
 	_ "github.com/lib/pq"
 
-	// "github.com/gogits/gogs/models/migrations"
+	"github.com/gogits/gogs/models/migrations"
 	"github.com/gogits/gogs/modules/setting"
 )
 
@@ -23,12 +24,22 @@ import (
 type Engine interface {
 	Delete(interface{}) (int64, error)
 	Exec(string, ...interface{}) (sql.Result, error)
+	Find(interface{}, ...interface{}) error
 	Get(interface{}) (bool, error)
 	Insert(...interface{}) (int64, error)
+	InsertOne(interface{}) (int64, error)
 	Id(interface{}) *xorm.Session
+	Sql(string, ...interface{}) *xorm.Session
 	Where(string, ...interface{}) *xorm.Session
 }
 
+func sessionRelease(sess *xorm.Session) {
+	if !sess.IsCommitedOrRollbacked {
+		sess.Rollback()
+	}
+	sess.Close()
+}
+
 var (
 	x         *xorm.Engine
 	tables    []interface{}
@@ -39,24 +50,30 @@ var (
 	}
 
 	EnableSQLite3 bool
-	UseSQLite3    bool
 )
 
 func init() {
 	tables = append(tables,
-		new(User), new(PublicKey), new(Follow), new(Oauth2), new(AccessToken),
-		new(Repository), new(Watch), new(Star), new(Action), new(Access),
+		new(User), new(PublicKey), new(Oauth2), new(AccessToken),
+		new(Repository), new(Collaboration), new(Access),
+		new(Watch), new(Star), new(Follow), new(Action),
 		new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone),
 		new(Mirror), new(Release), new(LoginSource), new(Webhook),
-		new(UpdateTask), new(HookTask), new(Team), new(OrgUser), new(TeamUser),
+		new(UpdateTask), new(HookTask),
+		new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
 		new(Notice), new(EmailAddress))
 }
 
 func LoadModelsConfig() {
 	sec := setting.Cfg.Section("database")
 	DbCfg.Type = sec.Key("DB_TYPE").String()
-	if DbCfg.Type == "sqlite3" {
-		UseSQLite3 = true
+	switch DbCfg.Type {
+	case "sqlite3":
+		setting.UseSQLite3 = true
+	case "mysql":
+		setting.UseMySQL = true
+	case "postgres":
+		setting.UsePostgreSQL = true
 	}
 	DbCfg.Host = sec.Key("HOST").String()
 	DbCfg.Name = sec.Key("NAME").String()
@@ -103,6 +120,7 @@ func NewTestEngine(x *xorm.Engine) (err error) {
 		return fmt.Errorf("connect to database: %v", err)
 	}
 
+	x.SetMapper(core.GonicMapper{})
 	return x.Sync(tables...)
 }
 
@@ -112,6 +130,8 @@ func SetEngine() (err error) {
 		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.
 	logPath := path.Join(setting.LogRootPath, "xorm.log")
@@ -136,13 +156,14 @@ func NewEngine() (err error) {
 		return err
 	}
 
-	// if err = migrations.Migrate(x); 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 database struct error: %v\n", err)
 	}
+
 	return nil
 }
 

+ 274 - 355
models/org.go

@@ -8,11 +8,8 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"path"
 	"strings"
 
-	"github.com/Unknwon/com"
-
 	"github.com/gogits/gogs/modules/base"
 )
 
@@ -31,22 +28,34 @@ func (org *User) IsOwnedBy(uid int64) bool {
 
 // IsOrgMember returns true if given user is member of organization.
 func (org *User) IsOrgMember(uid int64) bool {
-	return IsOrganizationMember(org.Id, uid)
+	return org.IsOrganization() && IsOrganizationMember(org.Id, uid)
+}
+
+func (org *User) getTeam(e Engine, name string) (*Team, error) {
+	return getTeam(e, org.Id, name)
 }
 
 // GetTeam returns named team of organization.
 func (org *User) GetTeam(name string) (*Team, error) {
-	return GetTeam(org.Id, name)
+	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.GetTeam(OWNER_TEAM)
+	return org.getOwnerTeam(x)
+}
+
+func (org *User) getTeams(e Engine) error {
+	return e.Where("org_id=?", org.Id).Find(&org.Teams)
 }
 
 // GetTeams returns all teams that belong to organization.
 func (org *User) GetTeams() error {
-	return x.Where("org_id=?", org.Id).Find(&org.Teams)
+	return org.getTeams(x)
 }
 
 // GetMembers returns all members of organization.
@@ -76,6 +85,15 @@ 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)
+}
+
 // IsOrgEmailUsed returns true if the e-mail has been used in organization account.
 func IsOrgEmailUsed(email string) (bool, error) {
 	if len(email) == 0 {
@@ -116,53 +134,48 @@ func CreateOrganization(org, owner *User) (*User, error) {
 	org.NumMembers = 1
 
 	sess := x.NewSession()
-	defer sess.Close()
+	defer sessionRelease(sess)
 	if err = sess.Begin(); err != nil {
 		return nil, err
 	}
 
 	if _, err = sess.Insert(org); err != nil {
-		sess.Rollback()
-		return nil, err
-	}
-
-	if err = os.MkdirAll(UserPath(org.Name), os.ModePerm); err != nil {
-		sess.Rollback()
 		return nil, err
 	}
 
 	// Create default owner team.
 	t := &Team{
-		OrgId:      org.Id,
+		OrgID:      org.Id,
 		LowerName:  strings.ToLower(OWNER_TEAM),
 		Name:       OWNER_TEAM,
-		Authorize:  ORG_ADMIN,
+		Authorize:  ACCESS_MODE_OWNER,
 		NumMembers: 1,
 	}
 	if _, err = sess.Insert(t); err != nil {
-		sess.Rollback()
 		return nil, err
 	}
 
 	// Add initial creator to organization and owner team.
 	ou := &OrgUser{
 		Uid:      owner.Id,
-		OrgId:    org.Id,
+		OrgID:    org.Id,
 		IsOwner:  true,
 		NumTeams: 1,
 	}
 	if _, err = sess.Insert(ou); err != nil {
-		sess.Rollback()
 		return nil, err
 	}
 
 	tu := &TeamUser{
 		Uid:    owner.Id,
-		OrgId:  org.Id,
-		TeamId: t.Id,
+		OrgID:  org.Id,
+		TeamID: t.ID,
 	}
 	if _, err = sess.Insert(tu); err != nil {
-		sess.Rollback()
+		return nil, err
+	}
+
+	if err = os.MkdirAll(UserPath(org.Name), os.ModePerm); err != nil {
 		return nil, err
 	}
 
@@ -213,15 +226,15 @@ func DeleteOrganization(org *User) (err error) {
 		return err
 	}
 
-	if _, err = sess.Delete(&Team{OrgId: org.Id}); err != nil {
+	if _, err = sess.Delete(&Team{OrgID: org.Id}); err != nil {
 		sess.Rollback()
 		return err
 	}
-	if _, err = sess.Delete(&OrgUser{OrgId: org.Id}); err != nil {
+	if _, err = sess.Delete(&OrgUser{OrgID: org.Id}); err != nil {
 		sess.Rollback()
 		return err
 	}
-	if _, err = sess.Delete(&TeamUser{OrgId: org.Id}); err != nil {
+	if _, err = sess.Delete(&TeamUser{OrgID: org.Id}); err != nil {
 		sess.Rollback()
 		return err
 	}
@@ -237,9 +250,9 @@ func DeleteOrganization(org *User) (err error) {
 
 // OrgUser represents an organization-user relation.
 type OrgUser struct {
-	Id       int64
+	ID       int64 `xorm:"pk autoincr"`
 	Uid      int64 `xorm:"INDEX UNIQUE(s)"`
-	OrgId    int64 `xorm:"INDEX UNIQUE(s)"`
+	OrgID    int64 `xorm:"INDEX UNIQUE(s)"`
 	IsPublic bool
 	IsOwner  bool
 	NumTeams int
@@ -288,7 +301,7 @@ func ChangeOrgUserStatus(orgId, uid int64, public bool) error {
 	}
 
 	ou.IsPublic = public
-	_, err = x.Id(ou.Id).AllCols().Update(ou)
+	_, err = x.Id(ou.ID).AllCols().Update(ou)
 	return err
 }
 
@@ -306,7 +319,7 @@ func AddOrgUser(orgId, uid int64) error {
 
 	ou := &OrgUser{
 		Uid:   uid,
-		OrgId: orgId,
+		OrgID: orgId,
 	}
 
 	if _, err := sess.Insert(ou); err != nil {
@@ -357,10 +370,10 @@ func RemoveOrgUser(orgId, uid int64) error {
 		return err
 	}
 
-	if _, err := sess.Id(ou.Id).Delete(ou); err != nil {
+	if _, err := sess.Id(ou.ID).Delete(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 {
+	} else if _, err = sess.Exec("UPDATE `user` SET num_members=num_members-1 WHERE id = ?", orgId); err != nil {
 		sess.Rollback()
 		return err
 	}
@@ -371,10 +384,10 @@ func RemoveOrgUser(orgId, uid int64) error {
 		return err
 	}
 	access := &Access{
-		UserName: u.LowerName,
+		UserID: u.Id,
 	}
 	for _, repo := range org.Repos {
-		access.RepoName = path.Join(org.LowerName, repo.LowerName)
+		access.RepoID = repo.Id
 		if _, err = sess.Delete(access); err != nil {
 			sess.Rollback()
 			return err
@@ -390,7 +403,7 @@ func RemoveOrgUser(orgId, uid int64) error {
 		return err
 	}
 	for _, t := range ts {
-		if err = removeTeamMember(sess, org.Id, t.Id, u.Id); err != nil {
+		if err = removeTeamMember(sess, org.Id, t.ID, u.Id); err != nil {
 			return err
 		}
 	}
@@ -405,32 +418,16 @@ func RemoveOrgUser(orgId, uid int64) error {
 //   |____| \___  >____  /__|_|  /
 //              \/     \/      \/
 
-type AuthorizeType int
-
-const (
-	ORG_READABLE AuthorizeType = iota + 1
-	ORG_WRITABLE
-	ORG_ADMIN
-)
-
-func AuthorizeToAccessType(auth AuthorizeType) AccessType {
-	if auth == ORG_READABLE {
-		return READABLE
-	}
-	return WRITABLE
-}
-
 const OWNER_TEAM = "Owners"
 
 // Team represents a organization team.
 type Team struct {
-	Id          int64
-	OrgId       int64 `xorm:"INDEX"`
+	ID          int64 `xorm:"pk autoincr"`
+	OrgID       int64 `xorm:"INDEX"`
 	LowerName   string
 	Name        string
 	Description string
-	Authorize   AuthorizeType
-	RepoIds     string        `xorm:"TEXT"`
+	Authorize   AccessMode
 	Repos       []*Repository `xorm:"-"`
 	Members     []*User       `xorm:"-"`
 	NumRepos    int
@@ -444,60 +441,80 @@ func (t *Team) IsOwnerTeam() bool {
 
 // IsTeamMember returns true if given user is a member of team.
 func (t *Team) IsMember(uid int64) bool {
-	return IsTeamMember(t.OrgId, t.Id, uid)
+	return IsTeamMember(t.OrgID, t.ID, uid)
 }
 
-// GetRepositories returns all repositories in team of organization.
-func (t *Team) GetRepositories() error {
-	idStrs := strings.Split(t.RepoIds, "|")
-	t.Repos = make([]*Repository, 0, len(idStrs))
-	for _, str := range idStrs {
-		if len(str) == 0 {
-			continue
-		}
-		id := com.StrTo(str[1:]).MustInt64()
-		if id == 0 {
-			continue
-		}
-		repo, err := GetRepositoryById(id)
+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 err
+			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) {
-	t.Members, err = GetTeamMembers(t.OrgId, t.Id)
-	return err
+	return t.getMembers(x)
 }
 
 // AddMember adds new member to team of organization.
 func (t *Team) AddMember(uid int64) error {
-	return AddTeamMember(t.OrgId, t.Id, uid)
+	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)
+	return RemoveTeamMember(t.OrgID, t.ID, uid)
 }
 
-// addAccessWithAuthorize inserts or updates access with given mode.
-func addAccessWithAuthorize(e Engine, access *Access, mode AccessType) error {
-	has, err := e.Get(access)
-	if err != nil {
-		return fmt.Errorf("fail to get access: %v", err)
+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 HasTeamRepo(t.OrgID, t.ID, 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
 	}
-	access.Mode = mode
-	if has {
-		if _, err = e.Id(access.Id).Update(access); err != nil {
-			return fmt.Errorf("fail to update access: %v", err)
-		}
-	} else {
-		if _, err = e.Insert(access); err != nil {
-			return fmt.Errorf("fail to insert access: %v", 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
@@ -505,119 +522,82 @@ func addAccessWithAuthorize(e Engine, access *Access, mode AccessType) error {
 
 // AddRepository adds new repository to team of organization.
 func (t *Team) AddRepository(repo *Repository) (err error) {
-	idStr := "$" + com.ToStr(repo.Id) + "|"
-	if repo.OwnerId != t.OrgId {
-		return errors.New("Repository not belong to organization")
-	} else if strings.Contains(t.RepoIds, idStr) {
+	if repo.OwnerId != t.OrgID {
+		return errors.New("Repository does not belong to organization")
+	} else if t.HasRepository(repo.Id) {
 		return nil
 	}
 
-	if err = repo.GetOwner(); err != nil {
+	sess := x.NewSession()
+	defer sessionRelease(sess)
+	if err = sess.Begin(); err != nil {
 		return err
-	} else if err = t.GetMembers(); err != nil {
+	}
+
+	if err = t.addRepository(sess, repo); err != nil {
 		return err
 	}
 
-	sess := x.NewSession()
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	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++
-	t.RepoIds += idStr
-	if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
-		sess.Rollback()
+	t.NumRepos--
+	if _, err = e.Id(t.ID).AllCols().Update(t); err != nil {
 		return err
 	}
 
-	// Give access to team members.
-	mode := AuthorizeToAccessType(t.Authorize)
+	// 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 _, u := range t.Members {
-		auth, err := getHighestAuthorize(sess, t.OrgId, u.Id, repo.Id, t.Id)
+		has, err := hasAccess(e, u, repo, ACCESS_MODE_READ)
 		if err != nil {
-			sess.Rollback()
 			return err
+		} else if has {
+			continue
 		}
 
-		access := &Access{
-			UserName: u.LowerName,
-			RepoName: path.Join(repo.Owner.LowerName, repo.LowerName),
-		}
-		if auth < t.Authorize {
-			if err = addAccessWithAuthorize(sess, access, mode); err != nil {
-				sess.Rollback()
-				return err
-			}
-		}
-		if err = watchRepo(sess, u.Id, repo.Id, true); err != nil {
-			sess.Rollback()
+		if err = watchRepo(e, u.Id, repo.Id, false); err != nil {
 			return err
 		}
 	}
-	return sess.Commit()
+
+	return nil
 }
 
 // RemoveRepository removes repository from team of organization.
-func (t *Team) RemoveRepository(repoId int64) error {
-	idStr := "$" + com.ToStr(repoId) + "|"
-	if !strings.Contains(t.RepoIds, idStr) {
+func (t *Team) RemoveRepository(repoID int64) error {
+	if !t.HasRepository(repoID) {
 		return nil
 	}
 
-	repo, err := GetRepositoryById(repoId)
+	repo, err := GetRepositoryById(repoID)
 	if err != nil {
 		return err
 	}
 
-	if err = repo.GetOwner(); err != nil {
-		return err
-	} else if err = t.GetMembers(); err != nil {
-		return err
-	}
-
 	sess := x.NewSession()
-	defer sess.Close()
+	defer sessionRelease(sess)
 	if err = sess.Begin(); err != nil {
 		return err
 	}
 
-	t.NumRepos--
-	t.RepoIds = strings.Replace(t.RepoIds, idStr, "", 1)
-	if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
-		sess.Rollback()
+	if err = t.removeRepository(sess, repo, true); err != nil {
 		return err
 	}
 
-	// Remove access to team members.
-	for _, u := range t.Members {
-		auth, err := getHighestAuthorize(sess, t.OrgId, u.Id, repo.Id, t.Id)
-		if err != nil {
-			sess.Rollback()
-			return err
-		}
-
-		access := &Access{
-			UserName: u.LowerName,
-			RepoName: path.Join(repo.Owner.LowerName, repo.LowerName),
-		}
-		if auth == 0 {
-			if _, err = sess.Delete(access); err != nil {
-				sess.Rollback()
-				return fmt.Errorf("fail to delete access: %v", err)
-			} else if err = watchRepo(sess, u.Id, repo.Id, false); err != nil {
-				sess.Rollback()
-				return err
-			}
-		} else if auth < t.Authorize {
-			if err = addAccessWithAuthorize(sess, access, AuthorizeToAccessType(auth)); err != nil {
-				sess.Rollback()
-				return err
-			}
-		}
-	}
-
 	return sess.Commit()
 }
 
@@ -628,7 +608,7 @@ func NewTeam(t *Team) error {
 		return ErrTeamNameIllegal
 	}
 
-	has, err := x.Id(t.OrgId).Get(new(User))
+	has, err := x.Id(t.OrgID).Get(new(User))
 	if err != nil {
 		return err
 	} else if !has {
@@ -636,7 +616,7 @@ func NewTeam(t *Team) error {
 	}
 
 	t.LowerName = strings.ToLower(t.Name)
-	has, err = x.Where("org_id=?", t.OrgId).And("lower_name=?", t.LowerName).Get(new(Team))
+	has, err = x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).Get(new(Team))
 	if err != nil {
 		return err
 	} else if has {
@@ -655,20 +635,19 @@ func NewTeam(t *Team) error {
 	}
 
 	// Update organization number of teams.
-	if _, err = sess.Exec("UPDATE `user` SET num_teams = num_teams + 1 WHERE id = ?", t.OrgId); err != nil {
+	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()
 }
 
-// GetTeam returns team by given team name and organization.
-func GetTeam(orgId int64, name string) (*Team, error) {
+func getTeam(e Engine, orgId int64, name string) (*Team, error) {
 	t := &Team{
-		OrgId:     orgId,
+		OrgID:     orgId,
 		LowerName: strings.ToLower(name),
 	}
-	has, err := x.Get(t)
+	has, err := e.Get(t)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -677,6 +656,11 @@ func GetTeam(orgId int64, name string) (*Team, error) {
 	return t, nil
 }
 
+// GetTeam returns team by given team name and organization.
+func GetTeam(orgId int64, name string) (*Team, error) {
+	return getTeam(x, orgId, name)
+}
+
 func getTeamById(e Engine, teamId int64) (*Team, error) {
 	t := new(Team)
 	has, err := e.Id(teamId).Get(t)
@@ -693,34 +677,6 @@ func GetTeamById(teamId int64) (*Team, error) {
 	return getTeamById(x, teamId)
 }
 
-func getHighestAuthorize(e Engine, orgId, uid, repoId, teamId int64) (AuthorizeType, error) {
-	ts, err := getUserTeams(e, orgId, uid)
-	if err != nil {
-		return 0, err
-	}
-
-	var auth AuthorizeType = 0
-	for _, t := range ts {
-		// Not current team and has given repository.
-		if t.Id != teamId && strings.Contains(t.RepoIds, "$"+com.ToStr(repoId)+"|") {
-			// Fast return.
-			if t.Authorize == ORG_WRITABLE {
-				return ORG_WRITABLE, nil
-			}
-			if t.Authorize > auth {
-				auth = t.Authorize
-			}
-		}
-	}
-
-	return auth, nil
-}
-
-// GetHighestAuthorize returns highest repository authorize level for given user and team.
-func GetHighestAuthorize(orgId, uid, repoId, teamId int64) (AuthorizeType, error) {
-	return getHighestAuthorize(x, orgId, uid, repoId, teamId)
-}
-
 // UpdateTeam updates information of team.
 func UpdateTeam(t *Team, authChanged bool) (err error) {
 	if !IsLegalName(t.Name) {
@@ -732,60 +688,29 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
 	}
 
 	sess := x.NewSession()
-	defer sess.Close()
+	defer sessionRelease(sess)
 	if err = sess.Begin(); err != nil {
 		return err
 	}
 
-	// Update access for team members if needed.
-	if authChanged && !t.IsOwnerTeam() {
-		if err = t.GetRepositories(); err != nil {
-			return err
-		} else if err = t.GetMembers(); err != nil {
-			return err
-		}
+	t.LowerName = strings.ToLower(t.Name)
+	if _, err = sess.Id(t.ID).AllCols().Update(t); err != nil {
+		return fmt.Errorf("update: %v", err)
+	}
 
-		// Get organization.
-		org, err := GetUserById(t.OrgId)
-		if err != nil {
-			return err
+	// Update access for team members if needed.
+	if authChanged {
+		if err = t.getRepositories(sess); err != nil {
+			return fmt.Errorf("getRepositories:%v", err)
 		}
 
-		// Update access.
-		mode := AuthorizeToAccessType(t.Authorize)
-
 		for _, repo := range t.Repos {
-			for _, u := range t.Members {
-				// ORG_WRITABLE is the highest authorize level for now.
-				// Skip checking others if current team has this level.
-				if t.Authorize < ORG_WRITABLE {
-					auth, err := GetHighestAuthorize(t.OrgId, u.Id, repo.Id, t.Id)
-					if err != nil {
-						sess.Rollback()
-						return err
-					}
-					if auth >= t.Authorize {
-						continue // Other team has higher or same authorize level.
-					}
-				}
-
-				access := &Access{
-					UserName: u.LowerName,
-					RepoName: path.Join(org.LowerName, repo.LowerName),
-				}
-				if err = addAccessWithAuthorize(sess, access, mode); err != nil {
-					sess.Rollback()
-					return err
-				}
+			if err = repo.recalculateTeamAccesses(sess, 0); err != nil {
+				return fmt.Errorf("recalculateTeamAccesses: %v", err)
 			}
 		}
 	}
 
-	t.LowerName = strings.ToLower(t.Name)
-	if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
-		sess.Rollback()
-		return err
-	}
 	return sess.Commit()
 }
 
@@ -794,64 +719,38 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
 func DeleteTeam(t *Team) error {
 	if err := t.GetRepositories(); err != nil {
 		return err
-	} else if err = t.GetMembers(); err != nil {
-		return err
 	}
 
 	// Get organization.
-	org, err := GetUserById(t.OrgId)
+	org, err := GetUserById(t.OrgID)
 	if err != nil {
 		return err
 	}
 
 	sess := x.NewSession()
-	defer sess.Close()
+	defer sessionRelease(sess)
 	if err = sess.Begin(); err != nil {
 		return err
 	}
 
 	// Delete all accesses.
 	for _, repo := range t.Repos {
-		for _, u := range t.Members {
-			auth, err := GetHighestAuthorize(t.OrgId, u.Id, repo.Id, t.Id)
-			if err != nil {
-				sess.Rollback()
-				return err
-			}
-
-			access := &Access{
-				UserName: u.LowerName,
-				RepoName: path.Join(org.LowerName, repo.LowerName),
-			}
-			if auth == 0 {
-				if _, err = sess.Delete(access); err != nil {
-					sess.Rollback()
-					return fmt.Errorf("fail to delete access: %v", err)
-				}
-			} else if auth < t.Authorize {
-				// Downgrade authorize level.
-				if err = addAccessWithAuthorize(sess, access, AuthorizeToAccessType(auth)); err != nil {
-					sess.Rollback()
-					return err
-				}
-			}
+		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 {
-		sess.Rollback()
+	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 {
-		sess.Rollback()
+	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 {
-		sess.Rollback()
+	if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
 		return err
 	}
 
@@ -867,27 +766,41 @@ func DeleteTeam(t *Team) error {
 
 // TeamUser represents an team-user relation.
 type TeamUser struct {
-	Id     int64
-	Uid    int64
-	OrgId  int64 `xorm:"INDEX"`
-	TeamId int64
+	ID     int64 `xorm:"pk autoincr"`
+	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("uid=?", uid).And("org_id=?", orgId).And("team_id=?", teamId).Get(new(TeamUser))
+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 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.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(orgId, teamId int64) ([]*User, error) {
-	us := make([]*User, 0, 10)
-	err := x.Sql("SELECT * FROM `user` JOIN `team_user` ON `team_user`.`team_id` = ? AND `team_user`.`uid` = `user`.`id`", teamId).Find(&us)
-	return us, err
+func GetTeamMembers(teamID int64) ([]*User, error) {
+	return getTeamMembers(x, teamID)
 }
 
 func getUserTeams(e Engine, orgId, uid int64) ([]*Team, error) {
@@ -899,7 +812,7 @@ func getUserTeams(e Engine, orgId, uid int64) ([]*Team, error) {
 	ts := make([]*Team, len(tus))
 	for i, tu := range tus {
 		t := new(Team)
-		has, err := e.Id(tu.TeamId).Get(t)
+		has, err := e.Id(tu.TeamID).Get(t)
 		if err != nil {
 			return nil, err
 		} else if !has {
@@ -936,72 +849,40 @@ func AddTeamMember(orgId, teamId, uid int64) error {
 		return err
 	}
 
-	// Get organization.
-	org, err := GetUserById(orgId)
-	if err != nil {
-		return err
-	}
-
-	// Get user.
-	u, err := GetUserById(uid)
-	if err != nil {
-		return err
-	}
-
 	sess := x.NewSession()
-	defer sess.Close()
+	defer sessionRelease(sess)
 	if err = sess.Begin(); err != nil {
 		return err
 	}
 
 	tu := &TeamUser{
 		Uid:    uid,
-		OrgId:  orgId,
-		TeamId: teamId,
+		OrgID:  orgId,
+		TeamID: teamId,
 	}
-
 	if _, err = sess.Insert(tu); err != nil {
-		sess.Rollback()
 		return err
-	} else if _, err = sess.Id(t.Id).Update(t); err != nil {
-		sess.Rollback()
+	} else if _, err = sess.Id(t.ID).Update(t); err != nil {
 		return err
 	}
 
 	// Give access to team repositories.
-	mode := AuthorizeToAccessType(t.Authorize)
 	for _, repo := range t.Repos {
-		auth, err := getHighestAuthorize(sess, t.OrgId, u.Id, repo.Id, teamId)
-		if err != nil {
-			sess.Rollback()
+		if err = repo.recalculateTeamAccesses(sess, 0); err != nil {
 			return err
 		}
-
-		access := &Access{
-			UserName: u.LowerName,
-			RepoName: path.Join(org.LowerName, repo.LowerName),
-		}
-		if auth < t.Authorize {
-			if err = addAccessWithAuthorize(sess, access, mode); err != nil {
-				sess.Rollback()
-				return err
-			}
-		}
 	}
 
 	// We make sure it exists before.
 	ou := new(OrgUser)
-	_, err = sess.Where("uid=?", uid).And("org_id=?", orgId).Get(ou)
-	if err != nil {
-		sess.Rollback()
+	if _, err = sess.Where("uid=?", uid).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 {
-		sess.Rollback()
+	if _, err = sess.Id(ou.ID).AllCols().Update(ou); err != nil {
 		return err
 	}
 
@@ -1026,58 +907,32 @@ func removeTeamMember(e Engine, orgId, teamId, uid int64) error {
 
 	t.NumMembers--
 
-	if err = t.GetRepositories(); err != nil {
+	if err = t.getRepositories(e); err != nil {
 		return err
 	}
 
 	// Get organization.
-	org, err := GetUserById(orgId)
-	if err != nil {
-		return err
-	}
-
-	// Get user.
-	u, err := GetUserById(uid)
+	org, err := getUserById(e, orgId)
 	if err != nil {
 		return err
 	}
 
 	tu := &TeamUser{
 		Uid:    uid,
-		OrgId:  orgId,
-		TeamId: teamId,
+		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 {
+	} else if _, err = e.Id(t.ID).AllCols().Update(t); err != nil {
 		return err
 	}
 
 	// Delete access to team repositories.
 	for _, repo := range t.Repos {
-		auth, err := getHighestAuthorize(e, t.OrgId, u.Id, repo.Id, teamId)
-		if err != nil {
+		if err = repo.recalculateTeamAccesses(e, 0); err != nil {
 			return err
 		}
-
-		access := &Access{
-			UserName: u.LowerName,
-			RepoName: path.Join(org.LowerName, repo.LowerName),
-		}
-		// Delete access if this is the last team user belongs to.
-		if auth == 0 {
-			if _, err = e.Delete(access); err != nil {
-				return fmt.Errorf("fail to delete access: %v", err)
-			} else if err = watchRepo(e, u.Id, repo.Id, false); err != nil {
-				return err
-			}
-		} else if auth < t.Authorize {
-			// Downgrade authorize level.
-			if err = addAccessWithAuthorize(e, access, AuthorizeToAccessType(auth)); err != nil {
-				return err
-			}
-		}
 	}
 
 	// This must exist.
@@ -1090,7 +945,7 @@ func removeTeamMember(e Engine, orgId, teamId, uid int64) error {
 	if t.IsOwnerTeam() {
 		ou.IsOwner = false
 	}
-	if _, err = e.Id(ou.Id).AllCols().Update(ou); err != nil {
+	if _, err = e.Id(ou.ID).AllCols().Update(ou); err != nil {
 		return err
 	}
 	return nil
@@ -1099,13 +954,77 @@ func removeTeamMember(e Engine, orgId, teamId, uid int64) error {
 // RemoveTeamMember removes member from given team of given organization.
 func RemoveTeamMember(orgId, teamId, uid int64) error {
 	sess := x.NewSession()
-	defer sess.Close()
+	defer sessionRelease(sess)
 	if err := sess.Begin(); err != nil {
 		return err
 	}
 	if err := removeTeamMember(sess, orgId, teamId, uid); err != nil {
-		sess.Rollback()
 		return err
 	}
 	return sess.Commit()
 }
+
+// ___________                  __________
+// \__    ___/___ _____    _____\______   \ ____ ______   ____
+//   |    |_/ __ \\__  \  /     \|       _// __ \\____ \ /  _ \
+//   |    |\  ___/ / __ \|  Y Y  \    |   \  ___/|  |_> >  <_> )
+//   |____| \___  >____  /__|_|  /____|_  /\___  >   __/ \____/
+//              \/     \/      \/       \/     \/|__|
+
+// TeamRepo represents an team-repository relation.
+type TeamRepo struct {
+	ID     int64 `xorm:"pk autoincr"`
+	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 repository belongs to team.
+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)
+}
+
+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)
+}

File diff suppressed because it is too large
+ 290 - 466
models/repo.go


+ 12 - 65
models/user.go

@@ -231,7 +231,7 @@ func (u *User) GetOrganizations() error {
 
 	u.Orgs = make([]*User, len(ous))
 	for i, ou := range ous {
-		u.Orgs[i], err = GetUserById(ou.OrgId)
+		u.Orgs[i], err = GetUserById(ou.OrgID)
 		if err != nil {
 			return err
 		}
@@ -398,63 +398,7 @@ func ChangeUserName(u *User, newUserName string) (err error) {
 		return ErrUserNameIllegal
 	}
 
-	newUserName = strings.ToLower(newUserName)
-	if u.LowerName == newUserName {
-		// User only change letter cases.
-		return nil
-	}
-
-	// Update accesses of user.
-	accesses := make([]Access, 0, 10)
-	if err = x.Find(&accesses, &Access{UserName: u.LowerName}); err != nil {
-		return err
-	}
-
-	sess := x.NewSession()
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
-		return err
-	}
-
-	for i := range accesses {
-		accesses[i].UserName = newUserName
-		if strings.HasPrefix(accesses[i].RepoName, u.LowerName+"/") {
-			accesses[i].RepoName = strings.Replace(accesses[i].RepoName, u.LowerName, newUserName, 1)
-		}
-		if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
-			return err
-		}
-	}
-
-	repos, err := GetRepositories(u.Id, true)
-	if err != nil {
-		return err
-	}
-	for i := range repos {
-		accesses = make([]Access, 0, 10)
-		// Update accesses of user repository.
-		if err = x.Find(&accesses, &Access{RepoName: u.LowerName + "/" + repos[i].LowerName}); err != nil {
-			return err
-		}
-
-		for j := range accesses {
-			// if the access is not the user's access (already updated above)
-			if accesses[j].UserName != u.LowerName {
-				accesses[j].RepoName = newUserName + "/" + repos[i].LowerName
-				if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil {
-					return err
-				}
-			}
-		}
-	}
-
-	// Change user directory name.
-	if err = os.Rename(UserPath(u.LowerName), UserPath(newUserName)); err != nil {
-		sess.Rollback()
-		return err
-	}
-
-	return sess.Commit()
+	return os.Rename(UserPath(u.LowerName), UserPath(newUserName))
 }
 
 // UpdateUser updates user's information.
@@ -527,7 +471,7 @@ func DeleteUser(u *User) error {
 		return err
 	}
 	// Delete all accesses.
-	if _, err = x.Delete(&Access{UserName: u.LowerName}); err != nil {
+	if _, err = x.Delete(&Access{UserID: u.Id}); err != nil {
 		return err
 	}
 	// Delete all alternative email addresses
@@ -570,8 +514,7 @@ func UserPath(userName string) string {
 
 func GetUserByKeyId(keyId int64) (*User, error) {
 	user := new(User)
-	rawSql := "SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?"
-	has, err := x.Sql(rawSql, keyId).Get(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 {
@@ -580,10 +523,9 @@ func GetUserByKeyId(keyId int64) (*User, error) {
 	return user, nil
 }
 
-// GetUserById returns the user object by given ID if exists.
-func GetUserById(id int64) (*User, error) {
+func getUserById(e Engine, id int64) (*User, error) {
 	u := new(User)
-	has, err := x.Id(id).Get(u)
+	has, err := e.Id(id).Get(u)
 	if err != nil {
 		return nil, err
 	} else if !has {
@@ -592,6 +534,11 @@ func GetUserById(id int64) (*User, error) {
 	return u, nil
 }
 
+// GetUserById returns the user object by given ID if exists.
+func GetUserById(id int64) (*User, error) {
+	return getUserById(x, id)
+}
+
 // GetUserByName returns user by given name.
 func GetUserByName(name string) (*User, error) {
 	if len(name) == 0 {
@@ -913,7 +860,7 @@ func UpdateMentions(userNames []string, issueId int64) error {
 		}
 
 		for _, orgUser := range orgUsers {
-			tempIds = append(tempIds, orgUser.Id)
+			tempIds = append(tempIds, orgUser.ID)
 		}
 
 		ids = append(ids, tempIds...)

+ 1 - 1
modules/base/template.go

@@ -164,7 +164,7 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
 	},
 	"DiffTypeToStr":     DiffTypeToStr,
 	"DiffLineTypeToStr": DiffLineTypeToStr,
-	"Sha1":	             Sha1,
+	"Sha1":              Sha1,
 	"ShortSha":          ShortSha,
 	"Md5":               EncodeMd5,
 	"ActionContent2Commits": ActionContent2Commits,

+ 32 - 23
modules/middleware/context.go

@@ -38,29 +38,7 @@ type Context struct {
 	IsSigned    bool
 	IsBasicAuth bool
 
-	Repo struct {
-		IsOwner      bool
-		IsTrueOwner  bool
-		IsWatching   bool
-		IsBranch     bool
-		IsTag        bool
-		IsCommit     bool
-		IsAdmin      bool // Current user is admin level.
-		HasAccess    bool
-		Repository   *models.Repository
-		Owner        *models.User
-		Commit       *git.Commit
-		Tag          *git.Tag
-		GitRepo      *git.Repository
-		BranchName   string
-		TagName      string
-		TreeName     string
-		CommitId     string
-		RepoLink     string
-		CloneLink    models.CloneLink
-		CommitsCount int
-		Mirror       *models.Mirror
-	}
+	Repo RepoContext
 
 	Org struct {
 		IsOwner      bool
@@ -73,6 +51,37 @@ type Context struct {
 	}
 }
 
+type RepoContext struct {
+	AccessMode   models.AccessMode
+	IsWatching   bool
+	IsBranch     bool
+	IsTag        bool
+	IsCommit     bool
+	Repository   *models.Repository
+	Owner        *models.User
+	Commit       *git.Commit
+	Tag          *git.Tag
+	GitRepo      *git.Repository
+	BranchName   string
+	TagName      string
+	TreeName     string
+	CommitId     string
+	RepoLink     string
+	CloneLink    models.CloneLink
+	CommitsCount int
+	Mirror       *models.Mirror
+}
+
+// Return if the current user has write access for this repository
+func (r RepoContext) IsOwner() bool {
+	return r.AccessMode >= models.ACCESS_MODE_WRITE
+}
+
+// Return if the current user has read access for this repository
+func (r RepoContext) HasAccess() bool {
+	return r.AccessMode >= models.ACCESS_MODE_READ
+}
+
 // HasError returns true if error occurs in form validation.
 func (ctx *Context) HasApiError() bool {
 	hasErr, ok := ctx.Data["HasError"]

+ 1 - 1
modules/middleware/org.go

@@ -87,7 +87,7 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler {
 				return
 			}
 			ctx.Data["Team"] = ctx.Org.Team
-			ctx.Org.IsAdminTeam = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.Authorize == models.ORG_ADMIN
+			ctx.Org.IsAdminTeam = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.Authorize >= models.ACCESS_MODE_ADMIN
 		}
 		ctx.Data["IsAdminTeam"] = ctx.Org.IsAdminTeam
 		if requireAdminTeam && !ctx.Org.IsAdminTeam {

+ 34 - 124
modules/middleware/repo.go

@@ -5,7 +5,6 @@
 package middleware
 
 import (
-	"errors"
 	"fmt"
 	"net/url"
 	"strings"
@@ -29,17 +28,10 @@ func ApiRepoAssignment() macaron.Handler {
 			err error
 		)
 
-		// Collaborators who have write access can be seen as owners.
-		if ctx.IsSigned {
-			ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE)
-			if err != nil {
-				ctx.JSON(500, &base.ApiJsonErr{"HasAccess: " + err.Error(), base.DOC_URL})
-				return
-			}
-			ctx.Repo.IsTrueOwner = ctx.User.LowerName == strings.ToLower(userName)
-		}
-
-		if !ctx.Repo.IsTrueOwner {
+		// Check if the user is the same as the repository owner.
+		if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
+			u = ctx.User
+		} else {
 			u, err = models.GetUserByName(userName)
 			if err != nil {
 				if err == models.ErrUserNotExist {
@@ -49,66 +41,36 @@ func ApiRepoAssignment() macaron.Handler {
 				}
 				return
 			}
-		} else {
-			u = ctx.User
 		}
 		ctx.Repo.Owner = u
 
-		// Organization owner team members are true owners as well.
-		if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOwnedBy(ctx.User.Id) {
-			ctx.Repo.IsTrueOwner = true
-		}
-
 		// Get repository.
 		repo, err := models.GetRepositoryByName(u.Id, repoName)
 		if err != nil {
 			if err == models.ErrRepoNotExist {
 				ctx.Error(404)
-				return
+			} else {
+				ctx.JSON(500, &base.ApiJsonErr{"GetRepositoryByName: " + err.Error(), base.DOC_URL})
 			}
-			ctx.JSON(500, &base.ApiJsonErr{"GetRepositoryByName: " + err.Error(), base.DOC_URL})
 			return
 		} else if err = repo.GetOwner(); err != nil {
 			ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL})
 			return
 		}
 
-		// Check if the mirror repository owner(mirror repository doesn't have access).
-		if ctx.IsSigned && !ctx.Repo.IsOwner {
-			if repo.OwnerId == ctx.User.Id {
-				ctx.Repo.IsOwner = true
-			}
-			// Check if current user has admin permission to repository.
-			if u.IsOrganization() {
-				auth, err := models.GetHighestAuthorize(u.Id, ctx.User.Id, repo.Id, 0)
-				if err != nil {
-					ctx.JSON(500, &base.ApiJsonErr{"GetHighestAuthorize: " + err.Error(), base.DOC_URL})
-					return
-				}
-				if auth == models.ORG_ADMIN {
-					ctx.Repo.IsOwner = true
-					ctx.Repo.IsAdmin = true
-				}
-			}
+		mode, err := models.AccessLevel(ctx.User, repo)
+		if err != nil {
+			ctx.JSON(500, &base.ApiJsonErr{"AccessLevel: " + err.Error(), base.DOC_URL})
+			return
 		}
 
-		// Check access.
-		if repo.IsPrivate && !ctx.Repo.IsOwner {
-			if ctx.User == nil {
-				ctx.Error(404)
-				return
-			}
+		ctx.Repo.AccessMode = mode
 
-			hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE)
-			if err != nil {
-				ctx.JSON(500, &base.ApiJsonErr{"HasAccess: " + err.Error(), base.DOC_URL})
-				return
-			} else if !hasAccess {
-				ctx.Error(404)
-				return
-			}
+		// Check access.
+		if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE {
+			ctx.Error(404)
+			return
 		}
-		ctx.Repo.HasAccess = true
 
 		ctx.Repo.Repository = repo
 	}
@@ -242,101 +204,49 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
 			refName = ctx.Params(":path")
 		}
 
-		// Collaborators who have write access can be seen as owners.
-		if ctx.IsSigned {
-			ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE)
-			if err != nil {
-				ctx.Handle(500, "HasAccess", err)
-				return
-			}
-			ctx.Repo.IsTrueOwner = ctx.User.LowerName == strings.ToLower(userName)
-		}
-
-		if !ctx.Repo.IsTrueOwner {
+		// Check if the user is the same as the repository owner
+		if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
+			u = ctx.User
+		} else {
 			u, err = models.GetUserByName(userName)
 			if err != nil {
 				if err == models.ErrUserNotExist {
 					ctx.Handle(404, "GetUserByName", err)
-				} else if redirect {
-					log.Error(4, "GetUserByName", err)
-					ctx.Redirect(setting.AppSubUrl + "/")
 				} else {
 					ctx.Handle(500, "GetUserByName", err)
 				}
 				return
 			}
-		} else {
-			u = ctx.User
-		}
-
-		if u == nil {
-			if redirect {
-				ctx.Redirect(setting.AppSubUrl + "/")
-				return
-			}
-			ctx.Handle(404, "RepoAssignment", errors.New("invliad user account for single repository"))
-			return
 		}
 		ctx.Repo.Owner = u
 
-		// Organization owner team members are true owners as well.
-		if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOwnedBy(ctx.User.Id) {
-			ctx.Repo.IsTrueOwner = true
-		}
-
 		// Get repository.
 		repo, err := models.GetRepositoryByName(u.Id, repoName)
 		if err != nil {
 			if err == models.ErrRepoNotExist {
 				ctx.Handle(404, "GetRepositoryByName", err)
-				return
-			} else if redirect {
-				ctx.Redirect(setting.AppSubUrl + "/")
-				return
+			} else {
+				ctx.Handle(500, "GetRepositoryByName", err)
 			}
-			ctx.Handle(500, "GetRepositoryByName", err)
 			return
 		} else if err = repo.GetOwner(); err != nil {
 			ctx.Handle(500, "GetOwner", err)
 			return
 		}
 
-		// Check if the mirror repository owner(mirror repository doesn't have access).
-		if ctx.IsSigned && !ctx.Repo.IsOwner {
-			if repo.OwnerId == ctx.User.Id {
-				ctx.Repo.IsOwner = true
-			}
-			// Check if current user has admin permission to repository.
-			if u.IsOrganization() {
-				auth, err := models.GetHighestAuthorize(u.Id, ctx.User.Id, repo.Id, 0)
-				if err != nil {
-					ctx.Handle(500, "GetHighestAuthorize", err)
-					return
-				}
-				if auth == models.ORG_ADMIN {
-					ctx.Repo.IsOwner = true
-					ctx.Repo.IsAdmin = true
-				}
-			}
+		mode, err := models.AccessLevel(ctx.User, repo)
+		if err != nil {
+			ctx.Handle(500, "AccessLevel", err)
+			return
 		}
+		ctx.Repo.AccessMode = mode
 
 		// Check access.
-		if repo.IsPrivate && !ctx.Repo.IsOwner {
-			if ctx.User == nil {
-				ctx.Handle(404, "HasAccess", nil)
-				return
-			}
-
-			hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE)
-			if err != nil {
-				ctx.Handle(500, "HasAccess", err)
-				return
-			} else if !hasAccess {
-				ctx.Handle(404, "HasAccess", nil)
-				return
-			}
+		if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE {
+			ctx.Handle(404, "no access right", err)
+			return
 		}
-		ctx.Repo.HasAccess = true
+
 		ctx.Data["HasAccess"] = true
 
 		if repo.IsMirror {
@@ -383,8 +293,8 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
 		ctx.Data["Title"] = u.Name + "/" + repo.Name
 		ctx.Data["Repository"] = repo
 		ctx.Data["Owner"] = ctx.Repo.Repository.Owner
-		ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner
-		ctx.Data["IsRepositoryTrueOwner"] = ctx.Repo.IsTrueOwner
+		ctx.Data["IsRepositoryOwner"] = ctx.Repo.AccessMode >= models.ACCESS_MODE_WRITE
+		ctx.Data["IsRepositoryAdmin"] = ctx.Repo.AccessMode >= models.ACCESS_MODE_ADMIN
 
 		ctx.Data["DisableSSH"] = setting.DisableSSH
 		ctx.Repo.CloneLink, err = repo.CloneLink()
@@ -438,9 +348,9 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
 	}
 }
 
-func RequireTrueOwner() macaron.Handler {
+func RequireAdmin() macaron.Handler {
 	return func(ctx *Context) {
-		if !ctx.Repo.IsTrueOwner && !ctx.Repo.IsAdmin {
+		if ctx.Repo.AccessMode < models.ACCESS_MODE_ADMIN {
 			if !ctx.IsSigned {
 				ctx.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl)
 				ctx.Redirect(setting.AppSubUrl + "/user/login")

+ 9 - 10
modules/setting/setting.go

@@ -67,6 +67,11 @@ var (
 	CookieRememberName   string
 	ReverseProxyAuthUser string
 
+	// Database settings.
+	UseSQLite3    bool
+	UseMySQL      bool
+	UsePostgreSQL bool
+
 	// Webhook settings.
 	Webhook struct {
 		TaskInterval   int
@@ -267,10 +272,6 @@ func NewConfigContext() {
 		"StampNano":   time.StampNano,
 	}[Cfg.Section("time").Key("FORMAT").MustString("RFC1123")]
 
-	if err = os.MkdirAll(AttachmentPath, os.ModePerm); err != nil {
-		log.Fatal(4, "Could not create directory %s: %s", AttachmentPath, err)
-	}
-
 	RunUser = Cfg.Section("").Key("RUN_USER").String()
 	curUser := os.Getenv("USER")
 	if len(curUser) == 0 {
@@ -293,9 +294,6 @@ func NewConfigContext() {
 	} else {
 		RepoRootPath = filepath.Clean(RepoRootPath)
 	}
-	if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
-		log.Fatal(4, "Fail to create repository root path(%s): %v", RepoRootPath, err)
-	}
 	ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
 
 	sec = Cfg.Section("picture")
@@ -304,7 +302,6 @@ func NewConfigContext() {
 	if !filepath.IsAbs(AvatarUploadPath) {
 		AvatarUploadPath = path.Join(workDir, AvatarUploadPath)
 	}
-	os.MkdirAll(AvatarUploadPath, os.ModePerm)
 	switch sec.Key("GRAVATAR_SOURCE").MustString("gravatar") {
 	case "duoshuo":
 		GravatarSource = "http://gravatar.duoshuo.com/avatar/"
@@ -369,9 +366,11 @@ func newLogService() {
 			log.Fatal(4, "Unknown log mode: %s", mode)
 		}
 
+		validLevels := []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"}
 		// Log level.
-		levelName := Cfg.Section("log."+mode).Key("LEVEL").In("Trace",
-			[]string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
+		levelName := Cfg.Section("log."+mode).Key("LEVEL").In(
+			Cfg.Section("log").Key("LEVEL").In("Trace", validLevels),
+			validLevels)
 		level, ok := logLevels[levelName]
 		if !ok {
 			log.Fatal(4, "Unknown log level: %s", levelName)

+ 12 - 9
routers/api/v1/repo.go

@@ -238,28 +238,31 @@ func ListMyRepos(ctx *middleware.Context) {
 	}
 	numOwnRepos := len(ownRepos)
 
-	collaRepos, err := models.GetCollaborativeRepos(ctx.User.Name)
+	accessibleRepos, err := ctx.User.GetAccessibleRepositories()
 	if err != nil {
-		ctx.JSON(500, &base.ApiJsonErr{"GetCollaborativeRepos: " + err.Error(), base.DOC_URL})
+		ctx.JSON(500, &base.ApiJsonErr{"GetAccessibleRepositories: " + err.Error(), base.DOC_URL})
 		return
 	}
 
-	repos := make([]*api.Repository, numOwnRepos+len(collaRepos))
+	repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos))
 	for i := range ownRepos {
 		repos[i] = ToApiRepository(ctx.User, ownRepos[i], api.Permission{true, true, true})
 	}
-	for i := range collaRepos {
-		if err = collaRepos[i].GetOwner(); err != nil {
+	i := numOwnRepos
+
+	for repo, access := range accessibleRepos {
+		if err = repo.GetOwner(); err != nil {
 			ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL})
 			return
 		}
-		j := i + numOwnRepos
-		repos[j] = ToApiRepository(collaRepos[i].Owner, collaRepos[i].Repository, api.Permission{false, collaRepos[i].CanPush, true})
+
+		repos[i] = ToApiRepository(repo.Owner, repo, api.Permission{false, access >= models.ACCESS_MODE_WRITE, true})
 
 		// FIXME: cache result to reduce DB query?
-		if collaRepos[i].Owner.IsOrganization() && collaRepos[i].Owner.IsOwnedBy(ctx.User.Id) {
-			repos[j].Permissions.Admin = true
+		if repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(ctx.User.Id) {
+			repos[i].Permissions.Admin = true
 		}
+		i++
 	}
 
 	ctx.JSON(200, &repos)

+ 1 - 1
routers/api/v1/repo_file.go

@@ -12,7 +12,7 @@ import (
 )
 
 func GetRepoRawFile(ctx *middleware.Context) {
-	if ctx.Repo.Repository.IsPrivate && !ctx.Repo.HasAccess {
+	if !ctx.Repo.HasAccess() {
 		ctx.Error(404)
 		return
 	}

+ 1 - 0
routers/install.go

@@ -224,6 +224,7 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	cfg.Section("session").Key("PROVIDER").SetValue("file")
 
 	cfg.Section("log").Key("MODE").SetValue("file")
+	cfg.Section("log").Key("LEVEL").SetValue("Info")
 
 	cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
 	cfg.Section("security").Key("SECRET_KEY").SetValue(base.GetRandomString(15))

+ 9 - 9
routers/org/teams.go

@@ -165,14 +165,14 @@ func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) {
 	}
 
 	// Validate permission level.
-	var auth models.AuthorizeType
+	var auth models.AccessMode
 	switch form.Permission {
 	case "read":
-		auth = models.ORG_READABLE
+		auth = models.ACCESS_MODE_READ
 	case "write":
-		auth = models.ORG_WRITABLE
+		auth = models.ACCESS_MODE_WRITE
 	case "admin":
-		auth = models.ORG_ADMIN
+		auth = models.ACCESS_MODE_ADMIN
 	default:
 		ctx.Error(401)
 		return
@@ -181,7 +181,7 @@ func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) {
 	org := ctx.Org.Organization
 
 	t := &models.Team{
-		OrgId:       org.Id,
+		OrgID:       org.Id,
 		Name:        form.TeamName,
 		Description: form.Description,
 		Authorize:   auth,
@@ -246,14 +246,14 @@ func EditTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) {
 	isAuthChanged := false
 	if !t.IsOwnerTeam() {
 		// Validate permission level.
-		var auth models.AuthorizeType
+		var auth models.AccessMode
 		switch form.Permission {
 		case "read":
-			auth = models.ORG_READABLE
+			auth = models.ACCESS_MODE_READ
 		case "write":
-			auth = models.ORG_WRITABLE
+			auth = models.ACCESS_MODE_WRITE
 		case "admin":
-			auth = models.ORG_ADMIN
+			auth = models.ACCESS_MODE_ADMIN
 		default:
 			ctx.Error(401)
 			return

+ 10 - 5
routers/repo/http.go

@@ -131,18 +131,18 @@ func Http(ctx *middleware.Context) {
 		}
 
 		if !isPublicPull {
-			var tp = models.WRITABLE
+			var tp = models.ACCESS_MODE_WRITE
 			if isPull {
-				tp = models.READABLE
+				tp = models.ACCESS_MODE_READ
 			}
 
-			has, err := models.HasAccess(authUsername, username+"/"+reponame, tp)
+			has, err := models.HasAccess(authUser, repo, tp)
 			if err != nil {
 				ctx.Handle(401, "no basic auth and digit auth", nil)
 				return
 			} else if !has {
-				if tp == models.READABLE {
-					has, err = models.HasAccess(authUsername, username+"/"+reponame, models.WRITABLE)
+				if tp == models.ACCESS_MODE_READ {
+					has, err = models.HasAccess(authUser, repo, models.ACCESS_MODE_WRITE)
 					if err != nil || !has {
 						ctx.Handle(401, "no basic auth and digit auth", nil)
 						return
@@ -152,6 +152,11 @@ func Http(ctx *middleware.Context) {
 					return
 				}
 			}
+
+			if !isPull && repo.IsMirror {
+				ctx.Handle(401, "can't push to mirror", nil)
+				return
+			}
 		}
 	}
 

+ 12 - 12
routers/repo/issue.go

@@ -174,7 +174,7 @@ func CreateIssue(ctx *middleware.Context) {
 		return
 	}
 
-	us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
+	us, err := ctx.Repo.Repository.GetCollaborators()
 	if err != nil {
 		ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
 		return
@@ -218,7 +218,7 @@ func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
 		return
 	}
 
-	_, err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
+	_, err = ctx.Repo.Repository.GetCollaborators()
 	if err != nil {
 		send(500, nil, err)
 		return
@@ -230,7 +230,7 @@ func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
 	}
 
 	// Only collaborators can assign.
-	if !ctx.Repo.IsOwner {
+	if !ctx.Repo.IsOwner() {
 		form.AssigneeId = 0
 	}
 	issue := &models.Issue{
@@ -246,8 +246,8 @@ func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
 	if err := models.NewIssue(issue); err != nil {
 		send(500, nil, err)
 		return
-	} else if err := models.NewIssueUserPairs(issue.RepoId, issue.Id, ctx.Repo.Owner.Id,
-		ctx.User.Id, form.AssigneeId, ctx.Repo.Repository.Name); err != nil {
+	} else if err := models.NewIssueUserPairs(ctx.Repo.Repository, issue.Id, ctx.Repo.Owner.Id,
+		ctx.User.Id, form.AssigneeId); err != nil {
 		send(500, nil, err)
 		return
 	}
@@ -384,7 +384,7 @@ func ViewIssue(ctx *middleware.Context) {
 	}
 
 	// Get all collaborators.
-	ctx.Data["Collaborators"], err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
+	ctx.Data["Collaborators"], err = ctx.Repo.Repository.GetCollaborators()
 	if err != nil {
 		ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
 		return
@@ -434,7 +434,7 @@ func ViewIssue(ctx *middleware.Context) {
 	ctx.Data["Title"] = issue.Name
 	ctx.Data["Issue"] = issue
 	ctx.Data["Comments"] = comments
-	ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id)
+	ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner() || (ctx.IsSigned && issue.PosterId == ctx.User.Id)
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = false
 	ctx.HTML(200, ISSUE_VIEW)
@@ -457,7 +457,7 @@ func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) {
 		return
 	}
 
-	if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner {
+	if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner() {
 		ctx.Error(403)
 		return
 	}
@@ -484,7 +484,7 @@ func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) {
 }
 
 func UpdateIssueLabel(ctx *middleware.Context) {
-	if !ctx.Repo.IsOwner {
+	if !ctx.Repo.IsOwner() {
 		ctx.Error(403)
 		return
 	}
@@ -561,7 +561,7 @@ func UpdateIssueLabel(ctx *middleware.Context) {
 }
 
 func UpdateIssueMilestone(ctx *middleware.Context) {
-	if !ctx.Repo.IsOwner {
+	if !ctx.Repo.IsOwner() {
 		ctx.Error(403)
 		return
 	}
@@ -607,7 +607,7 @@ func UpdateIssueMilestone(ctx *middleware.Context) {
 }
 
 func UpdateAssignee(ctx *middleware.Context) {
-	if !ctx.Repo.IsOwner {
+	if !ctx.Repo.IsOwner() {
 		ctx.Error(403)
 		return
 	}
@@ -753,7 +753,7 @@ func Comment(ctx *middleware.Context) {
 
 	// Check if issue owner changes the status of issue.
 	var newStatus string
-	if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id {
+	if ctx.Repo.IsOwner() || issue.PosterId == ctx.User.Id {
 		newStatus = ctx.Query("change_status")
 	}
 	if len(newStatus) > 0 {

+ 5 - 5
routers/repo/release.go

@@ -41,7 +41,7 @@ func Releases(ctx *middleware.Context) {
 	tags := make([]*models.Release, len(rawTags))
 	for i, rawTag := range rawTags {
 		for j, rel := range rels {
-			if rel == nil || (rel.IsDraft && !ctx.Repo.IsOwner) {
+			if rel == nil || (rel.IsDraft && !ctx.Repo.IsOwner()) {
 				continue
 			}
 			if rel.TagName == rawTag {
@@ -140,7 +140,7 @@ func Releases(ctx *middleware.Context) {
 }
 
 func NewRelease(ctx *middleware.Context) {
-	if !ctx.Repo.IsOwner {
+	if !ctx.Repo.IsOwner() {
 		ctx.Handle(403, "release.ReleasesNew", nil)
 		return
 	}
@@ -153,7 +153,7 @@ func NewRelease(ctx *middleware.Context) {
 }
 
 func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) {
-	if !ctx.Repo.IsOwner {
+	if !ctx.Repo.IsOwner() {
 		ctx.Handle(403, "release.ReleasesNew", nil)
 		return
 	}
@@ -211,7 +211,7 @@ func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) {
 }
 
 func EditRelease(ctx *middleware.Context) {
-	if !ctx.Repo.IsOwner {
+	if !ctx.Repo.IsOwner() {
 		ctx.Handle(403, "release.ReleasesEdit", nil)
 		return
 	}
@@ -234,7 +234,7 @@ func EditRelease(ctx *middleware.Context) {
 }
 
 func EditReleasePost(ctx *middleware.Context, form auth.EditReleaseForm) {
-	if !ctx.Repo.IsOwner {
+	if !ctx.Repo.IsOwner() {
 		ctx.Handle(403, "release.EditReleasePost", nil)
 		return
 	}

+ 1 - 1
routers/repo/repo.go

@@ -349,7 +349,7 @@ func Action(ctx *middleware.Context) {
 	case "unstar":
 		err = models.StarRepo(ctx.User.Id, ctx.Repo.Repository.Id, false)
 	case "desc":
-		if !ctx.Repo.IsOwner {
+		if !ctx.Repo.IsOwner() {
 			ctx.Error(404)
 			return
 		}

+ 13 - 52
routers/repo/setting.go

@@ -8,7 +8,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"path"
 	"strings"
 	"time"
 
@@ -54,15 +53,11 @@ func SettingsPost(ctx *middleware.Context, form auth.RepoSettingForm) {
 		newRepoName := form.RepoName
 		// Check if repository name has been changed.
 		if ctx.Repo.Repository.Name != newRepoName {
-			isExist, err := models.IsRepositoryExist(ctx.Repo.Owner, newRepoName)
-			if err != nil {
-				ctx.Handle(500, "IsRepositoryExist", err)
-				return
-			} else if isExist {
+			if models.IsRepositoryExist(ctx.Repo.Owner, newRepoName) {
 				ctx.Data["Err_RepoName"] = true
 				ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), SETTINGS_OPTIONS, nil)
 				return
-			} else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil {
+			} else if err := models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil {
 				if err == models.ErrRepoNameIllegal {
 					ctx.Data["Err_RepoName"] = true
 					ctx.RenderWithErr(ctx.Tr("form.illegal_repo_name"), SETTINGS_OPTIONS, nil)
@@ -169,22 +164,12 @@ func SettingsCollaboration(ctx *middleware.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.settings")
 	ctx.Data["PageIsSettingsCollaboration"] = true
 
-	repoLink := path.Join(ctx.Repo.Owner.LowerName, ctx.Repo.Repository.LowerName)
-
 	if ctx.Req.Method == "POST" {
 		name := strings.ToLower(ctx.Query("collaborator"))
 		if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
 			ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path)
 			return
 		}
-		has, err := models.HasAccess(name, repoLink, models.WRITABLE)
-		if err != nil {
-			ctx.Handle(500, "HasAccess", err)
-			return
-		} else if has {
-			ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path)
-			return
-		}
 
 		u, err := models.GetUserByName(name)
 		if err != nil {
@@ -204,9 +189,8 @@ func SettingsCollaboration(ctx *middleware.Context) {
 			return
 		}
 
-		if err = models.AddAccess(&models.Access{UserName: name, RepoName: repoLink,
-			Mode: models.WRITABLE}); err != nil {
-			ctx.Handle(500, "AddAccess", err)
+		if err = ctx.Repo.Repository.AddCollaborator(u); err != nil {
+			ctx.Handle(500, "AddCollaborator", err)
 			return
 		}
 
@@ -225,50 +209,27 @@ func SettingsCollaboration(ctx *middleware.Context) {
 	// Delete collaborator.
 	remove := strings.ToLower(ctx.Query("remove"))
 	if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName {
-		needDelete := true
-		if ctx.User.IsOrganization() {
-			// Check if user belongs to a team that has access to this repository.
-			auth, err := models.GetHighestAuthorize(ctx.Repo.Owner.Id, ctx.User.Id, ctx.Repo.Repository.Id, 0)
-			if err != nil {
-				ctx.Handle(500, "GetHighestAuthorize", err)
-				return
-			}
-			if auth > 0 {
-				needDelete = false
-			}
+		u, err := models.GetUserByName(remove)
+		if err != nil {
+			ctx.Handle(500, "GetUserByName", err)
+			return
 		}
-
-		if needDelete {
-			if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil {
-				ctx.Handle(500, "DeleteAccess", err)
-				return
-			}
+		if err := ctx.Repo.Repository.DeleteCollaborator(u); err != nil {
+			ctx.Handle(500, "DeleteCollaborator", err)
+			return
 		}
 		ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
 		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 		return
 	}
 
-	names, err := models.GetCollaboratorNames(repoLink)
+	users, err := ctx.Repo.Repository.GetCollaborators()
 	if err != nil {
 		ctx.Handle(500, "GetCollaborators", err)
 		return
 	}
 
-	collaborators := make([]*models.User, 0, len(names))
-	for _, name := range names {
-		u, err := models.GetUserByName(name)
-		if err != nil {
-			ctx.Handle(500, "GetUserByName", err)
-			return
-		}
-		// Does not show organization members.
-		if ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgMember(u.Id) {
-			continue
-		}
-		collaborators = append(collaborators, u)
-	}
-	ctx.Data["Collaborators"] = collaborators
+	ctx.Data["Collaborators"] = users
 	ctx.HTML(200, COLLABORATION)
 }
 

+ 24 - 10
routers/user/home.go

@@ -49,13 +49,19 @@ func Dashboard(ctx *middleware.Context) {
 	} else {
 		// Normal user.
 		ctxUser = ctx.User
-		collaborates, err := models.GetCollaborativeRepos(ctxUser.Name)
+		collaborates, err := ctx.User.GetAccessibleRepositories()
 		if err != nil {
-			ctx.Handle(500, "GetCollaborativeRepos", err)
+			ctx.Handle(500, "GetAccessibleRepositories", err)
 			return
 		}
-		ctx.Data["CollaborateCount"] = len(collaborates)
-		ctx.Data["CollaborativeRepos"] = collaborates
+
+		repositories := make([]*models.Repository, 0, len(collaborates))
+		for repo := range collaborates {
+			repositories = append(repositories, repo)
+		}
+
+		ctx.Data["CollaborateCount"] = len(repositories)
+		ctx.Data["CollaborativeRepos"] = repositories
 	}
 	ctx.Data["ContextUser"] = ctxUser
 
@@ -97,10 +103,14 @@ func Dashboard(ctx *middleware.Context) {
 	feeds := make([]*models.Action, 0, len(actions))
 	for _, act := range actions {
 		if act.IsPrivate {
-			if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName,
-				models.READABLE); !has {
-				continue
+			// This prevents having to retrieve the repository for each action
+			repo := &models.Repository{Id: act.RepoId, IsPrivate: true}
+			if act.RepoUserName != ctx.User.LowerName {
+				if has, _ := models.HasAccess(ctx.User, repo, models.ACCESS_MODE_READ); !has {
+					continue
+				}
 			}
+
 		}
 		// FIXME: cache results?
 		u, err := models.GetUserByName(act.ActUserName)
@@ -205,10 +215,14 @@ func Profile(ctx *middleware.Context) {
 				if !ctx.IsSigned {
 					continue
 				}
-				if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName,
-					models.READABLE); !has {
-					continue
+				// This prevents having to retrieve the repository for each action
+				repo := &models.Repository{Id: act.RepoId, IsPrivate: true}
+				if act.RepoUserName != ctx.User.LowerName {
+					if has, _ := models.HasAccess(ctx.User, repo, models.ACCESS_MODE_READ); !has {
+						continue
+					}
 				}
+
 			}
 			// FIXME: cache results?
 			u, err := models.GetUserByName(act.ActUserName)

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.5.14.0222 Beta
+0.5.16.0228 Beta

+ 1 - 1
templates/ng/base/footer.tmpl

@@ -1,7 +1,7 @@
 		</div>
 		<footer id="footer">
 		    <div class="container clear">
-		        <p class="left" id="footer-rights">© 2015 GoGits · {{.i18n.Tr "version"}}: {{AppVer}} · {{.i18n.Tr "page"}}: <strong>{{LoadTimes .PageStartTime}}</strong> ·
+		        <p class="left" id="footer-rights">© 2015 Gogs · {{.i18n.Tr "version"}}: {{AppVer}} · {{.i18n.Tr "page"}}: <strong>{{LoadTimes .PageStartTime}}</strong> ·
 		            {{.i18n.Tr "template"}}: <strong>{{call .TmplLoadTimes}}</strong></p>
 
 		        <div class="right" id="footer-links">

+ 1 - 1
templates/org/home.tmpl

@@ -27,7 +27,7 @@
     </div>
     <div id="org-repo-list">
 		{{range .Repos}}
-			{{if or (not .IsPrivate) (.HasAccess $.SignedUser.Name)}}
+			{{if or (not .IsPrivate) (.HasAccess $.SignedUser)}}
 			<div class="org-repo-item">
         <ul class="org-repo-status right">
           <li><i class="octicon octicon-star"></i> {{.NumStars}}</li>

+ 1 - 1
templates/repo/header.tmpl

@@ -49,7 +49,7 @@
                 </a>
             </li>
             <li id="repo-header-fork">
-                <a id="repo-header-fork-btn" {{if or (not $.IsRepositoryTrueOwner) $.Owner.IsOrganization}}href="{{AppSubUrl}}/repo/fork?fork_id={{.Id}}"{{end}}>
+                <a id="repo-header-fork-btn" {{if or (not $.IsRepositoryAdmin) $.Owner.IsOrganization}}href="{{AppSubUrl}}/repo/fork?fork_id={{.Id}}"{{end}}>
                     <button class="btn btn-gray text-bold btn-radius">
                         <i class="octicon octicon-repo-forked"></i>{{$.i18n.Tr "repo.fork"}}
                         <span class="num">{{.NumForks}}</span>

+ 1 - 1
templates/repo/sidebar.tmpl

@@ -20,7 +20,7 @@
         <!-- <li>
             <a class="radius" href="#"><i class="octicon octicon-organization"></i>contributors <span class="num right label label-gray label-radius">43</span></a>
         </li> -->
-        {{if .IsRepositoryTrueOwner}}
+        {{if .IsRepositoryAdmin}}
         <li class="border-bottom"></li>
         <li>
             <a class="radius" href="{{.RepoLink}}/settings"><i class="octicon octicon-tools"></i>{{.i18n.Tr "repo.settings"}}</a>

+ 1 - 1
templates/repo/toolbar.tmpl

@@ -35,7 +35,7 @@
                             <li><a href="#">Pulse</a></li>
                             <li><a href="#">Network</a></li>
                         </ul>
-                    </li> -->{{end}}{{if .IsRepositoryTrueOwner}}
+                    </li> -->{{end}}{{if .IsRepositoryAdmin}}
                     <li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="{{.RepoLink}}/settings">Settings</a>
                     </li>{{end}}
                 </ul>

+ 1 - 1
templates/user/profile.tmpl

@@ -74,7 +74,7 @@
                     <div class="tab-pane active">
                         <div id="org-repo-list">
                             {{range .Repos}}
-                                {{if or (not .IsPrivate) (.HasAccess $.SignedUserName)}}
+                                {{if or (not .IsPrivate) (.HasAccess $.SignedUser)}}
                                 <div class="org-repo-item">
                                     <ul class="org-repo-status right">
                                         <li><i class="octicon octicon-star"></i> {{.NumStars}}</li>

Some files were not shown because too many files changed in this diff