From 932f717adb1cb1738415fcddaf5eb3c0e4a60fc2 Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 04:44:57 -0400 Subject: [PATCH 01/38] Fixing bug --- models/action.go | 10 ++++++++++ models/repo.go | 9 +++++++-- models/user.go | 4 +--- modules/middleware/repo.go | 2 +- serve.go | 2 +- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/models/action.go b/models/action.go index 107d4b1057..89751b9779 100644 --- a/models/action.go +++ b/models/action.go @@ -79,6 +79,16 @@ func CommitRepoAction(userId int64, userName string, }) return err } + + // Update repository last update time. + repo, err := GetRepositoryByName(userId, repoName) + if err != nil { + return err + } + repo.Updated = time.Now() + if err = UpdateRepository(repo); err != nil { + return err + } return nil } diff --git a/models/repo.go b/models/repo.go index 918e5dc84c..6a764e6c31 100644 --- a/models/repo.go +++ b/models/repo.go @@ -358,6 +358,11 @@ func RepoPath(userName, repoName string) string { return filepath.Join(UserPath(userName), repoName+".git") } +func UpdateRepository(repo *Repository) error { + _, err := orm.Id(repo.Id).UseBool().Update(repo) + return err +} + // DeleteRepository deletes a repository for a user or orgnaztion. func DeleteRepository(userId, repoId int64, userName string) (err error) { repo := &Repository{Id: repoId, OwnerId: userId} @@ -402,9 +407,9 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) { } // GetRepositoryByName returns the repository by given name under user if exists. -func GetRepositoryByName(user *User, repoName string) (*Repository, error) { +func GetRepositoryByName(userId int64, repoName string) (*Repository, error) { repo := &Repository{ - OwnerId: user.Id, + OwnerId: userId, LowerName: strings.ToLower(repoName), } has, err := orm.Get(repo) diff --git a/models/user.go b/models/user.go index 3c11091285..d6dc041490 100644 --- a/models/user.go +++ b/models/user.go @@ -279,9 +279,7 @@ func GetUserByName(name string) (*User, error) { if len(name) == 0 { return nil, ErrUserNotExist } - user := &User{ - LowerName: strings.ToLower(name), - } + user := &User{LowerName: strings.ToLower(name)} has, err := orm.Get(user) if err != nil { return nil, err diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go index a9a90e3ff5..3864caaf80 100644 --- a/modules/middleware/repo.go +++ b/modules/middleware/repo.go @@ -54,7 +54,7 @@ func RepoAssignment(redirect bool) martini.Handler { ctx.Repo.Owner = user // get repository - repo, err := models.GetRepositoryByName(user, params["reponame"]) + repo, err := models.GetRepositoryByName(user.Id, params["reponame"]) if err != nil { if redirect { ctx.Redirect("/") diff --git a/serve.go b/serve.go index 3ce8f9046c..be8dedc985 100644 --- a/serve.go +++ b/serve.go @@ -86,7 +86,7 @@ func runServ(*cli.Context) { os.Setenv("userName", user.Name) os.Setenv("userId", strconv.Itoa(int(user.Id))) - repo, err := models.GetRepositoryByName(user, repoName) + repo, err := models.GetRepositoryByName(user.Id, repoName) if err != nil { println("Unavilable repository", err) return From 76cd448e7925997b60a54e8d9431ffd0826cc24e Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 06:20:00 -0400 Subject: [PATCH 02/38] Add admin delete user --- gogs.go | 2 +- models/action.go | 6 ++++++ routers/admin/user.go | 35 +++++++++++++++++++++++++++++++++ templates/admin/users/edit.tmpl | 2 +- web.go | 1 + 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/gogs.go b/gogs.go index 41df79280a..8ec4fd42f1 100644 --- a/gogs.go +++ b/gogs.go @@ -20,7 +20,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.1.5.0321" +const APP_VER = "0.1.5.0322" func init() { base.AppVer = APP_VER diff --git a/models/action.go b/models/action.go index 89751b9779..12122ae240 100644 --- a/models/action.go +++ b/models/action.go @@ -7,6 +7,8 @@ package models import ( "encoding/json" "time" + + "github.com/gogits/gogs/modules/log" ) // Operation types of user action. @@ -89,6 +91,8 @@ func CommitRepoAction(userId int64, userName string, if err = UpdateRepository(repo); err != nil { return err } + + log.Trace("action.CommitRepoAction: %d/%s", userId, repo.LowerName) return nil } @@ -102,6 +106,8 @@ func NewRepoAction(user *User, repo *Repository) error { RepoId: repo.Id, RepoName: repo.Name, }) + + log.Trace("action.NewRepoAction: %s/%s", user.LowerName, repo.LowerName) return err } diff --git a/routers/admin/user.go b/routers/admin/user.go index d6f8523218..fa27d11664 100644 --- a/routers/admin/user.go +++ b/routers/admin/user.go @@ -107,3 +107,38 @@ func EditUser(ctx *middleware.Context, params martini.Params, form auth.AdminEdi log.Trace("%s User profile updated by admin(%s): %s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.User.LowerName) } + +func DeleteUser(ctx *middleware.Context, params martini.Params) { + ctx.Data["Title"] = "Edit Account" + ctx.Data["PageIsUsers"] = true + + uid, err := base.StrTo(params["userid"]).Int() + if err != nil { + ctx.Handle(200, "admin.user.EditUser", err) + return + } + + u, err := models.GetUserById(int64(uid)) + if err != nil { + ctx.Handle(200, "admin.user.EditUser", err) + return + } + + if err = models.DeleteUser(u); err != nil { + ctx.Data["HasError"] = true + switch err { + case models.ErrUserOwnRepos: + ctx.Data["ErrorMsg"] = "This account still has ownership of repository, owner has to delete or transfer them first." + ctx.Data["User"] = u + ctx.HTML(200, "admin/users/edit") + default: + ctx.Handle(200, "admin.user.DeleteUser", err) + } + return + } + + log.Trace("%s User deleted by admin(%s): %s", ctx.Req.RequestURI, + ctx.User.LowerName, ctx.User.LowerName) + + ctx.Redirect("/admin/users", 302) +} diff --git a/templates/admin/users/edit.tmpl b/templates/admin/users/edit.tmpl index 415bcedc92..2a9882423a 100644 --- a/templates/admin/users/edit.tmpl +++ b/templates/admin/users/edit.tmpl @@ -71,7 +71,7 @@ <div class="form-group"> <div class="col-md-offset-3 col-md-6"> <button type="submit" class="btn btn-lg btn-primary btn-block">Update account profile</button> - <!-- <a type="button" href="/admin/users/{{.User.Id}}/delete" class="btn btn-lg btn-danger btn-block">Delete this account</a> --> + <a type="button" href="/admin/users/{{.User.Id}}/delete" class="btn btn-lg btn-danger btn-block">Delete this account</a> </div> </div> </form> diff --git a/web.go b/web.go index bb316a6724..595b8f74ed 100644 --- a/web.go +++ b/web.go @@ -119,6 +119,7 @@ func runWeb(*cli.Context) { m.Get("/admin/users", reqSignIn, adminReq, admin.Users) m.Any("/admin/users/new", reqSignIn, adminReq, binding.BindIgnErr(auth.RegisterForm{}), admin.NewUser) m.Any("/admin/users/:userid", reqSignIn, adminReq, binding.BindIgnErr(auth.AdminEditUserForm{}), admin.EditUser) + m.Any("/admin/users/:userid/delete", reqSignIn, adminReq, admin.DeleteUser) m.Get("/admin/repos", reqSignIn, adminReq, admin.Repositories) m.Get("/admin/config", reqSignIn, adminReq, admin.Config) From 7a1ff8636c01844a501dd9cdca2c436d1b7826b7 Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 06:42:19 -0400 Subject: [PATCH 03/38] Add config option: Picture cache path --- conf/app.ini | 8 ++++++++ modules/base/conf.go | 8 ++++++++ routers/admin/admin.go | 3 +++ templates/admin/config.tmpl | 12 ++++++++++++ 4 files changed, 31 insertions(+) diff --git a/conf/app.ini b/conf/app.ini index ecb0d2511f..cf99c9da09 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -44,6 +44,8 @@ REGISTER_EMAIL_CONFIRM = false DISENABLE_REGISTERATION = false ; User must sign in to view anything. REQUIRE_SIGNIN_VIEW = false +; Cache avatar as picture +ENABLE_CACHE_AVATAR = false [mailer] ENABLED = false @@ -70,6 +72,12 @@ INTERVAL = 60 ; memcache: "127.0.0.1:11211" HOST = +[picture] +; The place to picture data, either "server" or "qiniu", default is "server" +SERVICE = server +; For "server" only, root path of picture data, default is "data/pictures" +PATH = data/pictures + [log] ; Either "console", "file", "conn" or "smtp", default is "console" MODE = console diff --git a/modules/base/conf.go b/modules/base/conf.go index 863daca644..8c6ee62818 100644 --- a/modules/base/conf.go +++ b/modules/base/conf.go @@ -44,6 +44,9 @@ var ( CacheAdapter string CacheConfig string + PictureService string + PictureRootPath string + LogMode string LogConfig string ) @@ -52,6 +55,7 @@ var Service struct { RegisterEmailConfirm bool DisenableRegisteration bool RequireSignInView bool + EnableCacheAvatar bool ActiveCodeLives int ResetPwdCodeLives int } @@ -82,6 +86,7 @@ func newService() { Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180) Service.DisenableRegisteration = Cfg.MustBool("service", "DISENABLE_REGISTERATION", false) Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW", false) + Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR", false) } func newLogService() { @@ -214,6 +219,9 @@ func NewConfigContext() { SecretKey = Cfg.MustValue("security", "SECRET_KEY") RunUser = Cfg.MustValue("", "RUN_USER") + PictureService = Cfg.MustValue("picture", "SERVICE") + PictureRootPath = Cfg.MustValue("picture", "PATH") + // Determine and create root git reposiroty path. RepoRootPath = Cfg.MustValue("repository", "ROOT") if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil { diff --git a/routers/admin/admin.go b/routers/admin/admin.go index 2e19b99c10..25ed8981e0 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -70,6 +70,9 @@ func Config(ctx *middleware.Context) { ctx.Data["CacheAdapter"] = base.CacheAdapter ctx.Data["CacheConfig"] = base.CacheConfig + ctx.Data["PictureService"] = base.PictureService + ctx.Data["PictureRootPath"] = base.PictureRootPath + ctx.Data["LogMode"] = base.LogMode ctx.Data["LogConfig"] = base.LogConfig diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 6906f2409d..e3f69ee6ea 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -45,6 +45,7 @@ <div><b>Register Email Confirmation:</b> <i class="fa fa{{if .Service.RegisterEmailConfirm}}-check{{end}}-square-o"></i></div> <div><b>Disenable Registeration:</b> <i class="fa fa{{if .Service.DisenableRegisteration}}-check{{end}}-square-o"></i></div> <div><b>Require Sign In View:</b> <i class="fa fa{{if .Service.RequireSignInView}}-check{{end}}-square-o"></i></div> + <div><b>Enable Cache Avatar:</b> <i class="fa fa{{if .Service.EnableCacheAvatar}}-check{{end}}-square-o"></i></div> <hr/> <div><b>Active Code Lives:</b> {{.Service.ActiveCodeLives}} minutes</div> <div><b>Reset Password Code Lives:</b> {{.Service.ResetPwdCodeLives}} minutes</div> @@ -76,6 +77,17 @@ </div> </div> + <div class="panel panel-default"> + <div class="panel-heading"> + Picture Configuration + </div> + + <div class="panel-body"> + <div><b>Picture Service:</b> {{.PictureService}}</div> + <div><b>Picture Root Path:</b> {{.PictureRootPath}}</div> + </div> + </div> + <div class="panel panel-default"> <div class="panel-heading"> Log Configuration From 0d1872ebe3f11c14f31f454ed8d719a22c0884d0 Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 07:42:24 -0400 Subject: [PATCH 04/38] Add admin memStatus panel --- README.md | 3 +- routers/admin/admin.go | 81 ++++++++++++++++++++++++++++++++++ templates/admin/dashboard.tmpl | 33 +++++++++++++- 3 files changed, 115 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cbd1f588df..a9ab7fe498 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ There are two ways to install Gogs: ## Acknowledgments -- Mail service is based on [WeTalk](https://github.com/beego/wetalk). - Logo is inspired by [martini](https://github.com/martini-contrib). +- Mail service is based on [WeTalk](https://github.com/beego/wetalk). +- System Monitor Status is based on [GoBlog](https://github.com/fuxiaohei/goblog). ## Contributors diff --git a/routers/admin/admin.go b/routers/admin/admin.go index 25ed8981e0..57a46d1dfe 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -5,7 +5,10 @@ package admin import ( + "fmt" + "runtime" "strings" + "time" "github.com/codegangsta/martini" @@ -14,10 +17,88 @@ import ( "github.com/gogits/gogs/modules/middleware" ) +var sysStatus struct { + NumGoroutine int + + // General statistics. + MemAllocated string // bytes allocated and still in use + MemTotal string // bytes allocated (even if freed) + MemSys string // bytes obtained from system (sum of XxxSys below) + Lookups uint64 // number of pointer lookups + MemMallocs uint64 // number of mallocs + MemFrees uint64 // number of frees + + // Main allocation heap statistics. + HeapAlloc string // bytes allocated and still in use + HeapSys string // bytes obtained from system + HeapIdle string // bytes in idle spans + HeapInuse string // bytes in non-idle span + HeapReleased string // bytes released to the OS + HeapObjects uint64 // total number of allocated objects + + // Low-level fixed-size structure allocator statistics. + // Inuse is bytes used now. + // Sys is bytes obtained from system. + StackInuse string // bootstrap stacks + StackSys string + MSpanInuse string // mspan structures + MSpanSys string + MCacheInuse string // mcache structures + MCacheSys string + BuckHashSys string // profiling bucket hash table + GCSys string // GC metadata + OtherSys string // other system allocations + + // Garbage collector statistics. + NextGC string // next run in HeapAlloc time (bytes) + LastGC string // last run in absolute time (ns) + PauseTotalNs string + PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256] + NumGC uint32 +} + +func updateSystemStatus() { + m := new(runtime.MemStats) + runtime.ReadMemStats(m) + sysStatus.NumGoroutine = runtime.NumGoroutine() + + sysStatus.MemAllocated = base.FileSize(int64(m.Alloc)) + sysStatus.MemTotal = base.FileSize(int64(m.TotalAlloc)) + sysStatus.MemSys = base.FileSize(int64(m.Sys)) + sysStatus.Lookups = m.Lookups + sysStatus.MemMallocs = m.Mallocs + sysStatus.MemFrees = m.Frees + + sysStatus.HeapAlloc = base.FileSize(int64(m.HeapAlloc)) + sysStatus.HeapSys = base.FileSize(int64(m.HeapSys)) + sysStatus.HeapIdle = base.FileSize(int64(m.HeapIdle)) + sysStatus.HeapInuse = base.FileSize(int64(m.HeapInuse)) + sysStatus.HeapReleased = base.FileSize(int64(m.HeapReleased)) + sysStatus.HeapObjects = m.HeapObjects + + sysStatus.StackInuse = base.FileSize(int64(m.StackInuse)) + sysStatus.StackSys = base.FileSize(int64(m.StackSys)) + sysStatus.MSpanInuse = base.FileSize(int64(m.MSpanInuse)) + sysStatus.MSpanSys = base.FileSize(int64(m.MSpanSys)) + sysStatus.MCacheInuse = base.FileSize(int64(m.MCacheInuse)) + sysStatus.MCacheSys = base.FileSize(int64(m.MCacheSys)) + sysStatus.BuckHashSys = base.FileSize(int64(m.BuckHashSys)) + sysStatus.GCSys = base.FileSize(int64(m.GCSys)) + sysStatus.OtherSys = base.FileSize(int64(m.OtherSys)) + + sysStatus.NextGC = base.FileSize(int64(m.NextGC)) + sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000) + sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs/1000/1000/1000)) + sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256]/1000/1000/1000)) + sysStatus.NumGC = m.NumGC +} + func Dashboard(ctx *middleware.Context) { ctx.Data["Title"] = "Admin Dashboard" ctx.Data["PageIsDashboard"] = true ctx.Data["Stats"] = models.GetStatistic() + updateSystemStatus() + ctx.Data["SysStatus"] = sysStatus ctx.HTML(200, "admin/dashboard") } diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl index 6088487d62..0bebf8318f 100644 --- a/templates/admin/dashboard.tmpl +++ b/templates/admin/dashboard.tmpl @@ -15,10 +15,41 @@ <div class="panel panel-default"> <div class="panel-heading"> - System Status + System Monitor Status </div> <div class="panel-body"> + <div>Current Goroutines: <b>{{.SysStatus.NumGoroutine}}</b></div> + <hr/> + <div>Current Memory Usage: <b>{{.SysStatus.MemAllocated}}</b></div> + <div>Total Memory Allocated: <b>{{.SysStatus.MemTotal}}</b></div> + <div>Memory Obtained: <b>{{.SysStatus.MemSys}}</b></div> + <div>Pointer Lookup Times: <b>{{.SysStatus.Lookups}}</b></div> + <div>Memory Allocate Times: <b>{{.SysStatus.MemMallocs}}</b></div> + <div>Memory Free Times: <b>{{.SysStatus.MemFrees}}</b></div> + <hr/> + <div>Current Heap Usage: <b>{{.SysStatus.HeapAlloc}}</b></div> + <div>Heap Memory Obtained: <b>{{.SysStatus.HeapSys}}</b></div> + <div>Heap Memory Idle: <b>{{.SysStatus.HeapIdle}}</b></div> + <div>Heap Memory In Use: <b>{{.SysStatus.HeapInuse}}</b></div> + <div>Heap Memory Released: <b>{{.SysStatus.HeapReleased}}</b></div> + <div>Heap Objects: <b>{{.SysStatus.HeapObjects}}</b></div> + <hr/> + <div>Bootstrap Stack Usage: <b>{{.SysStatus.StackInuse}}</b></div> + <div>Stack Memory Obtained: <b>{{.SysStatus.StackSys}}</b></div> + <div>MSpan Structures Usage: <b>{{.SysStatus.MSpanInuse}}</b></div> + <div>MSpan Structures Obtained: <b>{{.SysStatus.HeapSys}}</b></div> + <div>MCache Structures Usage: <b>{{.SysStatus.MCacheInuse}}</b></div> + <div>MCache Structures Obtained: <b>{{.SysStatus.MCacheSys}}</b></div> + <div>Profiling Bucket Hash Table Obtained: <b>{{.SysStatus.BuckHashSys}}</b></div> + <div>GC Metadada Obtained: <b>{{.SysStatus.GCSys}}</b></div> + <div>Other System Allocation Obtained: <b>{{.SysStatus.OtherSys}}</b></div> + <hr/> + <div>Next GC Recycle: <b>{{.SysStatus.NextGC}}</b></div> + <div>Last GC Time: <b>{{.SysStatus.LastGC}} ago</b></div> + <div>Total GC Pause: <b>{{.SysStatus.PauseTotalNs}}</b></div> + <div>Last GC Pause: <b>{{.SysStatus.PauseNs}}</b></div> + <div>GC Times: <b>{{.SysStatus.NumGC}}</b></div> </div> </div> </div> From f9c07c4186b61a1548d9a908fe6228bd130f4f92 Mon Sep 17 00:00:00 2001 From: slene <vslene@gmail.com> Date: Sat, 22 Mar 2014 20:49:53 +0800 Subject: [PATCH 05/38] update session --- .gitignore | 1 + conf/app.ini | 27 +++++++++++++++++++++++++++ modules/auth/user.go | 11 ++++++----- modules/base/conf.go | 30 ++++++++++++++++++++++++++++++ modules/middleware/context.go | 24 ++++++++++++++---------- routers/user/user.go | 2 +- web.go | 5 ----- 7 files changed, 79 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index ad27cc8be8..d201223ef9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ gogs *.db *.log custom/ +data/ .vendor/ .idea/ *.iml \ No newline at end of file diff --git a/conf/app.ini b/conf/app.ini index cf99c9da09..cf2ae31d83 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -72,6 +72,33 @@ INTERVAL = 60 ; memcache: "127.0.0.1:11211" HOST = +[session] +; Either "memory", "file", "redis" or "mysql", default is "memory" +PROVIDER = file +; provider config +; memory: not have any config yet +; file: session file path +; e.g. tmp/sessions +; redis: config like redis server addr,poolSize,password +; e.g. 127.0.0.1:6379,100,astaxie +; mysql: go-sql-driver/mysql dsn config string +; e.g. root:password@/session_table +PROVIDER_CONFIG = data/sessions +; session cookie name +COOKIE_NAME = i_like_gogits +; if you use session in https only, default is false +COOKIE_SECURE = false +; enable set cookie, default is true +ENABLE_SET_COOKIE = true +; session gc time interval, default is 86400 +GC_INTERVAL_TIME = 86400 +; session life time, default is 86400 +SESSION_LIFE_TIME = 86400 +; session id hash func, default is sha1 +SESSION_ID_HASHFUNC = sha1 +; session hash key, default is use random string +SESSION_ID_HASHKEY = + [picture] ; The place to picture data, either "server" or "qiniu", default is "server" SERVICE = server diff --git a/modules/auth/user.go b/modules/auth/user.go index f8d8f66149..cb8db1b29a 100644 --- a/modules/auth/user.go +++ b/modules/auth/user.go @@ -9,7 +9,8 @@ import ( "reflect" "github.com/codegangsta/martini" - "github.com/martini-contrib/sessions" + + "github.com/gogits/session" "github.com/gogits/binding" @@ -19,7 +20,7 @@ import ( ) // SignedInId returns the id of signed in user. -func SignedInId(session sessions.Session) int64 { +func SignedInId(session session.SessionStore) int64 { userId := session.Get("userId") if userId == nil { return 0 @@ -34,7 +35,7 @@ func SignedInId(session sessions.Session) int64 { } // SignedInName returns the name of signed in user. -func SignedInName(session sessions.Session) string { +func SignedInName(session session.SessionStore) string { userName := session.Get("userName") if userName == nil { return "" @@ -46,7 +47,7 @@ func SignedInName(session sessions.Session) string { } // SignedInUser returns the user object of signed user. -func SignedInUser(session sessions.Session) *models.User { +func SignedInUser(session session.SessionStore) *models.User { id := SignedInId(session) if id <= 0 { return nil @@ -61,7 +62,7 @@ func SignedInUser(session sessions.Session) *models.User { } // IsSignedIn check if any user has signed in. -func IsSignedIn(session sessions.Session) bool { +func IsSignedIn(session session.SessionStore) bool { return SignedInId(session) > 0 } diff --git a/modules/base/conf.go b/modules/base/conf.go index 8c6ee62818..d5e27d043b 100644 --- a/modules/base/conf.go +++ b/modules/base/conf.go @@ -16,6 +16,7 @@ import ( "github.com/Unknwon/goconfig" "github.com/gogits/cache" + "github.com/gogits/session" "github.com/gogits/gogs/modules/log" ) @@ -49,6 +50,10 @@ var ( LogMode string LogConfig string + + SessionProvider string + SessionConfig *session.Config + SessionManager *session.Manager ) var Service struct { @@ -164,6 +169,30 @@ func newCacheService() { log.Info("Cache Service Enabled") } +func newSessionService() { + SessionProvider = Cfg.MustValue("session", "PROVIDER", "memory") + + SessionConfig = new(session.Config) + SessionConfig.ProviderConfig = Cfg.MustValue("session", "PROVIDER_CONFIG") + SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits") + SessionConfig.CookieSecure = Cfg.MustBool("session", "COOKIE_SECURE") + SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true) + SessionConfig.GcIntervalTime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400) + SessionConfig.SessionLifeTime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400) + SessionConfig.SessionIDHashFunc = Cfg.MustValue("session", "SESSION_ID_HASHFUNC", "sha1") + SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY") + + var err error + SessionManager, err = session.NewManager(SessionProvider, *SessionConfig) + if err != nil { + fmt.Printf("Init session system failed, provider: %s, %v\n", + SessionProvider, err) + os.Exit(2) + } + + log.Info("Session Service Enabled") +} + func newMailService() { // Check mailer setting. if Cfg.MustBool("mailer", "ENABLED") { @@ -234,6 +263,7 @@ func NewServices() { newService() newLogService() newCacheService() + newSessionService() newMailService() newRegisterMailService() } diff --git a/modules/middleware/context.go b/modules/middleware/context.go index a25a3dbbeb..c958c1d6cd 100644 --- a/modules/middleware/context.go +++ b/modules/middleware/context.go @@ -10,9 +10,9 @@ import ( "time" "github.com/codegangsta/martini" - "github.com/martini-contrib/sessions" "github.com/gogits/cache" + "github.com/gogits/session" "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/auth" @@ -27,7 +27,7 @@ type Context struct { p martini.Params Req *http.Request Res http.ResponseWriter - Session sessions.Session + Session session.SessionStore Cache cache.Cache User *models.User IsSigned bool @@ -92,21 +92,25 @@ func (ctx *Context) Handle(status int, title string, err error) { // InitContext initializes a classic context for a request. func InitContext() martini.Handler { - return func(res http.ResponseWriter, r *http.Request, c martini.Context, - session sessions.Session, rd *Render) { + return func(res http.ResponseWriter, r *http.Request, c martini.Context, rd *Render) { ctx := &Context{ c: c, // p: p, - Req: r, - Res: res, - Session: session, - Cache: base.Cache, - Render: rd, + Req: r, + Res: res, + Cache: base.Cache, + Render: rd, } + // start session + ctx.Session = base.SessionManager.SessionStart(res, r) + defer func() { + ctx.Session.SessionRelease(res) + }() + // Get user from session if logined. - user := auth.SignedInUser(session) + user := auth.SignedInUser(ctx.Session) ctx.User = user ctx.IsSigned = user != nil diff --git a/routers/user/user.go b/routers/user/user.go index d38eb1ceb3..2244697714 100644 --- a/routers/user/user.go +++ b/routers/user/user.go @@ -88,7 +88,7 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) { user, err := models.LoginUserPlain(form.UserName, form.Password) if err != nil { - if err.Error() == models.ErrUserNotExist.Error() { + if err == models.ErrUserNotExist { ctx.RenderWithErr("Username or password is not correct", "user/signin", &form) return } diff --git a/web.go b/web.go index 595b8f74ed..ac5761d720 100644 --- a/web.go +++ b/web.go @@ -12,7 +12,6 @@ import ( "github.com/codegangsta/cli" "github.com/codegangsta/martini" - "github.com/martini-contrib/sessions" "github.com/gogits/binding" @@ -81,10 +80,6 @@ func runWeb(*cli.Context) { // Middlewares. m.Use(middleware.Renderer(middleware.RenderOptions{Funcs: []template.FuncMap{base.TemplateFuncs}})) - // TODO: should use other store because cookie store is not secure. - store := sessions.NewCookieStore([]byte("secret123")) - m.Use(sessions.Sessions("my_session", store)) - m.Use(middleware.InitContext()) reqSignIn := middleware.SignInRequire(true) From 01e781dedb3c6d48349516de0eee5cea41c077e1 Mon Sep 17 00:00:00 2001 From: slene <vslene@gmail.com> Date: Sat, 22 Mar 2014 21:19:27 +0800 Subject: [PATCH 06/38] add comment --- conf/app.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/app.ini b/conf/app.ini index cf2ae31d83..6b3ce8d240 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -94,7 +94,7 @@ ENABLE_SET_COOKIE = true GC_INTERVAL_TIME = 86400 ; session life time, default is 86400 SESSION_LIFE_TIME = 86400 -; session id hash func, default is sha1 +; session id hash func, Either "sha1", "sha256" or "md5" default is sha1 SESSION_ID_HASHFUNC = sha1 ; session hash key, default is use random string SESSION_ID_HASHKEY = From fd1831052c3a79492643b89512282fc66f34dd8d Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 09:21:57 -0400 Subject: [PATCH 07/38] Update session --- .gopmfile | 1 - README.md | 2 +- conf/app.ini | 25 +++++----- gogs.go | 2 +- modules/base/conf.go | 16 ++++--- modules/base/tool.go | 84 +++++++++++++++++++++++++++++++++- routers/admin/admin.go | 12 ++++- templates/admin/config.tmpl | 19 ++++++++ templates/admin/dashboard.tmpl | 1 + 9 files changed, 135 insertions(+), 27 deletions(-) diff --git a/.gopmfile b/.gopmfile index 5b690a06a7..6e6b59c620 100644 --- a/.gopmfile +++ b/.gopmfile @@ -4,7 +4,6 @@ path=github.com/gogits/gogs [deps] github.com/codegangsta/cli= github.com/codegangsta/martini= -github.com/martini-contrib/sessions= github.com/Unknwon/com= github.com/Unknwon/cae= github.com/Unknwon/goconfig= diff --git a/README.md b/README.md index a9ab7fe498..35044927ff 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ There are two ways to install Gogs: ## Acknowledgments - Logo is inspired by [martini](https://github.com/martini-contrib). -- Mail service is based on [WeTalk](https://github.com/beego/wetalk). +- Mail Service is based on [WeTalk](https://github.com/beego/wetalk). - System Monitor Status is based on [GoBlog](https://github.com/fuxiaohei/goblog). ## Contributors diff --git a/conf/app.ini b/conf/app.ini index cf2ae31d83..30d6c7d483 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -75,28 +75,25 @@ HOST = [session] ; Either "memory", "file", "redis" or "mysql", default is "memory" PROVIDER = file -; provider config +; Provider config options ; memory: not have any config yet -; file: session file path -; e.g. tmp/sessions -; redis: config like redis server addr,poolSize,password -; e.g. 127.0.0.1:6379,100,astaxie -; mysql: go-sql-driver/mysql dsn config string -; e.g. root:password@/session_table +; file: session file path, e.g. data/sessions +; redis: config like redis server addr, poolSize, password, e.g. 127.0.0.1:6379,100,astaxie +; mysql: go-sql-driver/mysql dsn config string, e.g. root:password@/session_table PROVIDER_CONFIG = data/sessions -; session cookie name +; Session cookie name COOKIE_NAME = i_like_gogits -; if you use session in https only, default is false +; If you use session in https only, default is false COOKIE_SECURE = false -; enable set cookie, default is true +; Enable set cookie, default is true ENABLE_SET_COOKIE = true -; session gc time interval, default is 86400 +; Session GC time interval, default is 86400 GC_INTERVAL_TIME = 86400 -; session life time, default is 86400 +; Session life time, default is 86400 SESSION_LIFE_TIME = 86400 -; session id hash func, default is sha1 +; Session id hash func, default is sha1 SESSION_ID_HASHFUNC = sha1 -; session hash key, default is use random string +; Session hash key, default is use random string SESSION_ID_HASHKEY = [picture] diff --git a/gogs.go b/gogs.go index 8ec4fd42f1..a609032093 100644 --- a/gogs.go +++ b/gogs.go @@ -20,7 +20,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.1.5.0322" +const APP_VER = "0.1.5.0322.2" func init() { base.AppVer = APP_VER diff --git a/modules/base/conf.go b/modules/base/conf.go index d5e27d043b..7c8ed93654 100644 --- a/modules/base/conf.go +++ b/modules/base/conf.go @@ -41,19 +41,19 @@ var ( Cfg *goconfig.ConfigFile MailService *Mailer + LogMode string + LogConfig string + Cache cache.Cache CacheAdapter string CacheConfig string - PictureService string - PictureRootPath string - - LogMode string - LogConfig string - SessionProvider string SessionConfig *session.Config SessionManager *session.Manager + + PictureService string + PictureRootPath string ) var Service struct { @@ -182,6 +182,10 @@ func newSessionService() { SessionConfig.SessionIDHashFunc = Cfg.MustValue("session", "SESSION_ID_HASHFUNC", "sha1") SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY") + if SessionProvider == "file" { + os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm) + } + var err error SessionManager, err = session.NewManager(SessionProvider, *SessionConfig) if err != nil { diff --git a/modules/base/tool.go b/modules/base/tool.go index 8fabb8c531..4f368aa58c 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -111,6 +111,85 @@ const ( Year = 12 * Month ) +func computeTimeDiff(diff int64) (int64, string) { + diffStr := "" + switch { + case diff <= 0: + diff = 0 + diffStr = "now" + case diff < 2: + diff = 0 + diffStr = "1 second" + case diff < 1*Minute: + diffStr = fmt.Sprintf("%d seconds", diff) + diff = 0 + + case diff < 2*Minute: + diff -= 1 * Minute + diffStr = "1 minute" + case diff < 1*Hour: + diffStr = fmt.Sprintf("%d minutes", diff/Minute) + diff -= diff / Minute * Minute + + case diff < 2*Hour: + diff -= 1 * Hour + diffStr = "1 hour" + case diff < 1*Day: + diffStr = fmt.Sprintf("%d hours", diff/Hour) + diff -= diff / Hour * Hour + + case diff < 2*Day: + diff -= 1 * Day + diffStr = "1 day" + case diff < 1*Week: + diffStr = fmt.Sprintf("%d days", diff/Day) + diff -= diff / Day * Day + + case diff < 2*Week: + diff -= 1 * Week + diffStr = "1 week" + case diff < 1*Month: + diffStr = fmt.Sprintf("%d weeks", diff/Week) + diff -= diff / Week * Week + + case diff < 2*Month: + diff -= 1 * Month + diffStr = "1 month" + case diff < 1*Year: + diffStr = fmt.Sprintf("%d months", diff/Month) + diff -= diff / Month * Month + + case diff < 2*Year: + diff -= 1 * Year + diffStr = "1 year" + default: + diffStr = fmt.Sprintf("%d years", diff/Year) + diff = 0 + } + return diff, diffStr +} + +// TimeSincePro calculates the time interval and generate full user-friendly string. +func TimeSincePro(then time.Time) string { + now := time.Now() + diff := now.Unix() - then.Unix() + + if then.After(now) { + return "future" + } + + var timeStr, diffStr string + for { + if diff == 0 { + break + } + + diff, diffStr = computeTimeDiff(diff) + timeStr += ", " + diffStr + } + return strings.TrimPrefix(timeStr, ", ") +} + // TimeSince calculates the time interval and generate user-friendly string. func TimeSince(then time.Time) string { now := time.Now() @@ -123,7 +202,6 @@ func TimeSince(then time.Time) string { } switch { - case diff <= 0: return "now" case diff <= 2: @@ -156,8 +234,10 @@ func TimeSince(then time.Time) string { case diff < 1*Year: return fmt.Sprintf("%d months %s", diff/Month, lbl) - case diff < 18*Month: + case diff < 2*Year: return fmt.Sprintf("1 year %s", lbl) + default: + return fmt.Sprintf("%d years %s", diff/Year, lbl) } return then.String() } diff --git a/routers/admin/admin.go b/routers/admin/admin.go index 57a46d1dfe..c0f39f7159 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -17,7 +17,10 @@ import ( "github.com/gogits/gogs/modules/middleware" ) +var startTime = time.Now() + var sysStatus struct { + Uptime string NumGoroutine int // General statistics. @@ -58,6 +61,8 @@ var sysStatus struct { } func updateSystemStatus() { + sysStatus.Uptime = base.TimeSincePro(startTime) + m := new(runtime.MemStats) runtime.ReadMemStats(m) sysStatus.NumGoroutine = runtime.NumGoroutine() @@ -88,8 +93,8 @@ func updateSystemStatus() { sysStatus.NextGC = base.FileSize(int64(m.NextGC)) sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000) - sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs/1000/1000/1000)) - sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256]/1000/1000/1000)) + sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000) + sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000) sysStatus.NumGC = m.NumGC } @@ -151,6 +156,9 @@ func Config(ctx *middleware.Context) { ctx.Data["CacheAdapter"] = base.CacheAdapter ctx.Data["CacheConfig"] = base.CacheConfig + ctx.Data["SessionProvider"] = base.SessionProvider + ctx.Data["SessionConfig"] = base.SessionConfig + ctx.Data["PictureService"] = base.PictureService ctx.Data["PictureRootPath"] = base.PictureRootPath diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index e3f69ee6ea..048740e617 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -77,6 +77,25 @@ </div> </div> + <div class="panel panel-default"> + <div class="panel-heading"> + Session Configuration + </div> + + <div class="panel-body"> + <div><b>Session Provider:</b> {{.SessionProvider}}</div> + <div><b>Cookie Name:</b> {{.SessionConfig.CookieName}}</div> + <div><b>Enable Set Cookie:</b> <i class="fa fa{{if .SessionConfig.EnableSetCookie}}-check{{end}}-square-o"></i></div> + <div><b>GC Interval Time:</b> {{.SessionConfig.GcIntervalTime}} seconds</div> + <div><b>Session Life Time:</b> {{.SessionConfig.SessionLifeTime}} seconds</div> + <div><b>HTTPS Only:</b> <i class="fa fa{{if .SessionConfig.CookieSecure}}-check{{end}}-square-o"></i></div> + <div><b>Cookie Life Time:</b> {{.SessionConfig.CookieLifeTime}} seconds</div> + <div><b>Session ID Hash Function:</b> {{.SessionConfig.SessionIDHashFunc}}</div> + <div><b>Session ID Hash Key:</b> {{.SessionConfig.SessionIDHashKey}}</div> + <div><b>Provider Config:</b> {{.SessionConfig.ProviderConfig}}</div> + </div> + </div> + <div class="panel panel-default"> <div class="panel-heading"> Picture Configuration diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl index 0bebf8318f..2a5a161e03 100644 --- a/templates/admin/dashboard.tmpl +++ b/templates/admin/dashboard.tmpl @@ -19,6 +19,7 @@ </div> <div class="panel-body"> + <div>Server Uptime: <b>{{.SysStatus.Uptime}}</b></div> <div>Current Goroutines: <b>{{.SysStatus.NumGoroutine}}</b></div> <hr/> <div>Current Memory Usage: <b>{{.SysStatus.MemAllocated}}</b></div> From e3f55ca0fb0c8aee84f2935b76353ef8ce66384f Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 11:59:14 -0400 Subject: [PATCH 08/38] Need a field to specify if repository is bare --- models/action.go | 1 + models/repo.go | 6 ++++-- routers/repo/single.go | 10 ++-------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/models/action.go b/models/action.go index 12122ae240..4e1107f891 100644 --- a/models/action.go +++ b/models/action.go @@ -87,6 +87,7 @@ func CommitRepoAction(userId int64, userName string, if err != nil { return err } + repo.IsBare = false repo.Updated = time.Now() if err = UpdateRepository(repo); err != nil { return err diff --git a/models/repo.go b/models/repo.go index 1961b31e94..fb115de590 100644 --- a/models/repo.go +++ b/models/repo.go @@ -83,10 +83,11 @@ type Repository struct { Name string `xorm:"index not null"` Description string Website string - Private bool NumWatches int NumStars int NumForks int + IsPrivate bool + IsBare bool Created time.Time `xorm:"created"` Updated time.Time `xorm:"updated"` } @@ -139,7 +140,8 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv Name: repoName, LowerName: strings.ToLower(repoName), Description: desc, - Private: private, + IsPrivate: private, + IsBare: repoLang == "" && license == "" && !initReadme, } repoPath := RepoPath(user.Name, repoName) diff --git a/routers/repo/single.go b/routers/repo/single.go index 37c0fabd79..5906e64fb9 100644 --- a/routers/repo/single.go +++ b/routers/repo/single.go @@ -69,7 +69,7 @@ func Single(ctx *middleware.Context, params martini.Params) { log.Error("repo.Single(GetBranches): %v", err) ctx.Error(404) return - } else if len(brs) == 0 { + } else if ctx.Repo.Repository.IsBare { ctx.Data["IsBareRepo"] = true ctx.HTML(200, "repo/single") return @@ -224,13 +224,7 @@ func Setting(ctx *middleware.Context, params martini.Params) { ctx.Data["IsRepoToolbarSetting"] = true - // Branches. - brs, err := models.GetBranches(params["username"], params["reponame"]) - if err != nil { - log.Error("repo.Setting(GetBranches): %v", err) - ctx.Error(404) - return - } else if len(brs) == 0 { + if ctx.Repo.Repository.IsBare { ctx.Data["IsBareRepo"] = true ctx.HTML(200, "repo/setting") return From 076fc98d981aea3533eea363ca1c7e43f77b9802 Mon Sep 17 00:00:00 2001 From: slene <vslene@gmail.com> Date: Sun, 23 Mar 2014 01:44:02 +0800 Subject: [PATCH 09/38] add csrf check --- modules/base/tool.go | 10 ++- modules/middleware/auth.go | 58 +++++++++-------- modules/middleware/context.go | 107 +++++++++++++++++++++++++++++++- modules/middleware/render.go | 5 +- public/js/app.js | 33 ++++++++++ templates/admin/users/edit.tmpl | 1 + templates/admin/users/new.tmpl | 1 + templates/base/head.tmpl | 1 + templates/repo/create.tmpl | 1 + templates/repo/setting.tmpl | 1 + templates/user/active.tmpl | 3 +- templates/user/delete.tmpl | 1 + templates/user/password.tmpl | 4 +- templates/user/publickey.tmpl | 1 + templates/user/setting.tmpl | 1 + templates/user/signin.tmpl | 1 + templates/user/signup.tmpl | 1 + web.go | 24 +++---- 18 files changed, 208 insertions(+), 46 deletions(-) diff --git a/modules/base/tool.go b/modules/base/tool.go index 8fabb8c531..a2aeebf1b8 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -25,13 +25,17 @@ func EncodeMd5(str string) string { return hex.EncodeToString(m.Sum(nil)) } -// Random generate string -func GetRandomString(n int) string { +// GetRandomString generate random string by specify chars. +func GetRandomString(n int, alphabets ...byte) string { const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" var bytes = make([]byte, n) rand.Read(bytes) for i, b := range bytes { - bytes[i] = alphanum[b%byte(len(alphanum))] + if len(alphabets) == 0 { + bytes[i] = alphanum[b%byte(len(alphanum))] + } else { + bytes[i] = alphabets[b%byte(len(alphabets))] + } } return string(bytes) } diff --git a/modules/middleware/auth.go b/modules/middleware/auth.go index f211de32b9..b557188ee9 100644 --- a/modules/middleware/auth.go +++ b/modules/middleware/auth.go @@ -10,39 +10,45 @@ import ( "github.com/gogits/gogs/modules/base" ) -// SignInRequire requires user to sign in. -func SignInRequire(redirect bool) martini.Handler { - return func(ctx *Context) { - if !ctx.IsSigned { - if redirect { - ctx.Redirect("/user/login") - } - return - } else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm { - ctx.Data["Title"] = "Activate Your Account" - ctx.HTML(200, "user/active") - return - } - } +type ToggleOptions struct { + SignInRequire bool + SignOutRequire bool + AdminRequire bool + DisableCsrf bool } -// SignOutRequire requires user to sign out. -func SignOutRequire() martini.Handler { +func Toggle(options *ToggleOptions) martini.Handler { return func(ctx *Context) { - if ctx.IsSigned { + if options.SignOutRequire && ctx.IsSigned { ctx.Redirect("/") return } - } -} -// AdminRequire requires user signed in as administor. -func AdminRequire() martini.Handler { - return func(ctx *Context) { - if !ctx.User.IsAdmin { - ctx.Error(403) - return + if !options.DisableCsrf { + if ctx.Req.Method == "POST" { + if !ctx.CsrfTokenValid() { + ctx.Error(403, "CSRF token does not match") + return + } + } + } + + if options.SignInRequire { + if !ctx.IsSigned { + ctx.Redirect("/user/login") + return + } else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm { + ctx.Data["Title"] = "Activate Your Account" + ctx.HTML(200, "user/active") + return + } + } + + if options.AdminRequire { + if !ctx.User.IsAdmin { + ctx.Error(403) + return + } } - ctx.Data["PageIsAdmin"] = true } } diff --git a/modules/middleware/context.go b/modules/middleware/context.go index c958c1d6cd..b28953fc0e 100644 --- a/modules/middleware/context.go +++ b/modules/middleware/context.go @@ -6,6 +6,7 @@ package middleware import ( "fmt" + "html/template" "net/http" "time" @@ -32,6 +33,8 @@ type Context struct { User *models.User IsSigned bool + csrfToken string + Repo struct { IsValid bool IsOwner bool @@ -90,6 +93,95 @@ func (ctx *Context) Handle(status int, title string, err error) { ctx.HTML(status, fmt.Sprintf("status/%d", status)) } +func (ctx *Context) GetCookie(name string) string { + cookie, err := ctx.Req.Cookie(name) + if err != nil { + return "" + } + return cookie.Value +} + +func (ctx *Context) SetCookie(name string, value string, others ...interface{}) { + cookie := http.Cookie{} + cookie.Name = name + cookie.Value = value + + if len(others) > 0 { + switch v := others[0].(type) { + case int: + cookie.MaxAge = v + case int64: + cookie.MaxAge = int(v) + case int32: + cookie.MaxAge = int(v) + } + } + + // default "/" + if len(others) > 1 { + if v, ok := others[1].(string); ok && len(v) > 0 { + cookie.Path = v + } + } else { + cookie.Path = "/" + } + + // default empty + if len(others) > 2 { + if v, ok := others[2].(string); ok && len(v) > 0 { + cookie.Domain = v + } + } + + // default empty + if len(others) > 3 { + switch v := others[3].(type) { + case bool: + cookie.Secure = v + default: + if others[3] != nil { + cookie.Secure = true + } + } + } + + // default false. for session cookie default true + if len(others) > 4 { + if v, ok := others[4].(bool); ok && v { + cookie.HttpOnly = true + } + } + + ctx.Res.Header().Add("Set-Cookie", cookie.String()) +} + +func (ctx *Context) CsrfToken() string { + if len(ctx.csrfToken) > 0 { + return ctx.csrfToken + } + + token := ctx.GetCookie("_csrf") + if len(token) == 0 { + token = base.GetRandomString(30) + ctx.SetCookie("_csrf", token) + } + ctx.csrfToken = token + return token +} + +func (ctx *Context) CsrfTokenValid() bool { + token := ctx.Query("_csrf") + if token == "" { + token = ctx.Req.Header.Get("X-Csrf-Token") + } + if token == "" { + return false + } else if ctx.csrfToken != token { + return false + } + return true +} + // InitContext initializes a classic context for a request. func InitContext() martini.Handler { return func(res http.ResponseWriter, r *http.Request, c martini.Context, rd *Render) { @@ -103,11 +195,14 @@ func InitContext() martini.Handler { Render: rd, } + ctx.Data["PageStartTime"] = time.Now() + // start session ctx.Session = base.SessionManager.SessionStart(res, r) - defer func() { + rw := res.(martini.ResponseWriter) + rw.Before(func(martini.ResponseWriter) { ctx.Session.SessionRelease(res) - }() + }) // Get user from session if logined. user := auth.SignedInUser(ctx.Session) @@ -121,9 +216,15 @@ func InitContext() martini.Handler { ctx.Data["SignedUserId"] = user.Id ctx.Data["SignedUserName"] = user.LowerName ctx.Data["IsAdmin"] = ctx.User.IsAdmin + + if ctx.User.IsAdmin { + ctx.Data["PageIsAdmin"] = true + } } - ctx.Data["PageStartTime"] = time.Now() + // get or create csrf token + ctx.Data["CsrfToken"] = ctx.CsrfToken() + ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.csrfToken + `">`) c.Map(ctx) diff --git a/modules/middleware/render.go b/modules/middleware/render.go index 8a54183135..869ef9abaa 100644 --- a/modules/middleware/render.go +++ b/modules/middleware/render.go @@ -242,8 +242,11 @@ func (r *Render) HTMLString(name string, binding interface{}, htmlOpt ...HTMLOpt } } -func (r *Render) Error(status int) { +func (r *Render) Error(status int, message ...string) { r.WriteHeader(status) + if len(message) > 0 { + r.Write([]byte(message[0])) + } } func (r *Render) Redirect(location string, status ...int) { diff --git a/public/js/app.js b/public/js/app.js index f179342f4b..df755727b5 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -2,6 +2,39 @@ var Gogits = { "PageIsSignup": false }; +(function($){ + // extend jQuery ajax, set csrf token value + var ajax = $.ajax; + $.extend({ + ajax: function(url, options) { + if (typeof url === 'object') { + options = url; + url = undefined; + } + options = options || {}; + url = options.url; + var csrftoken = $('meta[name=_csrf]').attr('content'); + var headers = options.headers || {}; + var domain = document.domain.replace(/\./ig, '\\.'); + if (!/^(http:|https:).*/.test(url) || eval('/^(http:|https:)\\/\\/(.+\\.)*' + domain + '.*/').test(url)) { + headers = $.extend(headers, {'X-Csrf-Token':csrftoken}); + } + options.headers = headers; + var callback = options.success; + options.success = function(data){ + if(data.once){ + // change all _once value if ajax data.once exist + $('[name=_once]').val(data.once); + } + if(callback){ + callback.apply(this, arguments); + } + }; + return ajax(url, options); + } + }); +}(jQuery)); + (function ($) { Gogits.showTab = function (selector, index) { diff --git a/templates/admin/users/edit.tmpl b/templates/admin/users/edit.tmpl index 2a9882423a..08f11fcb12 100644 --- a/templates/admin/users/edit.tmpl +++ b/templates/admin/users/edit.tmpl @@ -12,6 +12,7 @@ <br/> <form action="/admin/users/{{.User.Id}}" method="post" class="form-horizontal"> {{if .IsSuccess}}<p class="alert alert-success">Account profile has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}} + {{.CsrfTokenHtml}} <input type="hidden" value="{{.User.Id}}" name="userId"/> <div class="form-group"> <label class="col-md-3 control-label">Username: </label> diff --git a/templates/admin/users/new.tmpl b/templates/admin/users/new.tmpl index 01d976caa0..7b41ae43a7 100644 --- a/templates/admin/users/new.tmpl +++ b/templates/admin/users/new.tmpl @@ -11,6 +11,7 @@ <div class="panel-body"> <br/> <form action="/admin/users/new" method="post" class="form-horizontal"> + {{.CsrfTokenHtml}} <div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div> <div class="form-group {{if .Err_UserName}}has-error has-feedback{{end}}"> <label class="col-md-3 control-label">Username: </label> diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index f02ea095ca..7f56ed7080 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -8,6 +8,7 @@ <meta name="author" content="Gogs - Go Git Service" /> <meta name="description" content="Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language" /> <meta name="keywords" content="go, git"> + <meta name="_csrf" content="{{.CsrfToken}}" /> <!-- Stylesheets --> <link href="/css/bootstrap.min.css" rel="stylesheet" /> diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index 2de92f515f..a43f510484 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -2,6 +2,7 @@ {{template "base/navbar" .}} <div class="container" id="gogs-body"> <form action="/repo/create" method="post" class="form-horizontal gogs-card" id="gogs-repo-create"> + {{.CsrfTokenHtml}} <h3>Create New Repository</h3> <div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div> <div class="form-group"> diff --git a/templates/repo/setting.tmpl b/templates/repo/setting.tmpl index a2fb1771d4..38c3fd3bcc 100644 --- a/templates/repo/setting.tmpl +++ b/templates/repo/setting.tmpl @@ -40,6 +40,7 @@ <div class="modal fade" id="delete-repository-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal-dialog"> <form action="/{{.Owner.Name}}/{{.Repository.Name}}/settings" method="post" class="modal-content"> + {{.CsrfTokenHtml}} <input type="hidden" name="action" value="delete"> <div class="modal-header"> diff --git a/templates/user/active.tmpl b/templates/user/active.tmpl index 47c87a591c..0df116cb40 100644 --- a/templates/user/active.tmpl +++ b/templates/user/active.tmpl @@ -1,7 +1,8 @@ {{template "base/head" .}} {{template "base/navbar" .}} <div id="gogs-body" class="container"> - <form action="/user/activate" method="get" class="form-horizontal gogs-card" id="gogs-login-card"> + <form action="/user/activate" method="post" class="form-horizontal gogs-card" id="gogs-login-card"> + {{.CsrfTokenHtml}} <h3>Activate Your Account</h3> {{if .IsActivatePage}} {{if .ServiceNotEnabled}} diff --git a/templates/user/delete.tmpl b/templates/user/delete.tmpl index 397ea8cc09..46376672d4 100644 --- a/templates/user/delete.tmpl +++ b/templates/user/delete.tmpl @@ -22,6 +22,7 @@ <div class="modal fade" id="delete-account-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal-dialog"> <form action="/user/delete" method="post" class="modal-content" id="gogs-user-delete"> + {{.CsrfTokenHtml}} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title" id="myModalLabel">Delete Account</h4> diff --git a/templates/user/password.tmpl b/templates/user/password.tmpl index 2ee178a3fc..936ec4b124 100644 --- a/templates/user/password.tmpl +++ b/templates/user/password.tmpl @@ -5,7 +5,9 @@ <div id="gogs-user-setting-container" class="col-md-9"> <div id="gogs-setting-pwd"> <h4>Password</h4> - <form class="form-horizontal" id="gogs-password-form" method="post" action="/user/setting/password">{{if .IsSuccess}} + <form class="form-horizontal" id="gogs-password-form" method="post" action="/user/setting/password"> + {{.CsrfTokenHtml}} + {{if .IsSuccess}} <p class="alert alert-success">Password is changed successfully. You can now sign in via new password.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}} <div class="form-group"> <label class="col-md-3 control-label">Old Password<strong class="text-danger">*</strong></label> diff --git a/templates/user/publickey.tmpl b/templates/user/publickey.tmpl index 72467659be..e645e1a84b 100644 --- a/templates/user/publickey.tmpl +++ b/templates/user/publickey.tmpl @@ -22,6 +22,7 @@ <div class="modal fade" id="ssh-add-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal-dialog"> <form class="modal-content form-horizontal" id="gogs-ssh-form" method="post" action="/user/setting/ssh/"> + {{.CsrfTokenHtml}} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title" id="myModalLabel">Add SSH Key</h4> diff --git a/templates/user/setting.tmpl b/templates/user/setting.tmpl index 222ddd895b..30c9529b12 100644 --- a/templates/user/setting.tmpl +++ b/templates/user/setting.tmpl @@ -6,6 +6,7 @@ <div id="gogs-setting-pwd"> <h4>Account Profile</h4> <form class="form-horizontal" id="gogs-password-form" method="post" action="/user/setting"> + {{.CsrfTokenHtml}} {{if .IsSuccess}}<p class="alert alert-success">Your profile has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}} <p>Your Email will be public and used for Account related notifications and any web based operations made via the web.</p> <div class="form-group"> diff --git a/templates/user/signin.tmpl b/templates/user/signin.tmpl index a49bf11405..8dc7292ff8 100644 --- a/templates/user/signin.tmpl +++ b/templates/user/signin.tmpl @@ -2,6 +2,7 @@ {{template "base/navbar" .}} <div class="container" id="gogs-body" data-page="user-signin"> <form action="/user/login" method="post" class="form-horizontal gogs-card" id="gogs-login-card"> + {{.CsrfTokenHtml}} <h3>Log in</h3> <div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div> <div class="form-group {{if .Err_UserName}}has-error has-feedback{{end}}"> diff --git a/templates/user/signup.tmpl b/templates/user/signup.tmpl index 069d34a5b2..fbfc4cadcc 100644 --- a/templates/user/signup.tmpl +++ b/templates/user/signup.tmpl @@ -2,6 +2,7 @@ {{template "base/navbar" .}} <div class="container" id="gogs-body" data-page="user-signup"> <form action="/user/sign_up" method="post" class="form-horizontal gogs-card" id="gogs-login-card"> + {{.CsrfTokenHtml}} {{if .DisenableRegisteration}} Sorry, registeration has been disenabled, you can only get account from administrator. {{else}} diff --git a/web.go b/web.go index ac5761d720..0da2d129d0 100644 --- a/web.go +++ b/web.go @@ -82,9 +82,10 @@ func runWeb(*cli.Context) { m.Use(middleware.InitContext()) - reqSignIn := middleware.SignInRequire(true) - ignSignIn := middleware.SignInRequire(base.Service.RequireSignInView) - reqSignOut := middleware.SignOutRequire() + reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true}) + ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: base.Service.RequireSignInView}) + reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true}) + // Routers. m.Get("/", ignSignIn, routers.Home) m.Get("/issues", reqSignIn, user.Issues) @@ -109,14 +110,15 @@ func runWeb(*cli.Context) { m.Get("/help", routers.Help) - adminReq := middleware.AdminRequire() - m.Get("/admin", reqSignIn, adminReq, admin.Dashboard) - m.Get("/admin/users", reqSignIn, adminReq, admin.Users) - m.Any("/admin/users/new", reqSignIn, adminReq, binding.BindIgnErr(auth.RegisterForm{}), admin.NewUser) - m.Any("/admin/users/:userid", reqSignIn, adminReq, binding.BindIgnErr(auth.AdminEditUserForm{}), admin.EditUser) - m.Any("/admin/users/:userid/delete", reqSignIn, adminReq, admin.DeleteUser) - m.Get("/admin/repos", reqSignIn, adminReq, admin.Repositories) - m.Get("/admin/config", reqSignIn, adminReq, admin.Config) + adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true}) + + m.Get("/admin", adminReq, admin.Dashboard) + m.Get("/admin/users", adminReq, admin.Users) + m.Any("/admin/users/new", adminReq, binding.BindIgnErr(auth.RegisterForm{}), admin.NewUser) + m.Any("/admin/users/:userid", adminReq, binding.BindIgnErr(auth.AdminEditUserForm{}), admin.EditUser) + m.Any("/admin/users/:userid/delete", adminReq, admin.DeleteUser) + m.Get("/admin/repos", adminReq, admin.Repositories) + m.Get("/admin/config", adminReq, admin.Config) m.Post("/:username/:reponame/settings", reqSignIn, middleware.RepoAssignment(true), repo.SettingPost) m.Get("/:username/:reponame/settings", reqSignIn, middleware.RepoAssignment(true), repo.Setting) From 61e29226015fad6451281035948c3d8d1364880c Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 13:50:50 -0400 Subject: [PATCH 10/38] Working on issues --- gogs.go | 2 +- models/action.go | 1 - models/issue.go | 123 ++++++++++++++++- models/models.go | 2 +- routers/repo/issue.go | 30 ++++ routers/repo/repo.go | 294 ++++++++++++++++++++++++++++++++++++++++ routers/repo/single.go | 301 ----------------------------------------- 7 files changed, 443 insertions(+), 310 deletions(-) create mode 100644 routers/repo/issue.go delete mode 100644 routers/repo/single.go diff --git a/gogs.go b/gogs.go index a609032093..2bb80a6644 100644 --- a/gogs.go +++ b/gogs.go @@ -20,7 +20,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.1.5.0322.2" +const APP_VER = "0.1.6.0323.1" func init() { base.AppVer = APP_VER diff --git a/models/action.go b/models/action.go index 4e1107f891..a996e16aa8 100644 --- a/models/action.go +++ b/models/action.go @@ -88,7 +88,6 @@ func CommitRepoAction(userId int64, userName string, return err } repo.IsBare = false - repo.Updated = time.Now() if err = UpdateRepository(repo); err != nil { return err } diff --git a/models/issue.go b/models/issue.go index c669d201f6..0b6ca4c323 100644 --- a/models/issue.go +++ b/models/issue.go @@ -4,16 +4,127 @@ package models +import ( + "strings" + "time" + + "github.com/gogits/gogs/modules/base" +) + +// Issue represents an issue or pull request of repository. type Issue struct { - Id int64 - RepoId int64 `xorm:"index"` - PosterId int64 + Id int64 + Index int64 // Index in one repository. + Name string + RepoId int64 `xorm:"index"` + PosterId int64 + MilestoneId int64 + AssigneeId int64 + IsPull bool // Indicates whether is a pull request or not. + IsClosed bool + Labels string + Mentions string + Content string + NumComments int + Created time.Time `xorm:"created"` + Updated time.Time `xorm:"updated"` } -type PullRequest struct { - Id int64 +// CreateIssue creates new issue for repository. +func CreateIssue(userId, repoId, milestoneId, assigneeId int64, name, labels, mentions, content string, isPull bool) error { + count, err := GetIssueCount(repoId) + if err != nil { + return err + } + + _, err = orm.Insert(&Issue{ + Index: count + 1, + Name: name, + RepoId: repoId, + PosterId: userId, + MilestoneId: milestoneId, + AssigneeId: assigneeId, + IsPull: isPull, + Labels: labels, + Mentions: mentions, + Content: content, + }) + return err } +// GetIssueCount returns count of issues in the repository. +func GetIssueCount(repoId int64) (int64, error) { + return orm.Count(&Issue{RepoId: repoId}) +} + +// GetIssues returns a list of issues by given conditions. +func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed, isMention bool, labels, sortType string) ([]Issue, error) { + sess := orm.Limit(20, (page-1)*20).Where("repo_id=?", repoId).And("is_closed=?", isClosed) + if userId > 0 { + sess = sess.And("assignee_id=?", userId) + } else if posterId > 0 { + sess = sess.And("poster_id=?", posterId) + } else if isMention { + sess = sess.And("mentions like '%$" + base.ToStr(userId) + "|%'") + } + + if milestoneId > 0 { + sess = sess.And("milestone_id=?", milestoneId) + } + + if len(labels) > 0 { + for _, label := range strings.Split(labels, ",") { + sess = sess.And("mentions like '%$" + label + "|%'") + } + } + + switch sortType { + case "oldest": + sess = sess.Asc("created") + case "recentupdate": + sess = sess.Desc("updated") + case "leastupdate": + sess = sess.Asc("updated") + case "mostcomment": + sess = sess.Desc("num_comments") + case "leastcomment": + sess = sess.Asc("num_comments") + default: + sess = sess.Desc("created") + } + + var issues []Issue + err := sess.Find(&issues) + return issues, err +} + +// Label represents a list of labels of repository for issues. +type Label struct { + Id int64 + RepoId int64 `xorm:"index"` + Names string + Colors string +} + +// Milestone represents a milestone of repository. +type Milestone struct { + Id int64 + Name string + RepoId int64 `xorm:"index"` + IsClosed bool + Content string + NumIssues int + DueDate time.Time + Created time.Time `xorm:"created"` +} + +// Comment represents a comment in commit and issue page. type Comment struct { - Id int64 + Id int64 + PosterId int64 + IssueId int64 + CommitId int64 + Line int + Content string + Created time.Time `xorm:"created"` } diff --git a/models/models.go b/models/models.go index 8713ff2896..fb749c5d8a 100644 --- a/models/models.go +++ b/models/models.go @@ -72,7 +72,7 @@ func setEngine() { func NewEngine() { setEngine() if err := orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch), - new(Action), new(Access)); err != nil { + new(Action), new(Access), new(Issue)); err != nil { fmt.Printf("sync database struct error: %v\n", err) os.Exit(2) } diff --git a/routers/repo/issue.go b/routers/repo/issue.go new file mode 100644 index 0000000000..c6af8ca0bc --- /dev/null +++ b/routers/repo/issue.go @@ -0,0 +1,30 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "github.com/codegangsta/martini" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/middleware" +) + +func Issues(ctx *middleware.Context, params martini.Params) { + ctx.Data["IsRepoToolbarIssues"] = true + + milestoneId, _ := base.StrTo(params["milestone"]).Int() + page, _ := base.StrTo(params["page"]).Int() + + var err error + ctx.Data["Issues"], err = models.GetIssues(0, ctx.Repo.Repository.Id, 0, + int64(milestoneId), page, params["state"] == "closed", false, params["labels"], params["sortType"]) + if err != nil { + ctx.Handle(200, "issue.Issues: %v", err) + return + } + + ctx.HTML(200, "repo/issues") +} diff --git a/routers/repo/repo.go b/routers/repo/repo.go index c83a6df522..ff0fa85dde 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -5,8 +5,17 @@ package repo import ( + "path" + "strings" + + "github.com/codegangsta/martini" + + "github.com/gogits/git" + "github.com/gogits/webdav" + "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/auth" + "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/middleware" ) @@ -61,3 +70,288 @@ func SettingPost(ctx *middleware.Context) { log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName) ctx.Redirect("/", 302) } + +func Branches(ctx *middleware.Context, params martini.Params) { + if !ctx.Repo.IsValid { + return + } + + brs, err := models.GetBranches(params["username"], params["reponame"]) + if err != nil { + ctx.Handle(200, "repo.Branches", err) + return + } else if len(brs) == 0 { + ctx.Error(404) + return + } + + ctx.Data["Username"] = params["username"] + ctx.Data["Reponame"] = params["reponame"] + + ctx.Data["Branchname"] = brs[0] + ctx.Data["Branches"] = brs + ctx.Data["IsRepoToolbarBranches"] = true + + ctx.HTML(200, "repo/branches") +} + +func Single(ctx *middleware.Context, params martini.Params) { + if !ctx.Repo.IsValid { + return + } + + if len(params["branchname"]) == 0 { + params["branchname"] = "master" + } + + // Get tree path + treename := params["_1"] + + if len(treename) > 0 && treename[len(treename)-1] == '/' { + ctx.Redirect("/"+ctx.Repo.Owner.LowerName+"/"+ + ctx.Repo.Repository.Name+"/src/"+params["branchname"]+"/"+treename[:len(treename)-1], 302) + return + } + + ctx.Data["IsRepoToolbarSource"] = true + + // Branches. + brs, err := models.GetBranches(params["username"], params["reponame"]) + if err != nil { + log.Error("repo.Single(GetBranches): %v", err) + ctx.Error(404) + return + } else if ctx.Repo.Repository.IsBare { + ctx.Data["IsBareRepo"] = true + ctx.HTML(200, "repo/single") + return + } + + ctx.Data["Branches"] = brs + + repoFile, err := models.GetTargetFile(params["username"], params["reponame"], + params["branchname"], params["commitid"], treename) + + if err != nil && err != models.ErrRepoFileNotExist { + log.Error("repo.Single(GetTargetFile): %v", err) + ctx.Error(404) + return + } + + branchLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/src/" + params["branchname"] + + if len(treename) != 0 && repoFile == nil { + ctx.Error(404) + return + } + + if repoFile != nil && repoFile.IsFile() { + if repoFile.Size > 1024*1024 || repoFile.Filemode != git.FileModeBlob { + ctx.Data["FileIsLarge"] = true + } else if blob, err := repoFile.LookupBlob(); err != nil { + log.Error("repo.Single(repoFile.LookupBlob): %v", err) + ctx.Error(404) + } else { + ctx.Data["IsFile"] = true + ctx.Data["FileName"] = repoFile.Name + ext := path.Ext(repoFile.Name) + if len(ext) > 0 { + ext = ext[1:] + } + ctx.Data["FileExt"] = ext + + readmeExist := base.IsMarkdownFile(repoFile.Name) || base.IsReadmeFile(repoFile.Name) + ctx.Data["ReadmeExist"] = readmeExist + if readmeExist { + ctx.Data["FileContent"] = string(base.RenderMarkdown(blob.Contents(), "")) + } else { + ctx.Data["FileContent"] = string(blob.Contents()) + } + } + + } else { + // Directory and file list. + files, err := models.GetReposFiles(params["username"], params["reponame"], + params["branchname"], params["commitid"], treename) + if err != nil { + log.Error("repo.Single(GetReposFiles): %v", err) + ctx.Error(404) + return + } + + ctx.Data["Files"] = files + + var readmeFile *models.RepoFile + + for _, f := range files { + if !f.IsFile() || !base.IsReadmeFile(f.Name) { + continue + } else { + readmeFile = f + break + } + } + + if readmeFile != nil { + ctx.Data["ReadmeExist"] = true + // if file large than 1M not show it + if readmeFile.Size > 1024*1024 || readmeFile.Filemode != git.FileModeBlob { + ctx.Data["FileIsLarge"] = true + } else if blob, err := readmeFile.LookupBlob(); err != nil { + log.Error("repo.Single(readmeFile.LookupBlob): %v", err) + ctx.Error(404) + return + } else { + // current repo branch link + + ctx.Data["FileName"] = readmeFile.Name + ctx.Data["FileContent"] = string(base.RenderMarkdown(blob.Contents(), branchLink)) + } + } + } + + ctx.Data["Username"] = params["username"] + ctx.Data["Reponame"] = params["reponame"] + ctx.Data["Branchname"] = params["branchname"] + + var treenames []string + Paths := make([]string, 0) + + if len(treename) > 0 { + treenames = strings.Split(treename, "/") + for i, _ := range treenames { + Paths = append(Paths, strings.Join(treenames[0:i+1], "/")) + } + + ctx.Data["HasParentPath"] = true + if len(Paths)-2 >= 0 { + ctx.Data["ParentPath"] = "/" + Paths[len(Paths)-2] + } + } + + // Get latest commit according username and repo name + commit, err := models.GetCommit(params["username"], params["reponame"], + params["branchname"], params["commitid"]) + if err != nil { + log.Error("repo.Single(GetCommit): %v", err) + ctx.Error(404) + return + } + ctx.Data["LastCommit"] = commit + + ctx.Data["Paths"] = Paths + ctx.Data["Treenames"] = treenames + ctx.Data["BranchLink"] = branchLink + ctx.HTML(200, "repo/single") +} + +func Http(ctx *middleware.Context, params martini.Params) { + /*if !ctx.Repo.IsValid { + return + }*/ + + // TODO: access check + + username := params["username"] + reponame := params["reponame"] + if strings.HasSuffix(reponame, ".git") { + reponame = reponame[:len(reponame)-4] + } + + prefix := path.Join("/", username, params["reponame"]) + server := &webdav.Server{ + Fs: webdav.Dir(models.RepoPath(username, reponame)), + TrimPrefix: prefix, + Listings: true, + } + + server.ServeHTTP(ctx.ResponseWriter, ctx.Req) +} + +func Setting(ctx *middleware.Context, params martini.Params) { + if !ctx.Repo.IsOwner { + ctx.Error(404) + return + } + + ctx.Data["IsRepoToolbarSetting"] = true + + if ctx.Repo.Repository.IsBare { + ctx.Data["IsBareRepo"] = true + ctx.HTML(200, "repo/setting") + return + } + + var title string + if t, ok := ctx.Data["Title"].(string); ok { + title = t + } + + if len(params["branchname"]) == 0 { + params["branchname"] = "master" + } + + ctx.Data["Branchname"] = params["branchname"] + ctx.Data["Title"] = title + " - settings" + ctx.HTML(200, "repo/setting") +} + +func Commits(ctx *middleware.Context, params martini.Params) { + brs, err := models.GetBranches(params["username"], params["reponame"]) + if err != nil { + ctx.Handle(200, "repo.Commits", err) + return + } else if len(brs) == 0 { + ctx.Error(404) + return + } + + ctx.Data["IsRepoToolbarCommits"] = true + commits, err := models.GetCommits(params["username"], + params["reponame"], params["branchname"]) + if err != nil { + ctx.Error(404) + return + } + ctx.Data["Username"] = params["username"] + ctx.Data["Reponame"] = params["reponame"] + ctx.Data["CommitCount"] = commits.Len() + ctx.Data["Commits"] = commits + ctx.HTML(200, "repo/commits") +} + +func Pulls(ctx *middleware.Context) { + ctx.Data["IsRepoToolbarPulls"] = true + ctx.HTML(200, "repo/pulls") +} + +func Action(ctx *middleware.Context, params martini.Params) { + var err error + switch params["action"] { + case "watch": + err = models.WatchRepo(ctx.User.Id, ctx.Repo.Repository.Id, true) + case "unwatch": + err = models.WatchRepo(ctx.User.Id, ctx.Repo.Repository.Id, false) + case "desc": + if !ctx.Repo.IsOwner { + ctx.Error(404) + return + } + + ctx.Repo.Repository.Description = ctx.Query("desc") + ctx.Repo.Repository.Website = ctx.Query("site") + err = models.UpdateRepository(ctx.Repo.Repository) + } + + if err != nil { + log.Error("repo.Action(%s): %v", params["action"], err) + ctx.JSON(200, map[string]interface{}{ + "ok": false, + "err": err.Error(), + }) + return + } + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) +} diff --git a/routers/repo/single.go b/routers/repo/single.go deleted file mode 100644 index 5906e64fb9..0000000000 --- a/routers/repo/single.go +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package repo - -import ( - "path" - "strings" - - "github.com/codegangsta/martini" - - "github.com/gogits/git" - "github.com/gogits/webdav" - - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/modules/base" - "github.com/gogits/gogs/modules/log" - "github.com/gogits/gogs/modules/middleware" -) - -func Branches(ctx *middleware.Context, params martini.Params) { - if !ctx.Repo.IsValid { - return - } - - brs, err := models.GetBranches(params["username"], params["reponame"]) - if err != nil { - ctx.Handle(200, "repo.Branches", err) - return - } else if len(brs) == 0 { - ctx.Error(404) - return - } - - ctx.Data["Username"] = params["username"] - ctx.Data["Reponame"] = params["reponame"] - - ctx.Data["Branchname"] = brs[0] - ctx.Data["Branches"] = brs - ctx.Data["IsRepoToolbarBranches"] = true - - ctx.HTML(200, "repo/branches") -} - -func Single(ctx *middleware.Context, params martini.Params) { - if !ctx.Repo.IsValid { - return - } - - if len(params["branchname"]) == 0 { - params["branchname"] = "master" - } - - // Get tree path - treename := params["_1"] - - if len(treename) > 0 && treename[len(treename)-1] == '/' { - ctx.Redirect("/"+ctx.Repo.Owner.LowerName+"/"+ - ctx.Repo.Repository.Name+"/src/"+params["branchname"]+"/"+treename[:len(treename)-1], 302) - return - } - - ctx.Data["IsRepoToolbarSource"] = true - - // Branches. - brs, err := models.GetBranches(params["username"], params["reponame"]) - if err != nil { - log.Error("repo.Single(GetBranches): %v", err) - ctx.Error(404) - return - } else if ctx.Repo.Repository.IsBare { - ctx.Data["IsBareRepo"] = true - ctx.HTML(200, "repo/single") - return - } - - ctx.Data["Branches"] = brs - - repoFile, err := models.GetTargetFile(params["username"], params["reponame"], - params["branchname"], params["commitid"], treename) - - if err != nil && err != models.ErrRepoFileNotExist { - log.Error("repo.Single(GetTargetFile): %v", err) - ctx.Error(404) - return - } - - branchLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/src/" + params["branchname"] - - if len(treename) != 0 && repoFile == nil { - ctx.Error(404) - return - } - - if repoFile != nil && repoFile.IsFile() { - if repoFile.Size > 1024*1024 || repoFile.Filemode != git.FileModeBlob { - ctx.Data["FileIsLarge"] = true - } else if blob, err := repoFile.LookupBlob(); err != nil { - log.Error("repo.Single(repoFile.LookupBlob): %v", err) - ctx.Error(404) - } else { - ctx.Data["IsFile"] = true - ctx.Data["FileName"] = repoFile.Name - ext := path.Ext(repoFile.Name) - if len(ext) > 0 { - ext = ext[1:] - } - ctx.Data["FileExt"] = ext - - readmeExist := base.IsMarkdownFile(repoFile.Name) || base.IsReadmeFile(repoFile.Name) - ctx.Data["ReadmeExist"] = readmeExist - if readmeExist { - ctx.Data["FileContent"] = string(base.RenderMarkdown(blob.Contents(), "")) - } else { - ctx.Data["FileContent"] = string(blob.Contents()) - } - } - - } else { - // Directory and file list. - files, err := models.GetReposFiles(params["username"], params["reponame"], - params["branchname"], params["commitid"], treename) - if err != nil { - log.Error("repo.Single(GetReposFiles): %v", err) - ctx.Error(404) - return - } - - ctx.Data["Files"] = files - - var readmeFile *models.RepoFile - - for _, f := range files { - if !f.IsFile() || !base.IsReadmeFile(f.Name) { - continue - } else { - readmeFile = f - break - } - } - - if readmeFile != nil { - ctx.Data["ReadmeExist"] = true - // if file large than 1M not show it - if readmeFile.Size > 1024*1024 || readmeFile.Filemode != git.FileModeBlob { - ctx.Data["FileIsLarge"] = true - } else if blob, err := readmeFile.LookupBlob(); err != nil { - log.Error("repo.Single(readmeFile.LookupBlob): %v", err) - ctx.Error(404) - return - } else { - // current repo branch link - - ctx.Data["FileName"] = readmeFile.Name - ctx.Data["FileContent"] = string(base.RenderMarkdown(blob.Contents(), branchLink)) - } - } - } - - ctx.Data["Username"] = params["username"] - ctx.Data["Reponame"] = params["reponame"] - ctx.Data["Branchname"] = params["branchname"] - - var treenames []string - Paths := make([]string, 0) - - if len(treename) > 0 { - treenames = strings.Split(treename, "/") - for i, _ := range treenames { - Paths = append(Paths, strings.Join(treenames[0:i+1], "/")) - } - - ctx.Data["HasParentPath"] = true - if len(Paths)-2 >= 0 { - ctx.Data["ParentPath"] = "/" + Paths[len(Paths)-2] - } - } - - // Get latest commit according username and repo name - commit, err := models.GetCommit(params["username"], params["reponame"], - params["branchname"], params["commitid"]) - if err != nil { - log.Error("repo.Single(GetCommit): %v", err) - ctx.Error(404) - return - } - ctx.Data["LastCommit"] = commit - - ctx.Data["Paths"] = Paths - ctx.Data["Treenames"] = treenames - ctx.Data["BranchLink"] = branchLink - ctx.HTML(200, "repo/single") -} - -func Http(ctx *middleware.Context, params martini.Params) { - /*if !ctx.Repo.IsValid { - return - }*/ - - // TODO: access check - - username := params["username"] - reponame := params["reponame"] - if strings.HasSuffix(reponame, ".git") { - reponame = reponame[:len(reponame)-4] - } - - prefix := path.Join("/", username, params["reponame"]) - server := &webdav.Server{ - Fs: webdav.Dir(models.RepoPath(username, reponame)), - TrimPrefix: prefix, - Listings: true, - } - - server.ServeHTTP(ctx.ResponseWriter, ctx.Req) -} - -func Setting(ctx *middleware.Context, params martini.Params) { - if !ctx.Repo.IsOwner { - ctx.Error(404) - return - } - - ctx.Data["IsRepoToolbarSetting"] = true - - if ctx.Repo.Repository.IsBare { - ctx.Data["IsBareRepo"] = true - ctx.HTML(200, "repo/setting") - return - } - - var title string - if t, ok := ctx.Data["Title"].(string); ok { - title = t - } - - if len(params["branchname"]) == 0 { - params["branchname"] = "master" - } - - ctx.Data["Branchname"] = params["branchname"] - ctx.Data["Title"] = title + " - settings" - ctx.HTML(200, "repo/setting") -} - -func Commits(ctx *middleware.Context, params martini.Params) { - brs, err := models.GetBranches(params["username"], params["reponame"]) - if err != nil { - ctx.Handle(200, "repo.Commits", err) - return - } else if len(brs) == 0 { - ctx.Error(404) - return - } - - ctx.Data["IsRepoToolbarCommits"] = true - commits, err := models.GetCommits(params["username"], - params["reponame"], params["branchname"]) - if err != nil { - ctx.Error(404) - return - } - ctx.Data["Username"] = params["username"] - ctx.Data["Reponame"] = params["reponame"] - ctx.Data["CommitCount"] = commits.Len() - ctx.Data["Commits"] = commits - ctx.HTML(200, "repo/commits") -} - -func Issues(ctx *middleware.Context) { - ctx.Data["IsRepoToolbarIssues"] = true - ctx.HTML(200, "repo/issues") -} - -func Pulls(ctx *middleware.Context) { - ctx.Data["IsRepoToolbarPulls"] = true - ctx.HTML(200, "repo/pulls") -} - -func Action(ctx *middleware.Context, params martini.Params) { - var err error - switch params["action"] { - case "watch": - err = models.WatchRepo(ctx.User.Id, ctx.Repo.Repository.Id, true) - case "unwatch": - err = models.WatchRepo(ctx.User.Id, ctx.Repo.Repository.Id, false) - } - - if err != nil { - log.Error("repo.Action(%s): %v", params["action"], err) - ctx.JSON(200, map[string]interface{}{ - "ok": false, - "err": err.Error(), - }) - return - } - ctx.JSON(200, map[string]interface{}{ - "ok": true, - }) -} From b3cfd9fe0c293ba9d84d38ec140db2c01b1e3109 Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 14:27:03 -0400 Subject: [PATCH 11/38] Fix SSH key bug in windows --- models/publickey.go | 45 +++++++++++++++++++++-------------- models/repo.go | 2 +- models/user.go | 2 +- modules/middleware/auth.go | 1 + modules/middleware/context.go | 4 ---- routers/repo/issue.go | 10 ++++++++ 6 files changed, 40 insertions(+), 24 deletions(-) diff --git a/models/publickey.go b/models/publickey.go index c69bca681d..9e7cc6f740 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -19,6 +19,8 @@ import ( "time" "github.com/Unknwon/com" + + "github.com/gogits/gogs/modules/log" ) const ( @@ -99,8 +101,8 @@ func AddPublicKey(key *PublicKey) (err error) { } // Calculate fingerprint. - tmpPath := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), - "id_rsa.pub") + tmpPath := strings.Replace(filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), + "id_rsa.pub"), "\\", "/", -1) os.MkdirAll(path.Dir(tmpPath), os.ModePerm) if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil { return err @@ -127,25 +129,11 @@ func AddPublicKey(key *PublicKey) (err error) { return nil } -// DeletePublicKey deletes SSH key information both in database and authorized_keys file. -func DeletePublicKey(key *PublicKey) (err error) { - // Delete SSH key in database. - has, err := orm.Id(key.Id).Get(key) - if err != nil { - return err - } else if !has { - return errors.New("Public key does not exist") - } - if _, err = orm.Delete(key); err != nil { - return err - } - +func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error { // Delete SSH key in SSH key file. sshOpLocker.Lock() defer sshOpLocker.Unlock() - p := filepath.Join(sshPath, "authorized_keys") - tmpP := filepath.Join(sshPath, "authorized_keys.tmp") fr, err := os.Open(p) if err != nil { return err @@ -188,8 +176,29 @@ func DeletePublicKey(key *PublicKey) (err error) { break } } + return nil +} - if err = os.Remove(p); err != nil { +// DeletePublicKey deletes SSH key information both in database and authorized_keys file. +func DeletePublicKey(key *PublicKey) (err error) { + // Delete SSH key in database. + has, err := orm.Id(key.Id).Get(key) + if err != nil { + return err + } else if !has { + return errors.New("Public key does not exist") + } + if _, err = orm.Delete(key); err != nil { + return err + } + + p := filepath.Join(sshPath, "authorized_keys") + tmpP := filepath.Join(sshPath, "authorized_keys.tmp") + log.Trace("ssh.DeletePublicKey(authorized_keys): %s", p) + + if err = rewriteAuthorizedKeys(key, p, tmpP); err != nil { + return err + } else if err = os.Remove(p); err != nil { return err } return os.Rename(tmpP, p) diff --git a/models/repo.go b/models/repo.go index fb115de590..317f936ece 100644 --- a/models/repo.go +++ b/models/repo.go @@ -372,7 +372,7 @@ func RepoPath(userName, repoName string) string { } func UpdateRepository(repo *Repository) error { - _, err := orm.Id(repo.Id).UseBool().Update(repo) + _, err := orm.Id(repo.Id).UseBool().Cols("description", "website").Update(repo) return err } diff --git a/models/user.go b/models/user.go index d6dc041490..88c29ae43e 100644 --- a/models/user.go +++ b/models/user.go @@ -201,7 +201,7 @@ func VerifyUserActiveCode(code string) (user *User) { // UpdateUser updates user's information. func UpdateUser(user *User) (err error) { - _, err = orm.Id(user.Id).UseBool().Update(user) + _, err = orm.Id(user.Id).UseBool().Cols("website", "location").Update(user) return err } diff --git a/modules/middleware/auth.go b/modules/middleware/auth.go index b557188ee9..3224b3df91 100644 --- a/modules/middleware/auth.go +++ b/modules/middleware/auth.go @@ -49,6 +49,7 @@ func Toggle(options *ToggleOptions) martini.Handler { ctx.Error(403) return } + ctx.Data["PageIsAdmin"] = true } } } diff --git a/modules/middleware/context.go b/modules/middleware/context.go index b28953fc0e..5727b4f094 100644 --- a/modules/middleware/context.go +++ b/modules/middleware/context.go @@ -216,10 +216,6 @@ func InitContext() martini.Handler { ctx.Data["SignedUserId"] = user.Id ctx.Data["SignedUserName"] = user.LowerName ctx.Data["IsAdmin"] = ctx.User.IsAdmin - - if ctx.User.IsAdmin { - ctx.Data["PageIsAdmin"] = true - } } // get or create csrf token diff --git a/routers/repo/issue.go b/routers/repo/issue.go index c6af8ca0bc..eee55c6fda 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -28,3 +28,13 @@ func Issues(ctx *middleware.Context, params martini.Params) { ctx.HTML(200, "repo/issues") } + +func CreateIssue(ctx *middleware.Context, params martini.Params) { + if !ctx.Repo.IsOwner { + ctx.Error(404) + return + } + // else if err = models.CreateIssue(userId, repoId, milestoneId, assigneeId, name, labels, mentions, content, isPull); err != nil { + + // } +} From 59ffdbf6f80328f9b9074930444dedd936aeae51 Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 16:00:46 -0400 Subject: [PATCH 12/38] Add create, list, view issue --- README.md | 2 +- models/action.go | 2 +- models/issue.go | 46 +++++++++++++++---- models/publickey.go | 2 +- models/repo.go | 7 +++ models/user.go | 7 +++ modules/auth/issue.go | 54 +++++++++++++++++++++++ routers/repo/issue.go | 51 +++++++++++++++++++-- routers/repo/repo.go | 5 +++ templates/admin/repos.tmpl | 2 +- web.go | 90 +++++++++++++++++++++++--------------- 11 files changed, 217 insertions(+), 51 deletions(-) create mode 100644 modules/auth/issue.go diff --git a/README.md b/README.md index 35044927ff..89a346d602 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language. Since we choose to use pure Go implementation of Git manipulation, Gogs certainly supports **ALL platforms** that Go supports, including Linux, Max OS X, and Windows with **ZERO** dependency. -##### Current version: 0.1.5 Alpha +##### Current version: 0.1.6 Alpha ## Purpose diff --git a/models/action.go b/models/action.go index a996e16aa8..cfb124363c 100644 --- a/models/action.go +++ b/models/action.go @@ -30,7 +30,7 @@ type Action struct { ActUserName string // Action user name. RepoId int64 RepoName string - Content string + Content string `xorm:"TEXT"` Created time.Time `xorm:"created"` } diff --git a/models/issue.go b/models/issue.go index 0b6ca4c323..f78c240cbc 100644 --- a/models/issue.go +++ b/models/issue.go @@ -5,12 +5,17 @@ package models import ( + "errors" "strings" "time" "github.com/gogits/gogs/modules/base" ) +var ( + ErrIssueNotExist = errors.New("Issue does not exist") +) + // Issue represents an issue or pull request of repository. type Issue struct { Id int64 @@ -22,22 +27,25 @@ type Issue struct { AssigneeId int64 IsPull bool // Indicates whether is a pull request or not. IsClosed bool - Labels string - Mentions string - Content string + Labels string `xorm:"TEXT"` + Mentions string `xorm:"TEXT"` + Content string `xorm:"TEXT"` NumComments int Created time.Time `xorm:"created"` Updated time.Time `xorm:"updated"` } // CreateIssue creates new issue for repository. -func CreateIssue(userId, repoId, milestoneId, assigneeId int64, name, labels, mentions, content string, isPull bool) error { +func CreateIssue(userId, repoId, milestoneId, assigneeId int64, name, labels, content string, isPull bool) (*Issue, error) { count, err := GetIssueCount(repoId) if err != nil { - return err + return nil, err } - _, err = orm.Insert(&Issue{ + // TODO: find out mentions + mentions := "" + + issue := &Issue{ Index: count + 1, Name: name, RepoId: repoId, @@ -48,8 +56,9 @@ func CreateIssue(userId, repoId, milestoneId, assigneeId int64, name, labels, me Labels: labels, Mentions: mentions, Content: content, - }) - return err + } + _, err = orm.Insert(issue) + return issue, err } // GetIssueCount returns count of issues in the repository. @@ -57,9 +66,28 @@ func GetIssueCount(repoId int64) (int64, error) { return orm.Count(&Issue{RepoId: repoId}) } +// GetIssueById returns issue object by given id. +func GetIssueById(id int64) (*Issue, error) { + issue := new(Issue) + has, err := orm.Id(id).Get(issue) + if err != nil { + return nil, err + } else if !has { + return nil, ErrIssueNotExist + } + return issue, nil +} + // GetIssues returns a list of issues by given conditions. func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed, isMention bool, labels, sortType string) ([]Issue, error) { - sess := orm.Limit(20, (page-1)*20).Where("repo_id=?", repoId).And("is_closed=?", isClosed) + sess := orm.Limit(20, (page-1)*20) + + if repoId > 0 { + sess = sess.Where("repo_id=?", repoId).And("is_closed=?", isClosed) + } else { + sess = sess.Where("is_closed=?", isClosed) + } + if userId > 0 { sess = sess.And("assignee_id=?", userId) } else if posterId > 0 { diff --git a/models/publickey.go b/models/publickey.go index 9e7cc6f740..3f2fcabd3b 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -80,7 +80,7 @@ type PublicKey struct { OwnerId int64 `xorm:"index"` Name string `xorm:"unique not null"` Fingerprint string - Content string `xorm:"text not null"` + Content string `xorm:"TEXT not null"` Created time.Time `xorm:"created"` Updated time.Time `xorm:"updated"` } diff --git a/models/repo.go b/models/repo.go index 317f936ece..a37923c8b1 100644 --- a/models/repo.go +++ b/models/repo.go @@ -372,6 +372,13 @@ func RepoPath(userName, repoName string) string { } func UpdateRepository(repo *Repository) error { + if len(repo.Description) > 255 { + repo.Description = repo.Description[:255] + } + if len(repo.Website) > 255 { + repo.Website = repo.Website[:255] + } + _, err := orm.Id(repo.Id).UseBool().Cols("description", "website").Update(repo) return err } diff --git a/models/user.go b/models/user.go index 88c29ae43e..9333d1ee67 100644 --- a/models/user.go +++ b/models/user.go @@ -201,6 +201,13 @@ func VerifyUserActiveCode(code string) (user *User) { // UpdateUser updates user's information. func UpdateUser(user *User) (err error) { + if len(user.Location) > 255 { + user.Location = user.Location[:255] + } + if len(user.Website) > 255 { + user.Website = user.Website[:255] + } + _, err = orm.Id(user.Id).UseBool().Cols("website", "location").Update(user) return err } diff --git a/modules/auth/issue.go b/modules/auth/issue.go new file mode 100644 index 0000000000..e2b1f9f2a7 --- /dev/null +++ b/modules/auth/issue.go @@ -0,0 +1,54 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package auth + +import ( + "net/http" + "reflect" + + "github.com/codegangsta/martini" + + "github.com/gogits/binding" + + "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/log" +) + +type CreateIssueForm struct { + IssueName string `form:"name" binding:"Required;MaxSize(50)"` + RepoId int64 `form:"repoid" binding:"Required"` + MilestoneId int64 `form:"milestoneid" binding:"Required"` + AssigneeId int64 `form:"assigneeid"` + Labels string `form:"labels"` + Content string `form:"content"` +} + +func (f *CreateIssueForm) Name(field string) string { + names := map[string]string{ + "IssueName": "Issue name", + "RepoId": "Repository ID", + "MilestoneId": "Milestone ID", + } + return names[field] +} + +func (f *CreateIssueForm) 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("CreateIssueForm.Validate: %v", err) + } + return + } + + validate(errors, data, f) +} diff --git a/routers/repo/issue.go b/routers/repo/issue.go index eee55c6fda..154e8308ab 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -5,14 +5,19 @@ package repo import ( + "fmt" + "github.com/codegangsta/martini" "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/middleware" ) func Issues(ctx *middleware.Context, params martini.Params) { + ctx.Data["Title"] = "Issues" ctx.Data["IsRepoToolbarIssues"] = true milestoneId, _ := base.StrTo(params["milestone"]).Int() @@ -29,12 +34,52 @@ func Issues(ctx *middleware.Context, params martini.Params) { ctx.HTML(200, "repo/issues") } -func CreateIssue(ctx *middleware.Context, params martini.Params) { +func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) { if !ctx.Repo.IsOwner { ctx.Error(404) return } - // else if err = models.CreateIssue(userId, repoId, milestoneId, assigneeId, name, labels, mentions, content, isPull); err != nil { - // } + ctx.Data["Title"] = "Create issue" + + if ctx.Req.Method == "GET" { + ctx.HTML(200, "issue/create") + return + } + + if ctx.HasError() { + ctx.HTML(200, "issue/create") + return + } + + issue, err := models.CreateIssue(ctx.User.Id, form.RepoId, form.MilestoneId, form.AssigneeId, + form.IssueName, form.Labels, form.Content, false) + if err == nil { + log.Trace("%s Issue created: %d", form.RepoId, issue.Id) + ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index), 302) + return + } + ctx.Handle(200, "issue.CreateIssue", err) +} + +func ViewIssue(ctx *middleware.Context, params martini.Params) { + issueid, err := base.StrTo(params["issueid"]).Int() + if err != nil { + ctx.Error(404) + return + } + + issue, err := models.GetIssueById(int64(issueid)) + if err != nil { + if err == models.ErrIssueNotExist { + ctx.Error(404) + } else { + ctx.Handle(200, "issue.ViewIssue", err) + } + return + } + + ctx.Data["Title"] = issue.Name + ctx.Data["Issue"] = issue + ctx.HTML(200, "issue/view") } diff --git a/routers/repo/repo.go b/routers/repo/repo.go index ff0fa85dde..c436d38714 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -31,6 +31,11 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) { return } + if ctx.HasError() { + ctx.HTML(200, "repo/create") + return + } + _, err := models.CreateRepository(ctx.User, form.RepoName, form.Description, form.Language, form.License, form.Visibility == "private", form.InitReadme == "on") if err == nil { diff --git a/templates/admin/repos.tmpl b/templates/admin/repos.tmpl index a1f41d8365..2c91ccc096 100644 --- a/templates/admin/repos.tmpl +++ b/templates/admin/repos.tmpl @@ -27,7 +27,7 @@ <td>{{.Id}}</td> <th>{{.UserName}}</th> <td><a href="/{{.UserName}}/{{.Name}}">{{.Name}}</a></td> - <td><i class="fa fa{{if .Private}}-check{{end}}-square-o"></i></td> + <td><i class="fa fa{{if .IsPrivate}}-check{{end}}-square-o"></i></td> <td>{{.NumWatches}}</td> <td>{{.NumForks}}</td> <td>{{DateFormat .Created "M d, Y"}}</td> diff --git a/web.go b/web.go index 0da2d129d0..bf654aace2 100644 --- a/web.go +++ b/web.go @@ -91,53 +91,73 @@ func runWeb(*cli.Context) { m.Get("/issues", reqSignIn, user.Issues) m.Get("/pulls", reqSignIn, user.Pulls) m.Get("/stars", reqSignIn, user.Stars) - m.Any("/user/login", reqSignOut, binding.BindIgnErr(auth.LogInForm{}), user.SignIn) - m.Any("/user/logout", reqSignIn, user.SignOut) - m.Any("/user/sign_up", reqSignOut, binding.BindIgnErr(auth.RegisterForm{}), user.SignUp) - m.Any("/user/delete", reqSignIn, user.Delete) - m.Get("/user/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) - m.Get("/user/activate", user.Activate) + m.Get("/help", routers.Help) - m.Any("/user/setting", reqSignIn, binding.BindIgnErr(auth.UpdateProfileForm{}), user.Setting) - m.Any("/user/setting/password", reqSignIn, binding.BindIgnErr(auth.UpdatePasswdForm{}), user.SettingPassword) - m.Any("/user/setting/ssh", reqSignIn, binding.BindIgnErr(auth.AddSSHKeyForm{}), user.SettingSSHKeys) - m.Any("/user/setting/notification", reqSignIn, user.SettingNotification) - m.Any("/user/setting/security", reqSignIn, user.SettingSecurity) + m.Group("/user", func(r martini.Router) { + r.Any("/login", binding.BindIgnErr(auth.LogInForm{}), user.SignIn) + r.Any("/sign_up", reqSignOut, binding.BindIgnErr(auth.RegisterForm{}), user.SignUp) + }, reqSignOut) + m.Group("/user", func(r martini.Router) { + r.Any("/logout", user.SignOut) + r.Any("/delete", user.Delete) + r.Any("/setting", binding.BindIgnErr(auth.UpdateProfileForm{}), user.Setting) + }, reqSignIn) + m.Group("/user", func(r martini.Router) { + r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) + r.Get("/activate", user.Activate) + }) + + m.Group("/user/setting", func(r martini.Router) { + r.Any("/password", binding.BindIgnErr(auth.UpdatePasswdForm{}), user.SettingPassword) + r.Any("/ssh", binding.BindIgnErr(auth.AddSSHKeyForm{}), user.SettingSSHKeys) + r.Any("/notification", user.SettingNotification) + r.Any("/security", user.SettingSecurity) + }, reqSignIn) m.Get("/user/:username", ignSignIn, user.Profile) m.Any("/repo/create", reqSignIn, binding.BindIgnErr(auth.CreateRepoForm{}), repo.Create) - m.Get("/help", routers.Help) - adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true}) m.Get("/admin", adminReq, admin.Dashboard) - m.Get("/admin/users", adminReq, admin.Users) - m.Any("/admin/users/new", adminReq, binding.BindIgnErr(auth.RegisterForm{}), admin.NewUser) - m.Any("/admin/users/:userid", adminReq, binding.BindIgnErr(auth.AdminEditUserForm{}), admin.EditUser) - m.Any("/admin/users/:userid/delete", adminReq, admin.DeleteUser) - m.Get("/admin/repos", adminReq, admin.Repositories) - m.Get("/admin/config", adminReq, admin.Config) + m.Group("/admin", func(r martini.Router) { + r.Get("/users", admin.Users) + r.Get("/repos", admin.Repositories) + r.Get("/config", admin.Config) + }, adminReq) + m.Group("/admin/users", func(r martini.Router) { + r.Any("/new", binding.BindIgnErr(auth.RegisterForm{}), admin.NewUser) + r.Any("/:userid", binding.BindIgnErr(auth.AdminEditUserForm{}), admin.EditUser) + r.Any("/:userid/delete", admin.DeleteUser) + }, adminReq) - m.Post("/:username/:reponame/settings", reqSignIn, middleware.RepoAssignment(true), repo.SettingPost) - m.Get("/:username/:reponame/settings", reqSignIn, middleware.RepoAssignment(true), repo.Setting) + m.Group("/:username/:reponame", func(r martini.Router) { + r.Post("/settings", repo.SettingPost) + r.Get("/settings", repo.Setting) + r.Get("/action/:action", repo.Action) + }, reqSignIn, middleware.RepoAssignment(true)) + m.Group("/:username/:reponame", func(r martini.Router) { + r.Get("/commits/:branchname", repo.Commits) + r.Get("/issues", repo.Issues) + r.Any("/issues/new", binding.BindIgnErr(auth.CreateIssueForm{}), repo.CreateIssue) + r.Get("/issues/:issueid", repo.ViewIssue) + r.Get("/pulls", repo.Pulls) + r.Get("/branches", repo.Branches) + r.Get("/src/:branchname", repo.Single) + r.Get("/src/:branchname/**", repo.Single) + r.Get("/commits/:branchname", repo.Commits) + r.Get("/commits/:branchname", repo.Commits) + }, ignSignIn, middleware.RepoAssignment(true)) - m.Get("/:username/:reponame/commits/:branchname", ignSignIn, middleware.RepoAssignment(true), repo.Commits) - m.Get("/:username/:reponame/issues", ignSignIn, middleware.RepoAssignment(true), repo.Issues) - m.Get("/:username/:reponame/pulls", ignSignIn, middleware.RepoAssignment(true), repo.Pulls) - m.Get("/:username/:reponame/branches", ignSignIn, middleware.RepoAssignment(true), repo.Branches) - m.Get("/:username/:reponame/action/:action", reqSignIn, middleware.RepoAssignment(true), repo.Action) - m.Get("/:username/:reponame/src/:branchname/**", - ignSignIn, middleware.RepoAssignment(true), repo.Single) - m.Get("/:username/:reponame/src/:branchname", - ignSignIn, middleware.RepoAssignment(true), repo.Single) - m.Get("/:username/:reponame/commit/:commitid/**", ignSignIn, middleware.RepoAssignment(true), repo.Single) - m.Get("/:username/:reponame/commit/:commitid", ignSignIn, middleware.RepoAssignment(true), repo.Single) + // TODO: implement single commit page + // m.Get("/:username/:reponame/commit/:commitid/**", ignSignIn, middleware.RepoAssignment(true), repo.Single) + // m.Get("/:username/:reponame/commit/:commitid", ignSignIn, middleware.RepoAssignment(true), repo.Single) - m.Get("/:username/:reponame", ignSignIn, middleware.RepoAssignment(true), repo.Single) - - m.Any("/:username/:reponame/**", ignSignIn, repo.Http) + m.Group("/:username", func(r martini.Router) { + r.Get("/:reponame", middleware.RepoAssignment(true), repo.Single) + r.Any("/:reponame/**", repo.Http) + }, ignSignIn) if martini.Env == martini.Dev { m.Get("/template/**", dev.TemplatePreview) From cb52f6d07d62925a31185fedf591d0241ee2bf63 Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 16:40:09 -0400 Subject: [PATCH 13/38] Add auto-login --- conf/app.ini | 4 ++++ modules/auth/auth.go | 1 + modules/base/conf.go | 8 +++++++ modules/middleware/context.go | 43 +++++++++++++++++++++++++++++++++++ routers/user/user.go | 42 +++++++++++++++++++++++++++++++++- templates/user/signin.tmpl | 11 +++++++++ 6 files changed, 108 insertions(+), 1 deletion(-) diff --git a/conf/app.ini b/conf/app.ini index ec5fcb23b3..7f283012fd 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -34,6 +34,10 @@ PATH = data/gogs.db [security] ; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!! SECRET_KEY = !#@FDEWREWR&*( +; Auto-login remember days +LOGIN_REMEMBER_DAYS = 7 +COOKIE_USERNAME = gogs_awesome +COOKIE_REMEMBER_NAME = gogs_incredible [service] ACTIVE_CODE_LIVE_MINUTES = 180 diff --git a/modules/auth/auth.go b/modules/auth/auth.go index 0e87168891..2e0555f6df 100644 --- a/modules/auth/auth.go +++ b/modules/auth/auth.go @@ -61,6 +61,7 @@ func (f *RegisterForm) Validate(errors *binding.Errors, req *http.Request, conte type LogInForm struct { UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"` Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"` + Remember string `form:"remember"` } func (f *LogInForm) Name(field string) string { diff --git a/modules/base/conf.go b/modules/base/conf.go index 7c8ed93654..cdbe2b36ce 100644 --- a/modules/base/conf.go +++ b/modules/base/conf.go @@ -38,6 +38,10 @@ var ( RunUser string RepoRootPath string + LogInRememberDays int + CookieUserName string + CookieRememberName string + Cfg *goconfig.ConfigFile MailService *Mailer @@ -252,6 +256,10 @@ func NewConfigContext() { SecretKey = Cfg.MustValue("security", "SECRET_KEY") RunUser = Cfg.MustValue("", "RUN_USER") + LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS") + CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME") + CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME") + PictureService = Cfg.MustValue("picture", "SERVICE") PictureRootPath = Cfg.MustValue("picture", "PATH") diff --git a/modules/middleware/context.go b/modules/middleware/context.go index 5727b4f094..d81ab999bf 100644 --- a/modules/middleware/context.go +++ b/modules/middleware/context.go @@ -5,9 +5,14 @@ package middleware import ( + "crypto/hmac" + "crypto/sha1" + "encoding/base64" "fmt" "html/template" "net/http" + "strconv" + "strings" "time" "github.com/codegangsta/martini" @@ -155,6 +160,44 @@ func (ctx *Context) SetCookie(name string, value string, others ...interface{}) ctx.Res.Header().Add("Set-Cookie", cookie.String()) } +// Get secure cookie from request by a given key. +func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) { + val := ctx.GetCookie(key) + if val == "" { + return "", false + } + + parts := strings.SplitN(val, "|", 3) + + if len(parts) != 3 { + return "", false + } + + vs := parts[0] + timestamp := parts[1] + sig := parts[2] + + h := hmac.New(sha1.New, []byte(Secret)) + fmt.Fprintf(h, "%s%s", vs, timestamp) + + if fmt.Sprintf("%02x", h.Sum(nil)) != sig { + return "", false + } + res, _ := base64.URLEncoding.DecodeString(vs) + return string(res), true +} + +// Set Secure cookie for response. +func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) { + vs := base64.URLEncoding.EncodeToString([]byte(value)) + timestamp := strconv.FormatInt(time.Now().UnixNano(), 10) + h := hmac.New(sha1.New, []byte(Secret)) + fmt.Fprintf(h, "%s%s", vs, timestamp) + sig := fmt.Sprintf("%02x", h.Sum(nil)) + cookie := strings.Join([]string{vs, timestamp, sig}, "|") + ctx.SetCookie(name, cookie, others...) +} + func (ctx *Context) CsrfToken() string { if len(ctx.csrfToken) > 0 { return ctx.csrfToken diff --git a/routers/user/user.go b/routers/user/user.go index 2244697714..56bc5f8e37 100644 --- a/routers/user/user.go +++ b/routers/user/user.go @@ -77,7 +77,39 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) { ctx.Data["Title"] = "Log In" if ctx.Req.Method == "GET" { - ctx.HTML(200, "user/signin") + // Check auto-login. + userName := ctx.GetCookie(base.CookieUserName) + if len(userName) == 0 { + ctx.HTML(200, "user/signin") + return + } + + isSucceed := false + defer func() { + if !isSucceed { + log.Trace("%s auto-login cookie cleared: %s", ctx.Req.RequestURI, userName) + ctx.SetCookie(base.CookieUserName, "", -1) + ctx.SetCookie(base.CookieRememberName, "", -1) + } + }() + + user, err := models.GetUserByName(userName) + if err != nil { + ctx.HTML(200, "user/signin") + return + } + + secret := base.EncodeMd5(user.Rands + user.Passwd) + value, _ := ctx.GetSecureCookie(secret, base.CookieRememberName) + if value != user.Name { + ctx.HTML(200, "user/signin") + return + } + + isSucceed = true + ctx.Session.Set("userId", user.Id) + ctx.Session.Set("userName", user.Name) + ctx.Redirect("/") return } @@ -89,6 +121,7 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) { user, err := models.LoginUserPlain(form.UserName, form.Password) if err != nil { if err == models.ErrUserNotExist { + log.Trace("%s Log in failed: %s/%s", ctx.Req.RequestURI, form.UserName, form.Password) ctx.RenderWithErr("Username or password is not correct", "user/signin", &form) return } @@ -97,6 +130,13 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) { return } + if form.Remember == "on" { + secret := base.EncodeMd5(user.Rands + user.Passwd) + days := 86400 * base.LogInRememberDays + ctx.SetCookie(base.CookieUserName, user.Name, days) + ctx.SetSecureCookie(secret, base.CookieRememberName, user.Name, days) + } + ctx.Session.Set("userId", user.Id) ctx.Session.Set("userName", user.Name) ctx.Redirect("/") diff --git a/templates/user/signin.tmpl b/templates/user/signin.tmpl index 8dc7292ff8..1cd3275cce 100644 --- a/templates/user/signin.tmpl +++ b/templates/user/signin.tmpl @@ -19,6 +19,17 @@ </div> </div> + <div class="form-group"> + <div class="col-md-6 col-md-offset-4"> + <div class="checkbox"> + <label> + <input type="checkbox" name="remember" {{if .remember}}checked{{end}}> + <strong>Remember me</strong> + </label> + </div> + </div> + </div> + <div class="form-group"> <div class="col-md-offset-4 col-md-6"> <button type="submit" class="btn btn-lg btn-primary">Log In</button> From 7356153ba3c19ff49f3ecfa28bac0b8bb38eccb9 Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 17:59:22 -0400 Subject: [PATCH 14/38] Batch updates --- README.md | 4 ++-- conf/app.ini | 10 ++++++++-- modules/base/conf.go | 4 ++++ modules/middleware/auth.go | 3 +++ routers/admin/user.go | 2 +- routers/repo/issue.go | 2 +- routers/repo/repo.go | 8 ++++---- routers/user/user.go | 21 ++++++++++++++++++--- 8 files changed, 41 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 89a346d602..325c3a9736 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ There are two ways to install Gogs: ## Acknowledgments - Logo is inspired by [martini](https://github.com/martini-contrib). -- Mail Service is based on [WeTalk](https://github.com/beego/wetalk). -- System Monitor Status is based on [GoBlog](https://github.com/fuxiaohei/goblog). +- Mail Service, modules design is inspired by [WeTalk](https://github.com/beego/wetalk). +- System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog). ## Contributors diff --git a/conf/app.ini b/conf/app.ini index 7f283012fd..b051557f41 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -107,7 +107,7 @@ SERVICE = server PATH = data/pictures [log] -; Either "console", "file", "conn" or "smtp", default is "console" +; Either "console", "file", "conn", "smtp" or "database", default is "console" MODE = console ; Buffer length of channel, keep it as it is if you don't know what it is. BUFFER_LEN = 10000 @@ -156,4 +156,10 @@ HOST = USER = PASSWD = ; Receivers, can be one or more, e.g. ["1@example.com","2@example.com"] -RECEIVERS = \ No newline at end of file +RECEIVERS = + +; For "database" mode only +[log.database] +LEVEL = +Driver = +CONN = \ No newline at end of file diff --git a/modules/base/conf.go b/modules/base/conf.go index cdbe2b36ce..19f587077b 100644 --- a/modules/base/conf.go +++ b/modules/base/conf.go @@ -143,6 +143,10 @@ func newLogService() { Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"), Cfg.MustValue(modeSec, "RECEIVERS", "[]"), Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve")) + case "database": + LogConfig = fmt.Sprintf(`{"level":%s,"driver":%s,"conn":%s}`, level, + Cfg.MustValue(modeSec, "Driver"), + Cfg.MustValue(modeSec, "CONN")) } log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig) diff --git a/modules/middleware/auth.go b/modules/middleware/auth.go index 3224b3df91..82c3367c48 100644 --- a/modules/middleware/auth.go +++ b/modules/middleware/auth.go @@ -5,6 +5,8 @@ package middleware import ( + "net/url" + "github.com/codegangsta/martini" "github.com/gogits/gogs/modules/base" @@ -35,6 +37,7 @@ func Toggle(options *ToggleOptions) martini.Handler { if options.SignInRequire { if !ctx.IsSigned { + ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI)) ctx.Redirect("/user/login") return } else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm { diff --git a/routers/admin/user.go b/routers/admin/user.go index fa27d11664..7f66c5528c 100644 --- a/routers/admin/user.go +++ b/routers/admin/user.go @@ -140,5 +140,5 @@ func DeleteUser(ctx *middleware.Context, params martini.Params) { log.Trace("%s User deleted by admin(%s): %s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.User.LowerName) - ctx.Redirect("/admin/users", 302) + ctx.Redirect("/admin/users") } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 154e8308ab..4cc007e9ee 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -56,7 +56,7 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat form.IssueName, form.Labels, form.Content, false) if err == nil { log.Trace("%s Issue created: %d", form.RepoId, issue.Id) - ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index), 302) + ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index)) return } ctx.Handle(200, "issue.CreateIssue", err) diff --git a/routers/repo/repo.go b/routers/repo/repo.go index c436d38714..4782d64f70 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -40,7 +40,7 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) { form.Language, form.License, form.Visibility == "private", form.InitReadme == "on") 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, 302) + ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName) return } else if err == models.ErrRepoAlreadyExist { ctx.RenderWithErr("Repository name has already been used", "repo/create", &form) @@ -73,7 +73,7 @@ func SettingPost(ctx *middleware.Context) { } log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName) - ctx.Redirect("/", 302) + ctx.Redirect("/") } func Branches(ctx *middleware.Context, params martini.Params) { @@ -113,8 +113,8 @@ func Single(ctx *middleware.Context, params martini.Params) { treename := params["_1"] if len(treename) > 0 && treename[len(treename)-1] == '/' { - ctx.Redirect("/"+ctx.Repo.Owner.LowerName+"/"+ - ctx.Repo.Repository.Name+"/src/"+params["branchname"]+"/"+treename[:len(treename)-1], 302) + ctx.Redirect("/" + ctx.Repo.Owner.LowerName + "/" + + ctx.Repo.Repository.Name + "/src/" + params["branchname"] + "/" + treename[:len(treename)-1]) return } diff --git a/routers/user/user.go b/routers/user/user.go index 56bc5f8e37..c34b529ec3 100644 --- a/routers/user/user.go +++ b/routers/user/user.go @@ -6,6 +6,7 @@ package user import ( "fmt" + "net/url" "strings" "github.com/codegangsta/martini" @@ -109,7 +110,13 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) { isSucceed = true ctx.Session.Set("userId", user.Id) ctx.Session.Set("userName", user.Name) - ctx.Redirect("/") + redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")) + if len(redirectTo) > 0 { + ctx.SetCookie("redirect_to", "", -1) + ctx.Redirect(redirectTo) + } else { + ctx.Redirect("/") + } return } @@ -139,12 +146,20 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) { ctx.Session.Set("userId", user.Id) ctx.Session.Set("userName", user.Name) - ctx.Redirect("/") + redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")) + if len(redirectTo) > 0 { + ctx.SetCookie("redirect_to", "", -1) + ctx.Redirect(redirectTo) + } else { + ctx.Redirect("/") + } } func SignOut(ctx *middleware.Context) { ctx.Session.Delete("userId") ctx.Session.Delete("userName") + ctx.SetCookie(base.CookieUserName, "", -1) + ctx.SetCookie(base.CookieRememberName, "", -1) ctx.Redirect("/") } @@ -314,7 +329,7 @@ func Activate(ctx *middleware.Context) { ctx.Session.Set("userId", user.Id) ctx.Session.Set("userName", user.Name) - ctx.Redirect("/", 302) + ctx.Redirect("/") return } From ad31893bbbb1479f6801235ddca44b5bae2cc5c2 Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sat, 22 Mar 2014 20:25:39 -0400 Subject: [PATCH 15/38] Update README --- README.md | 14 +++++++---- README_ZH.md | 53 +++++++++++++++++++++++++++++++++++++++++ gogs.go | 2 +- templates/repo/nav.tmpl | 4 ++-- 4 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 README_ZH.md diff --git a/README.md b/README.md index 325c3a9736..504c21975b 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ -Gogs - Go Git Service [](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [](https://gowalker.org/github.com/gogits/gogs) +Gogs - Go Git Service [](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [](https://drone.io/github.com/gogits/gogs/latest) ===================== -Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language. +Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language. -Since we choose to use pure Go implementation of Git manipulation, Gogs certainly supports **ALL platforms** that Go supports, including Linux, Max OS X, and Windows with **ZERO** dependency. + ##### Current version: 0.1.6 Alpha +[简体中文](README_ZH.md) + ## Purpose -There are some very good products in this category such as [gitlab](http://gitlab.com), but the environment setup steps often make us crazy. So our goal of Gogs is to build a GitHub-like clone with very easy setup steps, which take advantages of the Go Programming Language. +Since we choose to use pure Go implementation of Git manipulation, Gogs certainly supports **ALL platforms** that Go supports, including Linux, Mac OS X, and Windows with **ZERO** dependency. + +More importantly, Gogs only needs one binary to setup your own project hosting on the fly! ## Overview @@ -23,7 +27,7 @@ There are some very good products in this category such as [gitlab](http://gitla - Activity timeline - SSH protocol support. - Register/delete account. -- Create/delete public repository. +- Create/delete/watch public repository. - User profile page. - Repository viewer. - Gravatar support. diff --git a/README_ZH.md b/README_ZH.md new file mode 100644 index 0000000000..0ab8dfdd07 --- /dev/null +++ b/README_ZH.md @@ -0,0 +1,53 @@ +Gogs - Go Git Service [](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [](https://drone.io/github.com/gogits/gogs/latest) +===================== + +Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。 + + + +##### 当前版本:0.1.6 Alpha + +## 开发目的 + +Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依赖,并且支持 Go 语言所支持的 **所有平台**,包括 Linux、Mac OS X 以及 Windows。 + +更重要的是,您只需要一个可执行文件就能借助 Gogs 快速搭建属于您自己的代码托管服务! + +## 项目概览 + +- 有关项目设计、开发说明、变更日志和路线图,请通过 [Wiki](https://github.com/gogits/gogs/wiki) 查看。 +- 您可以到 [Trello Broad](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。 +- 想要先睹为快?通过 [在线体验](http://try.gogits.org/Unknown/gogs) 或查看 **安装部署 -> 二进制安装** 小节。 +- 使用过程中遇到问题?尝试从 [故障排查](https://github.com/gogits/gogs/wiki/Troubleshooting) 页面获取帮助。 + +## 功能特性 + +- 活动时间线 +- SSH 协议支持 +- 注册/删除用户 +- 创建/删除/关注公开仓库 +- 用户个人信息页面 +- 仓库浏览器 +- Gravatar 支持 +- 邮件服务(注册) +- 管理员面板 +- 支持 MySQL、PostgreSQL 以及 SQLite3(仅限二进制版本) + +## 安装部署 + +在安装 Gogs 之前,您需要先安装 [基本环境](https://github.com/gogits/gogs/wiki/Prerequirements)。 + +然后,您可以通过以下两种方式来安装 Gogs: + +- [二进制安装](https://github.com/gogits/gogs/wiki/Install-from-binary): **强烈推荐** 适合体验者和实际部署 +- [源码安装](https://github.com/gogits/gogs/wiki/Install-from-source) + +## 特别鸣谢 + +- Logo 基于 [martini](https://github.com/martini-contrib) 修改而来。 +- 邮件服务、模块设计基于 [WeTalk](https://github.com/beego/wetalk) 修改而来。 +- 系统监视状态基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改而来。 + +## 贡献成员 + +本项目最初由 [Unknown](https://github.com/Unknwon) 和 [lunny](https://github.com/lunny) 发起,随后 [fuxiaohei](https://github.com/fuxiaohei) 与 [slene](https://github.com/slene) 加入到开发团队。您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。 \ No newline at end of file diff --git a/gogs.go b/gogs.go index 2bb80a6644..0bdbbc0697 100644 --- a/gogs.go +++ b/gogs.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -// gogs(Go Git Service) is a Go clone of Github. +// Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language. package main import ( diff --git a/templates/repo/nav.tmpl b/templates/repo/nav.tmpl index d4a692fd03..cf1b7d0389 100644 --- a/templates/repo/nav.tmpl +++ b/templates/repo/nav.tmpl @@ -1,11 +1,11 @@ <div id="gogs-body-nav" class="gogs-repo-nav"> <div class="container"> <div class="row"> - <div class="col-md-6"> + <div class="col-md-7"> <h3 class="name"><i class="fa fa-book fa-lg"></i><a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / {{.Repository.Name}}</h3> <p class="desc">{{.Repository.Description}}{{if .Repository.Website}}<a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}</p> </div> - <div class="col-md-6 actions text-right clone-group-btn"> + <div class="col-md-5 actions text-right clone-group-btn"> {{if not .IsBareRepo}} <!--<div class="btn-group" id="gogs-repo-clone"> <button type="button" class="btn btn-default"><i class="fa fa-download fa-lg fa-m"></i></button> From 47493a0191f3de8aa4e80bce1911f14623cfa46a Mon Sep 17 00:00:00 2001 From: FuXiaoHei <fuxiaohei@hexiaz.com> Date: Sun, 23 Mar 2014 13:12:55 +0800 Subject: [PATCH 16/38] use ctx.Handle to handle 404 page --- routers/repo/issue.go | 6 +++--- routers/repo/repo.go | 32 ++++++++++++++++---------------- routers/user/user.go | 2 +- templates/status/404.tmpl | 7 +++++++ templates/status/500.tmpl | 7 +++++++ 5 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 templates/status/404.tmpl create mode 100644 templates/status/500.tmpl diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 4cc007e9ee..78fe4b25d0 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -36,7 +36,7 @@ func Issues(ctx *middleware.Context, params martini.Params) { func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) { if !ctx.Repo.IsOwner { - ctx.Error(404) + ctx.Handle(404, "issue.CreateIssue", nil) return } @@ -65,14 +65,14 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat func ViewIssue(ctx *middleware.Context, params martini.Params) { issueid, err := base.StrTo(params["issueid"]).Int() if err != nil { - ctx.Error(404) + ctx.Handle(404, "issue.ViewIssue", err) return } issue, err := models.GetIssueById(int64(issueid)) if err != nil { if err == models.ErrIssueNotExist { - ctx.Error(404) + ctx.Handle(404, "issue.ViewIssue", err) } else { ctx.Handle(200, "issue.ViewIssue", err) } diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 4782d64f70..0f1ea31235 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -86,7 +86,7 @@ func Branches(ctx *middleware.Context, params martini.Params) { ctx.Handle(200, "repo.Branches", err) return } else if len(brs) == 0 { - ctx.Error(404) + ctx.Handle(404, "repo.Branches", nil) return } @@ -123,8 +123,8 @@ func Single(ctx *middleware.Context, params martini.Params) { // Branches. brs, err := models.GetBranches(params["username"], params["reponame"]) if err != nil { - log.Error("repo.Single(GetBranches): %v", err) - ctx.Error(404) + //log.Error("repo.Single(GetBranches): %v", err) + ctx.Handle(404, "repo.Single(GetBranches)", err) return } else if ctx.Repo.Repository.IsBare { ctx.Data["IsBareRepo"] = true @@ -138,15 +138,15 @@ func Single(ctx *middleware.Context, params martini.Params) { params["branchname"], params["commitid"], treename) if err != nil && err != models.ErrRepoFileNotExist { - log.Error("repo.Single(GetTargetFile): %v", err) - ctx.Error(404) + //log.Error("repo.Single(GetTargetFile): %v", err) + ctx.Handle(404, "repo.Single(GetTargetFile)", err) return } branchLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/src/" + params["branchname"] if len(treename) != 0 && repoFile == nil { - ctx.Error(404) + ctx.Handle(404, "repo.Single", nil) return } @@ -154,8 +154,8 @@ func Single(ctx *middleware.Context, params martini.Params) { if repoFile.Size > 1024*1024 || repoFile.Filemode != git.FileModeBlob { ctx.Data["FileIsLarge"] = true } else if blob, err := repoFile.LookupBlob(); err != nil { - log.Error("repo.Single(repoFile.LookupBlob): %v", err) - ctx.Error(404) + //log.Error("repo.Single(repoFile.LookupBlob): %v", err) + ctx.Handle(404, "repo.Single(repoFile.LookupBlob)", err) } else { ctx.Data["IsFile"] = true ctx.Data["FileName"] = repoFile.Name @@ -179,8 +179,8 @@ func Single(ctx *middleware.Context, params martini.Params) { files, err := models.GetReposFiles(params["username"], params["reponame"], params["branchname"], params["commitid"], treename) if err != nil { - log.Error("repo.Single(GetReposFiles): %v", err) - ctx.Error(404) + //log.Error("repo.Single(GetReposFiles): %v", err) + ctx.Handle(404, "repo.Single(GetReposFiles)", err) return } @@ -203,8 +203,8 @@ func Single(ctx *middleware.Context, params martini.Params) { if readmeFile.Size > 1024*1024 || readmeFile.Filemode != git.FileModeBlob { ctx.Data["FileIsLarge"] = true } else if blob, err := readmeFile.LookupBlob(); err != nil { - log.Error("repo.Single(readmeFile.LookupBlob): %v", err) - ctx.Error(404) + //log.Error("repo.Single(readmeFile.LookupBlob): %v", err) + ctx.Handle(404, "repo.Single(readmeFile.LookupBlob)", err) return } else { // current repo branch link @@ -239,7 +239,7 @@ func Single(ctx *middleware.Context, params martini.Params) { params["branchname"], params["commitid"]) if err != nil { log.Error("repo.Single(GetCommit): %v", err) - ctx.Error(404) + ctx.Handle(404, "repo.Single(GetCommit)", err) return } ctx.Data["LastCommit"] = commit @@ -275,7 +275,7 @@ func Http(ctx *middleware.Context, params martini.Params) { func Setting(ctx *middleware.Context, params martini.Params) { if !ctx.Repo.IsOwner { - ctx.Error(404) + ctx.Handle(404, "repo.Setting", nil) return } @@ -307,7 +307,7 @@ func Commits(ctx *middleware.Context, params martini.Params) { ctx.Handle(200, "repo.Commits", err) return } else if len(brs) == 0 { - ctx.Error(404) + ctx.Handle(404, "repo.Commits", nil) return } @@ -315,7 +315,7 @@ func Commits(ctx *middleware.Context, params martini.Params) { commits, err := models.GetCommits(params["username"], params["reponame"], params["branchname"]) if err != nil { - ctx.Error(404) + ctx.Handle(404, "repo.Commits", nil) return } ctx.Data["Username"] = params["username"] diff --git a/routers/user/user.go b/routers/user/user.go index c34b529ec3..a0321f187b 100644 --- a/routers/user/user.go +++ b/routers/user/user.go @@ -301,7 +301,7 @@ func Activate(ctx *middleware.Context) { if len(code) == 0 { ctx.Data["IsActivatePage"] = true if ctx.User.IsActive { - ctx.Error(404) + ctx.Handle(404, "user.Activate", nil) return } // Resend confirmation e-mail. diff --git a/templates/status/404.tmpl b/templates/status/404.tmpl new file mode 100644 index 0000000000..4e836b228d --- /dev/null +++ b/templates/status/404.tmpl @@ -0,0 +1,7 @@ +{{template "base/head" .}} +{{template "base/navbar" .}} +<div id="gogs-body" class="container"> + <h4>This page is not found !</h4> + <p>Application Version: {{AppVer}}</p> +</div> +{{template "base/footer" .}} \ No newline at end of file diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl new file mode 100644 index 0000000000..9a00eb1ff7 --- /dev/null +++ b/templates/status/500.tmpl @@ -0,0 +1,7 @@ +{{template "base/head" .}} +{{template "base/navbar" .}} +<div id="gogs-body" class="container"> + <p>An error is occurred : {{.ErrorMsg}}</p> + <p>Application Version: {{AppVer}}</p> +</div> +{{template "base/footer" .}} \ No newline at end of file From bdd32f152d73f31beffa854fb7382072043ef235 Mon Sep 17 00:00:00 2001 From: FuXiaoHei <fuxiaohei@hexiaz.com> Date: Sun, 23 Mar 2014 13:48:01 +0800 Subject: [PATCH 17/38] add m.NotFound handler --- routers/dashboard.go | 7 +++++++ web.go | 3 +++ 2 files changed, 10 insertions(+) diff --git a/routers/dashboard.go b/routers/dashboard.go index f61d67b7de..dafe9f31ec 100644 --- a/routers/dashboard.go +++ b/routers/dashboard.go @@ -20,5 +20,12 @@ func Home(ctx *middleware.Context) { func Help(ctx *middleware.Context) { ctx.Data["PageIsHelp"] = true + ctx.Data["Title"] = "Help" ctx.HTML(200, "help") } + +func NotFound(ctx *middleware.Context) { + ctx.Data["PageIsNotFound"] = true + ctx.Data["Title"] = 404 + ctx.Handle(404, "home.NotFound", nil) +} diff --git a/web.go b/web.go index bf654aace2..a0f9f7051a 100644 --- a/web.go +++ b/web.go @@ -163,6 +163,9 @@ func runWeb(*cli.Context) { m.Get("/template/**", dev.TemplatePreview) } + // not found handler + m.NotFound(routers.NotFound) + listenAddr := fmt.Sprintf("%s:%s", base.Cfg.MustValue("server", "HTTP_ADDR"), base.Cfg.MustValue("server", "HTTP_PORT", "3000")) From 1c01db4019abc2460ac1743f9675c9effd211e3f Mon Sep 17 00:00:00 2001 From: slene <vslene@gmail.com> Date: Sun, 23 Mar 2014 15:21:34 +0800 Subject: [PATCH 18/38] minor fix --- web.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web.go b/web.go index bf654aace2..0d31161854 100644 --- a/web.go +++ b/web.go @@ -95,7 +95,7 @@ func runWeb(*cli.Context) { m.Group("/user", func(r martini.Router) { r.Any("/login", binding.BindIgnErr(auth.LogInForm{}), user.SignIn) - r.Any("/sign_up", reqSignOut, binding.BindIgnErr(auth.RegisterForm{}), user.SignUp) + r.Any("/sign_up", binding.BindIgnErr(auth.RegisterForm{}), user.SignUp) }, reqSignOut) m.Group("/user", func(r martini.Router) { r.Any("/logout", user.SignOut) From 1a16b3e99a0be7dbfeba1e683d253a22db5d1af5 Mon Sep 17 00:00:00 2001 From: FuXiaoHei <fuxiaohei@hexiaz.com> Date: Sun, 23 Mar 2014 16:06:07 +0800 Subject: [PATCH 19/38] repo options ui --- public/css/gogs.css | 8 ++++++-- templates/repo/setting.tmpl | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/public/css/gogs.css b/public/css/gogs.css index 78040bee51..65a6c03d71 100755 --- a/public/css/gogs.css +++ b/public/css/gogs.css @@ -346,6 +346,10 @@ html, body { border-left: 4px solid #DD4B39; } +#gogs-repo-setting-container .form-horizontal label { + line-height: 30px; +} + /* gogits user ssh keys */ #gogs-ssh-keys .list-group-item { @@ -575,12 +579,12 @@ html, body { min-width: 200px; } -#gogs-repo-clone .dropdown-menu{ +#gogs-repo-clone .dropdown-menu { width: 400px; padding: 20px; } -#gogs-repo-clone .input-group{ +#gogs-repo-clone .input-group { margin-bottom: 15px; } diff --git a/templates/repo/setting.tmpl b/templates/repo/setting.tmpl index 38c3fd3bcc..719547b1a9 100644 --- a/templates/repo/setting.tmpl +++ b/templates/repo/setting.tmpl @@ -19,7 +19,41 @@ </div> <div class="panel-body"> - + <form action="/{{.Owner.Name}}/{{.Repository.Name}}/settings" method="post" class="form-horizontal"> + {{.CsrfTokenHtml}} + <input type="hidden" name="action" value="update"> + <div class="form-group"> + <label class="col-md-3 text-right">Repository Name <strong class="text-danger">*</strong></label> + <div class="col-md-9"> + <input type="text" class="form-control" name="repo-name" required="required" value="{{.Repository.Name}}"/> + </div> + </div> + <div class="form-group"> + <label class="col-md-3 text-right">Description</label> + <div class="col-md-9"> + <textarea class="form-control" name="desc" id="repo-desc" rows="6"></textarea> + </div> + </div> + <div class="form-group"> + <label class="col-md-3 text-right">Official Site</label> + <div class="col-md-9"> + <input type="url" class="form-control" name="repo-site"/> + </div> + </div> + <div class="form-group"> + <label class="col-md-3 text-right">Default Branch</label> + <div class="col-md-9"> + <select name="branch" id="repo-default-branch" class="form-control"> + <option value="">Branch</option> + </select> + </div> + </div> + <div class="form-group"> + <div class="col-md-9 col-md-offset-3"> + <button class="btn btn-primary" type="submit">Save Options</button> + </div> + </div> + </form> </div> </div> From 13b6e06943b8cb44bb246e1d57856ed3574f6184 Mon Sep 17 00:00:00 2001 From: FuXiaoHei <fuxiaohei@hexiaz.com> Date: Sun, 23 Mar 2014 16:20:47 +0800 Subject: [PATCH 20/38] 404 and 500 page ui --- public/img/404.png | Bin 0 -> 9776 bytes public/img/500.png | Bin 0 -> 12087 bytes templates/status/404.tmpl | 5 +++-- templates/status/500.tmpl | 5 ++++- 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 public/img/404.png create mode 100644 public/img/500.png diff --git a/public/img/404.png b/public/img/404.png new file mode 100644 index 0000000000000000000000000000000000000000..1f0ee0ef4953defd225425d52531cd8b27be9ebb GIT binary patch literal 9776 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ፃ=Pb!Q=>XCrQkMb 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 NT{OE;rso{<e*lcD`ECFJ literal 0 HcmV?d00001 diff --git a/public/img/500.png b/public/img/500.png new file mode 100644 index 0000000000000000000000000000000000000000..2c122fde93fcba1d61c8412867a68b2a5ebcc44c GIT binary patch literal 12087 zcmcJ#c{J2-_&5HZu?^X>8`%<(3K`p^A(N$K-<Oan2H8S(Mnq&RZL%AbZEQse!%$h0 zHG{+$QDcc2>sae|^!fh&c>a6NbDrmq_qoseoY#F_uj_U0`#Sf1UstlZsUa5!lmh?& zuB%2@ECB!v002ONumS)OQbM)>0DwKf$RP*-j`1ITARs4C1OPZpypTw9bF_c3e-PR~ zK<X+IDHU+n-^1(9Z2$<H%(n`)f5H$#%rI{A*y>Ht>$C2}0ViefkRD!#V&2EV%>-UP z-A@l5a@Yz89?vtf`FNkiVU+*yN0EG!=jD`#j*o1dHCH94${M31Xe%rG<NK}DJnF$f z_#hAFIG<eRuVQaN%kqIXd_pqa%;;k;0Rt8^qA*MG{_5-WWcdUDbf)mInBY2{15m9$ z00sg`=TGr<&;E^XHkY#lq%r}@Nj~Z^P=+PYbKbf~0O%0`>a5<};sBlkz@6|Yy^}yC z7w~)YDuM-=sCY6Z3``WCsDJ=i5RffzcnYlB0=U`4S)K(GJAop+?#c7a#2OIB;q3)O zFowVa+)K01;L&vhFxh%%zW`F2U;rt>-_Im<ovBE9q@7aX8t|aW2m}!Op7m!LkEjYX zJsX0CuG1+$q<?hYsn@$HtibERK7TSVD_4f!h{|+*fD-_qyTcuSG<7nlzvgFuc~I}r zHa`{r@uB;lL`bjA{Ao=EGXuP)gS1K4pFhW#0=hwUA?-dDPQausfS%dCq57S@$dO|x zV)s$F=7Fw60nWTfTl@sav12Q;?l^+-+#|$a)meYaqnPajU5P*6Bj`Y2j&61}u_XR5 z@TT9r8p||lAG4l{AA>6Mr~5(dVVc754_LkPS;3Sw`{X9`lP2Ba?=3U0NcTSb!M7ND zVNLS+A^Ve1E&!isMl^lP*4TH47_$ljKjO9@xd-URY@ADl9?JxHJz*EZ836F4eK%cK z5d`#SmCgY`>w<`L`BUNRT@V1cQWSTl?lK!^hX9_)o!24WN)!g)WWA!xd5WmbtH(Nz zIl+Mwd45^cnh2j@ZmeU179G!-<!$It(d431_?+XG1B6-KL<%}ZS`*osFZV$BZ%V@- z9(PZhI5_|j&Q4=KdD95?7*i^>_)x&&<fopyrQixZ@5~!UGVAB=3TR=Sb@wv9_efnx zIY?<WRCyZr+Jb2m9HRTaToRhnRTMNF4CA?hd0y-@3~i9Ogn3(BHf+2Q5)4GAq@ggw zD%>8a-)=gmCh5B++e78Lg-}23)ojz%P~!F)_XNd=1vhDsQ{YD!{@)On1m4a&_cJmK z?XTM-%VmGdH1IV;?AYTIR60+ed{S=KAREEA`!UQNUY}uafR|g9mXI!xbvez=@A|~( z6nF3Ac~cH)*^CQUk6raK`fhZt)T$IQ!t0fHU2ZwUx0gH*d5ytKWo5q0;K?w1yx;p_ z{+$5))59Ov*^5tEmOkaXrgmHBC-cj~3wqf_l@|RK1*4G4hwwtjtG-3|%$2N?D)?a` zIkNq^Ldd?-b0wsq94}8V23lz`xS4%AUP3=>C~L%Lfo+kx7{3|H$(6_z-)-bBFX|&2 zDOx7-PE;xT`YV>NS=l|-%HtQrvOQn$yzsZNKeK%%N-*;?-!u3#+h;M)sFl_>K{h(1 z@XBM>RW>|TF1FFH3~b(*&sDIG3RRIR#BEwC)N2MTLoL(I2Q6Gn+FTFs@1eL}Cq6JJ zN;E35M!oA2#YzixUAVnwuQztrmTCE{&kGZGr`Nl`uis<Y7rUnvdEE*bROx~D$n?nn z71$5r?y9HIwZ7$D8;~C08i+i{bWZ17{g`*PM|DiK#$?1~+ob!--4&sg<w;sdK)!DN z+59cIFPss=-mKni(fpwKd&uAt;wx%7e35<Y^A`8w^swQU?`FxC@Dgo|HY};341=g= zh~81rMA5%wpcZ^ycr^{v6K2bg_o16JKcY*9ua=x{l3PvkoKp)EMtI=;Pq^b@<l9p| z+Be<3E_^TjSo&$cO`-p(PmV|PaKqQ&FJF4jPT?o;y}#46NO?X5H`~6oem}rIc&VTF z>DnXpN8QgSpGWnvJU4mHz`w9X*oNC~*^c9{jyW%%UG^QbyJIe9m&=(8&+U6#UY%S0 z@m{X)jKB|pbcGRxEi1oCmEQ-yIezE-W?j+RzPT;C?YliF{^bOp_}InZi&1xjL%!U* z6VSR`It%+y@v-8=qMfIhCoU*|LrwUVaYi3s*1arYE8nt|GQYxzzGpw7tIZ>gU$;tf zTXLtWzxj?%f4TH2x@zC#e6CpUjSrV-mpADQ`yYW{=JLyO%Q`nQs7fyPTpV2X2@+;+ z^5hD}lzn@?e{`Y{`Xb%nh)cKk1{RqRT?lYtL}60ftG1Ifm)c}v#$vK#*bWNzFYfgn zrth@RhOY~)nX<TnIzT@`gg9v28zvtnN#<<kmuzY*Se6!6B~FB(9w$4OgXG;O{zTz* zv0aqvl;_!;VZqOkd_hOCJ>@TQ*Cob|rzH+TW_V-#U+rFB{PGs^kYae}VUwhnXtwZf zw1C2y3zg@WEE=qW1A`f7$MEwG`<{ffwe*VA_ZPo=zrB0U6YqV;@(${EZWE@dvMDYs zX-9u8i!Ye}6#Gi@{Ktupmy+j#z|=cAs(JG{*3W}cH9x0{-wXd*9Ox?u)pk&G$cWN> z@PaSvwa(9YDLr!~5wo9VekS7l>j}9J%=FuevBehk@0zY@KmL_(`)pft`eHP451nXc z5YnZ}Zf2<%o+<kHz08J*?UgYye(+o1w{u!g^Pc9FSaNwiZ%`apekcFV%tKM}3D<o1 zFW=8%Pm(?}=4Ez+Hognqd2oHWq`|VroLlI+Qn_N7lZaDTlM%tlaa482<ImEr%(44I zQG|>-^QO8t6HQ?bYm}4i`Yl2-kF114&#T<9Y`zudMIVXoL-)OYerW!~-qL2>JA7(A z%`$%E<x8aEeLL8*il@DOel?qp?@@v`pWU*dd1sNFY-7&|KXb(g;LB@v>I|BG{jf82 zB}Hw8`%X@huf6-cvTU?my6lH%IJDPZBY0mX4JFdKs>Rl@KgC>quaSds5#C#A{SEz} z`<c{ks;xMEd5izPj#Dq$j>wO_(Pw*DUG=W*o807B`*djJ26w!;;6*a+>F?G?#V|V` zf`0Ut-^l9y>2#>OSabWYnCT|NP_s`3>5(rZ*|zm*%ih8MccyCF+h4Q$TB>6ujNk!3 zb20vtxhpxLjRB5lxH~WZ>^MF1Dk^f$d;vG`Vd_KwOm^(MEy%J_xZ()BJp_NQ#JA`Y zjzC*TEy)*m5jSjm_cw;7GKUK3R!g@lD9V?=w6;5c|H)k0t+IlAQ=Lnr*;C<+>j&9B zH!N-tJduM1I>N!L*WbA^dN;__^c(cnfX$N(_wTarjH@rzTr!LaIGi=Ab@E@n&FEXP z)2<2LG+DklO#2$U=S?I9jqi+~zfl-YpmqHgd*>%NeWFRWd28q1_KI*Q$LjT|8xiSY zwFhmJhf8dGJWH_+f9h`&eJML`M7O~Y4m>n&_N0P8Pyc>RnjJCxVn~uPkin+sOG=7y zK?&=(2A<58y)NS){2XKZH+(s$iaK-nW8Zkt{$PTBZ1>S%drf%7#$8%fMJ>ErrdzUg zApFwWtApx+@6og-TJuadK{8wZK=+UN#_%4hyKKmefuKhHIZ$FGZ0yU96siIMAcZor zGy#AJSpdMq1HkTI0KhB)K!`E`EV%&y`~?6A`#*O5WB>rCZe6`{**a|UcYgSb8#dXi zu`JT;SEZz_Aqku|(g}E7BNX%K!}(&(S(f3nAtUK{*jp<NhL@M0)=)!m;HU3ru<moC zTo1~Jn9fm#q}WeBxrQ<FWZ}DTyvI#Xt~`VH?y97Z)w?q`p5zbj2E$_qpBPFolc`QL zM)g)f^!B%wm*(<P3A}m_f4S?Ni{sUU;DDoF2n!&cz^f+@K#m5G?$N*j=s~0t%mLn` z0gO2s1b`##|M8Un$L1e)h!o3~rw<I6<^`!8wvhKz{yKOW$$+pzROib+q^Hnxy3(|e z;Nai|-pcsrdblE+a#aA$^f*x(yGFcDJA}EhDzj^z5MgToBje&3!xJp69Cw)1fn1Dm z+)v^Kii4X1J?B8VT1Dao`_rwqbORf*lT%eq&3SP>4&WOb;Hx{*qg%qFDWNH@skNu7 zfP>!ynE~xA63nYiZ$GDhfw68OaPYlw8ljzM!uw{m03ZN-Va8nG`umP(Gdb@*jQ0A` z2$!1N90Za1qkjw*rxA!U#A))rB&dqilI&wWAI%Z+dyA|Cp5l7Ch3IXx1j>1)nMG7( zbKah-k*@F{z8qGDzKQeaKmV1?<ZQl5H)~2e#Jndq4!pM^`eTgaj<0Lq!&bJM;nEKC zf`a0F;$D->>TUHv#0UubtK4bq8MSRDgkID9pHjvjexkI&@{40pZ1%?vB;D?7*BYaA zC^F=Iwz$)Am91v)(_WJmndJdkBm}Lh*)&pDQ7;*Ao(`k)Q&zMp=Xfdp7%IjeFznf= zgt3MdmcXw?)bbVqLwIQl;8v`z<z>C&wZurE+1^N77U4dSoi$w_Wcn~4?tm>OQ}JoV z{~1R?UNHZzH8F+Cics_RrDyj?`Jw1)1Mv8jLbO>HHQqaw>2pNkheqgV<O6^O<8ch+ zJ2_u!aetS{M`>kssq!Mmf@W?N=6=d)t^IY8`W&FD(W5x&F4DT7qHOfnSq1FL!VEu< z-)Zggm!e}!R>YH(COKMF3|XT$YIEZVCdIIxNcDwlzpUr!2GT*|CQY{8Ksq>fvNT$) zoMCc7^03toVh*4ad6jLF%3ynRzfLPsn(vQBDEk!1dHPng&B(r7c6!Wz@f_(qtAKs@ zh~3QmqP-6bF$<Hs5cK2@6T?QItT7z58R1%$Xb$us|KYf3htG%%cAaA+$HUk^Cz={N zkh(Vi!TxFaP})sizaZt+hc^|l8s-|3qaaB$E5OQGCK02^p5FhAdiy_a4ZO<P^k3)2 z3;L@9!)KUueQd1fv?(oIV|H1JJ2s`m%LF|PSXbr&rUI^=>o`|J9B;4{^l`ot7U3*| zx6z^f40ph~&;-E(092N_VUd2v5gQ8ri*KxSoP&1Wp4A>#DQB#=;tgsU93Tz=%L|^& zxfSqIMR{l1G&{Gg!B$_bjpxf}FL7By7WKAT1mQ@qo{PS4?Oan-Y0zpJ(Z1TIA1VWB z7u1pW3`u4F9HBI5?EOFbkArlRJimH5^L!a`Bvl7;2A~F(nqp=Yh1Xl<u>Z+A|LlnP zH54KsdF+fjb|a4GS83WVQFdKh{r^W`XNh6r1tukdr|9C<Hou`WvaJ`!?}{I`W>M?C z{>PKqQD|qd{vcF?ZM{ze*j2@)8=(CRKMQ60r=v15+vCpH*>5mtEJxnU#%tH-PP)|$ z!&4%Z22sNzlz$>h=n<{OAtpGLlz_Ryu`>=>u}3QoqGJ|gk>L`Lx`2No;*&7yTz*#Q z1OJp(T8F)OAG=O%a~l23SXEqZE2AC!_7^dIj$I?sVW}#mRb!~-u$4h}H3{Q)wIO@j z8(yX9`!)!0uvF`b2j9E*+?)KJJ(RT@ezviVttAfT$VnPr&2!Rgn*I92ksuNsu_s+u z`BA62J!sW8p|6^1^-1iQ)oPU*G1x6Dj_k`;xsLeRcxE}ev|h*j8AEhMmZ_Dtv@{}J zst9=0GB?PooZ5TpcWv$r?Jyudq!sKuo9Dy!r|ruu551Z3=W^%8<ucGa(Jj=4fb7h+ z=E_M*)`%kk?0lT%>0`1Xw3f*xyl|NF`Id`3V9x>aWz}Hw`dFqSv-j~<b=oIlE~XaT z;xlBA49o0~a!b0b^CyR%vvPLdJCX4xDxT0H680o$wTie3g`L?(l!%^PCX}CuW-&j# zAHy<~WHInwyzxL|xv6zqmjfy?^un_=O_GT!;#9>#%qI4!czt1Al{q-kXXzT9NB^>e zk9ap8iQG^j?!(p2tRvz@e{Z?Cg5&{vE}$C}YZMqX0i5ug$M~^gOE8|I^Jg3rL(T$Z z!(>{w&*%%m2YpD+-Fkr+>;e}Ssz78h_L%qx*D?cra+!8KfvgCOCbfZ$(%yQrm5fOz z!=Aq^ii1V$x=!p}owTI<woHl@hsFYkD?2q4DUzy~uc}K`AhPFFi!7nms^lcis-Po< zAj|<|vtk4^l77OAtiEbK4d`xo2EPZEWPK}JUeCKztfGU+zw9cwg(x9c_xu;bPtgOI z9*C&CqTtWn7ouDCDo`b$2nYX@NDpc#9bbY|-X$LFV1oSIKOf~F&WLyJwB^kse|a4| z0RM3gz`?S3_PZ7NM8Q{3eu}}Ppw$b=kb{B9ztvC2mO>~><&;Rq(*rc<yit9vV>!hW zt+2gHgV3amP>3_z2w5FR%&}mw0$|St@;zb@tXo#RcAg@S3~>hiHVLd20_j|s@7A}F zH~x?wdfX3a7-_KsBCYb$QwV0g>5t$SFFNsZ0IWz|r_Z?`?urjppq<g>X_4$RgRcm- zv9A3#n1pd}Xu#cTG0y1nP#TMnhq9z#WMQYHNzgz({;MH~=m^n#Arh-@)nV@Io{@_Y z0K1Qid_wp~_cf&{-EOco#{7lN?;Z%iA_y8IEv}!@h{|=ugT|b6b3j=LJb4s236!7G z<v=u&3DJfg_Ig+U>ByjNu+I#ht{5KNPmK)4Fcb#6bwqMDcBiMO_n^8UqBR6<79M0@ z!vq(^262%Bt-5@+GH`_jrr=+v@PRcrGC^T(H>`ZB;K>h1F1m|6aLeQ<zI6du&L!s5 zlT!d1mv$~e!^GpgEepj1Sx}f+WMDy4U6_7ME}J(5HGLb(E5ykdf{50DQFxF&jtL%! zWtey5&RAB?-7D%QI);d?9~`oKKix$9B%fQ3z7`WKmb1ZF?y&%rg#pW{e=#+6IV3JM zeeH3qmTQe3xKBN8IsZ~k1)X=hJd_5zAX!CVJi>n2O`e{fu7<DxGS^N1i^u{iR2j7$ zc0c3}+p0!`Ekk6j^#Q^^In|=Xon}QxwFIrkAU6WjGqsc!3Fbi0Zw_6K+z-YHG?p!e zOIlb5>)ltMn3tP!5wviPI$Isp`Q27#Smiq6rp_wsJ+YkhLeZQc7JvodPi5i078qpz z>L|9aT%^F-`o@R^1g&szVk4X#o#$5`N>fvpB#^~k@EqZD3;*zwPds15m^z~haBv~0 zQ`LA}cWPM7+1nCCoA@qmB%x(!B5HF3F}JJvwxwmCHvx?K31ko{=f^?$<p#%;$Bo35 zfGW-!(uYKC9R&ldkXubA`NqW;4Ax(BH<3Xxe0m@$6IQRT3I%^|#Z84v7F_RWWuG~h z?a{&25-Rh%S5>?>Bm9d(C6(cl=_3E4z<l{{;a-ToW@e45!NDa|n{C_G{$anUjkctV z=BLj<bB+S|U!iF@xRBV5s(vD?Oo60iN!JM^p=DwrN?;2S-w2n$0U3c1v~m6HI|HVP z--RPjMqi-H(@t~MlD-;f7jmX1Uqz+i$k8KoPK56*gj{H7X!>P&;Fbt@((29*fyl!? z!-HVLr|Wh2BzQJ5_UH1xOC0|7pEBQ!q)>_NfGY@L81`neFpOp>&#at4zZh^V1n-ey zhY)kDD&q=ATx7*QCYMdM*n}5$a3Xxs13v36Al;80H;E4g#A40{NYJ_6;K~vr;=V5N zff+%5uz*v_vU>>|E)*0|kQkESiPZtM{vfzAO|d!iIP30YlVVskib`Rxm_DSAu036A z-1A2kw1~hyM-&!Inu+rK`0I-v5Z41q$xPneTztzX7fzdDk9tnut}SDC)tVB&aZa-U zLA&Ws<W>G)F0PnbOS*>iQM<&76-|r?eXvHj^%B>ZQ-d-BNo@c_y3%g0Se1lve;48C z3*n%hvW07%ATA+rnyPg-$DB{&=OIUN@%XO5|C#y>*9dp)&H!VQfgz%Fk&+>hb$nIU zo@WY@Hzg(QmWYGfLS3t<T-Bsa5!l%qhYns*O5x;5k|YkuU|G@H3(igGvZp9v4B11^ zxpJiT1VS`FXGk7#JnxpD*_gu@-(`{LF|<c2yg`J^_^9b0`KkbhZb?(PJeX!cV}NVG z0Jit0_)6k|eKynJxQIj5%>=Q#%fo05SiE9TT^3&Dwe1J^#fv;&ElPn5j(r%)DUExf z0mjYJ#)-c>5G^5+{RD0RnoHeT<ET?*EAWSnwpc9<iBO^wLpYZR9a=MJbo+=6z=FB1 zr|ZPcv(NxDU>et?J_kv@?5SnrB&mGMkX*j|W7hylxQOG%_9iabXFd5hE2qfnDoARD zH-PzGjH;i*9E3VmO$485LTP+}%R$#sR;JoBx5`6#YLTd799;e9tpN?u$y)C5<We9* zeI;Vb&jwn?7h|xKeb<iZLgicl(x*}Sbb-J6XiE|a7ubY@tIyvW&=8xf4d%gsb(b2{ zCP4X#U2#Mmz8F1Cvb{JzXEmuSFRX3YoF-wB)WwVpx$~tr&mY#{DjsJJ^qd6bnMX_4 zBq%&YZDdHQYJ=^?yFYf-k<n_exUqqWOB0z-v}Qce?a+90py$j#unKpR@J?waybJ1B zC9bu0aQi=S{eR%tZ$mD{KoO7ue;%V+V270^zJvJ7-RW&gLvmG<((=N{sPbCw3gnzQ zGGzYel36;AFwZM)$N^-t0S_=aYBfMxejr4|2VL#bn@qiwxVKaUKUy^5vZsT{D72E6 zQFkU-cXC<teKPj~40c|o)lRC4SNZdh<JSDgjq1cbZ)<UA2`6dz$rG&^Z?sZHuMP`f zp~yc$H3{PY)uW@pP4E)9fv98Eiq=|G8Lkap$Bnf}T$(6&qBY}%R?;#zd<@pjX;51{ z(or}rjdMxlyiVoV#O?C5n;~e<EUJyq?+e!5$_ZfIl<TBl8RcCJj7FJrxBPCV4>mId zo%&ev{g_CyIZ$_xMK|8*O_w?4^wan6%i_kp)AuU9vC>?Y-4fw0ja|IrS2=(RUNw{x zKc~$Y)GE&Zm3_An(r4?gsrJlwDZ<4Ob8#i=tpUcbjvyS6aRq{YGfC%q6$lE5xE8{h zDQV+#$3Fv}*t1co_LlmR8wc=e_)ea%{MwYv;kQ2Qcohj9;pG6XswOOm0?;XI*kqiU zPxW>06Ge^Qj#wrEumF&gnN0j+1DkW{e55)C^s(1|jQ*^}5TKu;19ZArtYjF}o(nI# zhcFk**ar(DXf52>X*h4?n8-J&kl)X#Ha@9eP~!cj#yIn#GozP1J*nDmpgL}Q7CbPf zNgX@xlKH-XZc6JP2DQBzFp%IAk4e&`a<j<k+eyjk$EGF!;Y>!^SBiE%Rcl)yw98$_ zRuj=OJ|T{ysON{oNFCM*_tM&XnpEx@3zVYrq>*@;7F9@O`vBX1b~!qQ&{9%TQX)qa z1g<oK+^%d^{INkMQ9&4{G$!(uu>Mu{=-M5aE@XoF`Zk$%-Kd1Yt6XR6$rSoa>M3@O z?5{Fn_q}`_i-{&M$m!~q-QS4$UGc8^AgNE}>9OHq6@xOX@J&J8!%fb6J9gh28o93? z$Btt@5q*iS{1U~gtYMO~Wx>l(isdcEfC85eMJ!*?w}o2Dvn~qs5`+afCIYP#Cn{kE zc~_mns)ANox5G(y2Go;``9~)zunpeW;l!m0o1`w&qlJJ*hKz37yR!hp3(^#>ZE!lc zFl4|4K(J^(`2aU%O*1hn=^pa91UntWsTy48TZJs?HWx<*@qrUXIaI#~FUbS)%+_6f z1MkJ+vWeXoYLIRBK?3Y8bnm13$<r~Z%A1RC%eb-CiAy4xPYAaXaEj194qcFxICO!J z>_QQ8W2MK&jR#qFC-IL=c=v1!Yb{o(B>qAfi$k5bNGkBtk-Z|w$R9!pV2qxw3G@<? zim|$OgG<@EJCOL48Ky3_9(BcZ&pQFPJ7JmB<$(-YSTfrRkq3@wjaIBdX2+R$s6}ai zGcjGpi3gg>r=!(6hobIOAm?O}Atzq^qjbpvqzlrOmSV+X6S8P$m0h}9|5-)_o9=nz zapY<1q{Nq1Iwe7`vr^R>PW$N{8UI*3%oGLe5N6@z$C$21i3jNZtFGeS<)P!_xV2&& z+%f2N)(LO4nRB{1U=Dex#+;`gR$BiF{)(x@eazm!Cw(k$)20eXj;`m%ZY3_E`+HqR zywU9u$8kUrFYmtj4flpLA-8*9pgvr?D(6o}tC7uVe&tA1H4fhO^OlmRzgsnVXrhSU z=!kg?HWCx7>e#*cLTwDXj*2qft0bAz5UxpGj>wSI!Cn{l#ihXwc(ec@0De5&^q6lD z(o>gKq2hCgL2keG@rVoQra?rgM)di)@uK>Cju@85k0q<Mz8Q0?*4Z+(3UdG)pf&Ha zY=1tuiCggx*?WCV$8p|GCb_wqsEo8!Vf6B$ir6o1Iy5ettQ99-kXV_P#!dKPdqnOs zmL`Uut7C@QLf1>C!(6f8E~rD*GZi!*28&<mVm!8Zy33AE@v(X0&jIL37v06O5c|58 zjDD=Y(U>=ES!?rFZLsY_4bc-l>ZW&GMm*5f&zDOr0CR|uEh;N$6@dzAkSkhCmDZWW zGDx?OK8-L|tS4uG(N>kt6Rv8KW&VXD`n^8d9{99OcJx`5e^zsj?0hV$|2V|y*uJOQ zKje7svOG|#Z@M~VriZ%9^dQ?EY8*!}SkfHZaqNf9?~aJT_QDGTX%aqBO4(lMfe@B| ztA+*pvTI2sAJaA9E_RJI=F3TKdXKkjKWwBW_aRkffT?vbfVD58aE)^S_S)5VX<=c= z3J?SYz?MT3?)Wr4J*b(;8cLw|U{s|B?jgE3t*_#(ee)Kp;yU7kXx{Bx?T!gY3&=<x zl~fOZd%Co~CquGZY^6#jPQ@UCku0JnB%0GPhh3GGC76X*dmkP2<e9B&*K+FN+owv` z%B6M4cN%I<VWTbS-Q{cLIMJv#!-V-O$c?ooQz;hU0VWvMB~pd5(t|#NqzgAvx)lue zm#PyZ7_%a3*CGnnISbr_mBOoVhf6o}5l1>MqEl6DyMHCXTwZq4gskXur;Z*KqSeNY zXf>=Z4=qaY421#8RboJVWUsT#K540@rbak;{b<jWmZ*HP7gst}zUCtn!^va{8?!^k zX&FQ?aLlk6-&?Fn79BsgpLzz!D|h$AY-O09IO4Yn`z2C^a<~s|zcAl@5xYg2iq^^Y zXtF)VKeY1<CmK~&PbeLaWZI}VFT)-si3sSJ-?fPIm$5TI3wCGiGAcSG5l1E{fv_{? z9hamOcGoVWB6~#>x2yJfkK(C7b25>A@M&dyqYKi_@4sLq-z;vR?U|xO5;MusX&3zO zS6P&FYcc9FMf12C#3qdglwZ4E8Yhunz5~txrDpLu9PeIqwZz49k?PsP;5V@?oV$T< zGuv`lzbKEiv|l%$JC9Tidz(opEoX@0NlM56IrTbtUk@)}z9)+h%#zj_&=|3!iY-kS z%9RIZ;Y77(dR*?d;SQI|AV&&V!#wzJ?5SFJcM8Cs*AKp#Ro$Fp?i}oV{kuwtf-;B* zy;vu88S6shI@*A$IUOmjW?t2Anf_ht<(W^_4uhkx2qifc%kF4}YEnCNeeQMY{*rZ3 zzY*<E>Vg4*S?v#LD9`oznu}#ULjg}3Y;JD8Cv%i5_{N(=H2dwvYt4V0)>Ww`rE~5E zo)553jNY<Kj9_dFz+!Hp2iU?2*E#<@0}ZkrJrKz=pDIx~wqqC{a&R9p^Y?VCt?@%Y zbr_`^Qlu)Q;{o=BE>52t7Q~bu9vyIl5akSoT@`-KuRSqp<qW-}eBpvkP7uQxh~Vc= zE7X$CC2F^GzQ~A^EIhpj?a`%?EzSaRl<sqhzoN`(R`j_I^1hJFK1p0$T)ZWC@}K`Q zm0_(=9YO6qjd=K5%TMA&m1<JIKsdiUYXJ{ijmfukevFRlXp2Ufreu6X;c%Yt7UHr_ z1VQ7q>onfV03@Z_+|n|bju6-IhG-VhQ&&o`V}Xjd&;LDLXDjxNdOA)tG6pm}7iUFB z=}??D-?PK!$TzNUVqQHxkY9>6*svJgXFd?2M0f4m=#!7JU`o0F9>&F<)Q<k$O}xnZ zT_orP7{RX1dR^VQTSL{cN{q`nd$DRwi8vu}(3Hnl8{1zL>i^jd;Rh4ieiR}3=7{5L zObEXoND65R7=V$OAPgHMUFOh<Qhp){Q-X;sZqS(u5Z7TV=tWnrmi6>wM5{?3<r7v5 zi@KcTH$sq~2`%TE!B4lNo^B%IcjXtOuR~=CgKn%<+b;6J$NukZyOAqI8{*y0uZuM& zoec(&5Hw4T%+$(346@Mg(ORfzjSXlg!obcW#Ve0HD(j>jZfW$zue^OT3V<X;^3Q^F zy^`i%lRcFskDkI%Q1h|<r=E3kv<G0_J7F|4g^lztX*I0cEcg`G%@foqO_VeR-8r~# z%Ty@SI`@5~LtUhuS)yN4d72fk@zz%O_<437tOW`_t92|%l%uW|G}A-B=HNh6wXb?i z9xqp!)_Rw5axk|C-5$ZUlV~c%g28LOOi3&RN96@n_>}~$UZs2|KENpcMCu_!PSM#r zxnc$*XgbL3!W~f#5DVoPE8;;bN7N^Z;|<-g3qLYd(5+%&rL@>SBghV@?tBTwuAU)^ zx4BBI8eP~TPdw|B45N)N?j$GuS8IAaFfbsBsl7e)h_jE|?fC5a{>VZ*K_CnvhrOCW zTQg-P3*N{t_xVs6vVa<D(&8|^>TT`V9Y^G#1jPF^p5wSSgZsGu`w_YU>?(IK*7^xU z-Ir+V27E58PNxtC-KwuexEhp{Gejd0<%|I>I2;}<r2v#l0OzrZii?ReT&*I}lMc&y zPCTswEQyzfi`or#6sF`mvAWd>#`g?)&(BD#5Og%>I<Uqlr{GVw@^Ns))ux*x-fj^E zbf0IEhHSP9Sbm67_^#$GtDO&<7;#G=T)>x=;8f2Lt+uhCu??5^?584$0Ij9D`G0)- zf#7%d*x`fFtO#1WCq7L&p`|muH%_t&bGrC%^qgEJ5>qkoo<DA_Yv0>Rz*_^lI9=H~ z7{<yLM$5a%{DQ;!XSBi5T^5W1>k5I~WcA63FL^Uwbk#yaFA8&xt?5+E=FhLUdSm-4 zD6Dl)m&-p#hP%A!D*E>(>j%dFp%)q@+ui)Nsn$NGrq1sda7Lth?tJGP{`v5`P>G#a z9q`I^mxA$9i+YA0NXm;`(_wOBPOp=|`+RvvU`S?=e^?&K(xPsQRF|Bg{S3E(vUGAK zM;^UfYTCr-R(6qy(^}=IM#i5r;RO%7xQOwI%l0N=3t8Ifu5+x^sd)H30pL0%@1lNu zZcbOe1<+$G?z%z#>sZiTS%06tcXK%;ifgA)JKd&aeBY5K!^;8G4^K~D5_(b5VWK~G zoKkXSW}jM`Mj*0~_r=$>g|Oog+P5$*wvgYlQ)RD_1>MCe#DA49e75ms04JAUG1QF7 z^YI|f#*QhgXTu&=!yHps0=NC|hjr^;Ma!6s>o9Si6Qvz`5nr7c4`ic<gu7&Qtyt4L zW#dDpE<aF8c}&$;`>$Y^Ay|eE;3wJZg;}pjin&$S0IOM4H7{+`u=gg?Wg*U&!+h(s zYh6lC{wr-*zHDiUWL7qe&wbG)V?lKMF6Gn6w!>R@rNHiGy^!XkGqILl__RypeIETd z+XA~9i)a5zMqcYeoh3SIO=~^}hMT>AU$Qz?`4aar!t>$!&|J*dR)!Lc=;*saZs!TR zTwqsP&#qbhe2b3*@Cry_)i{n-Vv;=nY%i$I)&~>L0lKaBme=KS3qI}_3d@Ko(9?Bz z3(&U7N?%n<fQ;`z5wE$RQ(llK*tPgREh4DRwvU~rvQwgmQQYqRYu(xB4fRDlkEEHg z{gEG3SdOlS>Vc$Gnp;|u0Dlst>(!bV#SGJugRa)5t5dWd0PkpmkKch}(Ue^}dH)?~ zus7)Qzv7%5R*MMX@|%XXKtj6<p<J<Vs!8kU50TE&M36RX-j(2Q?HyE9xBytf4pM~w zr-BEaT=@72?lnG8Qdw;0%xrRaf3vL^yX)!c$$4ptEp<xU4awi%-Gx?q-`LA|?8MIa zbjL^!`7af3C0Bc38;ORHnK+%2MzGuIAyCv<WP3~RUL{+TX60X!TwZ!Ud7qo|hPW#C z-8cgCC_hM?Sao$wS^)TQOVQjMQhp7kID7YRaOab?23w~0AkE&$_NJ?QOBS8uUVrhs zbPe9~aY}xV38jS$uTg0Mz#UgrwX8?cqzKUtC6LJ57AVzOQuzvbzt*7eUZd@&@G~5s zaqInLWka8>3-qjnP_MJks065)zI4$UTF_fteS#gNr{KTTcz<AEKql@{UXU=zs_KwY z<u&hE82l`=D`kOyg)u>0@ztu5F~*kU2aWSfNQ^>!Y=9Ub>bU*sL1XU|T-HAcYmJo? z`w>eE3kyTIQXu0hFFM)Jy*Y#at69Y)td78a0Nzoj2eH0?Zzf$NhYcq-CX|`|s!s#c zX#?-uOFkP<nMi=8rcT(^Ggz19fxq0=KclPv-34!53k}sEf)WbfMrY8^E+=!f^`1L9 z)f<`1h(V?;*VU{^P}Ub>H_W@wQ`9?^F12dc8ke}!u%pAvgrI@4Mq3VG3l|A2o${U> zhv)?D!<0o|utc%$Xr#1`se8`Mt*{DR5gQTc?ALZ>Q(v&-PWC?F3sX{#4+!5$H(0j6 zzEcnip5=~J8eASLpV&`b7NHPs>m<|wAFp|OdcOZP&7)aB2VU?O>%76>5_KN@PDA|E zxe`{rsQ0-QXIwa8IXqEF5BeNb{9%@!x+D*nh0$oI4mwd4+Gfs2r)`pjl=j<qc!n3= z#?CH_Tbc#)n@RT>S1dp&HXHd$V0?vW2$(W)n}#z@Lb=x5JH;2x;8Y=tSz2(TgXgPZ zy|B8G#RS@;8NO%^<Ki^C67r5XT~~m^^|55Om%tVx4$c}zBgFQ8c2of5ffww6irN9r zf>-l|CMTMu3p7{KF0q^^#(jc0HMR3&+vb(RAECf^_S%=xy^()U)=7kA(w)}@6AD+n z-uQ81*)8g{69epaPtgvKTg)^V*yS5=z3!la)h|-j^$+Ww(<gpVF`n#Mn~WZwuTTOx zl9tXY2f9rN9au@xuKgUT=<^aN`q%67@XW9Y=Koqx%l}^I%>QxA%>SPG|D`bpcJ8k~ Z(7xq{YZhzGI_itRs&9IwM$hg3{|AblNq+zU literal 0 HcmV?d00001 diff --git a/templates/status/404.tmpl b/templates/status/404.tmpl index 4e836b228d..c2cafe0c9d 100644 --- a/templates/status/404.tmpl +++ b/templates/status/404.tmpl @@ -1,7 +1,8 @@ {{template "base/head" .}} {{template "base/navbar" .}} -<div id="gogs-body" class="container"> - <h4>This page is not found !</h4> +<div id="gogs-body" class="container text-center"> + <p style="margin-top: 80px"><img src="/img/404.png" alt="404"/></p> + <hr/> <p>Application Version: {{AppVer}}</p> </div> {{template "base/footer" .}} \ No newline at end of file diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl index 9a00eb1ff7..f3cd24d696 100644 --- a/templates/status/500.tmpl +++ b/templates/status/500.tmpl @@ -1,7 +1,10 @@ {{template "base/head" .}} {{template "base/navbar" .}} -<div id="gogs-body" class="container"> +<div id="gogs-body" class="container text-center"> + <p style="margin-top: 80px"><img src="/img/500.png" alt="404"/></p> + <hr/> <p>An error is occurred : {{.ErrorMsg}}</p> + <hr/> <p>Application Version: {{AppVer}}</p> </div> {{template "base/footer" .}} \ No newline at end of file From 1201c6a9b4897736650a4b416451aa521024b899 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 23 Mar 2014 16:31:13 +0800 Subject: [PATCH 21/38] bug fixed & more commits for push --- models/action.go | 2 +- models/models.go | 2 +- serve.go | 157 +++++++++++++++++++++++++++++++++++++++-------- update.go | 22 ++++--- 4 files changed, 145 insertions(+), 38 deletions(-) diff --git a/models/action.go b/models/action.go index 107d4b1057..d338da24ba 100644 --- a/models/action.go +++ b/models/action.go @@ -28,7 +28,7 @@ type Action struct { ActUserName string // Action user name. RepoId int64 RepoName string - Content string + Content string `xorm:"varchar(1000)"` Created time.Time `xorm:"created"` } diff --git a/models/models.go b/models/models.go index 8713ff2896..7f5f626f5e 100644 --- a/models/models.go +++ b/models/models.go @@ -91,5 +91,5 @@ func GetStatistic() (stats Statistic) { stats.Counter.Watch, _ = orm.Count(new(Watch)) stats.Counter.Action, _ = orm.Count(new(Action)) stats.Counter.Access, _ = orm.Count(new(Access)) - return stats + return } diff --git a/serve.go b/serve.go index 3ce8f9046c..b786c54356 100644 --- a/serve.go +++ b/serve.go @@ -5,14 +5,19 @@ package main import ( + "bytes" + "container/list" "fmt" + "io" "os" "os/exec" "strconv" "strings" "github.com/codegangsta/cli" + "github.com/qiniu/log" + "github.com/gogits/git" "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/base" ) @@ -39,12 +44,27 @@ gogs serv provide access auth for repositories`, Flags: []cli.Flag{}, } +func parseCmd(cmd string) (string, string) { + ss := strings.SplitN(cmd, " ", 2) + if len(ss) != 2 { + return "", "" + } + + verb, args := ss[0], ss[1] + if verb == "git" { + ss = strings.SplitN(args, " ", 2) + args = ss[1] + verb = fmt.Sprintf("%s %s", verb, ss[0]) + } + return verb, args +} + func In(b string, sl map[string]int) bool { _, e := sl[b] return e } -func runServ(*cli.Context) { +func runServ(k *cli.Context) { base.NewConfigContext() models.LoadModelsConfig() models.NewEngine() @@ -84,15 +104,20 @@ func runServ(*cli.Context) { repoName = repoName[:len(repoName)-4] } - os.Setenv("userName", user.Name) - os.Setenv("userId", strconv.Itoa(int(user.Id))) + //os.Setenv("userName", user.Name) + //os.Setenv("userId", strconv.Itoa(int(user.Id))) repo, err := models.GetRepositoryByName(user, repoName) + var isExist bool = true if err != nil { - println("Unavilable repository", err) - return + if err == models.ErrRepoNotExist { + isExist = false + } else { + println("Unavilable repository", err) + return + } } - os.Setenv("repoId", strconv.Itoa(int(repo.Id))) - os.Setenv("repoName", repoName) + //os.Setenv("repoId", strconv.Itoa(int(repo.Id))) + //os.Setenv("repoName", repoName) isWrite := In(verb, COMMANDS_WRITE) isRead := In(verb, COMMANDS_READONLY) @@ -130,12 +155,6 @@ func runServ(*cli.Context) { return } - isExist, err := models.IsRepositoryExist(user, repoName) - if err != nil { - println("Inernel error:", err.Error()) - return - } - if !isExist { if isRead { println("Repository", user.Name+"/"+repoName, "is not exist") @@ -149,28 +168,114 @@ func runServ(*cli.Context) { } } + rep, err := git.OpenRepository(models.RepoPath(user.Name, repoName)) + if err != nil { + println(err.Error()) + return + } + + refs, err := rep.AllReferencesMap() + if err != nil { + println(err.Error()) + return + } + gitcmd := exec.Command(verb, rRepo) gitcmd.Dir = base.RepoRootPath - gitcmd.Stdout = os.Stdout - gitcmd.Stdin = os.Stdin + + var s string + b := bytes.NewBufferString(s) + + gitcmd.Stdout = io.MultiWriter(os.Stdout, b) + gitcmd.Stdin = io.MultiReader(os.Stdin, b) gitcmd.Stderr = os.Stderr if err = gitcmd.Run(); err != nil { println("execute command error:", err.Error()) } -} -func parseCmd(cmd string) (string, string) { - ss := strings.SplitN(cmd, " ", 2) - if len(ss) != 2 { - return "", "" + // update + w, _ := os.Create("serve.log") + defer w.Close() + log.SetOutput(w) + + var t = "ok refs/heads/" + var i int + var refname string + for { + l, err := b.ReadString('\n') + if err != nil { + break + } + i = i + 1 + l = l[:len(l)-1] + idx := strings.Index(l, t) + if idx > 0 { + refname = l[idx+len(t):] + } + } + var ref *git.Reference + var ok bool + + var l *list.List + //log.Info("----", refname, "-----") + if ref, ok = refs[refname]; !ok { + refs, err = rep.AllReferencesMap() + if err != nil { + println(err.Error()) + return + } + if ref, ok = refs[refname]; !ok { + println("unknow reference name", refname) + return + } + l, err = ref.AllCommits() + if err != nil { + println(err.Error()) + return + } + } else { + //log.Info("----", ref, "-----") + var last *git.Commit + //log.Info("00000", ref.Oid.String()) + last, err = ref.LastCommit() + if err != nil { + println(err.Error()) + return + } + + ref2, err := rep.LookupReference(ref.Name) + if err != nil { + println(err.Error()) + return + } + + //log.Info("11111", ref2.Oid.String()) + before, err := ref2.LastCommit() + if err != nil { + println(err.Error()) + return + } + //log.Info("----", before.Id(), "-----", last.Id()) + l = ref.CommitsBetween(before, last) } - verb, args := ss[0], ss[1] - if verb == "git" { - ss = strings.SplitN(args, " ", 2) - args = ss[1] - verb = fmt.Sprintf("%s %s", verb, ss[0]) + commits := make([][]string, 0) + for e := l.Back(); e != nil; e = e.Prev() { + commit := e.Value.(*git.Commit) + commits = append(commits, []string{commit.Id().String(), commit.Message()}) + } + + if err = models.CommitRepoAction(user.Id, user.Name, + repo.Id, ref.BranchName(), commits); err != nil { + log.Error("runUpdate.models.CommitRepoAction: %v", err, commits) + } else { + //log.Info("refname", refname) + //log.Info("Listen: %v", cmd) + //fmt.Println("...", cmd) + + //runUpdate(k) + c := exec.Command("exec", "git", "update-server-info") + c.Run() } - return verb, args } diff --git a/update.go b/update.go index baa433d75c..915e046530 100644 --- a/update.go +++ b/update.go @@ -4,16 +4,9 @@ package main -import ( - "os" - "strconv" +import "github.com/codegangsta/cli" - "github.com/codegangsta/cli" - - "github.com/gogits/git" - "github.com/gogits/gogs/models" - "github.com/gogits/gogs/modules/log" -) +//"github.com/gogits/gogs/modules/log" var CmdUpdate = cli.Command{ Name: "update", @@ -26,6 +19,9 @@ gogs serv provide access auth for repositories`, // for command: ./gogs update func runUpdate(*cli.Context) { + /*w, _ := os.Create("update.log") + log.SetOutput(w) + userName := os.Getenv("userName") userId := os.Getenv("userId") repoId := os.Getenv("repoId") @@ -35,16 +31,19 @@ func runUpdate(*cli.Context) { repo, err := git.OpenRepository(f) if err != nil { + log.Error("runUpdate.Open repoId: %v", err) return } ref, err := repo.LookupReference("HEAD") if err != nil { + log.Error("runUpdate.Ref repoId: %v", err) return } lastCommit, err := repo.LookupCommit(ref.Oid) if err != nil { + log.Error("runUpdate.Commit repoId: %v", err) return } @@ -63,5 +62,8 @@ func runUpdate(*cli.Context) { if err = models.CommitRepoAction(int64(sUserId), userName, int64(sRepoId), repoName, commits); err != nil { log.Error("runUpdate.models.CommitRepoAction: %v", err) - } + } else { + l := exec.Command("exec", "git", "update-server-info") + l.Run() + }*/ } From 4adb024715bdb576d7acb2900d933e7196abee4c Mon Sep 17 00:00:00 2001 From: slene <vslene@gmail.com> Date: Sun, 23 Mar 2014 16:40:51 +0800 Subject: [PATCH 22/38] minor fix markdown setting --- bee.json | 3 ++- modules/base/markdown.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bee.json b/bee.json index 19efcbc205..4f7f7a771c 100644 --- a/bee.json +++ b/bee.json @@ -13,7 +13,8 @@ "others": [ "modules", "$GOPATH/src/github.com/gogits/binding", - "$GOPATH/src/github.com/gogits/git" + "$GOPATH/src/github.com/gogits/git", + "$GOPATH/src/github.com/gogits/gfm" ] }, "cmd_args": [ diff --git a/modules/base/markdown.go b/modules/base/markdown.go index 2273cd772f..05ce0c833c 100644 --- a/modules/base/markdown.go +++ b/modules/base/markdown.go @@ -72,7 +72,7 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte, func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte { htmlFlags := 0 - htmlFlags |= gfm.HTML_USE_XHTML + // htmlFlags |= gfm.HTML_USE_XHTML // htmlFlags |= gfm.HTML_USE_SMARTYPANTS // htmlFlags |= gfm.HTML_SMARTYPANTS_FRACTIONS // htmlFlags |= gfm.HTML_SMARTYPANTS_LATEX_DASHES @@ -81,7 +81,7 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte { htmlFlags |= gfm.HTML_SKIP_SCRIPT htmlFlags |= gfm.HTML_GITHUB_BLOCKCODE htmlFlags |= gfm.HTML_OMIT_CONTENTS - htmlFlags |= gfm.HTML_COMPLETE_PAGE + // htmlFlags |= gfm.HTML_COMPLETE_PAGE renderer := &CustomRender{ Renderer: gfm.HtmlRenderer(htmlFlags, "", ""), urlPrefix: urlPrefix, From d594bb386f92455079e56b48f5add16b05609dea Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 23 Mar 2014 16:51:43 +0800 Subject: [PATCH 23/38] log import fixed --- serve.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/serve.go b/serve.go index 54296049d5..d4ce138c1c 100644 --- a/serve.go +++ b/serve.go @@ -15,7 +15,7 @@ import ( "strings" "github.com/codegangsta/cli" - "github.com/qiniu/log" + "github.com/gogits/gogs/modules/log" "github.com/gogits/git" "github.com/gogits/gogs/models" @@ -195,9 +195,9 @@ func runServ(k *cli.Context) { } // update - w, _ := os.Create("serve.log") - defer w.Close() - log.SetOutput(w) + //w, _ := os.Create("serve.log") + //defer w.Close() + //log.SetOutput(w) var t = "ok refs/heads/" var i int From 21a138a75a379c1c8d1142b6bdf3f855c0361634 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 23 Mar 2014 17:10:09 +0800 Subject: [PATCH 24/38] bug fxied --- serve.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/serve.go b/serve.go index d4ce138c1c..16df38f420 100644 --- a/serve.go +++ b/serve.go @@ -104,8 +104,6 @@ func runServ(k *cli.Context) { repoName = repoName[:len(repoName)-4] } - //os.Setenv("userName", user.Name) - //os.Setenv("userId", strconv.Itoa(int(user.Id))) repo, err := models.GetRepositoryByName(user.Id, repoName) var isExist bool = true if err != nil { @@ -116,8 +114,6 @@ func runServ(k *cli.Context) { return } } - //os.Setenv("repoId", strconv.Itoa(int(repo.Id))) - //os.Setenv("repoName", repoName) isWrite := In(verb, COMMANDS_WRITE) isRead := In(verb, COMMANDS_READONLY) @@ -187,13 +183,18 @@ func runServ(k *cli.Context) { b := bytes.NewBufferString(s) gitcmd.Stdout = io.MultiWriter(os.Stdout, b) - gitcmd.Stdin = io.MultiReader(os.Stdin, b) + //gitcmd.Stdin = io.MultiReader(os.Stdin, b) + gitcmd.Stdin = os.Stdin gitcmd.Stderr = os.Stderr if err = gitcmd.Run(); err != nil { println("execute command error:", err.Error()) } + if !strings.HasPrefix(cmd, "git-receive-pack") { + return + } + // update //w, _ := os.Create("serve.log") //defer w.Close() From c4287bafabb96f3970bf629ff225c48112d008d9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 23 Mar 2014 17:13:22 +0800 Subject: [PATCH 25/38] bug fixed --- serve.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serve.go b/serve.go index 16df38f420..2f7a9d6052 100644 --- a/serve.go +++ b/serve.go @@ -227,7 +227,7 @@ func runServ(k *cli.Context) { return } if ref, ok = refs[refname]; !ok { - println("unknow reference name", refname) + println("unknow reference name -", refname, "-") return } l, err = ref.AllCommits() From 406afa3c32eab4782291ca01eea273f2df25748d Mon Sep 17 00:00:00 2001 From: slene <vslene@gmail.com> Date: Sun, 23 Mar 2014 17:22:14 +0800 Subject: [PATCH 26/38] markdown img max-width --- public/css/markdown.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/css/markdown.css b/public/css/markdown.css index a810fa3ce7..9f4024ba89 100644 --- a/public/css/markdown.css +++ b/public/css/markdown.css @@ -175,6 +175,10 @@ margin-bottom: 0; } +.markdown img { + max-width: 100%; +} + .markdown .btn { color: #fff; } From c9e830cefb3b19593e7de05c785efdd93a0982d6 Mon Sep 17 00:00:00 2001 From: FuXiaoHei <fuxiaohei@hexiaz.com> Date: Sun, 23 Mar 2014 17:36:12 +0800 Subject: [PATCH 27/38] fix dropdown close bug --- public/js/app.js | 2 +- templates/repo/nav.tmpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index df755727b5..e0f92854f4 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -98,7 +98,7 @@ var Gogits = { }; // fix dropdown inside click Gogits.initDropDown = function(){ - $('.dropdown-menu').on('click','a,button,input,select',function(e){ + $('.dropdown-menu.no-propagation').on('click',function(e){ e.stopPropagation(); }); }; diff --git a/templates/repo/nav.tmpl b/templates/repo/nav.tmpl index cf1b7d0389..4d939e2504 100644 --- a/templates/repo/nav.tmpl +++ b/templates/repo/nav.tmpl @@ -18,7 +18,7 @@ <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> <span class="caret"></span> </button> - <div class="dropdown-menu clone-group-btn dropdown-menu-right"> + <div class="dropdown-menu clone-group-btn dropdown-menu-right no-propagation"> <div class="input-group"> <span class="input-group-btn"> <button class="btn btn-default" data-link="{{.CloneLink.SSH}}" type="button">SSH</button> From 0119675480505a6534ee0fc98c91e3bd2b8cdaaf Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 23 Mar 2014 17:40:50 +0800 Subject: [PATCH 28/38] bug fixed --- models/action.go | 4 +++- serve.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/models/action.go b/models/action.go index cfb124363c..4f78deb30e 100644 --- a/models/action.go +++ b/models/action.go @@ -30,6 +30,7 @@ type Action struct { ActUserName string // Action user name. RepoId int64 RepoName string + RefName string Content string `xorm:"TEXT"` Created time.Time `xorm:"created"` } @@ -52,7 +53,7 @@ func (a Action) GetContent() string { // CommitRepoAction records action for commit repository. func CommitRepoAction(userId int64, userName string, - repoId int64, repoName string, commits [][]string) error { + repoId int64, repoName string, refName string, commits [][]string) error { bs, err := json.Marshal(commits) if err != nil { return err @@ -78,6 +79,7 @@ func CommitRepoAction(userId int64, userName string, Content: string(bs), RepoId: repoId, RepoName: repoName, + RefName: refName, }) return err } diff --git a/serve.go b/serve.go index 2f7a9d6052..bb9165dc06 100644 --- a/serve.go +++ b/serve.go @@ -268,7 +268,7 @@ func runServ(k *cli.Context) { } if err = models.CommitRepoAction(user.Id, user.Name, - repo.Id, ref.BranchName(), commits); err != nil { + repo.Id, repoName, refname, commits); err != nil { log.Error("runUpdate.models.CommitRepoAction: %v", err, commits) } else { //log.Info("refname", refname) From d6619cfe15885b7004585bf2b96933a7a3e51f39 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 23 Mar 2014 17:51:12 +0800 Subject: [PATCH 29/38] limit max commits view number on activity --- serve.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/serve.go b/serve.go index bb9165dc06..812c37bb58 100644 --- a/serve.go +++ b/serve.go @@ -262,9 +262,13 @@ func runServ(k *cli.Context) { } commits := make([][]string, 0) + var maxCommits = 5 for e := l.Back(); e != nil; e = e.Prev() { commit := e.Value.(*git.Commit) commits = append(commits, []string{commit.Id().String(), commit.Message()}) + if len(commits) >= maxCommits { + break + } } if err = models.CommitRepoAction(user.Id, user.Name, From 24630e0c9b92bcd9fdeb07ce15c3dd2cfc459a52 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 23 Mar 2014 18:00:09 +0800 Subject: [PATCH 30/38] improved activity --- models/action.go | 7 ++++++- serve.go | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/models/action.go b/models/action.go index 4f78deb30e..ca2ff3cbf1 100644 --- a/models/action.go +++ b/models/action.go @@ -51,9 +51,14 @@ func (a Action) GetContent() string { return a.Content } +type PushCommits struct { + Len int + Commits [][]string +} + // CommitRepoAction records action for commit repository. func CommitRepoAction(userId int64, userName string, - repoId int64, repoName string, refName string, commits [][]string) error { + repoId int64, repoName string, refName string, commits *PushCommits) error { bs, err := json.Marshal(commits) if err != nil { return err diff --git a/serve.go b/serve.go index 812c37bb58..71abf5fdd6 100644 --- a/serve.go +++ b/serve.go @@ -262,7 +262,7 @@ func runServ(k *cli.Context) { } commits := make([][]string, 0) - var maxCommits = 5 + var maxCommits = 3 for e := l.Back(); e != nil; e = e.Prev() { commit := e.Value.(*git.Commit) commits = append(commits, []string{commit.Id().String(), commit.Message()}) @@ -272,7 +272,7 @@ func runServ(k *cli.Context) { } if err = models.CommitRepoAction(user.Id, user.Name, - repo.Id, repoName, refname, commits); err != nil { + repo.Id, repoName, refname, &models.PushCommits{l.Len(), commits}); err != nil { log.Error("runUpdate.models.CommitRepoAction: %v", err, commits) } else { //log.Info("refname", refname) From 1eb078d0a8c5424de9512d810ab2fbf21f59ff78 Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sun, 23 Mar 2014 06:27:01 -0400 Subject: [PATCH 31/38] Fix action --- README.md | 6 ++-- README_ZH.md | 2 +- models/action.go | 12 ++++---- models/issue.go | 26 ++++++++-------- modules/base/tool.go | 23 ++++++++++++--- routers/repo/repo.go | 59 +++++++++++++++++++++---------------- serve.go | 2 +- templates/repo/nav.tmpl | 2 +- templates/repo/setting.tmpl | 16 ++++------ web.go | 2 +- 10 files changed, 85 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 504c21975b..e947d7739a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Gogs - Go Git Service [](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [](https://drone.io/github.com/gogits/gogs/latest) +Gogs - Go Git Service [](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [](https://drone.io/github.com/gogits/gogs/latest) [](https://bitdeli.com/free "Bitdeli Badge") ===================== Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language. @@ -7,7 +7,9 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language ##### Current version: 0.1.6 Alpha -[简体中文](README_ZH.md) +#### Other language version + +- [简体中文](README_ZH.md) ## Purpose diff --git a/README_ZH.md b/README_ZH.md index 0ab8dfdd07..78e26fada4 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -1,4 +1,4 @@ -Gogs - Go Git Service [](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [](https://drone.io/github.com/gogits/gogs/latest) +Gogs - Go Git Service [](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [](https://drone.io/github.com/gogits/gogs/latest) [](https://bitdeli.com/free "Bitdeli Badge") ===================== Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。 diff --git a/models/action.go b/models/action.go index ca2ff3cbf1..1174929354 100644 --- a/models/action.go +++ b/models/action.go @@ -8,6 +8,7 @@ import ( "encoding/json" "time" + "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" ) @@ -47,18 +48,17 @@ func (a Action) GetRepoName() string { return a.RepoName } +func (a Action) GetBranch() string { + return a.RefName +} + func (a Action) GetContent() string { return a.Content } -type PushCommits struct { - Len int - Commits [][]string -} - // CommitRepoAction records action for commit repository. func CommitRepoAction(userId int64, userName string, - repoId int64, repoName string, refName string, commits *PushCommits) error { + repoId int64, repoName string, refName string, commits *base.PushCommits) error { bs, err := json.Marshal(commits) if err != nil { return err diff --git a/models/issue.go b/models/issue.go index f78c240cbc..929567b1b7 100644 --- a/models/issue.go +++ b/models/issue.go @@ -83,42 +83,42 @@ func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed, sess := orm.Limit(20, (page-1)*20) if repoId > 0 { - sess = sess.Where("repo_id=?", repoId).And("is_closed=?", isClosed) + sess.Where("repo_id=?", repoId).And("is_closed=?", isClosed) } else { - sess = sess.Where("is_closed=?", isClosed) + sess.Where("is_closed=?", isClosed) } if userId > 0 { - sess = sess.And("assignee_id=?", userId) + sess.And("assignee_id=?", userId) } else if posterId > 0 { - sess = sess.And("poster_id=?", posterId) + sess.And("poster_id=?", posterId) } else if isMention { - sess = sess.And("mentions like '%$" + base.ToStr(userId) + "|%'") + sess.And("mentions like '%$" + base.ToStr(userId) + "|%'") } if milestoneId > 0 { - sess = sess.And("milestone_id=?", milestoneId) + sess.And("milestone_id=?", milestoneId) } if len(labels) > 0 { for _, label := range strings.Split(labels, ",") { - sess = sess.And("mentions like '%$" + label + "|%'") + sess.And("mentions like '%$" + label + "|%'") } } switch sortType { case "oldest": - sess = sess.Asc("created") + sess.Asc("created") case "recentupdate": - sess = sess.Desc("updated") + sess.Desc("updated") case "leastupdate": - sess = sess.Asc("updated") + sess.Asc("updated") case "mostcomment": - sess = sess.Desc("num_comments") + sess.Desc("num_comments") case "leastcomment": - sess = sess.Asc("num_comments") + sess.Asc("num_comments") default: - sess = sess.Desc("created") + sess.Desc("created") } var issues []Issue diff --git a/modules/base/tool.go b/modules/base/tool.go index c7ee2ee857..edf7a953c9 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -471,6 +471,7 @@ type Actioner interface { GetOpType() int GetActUserName() string GetRepoName() string + GetBranch() string GetContent() string } @@ -493,25 +494,39 @@ const ( TPL_COMMIT_REPO_LI = `<div><img id="gogs-user-avatar-commit" src="%s?s=16" alt="user-avatar" title="username"/> <a href="/%s/%s/commit/%s">%s</a> %s</div>` ) +type PushCommits struct { + Len int + Commits [][]string +} + // ActionDesc accepts int that represents action operation type // and returns the description. func ActionDesc(act Actioner, avatarLink string) string { actUserName := act.GetActUserName() repoName := act.GetRepoName() + branch := act.GetBranch() content := act.GetContent() switch act.GetOpType() { case 1: // Create repository. return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, actUserName, repoName, repoName) case 5: // Commit repository. - var commits [][]string - if err := json.Unmarshal([]byte(content), &commits); err != nil { + var push *PushCommits + if err := json.Unmarshal([]byte(content), &push); err != nil { return err.Error() } buf := bytes.NewBuffer([]byte("\n")) - for _, commit := range commits { + max := 3 + count := len(push.Commits) + if count < max { + max = count + } + for _, commit := range push.Commits[:max] { buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, actUserName, repoName, commit[0], commit[0][:7], commit[1]) + "\n") } - return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, "master", "master", actUserName, repoName, actUserName, repoName, + if count > max { + buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits">%d other commits >></a></div>`, actUserName, repoName, count-max)) + } + return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, branch, branch, actUserName, repoName, actUserName, repoName, buf.String()) default: return "invalid type" diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 0f1ea31235..82956098b7 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -52,30 +52,6 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) { ctx.Handle(200, "repo.Create", err) } -func SettingPost(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { - ctx.Error(404) - return - } - - switch ctx.Query("action") { - case "delete": - if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") { - ctx.Data["ErrorMsg"] = "Please make sure you entered repository name is correct." - ctx.HTML(200, "repo/setting") - return - } - - if err := models.DeleteRepository(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.LowerName); err != nil { - ctx.Handle(200, "repo.Delete", err) - return - } - } - - log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName) - ctx.Redirect("/") -} - func Branches(ctx *middleware.Context, params martini.Params) { if !ctx.Repo.IsValid { return @@ -203,7 +179,6 @@ func Single(ctx *middleware.Context, params martini.Params) { if readmeFile.Size > 1024*1024 || readmeFile.Filemode != git.FileModeBlob { ctx.Data["FileIsLarge"] = true } else if blob, err := readmeFile.LookupBlob(); err != nil { - //log.Error("repo.Single(readmeFile.LookupBlob): %v", err) ctx.Handle(404, "repo.Single(readmeFile.LookupBlob)", err) return } else { @@ -301,6 +276,40 @@ func Setting(ctx *middleware.Context, params martini.Params) { ctx.HTML(200, "repo/setting") } +func SettingPost(ctx *middleware.Context, params martini.Params) { + if !ctx.Repo.IsOwner { + ctx.Error(404) + return + } + + switch ctx.Query("action") { + case "update": + ctx.Repo.Repository.Description = ctx.Query("desc") + ctx.Repo.Repository.Website = ctx.Query("site") + if err := models.UpdateRepository(ctx.Repo.Repository); err != nil { + ctx.Handle(404, "repo.SettingPost(update)", err) + return + } + ctx.Data["IsSuccess"] = true + ctx.HTML(200, "repo/setting") + log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName) + case "delete": + if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") { + ctx.Data["ErrorMsg"] = "Please make sure you entered repository name is correct." + ctx.HTML(200, "repo/setting") + return + } + + if err := models.DeleteRepository(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.LowerName); err != nil { + ctx.Handle(200, "repo.Delete", err) + return + } + + log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName) + ctx.Redirect("/") + } +} + func Commits(ctx *middleware.Context, params martini.Params) { brs, err := models.GetBranches(params["username"], params["reponame"]) if err != nil { diff --git a/serve.go b/serve.go index 71abf5fdd6..f9291366ab 100644 --- a/serve.go +++ b/serve.go @@ -272,7 +272,7 @@ func runServ(k *cli.Context) { } if err = models.CommitRepoAction(user.Id, user.Name, - repo.Id, repoName, refname, &models.PushCommits{l.Len(), commits}); err != nil { + repo.Id, repoName, refname, &base.PushCommits{l.Len(), commits}); err != nil { log.Error("runUpdate.models.CommitRepoAction: %v", err, commits) } else { //log.Info("refname", refname) diff --git a/templates/repo/nav.tmpl b/templates/repo/nav.tmpl index 4d939e2504..b2d3500f92 100644 --- a/templates/repo/nav.tmpl +++ b/templates/repo/nav.tmpl @@ -3,7 +3,7 @@ <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> / {{.Repository.Name}}</h3> - <p class="desc">{{.Repository.Description}}{{if .Repository.Website}}<a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}</p> + <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"> {{if not .IsBareRepo}} diff --git a/templates/repo/setting.tmpl b/templates/repo/setting.tmpl index 719547b1a9..c826e55a77 100644 --- a/templates/repo/setting.tmpl +++ b/templates/repo/setting.tmpl @@ -12,7 +12,7 @@ </div> <div id="gogs-repo-setting-container" class="col-md-9"> - {{if .ErrorMsg}}<p class="alert alert-danger">{{.ErrorMsg}}</p>{{end}} + {{if .IsSuccess}}<p class="alert alert-success">Repository option has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}} <div class="panel panel-default"> <div class="panel-heading"> Repository Options @@ -22,32 +22,26 @@ <form action="/{{.Owner.Name}}/{{.Repository.Name}}/settings" method="post" class="form-horizontal"> {{.CsrfTokenHtml}} <input type="hidden" name="action" value="update"> - <div class="form-group"> - <label class="col-md-3 text-right">Repository Name <strong class="text-danger">*</strong></label> - <div class="col-md-9"> - <input type="text" class="form-control" name="repo-name" required="required" value="{{.Repository.Name}}"/> - </div> - </div> <div class="form-group"> <label class="col-md-3 text-right">Description</label> <div class="col-md-9"> - <textarea class="form-control" name="desc" id="repo-desc" rows="6"></textarea> + <textarea class="form-control" name="desc" id="repo-desc" rows="3">{{.Repository.Description}}</textarea> </div> </div> <div class="form-group"> <label class="col-md-3 text-right">Official Site</label> <div class="col-md-9"> - <input type="url" class="form-control" name="repo-site"/> + <input type="url" class="form-control" name="site" value="{{.Repository.Website}}" /> </div> </div> - <div class="form-group"> + <!-- <div class="form-group"> <label class="col-md-3 text-right">Default Branch</label> <div class="col-md-9"> <select name="branch" id="repo-default-branch" class="form-control"> <option value="">Branch</option> </select> </div> - </div> + </div> --> <div class="form-group"> <div class="col-md-9 col-md-offset-3"> <button class="btn btn-primary" type="submit">Save Options</button> diff --git a/web.go b/web.go index 7b36ccd9db..9d90851c96 100644 --- a/web.go +++ b/web.go @@ -163,7 +163,7 @@ func runWeb(*cli.Context) { m.Get("/template/**", dev.TemplatePreview) } - // not found handler + // Not found handler. m.NotFound(routers.NotFound) listenAddr := fmt.Sprintf("%s:%s", From c386bb4bd314843a532012877e148ae70ee44672 Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sun, 23 Mar 2014 06:34:41 -0400 Subject: [PATCH 32/38] Bug fix --- modules/base/tool.go | 11 +++-------- templates/repo/nav.tmpl | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/modules/base/tool.go b/modules/base/tool.go index edf7a953c9..b48566f542 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -515,16 +515,11 @@ func ActionDesc(act Actioner, avatarLink string) string { return err.Error() } buf := bytes.NewBuffer([]byte("\n")) - max := 3 - count := len(push.Commits) - if count < max { - max = count - } - for _, commit := range push.Commits[:max] { + for _, commit := range push.Commits { buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, actUserName, repoName, commit[0], commit[0][:7], commit[1]) + "\n") } - if count > max { - buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits">%d other commits >></a></div>`, actUserName, repoName, count-max)) + if push.Len > 3 { + buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits">%d other commits >></a></div>`, actUserName, repoName, push.Len)) } return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, branch, branch, actUserName, repoName, actUserName, repoName, buf.String()) diff --git a/templates/repo/nav.tmpl b/templates/repo/nav.tmpl index b2d3500f92..a3358fd890 100644 --- a/templates/repo/nav.tmpl +++ b/templates/repo/nav.tmpl @@ -32,7 +32,7 @@ <p class="help-block text-center">Need help cloning? Visit <a href="#">Help</a>!</p> </div> </div> - <div class="btn-group {{if .IsRepositoryWatching}}watching{{else}}no-watching{{end}}" id="gogs-repo-watching" data-watch="/{{.SignedUser.Name}}/{{.Repository.Name}}/action/watch" data-unwatch="/{{.SignedUser.Name}}/{{.Repository.Name}}/action/unwatch"> + <div class="btn-group {{if .IsRepositoryWatching}}watching{{else}}no-watching{{end}}" id="gogs-repo-watching" data-watch="/{{.Owner.Name}}/{{.Repository.Name}}/action/watch" data-unwatch="/{{.Owner.Name}}/{{.Repository.Name}}/action/unwatch"> {{if .IsRepositoryWatching}} <button type="button" class="btn btn-default"><i class="fa fa-eye fa-lg fa-m"></i></button> {{else}} From e9a3432d984d19b675ee4a1ff82b2a148f970645 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 23 Mar 2014 19:24:06 +0800 Subject: [PATCH 33/38] bug fixed --- serve.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serve.go b/serve.go index 71abf5fdd6..84fd8d7b82 100644 --- a/serve.go +++ b/serve.go @@ -263,7 +263,7 @@ func runServ(k *cli.Context) { commits := make([][]string, 0) var maxCommits = 3 - for e := l.Back(); e != nil; e = e.Prev() { + for e := l.Front(); e != nil; e = e.Next() { commit := e.Value.(*git.Commit) commits = append(commits, []string{commit.Id().String(), commit.Message()}) if len(commits) >= maxCommits { From 3d1a967477dd8245dabee6f9f8feb3c1c3924fc3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 23 Mar 2014 19:53:50 +0800 Subject: [PATCH 34/38] bug fixed --- models/repo.go | 7 +++++++ serve.go | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/models/repo.go b/models/repo.go index a37923c8b1..eafb12f471 100644 --- a/models/repo.go +++ b/models/repo.go @@ -10,6 +10,7 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" "path" "path/filepath" "regexp" @@ -198,6 +199,12 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv return nil, err } + c := exec.Command("git", "update-server-info") + err = c.Run() + if err != nil { + log.Error("repo.CreateRepository(exec update-server-info): %v", err) + } + return repo, NewRepoAction(user, repo) } diff --git a/serve.go b/serve.go index a8dfe70b4c..ce1dc3184f 100644 --- a/serve.go +++ b/serve.go @@ -280,7 +280,10 @@ func runServ(k *cli.Context) { //fmt.Println("...", cmd) //runUpdate(k) - c := exec.Command("exec", "git", "update-server-info") - c.Run() + c := exec.Command("git", "update-server-info") + err := c.Run() + if err != nil { + log.Error("update-server-info: %v", err) + } } } From 97e82a0ff6286fd0a42281cd1e34dd734110e2f4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao <xiaolunwen@gmail.com> Date: Sun, 23 Mar 2014 20:04:26 +0800 Subject: [PATCH 35/38] bug fixed --- models/repo.go | 1 + serve.go | 1 + 2 files changed, 2 insertions(+) diff --git a/models/repo.go b/models/repo.go index eafb12f471..e27e99b056 100644 --- a/models/repo.go +++ b/models/repo.go @@ -200,6 +200,7 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv } c := exec.Command("git", "update-server-info") + c.Dir = repoPath err = c.Run() if err != nil { log.Error("repo.CreateRepository(exec update-server-info): %v", err) diff --git a/serve.go b/serve.go index ce1dc3184f..b84fa2a4e5 100644 --- a/serve.go +++ b/serve.go @@ -281,6 +281,7 @@ func runServ(k *cli.Context) { //runUpdate(k) c := exec.Command("git", "update-server-info") + c.Dir = models.RepoPath(user.Name, repoName) err := c.Run() if err != nil { log.Error("update-server-info: %v", err) From 97debac18534e030924654befc6dc1eeb870a38b Mon Sep 17 00:00:00 2001 From: Unknown <joe2010xtmf@163.com> Date: Sun, 23 Mar 2014 08:40:40 -0400 Subject: [PATCH 36/38] SSL enable config option --- README.md | 4 ++-- README_ZH.md | 4 ++-- conf/app.ini | 2 ++ gogs.go | 2 +- models/user.go | 2 +- modules/base/conf.go | 4 ++++ modules/base/tool.go | 2 +- modules/middleware/repo.go | 6 +++++- routers/admin/admin.go | 1 + routers/dashboard.go | 2 +- templates/admin/config.tmpl | 1 + templates/status/404.tmpl | 1 + 12 files changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e947d7739a..42eba6362d 100644 --- 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  -##### Current version: 0.1.6 Alpha +##### Current version: 0.1.7 Alpha #### Other language version @@ -27,7 +27,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o ## Features - Activity timeline -- SSH protocol support. +- SSH/HTTPS protocol support. - Register/delete account. - Create/delete/watch public repository. - User profile page. diff --git a/README_ZH.md b/README_ZH.md index 78e26fada4..b405e04198 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。  -##### 当前版本:0.1.6 Alpha +##### 当前版本:0.1.7 Alpha ## 开发目的 @@ -23,7 +23,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依 ## 功能特性 - 活动时间线 -- SSH 协议支持 +- SSH/HTTPS 协议支持 - 注册/删除用户 - 创建/删除/关注公开仓库 - 用户个人信息页面 diff --git a/conf/app.ini b/conf/app.ini index b051557f41..ab9f6dc4bb 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -32,6 +32,8 @@ PATH = data/gogs.db [admin] [security] +; Use HTTPS to clone repository, otherwise use HTTP. +ENABLE_HTTPS_CLONE = false ; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!! SECRET_KEY = !#@FDEWREWR&*( ; Auto-login remember days diff --git a/gogs.go b/gogs.go index 0bdbbc0697..09b28f9b3f 100644 --- a/gogs.go +++ b/gogs.go @@ -20,7 +20,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.1.6.0323.1" +const APP_VER = "0.1.7.0323.1" func init() { base.AppVer = APP_VER diff --git a/models/user.go b/models/user.go index 9333d1ee67..c9d6e61303 100644 --- a/models/user.go +++ b/models/user.go @@ -208,7 +208,7 @@ func UpdateUser(user *User) (err error) { user.Website = user.Website[:255] } - _, err = orm.Id(user.Id).UseBool().Cols("website", "location").Update(user) + _, err = orm.Id(user.Id).UseBool().Cols("website", "location", "is_active", "is_admin").Update(user) return err } diff --git a/modules/base/conf.go b/modules/base/conf.go index 19f587077b..fba05e8800 100644 --- a/modules/base/conf.go +++ b/modules/base/conf.go @@ -38,6 +38,8 @@ var ( RunUser string RepoRootPath string + EnableHttpsClone bool + LogInRememberDays int CookieUserName string CookieRememberName string @@ -260,6 +262,8 @@ func NewConfigContext() { SecretKey = Cfg.MustValue("security", "SECRET_KEY") RunUser = Cfg.MustValue("", "RUN_USER") + EnableHttpsClone = Cfg.MustBool("security", "ENABLE_HTTPS_CLONE", false) + LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS") CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME") CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME") diff --git a/modules/base/tool.go b/modules/base/tool.go index b48566f542..6d31b05252 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -519,7 +519,7 @@ func ActionDesc(act Actioner, avatarLink string) string { buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, actUserName, repoName, commit[0], commit[0][:7], commit[1]) + "\n") } if push.Len > 3 { - buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits">%d other commits >></a></div>`, actUserName, repoName, push.Len)) + buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len)) } return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, branch, branch, actUserName, repoName, actUserName, repoName, buf.String()) diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go index 3864caaf80..eea2570ca6 100644 --- a/modules/middleware/repo.go +++ b/modules/middleware/repo.go @@ -69,8 +69,12 @@ func RepoAssignment(redirect bool) martini.Handler { ctx.Repo.IsWatching = models.IsWatching(ctx.User.Id, repo.Id) } ctx.Repo.Repository = repo + scheme := "http" + if base.EnableHttpsClone { + scheme = "https" + } ctx.Repo.CloneLink.SSH = fmt.Sprintf("git@%s:%s/%s.git", base.Domain, user.LowerName, repo.LowerName) - ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("https://%s/%s/%s.git", base.Domain, user.LowerName, repo.LowerName) + ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s://%s/%s/%s.git", scheme, base.Domain, user.LowerName, repo.LowerName) ctx.Data["IsRepositoryValid"] = true ctx.Data["Repository"] = repo diff --git a/routers/admin/admin.go b/routers/admin/admin.go index c0f39f7159..f1f951ef25 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -141,6 +141,7 @@ func Config(ctx *middleware.Context) { ctx.Data["Domain"] = base.Domain ctx.Data["RunUser"] = base.RunUser ctx.Data["RunMode"] = strings.Title(martini.Env) + ctx.Data["EnableHttpsClone"] = base.EnableHttpsClone ctx.Data["RepoRootPath"] = base.RepoRootPath ctx.Data["Service"] = base.Service diff --git a/routers/dashboard.go b/routers/dashboard.go index dafe9f31ec..76ecc3f676 100644 --- a/routers/dashboard.go +++ b/routers/dashboard.go @@ -26,6 +26,6 @@ func Help(ctx *middleware.Context) { func NotFound(ctx *middleware.Context) { ctx.Data["PageIsNotFound"] = true - ctx.Data["Title"] = 404 + ctx.Data["Title"] = "Page Not Found" ctx.Handle(404, "home.NotFound", nil) } diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 048740e617..915c9dc088 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -17,6 +17,7 @@ <div><b>Run User:</b> {{.RunUser}}</div> <div><b>Run Mode:</b> {{.RunMode}}</div> <hr/> + <div><b>Enable HTTPS Clone</b> <i class="fa fa{{if .EnableHttpsClone}}-check{{end}}-square-o"></i></div> <div><b>Repository Root Path:</b> {{.RepoRootPath}}</div> </div> </div> diff --git a/templates/status/404.tmpl b/templates/status/404.tmpl index c2cafe0c9d..b971f279a8 100644 --- a/templates/status/404.tmpl +++ b/templates/status/404.tmpl @@ -4,5 +4,6 @@ <p style="margin-top: 80px"><img src="/img/404.png" alt="404"/></p> <hr/> <p>Application Version: {{AppVer}}</p> + <p>If you think it is an error, please open an issue on <a href="https://github.com/gogits/gogs/issues/new">GitHub</a>.</p> </div> {{template "base/footer" .}} \ No newline at end of file From 559cd63fc5feace32bb44c695207579609516f15 Mon Sep 17 00:00:00 2001 From: slene <vslene@gmail.com> Date: Sun, 23 Mar 2014 20:58:12 +0800 Subject: [PATCH 37/38] fix code view indentation --- public/css/markdown.css | 5 ++-- public/js/app.js | 44 +++++++++++++++++++-------------- templates/repo/single_file.tmpl | 4 +-- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/public/css/markdown.css b/public/css/markdown.css index 9f4024ba89..d46fd94365 100644 --- a/public/css/markdown.css +++ b/public/css/markdown.css @@ -135,13 +135,12 @@ box-shadow: inset 40px 0 0 #f5f5f5, inset 41px 0 0 #ccc; } -.markdown > pre > code, -.markdown > pre > ol.linenums > li > code { +.markdown > pre > code { white-space: pre; word-wrap: normal; } -.markdown > pre > ol.linenums > li > code { +.markdown > pre > ol.linenums > li { padding: 0 10px; } diff --git a/public/js/app.js b/public/js/app.js index e0f92854f4..3e995d5b0e 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -110,25 +110,6 @@ var Gogits = { $pre.addClass('prettyprint linenums'); prettyPrint(); - var $lineNums = $pre.parent().siblings('.lines-num'); - if ($lineNums.length > 0) { - var nums = $pre.find('ol.linenums > li').length; - for (var i = 1; i <= nums; i++) { - $lineNums.append('<span id="L' + i + '" rel=".L' + i + '">' + i + '</span>'); - } - - var last; - $(document).on('click', '.lines-num span', function () { - var $e = $(this); - if (last) { - last.removeClass('active'); - } - last = $e.parent().siblings('.lines-code').find('ol.linenums > ' + $e.attr('rel')); - last.addClass('active'); - window.location.href = '#' + $e.attr('id'); - }); - } - // Set anchor. var headers = {}; $md.find('h1, h2, h3, h4, h5, h6').each(function () { @@ -148,6 +129,30 @@ var Gogits = { }); } + Gogits.renderCodeView = function () { + $('.code-view .lines-code > pre').each(function(){ + var $pre = $(this); + var $lineNums = $pre.parent().siblings('.lines-num'); + if ($lineNums.length > 0) { + var nums = $pre.find('ol.linenums > li').length; + for (var i = 1; i <= nums; i++) { + $lineNums.append('<span id="L' + i + '" rel=".L' + i + '">' + i + '</span>'); + } + + var last; + $(document).on('click', '.lines-num span', function () { + var $e = $(this); + if (last) { + last.removeClass('active'); + } + last = $e.parent().siblings('.lines-code').find('ol.linenums > ' + $e.attr('rel')); + last.addClass('active'); + window.location.href = '#' + $e.attr('id'); + }); + } + }); + }; + })(jQuery); // ajax utils @@ -177,6 +182,7 @@ function initCore() { Gogits.initModals(); Gogits.initDropDown(); Gogits.renderMarkdown(); + Gogits.renderCodeView(); } function initRegister() { diff --git a/templates/repo/single_file.tmpl b/templates/repo/single_file.tmpl index 7bca626aaa..40ef44eb1b 100644 --- a/templates/repo/single_file.tmpl +++ b/templates/repo/single_file.tmpl @@ -16,12 +16,12 @@ {{.FileContent|str2html}} </div> {{else}} - <div class="panel-body file-body file-code"> + <div class="panel-body file-body file-code code-view"> <table> <tbody> <tr> <td class="lines-num"></td> - <td class="lines-code markdown"><pre class="linenums lang-{{.FileExt}}"><code>{{.FileContent}}</code></pre></td> + <td class="lines-code markdown"><pre class="prettyprint linenums lang-{{.FileExt}}">{{.FileContent}}</pre></td> </tr> </tbody> </table> From f7f175a0793a53f3c50d20d89e324a610f94c442 Mon Sep 17 00:00:00 2001 From: slene <vslene@gmail.com> Date: Sun, 23 Mar 2014 22:14:29 +0800 Subject: [PATCH 38/38] fix code select range --- public/js/app.js | 83 ++++++++++++++++++++++++++++++++++++++++-------- public/js/lib.js | 2 +- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index 3e995d5b0e..d5185580f6 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -31,6 +31,23 @@ var Gogits = { } }; return ajax(url, options); + }, + + changeHash: function(hash) { + if(history.pushState) { + history.pushState(null, null, hash); + } + else { + location.hash = hash; + } + }, + + deSelect: function() { + if(window.getSelection) { + window.getSelection().removeAllRanges(); + } else { + document.selection.empty(); + } } }); }(jQuery)); @@ -130,27 +147,67 @@ var Gogits = { } Gogits.renderCodeView = function () { + function selectRange($list, $select, $from){ + $list.removeClass('active'); + if($from){ + var a = parseInt($select.attr('rel').substr(1)); + var b = parseInt($from.attr('rel').substr(1)); + var c; + if(a != b){ + if(a > b){ + c = a; + a = b; + b = c; + } + var classes = []; + for(i = a; i <= b; i++) { + classes.push('.L'+i); + } + $list.filter(classes.join(',')).addClass('active'); + $.changeHash('#L' + a + '-' + 'L' + b); + return + } + } + $select.addClass('active'); + $.changeHash('#' + $select.attr('rel')); + } + + $(document).on('click', '.lines-num span', function (e) { + var $select = $(this); + var $list = $select.parent().siblings('.lines-code').find('ol.linenums > li'); + selectRange($list, $list.filter('[rel='+$select.attr('rel')+']'), (e.shiftKey?$list.filter('.active').eq(0):null)); + $.deSelect(); + }); + $('.code-view .lines-code > pre').each(function(){ var $pre = $(this); - var $lineNums = $pre.parent().siblings('.lines-num'); + var $lineCode = $pre.parent(); + var $lineNums = $lineCode.siblings('.lines-num'); if ($lineNums.length > 0) { var nums = $pre.find('ol.linenums > li').length; for (var i = 1; i <= nums; i++) { - $lineNums.append('<span id="L' + i + '" rel=".L' + i + '">' + i + '</span>'); + $lineNums.append('<span id="L' + i + '" rel="L' + i + '">' + i + '</span>'); } - - var last; - $(document).on('click', '.lines-num span', function () { - var $e = $(this); - if (last) { - last.removeClass('active'); - } - last = $e.parent().siblings('.lines-code').find('ol.linenums > ' + $e.attr('rel')); - last.addClass('active'); - window.location.href = '#' + $e.attr('id'); - }); } }); + + $(window).on('hashchange', function(e) { + var m = window.location.hash.match(/^#(L\d+)\-(L\d+)$/); + var $list = $('.code-view ol.linenums > li'); + if(m){ + var $first = $list.filter('.'+m[1]); + selectRange($list, $first, $list.filter('.'+m[2])); + $("html, body").scrollTop($first.offset().top-200); + console.log($first.offset().top); + return; + } + m = window.location.hash.match(/^#(L\d+)$/); + if(m){ + var $first = $list.filter('.'+m[1]); + selectRange($list, $first); + $("html, body").scrollTop($first.offset().top-200); + } + }).trigger('hashchange'); }; })(jQuery); diff --git a/public/js/lib.js b/public/js/lib.js index b5cc41c042..8735ac9c11 100644 --- a/public/js/lib.js +++ b/public/js/lib.js @@ -340,7 +340,7 @@ q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(? s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/, q],["pun",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d= c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i<c.length;++i)b(c[i]);d===(d|0)&&c[0].setAttribute("value",d);var r=j.createElement("ol"); -r.className="linenums";for(var d=Math.max(0,d-1|0)||0,i=0,n=c.length;i<n;++i)k=c[i],k.className="L"+(i+d+1),k.firstChild||k.appendChild(j.createTextNode("\u00a0")),r.appendChild(k);a.appendChild(r)}function p(a,d){for(var g=d.length;--g>=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*</.test(d)?"default-markup":"default-code";return F[a]}function K(a){var d=a.h;try{var g=T(a.c,a.i),b=g.a; +r.className="linenums";for(var d=Math.max(0,d-1|0)||0,i=0,n=c.length;i<n;++i)k=c[i],k.setAttribute("rel", "L"+(i+d+1)),k.className="L"+(i+d+1),k.firstChild||k.appendChild(j.createTextNode("\u00a0")),r.appendChild(k);a.appendChild(r)}function p(a,d){for(var g=d.length;--g>=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*</.test(d)?"default-markup":"default-code";return F[a]}function K(a){var d=a.h;try{var g=T(a.c,a.i),b=g.a; a.a=b;a.d=g.d;a.e=0;I(d,b)(a);var s=/\bMSIE\s(\d+)/.exec(navigator.userAgent),s=s&&+s[1]<=8,d=/\n/g,x=a.a,m=x.length,g=0,j=a.d,k=j.length,b=0,c=a.g,i=c.length,r=0;c[i]=m;var n,e;for(e=n=0;e<i;)c[e]!==c[e+2]?(c[n++]=c[e++],c[n++]=c[e++]):e+=2;i=n;for(e=n=0;e<i;){for(var p=c[e],w=c[e+1],t=e+2;t+2<=i&&c[t+1]===w;)t+=2;c[n++]=p;c[n++]=w;e=t}c.length=n;var f=a.c,h;if(f)h=f.style.display,f.style.display="none";try{for(;b<k;){var l=j[b+2]||m,B=c[r+2]||m,t=Math.min(l,B),A=j[b+1],G;if(A.nodeType!==1&&(G=x.substring(g, t))){s&&(G=G.replace(d,"\r"));A.nodeValue=G;var L=A.ownerDocument,o=L.createElement("span");o.className=c[r+1];var v=A.parentNode;v.replaceChild(o,A);o.appendChild(A);g<l&&(j[b+1]=A=L.createTextNode(x.substring(t,l)),v.insertBefore(A,o.nextSibling))}g=t;g>=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=["break,continue,do,else,for,if,return,while"],E=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[E,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],N=[E,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],