#1984 Better mirror repo management
This commit is contained in:
parent
1cbd4c01fb
commit
120cd4e471
16 changed files with 128 additions and 60 deletions
|
@ -17,7 +17,7 @@ github.com/go-sql-driver/mysql = commit:d512f20
|
|||
github.com/go-xorm/core = commit:acb6f00
|
||||
github.com/go-xorm/xorm = commit:a8fba4d
|
||||
github.com/gogits/chardet = commit:2404f77725
|
||||
github.com/gogits/git-shell = commit:de77627
|
||||
github.com/gogits/git-shell =
|
||||
github.com/gogits/go-gogs-client = commit:4b541fa
|
||||
github.com/issue9/identicon = commit:f8c0d2c
|
||||
github.com/klauspost/compress = commit:bcd0709
|
||||
|
|
|
@ -5,7 +5,7 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
|
|||
|
||||
![](public/img/gogs-large-resize.png)
|
||||
|
||||
##### Current version: 0.7.34 Beta
|
||||
##### Current version: 0.7.35 Beta
|
||||
|
||||
| Web | UI | Preview |
|
||||
|:-------------:|:-------:|:-------:|
|
||||
|
|
|
@ -352,6 +352,8 @@ auto_init = Initialize this repository with selected files and template
|
|||
create_repo = Create Repository
|
||||
default_branch = Default Branch
|
||||
mirror_interval = Mirror Interval (hour)
|
||||
mirror_address = Mirror Address
|
||||
mirror_address_desc = Please include necessary user credentials in the address.
|
||||
watchers = Watchers
|
||||
stargazers = Stargazers
|
||||
forks = Forks
|
||||
|
@ -369,6 +371,7 @@ migrate.permission_denied = You are not allowed to import local repositories.
|
|||
migrate.invalid_local_path = Invalid local path, it does not exist or not a directory.
|
||||
migrate.failed = Migration failed: %v
|
||||
|
||||
mirror_from = mirror from
|
||||
forked_from = forked from
|
||||
fork_from_self = You cannot fork a repository you already own!
|
||||
copy_link = Copy
|
||||
|
|
2
gogs.go
2
gogs.go
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const APP_VER = "0.7.34.1208 Beta"
|
||||
const APP_VER = "0.7.35.1208 Beta"
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
|
124
models/repo.go
124
models/repo.go
|
@ -301,6 +301,10 @@ func (repo *Repository) RepoPath() string {
|
|||
return repo.repoPath(x)
|
||||
}
|
||||
|
||||
func (repo *Repository) GitConfigPath() string {
|
||||
return filepath.Join(repo.RepoPath(), "config")
|
||||
}
|
||||
|
||||
func (repo *Repository) RepoLink() string {
|
||||
return setting.AppSubUrl + "/" + repo.MustOwner().Name + "/" + repo.Name
|
||||
}
|
||||
|
@ -345,7 +349,7 @@ func (repo *Repository) LocalCopyPath() string {
|
|||
|
||||
func updateLocalCopy(repoPath, localPath string) error {
|
||||
if !com.IsExist(localPath) {
|
||||
if err := git.Clone(repoPath, localPath); err != nil {
|
||||
if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{}); err != nil {
|
||||
return fmt.Errorf("Clone: %v", err)
|
||||
}
|
||||
} else {
|
||||
|
@ -484,6 +488,8 @@ type Mirror struct {
|
|||
Interval int // Hour.
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
NextUpdate time.Time
|
||||
|
||||
address string `xorm:"-"`
|
||||
}
|
||||
|
||||
func (m *Mirror) AfterSet(colName string, _ xorm.Cell) {
|
||||
|
@ -497,6 +503,61 @@ func (m *Mirror) AfterSet(colName string, _ xorm.Cell) {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *Mirror) readAddress() {
|
||||
if len(m.address) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := ini.Load(m.Repo.GitConfigPath())
|
||||
if err != nil {
|
||||
log.Error(4, "Load: %v", err)
|
||||
return
|
||||
}
|
||||
m.address = cfg.Section("remote \"origin\"").Key("url").Value()
|
||||
}
|
||||
|
||||
// HandleCloneUserCredentials replaces user credentials from HTTP/HTTPS URL
|
||||
// with placeholder <credentials>.
|
||||
// It will fail for any other forms of clone addresses.
|
||||
func HandleCloneUserCredentials(url string, mosaics bool) string {
|
||||
i := strings.Index(url, "@")
|
||||
if i == -1 {
|
||||
return url
|
||||
}
|
||||
start := strings.Index(url, "://")
|
||||
if start == -1 {
|
||||
return url
|
||||
}
|
||||
if mosaics {
|
||||
return url[:start+3] + "<credentials>" + url[i:]
|
||||
}
|
||||
return url[:start+3] + url[i+1:]
|
||||
}
|
||||
|
||||
// Address returns mirror address from Git repository config without credentials.
|
||||
func (m *Mirror) Address() string {
|
||||
m.readAddress()
|
||||
return HandleCloneUserCredentials(m.address, false)
|
||||
}
|
||||
|
||||
// FullAddress returns mirror address from Git repository config.
|
||||
func (m *Mirror) FullAddress() string {
|
||||
m.readAddress()
|
||||
return m.address
|
||||
}
|
||||
|
||||
// SaveAddress writes new address to Git repository config.
|
||||
func (m *Mirror) SaveAddress(addr string) error {
|
||||
configPath := m.Repo.GitConfigPath()
|
||||
cfg, err := ini.Load(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Load: %v", err)
|
||||
}
|
||||
|
||||
cfg.Section("remote \"origin\"").Key("url").SetValue(addr)
|
||||
return cfg.SaveToIndent(configPath, "\t")
|
||||
}
|
||||
|
||||
func getMirror(e Engine, repoId int64) (*Mirror, error) {
|
||||
m := &Mirror{RepoID: repoId}
|
||||
has, err := e.Get(m)
|
||||
|
@ -527,25 +588,6 @@ func createUpdateHook(repoPath string) error {
|
|||
fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+setting.AppPath+"\"", setting.CustomConf))
|
||||
}
|
||||
|
||||
// MirrorRepository creates a mirror repository from source.
|
||||
func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
|
||||
_, stderr, err := process.ExecTimeout(10*time.Minute,
|
||||
fmt.Sprintf("MirrorRepository: %s/%s", userName, repoName),
|
||||
"git", "clone", "--mirror", url, repoPath)
|
||||
if err != nil {
|
||||
return errors.New("git clone --mirror: " + stderr)
|
||||
}
|
||||
|
||||
if _, err = x.InsertOne(&Mirror{
|
||||
RepoID: repoId,
|
||||
Interval: 24,
|
||||
NextUpdate: time.Now().Add(24 * time.Hour),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MigrateRepoOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
|
@ -582,29 +624,35 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
|||
repo.NumWatches = 1
|
||||
}
|
||||
|
||||
repo.IsBare = false
|
||||
if opts.IsMirror {
|
||||
if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, opts.RemoteAddr); err != nil {
|
||||
return repo, err
|
||||
}
|
||||
repo.IsMirror = true
|
||||
return repo, UpdateRepository(repo, false)
|
||||
} else {
|
||||
os.RemoveAll(repoPath)
|
||||
if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneRepoOptions{
|
||||
Mirror: true,
|
||||
Quiet: true,
|
||||
Timeout: 10 * time.Minute,
|
||||
}); err != nil {
|
||||
return repo, fmt.Errorf("Clone: %v", err)
|
||||
}
|
||||
|
||||
// FIXME: this command could for both migrate and mirror
|
||||
_, stderr, err := process.ExecTimeout(10*time.Minute,
|
||||
fmt.Sprintf("MigrateRepository: %s", repoPath),
|
||||
"git", "clone", "--mirror", "--bare", "--quiet", opts.RemoteAddr, repoPath)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("git clone --mirror --bare --quiet: %v", stderr)
|
||||
} else if err = createUpdateHook(repoPath); err != nil {
|
||||
return repo, fmt.Errorf("create update hook: %v", err)
|
||||
if opts.IsMirror {
|
||||
if _, err = x.InsertOne(&Mirror{
|
||||
RepoID: repo.ID,
|
||||
Interval: 24,
|
||||
NextUpdate: time.Now().Add(24 * time.Hour),
|
||||
}); err != nil {
|
||||
return repo, fmt.Errorf("InsertOne: %v", err)
|
||||
}
|
||||
|
||||
repo.IsMirror = true
|
||||
return repo, UpdateRepository(repo, false)
|
||||
}
|
||||
|
||||
if err = createUpdateHook(repoPath); err != nil {
|
||||
return repo, fmt.Errorf("createUpdateHook: %v", err)
|
||||
}
|
||||
|
||||
// Clean up mirror info which prevents "push --all".
|
||||
configPath := filepath.Join(repoPath, "/config")
|
||||
// This also removes possible user credentials.
|
||||
configPath := repo.GitConfigPath()
|
||||
cfg, err := ini.Load(configPath)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("open config file: %v", err)
|
||||
|
@ -615,7 +663,7 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
|||
}
|
||||
|
||||
// Check if repository is empty.
|
||||
_, stderr, err = com.ExecCmdDir(repoPath, "git", "log", "-1")
|
||||
_, stderr, err := com.ExecCmdDir(repoPath, "git", "log", "-1")
|
||||
if err != nil {
|
||||
if strings.Contains(stderr, "fatal: bad default revision 'HEAD'") {
|
||||
repo.IsBare = true
|
||||
|
|
|
@ -69,6 +69,9 @@ func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) {
|
|||
}
|
||||
if len(f.AuthUsername)+len(f.AuthPassword) > 0 {
|
||||
u.User = url.UserPassword(f.AuthUsername, f.AuthPassword)
|
||||
} else {
|
||||
// Fake user name and password to prevent prompt and fail quick.
|
||||
u.User = url.UserPassword("fake_user", "")
|
||||
}
|
||||
remoteAddr = u.String()
|
||||
} else if !user.CanImportLocal() {
|
||||
|
@ -86,6 +89,7 @@ type RepoSettingForm struct {
|
|||
Website string `binding:"Url;MaxSize(100)"`
|
||||
Branch string
|
||||
Interval int
|
||||
MirrorAddress string
|
||||
Private bool
|
||||
|
||||
// Advanced settings
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -116,6 +116,7 @@ func Toggle(options *ToggleOptions) macaron.Handler {
|
|||
ctx.Handle(500, "AutoSignIn", err)
|
||||
return
|
||||
} else if succeed {
|
||||
log.Trace("Auto-login succeed: %s", ctx.Session.Get("uname"))
|
||||
ctx.Redirect(setting.AppSubUrl + ctx.Req.RequestURI)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -129,6 +129,7 @@ func RepoAssignment(args ...bool) macaron.Handler {
|
|||
return
|
||||
}
|
||||
ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
|
||||
ctx.Data["Mirror"] = ctx.Repo.Mirror
|
||||
}
|
||||
|
||||
ctx.Repo.Repository = repo
|
||||
|
|
|
@ -234,7 +234,7 @@ func Migrate(ctx *middleware.Context, form auth.MigrateRepoForm) {
|
|||
log.Error(4, "DeleteRepository: %v", errDelete)
|
||||
}
|
||||
}
|
||||
ctx.APIError(500, "MigrateRepository", err)
|
||||
ctx.APIError(500, "MigrateRepository", models.HandleCloneUserCredentials(err.Error(), true))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,8 @@ func checkRunMode() {
|
|||
macaron.Env = macaron.PROD
|
||||
macaron.ColorLog = false
|
||||
setting.ProdMode = true
|
||||
git.Debug = false
|
||||
default:
|
||||
git.Debug = true
|
||||
}
|
||||
log.Info("Run Mode: %s", strings.Title(macaron.Env))
|
||||
}
|
||||
|
|
|
@ -206,11 +206,11 @@ func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) {
|
|||
if strings.Contains(err.Error(), "Authentication failed") ||
|
||||
strings.Contains(err.Error(), "could not read Username") {
|
||||
ctx.Data["Err_Auth"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.auth_failed", strings.Replace(err.Error(), ":"+form.AuthPassword+"@", ":<password>@", 1)), MIGRATE, &form)
|
||||
ctx.RenderWithErr(ctx.Tr("form.auth_failed", models.HandleCloneUserCredentials(err.Error(), true)), MIGRATE, &form)
|
||||
return
|
||||
} else if strings.Contains(err.Error(), "fatal:") {
|
||||
ctx.Data["Err_CloneAddr"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", strings.Replace(err.Error(), ":"+form.AuthPassword+"@", ":<password>@", 1)), MIGRATE, &form)
|
||||
ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", models.HandleCloneUserCredentials(err.Error(), true)), MIGRATE, &form)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -109,9 +109,14 @@ func SettingsPost(ctx *middleware.Context, form auth.RepoSettingForm) {
|
|||
ctx.Repo.Mirror.Interval = form.Interval
|
||||
ctx.Repo.Mirror.NextUpdate = time.Now().Add(time.Duration(form.Interval) * time.Hour)
|
||||
if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
|
||||
log.Error(4, "UpdateMirror: %v", err)
|
||||
ctx.Handle(500, "UpdateMirror", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := ctx.Repo.Mirror.SaveAddress(form.MirrorAddress); err != nil {
|
||||
ctx.Handle(500, "SaveAddress", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.7.34.1208 Beta
|
||||
0.7.35.1208 Beta
|
|
@ -8,7 +8,7 @@
|
|||
<a href="{{AppSubUrl}}/{{.Owner.Name}}">{{.Owner.Name}}</a>
|
||||
<div class="divider"> / </div>
|
||||
<a href="{{$.RepoLink}}">{{.Name}}</a>
|
||||
{{if .IsMirror}}<div class="ui label">{{$.i18n.Tr "mirror"}}</div>{{end}}
|
||||
{{if .IsMirror}}<div class="fork-flag">{{$.i18n.Tr "repo.mirror_from"}} <a target="_blank" href="{{$.MirrorAddress}}">{{$.Mirror.Address}}</a></div>{{end}}
|
||||
{{if .IsFork}}<div class="fork-flag">{{$.i18n.Tr "repo.forked_from"}} <a href="{{.BaseRepo.RepoLink}}">{{SubStr .BaseRepo.RepoLink 1 -1}}</a></div>{{end}}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -55,6 +55,11 @@
|
|||
<label for="interval">{{.i18n.Tr "repo.mirror_interval"}}</label>
|
||||
<input id="interval" name="interval" type="number" value="{{.MirrorInterval}}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="mirror_address">{{.i18n.Tr "repo.mirror_address"}}</label>
|
||||
<input id="mirror_address" name="mirror_address" value="{{.Mirror.FullAddress}}">
|
||||
<p class="help">{{.i18n.Tr "repo.mirror_address_desc"}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="ui divider"></div>
|
||||
|
|
Reference in a new issue