Jelajahi Sumber

add tree view

Lunny Xiao 11 tahun lalu
induk
melakukan
b27e8e87f8

+ 2 - 1
.gitignore

@@ -4,4 +4,5 @@ gogs
 .DS_Store
 *.db
 *.log
-custom/
+custom/
+.vendor/

+ 4 - 1
README.md

@@ -3,6 +3,8 @@ Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0b
 
 Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language, it currently supports Linux and Max OS X, but Windows has **NOT** supported yet due to installation problem with [libgit2](http://libgit2.github.com/) in Windows.
 
+##### Current version: 0.0.7 Alpha
+
 ## 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.
@@ -15,7 +17,8 @@ Please see [Wiki](https://github.com/gogits/gogs/wiki) for project design, devel
 
 - SSH protocal support.
 - Register/delete account.
-- Create public repository.
+- Create/delete public repository.
+- User/repository home page.
 - Git repository manipulation.
 
 ## Installation

+ 2 - 2
conf/app.ini

@@ -3,8 +3,8 @@ RUN_USER = lunny
 
 [repository]
 ROOT = /Users/lunny/git/gogs-repositories
-LANG_IGNS=Google Go
-LICENSES=Apache v2 License
+LANG_IGNS=Google Go|C
+LICENSES=Apache v2 License|BSD (3-Clause) License
 
 [server]
 HTTP_ADDR = 

+ 18 - 0
conf/gitignore/C

@@ -0,0 +1,18 @@
+# Object files
+*.o
+*.ko
+
+# Libraries
+*.lib
+*.a
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app

+ 27 - 0
conf/license/BSD (3-Clause) License

@@ -0,0 +1,27 @@
+Copyright (c) 2014
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the {organization} nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 1 - 1
gogs.go

@@ -20,7 +20,7 @@ import (
 // Test that go1.1 tag above is included in builds. main.go refers to this definition.
 const go11tag = true
 
-const APP_VER = "0.0.6.0313"
+const APP_VER = "0.0.7.0314"
 
 func init() {
 	runtime.GOMAXPROCS(runtime.NumCPU())

+ 30 - 4
models/repo.go

@@ -9,6 +9,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
+	"path"
 	"path/filepath"
 	"strings"
 	"time"
@@ -270,6 +271,7 @@ type RepoFile struct {
 	Id      *git.Oid
 	Type    int
 	Name    string
+	Path    string
 	Message string
 	Created time.Time
 }
@@ -282,7 +284,7 @@ func (f *RepoFile) IsDir() bool {
 	return f.Type == git.FilemodeTree
 }
 
-func GetReposFiles(userName, reposName, treeName, rpath string) ([]*RepoFile, error) {
+func GetReposFiles(userName, reposName, branchName, rpath string) ([]*RepoFile, error) {
 	f := RepoPath(userName, reposName)
 	repo, err := git.OpenRepository(f)
 	if err != nil {
@@ -299,8 +301,28 @@ func GetReposFiles(userName, reposName, treeName, rpath string) ([]*RepoFile, er
 	if err != nil {
 		return nil, err
 	}
-	var i uint64 = 0
-	for ; i < tree.EntryCount(); i++ {
+	//var i uint64 = 0
+	if rpath != "" {
+		rpath = rpath + "/"
+	}
+	fmt.Println("...", rpath, "...")
+
+	tree.Walk(func(dirname string, entry *git.TreeEntry) int {
+		if dirname == rpath {
+			fmt.Println("====", dirname, "==", entry.Name)
+			repofiles = append(repofiles, &RepoFile{
+				entry.Id,
+				entry.Filemode,
+				entry.Name,
+				path.Join(dirname, entry.Name),
+				lastCommit.Message(),
+				lastCommit.Committer().When,
+			})
+		}
+		return 0
+	})
+
+	/*for ; i < tree.EntryCount(); i++ {
 		entry := tree.EntryByIndex(i)
 
 		repofiles = append(repofiles, &RepoFile{
@@ -310,7 +332,7 @@ func GetReposFiles(userName, reposName, treeName, rpath string) ([]*RepoFile, er
 			lastCommit.Message(),
 			lastCommit.Committer().When,
 		})
-	}
+	}*/
 
 	return repofiles, nil
 }
@@ -354,6 +376,10 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
 		session.Rollback()
 		return err
 	}
+	if _, err := session.Delete(&Access{UserName: userName, RepoName: repo.Name}); err != nil {
+		session.Rollback()
+		return err
+	}
 	if _, err = session.Exec("update user set num_repos = num_repos - 1 where id = ?", userId); err != nil {
 		session.Rollback()
 		return err

+ 5 - 1
models/user.go

@@ -48,7 +48,10 @@ type User struct {
 	NumFollowings int
 	NumStars      int
 	NumRepos      int
-	Avatar        string    `xorm:"varchar(2048) not null"`
+	Avatar        string `xorm:"varchar(2048) not null"`
+	AvatarEmail   string `xorm:"not null"`
+	Location      string
+	Website       string
 	Created       time.Time `xorm:"created"`
 	Updated       time.Time `xorm:"updated"`
 }
@@ -104,6 +107,7 @@ func RegisterUser(user *User) (err error) {
 
 	user.LowerName = strings.ToLower(user.Name)
 	user.Avatar = base.EncodeMd5(user.Email)
+	user.AvatarEmail = user.Email
 	if err = user.EncodePasswd(); err != nil {
 		return err
 	}

+ 2 - 7
modules/auth/auth.go

@@ -23,7 +23,7 @@ type Form interface {
 }
 
 type RegisterForm struct {
-	UserName     string `form:"username" binding:"Required;AlphaDash;MinSize(5);MaxSize(30)"`
+	UserName     string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
 	Email        string `form:"email" binding:"Required;Email;MaxSize(50)"`
 	Password     string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
 	RetypePasswd string `form:"retypepasswd"`
@@ -59,7 +59,7 @@ func (f *RegisterForm) Validate(errors *binding.Errors, req *http.Request, conte
 }
 
 type LogInForm struct {
-	UserName string `form:"username" binding:"Required;AlphaDash;MinSize(5);MaxSize(30)"`
+	UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
 	Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
 }
 
@@ -90,11 +90,6 @@ func (f *LogInForm) Validate(errors *binding.Errors, req *http.Request, context
 	validate(errors, data, f)
 }
 
-type FeedsForm struct {
-	UserId int64 `form:"userid" binding:"Required"`
-	Offset int64 `form:"offset"`
-}
-
 func getMinMaxSize(field reflect.StructField) string {
 	for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
 		if strings.HasPrefix(rule, "MinSize(") || strings.HasPrefix(rule, "MaxSize(") {

+ 78 - 0
modules/auth/user.go

@@ -5,10 +5,15 @@
 package auth
 
 import (
+	"net/http"
+	"reflect"
+
 	"github.com/codegangsta/martini"
 	"github.com/martini-contrib/render"
 	"github.com/martini-contrib/sessions"
 
+	"github.com/gogits/binding"
+
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/log"
@@ -83,3 +88,76 @@ func SignOutRequire() martini.Handler {
 		}
 	}
 }
+
+type FeedsForm struct {
+	UserId int64 `form:"userid" binding:"Required"`
+	Offset int64 `form:"offset"`
+}
+
+type UpdateProfileForm struct {
+	Email    string `form:"email" binding:"Required;Email;MaxSize(50)"`
+	Website  string `form:"website" binding:"MaxSize(50)"`
+	Location string `form:"location" binding:"MaxSize(50)"`
+	Avatar   string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
+}
+
+func (f *UpdateProfileForm) Name(field string) string {
+	names := map[string]string{
+		"Email":    "Email address",
+		"Website":  "Website",
+		"Location": "Location",
+		"Avatar":   "Gravatar Email",
+	}
+	return names[field]
+}
+
+func (f *UpdateProfileForm) 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
+
+	if len(errors.Overall) > 0 {
+		for _, err := range errors.Overall {
+			log.Error("UpdateProfileForm.Validate: %v", err)
+		}
+		return
+	}
+
+	validate(errors, data, f)
+}
+
+type UpdatePasswdForm struct {
+	OldPasswd    string `form:"oldpasswd" binding:"Required;MinSize(6);MaxSize(30)"`
+	NewPasswd    string `form:"newpasswd" binding:"Required;MinSize(6);MaxSize(30)"`
+	RetypePasswd string `form:"retypepasswd"`
+}
+
+func (f *UpdatePasswdForm) Name(field string) string {
+	names := map[string]string{
+		"OldPasswd":    "Old password",
+		"NewPasswd":    "New password",
+		"RetypePasswd": "Re-type password",
+	}
+	return names[field]
+}
+
+func (f *UpdatePasswdForm) 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
+
+	if len(errors.Overall) > 0 {
+		for _, err := range errors.Overall {
+			log.Error("UpdatePasswdForm.Validate: %v", err)
+		}
+		return
+	}
+
+	validate(errors, data, f)
+}

+ 63 - 0
modules/base/tool.go

@@ -7,6 +7,8 @@ package base
 import (
 	"crypto/md5"
 	"encoding/hex"
+	"fmt"
+	"time"
 )
 
 // Encode string to md5 hex value
@@ -15,3 +17,64 @@ func EncodeMd5(str string) string {
 	m.Write([]byte(str))
 	return hex.EncodeToString(m.Sum(nil))
 }
+
+// Seconds-based time units
+const (
+	Minute = 60
+	Hour   = 60 * Minute
+	Day    = 24 * Hour
+	Week   = 7 * Day
+	Month  = 30 * Day
+	Year   = 12 * Month
+)
+
+// TimeSince calculates the time interval and generate user-friendly string.
+func TimeSince(then time.Time) string {
+	now := time.Now()
+
+	lbl := "ago"
+	diff := now.Unix() - then.Unix()
+	if then.After(now) {
+		lbl = "from now"
+		diff = then.Unix() - now.Unix()
+	}
+
+	switch {
+
+	case diff <= 0:
+		return "now"
+	case diff <= 2:
+		return fmt.Sprintf("1 second %s", lbl)
+	case diff < 1*Minute:
+		return fmt.Sprintf("%d seconds %s", diff, lbl)
+
+	case diff < 2*Minute:
+		return fmt.Sprintf("1 minute %s", lbl)
+	case diff < 1*Hour:
+		return fmt.Sprintf("%d minutes %s", diff/Minute, lbl)
+
+	case diff < 2*Hour:
+		return fmt.Sprintf("1 hour %s", lbl)
+	case diff < 1*Day:
+		return fmt.Sprintf("%d hours %s", diff/Hour, lbl)
+
+	case diff < 2*Day:
+		return fmt.Sprintf("1 day %s", lbl)
+	case diff < 1*Week:
+		return fmt.Sprintf("%d days %s", diff/Day, lbl)
+
+	case diff < 2*Week:
+		return fmt.Sprintf("1 week %s", lbl)
+	case diff < 1*Month:
+		return fmt.Sprintf("%d weeks %s", diff/Week, lbl)
+
+	case diff < 2*Month:
+		return fmt.Sprintf("1 month %s", lbl)
+	case diff < 1*Year:
+		return fmt.Sprintf("%d months %s", diff/Month, lbl)
+
+	case diff < 18*Month:
+		return fmt.Sprintf("1 year %s", lbl)
+	}
+	return then.String()
+}

+ 12 - 0
modules/log/log.go

@@ -6,7 +6,13 @@
 package log
 
 import (
+	"fmt"
+
+	"github.com/martini-contrib/render"
+
 	"github.com/gogits/logs"
+
+	"github.com/gogits/gogs/modules/base"
 )
 
 var logger *logs.BeeLogger
@@ -35,3 +41,9 @@ func Warn(format string, v ...interface{}) {
 func Critical(format string, v ...interface{}) {
 	logger.Critical(format, v...)
 }
+
+func Handle(status int, title string, data base.TmplData, r render.Render, err error) {
+	data["ErrorMsg"] = err
+	Error("%s: %v", title, err)
+	r.HTML(status, fmt.Sprintf("status/%d", status), data)
+}

+ 71 - 8
public/css/gogs.css

@@ -137,6 +137,11 @@ body {
     margin-top: 50px;
 }
 
+#gogs-body .btn-default {
+    background-color: #FFF;
+    background-image: linear-gradient(to bottom, #FFF 0, #FAFAFA 100%);
+}
+
 #gogs-body-nav {
     margin-top: 52px;
     margin-bottom: -50px;
@@ -269,7 +274,8 @@ body {
 
 /* gogits user setting */
 
-#gogs-user-setting-nav > h4, #gogs-user-setting-container > h4, #gogs-ssh-keys > h4, #gogs-user-delete > h4 ,#gogs-repo-setting-container .tab-pane > h4{
+#gogs-user-setting-nav > h4, #gogs-user-setting-container > h4, #gogs-user-setting-container > div > h4,
+#gogs-ssh-keys > h4, #gogs-user-delete > h4, #gogs-repo-setting-container .tab-pane > h4 {
     padding-bottom: 18px;
     margin-bottom: 18px;
     border-bottom: 1px solid #CCC;
@@ -380,6 +386,7 @@ body {
 }
 
 #gogs-feed-right .repo-panel .list-group-item:hover {
+    background-color: #eafffd;
     background-color: rgba(65, 131, 196, 0.1);
 }
 
@@ -391,6 +398,12 @@ body {
 
 /* gogits repo single page */
 
+#gogs-body-nav.gogs-repo-nav {
+    padding-top: 16px;
+    padding-bottom: 30px;
+    height: auto;
+}
+
 .gogs-repo-nav h3 .fa {
     color: #BBB;
 }
@@ -429,32 +442,82 @@ body {
     margin-bottom: 4px;
 }
 
-
-#gogs-repo-toolbar{
+#gogs-repo-toolbar {
     margin-top: 51px;
     margin-bottom: -50px;
     border-bottom: 1px solid #BBB;
     background-color: #FFF;
     height: 40px;
+    font-size: 14px;
 }
 
-#gogs-repo-toolbar .navbar-default{
+#gogs-repo-toolbar .navbar-default {
     border: none;
     height: 39px;
 }
 
-#gogs-repo-toolbar .nav > li > a{
+#gogs-repo-toolbar .nav > li > a {
     height: 39px;
 }
 
-#gogs-repo-toolbar .navbar-toolbar.navbar-default .navbar-nav>.active>a:after{
+#gogs-repo-toolbar .navbar-toolbar.navbar-default .navbar-nav > .active > a:after {
     border-bottom-color: #999;
 }
 
-#gogs-repo-toolbar .navbar.nav-toolbar{
+#gogs-repo-toolbar .navbar.nav-toolbar {
     margin-bottom: 0;
 }
 
-#gogs-repo-toolbar .navbar-collapse{
+#gogs-repo-toolbar .navbar-collapse {
     padding: 0;
+}
+
+/* #gogs-source */
+
+#gogs-source-toolbar:after {
+    clear: both;
+}
+
+#gogs-source-toolbar .branch-switch {
+    display: inline-block;
+}
+
+#gogs-source-toolbar .breadcrumb {
+    margin: 0 .5em;
+    font-size: 16px;
+    vertical-align: middle;
+    display: inline-block;
+    background-color: transparent;
+}
+
+#gogs-source-table {
+    margin-top: 1.5em;
+    font-size: 14px;
+}
+
+#gogs-source-table .fa{
+    font-size: 15px;
+    width: 16px;
+    text-align: center;
+    color: #666;
+}
+
+#gogs-source-table .name{
+    width: 160px;
+}
+
+#gogs-source-table .size{
+    width: 80px;
+}
+
+#gogs-source-table .date{
+    width: 120px;
+}
+
+#gogs-source-table .is-dir .name {
+    font-weight: bold;
+}
+
+#gogs-source-table.table-hover > tbody > tr:hover > td {
+    background-color: #FEFEFE;
 }

