Browse Source

Merge pull request #1 from gogits/master

Sync to lastest
Meaglith Ma 11 years ago
parent
commit
1a247340db
61 changed files with 1009 additions and 513 deletions
  1. 4 9
      .gitignore
  2. 3 1
      .gobuild.yml
  3. 3 1
      README.md
  4. 1 1
      README_ZH.md
  5. 2 2
      conf/app.ini
  6. 2 0
      conf/mysql.sql
  7. 1 1
      gogs.go
  8. 2 0
      models/access.go
  9. 10 5
      models/action.go
  10. 19 7
      models/git.go
  11. 48 23
      models/issue.go
  12. 42 15
      models/models.go
  13. 25 23
      models/repo.go
  14. 2 1
      models/user.go
  15. 1 1
      modules/auth/admin.go
  16. 52 1
      modules/auth/auth.go
  17. 1 1
      modules/auth/issue.go
  18. 1 1
      modules/auth/repo.go
  19. 1 1
      modules/auth/setting.go
  20. 6 3
      modules/auth/user.go
  21. 16 10
      modules/base/conf.go
  22. 8 0
      modules/base/template.go
  23. 13 4
      modules/base/tool.go
  24. 3 2
      modules/log/log.go
  25. 6 1
      modules/middleware/auth.go
  26. 15 3
      modules/middleware/context.go
  27. 1 1
      modules/middleware/logger.go
  28. 1 1
      modules/middleware/render.go
  29. 94 23
      modules/middleware/repo.go
  30. 3 3
      public/css/gogs.css
  31. 86 17
      public/js/app.js
  32. 1 1
      routers/admin/admin.go
  33. 1 1
      routers/admin/user.go
  34. 18 0
      routers/api/v1/miscellaneous.go
  35. 1 1
      routers/dev/template.go
  36. 166 5
      routers/install.go
  37. 4 9
      routers/repo/branch.go
  38. 7 13
      routers/repo/commit.go
  39. 49 40
      routers/repo/issue.go
  40. 1 1
      routers/repo/pull.go
  41. 17 55
      routers/repo/repo.go
  42. 9 9
      routers/user/user.go
  43. 35 30
      serve.go
  44. 2 0
      start.bat
  45. 6 0
      start.sh
  46. 32 27
      templates/install.tmpl
  47. 7 7
      templates/issue/create.tmpl
  48. 6 6
      templates/issue/list.tmpl
  49. 48 48
      templates/issue/view.tmpl
  50. 1 2
      templates/repo/diff.tmpl
  51. 4 10
      templates/repo/nav.tmpl
  52. 2 6
      templates/repo/single.tmpl
  53. 33 24
      templates/repo/single_bare.tmpl
  54. 10 10
      templates/repo/toolbar.tmpl
  55. 3 4
      templates/user/profile.tmpl
  56. 1 1
      templates/user/signin.tmpl
  57. 9 0
      tests/.travel.yml
  58. 11 0
      tests/README.md
  59. 17 0
      tests/default_test.go
  60. 17 3
      update.go
  61. 19 38
      web.go

+ 4 - 9
.gitignore

@@ -1,9 +1,3 @@
-<<<<<<< HEAD
-
-
-gogs
-*.exe
-*.exe~
 .DS_Store
 *.db
 *.log
@@ -14,8 +8,6 @@ data/
 *.iml
 public/img/avatar/
 
-=======
->>>>>>> b4db9f67548a41922f1b337daf9c9d2b975b55c4
 # Compiled Object files, Static and Dynamic libs (Shared Objects)
 *.o
 *.a
@@ -37,4 +29,7 @@ _cgo_export.*
 
 _testmain.go
 
-*.exe
+*.exe
+*.exe~
+gogs
+__pycache__

+ 3 - 1
.gobuild.yml

@@ -5,4 +5,6 @@ filesets:
         - conf
         - LICENSE
         - README.md
-        - README_ZH.md
+        - README_ZH.md
+        - start.bat
+        - start.sh

+ 3 - 1
README.md

@@ -5,7 +5,9 @@ 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.9 Alpha
+##### Current version: 0.2.0 Alpha
+
+#### Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in March 29, 2014 and will reset multiple times after. Please do NOT put your important data on the site.
 
 #### Other language version
 

+ 1 - 1
README_ZH.md

