![Demo](http://gowalker.org/public/gogs_demo.gif) | ![Demo](http://gowalker.org/public/gogs_demo.gif) | ||||
##### Current version: 0.2.7 Alpha | |||||
##### Current version: 0.2.8 Alpha | |||||
#### Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in April 6, 2014 and will reset multiple times after. Please do NOT put your important data on the site. | #### Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in April 6, 2014 and will reset multiple times after. Please do NOT put your important data on the site. | ||||
- Activity timeline | - Activity timeline | ||||
- SSH/HTTP(S) protocol support. | - SSH/HTTP(S) protocol support. | ||||
- Register/delete/rename account. | - Register/delete/rename account. | ||||
- Create/delete/watch/rename/transfer public/private repository. | |||||
- Repository viewer. | |||||
- Issue tracker. | |||||
- Create/migrate/mirror/delete/watch/rename/transfer public/private repository. | |||||
- Repository viewer/issue tracker. | |||||
- Gravatar and cache support. | - Gravatar and cache support. | ||||
- Mail service(register, issue). | - Mail service(register, issue). | ||||
- Administration panel. | - Administration panel. | ||||
- Supports MySQL, PostgreSQL and SQLite3(binary release only). | |||||
- Supports MySQL, PostgreSQL and SQLite3. | |||||
## Installation | ## Installation | ||||
![Demo](http://gowalker.org/public/gogs_demo.gif) | ![Demo](http://gowalker.org/public/gogs_demo.gif) | ||||
##### 当前版本:0.2.7 Alpha | |||||
##### 当前版本:0.2.8 Alpha | |||||
## 开发目的 | ## 开发目的 | ||||
- 活动时间线 | - 活动时间线 | ||||
- SSH/HTTP(S) 协议支持 | - SSH/HTTP(S) 协议支持 | ||||
- 注册/删除/重命名用户 | - 注册/删除/重命名用户 | ||||
- 创建/删除/关注/重命名/转移 公开/私有 仓库 | |||||
- 仓库浏览器 | |||||
- Bug 追踪系统 | |||||
- 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库 | |||||
- 仓库 浏览器/Bug 追踪 | |||||
- Gravatar 以及缓存支持 | - Gravatar 以及缓存支持 | ||||
- 邮件服务(注册、Issue) | - 邮件服务(注册、Issue) | ||||
- 管理员面板 | - 管理员面板 | ||||
- 支持 MySQL、PostgreSQL 以及 SQLite3(仅限二进制版本) | |||||
- 支持 MySQL、PostgreSQL 以及 SQLite3 | |||||
## 安装部署 | ## 安装部署 | ||||
// Test that go1.2 tag above is included in builds. main.go refers to this definition. | // Test that go1.2 tag above is included in builds. main.go refers to this definition. | ||||
const go12tag = true | const go12tag = true | ||||
const APP_VER = "0.2.7.0411 Alpha" | |||||
const APP_VER = "0.2.8.0412 Alpha" | |||||
func init() { | func init() { | ||||
base.AppVer = APP_VER | base.AppVer = APP_VER |
type Access struct { | type Access struct { | ||||
Id int64 | Id int64 | ||||
UserName string `xorm:"unique(s)"` | UserName string `xorm:"unique(s)"` | ||||
RepoName string `xorm:"unique(s)"` | |||||
RepoName string `xorm:"unique(s)"` // <user name>/<repo name> | |||||
Mode int `xorm:"unique(s)"` | Mode int `xorm:"unique(s)"` | ||||
Created time.Time `xorm:"created"` | Created time.Time `xorm:"created"` | ||||
} | } |
func init() { | func init() { | ||||
tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch), | tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch), | ||||
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow)) | |||||
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow), | |||||
new(Mirror)) | |||||
} | } | ||||
func LoadModelsConfig() { | func LoadModelsConfig() { |
NumClosedIssues int | NumClosedIssues int | ||||
NumOpenIssues int `xorm:"-"` | NumOpenIssues int `xorm:"-"` | ||||
IsPrivate bool | IsPrivate bool | ||||
IsMirror bool | |||||
IsBare bool | IsBare bool | ||||
IsGoget bool | IsGoget bool | ||||
DefaultBranch string | DefaultBranch string | ||||
return true | return true | ||||
} | } | ||||
// Mirror represents a mirror information of repository. | |||||
type Mirror struct { | |||||
Id int64 | |||||
RepoId int64 | |||||
RepoName string // <user name>/<repo name> | |||||
Interval int // Hour. | |||||
Updated time.Time `xorm:"UPDATED"` | |||||
NextUpdate time.Time | |||||
} | |||||
// MirrorRepository creates a mirror repository from source. | |||||
func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error { | |||||
_, stderr, err := com.ExecCmd("git", "clone", "--mirror", url, repoPath) | |||||
if err != nil { | |||||
return err | |||||
} else if strings.Contains(stderr, "fatal:") { | |||||
return errors.New(stderr) | |||||
} | |||||
if _, err = orm.InsertOne(&Mirror{ | |||||
RepoId: repoId, | |||||
RepoName: strings.ToLower(userName + "/" + repoName), | |||||
Interval: 24, | |||||
NextUpdate: time.Now().Add(24 * time.Hour), | |||||
}); err != nil { | |||||
return err | |||||
} | |||||
return git.UnpackRefs(repoPath) | |||||
} | |||||
// MigrateRepository migrates a existing repository from other project hosting. | |||||
func MigrateRepository(user *User, name, desc string, private, mirror bool, url string) (*Repository, error) { | |||||
repo, err := CreateRepository(user, name, desc, "", "", private, mirror, false) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
// Clone to temprory path and do the init commit. | |||||
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond())) | |||||
os.MkdirAll(tmpDir, os.ModePerm) | |||||
repoPath := RepoPath(user.Name, name) | |||||
repo.IsBare = false | |||||
if mirror { | |||||
if err = MirrorRepository(repo.Id, user.Name, repo.Name, repoPath, url); err != nil { | |||||
return repo, err | |||||
} | |||||
repo.IsMirror = true | |||||
return repo, UpdateRepository(repo) | |||||
} | |||||
// Clone from local repository. | |||||
_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir) | |||||
if err != nil { | |||||
return repo, err | |||||
} else if strings.Contains(stderr, "fatal:") { | |||||
return repo, errors.New("git clone: " + stderr) | |||||
} | |||||
// Pull data from source. | |||||
_, stderr, err = com.ExecCmdDir(tmpDir, "git", "pull", url) | |||||
if err != nil { | |||||
return repo, err | |||||
} else if strings.Contains(stderr, "fatal:") { | |||||
return repo, errors.New("git pull: " + stderr) | |||||
} | |||||
// Push data to local repository. | |||||
if _, stderr, err = com.ExecCmdDir(tmpDir, "git", "push", "origin", "master"); err != nil { | |||||
return repo, err | |||||
} else if strings.Contains(stderr, "fatal:") { | |||||
return repo, errors.New("git push: " + stderr) | |||||
} | |||||
return repo, UpdateRepository(repo) | |||||
} | |||||
// CreateRepository creates a repository for given user or orgnaziation. | // CreateRepository creates a repository for given user or orgnaziation. | ||||
func CreateRepository(user *User, repoName, desc, repoLang, license string, private bool, initReadme bool) (*Repository, error) { | |||||
if !IsLegalName(repoName) { | |||||
func CreateRepository(user *User, name, desc, lang, license string, private, mirror, initReadme bool) (*Repository, error) { | |||||
if !IsLegalName(name) { | |||||
return nil, ErrRepoNameIllegal | return nil, ErrRepoNameIllegal | ||||
} | } | ||||
isExist, err := IsRepositoryExist(user, repoName) | |||||
isExist, err := IsRepositoryExist(user, name) | |||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} else if isExist { | } else if isExist { | ||||
repo := &Repository{ | repo := &Repository{ | ||||
OwnerId: user.Id, | OwnerId: user.Id, | ||||
Name: repoName, | |||||
LowerName: strings.ToLower(repoName), | |||||
Name: name, | |||||
LowerName: strings.ToLower(name), | |||||
Description: desc, | Description: desc, | ||||
IsPrivate: private, | IsPrivate: private, | ||||
IsBare: repoLang == "" && license == "" && !initReadme, | |||||
IsBare: lang == "" && license == "" && !initReadme, | |||||
} | } | ||||
repoPath := RepoPath(user.Name, repoName) | |||||
repoPath := RepoPath(user.Name, repo.Name) | |||||
sess := orm.NewSession() | sess := orm.NewSession() | ||||
defer sess.Close() | defer sess.Close() | ||||
if err2 := os.RemoveAll(repoPath); err2 != nil { | if err2 := os.RemoveAll(repoPath); err2 != nil { | ||||
log.Error("repo.CreateRepository(repo): %v", err) | log.Error("repo.CreateRepository(repo): %v", err) | ||||
return nil, errors.New(fmt.Sprintf( | return nil, errors.New(fmt.Sprintf( | ||||
"delete repo directory %s/%s failed(1): %v", user.Name, repoName, err2)) | |||||
"delete repo directory %s/%s failed(1): %v", user.Name, repo.Name, err2)) | |||||
} | } | ||||
sess.Rollback() | sess.Rollback() | ||||
return nil, err | return nil, err | ||||
} | } | ||||
mode := AU_WRITABLE | |||||
if mirror { | |||||
mode = AU_READABLE | |||||
} | |||||
access := Access{ | access := Access{ | ||||
UserName: user.LowerName, | UserName: user.LowerName, | ||||
RepoName: strings.ToLower(path.Join(user.Name, repo.Name)), | RepoName: strings.ToLower(path.Join(user.Name, repo.Name)), | ||||
Mode: AU_WRITABLE, | |||||
Mode: mode, | |||||
} | } | ||||
if _, err = sess.Insert(&access); err != nil { | if _, err = sess.Insert(&access); err != nil { | ||||
sess.Rollback() | sess.Rollback() | ||||
if err2 := os.RemoveAll(repoPath); err2 != nil { | if err2 := os.RemoveAll(repoPath); err2 != nil { | ||||
log.Error("repo.CreateRepository(access): %v", err) | log.Error("repo.CreateRepository(access): %v", err) | ||||
return nil, errors.New(fmt.Sprintf( | return nil, errors.New(fmt.Sprintf( | ||||
"delete repo directory %s/%s failed(2): %v", user.Name, repoName, err2)) | |||||
"delete repo directory %s/%s failed(2): %v", user.Name, repo.Name, err2)) | |||||
} | } | ||||
return nil, err | return nil, err | ||||
} | } | ||||
if err2 := os.RemoveAll(repoPath); err2 != nil { | if err2 := os.RemoveAll(repoPath); err2 != nil { | ||||
log.Error("repo.CreateRepository(repo count): %v", err) | log.Error("repo.CreateRepository(repo count): %v", err) | ||||
return nil, errors.New(fmt.Sprintf( | return nil, errors.New(fmt.Sprintf( | ||||
"delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2)) | |||||
"delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2)) | |||||
} | } | ||||
return nil, err | return nil, err | ||||
} | } | ||||
if err2 := os.RemoveAll(repoPath); err2 != nil { | if err2 := os.RemoveAll(repoPath); err2 != nil { | ||||
log.Error("repo.CreateRepository(commit): %v", err) | log.Error("repo.CreateRepository(commit): %v", err) | ||||
return nil, errors.New(fmt.Sprintf( | return nil, errors.New(fmt.Sprintf( | ||||
"delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2)) | |||||
"delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2)) | |||||
} | } | ||||
return nil, err | return nil, err | ||||
} | } | ||||
log.Error("repo.CreateRepository(WatchRepo): %v", err) | log.Error("repo.CreateRepository(WatchRepo): %v", err) | ||||
} | } | ||||
if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil { | |||||
// No need for init for mirror. | |||||
if mirror { | |||||
return repo, nil | |||||
} | |||||
if err = initRepository(repoPath, user, repo, initReadme, lang, license); err != nil { | |||||
return nil, err | return nil, err | ||||
} | } | ||||
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond())) | tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond())) | ||||
os.MkdirAll(tmpDir, os.ModePerm) | os.MkdirAll(tmpDir, os.ModePerm) | ||||
if _, _, err := com.ExecCmd("git", "clone", repoPath, tmpDir); err != nil { | |||||
_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir) | |||||
if err != nil { | |||||
return err | return err | ||||
} | } | ||||
if len(stderr) > 0 { | |||||
log.Trace("repo.initRepository(git clone): %s", stderr) | |||||
} | |||||
// README | // README | ||||
if initReadme { | if initReadme { | ||||
return urepos, nil | return urepos, nil | ||||
} | } | ||||
// RepoPath returns repository path by given user and repository name. | |||||
func RepoPath(userName, repoName string) string { | func RepoPath(userName, repoName string) string { | ||||
return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") | return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") | ||||
} | } | ||||
sess.Rollback() | sess.Rollback() | ||||
return err | return err | ||||
} | } | ||||
rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?" | |||||
if _, err = sess.Exec(rawSql, userId); err != nil { | |||||
if _, err := sess.Delete(&Action{RepoId: repo.Id}); err != nil { | |||||
sess.Rollback() | sess.Rollback() | ||||
return err | return err | ||||
} | } | ||||
sess.Rollback() | sess.Rollback() | ||||
return err | return err | ||||
} | } | ||||
rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?" | |||||
if _, err = sess.Exec(rawSql, userId); err != nil { | |||||
sess.Rollback() | |||||
return err | |||||
} | |||||
if err = sess.Commit(); err != nil { | if err = sess.Commit(); err != nil { | ||||
sess.Rollback() | sess.Rollback() | ||||
return err | return err |
case binding.MaxSizeError: | case binding.MaxSizeError: | ||||
data["ErrorMsg"] = form.Name(field.Name) + " must contain at most " + getMinMaxSize(field) + " characters" | data["ErrorMsg"] = form.Name(field.Name) + " must contain at most " + getMinMaxSize(field) + " characters" | ||||
case binding.EmailError: | case binding.EmailError: | ||||
data["ErrorMsg"] = form.Name(field.Name) + " is not valid" | |||||
data["ErrorMsg"] = form.Name(field.Name) + " is not a valid e-mail address" | |||||
case binding.UrlError: | |||||
data["ErrorMsg"] = form.Name(field.Name) + " is not a valid URL" | |||||
default: | default: | ||||
data["ErrorMsg"] = "Unknown error: " + err | data["ErrorMsg"] = "Unknown error: " + err | ||||
} | } |
type CreateRepoForm struct { | type CreateRepoForm struct { | ||||
RepoName string `form:"repo" binding:"Required;AlphaDash"` | RepoName string `form:"repo" binding:"Required;AlphaDash"` | ||||
Private string `form:"private"` | |||||
Private bool `form:"private"` | |||||
Description string `form:"desc" binding:"MaxSize(100)"` | Description string `form:"desc" binding:"MaxSize(100)"` | ||||
Language string `form:"language"` | Language string `form:"language"` | ||||
License string `form:"license"` | License string `form:"license"` | ||||
InitReadme string `form:"initReadme"` | |||||
InitReadme bool `form:"initReadme"` | |||||
} | } | ||||
func (f *CreateRepoForm) Name(field string) string { | func (f *CreateRepoForm) Name(field string) string { | ||||
validate(errors, data, f) | validate(errors, data, f) | ||||
} | } | ||||
type MigrateRepoForm struct { | |||||
Url string `form:"url" binding:"Url"` | |||||
AuthUserName string `form:"auth_username"` | |||||
AuthPasswd string `form:"auth_password"` | |||||
RepoName string `form:"repo" binding:"Required;AlphaDash"` | |||||
Mirror bool `form:"mirror"` | |||||
Private bool `form:"private"` | |||||
Description string `form:"desc" binding:"MaxSize(100)"` | |||||
} | |||||
func (f *MigrateRepoForm) Name(field string) string { | |||||
names := map[string]string{ | |||||
"Url": "Migration URL", | |||||
"RepoName": "Repository name", | |||||
"Description": "Description", | |||||
} | |||||
return names[field] | |||||
} | |||||
func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { | |||||
if req.Method == "GET" || errors.Count() == 0 { | |||||
return | |||||
} | |||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData) | |||||
data["HasError"] = true | |||||
AssignForm(f, data) | |||||
if len(errors.Overall) > 0 { | |||||
for _, err := range errors.Overall { | |||||
log.Error("MigrateRepoForm.Validate: %v", err) | |||||
} | |||||
return | |||||
} | |||||
validate(errors, data, f) | |||||
} |
ctx.Data["IsRepositoryWatching"] = ctx.Repo.IsWatching | ctx.Data["IsRepositoryWatching"] = ctx.Repo.IsWatching | ||||
} | } | ||||
} | } | ||||
func WriteAccess() martini.Handler { | |||||
return func(ctx *Context) { | |||||
if ctx.Repo.Repository.IsPrivate { | |||||
ctx.Repo.HasAccess = false | |||||
ctx.Data["HasAccess"] = false | |||||
if ctx.User == nil { | |||||
ctx.Handle(404, "WriteAccess", nil) | |||||
return | |||||
} | |||||
hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+ctx.Repo.Repository.Name, models.AU_WRITABLE) | |||||
if err != nil { | |||||
ctx.Handle(500, "WriteAccess(HasAccess)", err) | |||||
return | |||||
} else if !hasAccess { | |||||
ctx.Handle(404, "WriteAccess(HasAccess)", nil) | |||||
return | |||||
} | |||||
} | |||||
ctx.Repo.HasAccess = true | |||||
ctx.Data["HasAccess"] = true | |||||
} | |||||
} |
return | return | ||||
} | } | ||||
_, err := models.CreateRepository(ctx.User, form.RepoName, form.Description, | |||||
form.Language, form.License, form.Private == "on", form.InitReadme == "on") | |||||
repo, err := models.CreateRepository(ctx.User, form.RepoName, form.Description, | |||||
form.Language, form.License, form.Private, false, form.InitReadme) | |||||
if err == nil { | if err == nil { | ||||
log.Trace("%s Repository created: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName) | log.Trace("%s Repository created: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName) | ||||
ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName) | ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName) | ||||
ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/create", &form) | ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/create", &form) | ||||
return | return | ||||
} | } | ||||
if repo != nil { | |||||
if errDelete := models.DeleteRepository(ctx.User.Id, repo.Id, ctx.User.Name); errDelete != nil { | |||||
log.Error("repo.MigratePost(CreatePost): %v", errDelete) | |||||
} | |||||
} | |||||
ctx.Handle(500, "repo.Create", err) | ctx.Handle(500, "repo.Create", err) | ||||
} | } | ||||
func Mirror(ctx *middleware.Context) { | |||||
ctx.Data["Title"] = "Mirror repository" | |||||
func Migrate(ctx *middleware.Context) { | |||||
ctx.Data["Title"] = "Migrate repository" | |||||
ctx.Data["PageIsNewRepo"] = true | ctx.Data["PageIsNewRepo"] = true | ||||
ctx.HTML(200, "repo/mirror") | |||||
ctx.HTML(200, "repo/migrate") | |||||
} | } | ||||
func MirrorPost(ctx *middleware.Context, form auth.CreateRepoForm) { | |||||
ctx.Data["Title"] = "Mirror repository" | |||||
func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) { | |||||
ctx.Data["Title"] = "Migrate repository" | |||||
ctx.Data["PageIsNewRepo"] = true | ctx.Data["PageIsNewRepo"] = true | ||||
if ctx.HasError() { | if ctx.HasError() { | ||||
ctx.HTML(200, "repo/mirror") | |||||
ctx.HTML(200, "repo/migrate") | |||||
return | return | ||||
} | } | ||||
_, err := models.CreateRepository(ctx.User, form.RepoName, form.Description, | |||||
"", form.License, form.Private == "on", false) | |||||
url := strings.Replace(form.Url, "://", fmt.Sprintf("://%s:%s@", form.AuthUserName, form.AuthPasswd), 1) | |||||
repo, err := models.MigrateRepository(ctx.User, form.RepoName, form.Description, form.Private, | |||||
form.Mirror, url) | |||||
if err == nil { | if err == nil { | ||||
log.Trace("%s Repository created: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName) | |||||
log.Trace("%s Repository migrated: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName) | |||||
ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName) | ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName) | ||||
return | return | ||||
} else if err == models.ErrRepoAlreadyExist { | } else if err == models.ErrRepoAlreadyExist { | ||||
ctx.RenderWithErr("Repository name has already been used", "repo/mirror", &form) | |||||
ctx.RenderWithErr("Repository name has already been used", "repo/migrate", &form) | |||||
return | return | ||||
} else if err == models.ErrRepoNameIllegal { | } else if err == models.ErrRepoNameIllegal { | ||||
ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/mirror", &form) | |||||
ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/migrate", &form) | |||||
return | |||||
} | |||||
if repo != nil { | |||||
if errDelete := models.DeleteRepository(ctx.User.Id, repo.Id, ctx.User.Name); errDelete != nil { | |||||
log.Error("repo.MigratePost(DeleteRepository): %v", errDelete) | |||||
} | |||||
} | |||||
if strings.Contains(err.Error(), "Authentication failed") { | |||||
ctx.RenderWithErr(err.Error(), "repo/migrate", &form) | |||||
return | return | ||||
} | } | ||||
ctx.Handle(500, "repo.Mirror", err) | |||||
ctx.Handle(500, "repo.Migrate", err) | |||||
} | } | ||||
func Single(ctx *middleware.Context, params martini.Params) { | func Single(ctx *middleware.Context, params martini.Params) { | ||||
"ok": true, | "ok": true, | ||||
}) | }) | ||||
} | } | ||||
func Import(ctx *middleware.Context, params martini.Params) { | |||||
ctx.ResponseWriter.Write([]byte("not done yet")) | |||||
} |
ctx.HTML(200, "user/profile") | ctx.HTML(200, "user/profile") | ||||
} | } | ||||
func Email2User(ctx *middleware.Context) { | |||||
u, err := models.GetUserByEmail(ctx.Query("email")) | |||||
if err != nil { | |||||
if err == models.ErrUserNotExist { | |||||
ctx.Handle(404, "user.Email2User", err) | |||||
} else { | |||||
ctx.Handle(500, "user.Email2User(GetUserByEmail)", err) | |||||
} | |||||
return | |||||
} | |||||
ctx.Redirect("/user/" + u.Name) | |||||
} | |||||
func SignIn(ctx *middleware.Context) { | func SignIn(ctx *middleware.Context) { | ||||
ctx.Data["Title"] = "Log In" | ctx.Data["Title"] = "Log In" | ||||
<div class="dropdown-menu"> | <div class="dropdown-menu"> | ||||
<ul class="list-unstyled"> | <ul class="list-unstyled"> | ||||
<li><a href="/repo/create"><i class="fa fa-book"></i>Repository</a></li> | <li><a href="/repo/create"><i class="fa fa-book"></i>Repository</a></li> | ||||
<li><a href="/repo/mirror"><i class="fa fa-clipboard"></i>Mirror</a></li> | |||||
<li><a href="/repo/migrate"><i class="fa fa-clipboard"></i>Migration</a></li> | |||||
<!-- <li><a href="#"><i class="fa fa-users"></i>Organization</a></li> --> | <!-- <li><a href="#"><i class="fa fa-users"></i>Organization</a></li> --> | ||||
</ul> | </ul> | ||||
</div> | </div> |
{{$r := List .Commits}} | {{$r := List .Commits}} | ||||
{{range $r}} | {{range $r}} | ||||
<tr> | <tr> | ||||
<td class="author"><img class="avatar" src="{{AvatarLink .Author.Email}}" alt=""/><a href="/user/{{.Author.Name}}">{{.Author.Name}}</a></td> | |||||
<td class="author"><img class="avatar" src="{{AvatarLink .Author.Email}}" alt=""/><a href="/user/email2user?email={{.Author.Email}}">{{.Author.Name}}</a></td> | |||||
<td class="sha"><a class="label label-success" href="/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td> | <td class="sha"><a class="label label-success" href="/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td> | ||||
<td class="message">{{.Message}} </td> | <td class="message">{{.Message}} </td> | ||||
<td class="date">{{TimeSince .Author.When}}</td> | <td class="date">{{TimeSince .Author.When}}</td> |
</span> | </span> | ||||
<p class="author"> | <p class="author"> | ||||
<img class="avatar" src="{{AvatarLink .Commit.Author.Email}}" alt=""/> | <img class="avatar" src="{{AvatarLink .Commit.Author.Email}}" alt=""/> | ||||
<a class="name" href="#"><strong>{{.Commit.Author.Name}}</strong></a> | |||||
<a class="name" href="/user/email2user?email={{.Commit.Author.Email}}"><strong>{{.Commit.Author.Name}}</strong></a> | |||||
<span class="time">{{TimeSince .Commit.Author.When}}</span> | <span class="time">{{TimeSince .Commit.Author.When}}</span> | ||||
</p> | </p> | ||||
</div> | </div> |
{{template "base/head" .}} | {{template "base/head" .}} | ||||
{{template "base/navbar" .}} | {{template "base/navbar" .}} | ||||
<div class="container" id="body"> | <div class="container" id="body"> | ||||
<form action="/repo/create" method="post" class="form-horizontal card" id="repo-create"> | |||||
<form action="/repo/migrate" method="post" class="form-horizontal card" id="repo-create"> | |||||
{{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
<h3>Create Repository Mirror</h3> | |||||
<h3>Repository Migration</h3> | |||||
{{template "base/alert" .}} | {{template "base/alert" .}} | ||||
<div class="form-group"> | |||||
<!-- <div class="form-group"> | |||||
<label class="col-md-2 control-label">From<strong class="text-danger">*</strong></label> | <label class="col-md-2 control-label">From<strong class="text-danger">*</strong></label> | ||||
<div class="col-md-8"> | <div class="col-md-8"> | ||||
<select class="form-control" name="from"> | <select class="form-control" name="from"> | ||||
<option value="">GitHub</option> | |||||
<option value="github">GitHub</option> | |||||
</select> | </select> | ||||
</div> | </div> | ||||
</div> | |||||
</div> --> | |||||
<div class="form-group"> | <div class="form-group"> | ||||
<label class="col-md-2 control-label">URL<strong class="text-danger">*</strong></label> | |||||
<label class="col-md-2 control-label">HTTPS URL<strong class="text-danger">*</strong></label> | |||||
<div class="col-md-8"> | <div class="col-md-8"> | ||||
<input name="url" type="text" class="form-control" placeholder="Type your mirror repository url link" required="required"> | |||||
<input name="url" type="text" class="form-control" placeholder="Type your migration repository HTTPS URL" value="{{.url}}" required="required" > | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<div class="col-md-offset-2 col-md-8"> | <div class="col-md-offset-2 col-md-8"> | ||||
<a class="btn btn-default" data-toggle="collapse" data-target="#repo-import-auth">Need Authorization</a> | <a class="btn btn-default" data-toggle="collapse" data-target="#repo-import-auth">Need Authorization</a> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<label class="col-md-2 control-label">Username</label> | <label class="col-md-2 control-label">Username</label> | ||||
<div class="col-md-8"> | <div class="col-md-8"> | ||||
<input name="auth-username" type="text" class="form-control"> | |||||
<input name="auth_username" type="text" class="form-control" placeholder="Type your user name" value="{{.auth_username}}" > | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<label class="col-md-2 control-label">Password</label> | <label class="col-md-2 control-label">Password</label> | ||||
<div class="col-md-8"> | <div class="col-md-8"> | ||||
<input name="auth-password" type="text" class="form-control"> | |||||
<input name="auth_password" type="password" class="form-control" placeholder="Type your password" value="{{.auth_password}}" > | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<label class="col-md-2 control-label">Visibility<strong class="text-danger">*</strong></label> | |||||
<label class="col-md-2 control-label">Migration Type</label> | |||||
<div class="col-md-8"> | <div class="col-md-8"> | ||||
<p class="form-control-static">Public</p> | |||||
<input type="hidden" value="public" name="visibility"/> | |||||
<div class="checkbox"> | |||||
<label> | |||||
<input type="checkbox" name="mirror" {{if .mirror}}checked{{end}}> | |||||
<strong>This repository is a mirror</strong> | |||||
</label> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="form-group"> | |||||
<label class="col-md-2 control-label">Visibility</label> | |||||
<div class="col-md-8"> | |||||
<div class="checkbox"> | |||||
<label> | |||||
<input type="checkbox" name="private" {{if .private}}checked{{end}}> | |||||
<strong>This repository is private</strong> | |||||
</label> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<div class="col-md-offset-2 col-md-8"> | <div class="col-md-offset-2 col-md-8"> | ||||
<button type="submit" class="btn btn-lg btn-primary">Mirror repository</button> | |||||
<button type="submit" class="btn btn-lg btn-primary">Migrate repository</button> | |||||
<a href="/" class="text-danger">Cancel</a> | <a href="/" class="text-danger">Cancel</a> | ||||
</div> | </div> | ||||
</div> | </div> |
<div class="container"> | <div class="container"> | ||||
<div class="row"> | <div class="row"> | ||||
<div class="col-md-7"> | <div class="col-md-7"> | ||||
<h3 class="name"><i class="fa fa-book fa-lg"></i><a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / <a href="/{{.Owner.Name}}/{{.Repository.Name}}">{{.Repository.Name}}</a>{{if .Repository.IsPrivate}} <span class="label label-default">Private</span> {{end}}</h3> | |||||
<h3 class="name"><i class="fa fa-book fa-lg"></i><a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / <a href="/{{.Owner.Name}}/{{.Repository.Name}}">{{.Repository.Name}}</a> {{if .Repository.IsPrivate}}<span class="label label-default">Private</span>{{else if .Repository.IsMirror}}<span class="label label-default">Mirror</span>{{end}}</h3> | |||||
<p class="desc">{{.Repository.Description}}{{if .Repository.Website}} <a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}</p> | <p class="desc">{{.Repository.Description}}{{if .Repository.Website}} <a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}</p> | ||||
</div> | </div> | ||||
<div class="col-md-5 actions text-right clone-group-btn"> | <div class="col-md-5 actions text-right clone-group-btn"> |
<h4>Quick Guide</h4> | <h4>Quick Guide</h4> | ||||
</div> | </div> | ||||
<div class="panel-body guide-content text-center"> | <div class="panel-body guide-content text-center"> | ||||
<form action="{{.RepoLink}}/import" method="post"> | |||||
{{.CsrfTokenHtml}} | |||||
<h3>Clone from existing repository</h3> | |||||
<div class="input-group col-md-6 col-md-offset-3"> | |||||
<span class="input-group-btn"> | |||||
<button class="btn btn-default" type="button">URL</button> | |||||
</span> | |||||
<input name="import_url" class="form-control" placeholder="Type existing repository address" required="required"> | |||||
<span class="input-group-btn"> | |||||
<button type="submit" class="btn btn-default" type="button">Clone</button> | |||||
</span> | |||||
</div> | |||||
</form> | |||||
<h3>Clone this repository</h3> | <h3>Clone this repository</h3> | ||||
<div class="input-group col-md-8 col-md-offset-2 guide-buttons"> | <div class="input-group col-md-8 col-md-offset-2 guide-buttons"> | ||||
<span class="input-group-btn"> | <span class="input-group-btn"> |
<div class="dropdown-menu dropdown-menu-right"> | <div class="dropdown-menu dropdown-menu-right"> | ||||
<ul class="list-unstyled"> | <ul class="list-unstyled"> | ||||
<li><a href="/repo/create"><i class="fa fa-book"></i>Repository</a></li> | <li><a href="/repo/create"><i class="fa fa-book"></i>Repository</a></li> | ||||
<li><a href="/repo/mirror"><i class="fa fa-clipboard"></i>Mirror</a></li> | |||||
<li><a href="/repo/migrate"><i class="fa fa-clipboard"></i>Migration</a></li> | |||||
<!-- <li><a href="#"><i class="fa fa-users"></i>Organization</a></li> --> | <!-- <li><a href="#"><i class="fa fa-users"></i>Organization</a></li> --> | ||||
</ul> | </ul> | ||||
</div> | </div> |
</div> | </div> | ||||
<div class="profile-info"> | <div class="profile-info"> | ||||
<ul class="list-group"> | <ul class="list-group"> | ||||
<li class="list-group-item"> | |||||
{{if .Owner.Location}} | |||||
<li class="list-group-item"><i class="fa fa-thumb-tack"></i>{{.Owner.Location}}</li> | |||||
{{end}} | |||||
{{if .Owner.Email}} | |||||
<li class="list-group-item"><i class="fa fa-envelope"></i><a href="mailto:{{.Owner.Email}}">{{.Owner.Email}}</a></li> | |||||
{{end}} | |||||
{{if .Owner.Website}} | |||||
<li class="list-group-item"><i class="fa fa-link"></i><a target="_blank" href="{{.Owner.Website}}">{{.Owner.Website}}</a></li> | |||||
{{end}} | |||||
<li class="list-group-item"><i class="fa fa-clock-o"></i>Joined on {{DateFormat .Owner.Created "M d, Y"}}</li> | |||||
<hr> | |||||
<li class="list-group-item" style="padding-top: 5px;"> | |||||
<div class="profile-rel"> | <div class="profile-rel"> | ||||
<div class="col-md-6 followers"> | <div class="col-md-6 followers"> | ||||
<strong>123</strong> | <strong>123</strong> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</li> | </li> | ||||
{{if .Owner.Location}} | |||||
<li class="list-group-item"><i class="fa fa-thumb-tack"></i>{{.Owner.Location}}</li> | |||||
{{end}} | |||||
{{if .Owner.Email}} | |||||
<li class="list-group-item"><i class="fa fa-envelope"></i><a href="mailto:{{.Owner.Email}}">{{.Owner.Email}}</a></li> | |||||
{{end}} | |||||
{{if .Owner.Website}} | |||||
<li class="list-group-item"><i class="fa fa-link"></i><a target="_blank" href="{{.Owner.Website}}">{{.Owner.Website}}</a></li> | |||||
{{end}} | |||||
<li class="list-group-item"><i class="fa fa-clock-o"></i>Joined on {{DateFormat .Owner.Created "M d, Y"}}</li> | |||||
<hr> | |||||
</ul> | </ul> | ||||
</div> | </div> | ||||
</div> | </div> |
m.Group("/user", func(r martini.Router) { | m.Group("/user", func(r martini.Router) { | ||||
r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) | r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) | ||||
r.Get("/activate", user.Activate) | r.Get("/activate", user.Activate) | ||||
r.Get("/email2user", user.Email2User) | |||||
r.Get("/forget_password", user.ForgotPasswd) | r.Get("/forget_password", user.ForgotPasswd) | ||||
r.Post("/forget_password", user.ForgotPasswdPost) | r.Post("/forget_password", user.ForgotPasswdPost) | ||||
}) | }) | ||||
m.Group("/repo", func(r martini.Router) { | m.Group("/repo", func(r martini.Router) { | ||||
m.Get("/create", repo.Create) | m.Get("/create", repo.Create) | ||||
m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost) | m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost) | ||||
m.Get("/mirror", repo.Mirror) | |||||
m.Post("/mirror", bindIgnErr(auth.CreateRepoForm{}), repo.MirrorPost) | |||||
m.Get("/migrate", repo.Migrate) | |||||
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost) | |||||
}, reqSignIn) | }, reqSignIn) | ||||
adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true}) | adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true}) | ||||
m.Get("/template/**", dev.TemplatePreview) | m.Get("/template/**", dev.TemplatePreview) | ||||
} | } | ||||
writeable := middleware.WriteAccess() | |||||
m.Group("/:username/:reponame", func(r martini.Router) { | m.Group("/:username/:reponame", func(r martini.Router) { | ||||
r.Get("/settings", writeable, repo.Setting) | |||||
r.Post("/settings", writeable, repo.SettingPost) | |||||
r.Get("/settings", repo.Setting) | |||||
r.Post("/settings", repo.SettingPost) | |||||
r.Get("/action/:action", repo.Action) | r.Get("/action/:action", repo.Action) | ||||
r.Get("/issues/new", repo.CreateIssue) | r.Get("/issues/new", repo.CreateIssue) | ||||
r.Post("/issues/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost) | r.Post("/issues/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost) | ||||
r.Post("/issues/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) | r.Post("/issues/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) | ||||
r.Post("/comment/:action", repo.Comment) | r.Post("/comment/:action", repo.Comment) | ||||
r.Post("/import", writeable, repo.Import) | |||||
}, reqSignIn, middleware.RepoAssignment(true)) | }, reqSignIn, middleware.RepoAssignment(true)) | ||||
m.Group("/:username/:reponame", func(r martini.Router) { | m.Group("/:username/:reponame", func(r martini.Router) { | ||||
r.Get("/issues", repo.Issues) | r.Get("/issues", repo.Issues) | ||||
r.Get("/issues/:index", repo.ViewIssue) | r.Get("/issues/:index", repo.ViewIssue) | ||||
r.Get("/releases", repo.Releases) | r.Get("/releases", repo.Releases) | ||||
r.Any("/releases/new", writeable, repo.ReleasesNew) // TODO: | |||||
r.Any("/releases/new", repo.ReleasesNew) // TODO: | |||||
r.Get("/pulls", repo.Pulls) | r.Get("/pulls", repo.Pulls) | ||||
r.Get("/branches", repo.Branches) | r.Get("/branches", repo.Branches) | ||||
}, ignSignIn, middleware.RepoAssignment(true)) | }, ignSignIn, middleware.RepoAssignment(true)) |