]> source.dussan.org Git - gitea.git/commitdiff
Add repo mirror and import
authorUnknown <joe2010xtmf@163.com>
Sun, 13 Apr 2014 00:35:35 +0000 (20:35 -0400)
committerUnknown <joe2010xtmf@163.com>
Sun, 13 Apr 2014 00:35:35 +0000 (20:35 -0400)
21 files changed:
README.md
README_ZH.md
gogs.go
models/access.go
models/models.go
models/repo.go
modules/auth/auth.go
modules/auth/repo.go
modules/middleware/repo.go
routers/repo/repo.go
routers/user/user.go
templates/base/navbar.tmpl
templates/repo/commits.tmpl
templates/repo/diff.tmpl
templates/repo/migrate.tmpl [new file with mode: 0644]
templates/repo/mirror.tmpl [deleted file]
templates/repo/nav.tmpl
templates/repo/single_bare.tmpl
templates/user/dashboard.tmpl
templates/user/profile.tmpl
web.go

index d48c05c252fa6d2a91fcbcb63f3ea1f47a3345ae..fa48027db318ab6e21ee93cb8760cdd576a24b52 100644 (file)
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
 
 ![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.
 
@@ -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
 
index 62996d4df8352d2443d1e321e86ec04af1b9f7ca..beb5a1050b2440a2654fa6b58ca763f02318a7b4 100644 (file)
@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
 
 ![Demo](http://gowalker.org/public/gogs_demo.gif)
 
-##### 当前版本: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
 
 ## 安装部署
 
diff --git a/gogs.go b/gogs.go
index f726e809a64dec9aa05a89ce094500bc9516aa67..a42d7225ba7fdb3da440342658018ef71f1ac283 100644 (file)
--- a/gogs.go
+++ b/gogs.go
@@ -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
index 5bf93f1b1bc8006467c76210b9a7588e19cb2a53..f20f8fa7cc0ebc3c86128903db00bf283fbf3774 100644 (file)
@@ -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"`
 }
index b8374a3d67009181988d6d71fa269264e59cafa9..f1d43531be84151aab0ae30713970360bb406f80 100644 (file)
@@ -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() {
index d01a716b50c326bd09e8704e1a3524831af880c2..a2c63a50c9ca9825fafb3825865e2f206c71e62d 100644 (file)
@@ -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
index 4561dd831dd95ab6e8b8c08c2049d1a522c7d8be..7329cbdcdf3586dd99072a9b6f70082e886d5472 100644 (file)
@@ -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
                        }
index 1c740bed194dd638f6ba60d599a7f290c9674d5d..aa94058f9d8f653a216ff853e484112f0896a502 100644 (file)
@@ -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)
+}
index 75d9f999c684414bd01593d2a7cfbe2ab4f26e1a..2a6d300e42b4f9bc7eee8cc533a96a92fb5fd790 100644 (file)
@@ -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
-       }
-}
index 3ffbde1a1e04541bdbe3ef79eed96fbfbfc444a6..f19ae02ee72f797cb29ed73b9d2c1339edd0a6b2 100644 (file)
@@ -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"))
-}
index 8585267a644f7d64ef56a5736f046f28baf65cb9..e5328173a2709fc16e7f10c43a87f34202f53fb5 100644 (file)
@@ -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"
 