@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
 
 ![Demo](http://gowalker.org/public/gogs_demo.gif)
 
-##### 当前版本:0.1.9 Alpha
+##### 当前版本:0.2.0 Alpha
 
 ## 开发目的
 

+ 2 - 2
conf/app.ini

@@ -7,7 +7,7 @@ RUN_USER = git
 RUN_MODE = dev
 
 [repository]
-ROOT = /Users/%(RUN_USER)s/git/gogs-repositories
+ROOT = 
 LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp
 LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License
 
@@ -20,7 +20,7 @@ HTTP_PORT = 3000
 [database]
 ; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice
 DB_TYPE = mysql
-HOST = 
+HOST = 127.0.0.1:3306
 NAME = gogs
 USER = root
 PASSWD =

+ 2 - 0
conf/mysql.sql

@@ -0,0 +1,2 @@
+DROP DATABASE gogs;
+CREATE DATABASE IF NOT EXISTS gogs CHARACTER SET utf8 COLLATE utf8_general_ci;

+ 1 - 1
gogs.go

@@ -19,7 +19,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.9.0328 Alpha"
+const APP_VER = "0.2.0.0330 Alpha"
 
 func init() {
 	base.AppVer = APP_VER

+ 2 - 0
models/access.go

@@ -26,6 +26,8 @@ type Access struct {
 
 // AddAccess adds new access record.
 func AddAccess(access *Access) error {
+	access.UserName = strings.ToLower(access.UserName)
+	access.RepoName = strings.ToLower(access.RepoName)
 	_, err := orm.Insert(access)
 	return err
 }

+ 10 - 5
models/action.go

@@ -31,6 +31,7 @@ type Action struct {
 	OpType      int    // Operations: CREATE DELETE STAR ...
 	ActUserId   int64  // Action user id.
 	ActUserName string // Action user name.
+	ActEmail    string
 	RepoId      int64
 	RepoName    string
 	RefName     string
@@ -46,6 +47,10 @@ func (a Action) GetActUserName() string {
 	return a.ActUserName
 }
 
+func (a Action) GetActEmail() string {
+	return a.ActEmail
+}
+
 func (a Action) GetRepoName() string {
 	return a.RepoName
 }
@@ -59,7 +64,7 @@ func (a Action) GetContent() string {
 }
 
 // CommitRepoAction adds new action for committing repository.
-func CommitRepoAction(userId int64, userName string,
+func CommitRepoAction(userId int64, userName, actEmail string,
 	repoId int64, repoName string, refName string, commit *base.PushCommits) error {
 	log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName)
 
@@ -69,8 +74,8 @@ func CommitRepoAction(userId int64, userName string,
 		return err
 	}
 
-	if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, OpType: OP_COMMIT_REPO,
-		Content: string(bs), RepoId: repoId, RepoName: repoName, RefName: refName}); err != nil {
+	if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, ActEmail: actEmail,
+		OpType: OP_COMMIT_REPO, Content: string(bs), RepoId: repoId, RepoName: repoName, RefName: refName}); err != nil {
 		log.Error("action.CommitRepoAction(notify watchers): %d/%s", userId, repoName)
 		return err
 	}
@@ -93,8 +98,8 @@ func CommitRepoAction(userId int64, userName string,
 
 // NewRepoAction adds new action for creating repository.
 func NewRepoAction(user *User, repo *Repository) (err error) {
-	if err = NotifyWatchers(&Action{ActUserId: user.Id, ActUserName: user.Name, OpType: OP_CREATE_REPO,
-		RepoId: repo.Id, RepoName: repo.Name}); err != nil {
+	if err = NotifyWatchers(&Action{ActUserId: user.Id, ActUserName: user.Name, ActEmail: user.Email,
+		OpType: OP_CREATE_REPO, RepoId: repo.Id, RepoName: repo.Name}); err != nil {
 		log.Error("action.NewRepoAction(notify watchers): %d/%s", user.Id, repo.Name)
 		return err
 	}

+ 19 - 7
models/git.go

@@ -70,9 +70,12 @@ func GetTargetFile(userName, repoName, branchName, commitId, rpath string) (*Rep
 		return nil, err
 	}
 
-	commit, err := repo.GetCommit(branchName, commitId)
+	commit, err := repo.GetCommitOfBranch(branchName)
 	if err != nil {
-		return nil, err
+		commit, err = repo.GetCommit(commitId)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	parts := strings.Split(path.Clean(rpath), "/")
@@ -110,13 +113,22 @@ func GetTargetFile(userName, repoName, branchName, commitId, rpath string) (*Rep
 }
 
 // GetReposFiles returns a list of file object in given directory of repository.
-func GetReposFiles(userName, repoName, branchName, commitId, rpath string) ([]*RepoFile, error) {
+// func GetReposFilesOfBranch(userName, repoName, branchName, rpath string) ([]*RepoFile, error) {
+// 	return getReposFiles(userName, repoName, commitId, rpath)
+// }
+
+// GetReposFiles returns a list of file object in given directory of repository.
+func GetReposFiles(userName, repoName, commitId, rpath string) ([]*RepoFile, error) {
+	return getReposFiles(userName, repoName, commitId, rpath)
+}
+
+func getReposFiles(userName, repoName, commitId string, rpath string) ([]*RepoFile, error) {
 	repo, err := git.OpenRepository(RepoPath(userName, repoName))
 	if err != nil {
 		return nil, err
 	}
 
-	commit, err := repo.GetCommit(branchName, commitId)
+	commit, err := repo.GetCommit(commitId)
 	if err != nil {
 		return nil, err
 	}
@@ -216,13 +228,13 @@ func GetReposFiles(userName, repoName, branchName, commitId, rpath string) ([]*R
 	return append(repodirs, repofiles...), nil
 }
 
-func GetCommit(userName, repoName, branchname, commitid string) (*git.Commit, error) {
+func GetCommit(userName, repoName, commitId string) (*git.Commit, error) {
 	repo, err := git.OpenRepository(RepoPath(userName, repoName))
 	if err != nil {
 		return nil, err
 	}
 
-	return repo.GetCommit(branchname, commitid)
+	return repo.GetCommit(commitId)
 }
 
 // GetCommitsByBranch returns all commits of given branch of repository.
@@ -397,7 +409,7 @@ func GetDiff(repoPath, commitid string) (*Diff, error) {
 		return nil, err
 	}
 
-	commit, err := repo.GetCommit("", commitid)
+	commit, err := repo.GetCommit(commitid)
 	if err != nil {
 		return nil, err
 	}

+ 48 - 23
models/issue.go

@@ -18,23 +18,24 @@ var (
 
 // Issue represents an issue or pull request of repository.
 type Issue struct {
-	Id          int64
-	Index       int64 // Index in one repository.
-	Name        string
-	RepoId      int64       `xorm:"index"`
-	Repo        *Repository `xorm:"-"`
-	PosterId    int64
-	Poster      *User `xorm:"-"`
-	MilestoneId int64
-	AssigneeId  int64
-	IsPull      bool // Indicates whether is a pull request or not.
-	IsClosed    bool
-	Labels      string `xorm:"TEXT"`
-	Mentions    string `xorm:"TEXT"`
-	Content     string `xorm:"TEXT"`
-	NumComments int
-	Created     time.Time `xorm:"created"`
-	Updated     time.Time `xorm:"updated"`
+	Id              int64
+	Index           int64 // Index in one repository.
+	Name            string
+	RepoId          int64       `xorm:"index"`
+	Repo            *Repository `xorm:"-"`
+	PosterId        int64
+	Poster          *User `xorm:"-"`
+	MilestoneId     int64
+	AssigneeId      int64
+	IsPull          bool // Indicates whether is a pull request or not.
+	IsClosed        bool
+	Labels          string `xorm:"TEXT"`
+	Mentions        string `xorm:"TEXT"`
+	Content         string `xorm:"TEXT"`
+	RenderedContent string `xorm:"-"`
+	NumComments     int
+	Created         time.Time `xorm:"created"`
+	Updated         time.Time `xorm:"updated"`
 }
 
 // CreateIssue creates new issue for repository.
@@ -169,9 +170,17 @@ type Milestone struct {
 	Created   time.Time `xorm:"created"`
 }
 
+// Issue types.
+const (
+	IT_PLAIN  = iota // Pure comment.
+	IT_REOPEN        // Issue reopen status change prompt.
+	IT_CLOSE         // Issue close status change prompt.
+)
+
 // Comment represents a comment in commit and issue page.
 type Comment struct {
 	Id       int64
+	Type     int
 	PosterId int64
 	Poster   *User `xorm:"-"`
 	IssueId  int64
@@ -182,21 +191,37 @@ type Comment struct {
 }
 
 // CreateComment creates comment of issue or commit.
-func CreateComment(userId, issueId, commitId, line int64, content string) error {
+func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string) error {
 	sess := orm.NewSession()
 	defer sess.Close()
 	sess.Begin()
 
-	if _, err := orm.Insert(&Comment{PosterId: userId, IssueId: issueId,
+	if _, err := orm.Insert(&Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
 		CommitId: commitId, Line: line, Content: content}); err != nil {
 		sess.Rollback()
 		return err
 	}
 
-	rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
-	if _, err := sess.Exec(rawSql, issueId); err != nil {
-		sess.Rollback()
-		return err
+	// Check comment type.
+	switch cmtType {
+	case IT_PLAIN:
+		rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
+		if _, err := sess.Exec(rawSql, issueId); err != nil {
+			sess.Rollback()
+			return err
+		}
+	case IT_REOPEN:
+		rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
+		if _, err := sess.Exec(rawSql, repoId); err != nil {
+			sess.Rollback()
+			return err
+		}
+	case IT_CLOSE:
+		rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
+		if _, err := sess.Exec(rawSql, repoId); err != nil {
+			sess.Rollback()
+			return err
+		}
 	}
 	return sess.Commit()
 }

+ 42 - 15
models/models.go

@@ -12,20 +12,27 @@ import (
 	_ "github.com/go-sql-driver/mysql"
 	_ "github.com/lib/pq"
 	"github.com/lunny/xorm"
+	// _ "github.com/mattn/go-sqlite3"
 
 	"github.com/gogits/gogs/modules/base"
 )
 
 var (
-	orm *xorm.Engine
+	orm       *xorm.Engine
+	HasEngine bool
 
 	DbCfg struct {
 		Type, Host, Name, User, Pwd, Path, SslMode string
 	}
+
+	UseSQLite3 bool
 )
 
 func LoadModelsConfig() {
 	DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE")
+	if DbCfg.Type == "sqlite3" {
+		UseSQLite3 = true
+	}
 	DbCfg.Host = base.Cfg.MustValue("database", "HOST")
 	DbCfg.Name = base.Cfg.MustValue("database", "NAME")
 	DbCfg.User = base.Cfg.MustValue("database", "USER")
@@ -34,11 +41,32 @@ func LoadModelsConfig() {
 	DbCfg.Path = base.Cfg.MustValue("database", "PATH", "data/gogs.db")
 }
 
-func SetEngine() {
-	var err error
+func NewTestEngine(x *xorm.Engine) (err error) {
+	switch DbCfg.Type {
+	case "mysql":
+		x, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8",
+			DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name))
+	case "postgres":
+		x, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s dbname=%s sslmode=%s",
+			DbCfg.User, DbCfg.Pwd, DbCfg.Name, DbCfg.SslMode))
+	// case "sqlite3":
+	// 	os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm)
+	// 	x, err = xorm.NewEngine("sqlite3", DbCfg.Path)
+	default:
+		return fmt.Errorf("Unknown database type: %s\n", DbCfg.Type)
+	}
+	if err != nil {
+		return fmt.Errorf("models.init(fail to conntect database): %v\n", err)
+	}
+
+	return x.Sync(new(User), new(PublicKey), new(Repository), new(Watch),
+		new(Action), new(Access), new(Issue), new(Comment))
+}
+
+func SetEngine() (err error) {
 	switch DbCfg.Type {
 	case "mysql":
-		orm, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@%s/%s?charset=utf8",
+		orm, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8",
 			DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name))
 	case "postgres":
 		orm, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s dbname=%s sslmode=%s",
@@ -47,12 +75,10 @@ func SetEngine() {
 		os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm)
 		orm, err = xorm.NewEngine("sqlite3", DbCfg.Path)
 	default:
-		fmt.Printf("Unknown database type: %s\n", DbCfg.Type)
-		os.Exit(2)
+		return fmt.Errorf("Unknown database type: %s\n", DbCfg.Type)
 	}
 	if err != nil {
-		fmt.Printf("models.init(fail to conntect database): %v\n", err)
-		os.Exit(2)
+		return fmt.Errorf("models.init(fail to conntect database): %v\n", err)
 	}
 
 	// WARNNING: for serv command, MUST remove the output to os.stdout,
@@ -62,20 +88,21 @@ func SetEngine() {
 	//orm.ShowErr = true
 	f, err := os.Create("xorm.log")
 	if err != nil {
-		fmt.Printf("models.init(fail to create xorm.log): %v\n", err)
-		os.Exit(2)
+		return fmt.Errorf("models.init(fail to create xorm.log): %v\n", err)
 	}
 	orm.Logger = f
 	orm.ShowSQL = true
+	return nil
 }
 
-func NewEngine() {
-	SetEngine()
-	if err := orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch),
+func NewEngine() (err error) {
+	if err = SetEngine(); err != nil {
+		return err
+	} else if err = orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch),
 		new(Action), new(Access), new(Issue), new(Comment)); err != nil {
-		fmt.Printf("sync database struct error: %v\n", err)
-		os.Exit(2)
+		return fmt.Errorf("sync database struct error: %v\n", err)
 	}
+	return nil
 }
 
 type Statistic struct {

+ 25 - 23
models/repo.go

@@ -10,8 +10,8 @@ import (
 	"io/ioutil"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
-	"regexp"
 	"strings"
 	"time"
 	"unicode/utf8"
@@ -59,15 +59,6 @@ func NewRepoContext() {
 			os.Exit(2)
 		}
 	}
-
-	// Initialize illegal patterns.
-	for i := range illegalPatterns[1:] {
-		pattern := ""
-		for j := range illegalPatterns[i+1] {
-			pattern += "[" + string(illegalPatterns[i+1][j]-32) + string(illegalPatterns[i+1][j]) + "]"
-		}
-		illegalPatterns[i+1] = pattern
-	}
 }
 
 // Repository represents a git repository.
@@ -105,15 +96,20 @@ func IsRepositoryExist(user *User, repoName string) (bool, error) {
 }
 
 var (
-	// Define as all lower case!!
-	illegalPatterns = []string{"[.][Gg][Ii][Tt]", "raw", "user", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin"}
+	illegalEquals  = []string{"raw", "install", "api", "avatar", "user", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin"}
+	illegalSuffixs = []string{".git"}
 )
 
 // IsLegalName returns false if name contains illegal characters.
 func IsLegalName(repoName string) bool {
-	for _, pattern := range illegalPatterns {
-		has, _ := regexp.MatchString(pattern, repoName)
-		if has {
+	repoName = strings.ToLower(repoName)
+	for _, char := range illegalEquals {
+		if repoName == char {
+			return false
+		}
+	}
+	for _, char := range illegalSuffixs {
+		if strings.HasSuffix(repoName, char) {
 			return false
 		}
 	}
@@ -161,8 +157,8 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
 	}
 
 	access := Access{
-		UserName: user.Name,
-		RepoName: repo.Name,
+		UserName: user.LowerName,
+		RepoName: strings.ToLower(path.Join(user.Name, repo.Name)),
 		Mode:     AU_WRITABLE,
 	}
 	if _, err = session.Insert(&access); err != nil {
@@ -198,12 +194,19 @@ 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 {
+	if err = c.Run(); err != nil {
 		log.Error("repo.CreateRepository(exec update-server-info): %v", err)
 	}
 
-	return repo, NewRepoAction(user, repo)
+	if err = NewRepoAction(user, repo); err != nil {
+		log.Error("repo.CreateRepository(NewRepoAction): %v", err)
+	}
+
+	if err = WatchRepo(user.Id, repo.Id, true); err != nil {
+		log.Error("repo.CreateRepository(WatchRepo): %v", err)
+	}
+
+	return repo, nil
 }
 
 // extractGitBareZip extracts git-bare.zip to repository path.
@@ -362,7 +365,7 @@ func GetRepos(num, offset int) ([]UserRepo, error) {
 }
 
 func RepoPath(userName, repoName string) string {
-	return filepath.Join(UserPath(userName), repoName+".git")
+	return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git")
 }
 
 func UpdateRepository(repo *Repository) error {
@@ -395,7 +398,7 @@ 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 {
+	if _, err := session.Delete(&Access{RepoName: strings.ToLower(path.Join(userName, repo.Name))}); err != nil {
 		session.Rollback()
 		return err
 	}
@@ -510,7 +513,6 @@ func NotifyWatchers(act *Action) error {
 			continue
 		}
 
-		act.Id = 0
 		act.UserId = watches[i].UserId
 		if _, err = orm.InsertOne(act); err != nil {
 			return errors.New("repo.NotifyWatchers(create action): " + err.Error())

+ 2 - 1
models/user.go

@@ -39,6 +39,7 @@ var (
 	ErrUserNotExist     = errors.New("User does not exist")
 	ErrEmailAlreadyUsed = errors.New("E-mail already used")
 	ErrUserNameIllegal  = errors.New("User name contains illegal characters")
+	ErrKeyNotExist      = errors.New("Public key does not exist")
 )
 
 // User represents the object of individual and member of organization.
@@ -67,7 +68,7 @@ type User struct {
 
 // HomeLink returns the user home page link.
 func (user *User) HomeLink() string {
-	return "/user/" + user.LowerName
+	return "/user/" + user.Name
 }
 
 // AvatarLink returns the user gravatar link.

+ 1 - 1
modules/auth/admin.go

@@ -8,7 +8,7 @@ import (
 	"net/http"
 	"reflect"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/binding"
 

+ 52 - 1
modules/auth/auth.go

@@ -9,7 +9,7 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/binding"
 
@@ -161,3 +161,54 @@ func AssignForm(form interface{}, data base.TmplData) {
 		data[fieldName] = val.Field(i).Interface()
 	}
 }
+
+type InstallForm struct {
+	Database        string `form:"database" binding:"Required"`
+	Host            string `form:"host"`
+	User            string `form:"user"`
+	Passwd          string `form:"passwd"`
+	DatabaseName    string `form:"database_name"`
+	SslMode         string `form:"ssl_mode"`
+	DatabasePath    string `form:"database_path"`
+	RepoRootPath    string `form:"repo_path"`
+	RunUser         string `form:"run_user"`
+	Domain          string `form:"domain"`
+	AppUrl          string `form:"app_url"`
+	AdminName       string `form:"admin_name" binding:"Required"`
+	AdminPasswd     string `form:"admin_pwd" binding:"Required;MinSize(6);MaxSize(30)"`
+	AdminEmail      string `form:"admin_email" binding:"Required;Email;MaxSize(50)"`
+	SmtpHost        string `form:"smtp_host"`
+	SmtpEmail       string `form:"mailer_user"`
+	SmtpPasswd      string `form:"mailer_pwd"`
+	RegisterConfirm string `form:"register_confirm"`
+	MailNotify      string `form:"mail_notify"`
+}
+
+func (f *InstallForm) Name(field string) string {
+	names := map[string]string{
+		"Database":    "Database name",
+		"AdminName":   "Admin user name",
+		"AdminPasswd": "Admin password",
+		"AdminEmail":  "Admin e-maill address",
+	}
+	return names[field]
+}
+
+func (f *InstallForm) 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("InstallForm.Validate: %v", err)
+		}
+		return
+	}
+
+	validate(errors, data, f)
+}

+ 1 - 1
modules/auth/issue.go

@@ -8,7 +8,7 @@ import (
 	"net/http"
 	"reflect"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/binding"
 

+ 1 - 1
modules/auth/repo.go

@@ -8,7 +8,7 @@ import (
 	"net/http"
 	"reflect"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/binding"
 

+ 1 - 1
modules/auth/setting.go

@@ -9,7 +9,7 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/binding"
 

+ 6 - 3
modules/auth/user.go

@@ -8,11 +8,10 @@ import (
 	"net/http"
 	"reflect"
 
-	"github.com/codegangsta/martini"
-
-	"github.com/gogits/session"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/binding"
+	"github.com/gogits/session"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"
@@ -21,6 +20,10 @@ import (
 
 // SignedInId returns the id of signed in user.
 func SignedInId(session session.SessionStore) int64 {
+	if !models.HasEngine {
+		return 0
+	}
+
 	userId := session.Get("userId")
 	if userId == nil {
 		return 0

+ 16 - 10
modules/base/conf.go

@@ -212,9 +212,9 @@ func newMailService() {
 	if Cfg.MustBool("mailer", "ENABLED") {
 		MailService = &Mailer{
 			Name:   Cfg.MustValue("mailer", "NAME", AppName),
-			Host:   Cfg.MustValue("mailer", "HOST", "127.0.0.1:25"),
-			User:   Cfg.MustValue("mailer", "USER", "[email protected]"),
-			Passwd: Cfg.MustValue("mailer", "PASSWD", "******"),
+			Host:   Cfg.MustValue("mailer", "HOST"),
+			User:   Cfg.MustValue("mailer", "USER"),
+			Passwd: Cfg.MustValue("mailer", "PASSWD"),
 		}
 		log.Info("Mail Service Enabled")
 	}
@@ -253,7 +253,7 @@ func NewConfigContext() {
 	cfgPath := filepath.Join(workDir, "conf/app.ini")
 	Cfg, err = goconfig.LoadConfigFile(cfgPath)
 	if err != nil {
-		fmt.Printf("Cannot load config file '%s'\n", cfgPath)
+		fmt.Printf("Cannot load config file(%s): %v\n", cfgPath, err)
 		os.Exit(2)
 	}
 	Cfg.BlockMode = false
@@ -261,7 +261,7 @@ func NewConfigContext() {
 	cfgPath = filepath.Join(workDir, "custom/conf/app.ini")
 	if com.IsFile(cfgPath) {
 		if err = Cfg.AppendFiles(cfgPath); err != nil {
-			fmt.Printf("Cannot load config file '%s'\n", cfgPath)
+			fmt.Printf("Cannot load config file(%s): %v\n", cfgPath, err)
 			os.Exit(2)
 		}
 	}
@@ -272,18 +272,19 @@ func NewConfigContext() {
 	Domain = Cfg.MustValue("server", "DOMAIN")
 	SecretKey = Cfg.MustValue("security", "SECRET_KEY")
 
+	InstallLock = Cfg.MustBool("security", "INSTALL_LOCK", false)
+
 	RunUser = Cfg.MustValue("", "RUN_USER")
 	curUser := os.Getenv("USERNAME")
 	if len(curUser) == 0 {
 		curUser = os.Getenv("USER")
 	}
-	if RunUser != curUser {
+	// Does not check run user when the install lock is off.
+	if InstallLock && RunUser != curUser {
 		fmt.Printf("Expect user(%s) but current user is: %s\n", RunUser, curUser)
 		os.Exit(2)
 	}
 
-	InstallLock = Cfg.MustBool("security", "INSTALL_LOCK", false)
-
 	LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
 	CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
 	CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
@@ -291,9 +292,14 @@ func NewConfigContext() {
 	PictureService = Cfg.MustValue("picture", "SERVICE")
 
 	// Determine and create root git reposiroty path.
-	RepoRootPath = Cfg.MustValue("repository", "ROOT")
+	homeDir, err := com.HomeDir()
+	if err != nil {
+		fmt.Printf("Fail to get home directory): %v\n", err)
+		os.Exit(2)
+	}
+	RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "git/gogs-repositories"))
 	if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
-		fmt.Printf("models.init(fail to create RepoRootPath(%s)): %v\n", RepoRootPath, err)
+		fmt.Printf("Fail to create RepoRootPath(%s): %v\n", RepoRootPath, err)
 		os.Exit(2)
 	}
 }

+ 8 - 0
modules/base/template.go

@@ -33,6 +33,13 @@ func List(l *list.List) chan interface{} {
 	return c
 }
 
+func ShortSha(sha1 string) string {
+	if len(sha1) == 40 {
+		return sha1[:10]
+	}
+	return sha1
+}
+
 var mailDomains = map[string]string{
 	"gmail.com": "gmail.com",
 }
@@ -72,4 +79,5 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
 	},
 	"DiffTypeToStr":     DiffTypeToStr,
 	"DiffLineTypeToStr": DiffLineTypeToStr,
+	"ShortSha":          ShortSha,
 }

+ 13 - 4
modules/base/tool.go

@@ -478,6 +478,7 @@ func (a argInt) Get(i int, args ...int) (r int) {
 type Actioner interface {
 	GetOpType() int
 	GetActUserName() string
+	GetActEmail() string
 	GetRepoName() string
 	GetBranch() string
 	GetContent() string
@@ -506,15 +507,23 @@ const (
 <div><img src="%s?s=16" alt="user-avatar"/> %s</div>`
 )
 
+type PushCommit struct {
+	Sha1        string
+	Message     string
+	AuthorEmail string
+	AuthorName  string
+}
+
 type PushCommits struct {
 	Len     int
-	Commits [][]string
+	Commits []*PushCommit
 }
 
 // ActionDesc accepts int that represents action operation type
 // and returns the description.
-func ActionDesc(act Actioner, avatarLink string) string {
+func ActionDesc(act Actioner) string {
 	actUserName := act.GetActUserName()
+	email := act.GetActEmail()
 	repoName := act.GetRepoName()
 	repoLink := actUserName + "/" + repoName
 	branch := act.GetBranch()
@@ -529,7 +538,7 @@ func ActionDesc(act Actioner, avatarLink string) string {
 		}
 		buf := bytes.NewBuffer([]byte("\n"))
 		for _, commit := range push.Commits {
-			buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, repoLink, commit[0], commit[0][:7], commit[1]) + "\n")
+			buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, AvatarLink(commit.AuthorEmail), repoLink, commit.Sha1, commit.Sha1[:7], commit.Message) + "\n")
 		}
 		if push.Len > 3 {
 			buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
@@ -539,7 +548,7 @@ func ActionDesc(act Actioner, avatarLink string) string {
 	case 6: // Create issue.
 		infos := strings.SplitN(content, "|", 2)
 		return fmt.Sprintf(TPL_CREATE_Issue, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
-			avatarLink, infos[1])
+			AvatarLink(email), infos[1])
 	default:
 		return "invalid type"
 	}

+ 3 - 2
modules/log/log.go

@@ -15,13 +15,14 @@ var (
 )
 
 func init() {
-	logger = logs.NewLogger(10000)
-	logger.SetLogger("console", `{"level": 0}`)
+	NewLogger(10000, "console", `{"level": 0}`)
 }
 
 func NewLogger(bufLen int64, mode, config string) {
 	Mode, Config = mode, config
 	logger = logs.NewLogger(bufLen)
+	logger.EnableFuncCallDepth(true)
+	logger.SetLogFuncCallDepth(4)
 	logger.SetLogger(mode, config)
 }
 

+ 6 - 1
modules/middleware/auth.go

@@ -7,7 +7,7 @@ package middleware
 import (
 	"net/url"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/modules/base"
 )
@@ -21,6 +21,11 @@ type ToggleOptions struct {
 
 func Toggle(options *ToggleOptions) martini.Handler {
 	return func(ctx *Context) {
+		if !base.InstallLock {
+			ctx.Redirect("/install")
+			return
+		}
+
 		if options.SignOutRequire && ctx.IsSigned && ctx.Req.RequestURI != "/" {
 			ctx.Redirect("/")
 			return

+ 15 - 3
modules/middleware/context.go

@@ -15,9 +15,10 @@ import (
 	"strings"
 	"time"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/cache"
+	"github.com/gogits/git"
 	"github.com/gogits/session"
 
 	"github.com/gogits/gogs/models"
@@ -41,11 +42,18 @@ type Context struct {
 	csrfToken string
 
 	Repo struct {
-		IsValid    bool
 		IsOwner    bool
 		IsWatching bool
+		IsBranch   bool
+		IsTag      bool
+		IsCommit   bool
 		Repository *models.Repository
 		Owner      *models.User
+		Commit     *git.Commit
+		GitRepo    *git.Repository
+		BranchName string
+		CommitId   string
+		RepoLink   string
 		CloneLink  struct {
 			SSH   string
 			HTTPS string
@@ -98,6 +106,10 @@ func (ctx *Context) Handle(status int, title string, err error) {
 	ctx.HTML(status, fmt.Sprintf("status/%d", status))
 }
 
+func (ctx *Context) Debug(msg string, args ...interface{}) {
+	log.Debug(msg, args...)
+}
+
 func (ctx *Context) GetCookie(name string) string {
 	cookie, err := ctx.Req.Cookie(name)
 	if err != nil {
@@ -257,7 +269,7 @@ func InitContext() martini.Handler {
 		if user != nil {
 			ctx.Data["SignedUser"] = user
 			ctx.Data["SignedUserId"] = user.Id
-			ctx.Data["SignedUserName"] = user.LowerName
+			ctx.Data["SignedUserName"] = user.Name
 			ctx.Data["IsAdmin"] = ctx.User.IsAdmin
 		}
 

+ 1 - 1
modules/middleware/logger.go

@@ -11,7 +11,7 @@ import (
 	"runtime"
 	"time"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 )
 
 var isWindows bool

+ 1 - 1
modules/middleware/render.go

@@ -17,7 +17,7 @@ import (
 	"path/filepath"
 	"time"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/modules/base"
 )

+ 94 - 23
modules/middleware/repo.go

@@ -9,24 +9,40 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
+
+	"github.com/gogits/git"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"
 )
 
-func RepoAssignment(redirect bool) martini.Handler {
+func RepoAssignment(redirect bool, args ...bool) martini.Handler {
 	return func(ctx *Context, params martini.Params) {
-		// assign false first
-		ctx.Data["IsRepositoryValid"] = false
+		// valid brachname
+		var validBranch bool
+		// display bare quick start if it is a bare repo
+		var displayBare bool
+
+		if len(args) >= 1 {
+			validBranch = args[0]
+		}
+
+		if len(args) >= 2 {
+			displayBare = args[1]
+		}
 
 		var (
 			user *models.User
 			err  error
 		)
 
+		userName := params["username"]
+		repoName := params["reponame"]
+		branchName := params["branchname"]
+
 		// get repository owner
-		ctx.Repo.IsOwner = ctx.IsSigned && ctx.User.LowerName == strings.ToLower(params["username"])
+		ctx.Repo.IsOwner = ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName)
 
 		if !ctx.Repo.IsOwner {
 			user, err = models.GetUserByName(params["username"])
@@ -51,10 +67,8 @@ func RepoAssignment(redirect bool) martini.Handler {
 			return
 		}
 
-		ctx.Repo.Owner = user
-
 		// get repository
-		repo, err := models.GetRepositoryByName(user.Id, params["reponame"])
+		repo, err := models.GetRepositoryByName(user.Id, repoName)
 		if err != nil {
 			if err == models.ErrRepoNotExist {
 				ctx.Handle(404, "RepoAssignment", err)
@@ -62,30 +76,87 @@ func RepoAssignment(redirect bool) martini.Handler {
 				ctx.Redirect("/")
 				return
 			}
-			ctx.Handle(200, "RepoAssignment", err)
+			ctx.Handle(404, "RepoAssignment", err)
 			return
 		}
-
-		ctx.Repo.IsValid = true
-		if ctx.User != nil {
-			ctx.Repo.IsWatching = models.IsWatching(ctx.User.Id, repo.Id)
-		}
 		ctx.Repo.Repository = repo
-		ctx.Repo.CloneLink.SSH = fmt.Sprintf("%s@%s:%s/%s.git", base.RunUser, base.Domain, user.LowerName, repo.LowerName)
-		ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s%s/%s.git", base.AppUrl, user.LowerName, repo.LowerName)
 
-		if len(params["branchname"]) == 0 {
-			params["branchname"] = "master"
+		ctx.Data["IsBareRepo"] = ctx.Repo.Repository.IsBare
+
+		gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
+		if err != nil {
+			ctx.Handle(404, "RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
+			return
 		}
-		ctx.Data["Branchname"] = params["branchname"]
+		ctx.Repo.GitRepo = gitRepo
 
-		ctx.Data["IsRepositoryValid"] = true
+		ctx.Repo.Owner = user
+		ctx.Repo.RepoLink = "/" + user.Name + "/" + repo.Name
+
+		ctx.Data["Title"] = user.Name + "/" + repo.Name
 		ctx.Data["Repository"] = repo
 		ctx.Data["Owner"] = user
-		ctx.Data["Title"] = user.Name + "/" + repo.Name
-		ctx.Data["CloneLink"] = ctx.Repo.CloneLink
-		ctx.Data["RepositoryLink"] = ctx.Data["Title"]
+		ctx.Data["RepoLink"] = ctx.Repo.RepoLink
 		ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner
+		ctx.Data["BranchName"] = ""
+
+		ctx.Repo.CloneLink.SSH = fmt.Sprintf("%s@%s:%s/%s.git", base.RunUser, base.Domain, user.LowerName, repo.LowerName)
+		ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s%s/%s.git", base.AppUrl, user.LowerName, repo.LowerName)
+		ctx.Data["CloneLink"] = ctx.Repo.CloneLink
+
+		// when repo is bare, not valid branch
+		if !ctx.Repo.Repository.IsBare && validBranch {
+		detect:
+			if len(branchName) > 0 {
+				// TODO check tag
+				if models.IsBranchExist(user.Name, repoName, branchName) {
+					ctx.Repo.IsBranch = true
+					ctx.Repo.BranchName = branchName
+
+					ctx.Repo.Commit, err = gitRepo.GetCommitOfBranch(branchName)
+					if err != nil {
+						ctx.Handle(404, "RepoAssignment invalid branch", nil)
+						return
+					}
+
+					ctx.Repo.CommitId = ctx.Repo.Commit.Oid.String()
+
+				} else if len(branchName) == 40 {
+					ctx.Repo.IsCommit = true
+					ctx.Repo.CommitId = branchName
+					ctx.Repo.BranchName = branchName
+
+					ctx.Repo.Commit, err = gitRepo.GetCommit(branchName)
+					if err != nil {
+						ctx.Handle(404, "RepoAssignment invalid commit", nil)
+						return
+					}
+				} else {
+					ctx.Handle(404, "RepoAssignment invalid repo", nil)
+					return
+				}
+
+			} else {
+				branchName = "master"
+				goto detect
+			}
+
+			ctx.Data["IsBranch"] = ctx.Repo.IsBranch
+			ctx.Data["IsCommit"] = ctx.Repo.IsCommit
+		}
+
+		// repo is bare and display enable
+		if displayBare && ctx.Repo.Repository.IsBare {
+			ctx.HTML(200, "repo/single_bare")
+			return
+		}
+
+		if ctx.IsSigned {
+			ctx.Repo.IsWatching = models.IsWatching(ctx.User.Id, repo.Id)
+		}
+
+		ctx.Data["BranchName"] = ctx.Repo.BranchName
+		ctx.Data["CommitId"] = ctx.Repo.CommitId
 		ctx.Data["IsRepositoryWatching"] = ctx.Repo.IsWatching
 	}
 }

+ 3 - 3
public/css/gogs.css

@@ -1197,13 +1197,13 @@ html, body {
     line-height: 42px;
 }
 
-#issue .issue-closed{
-    border-bottom: 3px solid #CCC;
+#issue .issue-closed, #issue .issue-opened {
+    border-bottom: 2px solid #CCC;
     margin-bottom: 24px;
     padding-bottom: 24px;
 }
 
-#issue .issue-closed .btn-danger,#issue .issue-opened .btn-success{
+#issue .issue-closed .label-danger,#issue .issue-opened .label-success{
     margin: 0 .8em;
 }
 

+ 86 - 17
public/js/app.js

@@ -56,6 +56,43 @@ var Gogits = {
         },
         toggleShow: function () {
             $(this).removeClass("hidden");
+        },
+        toggleAjax: function (successCallback) {
+            var url = $(this).data("ajax");
+            var method = $(this).data('ajax-method') || 'get';
+            var ajaxName = $(this).data('ajax-name');
+            var data = {};
+            $('[data-ajax-rel=' + ajaxName + ']').each(function () {
+                var field = $(this).data("ajax-field");
+                var t = $(this).data("ajax-val");
+                if (t == "val") {
+                    data[field] = $(this).val();
+                    return true;
+                }
+                if (t == "txt") {
+                    data[field] = $(this).text();
+                    return true;
+                }
+                if (t == "html") {
+                    data[field] = $(this).html();
+                    return true;
+                }
+                if (t == "data") {
+                    data[field] = $(this).data("ajax-data");
+                    return true;
+                }
+                return true;
+            });
+            $.ajax({
+                url: url,
+                method: method.toUpperCase(),
+                data: data,
+                success: function (d) {
+                    if (successCallback) {
+                        successCallback(d);
+                    }
+                }
+            })
         }
     })
 }(jQuery));
@@ -362,21 +399,24 @@ function initRepository() {
 
 function initInstall() {
     // database type change
-    $('#install-database').on("change", function () {
-        var val = $(this).val();
-        if (val != "sqlite") {
-            $('.server-sql').show();
-            $('.sqlite-setting').addClass("hide");
-            if (val == "pgsql") {
-                $('.pgsql-setting').removeClass("hide");
+    (function () {
+        $('#install-database').on("change", function () {
+            var val = $(this).val();
+            if (val != "sqlite") {
+                $('.server-sql').show();
+                $('.sqlite-setting').addClass("hide");
+                if (val == "pgsql") {
+                    $('.pgsql-setting').removeClass("hide");
+                } else {
+                    $('.pgsql-setting').addClass("hide");
+                }
             } else {
-                $('.pgsql-setting').addClass("hide");
+                $('.server-sql').hide();
+                $('.sqlite-setting').removeClass("hide");
             }
-        } else {
-            $('.server-sql').hide();
-            $('.sqlite-setting').removeClass("hide");
-        }
-    });
+        });
+    }());
+
 }
 
 function initIssue() {
@@ -386,11 +426,11 @@ function initIssue() {
         var $openBtn = $('#issue-open-btn');
         $('#issue-reply-content').on("keyup", function () {
             if ($(this).val().length) {
-                $closeBtn.text($closeBtn.data("text"));
-                $openBtn.text($openBtn.data("text"));
+                $closeBtn.val($closeBtn.data("text"));
+                $openBtn.val($openBtn.data("text"));
             } else {
-                $closeBtn.text($closeBtn.data("origin"));
-                $openBtn.text($openBtn.data("origin"));
+                $closeBtn.val($closeBtn.data("origin"));
+                $openBtn.val($openBtn.data("origin"));
             }
         });
     }());
@@ -406,6 +446,35 @@ function initIssue() {
             $('#issue-edit-title,#issue-edit-content,.issue-edit-cancel,.issue-edit-save').toggleHide();
         })
     }());
+
+    // issue ajax update
+    (function () {
+        $('.issue-edit-save').on("click", function () {
+            $(this).toggleAjax(function (json) {
+                if (json.ok) {
+                    $('.issue-head h1.title').text(json.title);
+                    $('.issue-main > .issue-content .content').html(json.content);
+                    $('.issue-edit-cancel').trigger("click");
+                }
+            });
+        });
+    }());
+
+    // issue ajax preview
+    (function () {
+        $('[data-ajax-name=issue-preview]').on("click", function () {
+            var $this = $(this);
+            $this.toggleAjax(function (json) {
+                if (json.ok) {
+                    $($this.data("preview")).html(json.content);
+                }
+            })
+        });
+        $('.issue-write a[data-toggle]').on("click", function () {
+            $('.issue-preview-content').html("loading...");
+        });
+    }())
+
 }
 
 (function ($) {

+ 1 - 1
routers/admin/admin.go

@@ -10,7 +10,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"

+ 1 - 1
routers/admin/user.go

@@ -7,7 +7,7 @@ package admin
 import (
 	"strings"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"

+ 18 - 0
routers/api/v1/miscellaneous.go

@@ -0,0 +1,18 @@
+// 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 v1
+
+import (
+	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/middleware"
+)
+
+func Markdown(ctx *middleware.Context) {
+	content := ctx.Query("content")
+	ctx.Render.JSON(200, map[string]interface{}{
+		"ok":      true,
+		"content": string(base.RenderMarkdown([]byte(content), "")),
+	})
+}

+ 1 - 1
routers/dev/template.go

@@ -5,7 +5,7 @@
 package dev
 
 import (
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"

+ 166 - 5
routers/install.go

@@ -6,27 +6,188 @@ package routers
 
 import (
 	"errors"
+	"os"
+	"strings"
+
+	"github.com/Unknwon/goconfig"
+	"github.com/go-martini/martini"
+	"github.com/lunny/xorm"
 
 	"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/mailer"
 	"github.com/gogits/gogs/modules/middleware"
 )
 
-func Install(ctx *middleware.Context) {
+// Check run mode(Default of martini is Dev).
+func checkRunMode() {
+	switch base.Cfg.MustValue("", "RUN_MODE") {
+	case "prod":
+		martini.Env = martini.Prod
+	case "test":
+		martini.Env = martini.Test
+	}
+	log.Info("Run Mode: %s", strings.Title(martini.Env))
+}
+
+// GlobalInit is for global configuration reload-able.
+func GlobalInit() {
+	base.NewConfigContext()
+	mailer.NewMailerContext()
+	models.LoadModelsConfig()
+	models.LoadRepoConfig()
+	models.NewRepoContext()
+
+	if base.InstallLock {
+		if err := models.NewEngine(); err != nil {
+			log.Error("%v", err)
+			os.Exit(2)
+		}
+
+		models.HasEngine = true
+	}
+	base.NewServices()
+	checkRunMode()
+}
+
+func Install(ctx *middleware.Context, form auth.InstallForm) {
 	if base.InstallLock {
 		ctx.Handle(404, "install.Install", errors.New("Installation is prohibited"))
 		return
 	}
 
 	ctx.Data["Title"] = "Install"
-	ctx.Data["DbCfg"] = models.DbCfg
-	ctx.Data["RepoRootPath"] = base.RepoRootPath
-	ctx.Data["RunUser"] = base.RunUser
-	ctx.Data["AppUrl"] = base.AppUrl
 	ctx.Data["PageIsInstall"] = true
 
 	if ctx.Req.Method == "GET" {
+		// Get and assign value to install form.
+		if len(form.Host) == 0 {
+			form.Host = models.DbCfg.Host
+		}
+		if len(form.User) == 0 {
+			form.User = models.DbCfg.User
+		}
+		if len(form.Passwd) == 0 {
+			form.Passwd = models.DbCfg.Pwd
+		}
+		if len(form.DatabaseName) == 0 {
+			form.DatabaseName = models.DbCfg.Name
+		}
+		if len(form.DatabasePath) == 0 {
+			form.DatabasePath = models.DbCfg.Path
+		}
+
+		if len(form.RepoRootPath) == 0 {
+			form.RepoRootPath = base.RepoRootPath
+		}
+		if len(form.RunUser) == 0 {
+			form.RunUser = base.RunUser
+		}
+		if len(form.Domain) == 0 {
+			form.Domain = base.Domain
+		}
+		if len(form.AppUrl) == 0 {
+			form.AppUrl = base.AppUrl
+		}
+
+		auth.AssignForm(form, ctx.Data)
+		ctx.HTML(200, "install")
+		return
+	}
+
+	if ctx.HasError() {
 		ctx.HTML(200, "install")
 		return
 	}
+
+	// Pass basic check, now test configuration.
+	// Test database setting.
+	dbTypes := map[string]string{"mysql": "mysql", "pgsql": "postgres", "sqlite": "sqlite3"}
+	models.DbCfg.Type = dbTypes[form.Database]
+	models.DbCfg.Host = form.Host
+	models.DbCfg.User = form.User
+	models.DbCfg.Pwd = form.Passwd
+	models.DbCfg.Name = form.DatabaseName
+	models.DbCfg.SslMode = form.SslMode
+	models.DbCfg.Path = form.DatabasePath
+
+	// Set test engine.
+	var x *xorm.Engine
+	if err := models.NewTestEngine(x); err != nil {
+		if strings.Contains(err.Error(), `Unknown database type: sqlite3`) {
+			ctx.RenderWithErr("Your release version does not support SQLite3, please download the official binary version "+
+				"from https://github.com/gogits/gogs/wiki/Install-from-binary, NOT the gobuild version.", "install", &form)
+		} else {
+			ctx.RenderWithErr("Database setting is not correct: "+err.Error(), "install", &form)
+		}
+		return
+	}
+
+	// Test repository root path.
+	if err := os.MkdirAll(form.RepoRootPath, os.ModePerm); err != nil {
+		ctx.RenderWithErr("Repository root path is invalid: "+err.Error(), "install", &form)
+		return
+	}
+
+	// Check run user.
+	curUser := os.Getenv("USERNAME")
+	if len(curUser) == 0 {
+		curUser = os.Getenv("USER")
+	}
+	// Does not check run user when the install lock is off.
+	if form.RunUser != curUser {
+		ctx.RenderWithErr("Run user isn't the current user: "+form.RunUser+" -> "+curUser, "install", &form)
+		return
+	}
+
+	// Save settings.
+	base.Cfg.SetValue("database", "DB_TYPE", models.DbCfg.Type)
+	base.Cfg.SetValue("database", "HOST", models.DbCfg.Host)
+	base.Cfg.SetValue("database", "NAME", models.DbCfg.Name)
+	base.Cfg.SetValue("database", "USER", models.DbCfg.User)
+	base.Cfg.SetValue("database", "PASSWD", models.DbCfg.Pwd)
+	base.Cfg.SetValue("database", "SSL_MODE", models.DbCfg.SslMode)
+	base.Cfg.SetValue("database", "PATH", models.DbCfg.Path)
+
+	base.Cfg.SetValue("repository", "ROOT", form.RepoRootPath)
+	base.Cfg.SetValue("", "RUN_USER", form.RunUser)
+	base.Cfg.SetValue("server", "DOMAIN", form.Domain)
+	base.Cfg.SetValue("server", "ROOT_URL", form.AppUrl)
+
+	if len(strings.TrimSpace(form.SmtpHost)) > 0 {
+		base.Cfg.SetValue("mailer", "ENABLED", "true")
+		base.Cfg.SetValue("mailer", "HOST", form.SmtpHost)
+		base.Cfg.SetValue("mailer", "USER", form.SmtpEmail)
+		base.Cfg.SetValue("mailer", "PASSWD", form.SmtpPasswd)
+
+		base.Cfg.SetValue("service", "REGISTER_EMAIL_CONFIRM", base.ToStr(form.RegisterConfirm == "on"))
+		base.Cfg.SetValue("service", "ENABLE_NOTIFY_MAIL", base.ToStr(form.MailNotify == "on"))
+	}
+
+	base.Cfg.SetValue("", "RUN_MODE", "prod")
+
+	base.Cfg.SetValue("security", "INSTALL_LOCK", "true")
+
+	os.MkdirAll("custom/conf", os.ModePerm)
+	if err := goconfig.SaveConfigFile(base.Cfg, "custom/conf/app.ini"); err != nil {
+		ctx.RenderWithErr("Fail to save configuration: "+err.Error(), "install", &form)
+		return
+	}
+
+	GlobalInit()
+
+	// Create admin account.
+	if _, err := models.RegisterUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd,
+		IsAdmin: true, IsActive: true}); err != nil {
+		if err != models.ErrUserAlreadyExist {
+			ctx.RenderWithErr("Admin account setting is invalid: "+err.Error(), "install", &form)
+			return
+		}
+		log.Info("Admin account already exist")
+	}
+
+	log.Info("First-time run install finished!")
+	ctx.Redirect("/user/login")
 }

+ 4 - 9
routers/repo/branch.go

@@ -5,27 +5,22 @@
 package repo
 
 import (
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
+
 	"github.com/gogits/gogs/models"
 	"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"])
+	brs, err := models.GetBranches(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
 	if err != nil {
-		ctx.Handle(200, "repo.Branches", err)
+		ctx.Handle(404, "repo.Branches", err)
 		return
 	} else if len(brs) == 0 {
 		ctx.Handle(404, "repo.Branches", nil)
 		return
 	}
 
-	ctx.Data["Username"] = params["username"]
-	ctx.Data["Reponame"] = params["reponame"]
 	ctx.Data["Branches"] = brs
 	ctx.Data["IsRepoToolbarBranches"] = true
 

+ 7 - 13
routers/repo/commit.go

@@ -8,7 +8,7 @@ import (
 	"container/list"
 	"path"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/base"
@@ -50,16 +50,12 @@ func Commits(ctx *middleware.Context, params martini.Params) {
 }
 
 func Diff(ctx *middleware.Context, params martini.Params) {
-	userName := params["username"]
-	repoName := params["reponame"]
-	branchName := params["branchname"]
-	commitId := params["commitid"]
+	userName := ctx.Repo.Owner.Name
+	repoName := ctx.Repo.Repository.Name
+	branchName := ctx.Repo.BranchName
+	commitId := ctx.Repo.CommitId
 
-	commit, err := models.GetCommit(userName, repoName, branchName, commitId)
-	if err != nil {
-		ctx.Handle(404, "repo.Diff", err)
-		return
-	}
+	commit := ctx.Repo.Commit
 
 	diff, err := models.GetDiff(models.RepoPath(userName, repoName), commitId)
 	if err != nil {
@@ -85,11 +81,9 @@ func Diff(ctx *middleware.Context, params martini.Params) {
 		return isImage
 	}
 
-	shortSha := params["commitid"][:10]
 	ctx.Data["IsImageFile"] = isImageFile
-	ctx.Data["Title"] = commit.Message() + " · " + shortSha
+	ctx.Data["Title"] = commit.Message() + " · " + base.ShortSha(commitId)
 	ctx.Data["Commit"] = commit
-	ctx.Data["ShortSha"] = shortSha
 	ctx.Data["Diff"] = diff
 	ctx.Data["IsRepoToolbarCommits"] = true
 	ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", commitId)

+ 49 - 40
routers/repo/issue.go

@@ -7,8 +7,9 @@ package repo
 import (
 	"fmt"
 	"net/url"
+	"strings"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
@@ -19,10 +20,6 @@ import (
 )
 
 func Issues(ctx *middleware.Context) {
-	if !ctx.Repo.IsValid {
-		ctx.Handle(404, "issue.Issues(invalid repo):", nil)
-	}
-
 	ctx.Data["Title"] = "Issues"
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = true
@@ -79,10 +76,6 @@ func Issues(ctx *middleware.Context) {
 }
 
 func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
-	if !ctx.Repo.IsValid {
-		ctx.Handle(404, "issue.CreateIssue(invalid repo):", nil)
-	}
-
 	ctx.Data["Title"] = "Create issue"
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = false
@@ -105,7 +98,7 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
 	}
 
 	// Notify watchers.
-	if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name,
+	if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email,
 		OpType: models.OP_CREATE_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
 		RepoId: ctx.Repo.Repository.Id, RepoName: ctx.Repo.Repository.Name, RefName: ""}); err != nil {
 		ctx.Handle(200, "issue.CreateIssue", err)
@@ -125,10 +118,6 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
 }
 
 func ViewIssue(ctx *middleware.Context, params martini.Params) {
-	if !ctx.Repo.IsValid {
-		ctx.Handle(404, "issue.ViewIssue(invalid repo):", nil)
-	}
-
 	index, err := base.StrTo(params["index"]).Int()
 	if err != nil {
 		ctx.Handle(404, "issue.ViewIssue", err)
@@ -152,7 +141,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
 		return
 	}
 	issue.Poster = u
-	issue.Content = string(base.RenderMarkdown([]byte(issue.Content), ""))
+	issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ""))
 
 	// Get comments.
 	comments, err := models.GetIssueComments(issue.Id)
@@ -175,16 +164,13 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
 	ctx.Data["Title"] = issue.Name
 	ctx.Data["Issue"] = issue
 	ctx.Data["Comments"] = comments
+	ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id)
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = false
 	ctx.HTML(200, "issue/view")
 }
 
 func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
-	if !ctx.Repo.IsValid {
-		ctx.Handle(404, "issue.UpdateIssue(invalid repo):", nil)
-	}
-
 	index, err := base.StrTo(params["index"]).Int()
 	if err != nil {
 		ctx.Handle(404, "issue.UpdateIssue", err)
@@ -216,22 +202,21 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
 		return
 	}
 
-	ctx.Data["Title"] = issue.Name
-	ctx.Data["Issue"] = issue
+	ctx.JSON(200, map[string]interface{}{
+		"ok":      true,
+		"title":   issue.Name,
+		"content": string(base.RenderMarkdown([]byte(issue.Content), "")),
+	})
 }
 
 func Comment(ctx *middleware.Context, params martini.Params) {
-	if !ctx.Repo.IsValid {
-		ctx.Handle(404, "issue.Comment(invalid repo):", nil)
-	}
-
-	index, err := base.StrTo(ctx.Query("issueIndex")).Int()
+	index, err := base.StrTo(ctx.Query("issueIndex")).Int64()
 	if err != nil {
-		ctx.Handle(404, "issue.Comment", err)
+		ctx.Handle(404, "issue.Comment(get index)", err)
 		return
 	}
 
-	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(index))
+	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index)
 	if err != nil {
 		if err == models.ErrIssueNotExist {
 			ctx.Handle(404, "issue.Comment", err)
@@ -241,22 +226,46 @@ func Comment(ctx *middleware.Context, params martini.Params) {
 		return
 	}
 
-	content := ctx.Query("content")
-	if len(content) == 0 {
-		ctx.Handle(404, "issue.Comment", err)
-		return
+	// Check if issue owner changes the status of issue.
+	var newStatus string
+	if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id {
+		newStatus = ctx.Query("change_status")
+	}
+	if len(newStatus) > 0 {
+		if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) ||
+			(strings.Contains(newStatus, "Close") && !issue.IsClosed) {
+			issue.IsClosed = !issue.IsClosed
+			if err = models.UpdateIssue(issue); err != nil {
+				ctx.Handle(200, "issue.Comment(update issue status)", err)
+				return
+			}
+
+			cmtType := models.IT_CLOSE
+			if !issue.IsClosed {
+				cmtType = models.IT_REOPEN
+			}
+
+			if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, ""); err != nil {
+				ctx.Handle(200, "issue.Comment(create status change comment)", err)
+				return
+			}
+			log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed)
+		}
 	}
 
-	switch params["action"] {
-	case "new":
-		if err = models.CreateComment(ctx.User.Id, issue.Id, 0, 0, content); err != nil {
-			ctx.Handle(500, "issue.Comment(create comment)", err)
+	content := ctx.Query("content")
+	if len(content) > 0 {
+		switch params["action"] {
+		case "new":
+			if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.IT_PLAIN, content); err != nil {
+				ctx.Handle(500, "issue.Comment(create comment)", err)
+				return
+			}
+			log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id)
+		default:
+			ctx.Handle(404, "issue.Comment", err)
 			return
 		}
-		log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id)
-	default:
-		ctx.Handle(404, "issue.Comment", err)
-		return
 	}
 
 	ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", ctx.User.Name, ctx.Repo.Repository.Name, index))

+ 1 - 1
routers/repo/pull.go

@@ -5,7 +5,7 @@
 package repo
 
 import (
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/modules/middleware"
 )

+ 17 - 55
routers/repo/repo.go

@@ -9,7 +9,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/webdav"
 
@@ -53,20 +53,20 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) {
 }
 
 func Single(ctx *middleware.Context, params martini.Params) {
-	if !ctx.Repo.IsValid {
-		return
-	}
+	branchName := ctx.Repo.BranchName
+	commitId := ctx.Repo.CommitId
+	userName := ctx.Repo.Owner.Name
+	repoName := ctx.Repo.Repository.Name
 
-	branchName := params["branchname"]
-	userName := params["username"]
-	repoName := params["reponame"]
+	repoLink := ctx.Repo.RepoLink
+	branchLink := ctx.Repo.RepoLink + "/src/" + branchName
+	rawLink := ctx.Repo.RepoLink + "/raw/" + branchName
 
 	// 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/" + branchName + "/" + treename[:len(treename)-1])
+		ctx.Redirect(repoLink + "/src/" + branchName + "/" + treename[:len(treename)-1])
 		return
 	}
 
@@ -77,30 +77,21 @@ func Single(ctx *middleware.Context, params martini.Params) {
 	if err != nil {
 		ctx.Handle(404, "repo.Single(GetBranches)", err)
 		return
-	} else if ctx.Repo.Repository.IsBare {
-		ctx.Data["IsBareRepo"] = true
-		ctx.HTML(200, "repo/single")
-		return
 	}
+
 	ctx.Data["Branches"] = brs
 
-	var commitId string
-	isViewBranch := models.IsBranchExist(userName, repoName, branchName)
-	if !isViewBranch {
-		commitId = branchName
-	}
+	isViewBranch := ctx.Repo.IsBranch
 	ctx.Data["IsViewBranch"] = isViewBranch
 
 	repoFile, err := models.GetTargetFile(userName, repoName,
 		branchName, commitId, treename)
+
 	if err != nil && err != models.ErrRepoFileNotExist {
 		ctx.Handle(404, "repo.Single(GetTargetFile)", err)
 		return
 	}
 
-	branchLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/src/" + branchName
-	rawLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/raw/" + branchName
-
 	if len(treename) != 0 && repoFile == nil {
 		ctx.Handle(404, "repo.Single", nil)
 		return
@@ -142,8 +133,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
 
 	} else {
 		// Directory and file list.
-		files, err := models.GetReposFiles(userName, repoName,
-			branchName, commitId, treename)
+		files, err := models.GetReposFiles(userName, repoName, ctx.Repo.CommitId, treename)
 		if err != nil {
 			ctx.Handle(404, "repo.Single(GetReposFiles)", err)
 			return
@@ -200,18 +190,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
 		}
 	}
 
-	// Get latest commit according username and repo name.
-	commit, err := models.GetCommit(userName, repoName,
-		branchName, commitId)
-	if err != nil {
-		log.Error("repo.Single(GetCommit): %v", err)
-		ctx.Handle(404, "repo.Single(GetCommit)", err)
-		return
-	}
-	ctx.Data["LastCommit"] = commit
-
-	ctx.Data["CommitId"] = commitId
-
+	ctx.Data["LastCommit"] = ctx.Repo.Commit
 	ctx.Data["Paths"] = Paths
 	ctx.Data["Treenames"] = treenames
 	ctx.Data["BranchLink"] = branchLink
@@ -219,11 +198,6 @@ func Single(ctx *middleware.Context, params martini.Params) {
 }
 
 func SingleDownload(ctx *middleware.Context, params martini.Params) {
-	if !ctx.Repo.IsValid {
-		ctx.Handle(404, "repo.SingleDownload", nil)
-		return
-	}
-
 	// Get tree path
 	treename := params["_1"]
 
@@ -263,10 +237,6 @@ func SingleDownload(ctx *middleware.Context, params martini.Params) {
 }
 
 func Http(ctx *middleware.Context, params martini.Params) {
-	/*if !ctx.Repo.IsValid {
-		return
-	}*/
-
 	// TODO: access check
 
 	username := params["username"]
@@ -276,11 +246,9 @@ func Http(ctx *middleware.Context, params martini.Params) {
 	}
 
 	prefix := path.Join("/", username, params["reponame"])
-	server := &webdav.Server{
-		Fs:         webdav.Dir(models.RepoPath(username, reponame)),
-		TrimPrefix: prefix,
-		Listings:   true,
-	}
+	server := webdav.NewServer(
+		models.RepoPath(username, reponame),
+		prefix, true)
 
 	server.ServeHTTP(ctx.ResponseWriter, ctx.Req)
 }
@@ -293,12 +261,6 @@ func Setting(ctx *middleware.Context, params martini.Params) {
 
 	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

+ 9 - 9
routers/user/user.go

@@ -9,7 +9,7 @@ import (
 	"net/url"
 	"strings"
 
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
@@ -120,7 +120,7 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) {
 		return
 	}
 
-	if hasErr, ok := ctx.Data["HasError"]; ok && hasErr.(bool) {
+	if ctx.HasError() {
 		ctx.HTML(200, "user/signin")
 		return
 	}
@@ -279,7 +279,7 @@ func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
 	feeds := make([]string, len(actions))
 	for i := range actions {
 		feeds[i] = fmt.Sprintf(TPL_FEED, base.ActionIcon(actions[i].OpType),
-			base.TimeSince(actions[i].Created), base.ActionDesc(actions[i], ctx.User.AvatarLink()))
+			base.TimeSince(actions[i].Created), base.ActionDesc(actions[i]))
 	}
 	ctx.JSON(200, &feeds)
 }
@@ -308,17 +308,19 @@ func Issues(ctx *middleware.Context) {
 
 	showRepos := make([]models.Repository, 0, len(repos))
 
-	var closedIssueCount, createdByCount int
+	isShowClosed := ctx.Query("state") == "closed"
+	var closedIssueCount, createdByCount, allIssueCount int
 
 	// Get all issues.
 	allIssues := make([]models.Issue, 0, 5*len(repos))
 	for i, repo := range repos {
-		issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, false, false, "", "")
+		issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, isShowClosed, false, "", "")
 		if err != nil {
 			ctx.Handle(200, "user.Issues(get issues)", err)
 			return
 		}
 
+		allIssueCount += repo.NumIssues
 		closedIssueCount += repo.NumClosedIssues
 
 		// Set repository information to issues.
@@ -330,12 +332,10 @@ func Issues(ctx *middleware.Context) {
 		repos[i].NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
 		if repos[i].NumOpenIssues > 0 {
 			showRepos = append(showRepos, repos[i])
-
 		}
 	}
 
 	showIssues := make([]models.Issue, 0, len(allIssues))
-	isShowClosed := ctx.Query("state") == "closed"
 	ctx.Data["IsShowClosed"] = isShowClosed
 
 	// Get posters and filter issues.
@@ -361,9 +361,9 @@ func Issues(ctx *middleware.Context) {
 
 	ctx.Data["Repos"] = showRepos
 	ctx.Data["Issues"] = showIssues
-	ctx.Data["AllIssueCount"] = len(allIssues)
+	ctx.Data["AllIssueCount"] = allIssueCount
 	ctx.Data["ClosedIssueCount"] = closedIssueCount
-	ctx.Data["OpenIssueCount"] = len(allIssues) - closedIssueCount
+	ctx.Data["OpenIssueCount"] = allIssueCount - closedIssueCount
 	ctx.Data["CreatedByCount"] = createdByCount
 	ctx.HTML(200, "issue/user")
 }

+ 35 - 30
serve.go

@@ -74,29 +74,33 @@ func In(b string, sl map[string]int) bool {
 func runServ(k *cli.Context) {
 	execDir, _ := base.ExecDir()
 	newLogger(execDir)
-	log.Trace("new serv request " + log.Mode + ":" + log.Config)
 
 	base.NewConfigContext()
 	models.LoadModelsConfig()
+
+	if models.UseSQLite3 {
+		os.Chdir(execDir)
+	}
+
 	models.SetEngine()
 
 	keys := strings.Split(os.Args[2], "-")
 	if len(keys) != 2 {
-		fmt.Println("auth file format error")
+		println("auth file format error")
 		log.Error("auth file format error")
 		return
 	}
 
 	keyId, err := strconv.ParseInt(keys[1], 10, 64)
 	if err != nil {
-		fmt.Println("auth file format error")
-		log.Error("auth file format error")
+		println("auth file format error")
+		log.Error("auth file format error", err)
 		return
 	}
 	user, err := models.GetUserByKeyId(keyId)
 	if err != nil {
-		fmt.Println("You have no right to access")
-		log.Error("You have no right to access")
+		println("You have no right to access")
+		log.Error("SSH visit error: %v", err)
 		return
 	}
 
@@ -107,13 +111,14 @@ func runServ(k *cli.Context) {
 	}
 
 	verb, args := parseCmd(cmd)
-	rRepo := strings.Trim(args, "'")
-	rr := strings.SplitN(rRepo, "/", 2)
+	repoPath := strings.Trim(args, "'")
+	rr := strings.SplitN(repoPath, "/", 2)
 	if len(rr) != 2 {
 		println("Unavilable repository", args)
 		log.Error("Unavilable repository %v", args)
 		return
 	}
+	repoUserName := rr[0]
 	repoName := rr[1]
 	if strings.HasSuffix(repoName, ".git") {
 		repoName = repoName[:len(repoName)-4]
@@ -122,46 +127,46 @@ func runServ(k *cli.Context) {
 	isWrite := In(verb, COMMANDS_WRITE)
 	isRead := In(verb, COMMANDS_READONLY)
 
-	/*//repo, err := models.GetRepositoryByName(user.Id, repoName)
-	//var isExist bool = true
+	repoUser, err := models.GetUserByName(repoUserName)
 	if err != nil {
-		if err == models.ErrRepoNotExist {
-			//isExist = false
-			if isRead {
-				println("Repository", user.Name+"/"+repoName, "is not exist")
-				log.Error("Repository " + user.Name + "/" + repoName + " is not exist")
-				return
-			}
-		} else {
-			println("Get repository error:", err)
-			log.Error("Get repository error: " + err.Error())
-			return
-		}
-	}*/
+		fmt.Println("You have no right to access")
+		log.Error("Get user failed", err)
+		return
+	}
 
 	// access check
 	switch {
 	case isWrite:
-		has, err := models.HasAccess(user.Name, repoName, models.AU_WRITABLE)
+		has, err := models.HasAccess(user.LowerName, path.Join(repoUserName, repoName), models.AU_WRITABLE)
 		if err != nil {
 			println("Inernel error:", err)
 			log.Error(err.Error())
 			return
-		}
-		if !has {
+		} else if !has {
 			println("You have no right to write this repository")
-			log.Error("You have no right to access this repository")
+			log.Error("User %s has no right to write repository %s", user.Name, repoPath)
 			return
 		}
 	case isRead:
-		has, err := models.HasAccess(user.Name, repoName, models.AU_READABLE)
+		repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
+		if err != nil {
+			println("Get repository error:", err)
+			log.Error("Get repository error: " + err.Error())
+			return
+		}
+
+		if !repo.IsPrivate {
+			break
+		}
+
+		has, err := models.HasAccess(user.Name, repoPath, models.AU_READABLE)
 		if err != nil {
 			println("Inernel error")
 			log.Error(err.Error())
 			return
 		}
 		if !has {
-			has, err = models.HasAccess(user.Name, repoName, models.AU_WRITABLE)
+			has, err = models.HasAccess(user.Name, repoPath, models.AU_WRITABLE)
 			if err != nil {
 				println("Inernel error")
 				log.Error(err.Error())
@@ -184,7 +189,7 @@ func runServ(k *cli.Context) {
 	os.Setenv("userId", strconv.Itoa(int(user.Id)))
 	os.Setenv("repoName", repoName)
 
-	gitcmd := exec.Command(verb, rRepo)
+	gitcmd := exec.Command(verb, repoPath)
 	gitcmd.Dir = base.RepoRootPath
 	gitcmd.Stdout = os.Stdout
 	gitcmd.Stdin = os.Stdin

+ 2 - 0
start.bat

@@ -0,0 +1,2 @@
+@echo off
+gogs.exe web

+ 6 - 0
start.sh

@@ -0,0 +1,6 @@
+#!/bin/bash -
+#
+# start gogs web
+#
+cd "$(dirname $0)"
+./gogs web

+ 32 - 27
templates/install.tmpl

@@ -3,9 +3,8 @@
     <form action="/install" method="post" class="form-horizontal card" id="install-card">
         {{.CsrfTokenHtml}}
         <h3>Install Steps For First-time Run</h3>
-
         <div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>
-        <p class="help-block text-center">Gogs requires MySQL or PostgreSQL based on your choice</p>
+        <p class="help-block text-center">Gogs requires MySQL or PostgreSQL, SQLite3 only available for official binary version</p>
         <div class="form-group">
             <label class="col-md-3 control-label">Database Type: </label>
             <div class="col-md-8">
@@ -16,26 +15,28 @@
                 </select>
             </div>
         </div>
+
         <div class="server-sql">
             <div class="form-group">
                 <label class="col-md-3 control-label">Host: </label>
-
                 <div class="col-md-8">
-                    <input name="host" class="form-control" placeholder="Type database server host, leave blank to keep default" value="{{.DbCfg.Host}}" required="required">
+                    <input name="host" class="form-control" placeholder="Type database server host" value="{{.host}}">
                 </div>
             </div>
+
             <div class="form-group">
                 <label class="col-md-3 control-label">User: </label>
 
                 <div class="col-md-8">
-                    <input name="user" class="form-control" placeholder="Type database username" required="required" value="{{.DbCfg.User}}">
+                    <input name="user" class="form-control" placeholder="Type database username" value="{{.user}}">
                 </div>
             </div>
+
             <div class="form-group">
                 <label class="col-md-3 control-label">Password: </label>
 
                 <div class="col-md-8">
-                    <input name="passwd" type="password" class="form-control" placeholder="Type database password" required="required" value="{{.DbCfg.Pwd}}">
+                    <input name="passwd" type="password" class="form-control" placeholder="Type database password" value="{{.passwd}}">
                 </div>
             </div>
 
@@ -43,7 +44,7 @@
                 <label class="col-md-3 control-label">Database Name: </label>
 
                 <div class="col-md-8">
-                    <input name="database" type="text" class="form-control" placeholder="Type mysql database name" value="{{.DbCfg.Name}}" required="required">
+                    <input name="database_name" type="text" class="form-control" placeholder="Type mysql database name" value="{{.database_name}}">
                     <p class="help-block">Recommend use INNODB engine with utf8_general_ci charset.</p>
                 </div>
             </div>
@@ -59,12 +60,13 @@
                 </div>
             </div>
         </div>
+
         <div class="sqlite-setting hide">
             <div class="form-group">
                 <label class="col-md-3 control-label">Path: </label>
 
                 <div class="col-md-8">
-                    <input name="path" class="form-control" placeholder="Type sqlite3 file path" value="{{.DbCfg.Path}}">
+                    <input name="database_path" class="form-control" placeholder="Type sqlite3 file path" value="{{.database_path}}">
                     <p class="help-block">The file path of SQLite3 database.</p>
                 </div>
             </div>
@@ -73,12 +75,11 @@
         <hr/>
 
         <p class="help-block text-center">General Settings of Gogs</p>
-
         <div class="form-group">
             <label class="col-md-3 control-label">Repository Path: </label>
 
             <div class="col-md-8">
-                <input name="repo-path" type="text" class="form-control" placeholder="Type your repository directory" value="{{.RepoRootPath}}" required="required">
+                <input name="repo_path" type="text" class="form-control" placeholder="Type your repository directory" value="{{.repo_path}}" required="required">
 
                 <p class="help-block">The git copy of each repository is saved in this directory.</p>
             </div>
@@ -88,16 +89,25 @@
             <label class="col-md-3 control-label">Run User: </label>
 
             <div class="col-md-8">
-                <input name="system-user" type="text" class="form-control" placeholder="Type system user name" value="{{.RunUser}}" required="required">
+                <input name="run_user" type="text" class="form-control" placeholder="Type system user name" value="{{.run_user}}" required="required">
                 <p class="help-block">The user has access to visit and run Gogs.</p>
             </div>
         </div>
         
+        <div class="form-group">
+            <label class="col-md-3 control-label">Domain: </label>
+
+            <div class="col-md-8">
+                <input name="domain" type="text" class="form-control" placeholder="Type your domain name" value="{{.domain}}" required="required">
+                <p class="help-block">This affects SSH clone URL.</p>
+            </div>
+        </div>
+        
         <div class="form-group">
             <label class="col-md-3 control-label">App URL: </label>
 
             <div class="col-md-8">
-                <input name="app_url" type="text" class="form-control" placeholder="Type app root URL " value="{{.AppUrl}}" required="required">
+                <input name="app_url" type="text" class="form-control" placeholder="Type app root URL" value="{{.app_url}}" required="required">
                 <p class="help-block">This affects HTTP/HTTPS clone URL and somewhere in e-mail.</p>
             </div>
         </div>
@@ -105,35 +115,30 @@
         <hr/>
 
         <p class="help-block text-center">Admin Account Settings</p>
-
         <div class="form-group">
             <label class="col-md-3 control-label">Username: </label>
-
             <div class="col-md-8">
-                <input name="admin_name" type="text" class="form-control" placeholder="Type admin user name" value="admin" required="required">
+                <input name="admin_name" type="text" class="form-control" placeholder="Type admin user name" value="{{.admin_name}}" required="required">
             </div>
         </div>
 
-        <div class="form-group">
+        <div class="form-group {{if .Err_AdminPasswd}}has-error has-feedback{{end}}">
             <label class="col-md-3 control-label">Password: </label>
-
             <div class="col-md-8">
-                <input name="admin_pwd" type="password" class="form-control" placeholder="Type admin user password" required="required">
+                <input name="admin_pwd" type="password" class="form-control" placeholder="Type admin user password" value="{{.admin_pwd}}" required="required">
             </div>
         </div>
 
-        <div class="form-group">
+        <div class="form-group {{if .Err_AdminEmail}}has-error has-feedback{{end}}">
             <label class="col-md-3 control-label">E-mail: </label>
-
             <div class="col-md-8">
-                <input name="admin_email" type="text" class="form-control" placeholder="Type admin user e-mail" required="required">
+                <input name="admin_email" type="text" class="form-control" placeholder="Type admin user e-mail" value="{{.admin_email}}" required="required">
             </div>
         </div>
 
         <hr/>
 
         <div class="form-group text-center">
-            <!-- <button class="btn btn-primary btn-lg">Test Configuration</button> -->
             <button class="btn btn-danger btn-lg">Install Gogs</button>
             <button class="btn btn-default btn-sm" type="button" data-toggle="modal" data-target="#advance-options-modal">
                 Advanced Options
@@ -151,21 +156,21 @@
                             <label class="col-md-3 control-label">SMTP Host: </label>
 
                             <div class="col-md-8">
-                                <input name="smtp_host" type="text" class="form-control" placeholder="Type SMTP host address">
+                                <input name="smtp_host" type="text" class="form-control" placeholder="Type SMTP host address" value="{{.smtp_host}}">
                             </div>
                         </div>
                         <div class="form-group">
                             <label class="col-md-3 control-label">Email: </label>
 
                             <div class="col-md-8">
-                                <input name="mailer_user" type="text" class="form-control" placeholder="Type SMTP user e-mail address">
+                                <input name="mailer_user" type="text" class="form-control" placeholder="Type SMTP user e-mail address" value="{{.mailer_user}}">
                             </div>
                         </div>
                         <div class="form-group">
                             <label class="col-md-3 control-label">Password: </label>
 
                             <div class="col-md-8">
-                                <input name="mailer_pwd" type="password" class="form-control" placeholder="Type SMTP user password">
+                                <input name="mailer_pwd" type="password" class="form-control" placeholder="Type SMTP user password" value="{{.mailer_pwd}}">
                             </div>
                         </div>
                         <hr/>
@@ -175,7 +180,7 @@
                             <div class="col-md-offset-3 col-md-7">
                                 <div class="checkbox">
                                     <label>
-                                        <input name="register_confirm" type="checkbox">
+                                        <input name="register_confirm" type="checkbox" {{if .register_confirm}}checked{{end}}>
                                         <strong>Enable Register Confirmation</strong>
                                     </label>
                                 </div>
@@ -186,7 +191,7 @@
                             <div class="col-md-offset-3 col-md-7">
                                 <div class="checkbox">
                                     <label>
-                                        <input name="mail_notify" type="checkbox">
+                                        <input name="mail_notify" type="checkbox" {{if .mail_notify}}checked{{end}}>
                                         <strong>Enable Mail Notification</strong>
                                     </label>
                                 </div>

+ 7 - 7
templates/issue/create.tmpl

@@ -4,7 +4,7 @@
 {{template "repo/toolbar" .}}
 <div id="body" class="container">
     <div id="issue">
-        <form class="form" action="/{{.RepositoryLink}}/issues/new" method="post" id="issue-create-form">
+        <form class="form" action="{{.RepoLink}}/issues/new" method="post" id="issue-create-form">
             {{.CsrfTokenHtml}}
             <div class="col-md-1">
                 <img class="avatar" src="{{.SignedUser.AvatarLink}}" alt=""/>
@@ -15,19 +15,19 @@
                 </div>
                 <div class="form-group panel-body">
                     <div class="md-help pull-right"><!-- todo help link -->
-                        Content with <a href="#">Markdown</a>
+                        Content with <a href="https://help.github.com/articles/markdown-basics">Markdown</a>
                     </div>
                     <ul class="nav nav-tabs" data-init="tabs">
-                        <li class="active"><a href="#issue-textarea" data-toggle="tab">Write</a></li>
-                        <li><a href="#issue-preview" data-toggle="tab">Preview</a></li>
+                        <li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li>
+                        <li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repo=repo_id&issue=new" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li>
                     </ul>
                     <div class="tab-content">
                         <div class="tab-pane" id="issue-textarea">
                             <div class="form-group">
-                                <textarea class="form-control" name="content" id="issue-content" rows="10" placeholder="Write some content">{{.content}}</textarea>
+                                <textarea class="form-control" name="content" id="issue-content" rows="10" placeholder="Write some content" data-ajax-rel="issue-preview" data-ajax-val="val" data-ajax-field="content">{{.content}}</textarea>
                             </div>
                         </div>
-                        <div class="tab-pane" id="issue-preview">preview</div>
+                        <div class="tab-pane issue-preview-content" id="issue-preview">loading...</div>
                     </div>
                 </div>
                 <div class="text-right panel-body">
@@ -40,4 +40,4 @@
         </form>
     </div>
 </div>
-{{template "base/footer" .}}
+{{template "base/footer" .}}

+ 6 - 6
templates/issue/list.tmpl

@@ -6,24 +6,24 @@
     <div id="issue">
         <div class="col-md-3 filter-list">
             <ul class="list-unstyled">
-                <li><a href="/{{.RepositoryLink}}/issues"{{if eq .ViewType "all"}} class="active"{{end}}>All Issues <strong class="pull-right">{{.IssueCount}}</strong></a></li>
+                <li><a href="{{.RepoLink}}/issues"{{if eq .ViewType "all"}} class="active"{{end}}>All Issues <strong class="pull-right">{{.IssueCount}}</strong></a></li>
                 <!-- <li><a href="#">Assigned to you</a></li> -->
-                <li><a href="/{{.RepositoryLink}}/issues?type=created_by"{{if eq .ViewType "created_by"}} class="active"{{end}}>Created by you <strong class="pull-right">{{.IssueCreatedCount}}</strong></a></li>
+                <li><a href="{{.RepoLink}}/issues?type=created_by"{{if eq .ViewType "created_by"}} class="active"{{end}}>Created by you <strong class="pull-right">{{.IssueCreatedCount}}</strong></a></li>
                 <!-- <li><a href="#">Mentioned</a></li> -->
             </ul>
         </div>
         <div class="col-md-9">
             <div class="filter-option">
                 <div class="btn-group">
-                    <a class="btn btn-default issue-open{{if not .IsShowClosed}} active{{end}}" href="/{{.RepositoryLink}}/issues?type={{.ViewType}}">{{.OpenCount}} Open</a>
-                    <a class="btn btn-default issue-close{{if .IsShowClosed}} active{{end}}" href="/{{.RepositoryLink}}/issues?state=closed&type={{.ViewType}}">{{.ClosedCount}} Closed</a>
+                    <a class="btn btn-default issue-open{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}">{{.OpenCount}} Open</a>
+                    <a class="btn btn-default issue-close{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/issues?state=closed&type={{.ViewType}}">{{.ClosedCount}} Closed</a>
                 </div>
             </div>
             <div class="issues list-group">
                 {{range .Issues}}
                 <div class="list-group-item issue-item" id="issue-{{.Id}}">
                     <span class="number pull-right">#{{.Index}}</span>
-                    <h5 class="title"><a href="/{{$.RepositoryLink}}/issues/{{.Index}}">{{.Name}}</a></h5>
+                    <h5 class="title"><a href="{{$.RepoLink}}/issues/{{.Index}}">{{.Name}}</a></h5>
                     <p class="info">
                         <span class="author"><img class="avatar" src="{{.Poster.AvatarLink}}" alt="" width="20"/>
                         <a href="/user/{{.Poster.Name}}">{{.Poster.Name}}</a></span>
@@ -37,4 +37,4 @@
         </div>
     </div>
 </div>
-{{template "base/footer" .}}
+{{template "base/footer" .}}

+ 48 - 48
templates/issue/view.tmpl

@@ -4,16 +4,17 @@
 {{template "repo/toolbar" .}}
 <div id="body" class="container">
     <div id="issue">
-        <div id="issue-{issue.id}" class="issue-whole issue-is-opening">
+        <div id="issue-{{.Issue.Id}}" class="issue-whole issue-is-opening">
             <div class="issue-head clearfix">
                 <div class="number pull-right">#{{.Issue.Index}}</div>
                 <a class="author pull-left" href="/user/{{.Issue.Poster.Name}}"><img class="avatar" src="{{.Issue.Poster.AvatarLink}}" alt="" width="30"/></a>
                 <h1 class="title pull-left">{{.Issue.Name}}</h1>
-                <input id="issue-edit-title" class="form-control input-lg pull-left hidden" type="text" value="{issue.title}" data-ajax-rel="issue-save"/>
+                <input id="issue-edit-title" class="form-control input-lg pull-left hidden" type="text" value="{{.Issue.Name}}" data-ajax-rel="issue-edit-save" data-ajax-val="val" data-ajax-field="title"/>
+                <input type="hidden" value="{{.Issue.Id}}" data-ajax-rel="issue-edit-save" data-ajax-val="val" data-ajax-field="issue_id"/>
                 <p class="info pull-left">
-                    <a class="btn btn-default pull-right issue-edit" href="#" id="issue-edit-btn">Edit</a>
+                    {{if .IsIssueOwner}}<a class="btn btn-default pull-right issue-edit" href="#" id="issue-edit-btn">Edit</a>
                     <a class="btn btn-danger pull-right issue-edit-cancel hidden" href="#">Cancel</a>
-                    <a class="btn btn-primary pull-right issue-edit-save hidden" href="#" data-ajax="{issue.save.link}" data-ajax-name="issue-save">Save</a>
+                    <a class="btn btn-primary pull-right issue-edit-save hidden" href="#" data-ajax="{{.RepoLink}}/issues/{{.Issue.Index}}" data-ajax-name="issue-edit-save" data-ajax-method="post">Save</a>{{end}}
                     <span class="status label label-{{if .Issue.IsClosed}}danger{{else}}success{{end}}">{{if .Issue.IsClosed}}Closed{{else}}Open{{end}}</span>
                     <a href="/user/{{.Issue.Poster.Name}}" class="author"><strong>{{.Issue.Poster.Name}}</strong></a> opened this issue
                     <span class="time">{{TimeSince .Issue.Created}}</span> · {{.Issue.NumComments}} comments
@@ -23,78 +24,77 @@
                <div class="panel panel-default issue-content">
                    <div class="panel-body markdown">
                        <div class="content">
-                           {{str2html .Issue.Content}}
+                           {{str2html .Issue.RenderedContent}}
                        </div>
-                       <textarea class="form-control hidden" name="content" id="issue-edit-content" rows="10" data-ajax-rel="issue-save">content</textarea>
+                       <textarea class="form-control hidden" name="content" id="issue-edit-content" rows="10" data-ajax-rel="issue-edit-save" data-ajax-val="val" data-ajax-field="content">{{.Issue.Content}}</textarea>
                    </div>
                </div>
                {{range .Comments}}
-               <div class="issue-child" id="issue-comment-{issue.comment.id}">
-                   <a class="user pull-left" href="/user/{{.Poster.Name}}"><img class="avatar" src="{{.Poster.AvatarLink}}" alt=""/></a>
-                   <div class="issue-content panel panel-default">
-                       <div class="panel-heading">
-                           <a href="/user/{{.Poster.Name}}" class="user">{{.Poster.Name}}</a> commented <span class="time">{{TimeSince .Created}}</span>
-                           <a class="issue-comment-del pull-right issue-action" href="#" title="Edit Comment"><i class="fa fa-times-circle"></i></a>
-                           <a class="issue-comment-edit pull-right issue-action" href="#" title="Remove Comment" data-url="{remove-link}"><i class="fa fa-edit"></i></a>
-                           <span class="role label label-default pull-right">Owner</span>
-                       </div>
-                       <div class="panel-body markdown">
-                          {{str2html .Content}}
-                       </div>
-                   </div>
-                </div>
-                {{end}}
-                <div class="issue-child issue-closed">
-                    <a class="user pull-left" href="{user.link}"><img class="avatar" src="{user.avatar}" alt=""/></a>
+                {{if eq .Type 0}}
+                 <div class="issue-child" id="issue-comment-{{.Id}}">
+                     <a class="user pull-left" href="/user/{{.Poster.Name}}"><img class="avatar" src="{{.Poster.AvatarLink}}" alt=""/></a>
+                     <div class="issue-content panel panel-default">
+                         <div class="panel-heading">
+                             <a href="/user/{{.Poster.Name}}" class="user">{{.Poster.Name}}</a> commented <span class="time">{{TimeSince .Created}}</span>
+                             <!-- <a class="issue-comment-del pull-right issue-action" href="#" title="Edit Comment"><i class="fa fa-times-circle"></i></a>
+                             <a class="issue-comment-edit pull-right issue-action" href="#" title="Remove Comment" data-url="{remove-link}"><i class="fa fa-edit"></i></a> -->
+                             <span class="role label label-default pull-right">Owner</span>
+                         </div>
+                         <div class="panel-body markdown">
+                            {{str2html .Content}}
+                         </div>
+                     </div>
+                  </div>
+                  {{else if eq .Type 1}}
+                  <div class="issue-child issue-opened">
+                      <a class="user pull-left" href="/user/{{.Poster.Name}}"><img class="avatar" src="{{.Poster.AvatarLink}}" alt="" /></a>
+                      <div class="issue-content">
+                          <a class="user pull-left" href="/user/{{.Poster.Name}}">{{.Poster.Name}}</a> <span class="label label-success">Reopened</span> this issue <span class="time">{{TimeSince .Created}}</span>
+                      </div>
+                  </div>
+                  {{else if eq .Type 2}}
+                  <div class="issue-child issue-closed">
+                    <a class="user pull-left" href="/user/{{.Poster.Name}}"><img class="avatar" src="{{.Poster.AvatarLink}}" alt=""/></a>
                     <div class="issue-content">
-                        <a class="user pull-left" href="{user.link}">{user.name}</a>
-                        <span class="btn btn-danger">Closed</span> this
-                        <span class="time">{close.time}</span>
+                        <a class="user pull-left" href="/user/{{.Poster.Name}}">{{.Poster.Name}}</a> <span class="label label-danger">Closed</span> this issue <span class="time">{{TimeSince .Created}}</span>
                     </div>
-                </div>
-                <div class="issue-child issue-opened">
-                    <a class="user pull-left" href="{user.link}"><img class="avatar" src="{user.avatar}" alt=""/></a>
-                    <div class="issue-content">
-                        <a class="user pull-left" href="{user.link}">{user.name}</a>
-                        <span class="btn btn-success">Reopened</span> this
-                        <span class="time">{close.time}</span>
-                    </div>
-                </div>
+                  </div>
+                  {{end}}
+                {{end}}
                 <hr class="issue-line"/>
-                <div class="issue-child issue-reply">
+                {{if .SignedUser}}<div class="issue-child issue-reply">
                     <a class="user pull-left" href="/user/{{.SignedUser.Name}}"><img class="avatar" src="{{.SignedUser.AvatarLink}}" alt=""/></a>
-                    <form class="panel panel-default issue-content" action="/{{.RepositoryLink}}/comment/new" method="post">
+                    <form class="panel panel-default issue-content" action="{{.RepoLink}}/comment/new" method="post">
                         {{.CsrfTokenHtml}}
                         <div class="panel-body">
                             <div class="form-group">
-                                <div class="md-help pull-right"><!-- todo help link -->
-                                    Content with <a href="#">Markdown</a>
+                                <div class="md-help pull-right">Content with <a href="https://help.github.com/articles/markdown-basics">Markdown</a>
                                 </div>
                                 <ul class="nav nav-tabs" data-init="tabs">
-                                    <li class="active"><a href="#issue-textarea" data-toggle="tab">Write</a></li>
-                                    <li><a href="#issue-preview" data-toggle="tab">Preview</a></li>
+                                    <li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li>
+                                    <li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repo=repo_id&issue=issue_id&comment=new" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li>
                                 </ul>
                                 <div class="tab-content">
                                     <div class="tab-pane" id="issue-textarea">
                                         <div class="form-group">
                                             <input type="hidden" value="{{.Issue.Index}}" name="issueIndex"/>
-                                            <textarea class="form-control" name="content" id="issue-reply-content" rows="10" placeholder="Write some content">{{.content}}</textarea>
+                                            <textarea class="form-control" name="content" id="issue-reply-content" rows="10" placeholder="Write some content" data-ajax-rel="issue-preview" data-ajax-val="val" data-ajax-field="content">{{.content}}</textarea>
                                         </div>
                                     </div>
-                                    <div class="tab-pane" id="issue-preview">preview</div>
+                                    <div class="tab-pane issue-preview-content" id="issue-preview">Loading...</div>
                                 </div>
                             </div>
                             <div class="text-right">
                                 <div class="form-group">
-                                    <input type="hidden" value="id" name="repo-id"/>
-                                    <button class="btn-default btn issue-open" id="issue-open-btn" data-origin="Re-Open" data-text="Re-Open & Comment">Open</button>&nbsp;&nbsp;
-                                    <button class="btn-default btn issue-close" id="issue-close-btn" data-origin="Close" data-text="Close & Comment">Close</button>&nbsp;&nbsp;
+                                    {{if .Issue.IsClosed}}
+                                    <input type="submit" class="btn-default btn issue-open" id="issue-open-btn" data-origin="Reopen" data-text="Reopen & Comment" name="change_status" value="Reopen"/>{{else}}
+                                    <input type="submit" class="btn-default btn issue-close" id="issue-close-btn" data-origin="Close" data-text="Close & Comment" name="change_status" value="Close"/>{{end}}&nbsp;&nbsp;
                                     <button class="btn-success btn" id="issue-reply-btn">Comment</button>
                                 </div>
                             </div>
                         </div>
                     </form>
-                </div>
+                </div>{{else}}<div class="alert alert-warning"><a class="btn btn-success btn-lg" href="/user/sign_up">Sign up for free</a> to join this conversation. Already have an account? <a href="/user/login">Sign in to comment</a></div>{{end}}
             </div><!--
             <div class="col-md-3">
                 label assignment milestone dashboard
@@ -102,4 +102,4 @@
         </div>
     </div>
 </div>
-{{template "base/footer" .}}
+{{template "base/footer" .}}

+ 1 - 2
templates/repo/diff.tmpl

@@ -1,7 +1,6 @@
 {{template "base/head" .}}
 {{template "base/navbar" .}}
 {{template "repo/nav" .}}
-{{template "repo/toolbar" .}}
 <div id="body" class="container" data-page="repo">
     <div id="source">
         <div class="panel panel-info diff-box diff-head-box">
@@ -11,7 +10,7 @@
             </div>
             <div class="panel-body">
                 <span class="pull-right">
-                    commit <span class="label label-default sha">{{.ShortSha}}</span>
+                    commit <span class="label label-default sha">{{ShortSha .CommitId}}</span>
                 </span>
                 <p class="author">
                     <img class="avatar" src="{{AvatarLink .Commit.Author.Email}}" alt=""/>

+ 4 - 10
templates/repo/nav.tmpl

@@ -7,12 +7,6 @@
             </div>
             <div class="col-md-5 actions text-right clone-group-btn">
                 {{if not .IsBareRepo}}
-                <!--<div class="btn-group" id="repo-clone">
-                    <button type="button" class="btn btn-default"><i class="fa fa-download fa-lg fa-m"></i></button>
-                    <button type="button" class="btn btn-default dropdown-toggle" data-container="body" data-toggle="popover" data-placement="bottom" data-content="<label>SSH:</label><div class='input-group'><input type='text' class='form-control' value='{{.CloneLink.SSH}}'></div>" data-html="1">
-                        <span class="caret"></span>
-                    </button>
-                </div>-->
                 <div class="btn-group" id="repo-clone">
                     <button type="button" class="btn btn-default"><i class="fa fa-download fa-lg fa-m"></i></button>
                     <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
@@ -30,10 +24,10 @@
                             </span>
                         </div>
                         <p class="help-block text-center">Need help cloning? Visit <a href="#">Help</a>!</p>
-                        <hr/>
+                        <!-- <hr/>
                         <div class="clone-zip text-center">
                             <a class="btn btn-success btn-lg" href="#"><i class="fa fa-suitcase"></i>Download ZIP</a>
-                        </div>
+                        </div> -->
                     </div>
                 </div>
                 <div class="btn-group {{if .IsRepositoryWatching}}watching{{else}}no-watching{{end}}" id="repo-watching" data-watch="/{{.Owner.Name}}/{{.Repository.Name}}/action/watch" data-unwatch="/{{.Owner.Name}}/{{.Repository.Name}}/action/unwatch">
@@ -61,9 +55,9 @@
                     <button type="button" class="btn btn-default" data-toggle="tooltip" data-placement="top" title="Star"><i class="fa fa-star"></i>&nbsp;{{.Repository.NumStars}}</button>
                 </div> -->
                 {{end}}
-                <div class="btn-group">
+                <!-- <div class="btn-group">
                     <a type="button" {{if not .IsRepositoryOwner}}href="/{{.Username}}/{{.Reponame}}/fork"{{end}} class="btn btn-default" data-toggle="tooltip" data-placement="top" title="Fork"><i class="fa fa-code-fork fa-lg"></i>&nbsp;{{.Repository.NumForks}}</a>
-                </div>
+                </div> -->
             </div>
         </div>
     </div>

+ 2 - 6
templates/repo/single.tmpl

@@ -4,18 +4,15 @@
 {{template "repo/toolbar" .}}
 <div id="body" class="container">
     <div id="source">
-        {{if .IsBareRepo}}
-        {{template "repo/single_bare" .}}
-        {{else}}
         <div class="source-toolbar">
             {{ $n := len .Treenames}}
             {{if not .IsFile}}<button class="btn btn-default pull-right hidden"><i class="fa fa-plus-square"></i>Add File</button>{{end}}
             <div class="dropdown branch-switch">
-                <a href="#" class="btn btn-success dropdown-toggle" data-toggle="dropdown"><i class="fa fa-chain"></i>{{if .CommitId}}{{SubStr .CommitId 0 10}}{{else}}{{.Branchname}}{{end}}&nbsp;&nbsp;
+                <a href="#" class="btn btn-success dropdown-toggle" data-toggle="dropdown"><i class="fa fa-chain"></i>{{if .IsBranch}}{{.BranchName}}{{else}}{{ShortSha .CommitId}}{{end}}&nbsp;&nbsp;
                     <b class="caret"></b></a>
                 <ul class="dropdown-menu">
                     {{range .Branches}}
-                    <li><a {{if eq . $.Branchname}}class="current" {{end}}href="/{{$.Username}}/{{$.Reponame}}/src/{{.}}">{{.}}</a></li>
+                    <li><a {{if eq . $.BranchName}}class="current" {{end}}href="/{{$.Username}}/{{$.Reponame}}/src/{{.}}">{{.}}</a></li>
                     {{end}}
                 </ul>
             </div>
@@ -38,7 +35,6 @@
         {{else}}
             {{template "repo/single_list" .}}
         {{end}}
-        {{end}}
     </div>
 </div>
 {{template "base/footer" .}}

+ 33 - 24
templates/repo/single_bare.tmpl

@@ -1,31 +1,40 @@
-<div class="panel panel-default guide-box clone-group-btn">
-    <div class="panel-heading guide-head">
-        <h4>Quick Guide</h4>
-    </div>
-    <div class="panel-body guide-content text-center">
-        <h3>Clone this repository</h3>
-        <div class="input-group col-md-8 col-md-offset-2 guide-buttons">
-            <span class="input-group-btn">
-                <button class="btn btn-default" data-link="{{.CloneLink.SSH}}" type="button">SSH</button>
-                <button class="btn btn-default" data-link="{{.CloneLink.HTTPS}}" type="button">HTTPS</button>
-            </span>
-            <input type="text" class="form-control clone-group-url" id="guide-clone-url" value="" readonly/>
-            <span class="input-group-btn">
-                <button class="btn btn-default" type="button"><i class="fa fa-copy" data-toggle="tooltip" title="copy to clipboard" data-placement="top"></i></button>
-            </span>
-        </div>
-        <p>We recommend every repository include a <strong>README</strong>, <strong>LICENSE</strong>, and <strong>.gitignore</strong>.</p>
-        <hr/>
-        <h3>Create a new repository on the command line</h3>
-            <pre class="text-left"><code>touch README.md
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+{{template "repo/nav" .}}
+{{template "repo/toolbar" .}}
+<div id="body" class="container">
+    <div id="source">
+        <div class="panel panel-default guide-box clone-group-btn">
+            <div class="panel-heading guide-head">
+                <h4>Quick Guide</h4>
+            </div>
+            <div class="panel-body guide-content text-center">
+                <h3>Clone this repository</h3>
+                <div class="input-group col-md-8 col-md-offset-2 guide-buttons">
+                    <span class="input-group-btn">
+                        <button class="btn btn-default" data-link="{{.CloneLink.SSH}}" type="button">SSH</button>
+                        <button class="btn btn-default" data-link="{{.CloneLink.HTTPS}}" type="button">HTTPS</button>
+                    </span>
+                    <input type="text" class="form-control clone-group-url" id="guide-clone-url" value="" readonly/>
+                    <span class="input-group-btn">
+                        <button class="btn btn-default" type="button"><i class="fa fa-copy" data-toggle="tooltip" title="copy to clipboard" data-placement="top"></i></button>
+                    </span>
+                </div>
+                <p>We recommend every repository include a <strong>README</strong>, <strong>LICENSE</strong>, and <strong>.gitignore</strong>.</p>
+                <hr/>
+                <h3>Create a new repository on the command line</h3>
+                    <pre class="text-left"><code>touch README.md
 git init
 git add README.md
 git commit -m "first commit"
 git remote add origin <span class="clone-url"></span>
 git push -u origin master</code></pre>
-        <hr/>
-        <h3>Push an existing repository from the command line</h3>
-        <pre class="text-left"><code>git remote add origin <span class="clone-url"></span>
+                <hr/>
+                <h3>Push an existing repository from the command line</h3>
+                <pre class="text-left"><code>git remote add origin <span class="clone-url"></span>
 git push -u origin master</code></pre>
+            </div>
+        </div>
     </div>
-</div>
+</div>
+{{template "base/footer" .}}

+ 10 - 10
templates/repo/toolbar.tmpl

@@ -3,24 +3,24 @@
         <nav class="navbar navbar-toolbar navbar-default" role="navigation">
             <div class="collapse navbar-collapse">
                 <ul class="nav navbar-nav">
-                    <li class="{{if .IsRepoToolbarSource}}active{{end}}"><a href="/{{.RepositoryLink}}">Source</a></li>
+                    <li class="{{if .IsRepoToolbarSource}}active{{end}}"><a href="{{.RepoLink}}{{if .BranchName}}{{if ne .BranchName `master`}}/src/{{.BranchName}}{{end}}{{end}}">Source</a></li>
                     {{if not .IsBareRepo}}
-                    <li class="{{if .IsRepoToolbarCommits}}active{{end}}"><a href="/{{.RepositoryLink}}/commits/{{.Branchname}}">Commits</a></li>
-                    <!-- <li class="{{if .IsRepoToolbarBranches}}active{{end}}"><a href="/{{.RepositoryLink}}/branches">Branches</a></li> -->
-                    <!-- <li class="{{if .IsRepoToolbarPulls}}active{{end}}"><a href="/{{.RepositoryLink}}/pulls">Pull Requests</a></li> -->
-                    <li class="{{if .IsRepoToolbarIssues}}active{{end}}"><a href="/{{.RepositoryLink}}/issues">Issues <!--<span class="badge">42</span>--></a></li>
+                    <li class="{{if .IsRepoToolbarCommits}}active{{end}}"><a href="{{.RepoLink}}/commits/{{if .BranchName}}{{.BranchName}}{{else}}master{{end}}">Commits</a></li>
+                    <!-- <li class="{{if .IsRepoToolbarBranches}}active{{end}}"><a href="{{.RepoLink}}/branches">Branches</a></li> -->
+                    <!-- <li class="{{if .IsRepoToolbarPulls}}active{{end}}"><a href="{{.RepoLink}}/pulls">Pull Requests</a></li> -->
+                    <li class="{{if .IsRepoToolbarIssues}}active{{end}}"><a href="{{.RepoLink}}/issues">Issues <!--<span class="badge">42</span>--></a></li>
                     {{if .IsRepoToolbarIssues}}
-                    <li class="tmp">{{if .IsRepoToolbarIssuesList}}<a href="/{{.RepositoryLink}}/issues/new">
+                    <li class="tmp">{{if .IsRepoToolbarIssuesList}}<a href="{{.RepoLink}}/issues/new">
                         <button class="btn btn-primary btn-sm">New Issue</button>
-                    </a>{{else}}<a href="/{{.RepositoryLink}}/issues">
+                    </a>{{else}}<a href="{{.RepoLink}}/issues">
                         <button class="btn btn-primary btn-sm">Issues List</button>
                     </a>{{end}}</li>
                     {{end}}
                     <!-- <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>
+                            <li><a href="{{.RepoLink}}/release">Release</a></li>
+                            <li><a href="{{.RepoLink}}/wiki">Wiki</a></li>
                         </ul>
                     </li> -->{{end}}
                 </ul>
@@ -34,7 +34,7 @@
                             <li><a href="#">Network</a></li>
                         </ul>
                     </li> -->{{end}}{{if .IsRepositoryOwner}}
-                    <li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="/{{.RepositoryLink}}/settings">Settings</a>
+                    <li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="{{.RepoLink}}/settings">Settings</a>
                     </li>{{end}}
                 </ul>
             </div>

+ 3 - 4
templates/user/profile.tmpl

@@ -32,11 +32,10 @@
             {{if eq .TabName "activity"}}
             <div class="tab-pane active">
                 <ul class="list-unstyled activity-list">
-                {{$avatarLink := .Owner.AvatarLink}}
                 {{range .Feeds}}
                     <li>
                         <i class="icon fa fa-{{ActionIcon .OpType}}"></i>
-                        <div class="info"><span class="meta">{{TimeSince .Created}}</span><br>{{ActionDesc . $avatarLink | str2html}}</div>
+                        <div class="info"><span class="meta">{{TimeSince .Created}}</span><br>{{ActionDesc . | str2html}}</div>
                         <span class="clearfix"></span>
                     </li>
                 {{else}}
@@ -52,7 +51,7 @@
                     <li>
                         <div class="meta pull-right"><!-- <i class="fa fa-star"></i> {{.NumStars}} --> <i class="fa fa-code-fork"></i> {{.NumForks}}</div>
                         <h4>
-                            <a href="/{{$owner.Name}}/{{.LowerName}}">{{.LowerName}}</a>
+                            <a href="/{{$owner.Name}}/{{.Name}}">{{.Name}}</a>
                         </h4>
                         <p class="desc">{{.Description}}</p>
                         <div class="info">Last updated {{.Updated|TimeSince}}</div>
@@ -64,4 +63,4 @@
         </div>
     </div>
 </div>
-{{template "base/footer" .}}
+{{template "base/footer" .}}

+ 1 - 1
templates/user/signin.tmpl

@@ -44,7 +44,7 @@
         </div>
 
         <div class="form-group text-center" id="social-login">
-            <a class="btn btn-danger btn-lg">Register new account</a>
+            <a class="btn btn-danger btn-lg" href="/user/sign_up">Register new account</a>
         </div>
     </form>
 </div>

+ 9 - 0
tests/.travel.yml

@@ -0,0 +1,9 @@
+command: go test -v {}
+include: ^.+_test\.go$
+path: ./
+depth: 1
+verbose: true
+timeout: 1m
+reload: false
+html: test.html
+notify: []

+ 11 - 0
tests/README.md

@@ -0,0 +1,11 @@
+## gogs test
+
+this is for developers
+
+## prepare environment
+	go get -u github.com/shxsun/travelexec
+	# start gogs server
+	gogs web
+
+## start test
+	travelexec

+ 17 - 0
tests/default_test.go

@@ -0,0 +1,17 @@
+package test
+
+import (
+	"net/http"
+	"testing"
+)
+
+func TestMain(t *testing.T) {
+	r, err := http.Get("http://localhost:3000/")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer r.Body.Close()
+	if r.StatusCode != http.StatusOK {
+		t.Error(r.StatusCode)
+	}
+}

+ 17 - 3
update.go

@@ -32,6 +32,12 @@ gogs serv provide access auth for repositories`,
 func runUpdate(c *cli.Context) {
 	base.NewConfigContext()
 	models.LoadModelsConfig()
+
+	if models.UseSQLite3 {
+		execDir, _ := base.ExecDir()
+		os.Chdir(execDir)
+	}
+
 	models.SetEngine()
 
 	w, _ := os.Create("update.log")
@@ -130,18 +136,26 @@ func runUpdate(c *cli.Context) {
 		return
 	}
 
-	commits := make([][]string, 0)
+	commits := make([]*base.PushCommit, 0)
 	var maxCommits = 3
+	var actEmail string
 	for e := l.Front(); e != nil; e = e.Next() {
 		commit := e.Value.(*git.Commit)
-		commits = append(commits, []string{commit.Id().String(), commit.Message()})
+		if actEmail == "" {
+			actEmail = commit.Committer.Email
+		}
+		commits = append(commits,
+			&base.PushCommit{commit.Id().String(),
+				commit.Message(),
+				commit.Author.Email,
+				commit.Author.Name})
 		if len(commits) >= maxCommits {
 			break
 		}
 	}
 
 	//commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()})
-	if err = models.CommitRepoAction(int64(sUserId), userName,
+	if err = models.CommitRepoAction(int64(sUserId), userName, actEmail,
 		repos.Id, repoName, git.BranchName(refName), &base.PushCommits{l.Len(), commits}); err != nil {
 		log.Error("runUpdate.models.CommitRepoAction: %v", err)
 	}

+ 19 - 38
web.go

@@ -8,22 +8,20 @@ import (
 	"fmt"
 	"html/template"
 	"net/http"
-	"strings"
 
 	"github.com/codegangsta/cli"
-	"github.com/codegangsta/martini"
+	"github.com/go-martini/martini"
 
 	"github.com/gogits/binding"
 
-	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
 	"github.com/gogits/gogs/modules/avatar"
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/log"
-	"github.com/gogits/gogs/modules/mailer"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/routers"
 	"github.com/gogits/gogs/routers/admin"
+	"github.com/gogits/gogs/routers/api/v1"
 	"github.com/gogits/gogs/routers/dev"
 	"github.com/gogits/gogs/routers/repo"
 	"github.com/gogits/gogs/routers/user"
@@ -39,27 +37,6 @@ and it takes care of all the other things for you`,
 	Flags:  []cli.Flag{},
 }
 
-// globalInit is for global configuration reload-able.
-func globalInit() {
-	base.NewConfigContext()
-	mailer.NewMailerContext()
-	models.LoadModelsConfig()
-	models.LoadRepoConfig()
-	models.NewRepoContext()
-	models.NewEngine()
-}
-
-// Check run mode(Default of martini is Dev).
-func checkRunMode() {
-	switch base.Cfg.MustValue("", "RUN_MODE") {
-	case "prod":
-		martini.Env = martini.Prod
-	case "test":
-		martini.Env = martini.Test
-	}
-	log.Info("Run Mode: %s", strings.Title(martini.Env))
-}
-
 func newMartini() *martini.ClassicMartini {
 	r := martini.NewRouter()
 	m := martini.New()
@@ -72,9 +49,8 @@ func newMartini() *martini.ClassicMartini {
 }
 
 func runWeb(*cli.Context) {
-	globalInit()
-	base.NewServices()
-	checkRunMode()
+	fmt.Println("Server is running...")
+	routers.GlobalInit()
 	log.Info("%s %s", base.AppName, base.AppVer)
 
 	m := newMartini()
@@ -90,12 +66,16 @@ func runWeb(*cli.Context) {
 
 	// Routers.
 	m.Get("/", ignSignIn, routers.Home)
-	m.Get("/install", routers.Install)
+	m.Any("/install", binding.BindIgnErr(auth.InstallForm{}), routers.Install)
 	m.Get("/issues", reqSignIn, user.Issues)
 	m.Get("/pulls", reqSignIn, user.Pulls)
 	m.Get("/stars", reqSignIn, user.Stars)
 	m.Get("/help", routers.Help)
 
+	m.Group("/api/v1", func(r martini.Router) {
+		r.Post("/markdown", v1.Markdown)
+	})
+
 	avt := avatar.CacheServer("public/img/avatar/", "public/img/avatar_default.jpg")
 	m.Get("/avatar/:hash", avt.ServeHTTP)
 
@@ -150,26 +130,26 @@ func runWeb(*cli.Context) {
 		r.Post("/issues/:index", binding.BindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
 		r.Post("/comment/:action", repo.Comment)
 	}, reqSignIn, middleware.RepoAssignment(true))
+
 	m.Group("/:username/:reponame", func(r martini.Router) {
-		r.Get("/commits/:branchname", repo.Commits)
 		r.Get("/issues", repo.Issues)
 		r.Get("/issues/:index", repo.ViewIssue)
 		r.Get("/pulls", repo.Pulls)
 		r.Get("/branches", repo.Branches)
+	}, ignSignIn, middleware.RepoAssignment(true))
+
+	m.Group("/:username/:reponame", func(r martini.Router) {
 		r.Get("/src/:branchname", repo.Single)
 		r.Get("/src/:branchname/**", repo.Single)
 		r.Get("/raw/:branchname/**", repo.SingleDownload)
 		r.Get("/commits/:branchname", repo.Commits)
-		r.Get("/commits/:branchname", repo.Commits)
-	}, ignSignIn, middleware.RepoAssignment(true))
-
-	m.Get("/:username/:reponame/commit/:commitid/**", ignSignIn, middleware.RepoAssignment(true), repo.Diff)
-	m.Get("/:username/:reponame/commit/:commitid", ignSignIn, middleware.RepoAssignment(true), repo.Diff)
+		r.Get("/commit/:branchname", repo.Diff)
+		r.Get("/commit/:branchname/**", repo.Diff)
+	}, ignSignIn, middleware.RepoAssignment(true, true))
 
 	m.Group("/:username", func(r martini.Router) {
-		r.Get("/:reponame", middleware.RepoAssignment(true), repo.Single)
-		r.Get("/:reponame", middleware.RepoAssignment(true), repo.Single)
 		r.Any("/:reponame/**", repo.Http)
+		r.Get("/:reponame", middleware.RepoAssignment(true, true, true), repo.Single)
 	}, ignSignIn)
 
 	// Not found handler.
@@ -180,6 +160,7 @@ func runWeb(*cli.Context) {
 		base.Cfg.MustValue("server", "HTTP_PORT", "3000"))
 	log.Info("Listen: %s", listenAddr)
 	if err := http.ListenAndServe(listenAddr, m); err != nil {
-		log.Critical(err.Error())
+		fmt.Println(err.Error())
+		//log.Critical(err.Error()) // not working now
 	}
 }