| @@ -5,7 +5,7 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language | |||
|  | |||
| ##### 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. | |||
| @@ -31,13 +31,12 @@ More importantly, Gogs only needs one binary to setup your own project hosting o | |||
| - Activity timeline | |||
| - SSH/HTTP(S) protocol support. | |||
| - 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. | |||
| - Mail service(register, issue). | |||
| - Administration panel. | |||
| - Supports MySQL, PostgreSQL and SQLite3(binary release only). | |||
| - Supports MySQL, PostgreSQL and SQLite3. | |||
| ## Installation | |||
| @@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。 | |||
|  | |||
| ##### 当前版本:0.2.7 Alpha | |||
| ##### 当前版本:0.2.8 Alpha | |||
| ## 开发目的 | |||
| @@ -25,13 +25,12 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依 | |||
| - 活动时间线 | |||
| - SSH/HTTP(S) 协议支持 | |||
| - 注册/删除/重命名用户 | |||
| - 创建/删除/关注/重命名/转移 公开/私有 仓库 | |||
| - 仓库浏览器 | |||
| - Bug 追踪系统 | |||
| - 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库 | |||
| - 仓库 浏览器/Bug 追踪 | |||
| - Gravatar 以及缓存支持 | |||
| - 邮件服务(注册、Issue) | |||
| - 管理员面板 | |||
| - 支持 MySQL、PostgreSQL 以及 SQLite3(仅限二进制版本) | |||
| - 支持 MySQL、PostgreSQL 以及 SQLite3 | |||
| ## 安装部署 | |||
| @@ -19,7 +19,7 @@ import ( | |||
| // Test that go1.2 tag above is included in builds. main.go refers to this definition. | |||
| const go12tag = true | |||
| const APP_VER = "0.2.7.0411 Alpha" | |||
| const APP_VER = "0.2.8.0412 Alpha" | |||
| func init() { | |||
| base.AppVer = APP_VER | |||
| @@ -21,7 +21,7 @@ const ( | |||
| type Access struct { | |||
| Id int64 | |||
| UserName string `xorm:"unique(s)"` | |||
| RepoName string `xorm:"unique(s)"` | |||
| RepoName string `xorm:"unique(s)"` // <user name>/<repo name> | |||
| Mode int `xorm:"unique(s)"` | |||
| Created time.Time `xorm:"created"` | |||
| } | |||
| @@ -32,7 +32,8 @@ var ( | |||
| func init() { | |||
| 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() { | |||
| @@ -78,6 +78,7 @@ type Repository struct { | |||
| NumClosedIssues int | |||
| NumOpenIssues int `xorm:"-"` | |||
| IsPrivate bool | |||
| IsMirror bool | |||
| IsBare bool | |||
| IsGoget bool | |||
| DefaultBranch string | |||
| @@ -119,13 +120,92 @@ func IsLegalName(repoName string) bool { | |||
| 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. | |||
| 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 | |||
| } | |||
| isExist, err := IsRepositoryExist(user, repoName) | |||
| isExist, err := IsRepositoryExist(user, name) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if isExist { | |||
| @@ -134,13 +214,13 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv | |||
| repo := &Repository{ | |||
| OwnerId: user.Id, | |||
| Name: repoName, | |||
| LowerName: strings.ToLower(repoName), | |||
| Name: name, | |||
| LowerName: strings.ToLower(name), | |||
| Description: desc, | |||
| IsPrivate: private, | |||
| IsBare: repoLang == "" && license == "" && !initReadme, | |||
| IsBare: lang == "" && license == "" && !initReadme, | |||
| } | |||
| repoPath := RepoPath(user.Name, repoName) | |||
| repoPath := RepoPath(user.Name, repo.Name) | |||
| sess := orm.NewSession() | |||
| defer sess.Close() | |||
| @@ -150,23 +230,27 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv | |||
| if err2 := os.RemoveAll(repoPath); err2 != nil { | |||
| log.Error("repo.CreateRepository(repo): %v", err) | |||
| 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() | |||
| return nil, err | |||
| } | |||
| mode := AU_WRITABLE | |||
| if mirror { | |||
| mode = AU_READABLE | |||
| } | |||
| access := Access{ | |||
| UserName: user.LowerName, | |||
| RepoName: strings.ToLower(path.Join(user.Name, repo.Name)), | |||
| Mode: AU_WRITABLE, | |||
| Mode: mode, | |||
| } | |||
| if _, err = sess.Insert(&access); err != nil { | |||
| sess.Rollback() | |||
| if err2 := os.RemoveAll(repoPath); err2 != nil { | |||
| log.Error("repo.CreateRepository(access): %v", err) | |||
| 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 | |||
| } | |||
| @@ -177,7 +261,7 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv | |||
| if err2 := os.RemoveAll(repoPath); err2 != nil { | |||
| log.Error("repo.CreateRepository(repo count): %v", err) | |||
| 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 | |||
| } | |||
| @@ -187,7 +271,7 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv | |||
| if err2 := os.RemoveAll(repoPath); err2 != nil { | |||
| log.Error("repo.CreateRepository(commit): %v", err) | |||
| 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 | |||
| } | |||
| @@ -202,7 +286,12 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv | |||
| 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 | |||
| } | |||
| @@ -304,9 +393,13 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep | |||
| tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond())) | |||
| 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 | |||
| } | |||
| if len(stderr) > 0 { | |||
| log.Trace("repo.initRepository(git clone): %s", stderr) | |||
| } | |||
| // README | |||
| if initReadme { | |||
| @@ -379,6 +472,7 @@ func GetRepos(num, offset int) ([]UserRepo, error) { | |||
| return urepos, nil | |||
| } | |||
| // RepoPath returns repository path by given user and repository name. | |||
| func RepoPath(userName, repoName string) string { | |||
| return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") | |||
| } | |||
| @@ -519,8 +613,7 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) { | |||
| sess.Rollback() | |||
| 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() | |||
| return err | |||
| } | |||
| @@ -528,6 +621,12 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) { | |||
| sess.Rollback() | |||
| 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 { | |||
| sess.Rollback() | |||
| return err | |||
| @@ -130,7 +130,9 @@ func validate(errors *binding.Errors, data base.TmplData, form Form) { | |||
| case binding.MaxSizeError: | |||
| data["ErrorMsg"] = form.Name(field.Name) + " must contain at most " + getMinMaxSize(field) + " characters" | |||
| 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: | |||
| data["ErrorMsg"] = "Unknown error: " + err | |||
| } | |||
| @@ -18,11 +18,11 @@ import ( | |||
| type CreateRepoForm struct { | |||
| RepoName string `form:"repo" binding:"Required;AlphaDash"` | |||
| Private string `form:"private"` | |||
| Private bool `form:"private"` | |||
| Description string `form:"desc" binding:"MaxSize(100)"` | |||
| Language string `form:"language"` | |||
| License string `form:"license"` | |||
| InitReadme string `form:"initReadme"` | |||
| InitReadme bool `form:"initReadme"` | |||
| } | |||
| func (f *CreateRepoForm) Name(field string) string { | |||
| @@ -51,3 +51,41 @@ func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, con | |||
| 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) | |||
| } | |||
| @@ -190,27 +190,3 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler { | |||
| 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 | |||
| } | |||
| } | |||
| @@ -40,8 +40,8 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) { | |||
| 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 { | |||
| log.Trace("%s Repository created: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName) | |||
| ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName) | |||
| @@ -53,38 +53,56 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) { | |||
| ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/create", &form) | |||
| 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) | |||
| } | |||
| 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.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 | |||
| if ctx.HasError() { | |||
| ctx.HTML(200, "repo/mirror") | |||
| ctx.HTML(200, "repo/migrate") | |||
| 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 { | |||
| 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) | |||
| return | |||
| } 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 | |||
| } 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 | |||
| } | |||
| ctx.Handle(500, "repo.Mirror", err) | |||
| ctx.Handle(500, "repo.Migrate", err) | |||
| } | |||
| func Single(ctx *middleware.Context, params martini.Params) { | |||
| @@ -425,7 +443,3 @@ func Action(ctx *middleware.Context, params martini.Params) { | |||
| "ok": true, | |||
| }) | |||
| } | |||
| func Import(ctx *middleware.Context, params martini.Params) { | |||
| ctx.ResponseWriter.Write([]byte("not done yet")) | |||
| } | |||
| @@ -74,6 +74,20 @@ func Profile(ctx *middleware.Context, params martini.Params) { | |||
| 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) { | |||
| ctx.Data["Title"] = "Log In" | |||
| @@ -28,7 +28,7 @@ | |||
| <div class="dropdown-menu"> | |||
| <ul class="list-unstyled"> | |||
| <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> --> | |||
| </ul> | |||
| </div> | |||
| @@ -31,7 +31,7 @@ | |||
| {{$r := List .Commits}} | |||
| {{range $r}} | |||
| <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="message">{{.Message}} </td> | |||
| <td class="date">{{TimeSince .Author.When}}</td> | |||
| @@ -14,7 +14,7 @@ | |||
| </span> | |||
| <p class="author"> | |||
| <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> | |||
| </p> | |||
| </div> | |||
| @@ -1,24 +1,26 @@ | |||
| {{template "base/head" .}} | |||
| {{template "base/navbar" .}} | |||
| <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}} | |||
| <h3>Create Repository Mirror</h3> | |||
| <h3>Repository Migration</h3> | |||
| {{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> | |||
| <div class="col-md-8"> | |||
| <select class="form-control" name="from"> | |||
| <option value="">GitHub</option> | |||
| <option value="github">GitHub</option> | |||
| </select> | |||
| </div> | |||
| </div> | |||
| </div> --> | |||
| <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"> | |||
| <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 class="form-group"> | |||
| <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> | |||
| @@ -27,13 +29,13 @@ | |||
| <div class="form-group"> | |||
| <label class="col-md-2 control-label">Username</label> | |||
| <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 class="form-group"> | |||
| <label class="col-md-2 control-label">Password</label> | |||
| <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> | |||
| @@ -56,10 +58,26 @@ | |||
| </div> | |||
| <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"> | |||
| <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> | |||
| @@ -72,7 +90,7 @@ | |||
| <div class="form-group"> | |||
| <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> | |||
| </div> | |||
| </div> | |||
| @@ -2,7 +2,7 @@ | |||
| <div class="container"> | |||
| <div class="row"> | |||
| <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> | |||
| </div> | |||
| <div class="col-md-5 actions text-right clone-group-btn"> | |||
| @@ -9,20 +9,6 @@ | |||
| <h4>Quick Guide</h4> | |||
| </div> | |||
| <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> | |||
| <div class="input-group col-md-8 col-md-offset-2 guide-buttons"> | |||
| <span class="input-group-btn"> | |||
| @@ -34,7 +34,7 @@ | |||
| <div class="dropdown-menu dropdown-menu-right"> | |||
| <ul class="list-unstyled"> | |||
| <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> --> | |||
| </ul> | |||
| </div> | |||
| @@ -10,7 +10,18 @@ | |||
| </div> | |||
| <div class="profile-info"> | |||
| <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="col-md-6 followers"> | |||
| <strong>123</strong> | |||
| @@ -22,16 +33,7 @@ | |||
| </div> | |||
| </div> | |||
| </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> | |||
| </div> | |||
| </div> | |||
| @@ -104,6 +104,7 @@ func runWeb(*cli.Context) { | |||
| m.Group("/user", func(r martini.Router) { | |||
| r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) | |||
| r.Get("/activate", user.Activate) | |||
| r.Get("/email2user", user.Email2User) | |||
| r.Get("/forget_password", user.ForgotPasswd) | |||
| r.Post("/forget_password", user.ForgotPasswdPost) | |||
| }) | |||
| @@ -120,8 +121,8 @@ func runWeb(*cli.Context) { | |||
| m.Group("/repo", func(r martini.Router) { | |||
| m.Get("/create", repo.Create) | |||
| 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) | |||
| adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true}) | |||
| @@ -144,24 +145,21 @@ func runWeb(*cli.Context) { | |||
| m.Get("/template/**", dev.TemplatePreview) | |||
| } | |||
| writeable := middleware.WriteAccess() | |||
| 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("/issues/new", repo.CreateIssue) | |||
| r.Post("/issues/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost) | |||
| r.Post("/issues/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) | |||
| r.Post("/comment/:action", repo.Comment) | |||
| r.Post("/import", writeable, repo.Import) | |||
| }, reqSignIn, middleware.RepoAssignment(true)) | |||
| m.Group("/:username/:reponame", func(r martini.Router) { | |||
| r.Get("/issues", repo.Issues) | |||
| r.Get("/issues/:index", repo.ViewIssue) | |||
| 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("/branches", repo.Branches) | |||
| }, ignSignIn, middleware.RepoAssignment(true)) | |||