| @@ -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 [ | |||
| ##### 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 | |||
| @@ -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()) | |||
| @@ -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 | |||
| 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) | |||
| } | |||
| if opts.IsMirror { | |||
| if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, opts.RemoteAddr); err != nil { | |||
| return repo, err | |||
| 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) | |||
| } else { | |||
| os.RemoveAll(repoPath) | |||
| } | |||
| // 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 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() { | |||
| @@ -81,12 +84,13 @@ func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) { | |||
| } | |||
| type RepoSettingForm struct { | |||
| RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` | |||
| Description string `binding:"MaxSize(255)"` | |||
| Website string `binding:"Url;MaxSize(100)"` | |||
| Branch string | |||
| Interval int | |||
| Private bool | |||
| RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` | |||
| Description string `binding:"MaxSize(255)"` | |||
| Website string `binding:"Url;MaxSize(100)"` | |||
| Branch string | |||
| Interval int | |||
| MirrorAddress string | |||
| Private bool | |||
| // Advanced settings | |||
| EnableWiki bool | |||
| @@ -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)) | |||
| } | |||
| @@ -192,7 +192,7 @@ func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) { | |||
| RemoteAddr: remoteAddr, | |||
| }) | |||
| if err == nil { | |||
| log.Trace("Repository migrated[%d]: %s/%s", repo.ID, ctxUser.Name, form.RepoName) | |||
| log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, form.RepoName) | |||
| ctx.Redirect(setting.AppSubUrl + "/" + ctxUser.Name + "/" + form.RepoName) | |||
| return | |||
| } | |||
| @@ -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> | |||