From a342d58d7e208ef64d29151970244de7f7b4fac6 Mon Sep 17 00:00:00 2001 From: Unknwon Date: Sun, 19 Oct 2014 01:35:24 -0400 Subject: [PATCH] Able to fork repo to individuals --- README.md | 2 +- README_ZH.md | 2 +- gogs.go | 2 +- models/models.go | 12 +- models/repo.go | 383 ++++++++++++++++------------ modules/base/template.go | 14 +- modules/middleware/repo.go | 12 +- public/ng/css/gogs.css | 8 + public/ng/less/gogs/repository.less | 8 + routers/repo/repo.go | 29 +-- templates/.VERSION | 2 +- templates/repo/header.tmpl | 41 +-- 12 files changed, 305 insertions(+), 210 deletions(-) diff --git a/README.md b/README.md index f46d712779..36e8ca6e66 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Gogs(Go Git Service) is a painless self-hosted Git Service written in Go. ![Demo](https://gowalker.org/public/gogs_demo.gif) -##### Current version: 0.5.5 Beta +##### Current version: 0.5.6 Beta ### NOTICES diff --git a/README_ZH.md b/README_ZH.md index 783c529f6c..83addc0f4c 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。 ![Demo](https://gowalker.org/public/gogs_demo.gif) -##### 当前版本:0.5.5 Beta +##### 当前版本:0.5.6 Beta ## 开发目的 diff --git a/gogs.go b/gogs.go index c54abbe594..aa6bd75065 100644 --- a/gogs.go +++ b/gogs.go @@ -17,7 +17,7 @@ import ( "github.com/gogits/gogs/modules/setting" ) -const APP_VER = "0.5.5.1018 Beta" +const APP_VER = "0.5.6.1019 Beta" func init() { runtime.GOMAXPROCS(runtime.NumCPU()) diff --git a/models/models.go b/models/models.go index 4dcc447b34..46716728b0 100644 --- a/models/models.go +++ b/models/models.go @@ -5,6 +5,7 @@ package models import ( + "database/sql" "fmt" "os" "path" @@ -17,9 +18,16 @@ import ( "github.com/gogits/gogs/modules/setting" ) +// Engine represents a xorm engine or session. +type Engine interface { + Delete(interface{}) (int64, error) + Exec(string, ...interface{}) (sql.Result, error) + Insert(...interface{}) (int64, error) +} + var ( - x *xorm.Engine - tables []interface{} + x *xorm.Engine + tables []interface{} HasEngine bool DbCfg struct { diff --git a/models/repo.go b/models/repo.go index d156621c83..a55e91402b 100644 --- a/models/repo.go +++ b/models/repo.go @@ -133,14 +133,15 @@ func NewRepoContext() { // Repository represents a git repository. type Repository struct { - Id int64 - OwnerId int64 `xorm:"UNIQUE(s)"` - Owner *User `xorm:"-"` - ForkId int64 - LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` - Name string `xorm:"INDEX NOT NULL"` - Description string - Website string + Id int64 + OwnerId int64 `xorm:"UNIQUE(s)"` + Owner *User `xorm:"-"` + LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` + Name string `xorm:"INDEX NOT NULL"` + Description string + Website string + DefaultBranch string + NumWatches int NumStars int NumForks int @@ -154,15 +155,20 @@ type Repository struct { NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` NumOpenMilestones int `xorm:"-"` NumTags int `xorm:"-"` - IsPrivate bool - IsMirror bool - *Mirror `xorm:"-"` - IsFork bool `xorm:"NOT NULL DEFAULT false"` - IsBare bool - IsGoget bool - DefaultBranch string - Created time.Time `xorm:"CREATED"` - Updated time.Time `xorm:"UPDATED"` + + IsPrivate bool + IsBare bool + IsGoget bool + + IsMirror bool + *Mirror `xorm:"-"` + + IsFork bool `xorm:"NOT NULL DEFAULT false"` + ForkId int64 + ForkRepo *Repository `xorm:"-"` + + Created time.Time `xorm:"CREATED"` + Updated time.Time `xorm:"UPDATED"` } func (repo *Repository) GetOwner() (err error) { @@ -177,12 +183,31 @@ func (repo *Repository) GetMirror() (err error) { return err } -func (repo *Repository) GetPath() string { - return RepoPath(repo.Owner.Name, repo.Name) +func (repo *Repository) GetForkRepo() (err error) { + if !repo.IsFork { + return nil + } + + repo.ForkRepo, err = GetRepositoryById(repo.ForkId) + return err +} + +func (repo *Repository) RepoPath() (string, error) { + if err := repo.GetOwner(); err != nil { + return "", err + } + return RepoPath(repo.Owner.Name, repo.Name), nil +} + +func (repo *Repository) RepoLink() (string, error) { + if err := repo.GetOwner(); err != nil { + return "", err + } + return setting.AppSubUrl + "/" + repo.Owner.Name + "/" + repo.Name, nil } func (repo *Repository) IsOwnedBy(u *User) bool { - return repo.OwnerId == u.Id + return repo.OwnerId == u.Id } func (repo *Repository) HasAccess(uname string) bool { @@ -947,12 +972,12 @@ func DeleteRepository(uid, repoId int64, userName string) error { sess.Rollback() return err } - + if repo.IsFork { - if _, err = sess.Exec("UPDATE `repository` SET num_forks = num_forks - 1 WHERE id = ?", repo.ForkId); err != nil { - sess.Rollback() - return err - } + if _, err = sess.Exec("UPDATE `repository` SET num_forks = num_forks - 1 WHERE id = ?", repo.ForkId); err != nil { + sess.Rollback() + return err + } } if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?", uid); err != nil { @@ -1147,32 +1172,36 @@ type Watch struct { RepoId int64 `xorm:"UNIQUE(watch)"` } -// Watch or unwatch repository. -func WatchRepo(uid, repoId int64, watch bool) (err error) { +// IsWatching checks if user has watched given repository. +func IsWatching(uid, repoId int64) bool { + has, _ := x.Get(&Watch{0, uid, repoId}) + return has +} + +func watchRepoWithEngine(e Engine, uid, repoId int64, watch bool) (err error) { if watch { if IsWatching(uid, repoId) { return nil } - if _, err = x.Insert(&Watch{RepoId: repoId, UserId: uid}); err != nil { + if _, err = e.Insert(&Watch{RepoId: repoId, UserId: uid}); err != nil { return err } - _, err = x.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoId) + _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoId) } else { if !IsWatching(uid, repoId) { return nil } - if _, err = x.Delete(&Watch{0, uid, repoId}); err != nil { + if _, err = e.Delete(&Watch{0, uid, repoId}); err != nil { return err } - _, err = x.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoId) + _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoId) } return err } -// IsWatching checks if user has watched given repository. -func IsWatching(uid, rid int64) bool { - has, _ := x.Get(&Watch{0, uid, rid}) - return has +// Watch or unwatch repository. +func WatchRepo(uid, repoId int64, watch bool) (err error) { + return watchRepoWithEngine(x, uid, repoId, watch) } // GetWatchers returns all watchers of given repository. @@ -1255,137 +1284,157 @@ func IsStaring(uid, repoId int64) bool { return has } -func ForkRepository(u *User, oldRepo *Repository) (*Repository, error) { - isExist, err := IsRepositoryExist(u, oldRepo.Name) - if err != nil { - return nil, err - } else if isExist { - return nil, ErrRepoAlreadyExist - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - repo := &Repository{ - OwnerId: u.Id, - Owner: u, - Name: oldRepo.Name, - LowerName: oldRepo.LowerName, - Description: oldRepo.Description, - IsPrivate: oldRepo.IsPrivate, - IsFork: true, - ForkId: oldRepo.Id, - } - - if _, err = sess.Insert(repo); err != nil { - sess.Rollback() - return nil, err - } - - var t *Team // Owner team. - - mode := WRITABLE - - access := &Access{ - UserName: u.LowerName, - RepoName: path.Join(u.LowerName, repo.LowerName), - Mode: mode, - } - // Give access to all members in owner team. - if u.IsOrganization() { - t, err = u.GetOwnerTeam() - if err != nil { - sess.Rollback() - return nil, err - } - if err = t.GetMembers(); err != nil { - sess.Rollback() - return nil, err - } - for _, u := range t.Members { - access.Id = 0 - access.UserName = u.LowerName - if _, err = sess.Insert(access); err != nil { - sess.Rollback() - return nil, err - } - } - } else { - if _, err = sess.Insert(access); err != nil { - sess.Rollback() - return nil, err - } - } - - if _, err = sess.Exec( - "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil { - sess.Rollback() - return nil, err - } - - // Update owner team info and count. - if u.IsOrganization() { - t.RepoIds += "$" + com.ToStr(repo.Id) + "|" - t.NumRepos++ - if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { - sess.Rollback() - return nil, err - } - } - - - - if u.IsOrganization() { - t, err := u.GetOwnerTeam() - if err != nil { - log.Error(4, "GetOwnerTeam: %v", err) - } else { - if err = t.GetMembers(); err != nil { - log.Error(4, "GetMembers: %v", err) - } else { - for _, u := range t.Members { - if err = WatchRepo(u.Id, repo.Id, true); err != nil { - log.Error(4, "WatchRepo2: %v", err) - } - } - } - } - } else { - if err = WatchRepo(u.Id, repo.Id, true); err != nil { - log.Error(4, "WatchRepo3: %v", err) - } - } - - if err = NewRepoAction(u, repo); err != nil { - log.Error(4, "NewRepoAction: %v", err) - } - - if _, err = sess.Exec( - "UPDATE `repository` SET num_forks = num_forks + 1 WHERE id = ?", oldRepo.Id); err != nil { - sess.Rollback() - return nil, err - } - - if err = sess.Commit(); err != nil { - return nil, err - } - - repoPath := RepoPath(u.Name, repo.Name) - _, stderr, err := process.ExecTimeout(10*time.Minute, - fmt.Sprintf("ForkRepository: %s/%s", u.Name, repo.Name), - "git", "clone", oldRepo.GetPath(), repoPath) - - _, stderr, err = process.ExecDir(-1, - repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath), - "git", "update-server-info") - if err != nil { - return nil, errors.New("CreateRepository(git update-server-info): " + stderr) - } - - return repo, nil - +// ___________ __ +// \_ _____/__________| | __ +// | __)/ _ \_ __ \ |/ / +// | \( <_> ) | \/ < +// \___ / \____/|__| |__|_ \ +// \/ \/ +func ForkRepository(u *User, oldRepo *Repository) (*Repository, error) { + isExist, err := IsRepositoryExist(u, oldRepo.Name) + if err != nil { + return nil, err + } else if isExist { + return nil, ErrRepoAlreadyExist + } + + // In case the old repository is a fork. + if oldRepo.IsFork { + oldRepo, err = GetRepositoryById(oldRepo.ForkId) + if err != nil { + return nil, err + } + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return nil, err + } + + repo := &Repository{ + OwnerId: u.Id, + Owner: u, + Name: oldRepo.Name, + LowerName: oldRepo.LowerName, + Description: oldRepo.Description, + IsPrivate: oldRepo.IsPrivate, + IsFork: true, + ForkId: oldRepo.Id, + } + + if _, err = sess.Insert(repo); err != nil { + sess.Rollback() + return nil, err + } + + var t *Team // Owner team. + + mode := WRITABLE + + access := &Access{ + UserName: u.LowerName, + RepoName: path.Join(u.LowerName, repo.LowerName), + Mode: mode, + } + // Give access to all members in owner team. + if u.IsOrganization() { + t, err = u.GetOwnerTeam() + if err != nil { + sess.Rollback() + return nil, err + } + if err = t.GetMembers(); err != nil { + sess.Rollback() + return nil, err + } + for _, u := range t.Members { + access.Id = 0 + access.UserName = u.LowerName + if _, err = sess.Insert(access); err != nil { + sess.Rollback() + return nil, err + } + } + } else { + if _, err = sess.Insert(access); err != nil { + sess.Rollback() + return nil, err + } + } + + if _, err = sess.Exec( + "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil { + sess.Rollback() + return nil, err + } + + // Update owner team info and count. + if u.IsOrganization() { + t.RepoIds += "$" + com.ToStr(repo.Id) + "|" + t.NumRepos++ + if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { + sess.Rollback() + return nil, err + } + } + + if u.IsOrganization() { + t, err := u.GetOwnerTeam() + if err != nil { + log.Error(4, "GetOwnerTeam: %v", err) + } else { + if err = t.GetMembers(); err != nil { + log.Error(4, "GetMembers: %v", err) + } else { + for _, u := range t.Members { + if err = watchRepoWithEngine(sess, u.Id, repo.Id, true); err != nil { + log.Error(4, "WatchRepo2: %v", err) + } + } + } + } + } else { + if err = watchRepoWithEngine(sess, u.Id, repo.Id, true); err != nil { + log.Error(4, "WatchRepo3: %v", err) + } + } + + if err = NewRepoAction(u, repo); err != nil { + log.Error(4, "NewRepoAction: %v", err) + } + + if _, err = sess.Exec( + "UPDATE `repository` SET num_forks = num_forks + 1 WHERE id = ?", oldRepo.Id); err != nil { + sess.Rollback() + return nil, err + } + + oldRepoPath, err := oldRepo.RepoPath() + if err != nil { + sess.Rollback() + return nil, fmt.Errorf("fail to get repo path(%s): %v", oldRepo.Name, err) + } + + if err = sess.Commit(); err != nil { + return nil, err + } + + repoPath := RepoPath(u.Name, repo.Name) + _, stderr, err := process.ExecTimeout(10*time.Minute, + fmt.Sprintf("ForkRepository(git clone): %s/%s", u.Name, repo.Name), + "git", "clone", oldRepoPath, repoPath) + if err != nil { + return nil, errors.New("ForkRepository(git clone): " + stderr) + } + + _, stderr, err = process.ExecDir(-1, + repoPath, fmt.Sprintf("ForkRepository(git update-server-info): %s", repoPath), + "git", "update-server-info") + if err != nil { + return nil, errors.New("ForkRepository(git update-server-info): " + stderr) + } + + return repo, nil } diff --git a/modules/base/template.go b/modules/base/template.go index 6d25cd45a8..58572e24e4 100644 --- a/modules/base/template.go +++ b/modules/base/template.go @@ -110,7 +110,7 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{ "List": List, "Mail2Domain": func(mail string) string { if !strings.Contains(mail, "@") { - return "try.gogits.org" + return "try.gogs.io" } suffix := strings.SplitN(mail, "@", 2)[1] @@ -121,7 +121,17 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{ return domain }, "SubStr": func(str string, start, length int) string { - return str[start : start+length] + if len(str) == 0 { + return "" + } + end := start + length + if length == -1 { + end = len(str) + } + if len(str) < end { + return str + } + return str[start:end] }, "DiffTypeToStr": DiffTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr, diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go index 78af58eac8..fec9c54161 100644 --- a/modules/middleware/repo.go +++ b/modules/middleware/repo.go @@ -160,7 +160,11 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { return } ctx.Repo.GitRepo = gitRepo - ctx.Repo.RepoLink = setting.AppSubUrl + "/" + u.Name + "/" + repo.Name + ctx.Repo.RepoLink, err = repo.RepoLink() + if err != nil { + ctx.Handle(500, "RepoLink", err) + return + } ctx.Data["RepoLink"] = ctx.Repo.RepoLink tags, err := ctx.Repo.GitRepo.GetTags() @@ -171,6 +175,12 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { ctx.Data["Tags"] = tags ctx.Repo.Repository.NumTags = len(tags) + // Non-fork repository will not return error in this method. + if err = repo.GetForkRepo(); err != nil { + ctx.Handle(500, "GetForkRepo", err) + return + } + ctx.Data["Title"] = u.Name + "/" + repo.Name ctx.Data["Repository"] = repo ctx.Data["Owner"] = ctx.Repo.Repository.Owner diff --git a/public/ng/css/gogs.css b/public/ng/css/gogs.css index 88d2c887dc..f10eb788cb 100644 --- a/public/ng/css/gogs.css +++ b/public/ng/css/gogs.css @@ -1602,6 +1602,14 @@ The register and sign-in page style .compare-head-box .compare { padding: 0 15px 15px 15px; } +.fork-flag { + display: block; + font-size: 11px; + line-height: 10px; + white-space: nowrap; + margin-left: 44px; + margin-top: -15px; +} #admin-wrapper, #setting-wrapper { padding-bottom: 100px; diff --git a/public/ng/less/gogs/repository.less b/public/ng/less/gogs/repository.less index 05e203a774..4a8bc738da 100644 --- a/public/ng/less/gogs/repository.less +++ b/public/ng/less/gogs/repository.less @@ -647,4 +647,12 @@ .compare { padding: 0 15px 15px 15px; } +} +.fork-flag { + display: block; + font-size: 11px; + line-height: 10px; + white-space: nowrap; + margin-left: 44px; + margin-top: -15px; } \ No newline at end of file diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 8b18eb25bf..2116e2c9fc 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -217,21 +217,20 @@ func Action(ctx *middleware.Context) { err = models.StarRepo(ctx.User.Id, ctx.Repo.Repository.Id, true) case "unstar": err = models.StarRepo(ctx.User.Id, ctx.Repo.Repository.Id, false) - case "fork": - repo, error := models.ForkRepository(ctx.User, ctx.Repo.Repository) - if error != nil { - log.Error(4, "Action(%s): %v", ctx.Params(":action"), error) - ctx.JSON(200, map[string]interface{}{ - "ok": false, - "err": error.Error(), - }) - return - } - if error == nil { - ctx.Redirect(setting.AppSubUrl + "/" + repo.Owner.Name + "/" + repo.Name) - - return - } + case "fork": + repo, err := models.ForkRepository(ctx.User, ctx.Repo.Repository) + if err != nil { + if err != models.ErrRepoAlreadyExist { + log.Error(4, "Action(%s): %v", ctx.Params(":action"), err) + ctx.JSON(200, map[string]interface{}{ + "ok": false, + "err": err.Error(), + }) + return + } + } + ctx.Redirect(setting.AppSubUrl + "/" + repo.Owner.Name + "/" + repo.Name) + return case "desc": if !ctx.Repo.IsOwner { ctx.Error(404) diff --git a/templates/.VERSION b/templates/.VERSION index 38133b40a6..3afe67a377 100644 --- a/templates/.VERSION +++ b/templates/.VERSION @@ -1 +1 @@ -0.5.5.1018 Beta \ No newline at end of file +0.5.6.1019 Beta \ No newline at end of file diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 884910d1c5..c2caf4b015 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -1,11 +1,13 @@ +{{with .Repository}}
-
\ No newline at end of file + +{{end}} \ No newline at end of file