index 708a3976801b6a1e48bd5011f7612e73ef391a9f..cd3f2b83a91ddc32b94678dbb5c82601b2fa733c 100644 (file)
@@ -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>
index 68b140358942f8744a2a3b8586d2ca82ba2fb479..8125fedaa8fb6b8e638292a2fc286928ba03e83f 100644 (file)
@@ -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>
index 796a8e94c3db26b8b6e42624ac965d9791d5af39..f3d935aebefa08c8b32912d2b699afe16dbfce42 100644 (file)
@@ -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>
diff --git a/templates/repo/migrate.tmpl b/templates/repo/migrate.tmpl
new file mode 100644 (file)
index 0000000..34a4077
--- /dev/null
@@ -0,0 +1,99 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div class="container" id="body">
+    <form action="/repo/migrate" method="post" class="form-horizontal card" id="repo-create">
+        {{.CsrfTokenHtml}}
+        <h3>Repository Migration</h3>
+        {{template "base/alert" .}}
+        <!-- <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">GitHub</option>
+                </select>
+            </div>
+        </div> -->
+
+        <div class="form-group">
+            <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 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>
+            </div>
+            <div id="repo-import-auth" class="collapse">
+                <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" 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="password" class="form-control" placeholder="Type your password" value="{{.auth_password}}" >
+                    </div>
+                </div>
+            </div>
+        </div>
+        <hr/>
+        <div class="form-group">
+            <label class="col-md-2 control-label">Owner<strong class="text-danger">*</strong></label>
+            <div class="col-md-8">
+                <p class="form-control-static">{{.SignedUserName}}</p>
+                <input type="hidden" value="{{.SignedUserId}}" name="userId"/>
+            </div>
+        </div>
+
+        <div class="form-group {{if .Err_RepoName}}has-error has-feedback{{end}}">
+            <label class="col-md-2 control-label">Repository<strong class="text-danger">*</strong></label>
+            <div class="col-md-8">
+                <input name="repo" type="text" class="form-control" placeholder="Type your repository name" value="{{.repo}}" required="required">
+                <span class="help-block">Great repository names are short and memorable. </span>
+            </div>
+        </div>
+
+        <div class="form-group">
+            <label class="col-md-2 control-label">Migration Type</label>
+            <div class="col-md-8">
+                <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 class="form-group {{if .Err_Description}}has-error has-feedback{{end}}">
+            <label class="col-md-2 control-label">Description</label>
+            <div class="col-md-8">
+                <textarea name="desc" class="form-control" placeholder="Type your repository description">{{.desc}}</textarea>
+            </div>
+        </div>
+
+        <div class="form-group">
+            <div class="col-md-offset-2 col-md-8">
+                <button type="submit" class="btn btn-lg btn-primary">Migrate repository</button>
+                <a href="/" class="text-danger">Cancel</a>
+            </div>
+        </div>
+    </form>
+</div>
+{{template "base/footer" .}}
\ No newline at end of file
diff --git a/templates/repo/mirror.tmpl b/templates/repo/mirror.tmpl
deleted file mode 100644 (file)
index 0f10d1f..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-{{template "base/head" .}}
-{{template "base/navbar" .}}
-<div class="container" id="body">
-    <form action="/repo/create" method="post" class="form-horizontal card" id="repo-create">
-        {{.CsrfTokenHtml}}
-        <h3>Create Repository Mirror</h3>
-        {{template "base/alert" .}}
-        <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>
-                </select>
-            </div>
-        </div>
-        <div class="form-group">
-            <label class="col-md-2 control-label">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">
-            </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>
-            </div>
-            <div id="repo-import-auth" class="collapse">
-                <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">
-                    </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">
-                    </div>
-                </div>
-            </div>
-        </div>
-        <hr/>
-        <div class="form-group">
-            <label class="col-md-2 control-label">Owner<strong class="text-danger">*</strong></label>
-            <div class="col-md-8">
-                <p class="form-control-static">{{.SignedUserName}}</p>
-                <input type="hidden" value="{{.SignedUserId}}" name="userId"/>
-            </div>
-        </div>
-
-        <div class="form-group {{if .Err_RepoName}}has-error has-feedback{{end}}">
-            <label class="col-md-2 control-label">Repository<strong class="text-danger">*</strong></label>
-            <div class="col-md-8">
-                <input name="repo" type="text" class="form-control" placeholder="Type your repository name" value="{{.repo}}" required="required">
-                <span class="help-block">Great repository names are short and memorable. </span>
-            </div>
-        </div>
-
-        <div class="form-group">
-            <label class="col-md-2 control-label">Visibility<strong class="text-danger">*</strong></label>
-            <div class="col-md-8">
-                <p class="form-control-static">Public</p>
-                <input type="hidden" value="public" name="visibility"/>
-            </div>
-        </div>
-
-        <div class="form-group {{if .Err_Description}}has-error has-feedback{{end}}">
-            <label class="col-md-2 control-label">Description</label>
-            <div class="col-md-8">
-                <textarea name="desc" class="form-control" placeholder="Type your repository description">{{.desc}}</textarea>
-            </div>
-        </div>
-
-        <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>
-                <a href="/" class="text-danger">Cancel</a>
-            </div>
-        </div>
-    </form>
-</div>
-{{template "base/footer" .}}
\ No newline at end of file
index f365ab6493bb6873c15ddfeba30414e708b88652..f9f74dd194305506fff59133983b664fee150245 100644 (file)
@@ -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">
index 7d7016e5c5f06866f11948744c1a226575305c5a..fc0a3bd96c49f5a6df0f64d9c8e635b479d8fe6d 100644 (file)
@@ -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">
index efa78d88079ebf1f1a2e163a3eb74ca6f3212abb..167ae9abbdb43280bac7f7e1fce05858fe85034a 100644 (file)
@@ -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>
index 0cbd3489100c369c44f8f73e78e95869673b8f65..255eb71cb2fec18eb9f80d8584a34f1f9304e806 100644 (file)
         </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>
                         </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>
diff --git a/web.go b/web.go
index de8e72505fb40eba1eb3d8f92be0ba611aeda8c9..a17be2a34ac8ee4d6ab757c794748efc817fd2b6 100644 (file)
--- a/web.go
+++ b/web.go
@@ -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))