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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
NT{OE;rso{<e*lcD`ECFJ

literal 0
HcmV?d00001

diff --git a/public/img/500.png b/public/img/500.png
new file mode 100644
index 0000000000000000000000000000000000000000..2c122fde93fcba1d61c8412867a68b2a5ebcc44c
GIT binary patch
literal 12087
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
Z(7xq{YZhzGI_itRs&9IwM$hg3{|AblNq+zU

literal 0
HcmV?d00001

diff --git a/templates/status/404.tmpl b/templates/status/404.tmpl
index 4e836b228d..c2cafe0c9d 100644
--- a/templates/status/404.tmpl
+++ b/templates/status/404.tmpl
@@ -1,7 +1,8 @@
 {{template "base/head" .}}
 {{template "base/navbar" .}}
-<div id="gogs-body" class="container">
-    <h4>This page is not found !</h4>
+<div id="gogs-body" class="container text-center">
+    <p style="margin-top: 80px"><img src="/img/404.png" alt="404"/></p>
+    <hr/>
     <p>Application Version: {{AppVer}}</p>
 </div>
 {{template "base/footer" .}}
\ No newline at end of file
diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl
index 9a00eb1ff7..f3cd24d696 100644
--- a/templates/status/500.tmpl
+++ b/templates/status/500.tmpl
@@ -1,7 +1,10 @@
 {{template "base/head" .}}
 {{template "base/navbar" .}}
-<div id="gogs-body" class="container">
+<div id="gogs-body" class="container text-center">
+    <p style="margin-top: 80px"><img src="/img/500.png" alt="404"/></p>
+    <hr/>
     <p>An error is occurred : {{.ErrorMsg}}</p>
+    <hr/>
     <p>Application Version: {{AppVer}}</p>
 </div>
 {{template "base/footer" .}}
\ No newline at end of file

From 1201c6a9b4897736650a4b416451aa521024b899 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Sun, 23 Mar 2014 16:31:13 +0800
Subject: [PATCH 21/38] bug fixed & more commits for push

---
 models/action.go |   2 +-
 models/models.go |   2 +-
 serve.go         | 157 +++++++++++++++++++++++++++++++++++++++--------
 update.go        |  22 ++++---
 4 files changed, 145 insertions(+), 38 deletions(-)

diff --git a/models/action.go b/models/action.go
index 107d4b1057..d338da24ba 100644
--- a/models/action.go
+++ b/models/action.go
@@ -28,7 +28,7 @@ type Action struct {
 	ActUserName string // Action user name.
 	RepoId      int64
 	RepoName    string
-	Content     string
+	Content     string    `xorm:"varchar(1000)"`
 	Created     time.Time `xorm:"created"`
 }
 
diff --git a/models/models.go b/models/models.go
index 8713ff2896..7f5f626f5e 100644
--- a/models/models.go
+++ b/models/models.go
@@ -91,5 +91,5 @@ func GetStatistic() (stats Statistic) {
 	stats.Counter.Watch, _ = orm.Count(new(Watch))
 	stats.Counter.Action, _ = orm.Count(new(Action))
 	stats.Counter.Access, _ = orm.Count(new(Access))
-	return stats
+	return
 }
diff --git a/serve.go b/serve.go
index 3ce8f9046c..b786c54356 100644
--- a/serve.go
+++ b/serve.go
@@ -5,14 +5,19 @@
 package main
 
 import (
+	"bytes"
+	"container/list"
 	"fmt"
+	"io"
 	"os"
 	"os/exec"
 	"strconv"
 	"strings"
 
 	"github.com/codegangsta/cli"
+	"github.com/qiniu/log"
 
+	"github.com/gogits/git"
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"
 )
@@ -39,12 +44,27 @@ gogs serv provide access auth for repositories`,
 	Flags:  []cli.Flag{},
 }
 
+func parseCmd(cmd string) (string, string) {
+	ss := strings.SplitN(cmd, " ", 2)
+	if len(ss) != 2 {
+		return "", ""
+	}
+
+	verb, args := ss[0], ss[1]
+	if verb == "git" {
+		ss = strings.SplitN(args, " ", 2)
+		args = ss[1]
+		verb = fmt.Sprintf("%s %s", verb, ss[0])
+	}
+	return verb, args
+}
+
 func In(b string, sl map[string]int) bool {
 	_, e := sl[b]
 	return e
 }
 
-func runServ(*cli.Context) {
+func runServ(k *cli.Context) {
 	base.NewConfigContext()
 	models.LoadModelsConfig()
 	models.NewEngine()
@@ -84,15 +104,20 @@ func runServ(*cli.Context) {
 		repoName = repoName[:len(repoName)-4]
 	}
 
-	os.Setenv("userName", user.Name)
-	os.Setenv("userId", strconv.Itoa(int(user.Id)))
+	//os.Setenv("userName", user.Name)
+	//os.Setenv("userId", strconv.Itoa(int(user.Id)))
 	repo, err := models.GetRepositoryByName(user, repoName)
+	var isExist bool = true
 	if err != nil {
-		println("Unavilable repository", err)
-		return
+		if err == models.ErrRepoNotExist {
+			isExist = false
+		} else {
+			println("Unavilable repository", err)
+			return
+		}
 	}
-	os.Setenv("repoId", strconv.Itoa(int(repo.Id)))
-	os.Setenv("repoName", repoName)
+	//os.Setenv("repoId", strconv.Itoa(int(repo.Id)))
+	//os.Setenv("repoName", repoName)
 
 	isWrite := In(verb, COMMANDS_WRITE)
 	isRead := In(verb, COMMANDS_READONLY)
@@ -130,12 +155,6 @@ func runServ(*cli.Context) {
 		return
 	}
 
-	isExist, err := models.IsRepositoryExist(user, repoName)
-	if err != nil {
-		println("Inernel error:", err.Error())
-		return
-	}
-
 	if !isExist {
 		if isRead {
 			println("Repository", user.Name+"/"+repoName, "is not exist")
@@ -149,28 +168,114 @@ func runServ(*cli.Context) {
 		}
 	}
 
+	rep, err := git.OpenRepository(models.RepoPath(user.Name, repoName))
+	if err != nil {
+		println(err.Error())
+		return
+	}
+
+	refs, err := rep.AllReferencesMap()
+	if err != nil {
+		println(err.Error())
+		return
+	}
+
 	gitcmd := exec.Command(verb, rRepo)
 	gitcmd.Dir = base.RepoRootPath
-	gitcmd.Stdout = os.Stdout
-	gitcmd.Stdin = os.Stdin
+
+	var s string
+	b := bytes.NewBufferString(s)
+
+	gitcmd.Stdout = io.MultiWriter(os.Stdout, b)
+	gitcmd.Stdin = io.MultiReader(os.Stdin, b)
 	gitcmd.Stderr = os.Stderr
 
 	if err = gitcmd.Run(); err != nil {
 		println("execute command error:", err.Error())
 	}
-}
 
-func parseCmd(cmd string) (string, string) {
-	ss := strings.SplitN(cmd, " ", 2)
-	if len(ss) != 2 {
-		return "", ""
+	// update
+	w, _ := os.Create("serve.log")
+	defer w.Close()
+	log.SetOutput(w)
+
+	var t = "ok refs/heads/"
+	var i int
+	var refname string
+	for {
+		l, err := b.ReadString('\n')
+		if err != nil {
+			break
+		}
+		i = i + 1
+		l = l[:len(l)-1]
+		idx := strings.Index(l, t)
+		if idx > 0 {
+			refname = l[idx+len(t):]
+		}
+	}
+	var ref *git.Reference
+	var ok bool
+
+	var l *list.List
+	//log.Info("----", refname, "-----")
+	if ref, ok = refs[refname]; !ok {
+		refs, err = rep.AllReferencesMap()
+		if err != nil {
+			println(err.Error())
+			return
+		}
+		if ref, ok = refs[refname]; !ok {
+			println("unknow reference name", refname)
+			return
+		}
+		l, err = ref.AllCommits()
+		if err != nil {
+			println(err.Error())
+			return
+		}
+	} else {
+		//log.Info("----", ref, "-----")
+		var last *git.Commit
+		//log.Info("00000", ref.Oid.String())
+		last, err = ref.LastCommit()
+		if err != nil {
+			println(err.Error())
+			return
+		}
+
+		ref2, err := rep.LookupReference(ref.Name)
+		if err != nil {
+			println(err.Error())
+			return
+		}
+
+		//log.Info("11111", ref2.Oid.String())
+		before, err := ref2.LastCommit()
+		if err != nil {
+			println(err.Error())
+			return
+		}
+		//log.Info("----", before.Id(), "-----", last.Id())
+		l = ref.CommitsBetween(before, last)
 	}
 
-	verb, args := ss[0], ss[1]
-	if verb == "git" {
-		ss = strings.SplitN(args, " ", 2)
-		args = ss[1]
-		verb = fmt.Sprintf("%s %s", verb, ss[0])
+	commits := make([][]string, 0)
+	for e := l.Back(); e != nil; e = e.Prev() {
+		commit := e.Value.(*git.Commit)
+		commits = append(commits, []string{commit.Id().String(), commit.Message()})
+	}
+
+	if err = models.CommitRepoAction(user.Id, user.Name,
+		repo.Id, ref.BranchName(), commits); err != nil {
+		log.Error("runUpdate.models.CommitRepoAction: %v", err, commits)
+	} else {
+		//log.Info("refname", refname)
+		//log.Info("Listen: %v", cmd)
+		//fmt.Println("...", cmd)
+
+		//runUpdate(k)
+		c := exec.Command("exec", "git", "update-server-info")
+		c.Run()
 	}
-	return verb, args
 }
diff --git a/update.go b/update.go
index baa433d75c..915e046530 100644
--- a/update.go
+++ b/update.go
@@ -4,16 +4,9 @@
 
 package main
 
-import (
-	"os"
-	"strconv"
+import "github.com/codegangsta/cli"
 
-	"github.com/codegangsta/cli"
-
-	"github.com/gogits/git"
-	"github.com/gogits/gogs/models"
-	"github.com/gogits/gogs/modules/log"
-)
+//"github.com/gogits/gogs/modules/log"
 
 var CmdUpdate = cli.Command{
 	Name:  "update",
@@ -26,6 +19,9 @@ gogs serv provide access auth for repositories`,
 
 // for command: ./gogs update
 func runUpdate(*cli.Context) {
+	/*w, _ := os.Create("update.log")
+	log.SetOutput(w)
+
 	userName := os.Getenv("userName")
 	userId := os.Getenv("userId")
 	repoId := os.Getenv("repoId")
@@ -35,16 +31,19 @@ func runUpdate(*cli.Context) {
 
 	repo, err := git.OpenRepository(f)
 	if err != nil {
+		log.Error("runUpdate.Open repoId: %v", err)
 		return
 	}
 
 	ref, err := repo.LookupReference("HEAD")
 	if err != nil {
+		log.Error("runUpdate.Ref repoId: %v", err)
 		return
 	}
 
 	lastCommit, err := repo.LookupCommit(ref.Oid)
 	if err != nil {
+		log.Error("runUpdate.Commit repoId: %v", err)
 		return
 	}
 
@@ -63,5 +62,8 @@ func runUpdate(*cli.Context) {
 	if err = models.CommitRepoAction(int64(sUserId), userName,
 		int64(sRepoId), repoName, commits); err != nil {
 		log.Error("runUpdate.models.CommitRepoAction: %v", err)
-	}
+	} else {
+		l := exec.Command("exec", "git", "update-server-info")
+		l.Run()
+	}*/
 }

From 4adb024715bdb576d7acb2900d933e7196abee4c Mon Sep 17 00:00:00 2001
From: slene <vslene@gmail.com>
Date: Sun, 23 Mar 2014 16:40:51 +0800
Subject: [PATCH 22/38] minor fix markdown setting

---
 bee.json                 | 3 ++-
 modules/base/markdown.go | 4 ++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/bee.json b/bee.json
index 19efcbc205..4f7f7a771c 100644
--- a/bee.json
+++ b/bee.json
@@ -13,7 +13,8 @@
 		"others": [
 			"modules",
 			"$GOPATH/src/github.com/gogits/binding",
-			"$GOPATH/src/github.com/gogits/git"
+			"$GOPATH/src/github.com/gogits/git",
+			"$GOPATH/src/github.com/gogits/gfm"
 		]
 	},
 	"cmd_args": [
diff --git a/modules/base/markdown.go b/modules/base/markdown.go
index 2273cd772f..05ce0c833c 100644
--- a/modules/base/markdown.go
+++ b/modules/base/markdown.go
@@ -72,7 +72,7 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
 
 func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
 	htmlFlags := 0
-	htmlFlags |= gfm.HTML_USE_XHTML
+	// htmlFlags |= gfm.HTML_USE_XHTML
 	// htmlFlags |= gfm.HTML_USE_SMARTYPANTS
 	// htmlFlags |= gfm.HTML_SMARTYPANTS_FRACTIONS
 	// htmlFlags |= gfm.HTML_SMARTYPANTS_LATEX_DASHES
@@ -81,7 +81,7 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
 	htmlFlags |= gfm.HTML_SKIP_SCRIPT
 	htmlFlags |= gfm.HTML_GITHUB_BLOCKCODE
 	htmlFlags |= gfm.HTML_OMIT_CONTENTS
-	htmlFlags |= gfm.HTML_COMPLETE_PAGE
+	// htmlFlags |= gfm.HTML_COMPLETE_PAGE
 	renderer := &CustomRender{
 		Renderer:  gfm.HtmlRenderer(htmlFlags, "", ""),
 		urlPrefix: urlPrefix,

From d594bb386f92455079e56b48f5add16b05609dea Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Sun, 23 Mar 2014 16:51:43 +0800
Subject: [PATCH 23/38] log import fixed

---
 serve.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/serve.go b/serve.go
index 54296049d5..d4ce138c1c 100644
--- a/serve.go
+++ b/serve.go
@@ -15,7 +15,7 @@ import (
 	"strings"
 
 	"github.com/codegangsta/cli"
-	"github.com/qiniu/log"
+	"github.com/gogits/gogs/modules/log"
 
 	"github.com/gogits/git"
 	"github.com/gogits/gogs/models"
@@ -195,9 +195,9 @@ func runServ(k *cli.Context) {
 	}
 
 	// update
-	w, _ := os.Create("serve.log")
-	defer w.Close()
-	log.SetOutput(w)
+	//w, _ := os.Create("serve.log")
+	//defer w.Close()
+	//log.SetOutput(w)
 
 	var t = "ok refs/heads/"
 	var i int

From 21a138a75a379c1c8d1142b6bdf3f855c0361634 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Sun, 23 Mar 2014 17:10:09 +0800
Subject: [PATCH 24/38] bug fxied

---
 serve.go | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/serve.go b/serve.go
index d4ce138c1c..16df38f420 100644
--- a/serve.go
+++ b/serve.go
@@ -104,8 +104,6 @@ func runServ(k *cli.Context) {
 		repoName = repoName[:len(repoName)-4]
 	}
 
-	//os.Setenv("userName", user.Name)
-	//os.Setenv("userId", strconv.Itoa(int(user.Id)))
 	repo, err := models.GetRepositoryByName(user.Id, repoName)
 	var isExist bool = true
 	if err != nil {
@@ -116,8 +114,6 @@ func runServ(k *cli.Context) {
 			return
 		}
 	}
-	//os.Setenv("repoId", strconv.Itoa(int(repo.Id)))
-	//os.Setenv("repoName", repoName)
 
 	isWrite := In(verb, COMMANDS_WRITE)
 	isRead := In(verb, COMMANDS_READONLY)
@@ -187,13 +183,18 @@ func runServ(k *cli.Context) {
 	b := bytes.NewBufferString(s)
 
 	gitcmd.Stdout = io.MultiWriter(os.Stdout, b)
-	gitcmd.Stdin = io.MultiReader(os.Stdin, b)
+	//gitcmd.Stdin = io.MultiReader(os.Stdin, b)
+	gitcmd.Stdin = os.Stdin
 	gitcmd.Stderr = os.Stderr
 
 	if err = gitcmd.Run(); err != nil {
 		println("execute command error:", err.Error())
 	}
 
+	if !strings.HasPrefix(cmd, "git-receive-pack") {
+		return
+	}
+
 	// update
 	//w, _ := os.Create("serve.log")
 	//defer w.Close()

From c4287bafabb96f3970bf629ff225c48112d008d9 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Sun, 23 Mar 2014 17:13:22 +0800
Subject: [PATCH 25/38] bug fixed

---
 serve.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/serve.go b/serve.go
index 16df38f420..2f7a9d6052 100644
--- a/serve.go
+++ b/serve.go
@@ -227,7 +227,7 @@ func runServ(k *cli.Context) {
 			return
 		}
 		if ref, ok = refs[refname]; !ok {
-			println("unknow reference name", refname)
+			println("unknow reference name -", refname, "-")
 			return
 		}
 		l, err = ref.AllCommits()

From 406afa3c32eab4782291ca01eea273f2df25748d Mon Sep 17 00:00:00 2001
From: slene <vslene@gmail.com>
Date: Sun, 23 Mar 2014 17:22:14 +0800
Subject: [PATCH 26/38] markdown img max-width

---
 public/css/markdown.css | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/public/css/markdown.css b/public/css/markdown.css
index a810fa3ce7..9f4024ba89 100644
--- a/public/css/markdown.css
+++ b/public/css/markdown.css
@@ -175,6 +175,10 @@
   margin-bottom: 0;
 }
 
+.markdown img {
+  max-width: 100%;
+}
+
 .markdown .btn {
   color: #fff;
 }

From c9e830cefb3b19593e7de05c785efdd93a0982d6 Mon Sep 17 00:00:00 2001
From: FuXiaoHei <fuxiaohei@hexiaz.com>
Date: Sun, 23 Mar 2014 17:36:12 +0800
Subject: [PATCH 27/38] fix dropdown close bug

---
 public/js/app.js        | 2 +-
 templates/repo/nav.tmpl | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/public/js/app.js b/public/js/app.js
index df755727b5..e0f92854f4 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -98,7 +98,7 @@ var Gogits = {
     };
     // fix dropdown inside click
     Gogits.initDropDown = function(){
-        $('.dropdown-menu').on('click','a,button,input,select',function(e){
+        $('.dropdown-menu.no-propagation').on('click',function(e){
             e.stopPropagation();
         });
     };
diff --git a/templates/repo/nav.tmpl b/templates/repo/nav.tmpl
index cf1b7d0389..4d939e2504 100644
--- a/templates/repo/nav.tmpl
+++ b/templates/repo/nav.tmpl
@@ -18,7 +18,7 @@
                     <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
                         <span class="caret"></span>
                     </button>
-                    <div class="dropdown-menu clone-group-btn dropdown-menu-right">
+                    <div class="dropdown-menu clone-group-btn dropdown-menu-right no-propagation">
                         <div class="input-group">
                             <span class="input-group-btn">
                                 <button class="btn btn-default" data-link="{{.CloneLink.SSH}}" type="button">SSH</button>

From 0119675480505a6534ee0fc98c91e3bd2b8cdaaf Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Sun, 23 Mar 2014 17:40:50 +0800
Subject: [PATCH 28/38] bug fixed

---
 models/action.go | 4 +++-
 serve.go         | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/models/action.go b/models/action.go
index cfb124363c..4f78deb30e 100644
--- a/models/action.go
+++ b/models/action.go
@@ -30,6 +30,7 @@ type Action struct {
 	ActUserName string // Action user name.
 	RepoId      int64
 	RepoName    string
+	RefName     string
 	Content     string    `xorm:"TEXT"`
 	Created     time.Time `xorm:"created"`
 }
@@ -52,7 +53,7 @@ func (a Action) GetContent() string {
 
 // CommitRepoAction records action for commit repository.
 func CommitRepoAction(userId int64, userName string,
-	repoId int64, repoName string, commits [][]string) error {
+	repoId int64, repoName string, refName string, commits [][]string) error {
 	bs, err := json.Marshal(commits)
 	if err != nil {
 		return err
@@ -78,6 +79,7 @@ func CommitRepoAction(userId int64, userName string,
 			Content:     string(bs),
 			RepoId:      repoId,
 			RepoName:    repoName,
+			RefName:     refName,
 		})
 		return err
 	}
diff --git a/serve.go b/serve.go
index 2f7a9d6052..bb9165dc06 100644
--- a/serve.go
+++ b/serve.go
@@ -268,7 +268,7 @@ func runServ(k *cli.Context) {
 	}
 
 	if err = models.CommitRepoAction(user.Id, user.Name,
-		repo.Id, ref.BranchName(), commits); err != nil {
+		repo.Id, repoName, refname, commits); err != nil {
 		log.Error("runUpdate.models.CommitRepoAction: %v", err, commits)
 	} else {
 		//log.Info("refname", refname)

From d6619cfe15885b7004585bf2b96933a7a3e51f39 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Sun, 23 Mar 2014 17:51:12 +0800
Subject: [PATCH 29/38] limit max commits view number on activity

---
 serve.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/serve.go b/serve.go
index bb9165dc06..812c37bb58 100644
--- a/serve.go
+++ b/serve.go
@@ -262,9 +262,13 @@ func runServ(k *cli.Context) {
 	}
 
 	commits := make([][]string, 0)
+	var maxCommits = 5
 	for e := l.Back(); e != nil; e = e.Prev() {
 		commit := e.Value.(*git.Commit)
 		commits = append(commits, []string{commit.Id().String(), commit.Message()})
+		if len(commits) >= maxCommits {
+			break
+		}
 	}
 
 	if err = models.CommitRepoAction(user.Id, user.Name,

From 24630e0c9b92bcd9fdeb07ce15c3dd2cfc459a52 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Sun, 23 Mar 2014 18:00:09 +0800
Subject: [PATCH 30/38] improved activity

---
 models/action.go | 7 ++++++-
 serve.go         | 4 ++--
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/models/action.go b/models/action.go
index 4f78deb30e..ca2ff3cbf1 100644
--- a/models/action.go
+++ b/models/action.go
@@ -51,9 +51,14 @@ func (a Action) GetContent() string {
 	return a.Content
 }
 
+type PushCommits struct {
+	Len     int
+	Commits [][]string
+}
+
 // CommitRepoAction records action for commit repository.
 func CommitRepoAction(userId int64, userName string,
-	repoId int64, repoName string, refName string, commits [][]string) error {
+	repoId int64, repoName string, refName string, commits *PushCommits) error {
 	bs, err := json.Marshal(commits)
 	if err != nil {
 		return err
diff --git a/serve.go b/serve.go
index 812c37bb58..71abf5fdd6 100644
--- a/serve.go
+++ b/serve.go
@@ -262,7 +262,7 @@ func runServ(k *cli.Context) {
 	}
 
 	commits := make([][]string, 0)
-	var maxCommits = 5
+	var maxCommits = 3
 	for e := l.Back(); e != nil; e = e.Prev() {
 		commit := e.Value.(*git.Commit)
 		commits = append(commits, []string{commit.Id().String(), commit.Message()})
@@ -272,7 +272,7 @@ func runServ(k *cli.Context) {
 	}
 
 	if err = models.CommitRepoAction(user.Id, user.Name,
-		repo.Id, repoName, refname, commits); err != nil {
+		repo.Id, repoName, refname, &models.PushCommits{l.Len(), commits}); err != nil {
 		log.Error("runUpdate.models.CommitRepoAction: %v", err, commits)
 	} else {
 		//log.Info("refname", refname)

From 1eb078d0a8c5424de9512d810ab2fbf21f59ff78 Mon Sep 17 00:00:00 2001
From: Unknown <joe2010xtmf@163.com>
Date: Sun, 23 Mar 2014 06:27:01 -0400
Subject: [PATCH 31/38] Fix action

---
 README.md                   |  6 ++--
 README_ZH.md                |  2 +-
 models/action.go            | 12 ++++----
 models/issue.go             | 26 ++++++++--------
 modules/base/tool.go        | 23 ++++++++++++---
 routers/repo/repo.go        | 59 +++++++++++++++++++++----------------
 serve.go                    |  2 +-
 templates/repo/nav.tmpl     |  2 +-
 templates/repo/setting.tmpl | 16 ++++------
 web.go                      |  2 +-
 10 files changed, 85 insertions(+), 65 deletions(-)

diff --git a/README.md b/README.md
index 504c21975b..e947d7739a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-Gogs - Go Git Service [![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"],