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">&times;</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">&times;</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 [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Go Walker](http://gowalker.org/api/v1/badge)](https://gowalker.org/github.com/gogits/gogs)
+Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](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.
+![Demo](http://gowalker.org/public/gogs_demo.gif)
 
 ##### 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 [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest)
+=====================
+
+Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
+
+![Demo](http://gowalker.org/public/gogs_demo.gif)
+
+##### 当前版本: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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

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
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
I?R@|L1B#tVe*gdg

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 [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest)
+Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/gogits/gogs/trend.png)](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 [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest)
+Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/gogits/gogs/trend.png)](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
 
 ![Demo](http://gowalker.org/public/gogs_demo.gif)
 
-##### 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 托管服务。
 
 ![Demo](http://gowalker.org/public/gogs_demo.gif)
 
-##### 当前版本: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"],