Skip to content

Commit 90f6aa8

Browse files
committed
Add repo mirror and import
1 parent 23bba76 commit 90f6aa8

20 files changed

+271
-125
lines changed

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
55

66
![Demo](http://gowalker.org/public/gogs_demo.gif)
77

8-
##### Current version: 0.2.7 Alpha
8+
##### Current version: 0.2.8 Alpha
99

1010
#### 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.
1111

@@ -31,13 +31,12 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
3131
- Activity timeline
3232
- SSH/HTTP(S) protocol support.
3333
- Register/delete/rename account.
34-
- Create/delete/watch/rename/transfer public/private repository.
35-
- Repository viewer.
36-
- Issue tracker.
34+
- Create/migrate/mirror/delete/watch/rename/transfer public/private repository.
35+
- Repository viewer/issue tracker.
3736
- Gravatar and cache support.
3837
- Mail service(register, issue).
3938
- Administration panel.
40-
- Supports MySQL, PostgreSQL and SQLite3(binary release only).
39+
- Supports MySQL, PostgreSQL and SQLite3.
4140

4241
## Installation
4342

README_ZH.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
55

66
![Demo](http://gowalker.org/public/gogs_demo.gif)
77

8-
##### 当前版本:0.2.7 Alpha
8+
##### 当前版本:0.2.8 Alpha
99

1010
## 开发目的
1111

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

3635
## 安装部署
3736

gogs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
// Test that go1.2 tag above is included in builds. main.go refers to this definition.
2020
const go12tag = true
2121

22-
const APP_VER = "0.2.7.0411 Alpha"
22+
const APP_VER = "0.2.8.0412 Alpha"
2323

2424
func init() {
2525
base.AppVer = APP_VER

models/access.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const (
2121
type Access struct {
2222
Id int64
2323
UserName string `xorm:"unique(s)"`
24-
RepoName string `xorm:"unique(s)"`
24+
RepoName string `xorm:"unique(s)"` // <user name>/<repo name>
2525
Mode int `xorm:"unique(s)"`
2626
Created time.Time `xorm:"created"`
2727
}

models/models.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ var (
3232

3333
func init() {
3434
tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch),
35-
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow))
35+
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow),
36+
new(Mirror))
3637
}
3738

