Skip to content

Commit

Permalink
Add repo mirror and import
Browse files Browse the repository at this point in the history
  • Loading branch information
unknwon committed Apr 13, 2014
1 parent 23bba76 commit 90f6aa8
Show file tree
Hide file tree
Showing 20 changed files with 271 additions and 125 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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

Expand Down
9 changes: 4 additions & 5 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

## 开发目的

Expand All @@ -25,13 +25,12 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
- 活动时间线
- SSH/HTTP(S) 协议支持
- 注册/删除/重命名用户
- 创建/删除/关注/重命名/转移 公开/私有 仓库
- 仓库浏览器
- Bug 追踪系统
- 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库
- 仓库 浏览器/Bug 追踪
- Gravatar 以及缓存支持
- 邮件服务(注册、Issue)
- 管理员面板
- 支持 MySQL、PostgreSQL 以及 SQLite3(仅限二进制版本)
- 支持 MySQL、PostgreSQL 以及 SQLite3

## 安装部署

Expand Down
2 changes: 1 addition & 1 deletion gogs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion models/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down
3 changes: 2 additions & 1 deletion models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
131 changes: 115 additions & 16 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ type Repository struct {
NumClosedIssues int
NumOpenIssues int `xorm:"-"`
IsPrivate bool
IsMirror bool
IsBare bool
IsGoget bool
DefaultBranch string
Expand Down Expand Up @@ -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 {
Expand All @@ -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()
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -519,15 +613,20 @@ 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
}
if _, err = sess.Delete(&Watch{RepoId: repoId}); err != nil {
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
Expand Down
4 changes: 3 additions & 1 deletion modules/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
42 changes: 40 additions & 2 deletions modules/auth/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Loading

0 comments on commit 90f6aa8

Please sign in to comment.