Able to fork repo to individuals
This commit is contained in:
		
							parent
							
								
									d7d167ac63
								
							
						
					
					
						commit
						a342d58d7e
					
				
					 12 changed files with 305 additions and 210 deletions
				
			
		|  | @ -5,7 +5,7 @@ Gogs(Go Git Service) is a painless self-hosted Git Service written in Go. | |||
| 
 | ||||
|  | ||||
| 
 | ||||
| ##### Current version: 0.5.5 Beta | ||||
| ##### Current version: 0.5.6 Beta | ||||
| 
 | ||||
| ### NOTICES | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。 | |||
| 
 | ||||
|  | ||||
| 
 | ||||
| ##### 当前版本:0.5.5 Beta | ||||
| ##### 当前版本:0.5.6 Beta | ||||
| 
 | ||||
| ## 开发目的 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								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()) | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
							
								
								
									
										383
									
								
								models/repo.go
									
									
									
									
									
								
							
							
						
						
									
										383
									
								
								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 | ||||
| } | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  | @ -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) | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| 0.5.5.1018 Beta | ||||
| 0.5.6.1019 Beta | ||||
|  | @ -1,11 +1,13 @@ | |||
| {{with .Repository}} | ||||
| <div id="repo-header" class="clear"> | ||||
|     <div class="container clear"> | ||||
|         <h1 id="repo-header-name" class="left public"> | ||||
|             <i class="mega-octicon octicon-{{if .Repository.IsPrivate}}lock{{else if .Repository.IsMirror}}repo-clone{{else}}repo{{end}}"></i> | ||||
|             <i class="mega-octicon octicon-{{if .IsPrivate}}lock{{else if .IsMirror}}repo-clone{{else if .IsFork}}repo-forked{{else}}repo{{end}}"></i> | ||||
|             <a class="author" href="{{AppSubUrl}}/{{.Owner.Name}}">{{.Owner.Name}}</a> | ||||
|             <span class="divider">/</span> | ||||
|             <a class="repo text-bold" href="{{.RepoLink}}">{{.Repository.Name}}</a> | ||||
|             {{if .Repository.IsMirror}}<span class="label label-gray">{{.i18n.Tr "mirror"}}</span>{{end}} | ||||
|             <a class="repo text-bold" href="{{$.RepoLink}}">{{.Name}}</a> | ||||
|             {{if .IsMirror}}<span class="label label-gray">{{$.i18n.Tr "mirror"}}</span>{{end}} | ||||
|             {{if .IsFork}}<span class="fork-flag">forked from <a href="{{.ForkRepo.RepoLink}}">{{SubStr .ForkRepo.RepoLink 1 -1}}</a></span>{{end}} | ||||
|         </h1> | ||||
|         <ul id="repo-header-meta" class="right menu menu-line"> | ||||
|             <li id="repo-header-download" class="drop"> | ||||
|  | @ -16,43 +18,44 @@ | |||
|                 </a> | ||||
|                 <div id="repo-header-download-drop" class="drop-down"> | ||||
|                     <div id="repo-clone" class="clear"> | ||||
|                         <button class="btn btn-blue left left btn-left-radius" id="repo-clone-ssh" data-link="{{.CloneLink.SSH}}">SSH</button> | ||||
|                         <button class="btn btn-gray left" id="repo-clone-https" data-link="{{.CloneLink.HTTPS}}">HTTPS</button> | ||||
|                         <input id="repo-clone-url" class="ipt ipt-disabled left" value="{{.CloneLink.SSH}}" readonly /> | ||||
|                         <button id="repo-clone-copy" class="btn btn-black left btn-right-radius" data-copy-val="val" data-copy-from="#repo-clone-url" original-title="{{.i18n.Tr "repo.click_to_copy"}}" data-original-title="{{.i18n.Tr "repo.click_to_copy"}}" data-after-title="{{.i18n.Tr "repo.copied"}}">{{.i18n.Tr "repo.copy_link"}}</button> | ||||
|                         <p class="text-center" id="repo-clone-help">{{.i18n.Tr "repo.clone_helper" | Str2html}}</p> | ||||
|                         <button class="btn btn-blue left left btn-left-radius" id="repo-clone-ssh" data-link="{{$.CloneLink.SSH}}">SSH</button> | ||||
|                         <button class="btn btn-gray left" id="repo-clone-https" data-link="{{$.CloneLink.HTTPS}}">HTTPS</button> | ||||
|                         <input id="repo-clone-url" class="ipt ipt-disabled left" value="{{$.CloneLink.SSH}}" readonly /> | ||||
|                         <button id="repo-clone-copy" class="btn btn-black left btn-right-radius" data-copy-val="val" data-copy-from="#repo-clone-url" original-title="{{$.i18n.Tr "repo.click_to_copy"}}" data-original-title="{{$.i18n.Tr "repo.click_to_copy"}}" data-after-title="{{$.i18n.Tr "repo.copied"}}">{{$.i18n.Tr "repo.copy_link"}}</button> | ||||
|                         <p class="text-center" id="repo-clone-help">{{$.i18n.Tr "repo.clone_helper" | Str2html}}</p> | ||||
|                         <hr/> | ||||
|                         <div class="text-center" id="repo-clone-zip"> | ||||
|                             <a class="btn btn-green btn-radius" href="{{.RepoLink}}/archive/{{.BranchName}}.zip"><i class="octicon octicon-file-zip"></i>ZIP</a> | ||||
|                             <a class="btn btn-green btn-radius" href="{{.RepoLink}}/archive/{{.BranchName}}.tar.gz"><i class="octicon octicon-file-zip"></i>TAR.GZ</a> | ||||
|                             <a class="btn btn-green btn-radius" href="{{$.RepoLink}}/archive/{{$.BranchName}}.zip"><i class="octicon octicon-file-zip"></i>ZIP</a> | ||||
|                             <a class="btn btn-green btn-radius" href="{{$.RepoLink}}/archive/{{$.BranchName}}.tar.gz"><i class="octicon octicon-file-zip"></i>TAR.GZ</a> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </li> | ||||
|             <li id="repo-header-watch"> | ||||
|                 <a id="repo-header-watch-btn" href="{{.RepoLink}}/action/{{if .IsWatchingRepo}}un{{end}}watch"> | ||||
|                 <a id="repo-header-watch-btn" href="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}un{{end}}watch"> | ||||
|                     <button class="btn btn-gray text-bold btn-radius"> | ||||
|                         <i class="octicon octicon-eye-watch"></i>{{if .IsWatchingRepo}}{{.i18n.Tr "repo.unwatch"}}{{else}}{{.i18n.Tr "repo.watch"}}{{end}}<span class="num">{{.Repository.NumWatches}}</span> | ||||
|                         <i class="octicon octicon-eye-watch"></i>{{if $.IsWatchingRepo}}{{$.i18n.Tr "repo.unwatch"}}{{else}}{{$.i18n.Tr "repo.watch"}}{{end}}<span class="num">{{.NumWatches}}</span> | ||||
|                     </button> | ||||
|                 </a> | ||||
|             </li> | ||||
|             <li id="repo-header-star"> | ||||
|                 <a id="repo-header-star-btn" href="{{.RepoLink}}/action/{{if .IsStaringRepo}}un{{end}}star"> | ||||
|                 <a id="repo-header-star-btn" href="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}un{{end}}star"> | ||||
|                     <button class="btn btn-gray text-bold btn-radius"> | ||||
|                         <i class="octicon octicon-star"></i>{{if .IsStaringRepo}}{{.i18n.Tr "repo.unstar"}}{{else}}{{.i18n.Tr "repo.star"}}{{end}} | ||||
|                         <span class="num">{{.Repository.NumStars}}</span> | ||||
|                         <i class="octicon octicon-star"></i>{{if $.IsStaringRepo}}{{$.i18n.Tr "repo.unstar"}}{{else}}{{$.i18n.Tr "repo.star"}}{{end}} | ||||
|                         <span class="num">{{.NumStars}}</span> | ||||
|                     </button> | ||||
|                 </a> | ||||
|             </li> | ||||
|             <li id="repo-header-fork"> | ||||
|                 <a id="repo-header-fork-btn" {{if not .IsRepositoryOwner}} href="{{.RepoLink}}/action/fork"{{end}}> | ||||
|                 <a id="repo-header-fork-btn" {{if not $.IsRepositoryTrueOwner}}href="{{.RepoLink}}/action/fork"{{end}}> | ||||
|                     <button class="btn btn-gray text-bold btn-radius"> | ||||
|                         <i class="octicon octicon-repo-forked"></i>{{.i18n.Tr "repo.fork"}} | ||||
|                         <span class="num">{{.Repository.NumForks}}</span> | ||||
|                         <i class="octicon octicon-repo-forked"></i>{{$.i18n.Tr "repo.fork"}} | ||||
|                         <span class="num">{{.NumForks}}</span> | ||||
|                     </button> | ||||
|                 </a> | ||||
|              | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
| </div> | ||||
| </div> | ||||
| {{end}} | ||||
		Loading…
	
		Reference in a new issue