3839
func LoadModelsConfig() {

models/repo.go

Lines changed: 115 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ type Repository struct {
7878
NumClosedIssues int
7979
NumOpenIssues int `xorm:"-"`
8080
IsPrivate bool
81+
IsMirror bool
8182
IsBare bool
8283
IsGoget bool
8384
DefaultBranch string
@@ -119,13 +120,92 @@ func IsLegalName(repoName string) bool {
119120
return true
120121
}
121122

123+
// Mirror represents a mirror information of repository.
124+
type Mirror struct {
125+
Id int64
126+
RepoId int64
127+
RepoName string // <user name>/<repo name>
128+
Interval int // Hour.
129+
Updated time.Time `xorm:"UPDATED"`
130+
NextUpdate time.Time
131+
}
132+
133+
// MirrorRepository creates a mirror repository from source.
134+
func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
135+
_, stderr, err := com.ExecCmd("git", "clone", "--mirror", url, repoPath)
136+
if err != nil {
137+
return err
138+
} else if strings.Contains(stderr, "fatal:") {
139+
return errors.New(stderr)
140+
}
141+
142+
if _, err = orm.InsertOne(&Mirror{
143+
RepoId: repoId,
144+
RepoName: strings.ToLower(userName + "/" + repoName),
145+
Interval: 24,
146+
NextUpdate: time.Now().Add(24 * time.Hour),
147+
}); err != nil {
148+
return err
149+
}
150+
151+
return git.UnpackRefs(repoPath)
152+
}
153+
154+
// MigrateRepository migrates a existing repository from other project hosting.
155+
func MigrateRepository(user *User, name, desc string, private, mirror bool, url string) (*Repository, error) {
156+
repo, err := CreateRepository(user, name, desc, "", "", private, mirror, false)
157+
if err != nil {
158+
return nil, err
159+
}
160+
161+
// Clone to temprory path and do the init commit.
162+
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
163+
os.MkdirAll(tmpDir, os.ModePerm)
164+
165+
repoPath := RepoPath(user.Name, name)
166+
167+
repo.IsBare = false
168+
if mirror {
169+
if err = MirrorRepository(repo.Id, user.Name, repo.Name, repoPath, url); err != nil {
170+
return repo, err
171+
}
172+
repo.IsMirror = true
173+
return repo, UpdateRepository(repo)
174+
}
175+
176+
// Clone from local repository.
177+
_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir)
178+
if err != nil {
179+
return repo, err
180+
} else if strings.Contains(stderr, "fatal:") {
181+
return repo, errors.New("git clone: " + stderr)
182+
}
183+
184+
// Pull data from source.
185+
_, stderr, err = com.ExecCmdDir(tmpDir, "git", "pull", url)
186+
if err != nil {
187+
return repo, err
188+
} else if strings.Contains(stderr, "fatal:") {
189+
return repo, errors.New("git pull: " + stderr)
190+
}
191+
192+
// Push data to local repository.
193+
if _, stderr, err = com.ExecCmdDir(tmpDir, "git", "push", "origin", "master"); err != nil {
194+
return repo, err
195+
} else if strings.Contains(stderr, "fatal:") {
196+
return repo, errors.New("git push: " + stderr)
197+
}
198+
199+
return repo, UpdateRepository(repo)
200+
}
201+
122202
// CreateRepository creates a repository for given user or orgnaziation.
123-
func CreateRepository(user *User, repoName, desc, repoLang, license string, private bool, initReadme bool) (*Repository, error) {
124-
if !IsLegalName(repoName) {
203+
func CreateRepository(user *User, name, desc, lang, license string, private, mirror, initReadme bool) (*Repository, error) {
204+
if !IsLegalName(name) {
125205
return nil, ErrRepoNameIllegal
126206
}
127207

128-
isExist, err := IsRepositoryExist(user, repoName)
208+
isExist, err := IsRepositoryExist(user, name)
129209
if err != nil {
130210
return nil, err
131211
} else if isExist {
@@ -134,13 +214,13 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
134214

135215
repo := &Repository{
136216
OwnerId: user.Id,
137-
Name: repoName,
138-
LowerName: strings.ToLower(repoName),
217+
Name: name,
218+
LowerName: strings.ToLower(name),
139219
Description: desc,
140220
IsPrivate: private,
141-
IsBare: repoLang == "" && license == "" && !initReadme,
221+
IsBare: lang == "" && license == "" && !initReadme,
142222
}
143-
repoPath := RepoPath(user.Name, repoName)
223+
repoPath := RepoPath(user.Name, repo.Name)
144224

145225
sess := orm.NewSession()
146226
defer sess.Close()
@@ -150,23 +230,27 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
150230
if err2 := os.RemoveAll(repoPath); err2 != nil {
151231
log.Error("repo.CreateRepository(repo): %v", err)
152232
return nil, errors.New(fmt.Sprintf(
153-
"delete repo directory %s/%s failed(1): %v", user.Name, repoName, err2))
233+
"delete repo directory %s/%s failed(1): %v", user.Name, repo.Name, err2))
154234
}
155235
sess.Rollback()
156236
return nil, err
157237
}
158238

239+
mode := AU_WRITABLE
240+
if mirror {
241+
mode = AU_READABLE
242+
}
159243
access := Access{
160244
UserName: user.LowerName,
161245
RepoName: strings.ToLower(path.Join(user.Name, repo.Name)),
162-
Mode: AU_WRITABLE,
246+
Mode: mode,
163247
}
164248
if _, err = sess.Insert(&access); err != nil {
165249
sess.Rollback()
166250
if err2 := os.RemoveAll(repoPath); err2 != nil {
167251
log.Error("repo.CreateRepository(access): %v", err)
168252
return nil, errors.New(fmt.Sprintf(
169-
"delete repo directory %s/%s failed(2): %v", user.Name, repoName, err2))
253+
"delete repo directory %s/%s failed(2): %v", user.Name, repo.Name, err2))
170254
}
171255
return nil, err
172256
}
@@ -177,7 +261,7 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
177261
if err2 := os.RemoveAll(repoPath); err2 != nil {
178262
log.Error("repo.CreateRepository(repo count): %v", err)
179263
return nil, errors.New(fmt.Sprintf(
180-
"delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2))
264+
"delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2))
181265
}
182266
return nil, err
183267
}
@@ -187,7 +271,7 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
187271
if err2 := os.RemoveAll(repoPath); err2 != nil {
188272
log.Error("repo.CreateRepository(commit): %v", err)
189273
return nil, errors.New(fmt.Sprintf(
190-
"delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2))
274+
"delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2))
191275
}
192276
return nil, err
193277
}
@@ -202,7 +286,12 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
202286
log.Error("repo.CreateRepository(WatchRepo): %v", err)
203287
}
204288

205-
if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil {
289+
// No need for init for mirror.
290+
if mirror {
291+
return repo, nil
292+
}
293+
294+
if err = initRepository(repoPath, user, repo, initReadme, lang, license); err != nil {
206295
return nil, err
207296
}
208297

@@ -304,9 +393,13 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
304393
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
305394
os.MkdirAll(tmpDir, os.ModePerm)
306395

307-
if _, _, err := com.ExecCmd("git", "clone", repoPath, tmpDir); err != nil {
396+
_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir)
397+
if err != nil {
308398
return err
309399
}
400+
if len(stderr) > 0 {
401+
log.Trace("repo.initRepository(git clone): %s", stderr)
402+
}
310403

311404
// README
312405
if initReadme {
@@ -379,6 +472,7 @@ func GetRepos(num, offset int) ([]UserRepo, error) {
379472
return urepos, nil
380473
}
381474

475+
// RepoPath returns repository path by given user and repository name.
382476
func RepoPath(userName, repoName string) string {
383477
return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git")
384478
}
@@ -519,15 +613,20 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
519613
sess.Rollback()
520614
return err
521615
}
522-
rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?"
523-
if _, err = sess.Exec(rawSql, userId); err != nil {
616+
if _, err := sess.Delete(&Action{RepoId: repo.Id}); err != nil {
524617
sess.Rollback()
525618
return err
526619
}
527620
if _, err = sess.Delete(&Watch{RepoId: repoId}); err != nil {
528621
sess.Rollback()
529622
return err
530623
}
624+
625+
rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?"
626+
if _, err = sess.Exec(rawSql, userId); err != nil {
627+
sess.Rollback()
628+
return err
629+
}
531630
if err = sess.Commit(); err != nil {
532631
sess.Rollback()
533632
return err

modules/auth/auth.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ func validate(errors *binding.Errors, data base.TmplData, form Form) {
130130
case binding.MaxSizeError:
131131
data["ErrorMsg"] = form.Name(field.Name) + " must contain at most " + getMinMaxSize(field) + " characters"
132132
case binding.EmailError:
133-
data["ErrorMsg"] = form.Name(field.Name) + " is not valid"
133+
data["ErrorMsg"] = form.Name(field.Name) + " is not a valid e-mail address"
134+
case binding.UrlError:
135+
data["ErrorMsg"] = form.Name(field.Name) + " is not a valid URL"
134136
default:
135137
data["ErrorMsg"] = "Unknown error: " + err
136138
}

modules/auth/repo.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import (
1818

1919
type CreateRepoForm struct {
2020
RepoName string `form:"repo" binding:"Required;AlphaDash"`
21-
Private string `form:"private"`
21+
Private bool `form:"private"`
2222
Description string `form:"desc" binding:"MaxSize(100)"`
2323
Language string `form:"language"`
2424
License string `form:"license"`
25-
InitReadme string `form:"initReadme"`
25+
InitReadme bool `form:"initReadme"`
2626
}
2727

2828
func (f *CreateRepoForm) Name(field string) string {
@@ -51,3 +51,41 @@ func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, con
5151

5252
validate(errors, data, f)
5353
}
54+
55+
type MigrateRepoForm struct {
56+
Url string `form:"url" binding:"Url"`
57+
AuthUserName string `form:"auth_username"`
58+
AuthPasswd string `form:"auth_password"`
59+
RepoName string `form:"repo" binding:"Required;AlphaDash"`
60+
Mirror bool `form:"mirror"`
61+
Private bool `form:"private"`
62+
Description string `form:"desc" binding:"MaxSize(100)"`
63+
}
64+
65+
func (f *MigrateRepoForm) Name(field string) string {
66+
names := map[string]string{
67+
"Url": "Migration URL",
68+
"RepoName": "Repository name",
69+
"Description": "Description",
70+
}
71+
return names[field]
72+
}
73+
74+
func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
75+
if req.Method == "GET" || errors.Count() == 0 {
76+
return
77+
}
78+
79+
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
80+
data["HasError"] = true
81+
AssignForm(f, data)
82+
83+
if len(errors.Overall) > 0 {
84+
for _, err := range errors.Overall {
85+
log.Error("MigrateRepoForm.Validate: %v", err)
86+
}
87+
return
88+
}
89+
90+
validate(errors, data, f)
91+
}

0 commit comments

Comments
 (0)