Browse Source

Merge branch 'master' of github.com:gogits/gogs

Lunny Xiao 11 years ago
parent
commit
89258e868b

+ 7 - 3
README.md

@@ -5,7 +5,7 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
 
 ![Demo](http://gowalker.org/public/gogs_demo.gif)
 
-##### Current version: 0.1.8 Alpha
+##### Current version: 0.1.9 Alpha
 
 #### Other language version
 
@@ -19,7 +19,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
 
 ## Overview
 
-- Please see [Wiki](https://github.com/gogits/gogs/wiki) for project design, develop specification, change log and road map.
+- Please see [Wiki](https://github.com/gogits/gogs/wiki) for project design, known issues, change log and road map.
 - See [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) to follow the develop team.
 - Try it before anything? Do it [online](http://try.gogits.org/Unknown/gogs) or go down to **Installation -> Install from binary** section!
 - Having troubles? Get help from [Troubleshooting](https://github.com/gogits/gogs/wiki/Troubleshooting).
@@ -27,7 +27,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
 ## Features
 
 - Activity timeline
-- SSH/HTTPS protocol support.
+- SSH/HTTPS(Clone only) protocol support.
 - Register/delete account.
 - Create/delete/watch public repository.
 - User profile page.
@@ -58,3 +58,7 @@ There are two ways to install Gogs:
 ## Contributors
 
 This project was launched by [Unknown](https://github.com/Unknwon) and [lunny](https://github.com/lunny); [fuxiaohei](https://github.com/fuxiaohei) and [slene](https://github.com/slene) joined the team soon after. See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for full list of contributors.
+
+## License
+
+Gogs is under the MIT License. See the [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) file for the full license text.

+ 8 - 4
README_ZH.md

@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
 
 ![Demo](http://gowalker.org/public/gogs_demo.gif)
 
-##### 当前版本:0.1.8 Alpha
+##### 当前版本:0.1.9 Alpha
 
 ## 开发目的
 
@@ -15,7 +15,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
 
 ## 项目概览
 
-- 有关项目设计、开发说明、变更日志和路线图,请通过  [Wiki](https://github.com/gogits/gogs/wiki) 查看。
+- 有关项目设计、已知问题、变更日志和路线图,请通过  [Wiki](https://github.com/gogits/gogs/wiki) 查看。
 - 您可以到 [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
 - 想要先睹为快?通过 [在线体验](http://try.gogits.org/Unknown/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
 - 使用过程中遇到问题?尝试从 [故障排查](https://github.com/gogits/gogs/wiki/Troubleshooting) 页面获取帮助。
@@ -23,7 +23,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
 ## 功能特性
 
 - 活动时间线
-- SSH/HTTPS 协议支持
+- SSH/HTTPS(仅限 Clone) 协议支持
 - 注册/删除用户
 - 创建/删除/关注公开仓库
 - 用户个人信息页面
@@ -53,4 +53,8 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
 
 ## 贡献成员
 
-本项目最初由 [Unknown](https://github.com/Unknwon) 和 [lunny](https://github.com/lunny) 发起,随后 [fuxiaohei](https://github.com/fuxiaohei) 与 [slene](https://github.com/slene) 加入到开发团队。您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
+本项目最初由 [Unknown](https://github.com/Unknwon) 和 [lunny](https://github.com/lunny) 发起,随后 [fuxiaohei](https://github.com/fuxiaohei) 与 [slene](https://github.com/slene) 加入到开发团队。您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
+
+## 授权许可
+
+Gogs 采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。

+ 1 - 0
conf/app.ini

@@ -32,6 +32,7 @@ PATH = data/gogs.db
 [admin]
 
 [security]
+INSTALL_LOCK = false
 ; Use HTTPS to clone repository, otherwise use HTTP.
 ENABLE_HTTPS_CLONE = false
 ; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!!

+ 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.8.0326 Alpha"
+const APP_VER = "0.1.9.0327 Alpha"
 
 func init() {
 	base.AppVer = APP_VER

+ 2 - 2
models/access.go

@@ -15,7 +15,7 @@ const (
 	AU_WRITABLE
 )
 
-// Access represents the accessibility of user and repository.
+// Access represents the accessibility of user to repository.
 type Access struct {
 	Id       int64
 	UserName string    `xorm:"unique(s)"`
@@ -30,7 +30,7 @@ func AddAccess(access *Access) error {
 	return err
 }
 
-// HasAccess returns true if someone can read or write given repository.
+// HasAccess returns true if someone can read or write to given repository.
 func HasAccess(userName, repoName string, mode int) (bool, error) {
 	return orm.Get(&Access{
 		Id:       0,

+ 15 - 16
models/action.go

@@ -23,7 +23,8 @@ const (
 	OP_PULL_REQUEST
 )
 
-// Action represents user operation type and information to the repository.
+// Action represents user operation type and other information to repository.,
+// it implemented interface base.Actioner so that can be used in template render.
 type Action struct {
 	Id          int64
 	UserId      int64  // Receiver user id.
@@ -57,23 +58,24 @@ func (a Action) GetContent() string {
 	return a.Content
 }
 
-// CommitRepoAction records action for commit repository.
+// CommitRepoAction adds new action for committing repository.
 func CommitRepoAction(userId int64, userName string,
-	repoId int64, repoName string, refName string, commits *base.PushCommits) error {
+	repoId int64, repoName string, refName string, commit *base.PushCommits) error {
 	log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName)
 
-	bs, err := json.Marshal(commits)
+	bs, err := json.Marshal(commit)
 	if err != nil {
 		log.Error("action.CommitRepoAction(json): %d/%s", userId, repoName)
 		return err
 	}
 
-	if err = NotifyWatchers(userId, repoId, OP_COMMIT_REPO, userName, repoName, refName, string(bs)); err != nil {
+	if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, 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
 	}
 
-	// Update repository last update time.
+	// Change repository bare status and update last updated time.
 	repo, err := GetRepositoryByName(userId, repoName)
 	if err != nil {
 		log.Error("action.CommitRepoAction(GetRepositoryByName): %d/%s", userId, repoName)
@@ -89,16 +91,13 @@ func CommitRepoAction(userId int64, userName string,
 	return nil
 }
 
-// NewRepoAction records action for create repository.
-func NewRepoAction(user *User, repo *Repository) error {
-	_, err := orm.InsertOne(&Action{
-		UserId:      user.Id,
-		ActUserId:   user.Id,
-		ActUserName: user.Name,
-		OpType:      OP_CREATE_REPO,
-		RepoId:      repo.Id,
-		RepoName:    repo.Name,
-	})
+// 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 {
+		log.Error("action.NewRepoAction(notify watchers): %d/%s", user.Id, repo.Name)
+		return err
+	}
 
 	log.Trace("action.NewRepoAction: %s/%s", user.LowerName, repo.LowerName)
 	return err

+ 31 - 10
models/git.go

@@ -38,8 +38,8 @@ func (file *RepoFile) LookupBlob() (*git.Blob, error) {
 }
 
 // GetBranches returns all branches of given repository.
-func GetBranches(userName, reposName string) ([]string, error) {
-	repo, err := git.OpenRepository(RepoPath(userName, reposName))
+func GetBranches(userName, repoName string) ([]string, error) {
+	repo, err := git.OpenRepository(RepoPath(userName, repoName))
 	if err != nil {
 		return nil, err
 	}
@@ -56,8 +56,16 @@ func GetBranches(userName, reposName string) ([]string, error) {
 	return brs, nil
 }
 
-func GetTargetFile(userName, reposName, branchName, commitId, rpath string) (*RepoFile, error) {
-	repo, err := git.OpenRepository(RepoPath(userName, reposName))
+func IsBranchExist(userName, repoName, branchName string) bool {
+	repo, err := git.OpenRepository(RepoPath(userName, repoName))
+	if err != nil {
+		return false
+	}
+	return repo.IsBranchExist(branchName)
+}
+
+func GetTargetFile(userName, repoName, branchName, commitId, rpath string) (*RepoFile, error) {
+	repo, err := git.OpenRepository(RepoPath(userName, repoName))
 	if err != nil {
 		return nil, err
 	}
@@ -102,8 +110,8 @@ func GetTargetFile(userName, reposName, branchName, commitId, rpath string) (*Re
 }
 
 // GetReposFiles returns a list of file object in given directory of repository.
-func GetReposFiles(userName, reposName, branchName, commitId, rpath string) ([]*RepoFile, error) {
-	repo, err := git.OpenRepository(RepoPath(userName, reposName))
+func GetReposFiles(userName, repoName, branchName, commitId, rpath string) ([]*RepoFile, error) {
+	repo, err := git.OpenRepository(RepoPath(userName, repoName))
 	if err != nil {
 		return nil, err
 	}
@@ -217,13 +225,26 @@ func GetCommit(userName, repoName, branchname, commitid string) (*git.Commit, er
 	return repo.GetCommit(branchname, commitid)
 }
 
-// GetCommits returns all commits of given branch of repository.
-func GetCommits(userName, reposName, branchname string) (*list.List, error) {
-	repo, err := git.OpenRepository(RepoPath(userName, reposName))
+// GetCommitsByBranch returns all commits of given branch of repository.
+func GetCommitsByBranch(userName, repoName, branchName string) (*list.List, error) {
+	repo, err := git.OpenRepository(RepoPath(userName, repoName))
+	if err != nil {
+		return nil, err
+	}
+	r, err := repo.LookupReference(fmt.Sprintf("refs/heads/%s", branchName))
+	if err != nil {
+		return nil, err
+	}
+	return r.AllCommits()
+}
+
+// GetCommitsByCommitId returns all commits of given commitId of repository.
+func GetCommitsByCommitId(userName, repoName, commitId string) (*list.List, error) {
+	repo, err := git.OpenRepository(RepoPath(userName, repoName))
 	if err != nil {
 		return nil, err
 	}
-	r, err := repo.LookupReference(fmt.Sprintf("refs/heads/%s", branchname))
+	r, err := repo.LookupReference(commitId)
 	if err != nil {
 		return nil, err
 	}

+ 33 - 24
models/issue.go

@@ -21,7 +21,8 @@ type Issue struct {
 	Id          int64
 	Index       int64 // Index in one repository.
 	Name        string
-	RepoId      int64 `xorm:"index"`
+	RepoId      int64       `xorm:"index"`
+	Repo        *Repository `xorm:"-"`
 	PosterId    int64
 	Poster      *User `xorm:"-"`
 	MilestoneId int64
@@ -37,17 +38,16 @@ type Issue struct {
 }
 
 // CreateIssue creates new issue for repository.
-func CreateIssue(userId, repoId, milestoneId, assigneeId int64, name, labels, content string, isPull bool) (*Issue, error) {
-	count, err := GetIssueCount(repoId)
-	if err != nil {
-		return nil, err
-	}
-
+func CreateIssue(userId, repoId, milestoneId, assigneeId int64, issueCount int, name, labels, content string, isPull bool) (issue *Issue, err error) {
 	// TODO: find out mentions
 	mentions := ""
 
-	issue := &Issue{
-		Index:       count + 1,
+	sess := orm.NewSession()
+	defer sess.Close()
+	sess.Begin()
+
+	issue = &Issue{
+		Index:       int64(issueCount) + 1,
 		Name:        name,
 		RepoId:      repoId,
 		PosterId:    userId,
@@ -58,13 +58,23 @@ func CreateIssue(userId, repoId, milestoneId, assigneeId int64, name, labels, co
 		Mentions:    mentions,
 		Content:     content,
 	}
-	_, err = orm.Insert(issue)
-	return issue, err
-}
+	if _, err = sess.Insert(issue); err != nil {
+		sess.Rollback()
+		return nil, err
+	}
 
-// GetIssueCount returns count of issues in the repository.
-func GetIssueCount(repoId int64) (int64, error) {
-	return orm.Count(&Issue{RepoId: repoId})
+	rawSql := "UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?"
+	if _, err = sess.Exec(rawSql, repoId); err != nil {
+		sess.Rollback()
+		return nil, err
+	}
+
+	if err = sess.Commit(); err != nil {
+		sess.Rollback()
+		return nil, err
+	}
+
+	return issue, nil
 }
 
 // GetIssueById returns issue object by given id.
@@ -127,18 +137,18 @@ func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed,
 	return issues, err
 }
 
+// GetUserIssueCount returns the number of issues that were created by given user in repository.
+func GetUserIssueCount(userId, repoId int64) int64 {
+	count, _ := orm.Where("poster_id=?", userId).And("repo_id=?", repoId).Count(new(Issue))
+	return count
+}
+
 // UpdateIssue updates information of issue.
 func UpdateIssue(issue *Issue) error {
-	_, err := orm.Update(issue, &Issue{RepoId: issue.RepoId, Index: issue.Index})
+	_, err := orm.Id(issue.Id).AllCols().Update(issue)
 	return err
 }
 
-func CloseIssue() {
-}
-
-func ReopenIssue() {
-}
-
 // Label represents a list of labels of repository for issues.
 type Label struct {
 	Id     int64
@@ -178,8 +188,7 @@ func CreateComment(userId, issueId, commitId, line int64, content string) error
 	sess.Begin()
 
 	if _, err := orm.Insert(&Comment{PosterId: userId, IssueId: issueId,
-		CommitId: commitId, Line: line, Content: content,
-	}); err != nil {
+		CommitId: commitId, Line: line, Content: content}); err != nil {
 		sess.Rollback()
 		return err
 	}

+ 14 - 9
models/models_test.go

@@ -10,12 +10,12 @@ import (
 
 	"github.com/lunny/xorm"
 	_ "github.com/mattn/go-sqlite3"
+	. "github.com/smartystreets/goconvey/convey"
+
+	"github.com/gogits/gogs/modules/base"
 )
 
 func init() {
-	LoadModelsConfig()
-	NewEngine()
-
 	var err error
 	orm, err = xorm.NewEngine("sqlite3", "./test.db")
 	if err != nil {
@@ -25,26 +25,31 @@ func init() {
 	orm.ShowSQL = true
 	orm.ShowDebug = true
 
-	err = orm.Sync(&User{}, &Repo{})
+	err = orm.Sync(&User{}, &Repository{})
 	if err != nil {
 		fmt.Println(err)
 	}
 
-	root = "test"
+	base.RepoRootPath = "test"
 }
 
 func TestCreateRepository(t *testing.T) {
-	user := User{Id: 1, Type: Individual}
-	_, err := CreateRepository(&user, "test")
+	user := User{Id: 1, Name: "foobar", Type: UT_INDIVIDUAL}
+	_, err := CreateRepository(&user, "test", "", "", "test repo desc", false, false)
 	if err != nil {
 		t.Error(err)
 	}
 }
 
 func TestDeleteRepository(t *testing.T) {
-	user := User{Id: 1, Type: Individual}
-	err := DeleteRepository(&user, "test")
+	err := DeleteRepository(1, 1, "foobar")
 	if err != nil {
 		t.Error(err)
 	}
 }
+
+func TestCommitRepoAction(t *testing.T) {
+	Convey("Create a commit repository action", t, func() {
+
+	})
+}

+ 51 - 48
models/repo.go

@@ -72,20 +72,23 @@ func NewRepoContext() {
 
 // Repository represents a git repository.
 type Repository struct {
-	Id          int64
-	OwnerId     int64 `xorm:"unique(s)"`
-	ForkId      int64
-	LowerName   string `xorm:"unique(s) index not null"`
-	Name        string `xorm:"index not null"`
-	Description string
-	Website     string
-	NumWatches  int
-	NumStars    int
-	NumForks    int
-	IsPrivate   bool
-	IsBare      bool
-	Created     time.Time `xorm:"created"`
-	Updated     time.Time `xorm:"updated"`
+	Id              int64
+	OwnerId         int64 `xorm:"unique(s)"`
+	ForkId          int64
+	LowerName       string `xorm:"unique(s) index not null"`
+	Name            string `xorm:"index not null"`
+	Description     string
+	Website         string
+	NumWatches      int
+	NumStars        int
+	NumForks        int
+	NumIssues       int
+	NumClosedIssues int
+	NumOpenIssues   int `xorm:"-"`
+	IsPrivate       bool
+	IsBare          bool
+	Created         time.Time `xorm:"created"`
+	Updated         time.Time `xorm:"updated"`
 }
 
 // IsRepositoryExist returns true if the repository with given name under user has already existed.
@@ -94,17 +97,16 @@ func IsRepositoryExist(user *User, repoName string) (bool, error) {
 	has, err := orm.Where("lower_name = ?", strings.ToLower(repoName)).Get(&repo)
 	if err != nil {
 		return has, err
+	} else if !has {
+		return false, nil
 	}
-	s, err := os.Stat(RepoPath(user.Name, repoName))
-	if err != nil {
-		return false, nil // Error simply means does not exist, but we don't want to show up.
-	}
-	return s.IsDir(), nil
+
+	return com.IsDir(RepoPath(user.Name, repoName)), nil
 }
 
 var (
 	// Define as all lower case!!
-	illegalPatterns = []string{"[.][Gg][Ii][Tt]", "raw", "user", "help", "stars", "issues", "pulls", "commits", "admin", "repo", "template", "admin"}
+	illegalPatterns = []string{"[.][Gg][Ii][Tt]", "raw", "user", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin"}
 )
 
 // IsLegalName returns false if name contains illegal characters.
@@ -222,16 +224,24 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
 	if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "add", "--all"); err != nil {
 		return err
 	}
-	log.Trace("stderr(1): %s", stderr)
+	if len(stderr) > 0 {
+		log.Trace("stderr(1): %s", stderr)
+	}
+
 	if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
 		"-m", "Init commit"); err != nil {
 		return err
 	}
-	log.Trace("stderr(2): %s", stderr)
+	if len(stderr) > 0 {
+		log.Trace("stderr(2): %s", stderr)
+	}
+
 	if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "push", "origin", "master"); err != nil {
 		return err
 	}
-	log.Trace("stderr(3): %s", stderr)
+	if len(stderr) > 0 {
+		log.Trace("stderr(3): %s", stderr)
+	}
 	return nil
 }
 
@@ -241,10 +251,9 @@ func createHookUpdate(hookPath, content string) error {
 		return err
 	}
 	defer pu.Close()
-	if _, err = pu.WriteString(content); err != nil {
-		return err
-	}
-	return nil
+
+	_, err = pu.WriteString(content)
+	return err
 }
 
 // InitRepository initializes README and .gitignore if needed.
@@ -320,10 +329,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
 	}
 
 	// Apply changes and commit.
-	if err := initRepoCommit(tmpDir, user.NewGitSig()); err != nil {
-		return err
-	}
-	return nil
+	return initRepoCommit(tmpDir, user.NewGitSig())
 }
 
 // UserRepo reporesents a repository with user name.
@@ -430,7 +436,8 @@ func GetRepositoryByName(userId int64, repoName string) (*Repository, error) {
 }
 
 // GetRepositoryById returns the repository by given id if exists.
-func GetRepositoryById(id int64) (repo *Repository, err error) {
+func GetRepositoryById(id int64) (*Repository, error) {
+	repo := &Repository{}
 	has, err := orm.Id(id).Get(repo)
 	if err != nil {
 		return nil, err
@@ -485,30 +492,26 @@ func GetWatches(repoId int64) ([]Watch, error) {
 }
 
 // NotifyWatchers creates batch of actions for every watcher.
-func NotifyWatchers(userId, repoId int64, opType int, userName, repoName, refName, content string) error {
+func NotifyWatchers(act *Action) error {
 	// Add feeds for user self and all watchers.
-	watches, err := GetWatches(repoId)
+	watches, err := GetWatches(act.RepoId)
 	if err != nil {
 		return errors.New("repo.NotifyWatchers(get watches): " + err.Error())
 	}
-	watches = append(watches, Watch{UserId: userId})
+
+	// Add feed for actioner.
+	act.UserId = act.ActUserId
+	if _, err = orm.InsertOne(act); err != nil {
+		return errors.New("repo.NotifyWatchers(create action): " + err.Error())
+	}
 
 	for i := range watches {
-		if userId == watches[i].UserId && i > 0 {
-			continue // Do not add twice in case author watches his/her repository.
+		if act.ActUserId == watches[i].UserId {
+			continue
 		}
 
-		_, err = orm.InsertOne(&Action{
-			UserId:      watches[i].UserId,
-			ActUserId:   userId,
-			ActUserName: userName,
-			OpType:      opType,
-			Content:     content,
-			RepoId:      repoId,
-			RepoName:    repoName,
-			RefName:     refName,
-		})
-		if err != nil {
+		act.UserId = watches[i].UserId
+		if _, err = orm.InsertOne(act); err != nil {
 			return errors.New("repo.NotifyWatchers(create action): " + err.Error())
 		}
 	}

+ 9 - 10
modules/avatar/avatar.go

@@ -47,6 +47,7 @@ func HashEmail(email string) string {
 	return hex.EncodeToString(h.Sum(nil))
 }
 
+// Avatar represents the avatar object.
 type Avatar struct {
 	Hash           string
 	AlterImage     string // image path
@@ -96,8 +97,8 @@ func (this *Avatar) Encode(wr io.Writer, size int) (err error) {
 			return
 		}
 		defer fd.Close()
-		img, err = jpeg.Decode(fd)
-		if err != nil {
+
+		if img, err = jpeg.Decode(fd); err != nil {
 			fd.Seek(0, os.SEEK_SET)
 			img, err = png.Decode(fd)
 		}
@@ -110,8 +111,8 @@ func (this *Avatar) Encode(wr io.Writer, size int) (err error) {
 		}
 		imgPath = this.AlterImage
 	}
-	img, err = decodeImageFile(imgPath)
-	if err != nil {
+
+	if img, err = decodeImageFile(imgPath); err != nil {
 		return
 	}
 	m := resize.Resize(uint(size), 0, img, resize.Lanczos3)
@@ -124,8 +125,7 @@ func (this *Avatar) Update() {
 		this.imagePath)
 }
 
-func (this *Avatar) UpdateTimeout(timeout time.Duration) error {
-	var err error
+func (this *Avatar) UpdateTimeout(timeout time.Duration) (err error) {
 	select {
 	case <-time.After(timeout):
 		err = fmt.Errorf("get gravatar image %s timeout", this.Hash)
@@ -140,8 +140,7 @@ type service struct {
 	altImage string
 }
 
-func (this *service) mustInt(r *http.Request, defaultValue int, keys ...string) int {
-	var v int
+func (this *service) mustInt(r *http.Request, defaultValue int, keys ...string) (v int) {
 	for _, k := range keys {
 		if _, err := fmt.Sscanf(r.FormValue(k), "%d", &v); err == nil {
 			defaultValue = v
@@ -176,8 +175,8 @@ func (this *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("ETag", etag)
 	}
 	w.Header().Set("Content-Type", "image/jpeg")
-	err := avatar.Encode(w, size)
-	if err != nil {
+
+	if err := avatar.Encode(w, size); err != nil {
 		log.Warn("avatar encode error: %v", err)
 		w.WriteHeader(500)
 	}

+ 8 - 0
modules/base/markdown.go

@@ -51,6 +51,14 @@ func IsTextFile(data []byte) (string, bool) {
 	return contentType, false
 }
 
+func IsImageFile(data []byte) (string, bool) {
+	contentType := http.DetectContentType(data)
+	if strings.Index(contentType, "image/") != -1 {
+		return contentType, true
+	}
+	return contentType, false
+}
+
 func IsReadmeFile(name string) bool {
 	name = strings.ToLower(name)
 	if len(name) < 6 {

+ 8 - 9
modules/base/tool.go

@@ -412,6 +412,11 @@ func (f StrTo) Int() (int, error) {
 	return int(v), err
 }
 
+func (f StrTo) Int64() (int64, error) {
+	v, err := strconv.ParseInt(f.String(), 10, 64)
+	return int64(v), err
+}
+
 func (f StrTo) String() string {
 	if f.Exist() {
 		return string(f)
@@ -541,16 +546,10 @@ func ActionDesc(act Actioner, avatarLink string) string {
 }
 
 func DiffTypeToStr(diffType int) string {
-	switch diffType {
-	case 1:
-		return "add"
-	case 2:
-		return "modify"
-	case 3:
-		return "del"
-	default:
-		return "unknown"
+	diffTypes := map[int]string{
+		1: "add", 2: "modify", 3: "del",
 	}
+	return diffTypes[diffType]
 }
 
 func DiffLineTypeToStr(diffType int) string {

+ 3 - 1
modules/middleware/repo.go

@@ -56,7 +56,9 @@ func RepoAssignment(redirect bool) martini.Handler {
 		// get repository
 		repo, err := models.GetRepositoryByName(user.Id, params["reponame"])
 		if err != nil {
-			if redirect {
+			if err == models.ErrRepoNotExist {
+				ctx.Handle(404, "RepoAssignment", err)
+			} else if redirect {
 				ctx.Redirect("/")
 				return
 			}

+ 20 - 1
public/css/gogs.css

@@ -854,6 +854,10 @@ html, body {
     min-width: 180px;
 }
 
+.commit-list .sha a {
+    font-family: Consolas, Menlo, Monaco, "Lucida Console", monospace;
+}
+
 .guide-box pre, .guide-box .input-group {
     margin-top: 20px;
     margin-bottom: 30px;
@@ -1119,7 +1123,7 @@ html, body {
 #issue .issue-head .info {
     width: 99%;
     margin-top: 10px;
-    padding-left: 64px;
+    padding-left: 74px;
     margin-bottom: 16px;
     padding-bottom: 20px;
     border-bottom: 1px solid #CCC;
@@ -1169,6 +1173,21 @@ html, body {
     border-color: #CCC;
 }
 
+#issue .issue-head .info .btn {
+    margin-top: -8px;
+    margin-left: 8px;
+}
+
+#issue .issue-action {
+    padding-left: 8px;
+    color: #888;
+    width: 24px;
+}
+
+#issue-edit-title {
+    width: 60%;
+}
+
 /* wrapper and footer */
 
 #wrapper {

+ 62 - 0
public/js/app.js

@@ -50,6 +50,14 @@ var Gogits = {
             }
         }
     });
+    $.fn.extend({
+        toggleHide: function () {
+            $(this).addClass("hidden");
+        },
+        toggleShow: function () {
+            $(this).removeClass("hidden");
+        }
+    })
 }(jQuery));
 
 (function ($) {
@@ -352,6 +360,54 @@ 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");
+            } else {
+                $('.pgsql-setting').addClass("hide");
+            }
+        } else {
+            $('.server-sql').hide();
+            $('.sqlite-setting').removeClass("hide");
+        }
+    });
+}
+
+function initIssue() {
+    // close button
+    (function () {
+        var $closeBtn = $('#issue-close-btn');
+        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"));
+            } else {
+                $closeBtn.text($closeBtn.data("origin"));
+                $openBtn.text($openBtn.data("origin"));
+            }
+        });
+    }());
+
+    // issue edit mode
+    (function () {
+        $("#issue-edit-btn").on("click", function () {
+            $('#issue h1.title,#issue .issue-main > .issue-content .content,#issue-edit-btn').toggleHide();
+            $('#issue-edit-title,#issue-edit-content,.issue-edit-cancel,.issue-edit-save').toggleShow();
+        });
+        $('.issue-edit-cancel').on("click", function () {
+            $('#issue h1.title,#issue .issue-main > .issue-content .content,#issue-edit-btn').toggleShow();
+            $('#issue-edit-title,#issue-edit-content,.issue-edit-cancel,.issue-edit-save').toggleHide();
+        })
+    }());
+}
+
 (function ($) {
     $(function () {
         initCore();
@@ -365,5 +421,11 @@ function initRepository() {
         if ($('.repo-nav').length) {
             initRepository();
         }
+        if ($('#install-card').length) {
+            initInstall();
+        }
+        if ($('#issue').length) {
+            initIssue();
+        }
     });
 })(jQuery);

+ 51 - 10
routers/repo/commit.go

@@ -5,13 +5,22 @@
 package repo
 
 import (
+	"container/list"
+	"path"
+
 	"github.com/codegangsta/martini"
+
 	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/middleware"
 )
 
 func Commits(ctx *middleware.Context, params martini.Params) {
-	brs, err := models.GetBranches(params["username"], params["reponame"])
+	userName := params["username"]
+	repoName := params["reponame"]
+	branchName := params["branchname"]
+
+	brs, err := models.GetBranches(userName, repoName)
 	if err != nil {
 		ctx.Handle(200, "repo.Commits", err)
 		return
@@ -20,38 +29,70 @@ func Commits(ctx *middleware.Context, params martini.Params) {
 		return
 	}
 
-	ctx.Data["IsRepoToolbarCommits"] = true
-	commits, err := models.GetCommits(params["username"],
-		params["reponame"], params["branchname"])
+	var commits *list.List
+	if models.IsBranchExist(userName, repoName, branchName) {
+		commits, err = models.GetCommitsByBranch(userName, repoName, branchName)
+	} else {
+		commits, err = models.GetCommitsByCommitId(userName, repoName, branchName)
+	}
+
 	if err != nil {
-		ctx.Handle(404, "repo.Commits", nil)
+		ctx.Handle(404, "repo.Commits", err)
 		return
 	}
-	ctx.Data["Username"] = params["username"]
-	ctx.Data["Reponame"] = params["reponame"]
+
+	ctx.Data["Username"] = userName
+	ctx.Data["Reponame"] = repoName
 	ctx.Data["CommitCount"] = commits.Len()
 	ctx.Data["Commits"] = commits
+	ctx.Data["IsRepoToolbarCommits"] = true
 	ctx.HTML(200, "repo/commits")
 }
 
 func Diff(ctx *middleware.Context, params martini.Params) {
-	commit, err := models.GetCommit(params["username"], params["reponame"], params["branchname"], params["commitid"])
+	userName := params["username"]
+	repoName := params["reponame"]
+	branchName := params["branchname"]
+	commitId := params["commitid"]
+
+	commit, err := models.GetCommit(userName, repoName, branchName, commitId)
 	if err != nil {
 		ctx.Handle(404, "repo.Diff", err)
 		return
 	}
 
-	diff, err := models.GetDiff(models.RepoPath(params["username"], params["reponame"]), params["commitid"])
+	diff, err := models.GetDiff(models.RepoPath(userName, repoName), commitId)
 	if err != nil {
 		ctx.Handle(404, "repo.Diff", err)
 		return
 	}
 
-	shortSha := params["commitid"][:7]
+	isImageFile := func(name string) bool {
+		repoFile, err := models.GetTargetFile(userName, repoName,
+			branchName, commitId, name)
+
+		if err != nil {
+			return false
+		}
+
+		blob, err := repoFile.LookupBlob()
+		if err != nil {
+			return false
+		}
+
+		data := blob.Contents()
+		_, isImage := base.IsImageFile(data)
+		return isImage
+	}
+
+	shortSha := params["commitid"][:10]
+	ctx.Data["IsImageFile"] = isImageFile
 	ctx.Data["Title"] = commit.Message() + " · " + shortSha
 	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)
+	ctx.Data["RawPath"] = "/" + path.Join(userName, repoName, "raw", commitId)
 	ctx.HTML(200, "repo/diff")
 }

+ 51 - 16
routers/repo/issue.go

@@ -6,6 +6,7 @@ package repo
 
 import (
 	"fmt"
+	"net/url"
 
 	"github.com/codegangsta/martini"
 
@@ -17,23 +18,41 @@ import (
 	"github.com/gogits/gogs/modules/middleware"
 )
 
-func Issues(ctx *middleware.Context, params martini.Params) {
+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
+	ctx.Data["ViewType"] = "all"
+
+	milestoneId, _ := base.StrTo(ctx.Query("milestone")).Int()
+	page, _ := base.StrTo(ctx.Query("page")).Int()
 
-	milestoneId, _ := base.StrTo(params["milestone"]).Int()
-	page, _ := base.StrTo(params["page"]).Int()
+	ctx.Data["IssueCreatedCount"] = 0
+
+	var posterId int64 = 0
+	if ctx.Query("type") == "created_by" {
+		if !ctx.IsSigned {
+			ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
+			ctx.Redirect("/user/login/", 302)
+			return
+		}
+		posterId = ctx.User.Id
+		ctx.Data["ViewType"] = "created_by"
+		ctx.Data["IssueCreatedCount"] = models.GetUserIssueCount(posterId, ctx.Repo.Repository.Id)
+	}
 
 	// Get issues.
-	issues, err := models.GetIssues(0, ctx.Repo.Repository.Id, 0,
-		int64(milestoneId), page, params["state"] == "closed", false, params["labels"], params["sortType"])
+	issues, err := models.GetIssues(0, ctx.Repo.Repository.Id, posterId, int64(milestoneId), page,
+		ctx.Query("state") == "closed", false, ctx.Query("labels"), ctx.Query("sortType"))
 	if err != nil {
 		ctx.Handle(200, "issue.Issues: %v", err)
 		return
 	}
 
-	var closedCount int
 	// Get posters.
 	for i := range issues {
 		u, err := models.GetUserById(issues[i].PosterId)
@@ -41,21 +60,22 @@ func Issues(ctx *middleware.Context, params martini.Params) {
 			ctx.Handle(200, "issue.Issues(get poster): %v", err)
 			return
 		}
-
-		if issues[i].IsClosed {
-			closedCount++
-		}
 		issues[i].Poster = u
 	}
 
 	ctx.Data["Issues"] = issues
-	ctx.Data["IssueCount"] = len(issues)
-	ctx.Data["OpenCount"] = len(issues) - closedCount
-	ctx.Data["ClosedCount"] = closedCount
+	ctx.Data["IssueCount"] = ctx.Repo.Repository.NumIssues
+	ctx.Data["OpenCount"] = ctx.Repo.Repository.NumIssues - ctx.Repo.Repository.NumClosedIssues
+	ctx.Data["ClosedCount"] = ctx.Repo.Repository.NumClosedIssues
+	ctx.Data["IsShowClosed"] = ctx.Query("state") == "closed"
 	ctx.HTML(200, "issue/list")
 }
 
 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
@@ -71,15 +91,16 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
 	}
 
 	issue, err := models.CreateIssue(ctx.User.Id, ctx.Repo.Repository.Id, form.MilestoneId, form.AssigneeId,
-		form.IssueName, form.Labels, form.Content, false)
+		ctx.Repo.Repository.NumIssues, form.IssueName, form.Labels, form.Content, false)
 	if err != nil {
 		ctx.Handle(200, "issue.CreateIssue", err)
 		return
 	}
 
 	// Notify watchers.
-	if err = models.NotifyWatchers(ctx.User.Id, ctx.Repo.Repository.Id, models.OP_CREATE_ISSUE,
-		ctx.User.Name, ctx.Repo.Repository.Name, "", fmt.Sprintf("%d|%s", issue.Index, issue.Name)); err != nil {
+	if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name,
+		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)
 		return
 	}
@@ -97,6 +118,10 @@ 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)
@@ -120,6 +145,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
 		return
 	}
 	issue.Poster = u
+	issue.Content = string(base.RenderMarkdown([]byte(issue.Content), ""))
 
 	// Get comments.
 	comments, err := models.GetIssueComments(issue.Id)
@@ -136,6 +162,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
 			return
 		}
 		comments[i].Poster = u
+		comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ""))
 	}
 
 	ctx.Data["Title"] = issue.Name
@@ -147,6 +174,10 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
 }
 
 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)
@@ -183,6 +214,10 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
 }
 
 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()
 	if err != nil {
 		ctx.Handle(404, "issue.Comment", err)

+ 50 - 22
routers/repo/repo.go

@@ -57,19 +57,23 @@ func Single(ctx *middleware.Context, params martini.Params) {
 		return
 	}
 
+	branchName := params["branchname"]
+	userName := params["username"]
+	repoName := params["reponame"]
+
 	// Get tree path
 	treename := params["_1"]
 
 	if len(treename) > 0 && treename[len(treename)-1] == '/' {
 		ctx.Redirect("/" + ctx.Repo.Owner.LowerName + "/" +
-			ctx.Repo.Repository.Name + "/src/" + params["branchname"] + "/" + treename[:len(treename)-1])
+			ctx.Repo.Repository.Name + "/src/" + branchName + "/" + treename[:len(treename)-1])
 		return
 	}
 
 	ctx.Data["IsRepoToolbarSource"] = true
 
 	// Branches.
-	brs, err := models.GetBranches(params["username"], params["reponame"])
+	brs, err := models.GetBranches(userName, repoName)
 	if err != nil {
 		ctx.Handle(404, "repo.Single(GetBranches)", err)
 		return
@@ -80,15 +84,22 @@ func Single(ctx *middleware.Context, params martini.Params) {
 	}
 	ctx.Data["Branches"] = brs
 
-	repoFile, err := models.GetTargetFile(params["username"], params["reponame"],
-		params["branchname"], params["commitid"], treename)
+	var commitId string
+	isViewBranch := models.IsBranchExist(userName, repoName, branchName)
+	if !isViewBranch {
+		commitId = branchName
+	}
+	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/" + params["branchname"]
-	rawLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/raw/" + params["branchname"]
+	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)
@@ -111,23 +122,28 @@ func Single(ctx *middleware.Context, params martini.Params) {
 
 			data := blob.Contents()
 			_, isTextFile := base.IsTextFile(data)
+			_, isImageFile := base.IsImageFile(data)
 			ctx.Data["FileIsText"] = isTextFile
 
-			readmeExist := base.IsMarkdownFile(repoFile.Name) || base.IsReadmeFile(repoFile.Name)
-			ctx.Data["ReadmeExist"] = readmeExist
-			if readmeExist {
-				ctx.Data["FileContent"] = string(base.RenderMarkdown(data, ""))
+			if isImageFile {
+				ctx.Data["IsImageFile"] = true
 			} else {
-				if isTextFile {
-					ctx.Data["FileContent"] = string(data)
+				readmeExist := base.IsMarkdownFile(repoFile.Name) || base.IsReadmeFile(repoFile.Name)
+				ctx.Data["ReadmeExist"] = readmeExist
+				if readmeExist {
+					ctx.Data["FileContent"] = string(base.RenderMarkdown(data, ""))
+				} else {
+					if isTextFile {
+						ctx.Data["FileContent"] = string(data)
+					}
 				}
 			}
 		}
 
 	} else {
 		// Directory and file list.
-		files, err := models.GetReposFiles(params["username"], params["reponame"],
-			params["branchname"], params["commitid"], treename)
+		files, err := models.GetReposFiles(userName, repoName,
+			branchName, commitId, treename)
 		if err != nil {
 			ctx.Handle(404, "repo.Single(GetReposFiles)", err)
 			return
@@ -166,8 +182,8 @@ func Single(ctx *middleware.Context, params martini.Params) {
 		}
 	}
 
-	ctx.Data["Username"] = params["username"]
-	ctx.Data["Reponame"] = params["reponame"]
+	ctx.Data["Username"] = userName
+	ctx.Data["Reponame"] = repoName
 
 	var treenames []string
 	Paths := make([]string, 0)
@@ -185,8 +201,8 @@ func Single(ctx *middleware.Context, params martini.Params) {
 	}
 
 	// Get latest commit according username and repo name.
-	commit, err := models.GetCommit(params["username"], params["reponame"],
-		params["branchname"], params["commitid"])
+	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)
@@ -194,6 +210,8 @@ func Single(ctx *middleware.Context, params martini.Params) {
 	}
 	ctx.Data["LastCommit"] = commit
 
+	ctx.Data["CommitId"] = commitId
+
 	ctx.Data["Paths"] = Paths
 	ctx.Data["Treenames"] = treenames
 	ctx.Data["BranchLink"] = branchLink
@@ -209,8 +227,18 @@ func SingleDownload(ctx *middleware.Context, params martini.Params) {
 	// Get tree path
 	treename := params["_1"]
 
-	repoFile, err := models.GetTargetFile(params["username"], params["reponame"],
-		params["branchname"], params["commitid"], treename)
+	branchName := params["branchname"]
+	userName := params["username"]
+	repoName := params["reponame"]
+
+	var commitId string
+	if !models.IsBranchExist(userName, repoName, branchName) {
+		commitId = branchName
+		branchName = ""
+	}
+
+	repoFile, err := models.GetTargetFile(userName, repoName,
+		branchName, commitId, treename)
 
 	if err != nil {
 		ctx.Handle(404, "repo.SingleDownload(GetTargetFile)", err)
@@ -225,9 +253,9 @@ func SingleDownload(ctx *middleware.Context, params martini.Params) {
 
 	data := blob.Contents()
 	contentType, isTextFile := base.IsTextFile(data)
+	_, isImageFile := base.IsImageFile(data)
 	ctx.Res.Header().Set("Content-Type", contentType)
-	if !isTextFile {
-		ctx.Res.Header().Set("Content-Type", contentType)
+	if !isTextFile && !isImageFile {
 		ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(treename))
 		ctx.Res.Header().Set("Content-Transfer-Encoding", "binary")
 	}

+ 79 - 0
routers/user/user.go

@@ -286,6 +286,85 @@ func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
 
 func Issues(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Your Issues"
+	ctx.Data["ViewType"] = "all"
+
+	page, _ := base.StrTo(ctx.Query("page")).Int()
+	repoId, _ := base.StrTo(ctx.Query("repoid")).Int64()
+
+	ctx.Data["RepoId"] = repoId
+
+	var posterId int64 = 0
+	if ctx.Query("type") == "created_by" {
+		posterId = ctx.User.Id
+		ctx.Data["ViewType"] = "created_by"
+	}
+
+	// Get all repositories.
+	repos, err := models.GetRepositories(ctx.User)
+	if err != nil {
+		ctx.Handle(200, "user.Issues(get repositories)", err)
+		return
+	}
+
+	showRepos := make([]models.Repository, 0, len(repos))
+
+	var closedIssueCount, createdByCount 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, "", "")
+		if err != nil {
+			ctx.Handle(200, "user.Issues(get issues)", err)
+			return
+		}
+
+		closedIssueCount += repo.NumClosedIssues
+
+		// Set repository information to issues.
+		for j := range issues {
+			issues[j].Repo = &repos[i]
+		}
+		allIssues = append(allIssues, issues...)
+
+		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.
+	for i := range allIssues {
+		u, err := models.GetUserById(allIssues[i].PosterId)
+		if err != nil {
+			ctx.Handle(200, "user.Issues(get poster): %v", err)
+			return
+		}
+		allIssues[i].Poster = u
+		if u.Id == ctx.User.Id {
+			createdByCount++
+		}
+
+		if repoId > 0 && repoId != allIssues[i].Repo.Id {
+			continue
+		}
+
+		if isShowClosed == allIssues[i].IsClosed {
+			showIssues = append(showIssues, allIssues[i])
+		}
+	}
+
+	ctx.Data["Repos"] = showRepos
+	ctx.Data["Issues"] = showIssues
+	ctx.Data["AllIssueCount"] = len(allIssues)
+	ctx.Data["ClosedIssueCount"] = closedIssueCount
+	ctx.Data["OpenIssueCount"] = len(allIssues) - closedIssueCount
+	ctx.Data["CreatedByCount"] = createdByCount
 	ctx.HTML(200, "issue/user")
 }
 

+ 162 - 33
templates/install.tmpl

@@ -2,69 +2,198 @@
 <div id="body" class="container">
     <form action="/install" method="post" class="form-horizontal card" id="install-card">
         {{.CsrfTokenHtml}}
-        <h3>Install Steps</h3>
+        <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">GoGits need MySQL or PostgreSQL server</p>
-        <div class="form-group {{if .Err_User}}has-error has-feedback{{end}}">
-            <label class="col-md-3 control-label"><strong>MySQL </strong>Host: </label>
+        <p class="help-block text-center">Gogs requires MySQL or PostgreSQL based on your choice</p>
+        <div class="form-group">
+            <label class="col-md-3 control-label">Database Type: </label>
             <div class="col-md-8">
-                <input name="host" class="form-control" placeholder="Type mysql server ip or domain" value="localhost" required="required">
+                <select name="database" id="install-database" class="form-control">
+                    <option value="mysql">MySQL</option>
+                    <option value="pgsql">PostgreSQL</option>
+                    <option value="sqlite">SQLite</option>
+                </select>
             </div>
         </div>
-        <div class="form-group {{if .Err_User}}has-error has-feedback{{end}}">
-            <label class="col-md-3 control-label">Port: </label>
-            <div class="col-md-8">
-                <input name="port" class="form-control" placeholder="Type mysql server port" value="3306" required="required">
+        <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 mysql server ip or domain" value="localhost" required="required">
+                </div>
             </div>
-        </div>
-        <div class="form-group {{if .Err_User}}has-error has-feedback{{end}}">
-            <label class="col-md-3 control-label">User: </label>
-            <div class="col-md-8">
-                <input name="user" class="form-control" placeholder="Type mysql username" required="required">
+            <div class="form-group">
+                <label class="col-md-3 control-label">Port: </label>
+
+                <div class="col-md-8">
+                    <input name="port" class="form-control" placeholder="Type mysql server port" value="3306" required="required">
+                </div>
             </div>
-        </div>
-        <div class="form-group {{if .Err_Password}}has-error has-feedback{{end}}">
-            <label class="col-md-3 control-label">Password: </label>
-            <div class="col-md-8">
-                <input name="passwd" type="password" class="form-control" placeholder="Type mysql password" required="required">
+            <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 mysql username" required="required">
+                </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 mysql password" required="required">
+                </div>
+            </div>
+
+            <div class="form-group">
+                <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="gogs" required="required">
+                    <p class="help-block">Recommend use INNODB engine with utf8_general_ci charset.</p>
+                </div>
+            </div>
+
+            <div class="form-group pgsql-setting hide">
+                <label class="col-md-3 control-label">SSL Mode: </label>
+                <div class="col-md-8">
+                    <select name="ssl_mode" class="form-control">
+                        <option value="disable">Disable</option>
+                        <option value="require">Require</option>
+                        <option value="verify-full">Verify Full</option>
+                    </select>
+                </div>
             </div>
         </div>
-        <div class="form-group {{if .Err_Password}}has-error has-feedback{{end}}">
-            <label class="col-md-3 control-label">Database: </label>
-            <div class="col-md-8">
-                <input name="database" type="text" class="form-control" placeholder="Type mysql database name" value="gogs" required="required">
-                <p class="help-block">Recommend use INNODB engine with utf8_general_ci charset.</p>
+        <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 sqlite file path" value="xxx/file.db">
+                    <p class="help-block">The file path of SQLite database.</p>
+                </div>
             </div>
         </div>
 
-        <div class="form-group">
+        <!-- <div class="form-group">
             <div class="col-md-8 col-md-offset-3">
-                   <button class="btn btn-sm btn-info">Test Connection</button>
+                <button class="btn btn-sm btn-info">Test Connection</button>
             </div>
-        </div>
+        </div> -->
 
         <hr/>
 
-        <p class="help-block text-center">General settings for GoGits</p>
+        <p class="help-block text-center">General Settings of Gogs</p>
 
-        <div class="form-group {{if .Err_Password}}has-error has-feedback{{end}}">
+        <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="/var/gogs/repostiory" required="required">
+
                 <p class="help-block">The git copy of each repository is saved in this directory.</p>
             </div>
         </div>
-        <div class="form-group {{if .Err_Password}}has-error has-feedback{{end}}">
-            <label class="col-md-3 control-label">System User: </label>
+        <div class="form-group">
+            <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 mysql password" value="root" required="required">
-                <p class="help-block">The user has access to visit and run GoGits.</p>
+                <p class="help-block">The user has access to visit and run Gogs.</p>
             </div>
         </div>
+
         <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="repo-path" type="text" class="form-control" placeholder="Type admin user name" value="admin" required="required">
+            </div>
+        </div>
+        <div class="form-group">
+            <label class="col-md-3 control-label">Password: </label>
+
+            <div class="col-md-8">
+                <input name="system-user" type="password" class="form-control" placeholder="Type admin user password" required="required">
+            </div>
+        </div>
+
+        <hr/>
+
         <div class="form-group text-center">
-            <a class="btn btn-danger btn-lg">Install GoGits</a>
+            <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
+            </button>
         </div>
+
+        <div class="modal fade" id="advance-options-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header"><h4 class="modal-title">Advanced Options</h4></div>
+                    <div class="modal-body">
+                        <p class="help-block text-center">Email Service Settings</p>
+
+                        <div class="form-group">
+                            <label class="col-md-3 control-label">SMTP Host: </label>
+
+                            <div class="col-md-8">
+                                <input name="repo-path" type="text" class="form-control" placeholder="Type admin user name">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-md-3 control-label">Email: </label>
+
+                            <div class="col-md-8">
+                                <input name="repo-path" type="text" class="form-control" placeholder="Type admin user name">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-md-3 control-label">Password: </label>
+
+                            <div class="col-md-8">
+                                <input name="system-user" type="password" class="form-control" placeholder="Type admin user password">
+                            </div>
+                        </div>
+                        <hr/>
+                        <p class="text-center help-block">Notification Settings</p>
+
+                        <div class="form-group">
+                            <div class="col-md-offset-3 col-md-7">
+                                <div class="checkbox">
+                                    <label>
+                                        <input name="system-user" type="checkbox">
+                                        <strong>Enable Register Confirmation</strong>
+                                    </label>
+                                </div>
+                            </div>
+                        </div>
+
+                        <div class="form-group">
+                            <div class="col-md-offset-3 col-md-7">
+                                <div class="checkbox">
+                                    <label>
+                                        <input name="system-user" type="checkbox">
+                                        <strong>Enable Mail Notification</strong>
+                                    </label>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-success" data-dismiss="modal">Confirm</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
     </form>
 </div>
 {{template "base/footer" .}}

+ 6 - 5
templates/issue/list.tmpl

@@ -6,16 +6,17 @@
     <div id="issue">
         <div class="col-md-3 filter-list">
             <ul class="list-unstyled">
-                <li><a href="#" class="active">All Issues <strong class="pull-right">{{.IssueCount}}</strong></a></li>
-                <li><a href="#">My Issues</a></li>
-                <li><a href="#">Mentioned</a></li>
+                <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="#">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="#">Mentioned</a></li> -->
             </ul>
         </div>
         <div class="col-md-9">
             <div class="filter-option">
                 <div class="btn-group">
-                    <a class="btn btn-default active issue-open" href="#">{{.OpenCount}} Open</a>
-                    <a class="btn btn-default issue-close" href="#">{{.ClosedCount}} Closed</a>
+                    <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>
                 </div>
             </div>
             <div class="issues list-group">

+ 17 - 35
templates/issue/user.tmpl

@@ -16,53 +16,35 @@
     <div id="issue">
         <div class="col-md-3 filter-list">
             <ul class="list-unstyled">
-                <li><a href="#" class="active">In your repositories <strong class="pull-right">10</strong></a></li>
-                <li><a href="#">Created by you</a></li>
-                <li><a href="#">Assigned to you</a></li>
+                <li><a href="/issues"{{if eq .ViewType "all"}} class="active"{{end}}>In your repositories <strong class="pull-right">{{.AllIssueCount}}</strong></a></li>
+                <!-- <li><a href="#">Assigned to you</a></li> -->
+                <li><a href="/issues?type=created_by"{{if eq .ViewType "created_by"}} class="active"{{end}}>Created by you <strong class="pull-right">{{.CreatedByCount}}</strong></a></li>
                 <li><hr/></li>
-                <li><a href="" class="sm">gogits/gogs <strong class="pull-right">12</strong></a></li>
-                <li><a href="" class="sm">gogits/session <strong class="pull-right">8</strong></a></li>
-                <li><a href="" class="sm">gogits/git <strong class="pull-right">2</strong></a></li>
+                {{range .Repos}}
+                <li><a href="/issues?type={{$.ViewType}}{{if eq $.RepoId .Id}}{{else}}&repoid={{.Id}}{{end}}" class="sm{{if eq $.RepoId .Id}} active{{end}}">{{$.SignedUser.Name}}/{{.Name}} <strong class="pull-right">{{.NumOpenIssues}}</strong></a></li>
+                {{end}}
             </ul>
         </div>
         <div class="col-md-9">
             <div class="filter-option">
                 <div class="btn-group">
-                    <a class="btn btn-default active issue-open" href="#">27 Open</a>
-                    <a class="btn btn-default issue-close" href="#">Close 128</a>
+                    <a class="btn btn-default issue-open{{if not .IsShowClosed}} active{{end}}" href="/issues?type={{.ViewType}}&repoid={{.RepoId}}">{{.OpenIssueCount}} Open</a>
+                    <a class="btn btn-default issue-close{{if .IsShowClosed}} active{{end}}" href="/issues?state=closed&type={{.ViewType}}&repoid={{.RepoId}}">{{.ClosedIssueCount}} Close</a>
                 </div>
             </div>
             <div class="issues list-group">
-                <div class="list-group-item unread issue-item" id="issue-id">
-                    <span class="number pull-right">#123</span>
-                    <h5 class="title"><a href="#">Bug: When running tests after generating a beego app, templates do not load.</a></h5>
+                {{range .Issues}}
+                <div class="list-group-item issue-item" id="issue-{{.Id}}">
+                    <span class="number pull-right">#{{.Index}}</span>
+                    <h5 class="title"><a href="/{{$.SignedUser.Name}}/{{.Repo.Name}}/issues/{{.Index}}">{{.Name}}</a></h5>
                     <p class="info">
-                        <span class="author"><img class="avatar" src="http://tp2.sinaimg.cn/5068084885/50/40050297589/1" alt="" width="20"/>
-                        <a href="#">Obama</a></span>
-                        <span class="time">3 days ago</span>
-                        <span class="comment"><i class="fa fa-comments"></i> 3</span>
-                    </p>
-                </div>
-                <div class="list-group-item issue-item" id="issue-id2">
-                    <span class="number pull-right">#123</span>
-                    <h5 class="title"><a href="#">Bug: When running tests after generating a beego app, templates do not load.</a></h5>
-                    <p class="info">
-                        <span class="author"><img class="avatar" src="http://tp2.sinaimg.cn/5068084885/50/40050297589/1" alt="" width="20"/>
-                        <a href="#">Obama</a></span>
-                        <span class="time">3 days ago</span>
-                        <span class="comment"><i class="fa fa-comments"></i> 3</span>
-                    </p>
-                </div>
-                <div class="list-group-item issue-item" id="issue-id3">
-                    <span class="number pull-right">#123</span>
-                    <h5 class="title"><a href="#">Bug: When running tests after generating a beego app, templates do not load.</a></h5>
-                    <p class="info">
-                        <span class="author"><img class="avatar" src="http://tp2.sinaimg.cn/5068084885/50/40050297589/1" alt="" width="20"/>
-                        <a href="#">Obama</a></span>
-                        <span class="time">3 days ago</span>
-                        <span class="comment"><i class="fa fa-comments"></i> 3</span>
+                        <span class="author"><img class="avatar" src="{{.Poster.AvatarLink}}" alt="" width="20"/>
+                        <a href="/user/{{.Poster.Name}}">{{.Poster.Name}}</a></span>
+                        <span class="time">{{TimeSince .Created}}</span>
+                        <span class="comment"><i class="fa fa-comments"></i> {{.NumComments}}</span>
                     </p>
                 </div>
+                {{end}}
             </div>
         </div>
     </div>

+ 18 - 6
templates/issue/view.tmpl

@@ -4,12 +4,16 @@
 {{template "repo/toolbar" .}}
 <div id="body" class="container">
     <div id="issue">
-        <div id="issue-id" class="issue-whole">
+        <div id="issue-{issue.id}" class="issue-whole">
             <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"/>
                 <p class="info pull-left">
+                    <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>
                     <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
@@ -18,18 +22,24 @@
             <div class="issue-main">
                <div class="panel panel-default issue-content">
                    <div class="panel-body markdown">
-                       <p>{{.Issue.Content}}</p>
+                       <div class="content">
+                           {{str2html .Issue.Content}}
+                       </div>
+                       <textarea class="form-control hidden" name="content" id="issue-edit-content" rows="10" data-ajax-rel="issue-save">content</textarea>
                    </div>
                </div>
                {{range .Comments}}
-               <div class="issue-child">
+               <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">
-                           <p>{{.Content}}</p>
+                          {{str2html .Content}}
                        </div>
                    </div>
                 </div>
@@ -52,7 +62,7 @@
                                     <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-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">{{.content}}</textarea>
                                         </div>
                                     </div>
                                     <div class="tab-pane" id="issue-preview">preview</div>
@@ -61,7 +71,9 @@
                             <div class="text-right">
                                 <div class="form-group">
                                     <input type="hidden" value="id" name="repo-id"/>
-                                    <button class="btn-success btn">Comment</button>
+                                    <button class="btn-default btn issue-open" id="issue-open-btn" data-origin="Open" data-text="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;
+                                    <button class="btn-success btn" id="issue-reply-btn">Comment</button>
                                 </div>
                             </div>
                         </div>

+ 2 - 2
templates/repo/commits.tmpl

@@ -27,7 +27,7 @@
                 {{range $r}}
                 <tr>
                     <td class="author"><img class="avatar" src="{{AvatarLink .Committer.Email}}" alt=""/><a href="/user/{{.Committer.Name}}">{{.Committer.Name}}</a></td>
-                    <td class="sha"><a class="label label-success" href="/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 7}} </a></td>
+                    <td class="sha"><a class="label label-success" href="/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td>
                     <td class="message">{{.Message}} </td>
                     <td class="date">{{TimeSince .Committer.When}}</td>
                 </tr>
@@ -37,4 +37,4 @@
         </div>
     </div>
 </div>
-{{template "base/footer" .}}
+{{template "base/footer" .}}

+ 10 - 3
templates/repo/diff.tmpl

@@ -6,7 +6,7 @@
     <div id="source">
         <div class="panel panel-info diff-box diff-head-box">
             <div class="panel-heading">
-                <a class="pull-right btn btn-primary btn-sm" href="#commit-source">Browse Source</a>
+                <a class="pull-right btn btn-primary btn-sm" href="{{.SourcePath}}">Browse Source</a>
                 <h4>{{.Commit.Message}}</h4>
             </div>
             <div class="panel-body">
@@ -57,10 +57,16 @@
                     </span>
                     <span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span>
                 </div>
-                <a class="btn btn-default btn-sm pull-right" href="#">View File</a>
+                <a class="btn btn-default btn-sm pull-right" href="{{$.SourcePath}}/{{.Name}}">View File</a>
                 <span class="file">{{.Name}}</span>
             </div>
+            {{$isImage := (call $.IsImageFile .Name)}}
             <div class="panel-body file-body file-code code-view code-diff">
+                {{if $isImage}}
+                    <div class="text-center">
+                        <img src="{{$.RawPath}}/{{.Name}}">
+                    </div>
+                {{else}}
                 <table>
                     <tbody>
                         {{range .Sections}}
@@ -201,6 +207,7 @@
                         </tr> -->
                     </tbody>
                 </table>
+                {{end}}
             </div>
         </div>
         {{end}}
@@ -411,4 +418,4 @@
         </div> -->
     </div>
 </div>
-{{template "base/footer" .}}
+{{template "base/footer" .}}

+ 2 - 2
templates/repo/single.tmpl

@@ -11,7 +11,7 @@
             {{ $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>{{.Branchname}}&nbsp;&nbsp;
+                <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;
                     <b class="caret"></b></a>
                 <ul class="dropdown-menu">
                     {{range .Branches}}
@@ -41,4 +41,4 @@
         {{end}}
     </div>
 </div>
-{{template "base/footer" .}}
+{{template "base/footer" .}}

+ 6 - 2
templates/repo/single_file.tmpl

@@ -23,7 +23,11 @@
     </div>
     {{if not .FileIsText}}
         <div class="panel-footer text-center">
-            <a href="{{.FileLink}}" class="btn btn-default">View Raw</a>
+            {{if .IsImageFile}}
+                <img src="{{.FileLink}}">
+            {{else}}
+                <a href="{{.FileLink}}" class="btn btn-default">View Raw</a>
+            {{end}}
         </div>
     {{else}}
         {{if .ReadmeExist}}
@@ -43,4 +47,4 @@
             </div>
         {{end}}
     {{end}}
-</div>
+</div>

+ 1 - 1
templates/repo/toolbar.tmpl

@@ -5,7 +5,7 @@
                 <ul class="nav navbar-nav">
                     <li class="{{if .IsRepoToolbarSource}}active{{end}}"><a href="/{{.RepositoryLink}}">Source</a></li>
                     {{if not .IsBareRepo}}
-                    <li class="{{if .IsRepoToolbarCommits}}active{{end}}"><a href="/{{.RepositoryLink}}/commits/{{.Branchname}}">Commits</a></li>
+                    {{if .IsViewBranch}}<li class="{{if .IsRepoToolbarCommits}}active{{end}}"><a href="/{{.RepositoryLink}}/commits/{{.Branchname}}">Commits</a></li>{{end}}
                     <!-- <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>

+ 4 - 4
web.go

@@ -138,6 +138,10 @@ func runWeb(*cli.Context) {
 		r.Any("/:userid/delete", admin.DeleteUser)
 	}, adminReq)
 
+	if martini.Env == martini.Dev {
+		m.Get("/template/**", dev.TemplatePreview)
+	}
+
 	m.Group("/:username/:reponame", func(r martini.Router) {
 		r.Post("/settings", repo.SettingPost)
 		r.Get("/settings", repo.Setting)
@@ -168,10 +172,6 @@ func runWeb(*cli.Context) {
 		r.Any("/:reponame/**", repo.Http)
 	}, ignSignIn)
 
-	if martini.Env == martini.Dev {
-		m.Get("/template/**", dev.TemplatePreview)
-	}
-
 	// Not found handler.
 	m.NotFound(routers.NotFound)