+ 0 - 1
public/js/app.js

@@ -78,7 +78,6 @@ function initRegister() {
             rules: {
                 "username": {
                     required: true,
-                    minlength: 5,
                     maxlength: 30
                 },
                 "email": {

+ 7 - 12
routers/repo/repo.go

@@ -5,9 +5,10 @@
 package repo
 
 import (
+	"net/http"
+
 	"github.com/martini-contrib/render"
 	"github.com/martini-contrib/sessions"
-	"net/http"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
@@ -46,7 +47,7 @@ func Create(form auth.CreateRepoForm, req *http.Request, r render.Render, data b
 	if err == nil {
 		if _, err = models.CreateRepository(user,
 			form.RepoName, form.Description, form.Language, form.License,
-			form.Visibility == "private", form.InitReadme == "true"); err == nil {
+			form.Visibility == "private", form.InitReadme == "on"); err == nil {
 			if err == nil {
 				data["RepoName"] = user.Name + "/" + form.RepoName
 				r.HTML(200, "repo/created", data)
@@ -63,9 +64,7 @@ func Create(form auth.CreateRepoForm, req *http.Request, r render.Render, data b
 		return
 	}
 
-	data["ErrorMsg"] = err
-	log.Error("repo.Create: %v", err)
-	r.HTML(200, "base/error", data)
+	log.Handle(200, "repo.Create", data, r, err)
 }
 
 func Delete(form auth.DeleteRepoForm, req *http.Request, r render.Render, data base.TmplData, session sessions.Session) {
@@ -77,13 +76,11 @@ func Delete(form auth.DeleteRepoForm, req *http.Request, r render.Render, data b
 	}
 
 	if err := models.DeleteRepository(form.UserId, form.RepoId, form.UserName); err != nil {
-		data["ErrorMsg"] = err
-		log.Error("repo.Delete: %v", err)
-		r.HTML(200, "base/error", data)
+		log.Handle(200, "repo.Delete", data, r, err)
 		return
 	}
 
-	r.Redirect("/", 200)
+	r.Redirect("/", 302)
 }
 
 func List(req *http.Request, r render.Render, data base.TmplData, session sessions.Session) {
@@ -96,9 +93,7 @@ func List(req *http.Request, r render.Render, data base.TmplData, session sessio
 	data["Title"] = "Repositories"
 	repos, err := models.GetRepositories(u)
 	if err != nil {
-		data["ErrorMsg"] = err
-		log.Error("repo.List: %v", err)
-		r.HTML(200, "base/error", data)
+		log.Handle(200, "repo.List", data, r, err)
 		return
 	}
 

+ 18 - 5
routers/repo/single.go

@@ -1,6 +1,7 @@
 package repo
 
 import (
+	"strings"
 	"github.com/codegangsta/martini"
 	"github.com/martini-contrib/render"
 
@@ -13,15 +14,27 @@ func Single(params martini.Params, r render.Render, data base.TmplData) {
 	if !data["IsRepositoryValid"].(bool) {
 		return
 	}
-
-	files, err := models.GetReposFiles(params["username"], params["reponame"], "HEAD", "/")
+	if params["branchname"] == "" {
+		params["branchname"] = "master"
+	}
+	treename := params["_1"]
+	files, err := models.GetReposFiles(params["username"], params["reponame"],
+		params["branchname"], treename)
 	if err != nil {
-		data["ErrorMsg"] = err
-		log.Error("repo.List: %v", err)
-		r.HTML(200, "base/error", data)
+		log.Handle(200, "repo.Single", data, r, err)
 		return
 	}
 
+	data["Username"] = params["username"]
+	data["Reponame"] = params["reponame"]
+	data["Branchname"] = params["branchname"]
+	treenames := strings.Split(treename, "/")
+	Paths := make([]string, 0)
+	for i, _ := range treenames {
+		Paths = append(Paths, strings.Join(treenames[0:i+1], "/"))
+	}
+	data["Paths"] = Paths
+	data["Treenames"] = treenames
 	data["IsRepoToolbarSource"] = true
 	data["Files"] = files
 

+ 75 - 1
routers/user/setting.go

@@ -17,12 +17,72 @@ import (
 	"github.com/gogits/gogs/modules/log"
 )
 
-func Setting(r render.Render, data base.TmplData, session sessions.Session) {
+func Setting(form auth.UpdateProfileForm, r render.Render, data base.TmplData, req *http.Request, session sessions.Session) {
 	data["Title"] = "Setting"
 	data["PageIsUserSetting"] = true
+
+	user := auth.SignedInUser(session)
+	data["Owner"] = user
+
+	if req.Method == "GET" {
+		r.HTML(200, "user/setting", data)
+		return
+	}
+
+	if hasErr, ok := data["HasError"]; ok && hasErr.(bool) {
+		r.HTML(200, "user/setting", data)
+		return
+	}
+
+	user.Email = form.Email
+	user.Website = form.Website
+	user.Location = form.Location
+	user.Avatar = base.EncodeMd5(form.Avatar)
+	user.AvatarEmail = form.Avatar
+	if err := models.UpdateUser(user); err != nil {
+		log.Handle(200, "setting.Setting", data, r, err)
+		return
+	}
+
+	data["IsSuccess"] = true
 	r.HTML(200, "user/setting", data)
 }
 
+func SettingPassword(form auth.UpdatePasswdForm, r render.Render, data base.TmplData, session sessions.Session, req *http.Request) {
+	data["Title"] = "Password"
+	data["PageIsUserSetting"] = true
+
+	if req.Method == "GET" {
+		r.HTML(200, "user/password", data)
+		return
+	}
+
+	user := auth.SignedInUser(session)
+	newUser := &models.User{Passwd: form.NewPasswd}
+	if err := newUser.EncodePasswd(); err != nil {
+		log.Handle(200, "setting.SettingPassword", data, r, err)
+		return
+	}
+
+	if user.Passwd != newUser.Passwd {
+		data["HasError"] = true
+		data["ErrorMsg"] = "Old password is not correct"
+	} else if form.NewPasswd != form.RetypePasswd {
+		data["HasError"] = true
+		data["ErrorMsg"] = "New password and re-type password are not same"
+	} else {
+		user.Passwd = newUser.Passwd
+		if err := models.UpdateUser(user); err != nil {
+			log.Handle(200, "setting.SettingPassword", data, r, err)
+			return
+		}
+		data["IsSuccess"] = true
+	}
+
+	data["Owner"] = user
+	r.HTML(200, "user/password", data)
+}
+
 func SettingSSHKeys(form auth.AddSSHKeyForm, r render.Render, data base.TmplData, req *http.Request, session sessions.Session) {
 	data["Title"] = "SSH Keys"
 
@@ -94,3 +154,17 @@ func SettingSSHKeys(form auth.AddSSHKeyForm, r render.Render, data base.TmplData
 	data["Keys"] = keys
 	r.HTML(200, "user/publickey", data)
 }
+
+func SettingNotification(r render.Render, data base.TmplData) {
+	// todo user setting notification
+	data["Title"] = "Notification"
+	data["PageIsUserSetting"] = true
+	r.HTML(200, "user/notification", data)
+}
+
+func SettingSecurity(r render.Render, data base.TmplData) {
+	// todo user setting security
+	data["Title"] = "Security"
+	data["PageIsUserSetting"] = true
+	r.HTML(200, "user/security", data)
+}

+ 6 - 17
routers/user/user.go

@@ -22,9 +22,7 @@ func Dashboard(r render.Render, data base.TmplData, session sessions.Session) {
 	data["PageIsUserDashboard"] = true
 	repos, err := models.GetRepositories(&models.User{Id: auth.SignedInId(session)})
 	if err != nil {
-		data["ErrorMsg"] = err
-		log.Error("dashboard: %v", err)
-		r.HTML(200, "base/error", data)
+		log.Handle(200, "user.Dashboard", data, r, err)
 		return
 	}
 	data["MyRepos"] = repos
@@ -37,14 +35,11 @@ func Profile(params martini.Params, r render.Render, data base.TmplData, session
 	// TODO: Need to check view self or others.
 	user, err := models.GetUserByName(params["username"])
 	if err != nil {
-		data["ErrorMsg"] = err
-		log.Error("user.Profile: %v", err)
-		r.HTML(200, "base/error", data)
+		log.Handle(200, "user.Profile", data, r, err)
 		return
 	}
 
-	data["Avatar"] = user.Avatar
-	data["Username"] = user.Name
+	data["Owner"] = user
 	r.HTML(200, "user/profile", data)
 }
 
@@ -71,9 +66,7 @@ func SignIn(form auth.LogInForm, data base.TmplData, req *http.Request, r render
 			return
 		}
 
-		data["ErrorMsg"] = err
-		log.Error("user.SignIn: %v", err)
-		r.HTML(200, "base/error", data)
+		log.Handle(200, "user.SignIn", data, r, err)
 		return
 	}
 
@@ -130,9 +123,7 @@ func SignUp(form auth.RegisterForm, data base.TmplData, req *http.Request, r ren
 			data["ErrorMsg"] = "E-mail address has been already used"
 			r.HTML(200, "user/signup", data)
 		default:
-			data["ErrorMsg"] = err
-			log.Error("user.SignUp: %v", data)
-			r.HTML(200, "base/error", nil)
+			log.Handle(200, "user.SignUp", data, r, err)
 		}
 		return
 	}
@@ -156,9 +147,7 @@ func Delete(data base.TmplData, req *http.Request, session sessions.Session, r r
 		case models.ErrUserOwnRepos.Error():
 			data["ErrorMsg"] = "Your account still have ownership of repository, you have to delete or transfer them first."
 		default:
-			data["ErrorMsg"] = err
-			log.Error("user.Delete: %v", data)
-			r.HTML(200, "base/error", nil)
+			log.Handle(200, "user.Delete", data, r, err)
 			return
 		}
 	}

+ 11 - 0
templates/repo/nav.tmpl

@@ -1,6 +1,17 @@
 <div id="gogs-body-nav" class="gogs-repo-nav">
     <div class="container">
         <div class="gogs-repo-btns pull-right">
+            <div class="btn-group" id="gogs-repo-clone">
+                <button type="button" class="btn btn-default"><i class="fa fa-download"></i>Clone</button>
+                <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
+                    <span class="caret"></span>
+                    <span class="sr-only">Toggle Dropdown</span>
+                </button>
+                <div class="dropdown-menu" role="menu">
+                    <div data-val="down-http">http link</div>
+                    <div data-val="down-git">git link</div>
+                </div>
+            </div>
             <div class="btn-group" id="gogs-repo-watching">
                 <button type="button" class="btn btn-default"><i class="fa fa-eye"></i>Watch {x}</button>
                 <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">

+ 53 - 6
templates/repo/single.tmpl

@@ -3,11 +3,58 @@
 {{template "repo/nav" .}}
 {{template "repo/toolbar" .}}
 <div id="gogs-body" class="container">
-    <h4>Source Files:</h4>
-    <ul>
-    {{range .Files}}
-        <li>{{.Name}} - {{.Id}} - {{.Message}} - {{.Created}} - {{.IsFile}} - {{.IsDir}}</li>
-    {{end}}
-    </ul>
+    <div id="gogs-source">
+        <div id="gogs-source-toolbar">
+            <button class="btn btn-default pull-right"><i class="fa fa-plus-square"></i>Add File</button>
+            <div class="dropdown branch-switch">
+                <a href="#" class="btn btn-success dropdown-toggle" data-toggle="dropdown"><i class="fa fa-chain"></i>master&nbsp;&nbsp;
+                    <b class="caret"></b></a>
+                <ul class="dropdown-menu">
+                    <li><a class="current" href="/{{.RepositoryLink}}/branch/master">master</a></li>
+                    <li><a href="//{{.RepositoryLink}}/branch/develop">develop</a></li>
+                </ul>
+            </div>
+            <ol class="breadcrumb">
+                <li class="root dir">{{.Repository.Name}}</li>
+                {{$paths := .Paths}}
+                {{ $username := .Username}}
+            {{ $reponame := .Reponame}}
+            {{ $branchname := .Branchname}}
+            {{ $treenames := .Treenames}}
+            {{ $n := len $treenames}}
+            {{ $l := Subtract $n 1}}
+                {{range $i, $v := $treenames}}
+                <li class="dir">
+                {{if eq $i $l}}{{$v}}
+                {{else}}
+                <a href="/{{$username}}/{{$reponame}}/tree/{{$branchname}}/{{index $paths $i}}">{{$v}}</a>&nbsp;
+                {{end}}</li>
+                {{end}}
+            </ol>
+        </div>
+        <table id="gogs-source-table" class="table table-hover">
+            <thead class="hidden">
+	            <tr>
+	                <th class="name">Filename</th>
+	                <th class="date">Date modified</th>
+	                <th class="text">Message</th>
+	            </tr>
+            </thead>
+            <tbody>
+    			{{range .Files}}
+				<tr {{if .IsDir}}class="is-dir"{{end}}>
+	                <td class="name"><i class="fa {{if .IsDir}}fa-folder{{else}}fa-file{{end}}"></i>
+                    {{if .IsDir}}
+                    <a href="/{{$username}}/{{$reponame}}/tree/{{$branchname}}/{{.Path}}">{{.Name}}</a>
+                    {{else}}
+                    <a href="#">{{.Name}}</a>
+                    {{end}}</td>
+	                <td class="date"><time datetime="{{.Created}}" data-title="true" title="{{.Created}}">{{TimeSince .Created}}</time></td>
+	                <td class="text">{{.Message}}</td>
+				</tr>
+    			{{end}}
+            </tbody>
+        </table>
+    </div>
 </div>
 {{template "base/footer" .}}

+ 26 - 34
templates/repo/toolbar.tmpl

@@ -1,40 +1,32 @@
 <div id="gogs-repo-toolbar">
     <div class="container">
         <nav class="navbar navbar-toolbar navbar-default" role="navigation">
-            <div class="container-fluid">
-                <div class="collapse navbar-collapse">
-                    <ul class="nav navbar-nav">
-                        <li class="dropdown">
-                            <a href="#" class="dropdown-toggle" data-toggle="dropdown">Branches <b class="caret"></b></a>
-                            <ul class="dropdown-menu">
-                                <li><a href="#">master</a></li>
-                                <li><a href="#">develop</a></li>
-                            </ul>
-                        </li>
-                        <li class="{{if .IsRepoToolbarSource}}active{{end}}"><a href="/{{.RepositoryLink}}">Source</a></li>
-                        <li><a href="#">Commits</a></li>
-                        <li><a href="#">Issues <span class="badge">42</span></a></li>
-                        <li><a href="#">Pull Requests</a></li>
-                        <li class="dropdown">
-                            <a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>
-                            <ul class="dropdown-menu">
-                                <li><a href="#">Release</a></li>
-                                <li><a href="#">Wiki</a></li>
-                            </ul>
-                        </li>
-                    </ul>
-                    <ul class="nav navbar-nav navbar-right">
-                        <li class="dropdown">
-                            <a href="#" class="dropdown-toggle" data-toggle="dropdown">Statistic <b class="caret"></b></a>
-                            <ul class="dropdown-menu">
-                                <li><a href="#">Graphic</a></li>
-                                <li><a href="#">Pulse</a></li>
-                                <li><a href="#">Network</a></li>
-                            </ul>
-                        </li>{{if .IsRepositoryOwner}}
-                        <li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="/{{.RepositoryLink}}/settings">Settings</a></li>{{end}}
-                    </ul>
-                </div>
+            <div class="collapse navbar-collapse">
+                <ul class="nav navbar-nav">
+                    <li class="{{if .IsRepoToolbarSource}}active{{end}}"><a href="/{{.RepositoryLink}}">Source</a></li>
+                    <li><a href="/{{.RepositoryLink}}/commits">Commits</a></li>
+                    <li><a href="/{{.RepositoryLink}}/issues">Issues <!--<span class="badge">42</span>--></a></li>
+                    <li><a href="/{{.RepositoryLink}}/pulls">Pull Requests</a></li>
+                    <li class="dropdown">
+                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>
+                        <ul class="dropdown-menu">
+                            <li><a href="/{{.RepositoryLink}}/release">Release</a></li>
+                            <li><a href="//{{.RepositoryLink}}/wiki">Wiki</a></li>
+                        </ul>
+                    </li>
+                </ul>
+                <ul class="nav navbar-nav navbar-right">
+                    <li class="dropdown">
+                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">Statistic <b class="caret"></b></a>
+                        <ul class="dropdown-menu">
+                            <li><a href="#">Graphic</a></li>
+                            <li><a href="#">Pulse</a></li>
+                            <li><a href="#">Network</a></li>
+                        </ul>
+                    </li>{{if .IsRepositoryOwner}}
+                    <li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="/{{.RepositoryLink}}/settings">Settings</a>
+                    </li>{{end}}
+                </ul>
             </div>
         </nav>
     </div>

+ 0 - 0
templates/base/error.tmpl → templates/status/200.tmpl


+ 3 - 3
templates/user/delete.tmpl

@@ -5,10 +5,10 @@
         <h4>Account Setting</h4>
         <ul class="list-group">
             <li class="list-group-item"><a href="/user/setting">Account Profile</a></li>
-            <li class="list-group-item"><a href="#">Emails and Password</a></li>
-            <li class="list-group-item"><a href="#">Notifications</a></li>
+            <li class="list-group-item"><a href="/user/setting/password">Password</a></li>
+            <li class="list-group-item"><a href="/user/setting/notification">Notifications</a></li>
             <li class="list-group-item"><a href="/user/setting/ssh/">SSH Keys</a></li>
-            <li class="list-group-item"><a href="#">Security</a></li>
+            <li class="list-group-item"><a href="/user/setting/security">Security</a></li>
             <li class="list-group-item list-group-item-success"><a href="/user/delete">Delete Account</a></li>
         </ul>
     </div>

+ 19 - 0
templates/user/notification.tmpl

@@ -0,0 +1,19 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="gogs-body" class="container">
+    <div id="gogs-user-setting-nav" class="col-md-3">
+        <h4>Account Setting</h4>
+        <ul class="list-group">
+            <li class="list-group-item"><a href="/user/setting">Account Profile</a></li>
+            <li class="list-group-item"><a href="/user/setting/password">Password</a></li>
+            <li class="list-group-item list-group-item-success"><a href="/user/setting/notification">Notifications</a></li>
+            <li class="list-group-item"><a href="/user/setting/ssh/">SSH Keys</a></li>
+            <li class="list-group-item"><a href="/user/setting/security">Security</a></li>
+            <li class="list-group-item"><a href="/user/delete">Delete Account</a></li>
+        </ul>
+    </div>
+    <div id="gogs-user-setting-container" class="col-md-9">
+        <h4>Notification</h4>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 51 - 0
templates/user/password.tmpl

@@ -0,0 +1,51 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="gogs-body" class="container">
+    <div id="gogs-user-setting-nav" class="col-md-3">
+        <h4>Account Setting</h4>
+        <ul class="list-group">
+            <li class="list-group-item"><a href="/user/setting">Account Profile</a></li>
+            <li class="list-group-item list-group-item-success"><a href="/user/setting/password">Password</a></li>
+            <li class="list-group-item"><a href="/user/setting/notification">Notifications</a></li>
+            <li class="list-group-item"><a href="/user/setting/ssh/">SSH Keys</a></li>
+            <li class="list-group-item"><a href="/user/setting/security">Security</a></li>
+            <li class="list-group-item"><a href="/user/delete">Delete Account</a></li>
+        </ul>
+    </div>
+    <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}}
+                <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-2 control-label">Old Password<strong class="text-danger">*</strong></label>
+                    <div class="col-md-8">
+                        <input type="password" name="oldpasswd" class="form-control" placeholder="Type your current password" required="required">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-md-2 control-label">New Password<strong class="text-danger">*</strong></label>
+                    <div class="col-md-8">
+                        <input type="password" name="newpasswd" class="form-control" placeholder="Type your new password" required="required">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-md-2 control-label">Re-Type<strong class="text-danger">*</strong></label>
+                    <div class="col-md-8">
+                        <input type="password" name="retypepasswd" class="form-control" placeholder="Re-type your new password" required="required">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <div class="col-md-offset-2 col-md-8">
+                        <button type="submit" class="btn btn-primary">Change Password</button>&nbsp;&nbsp;
+                        <a href="/forget-password/">Forgot your password?</a>
+                    </div>
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 6 - 6
templates/user/profile.tmpl

@@ -4,16 +4,16 @@
     <div id="gogs-user-profile" class="col-md-3">
         <div class="profile-avatar text-center">
             <a href="#" class="center-block" data-toggle="tooltip" data-placement="bottom" title="Change Avatar">
-                <img id="gogs-user-avatar" src="http://1.gravatar.com/avatar/{{.Avatar}}?s=200" alt="user-avatar" title="username"/>
+                <img id="gogs-user-avatar" src="http://1.gravatar.com/avatar/{{.Owner.Avatar}}?s=200" alt="user-avatar" title="username"/>
             </a>
-            <span id="gogs-user-name" class="center-block" href="#">{{.Username}}</span>
+            <span id="gogs-user-name" class="center-block" href="#">{{.Owner.Name}}</span>
         </div>
         <div class="profile-info">
             <ul class="list-group">
-                <li class="list-group-item"><i class="fa fa-thumb-tack"></i>City, County, State, Nation</li>
-                <li class="list-group-item"><i class="fa fa-envelope"></i><a href="#">Email@EmailAddress.com</a></li>
-                <li class="list-group-item"><i class="fa fa-link"></i><a href="#">http://yousite/</a></li>
-                <li class="list-group-item"><i class="fa fa-clock-o"></i>Joined At 03.02, 2014</li>
+                <li class="list-group-item"><i class="fa fa-thumb-tack"></i>{{.Owner.Location}}</li>
+                <li class="list-group-item"><i class="fa fa-envelope"></i><a href="mailto:{{.Owner.Email}}">{{.Owner.Email}}</a></li>
+                <li class="list-group-item"><i class="fa fa-link"></i><a target="_blank" href="{{.Owner.Website}}">{{.Owner.Website}}</a></li>
+                <li class="list-group-item"><i class="fa fa-clock-o"></i>{{.Owner.Created}}</li>
             </ul>
         </div>
     </div>

+ 3 - 3
templates/user/publickey.tmpl

@@ -5,10 +5,10 @@
         <h4>Account Setting</h4>
         <ul class="list-group">
             <li class="list-group-item"><a href="/user/setting">Account Profile</a></li>
-            <li class="list-group-item"><a href="#">Emails and Password</a></li>
-            <li class="list-group-item"><a href="#">Notifications</a></li>
+            <li class="list-group-item"><a href="/user/setting/Password">Password</a></li>
+            <li class="list-group-item"><a href="/user/setting/notification">Notifications</a></li>
             <li class="list-group-item list-group-item-success"><a href="/user/setting/ssh/">SSH Keys</a></li>
-            <li class="list-group-item"><a href="#">Security</a></li>
+            <li class="list-group-item"><a href="/user/setting/security">Security</a></li>
             <li class="list-group-item"><a href="/user/delete">Delete Account</a></li>
         </ul>
     </div>

+ 0 - 26
templates/user/publickey_add.tmpl

@@ -1,26 +0,0 @@
-{{template "base/head" .}}
-{{template "base/navbar" .}}
-<div class="container" id="gogs-body">
-	<form action="/user/publickey/add" method="post" class="form-horizontal">
-		<div class="form-group">
-			<label class="col-md-4 control-label">Name of this public key: </label>
-			<div class="col-md-3">
-				<input name="keyname" class="form-control" placeholder="Type your preferred name" value="{{.KeyName}}">
-			</div>
-		</div>
-
-		<div class="form-group">
-			<label class="col-md-4 control-label">Paste your key here: </label>
-			<div class="col-md-3">
-				<textarea name="key_content" cols="30" rows="10" class="form-control">{{.KeyContent}}</textarea>
-			</div>
-		</div>
-
-		<div class="form-group">
-		    <div class="col-md-offset-4 col-md-3">
-		    	<button type="submit" class="btn btn-info">Add public key</button>
-		    </div>
-		</div>
-	</form>
-</div>
-{{template "base/footer" .}}

+ 0 - 8
templates/user/publickey_added.tmpl

@@ -1,8 +0,0 @@
-{{template "base/head" .}}
-{{template "base/navbar" .}}
-<div class="container">
-		<div class="form-group">
-			publickey added
-		</div>
-</div>
-{{template "base/footer" .}}

+ 0 - 12
templates/user/publickey_list.tmpl

@@ -1,12 +0,0 @@
-{{template "base/head" .}}
-{{template "base/navbar" .}}
-<div class="container" id="gogs-body">
-<div><a href="/user/publickey/add">Add publick key</a></div>
-	<ul>
-	{{range .Keys}}
-		<li>{{.Name}}</li>
-		<li>{{.Content}}</li>
-	{{end}}
-	</ul>
-</div>
-{{template "base/footer" .}}

+ 19 - 0
templates/user/security.tmpl

@@ -0,0 +1,19 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+<div id="gogs-body" class="container">
+    <div id="gogs-user-setting-nav" class="col-md-3">
+        <h4>Account Setting</h4>
+        <ul class="list-group">
+            <li class="list-group-item"><a href="/user/setting">Account Profile</a></li>
+            <li class="list-group-item"><a href="/user/setting/password">Password</a></li>
+            <li class="list-group-item"><a href="/user/setting/notification">Notifications</a></li>
+            <li class="list-group-item"><a href="/user/setting/ssh/">SSH Keys</a></li>
+            <li class="list-group-item list-group-item-success"><a href="/user/setting/security">Security</a></li>
+            <li class="list-group-item"><a href="/user/delete">Delete Account</a></li>
+        </ul>
+    </div>
+    <div id="gogs-user-setting-container" class="col-md-9">
+        <h4>Security</h4>
+    </div>
+</div>
+{{template "base/footer" .}}

+ 44 - 5
templates/user/setting.tmpl

@@ -5,15 +5,54 @@
         <h4>Account Setting</h4>
         <ul class="list-group">
             <li class="list-group-item list-group-item-success"><a href="/user/setting">Account Profile</a></li>
-            <li class="list-group-item"><a href="#">Emails and Password</a></li>
-            <li class="list-group-item"><a href="#">Notifications</a></li>
-            <li class="list-group-item"><a href="/user/setting/ssh">SSH Keys</a></li>
-            <li class="list-group-item"><a href="#">Security</a></li>
+            <li class="list-group-item"><a href="/user/setting/password">Password</a></li>
+            <li class="list-group-item"><a href="/user/setting/notification">Notifications</a></li>
+            <li class="list-group-item"><a href="/user/setting/ssh/">SSH Keys</a></li>
+            <li class="list-group-item"><a href="/user/setting/security">Security</a></li>
             <li class="list-group-item"><a href="/user/delete">Delete Account</a></li>
         </ul>
     </div>
     <div id="gogs-user-setting-container" class="col-md-9">
-        setting container
+        <div id="gogs-setting-pwd">
+            <h4>Account Profile</h4>
+            <form class="form-horizontal" id="gogs-password-form" method="post" action="/user/setting">{{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">
+                    <label class="col-md-2 control-label">Email</label>
+                    <div class="col-md-8">
+                        <input type="text" name="email" class="form-control" placeholder="Type your e-mail address" value="{{.Owner.Email}}">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-md-2 control-label">Website</label>
+                    <div class="col-md-8">
+                        <input type="text" name="website" class="form-control" placeholder="Type your website URL" value="{{.Owner.Website}}">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-md-2 control-label">Location</label>
+                    <div class="col-md-8">
+                        <input type="text" name="location" class="form-control" placeholder="Type your current location" value="{{.Owner.Location}}">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-md-2 control-label">Gravatar Email<strong class="text-danger">*</strong></label>
+                    <div class="col-md-8">
+                        <input type="text" name="avatar" class="form-control" placeholder="Type your Gravatar e-mail address" required="required" value="{{.Owner.AvatarEmail}}">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <div class="col-md-offset-2 col-md-8">
+                        <button type="submit" class="btn btn-primary">Update Profile</button>
+                    </div>
+                </div>
+            </form>
+        </div>
     </div>
 </div>
 {{template "base/footer" .}}

+ 1 - 1
templates/user/signup.tmpl

@@ -7,7 +7,7 @@
 		<div class="form-group {{if .Err_UserName}}has-error has-feedback{{end}}">
 			<label class="col-md-4 control-label">Username: </label>
 			<div class="col-md-6">
-				<input name="username" class="form-control" placeholder="Type your username" value="{{.username}}" required="required" title="Username must contain at least 5 characters">
+				<input name="username" class="form-control" placeholder="Type your username" value="{{.username}}" required="required">
 			</div>
 		</div>
 

+ 59 - 1
web.go

@@ -33,6 +33,55 @@ gogs web`,
 	Flags:  []cli.Flag{},
 }
 
+func Subtract(left interface{}, right interface{}) interface{} {
+	var rleft, rright int64
+	var fleft, fright float64
+	var isInt bool = true
+	switch left.(type) {
+	case int:
+		rleft = int64(left.(int))
+	case int8:
+		rleft = int64(left.(int8))
+	case int16:
+		rleft = int64(left.(int16))
+	case int32:
+		rleft = int64(left.(int32))
+	case int64:
+		rleft = left.(int64)
+	case float32:
+		fleft = float64(left.(float32))
+		isInt = false
+	case float64:
+		fleft = left.(float64)
+		isInt = false
+	}
+
+	switch right.(type) {
+	case int:
+		rright = int64(right.(int))
+	case int8:
+		rright = int64(right.(int8))
+	case int16:
+		rright = int64(right.(int16))
+	case int32:
+		rright = int64(right.(int32))
+	case int64:
+		rright = right.(int64)
+	case float32:
+		fright = float64(left.(float32))
+		isInt = false
+	case float64:
+		fleft = left.(float64)
+		isInt = false
+	}
+
+	if isInt {
+		return rleft - rright
+	} else {
+		return fleft + float64(rleft) - (fright + float64(rright))
+	}
+}
+
 var AppHelpers template.FuncMap = map[string]interface{}{
 	"AppName": func() string {
 		return base.Cfg.MustValue("", "APP_NAME")
@@ -40,6 +89,8 @@ var AppHelpers template.FuncMap = map[string]interface{}{
 	"AppVer": func() string {
 		return APP_VER
 	},
+	"TimeSince": base.TimeSince,
+	"Subtract":  Subtract,
 }
 
 func runWeb(*cli.Context) {
@@ -63,8 +114,11 @@ func runWeb(*cli.Context) {
 	m.Any("/user/delete", auth.SignInRequire(true), user.Delete)
 	m.Get("/user/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
 
-	m.Any("/user/setting", auth.SignInRequire(true), user.Setting)
+	m.Any("/user/setting", auth.SignInRequire(true), binding.BindIgnErr(auth.UpdateProfileForm{}), user.Setting)
+	m.Any("/user/setting/password", auth.SignInRequire(true), binding.BindIgnErr(auth.UpdatePasswdForm{}), user.SettingPassword)
 	m.Any("/user/setting/ssh", auth.SignInRequire(true), binding.BindIgnErr(auth.AddSSHKeyForm{}), user.SettingSSHKeys)
+	m.Any("/user/setting/notification", auth.SignInRequire(true), user.SettingNotification)
+	m.Any("/user/setting/security", auth.SignInRequire(true), user.SettingSecurity)
 
 	m.Get("/user/:username", auth.SignInRequire(false), user.Profile)
 
@@ -73,6 +127,10 @@ func runWeb(*cli.Context) {
 	m.Any("/repo/list", auth.SignInRequire(false), repo.List)
 
 	m.Get("/:username/:reponame/settings", auth.SignInRequire(false), auth.RepoAssignment(true), repo.Setting)
+	m.Get("/:username/:reponame/tree/:branchname/**",
+		auth.SignInRequire(false), auth.RepoAssignment(true), repo.Single)
+	m.Get("/:username/:reponame/tree/:branchname",
+		auth.SignInRequire(false), auth.RepoAssignment(true), repo.Single)
 	m.Get("/:username/:reponame", auth.SignInRequire(false), auth.RepoAssignment(true), repo.Single)
 
 	//m.Get("/:username/:reponame", repo.Repo)