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

This commit is contained in:
Lunny Xiao 2014-03-28 10:51:42 +08:00
commit 89258e868b
30 changed files with 735 additions and 282 deletions

View file

@ -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) ![Demo](http://gowalker.org/public/gogs_demo.gif)
##### Current version: 0.1.8 Alpha ##### Current version: 0.1.9 Alpha
#### Other language version #### Other language version
@ -19,7 +19,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
## Overview ## 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. - 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! - 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). - 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 ## Features
- Activity timeline - Activity timeline
- SSH/HTTPS protocol support. - SSH/HTTPS(Clone only) protocol support.
- Register/delete account. - Register/delete account.
- Create/delete/watch public repository. - Create/delete/watch public repository.
- User profile page. - User profile page.
@ -58,3 +58,7 @@ There are two ways to install Gogs:
## Contributors ## 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. 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.

View file

@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
![Demo](http://gowalker.org/public/gogs_demo.gif) ![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) 跟随开发团队的脚步。 - 您可以到 [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
- 想要先睹为快?通过 [在线体验](http://try.gogits.org/Unknown/gogs) 或查看 **安装部署 -> 二进制安装** 小节。 - 想要先睹为快?通过 [在线体验](http://try.gogits.org/Unknown/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
- 使用过程中遇到问题?尝试从 [故障排查](https://github.com/gogits/gogs/wiki/Troubleshooting) 页面获取帮助。 - 使用过程中遇到问题?尝试从 [故障排查](https://github.com/gogits/gogs/wiki/Troubleshooting) 页面获取帮助。
@ -23,7 +23,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
## 功能特性 ## 功能特性
- 活动时间线 - 活动时间线
- SSH/HTTPS 协议支持 - SSH/HTTPS(仅限 Clone 协议支持
- 注册/删除用户 - 注册/删除用户
- 创建/删除/关注公开仓库 - 创建/删除/关注公开仓库
- 用户个人信息页面 - 用户个人信息页面
@ -54,3 +54,7 @@ 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) 文件中。

View file

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

View file

@ -19,7 +19,7 @@ import (
// Test that go1.2 tag above is included in builds. main.go refers to this definition. // Test that go1.2 tag above is included in builds. main.go refers to this definition.
const go12tag = true const go12tag = true
const APP_VER = "0.1.8.0326 Alpha" const APP_VER = "0.1.9.0327 Alpha"
func init() { func init() {
base.AppVer = APP_VER base.AppVer = APP_VER

View file

@ -15,7 +15,7 @@ const (
AU_WRITABLE AU_WRITABLE
) )
// Access represents the accessibility of user and repository. // Access represents the accessibility of user to repository.
type Access struct { type Access struct {
Id int64 Id int64
UserName string `xorm:"unique(s)"` UserName string `xorm:"unique(s)"`
@ -30,7 +30,7 @@ func AddAccess(access *Access) error {
return err 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) { func HasAccess(userName, repoName string, mode int) (bool, error) {
return orm.Get(&Access{ return orm.Get(&Access{
Id: 0, Id: 0,

View file

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

View file

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

View file

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

View file

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

View file

@ -82,6 +82,9 @@ type Repository struct {
NumWatches int NumWatches int
NumStars int NumStars int
NumForks int NumForks int
NumIssues int
NumClosedIssues int
NumOpenIssues int `xorm:"-"`
IsPrivate bool IsPrivate bool
IsBare bool IsBare bool
Created time.Time `xorm:"created"` Created time.Time `xorm:"created"`
@ -94,17 +97,16 @@ func IsRepositoryExist(user *User, repoName string) (bool, error) {
has, err := orm.Where("lower_name = ?", strings.ToLower(repoName)).Get(&repo) has, err := orm.Where("lower_name = ?", strings.ToLower(repoName)).Get(&repo)
if err != nil { if err != nil {
return has, err return has, err
} else if !has {
return false, nil
} }
s, err := os.Stat(RepoPath(user.Name, repoName))
if err != nil { return com.IsDir(RepoPath(user.Name, repoName)), nil
return false, nil // Error simply means does not exist, but we don't want to show up.
}
return s.IsDir(), nil
} }
var ( var (
// Define as all lower case!! // 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. // 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 { if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "add", "--all"); err != nil {
return err return err
} }
if len(stderr) > 0 {
log.Trace("stderr(1): %s", stderr) log.Trace("stderr(1): %s", stderr)
}
if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
"-m", "Init commit"); err != nil { "-m", "Init commit"); err != nil {
return err return err
} }
if len(stderr) > 0 {
log.Trace("stderr(2): %s", stderr) log.Trace("stderr(2): %s", stderr)
}
if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "push", "origin", "master"); err != nil { if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "push", "origin", "master"); err != nil {
return err return err
} }
if len(stderr) > 0 {
log.Trace("stderr(3): %s", stderr) log.Trace("stderr(3): %s", stderr)
}
return nil return nil
} }
@ -241,11 +251,10 @@ func createHookUpdate(hookPath, content string) error {
return err return err
} }
defer pu.Close() defer pu.Close()
if _, err = pu.WriteString(content); err != nil {
_, err = pu.WriteString(content)
return err return err
} }
return nil
}
// InitRepository initializes README and .gitignore if needed. // InitRepository initializes README and .gitignore if needed.
func initRepository(f string, user *User, repo *Repository, initReadme bool, repoLang, license string) error { func initRepository(f string, user *User, repo *Repository, initReadme bool, repoLang, license string) error {
@ -320,10 +329,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
} }
// Apply changes and commit. // Apply changes and commit.
if err := initRepoCommit(tmpDir, user.NewGitSig()); err != nil { return initRepoCommit(tmpDir, user.NewGitSig())
return err
}
return nil
} }
// UserRepo reporesents a repository with user name. // 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. // 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) has, err := orm.Id(id).Get(repo)
if err != nil { if err != nil {
return nil, err return nil, err
@ -485,30 +492,26 @@ func GetWatches(repoId int64) ([]Watch, error) {
} }
// NotifyWatchers creates batch of actions for every watcher. // 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. // Add feeds for user self and all watchers.
watches, err := GetWatches(repoId) watches, err := GetWatches(act.RepoId)
if err != nil { if err != nil {
return errors.New("repo.NotifyWatchers(get watches): " + err.Error()) return errors.New("repo.NotifyWatchers(get watches): " + err.Error())
} }
watches = append(watches, Watch{UserId: userId})
for i := range watches { // Add feed for actioner.
if userId == watches[i].UserId && i > 0 { act.UserId = act.ActUserId
continue // Do not add twice in case author watches his/her repository. if _, err = orm.InsertOne(act); err != nil {
return errors.New("repo.NotifyWatchers(create action): " + err.Error())
} }
_, err = orm.InsertOne(&Action{ for i := range watches {
UserId: watches[i].UserId, if act.ActUserId == watches[i].UserId {
ActUserId: userId, continue
ActUserName: userName, }
OpType: opType,
Content: content, act.UserId = watches[i].UserId
RepoId: repoId, if _, err = orm.InsertOne(act); err != nil {
RepoName: repoName,
RefName: refName,
})
if err != nil {
return errors.New("repo.NotifyWatchers(create action): " + err.Error()) return errors.New("repo.NotifyWatchers(create action): " + err.Error())
} }
} }

View file

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

View file

@ -51,6 +51,14 @@ func IsTextFile(data []byte) (string, bool) {
return contentType, false 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 { func IsReadmeFile(name string) bool {
name = strings.ToLower(name) name = strings.ToLower(name)
if len(name) < 6 { if len(name) < 6 {

View file

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

View file

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

View file

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

View file

@ -50,6 +50,14 @@ var Gogits = {
} }
} }
}); });
$.fn.extend({
toggleHide: function () {
$(this).addClass("hidden");
},
toggleShow: function () {
$(this).removeClass("hidden");
}
})
}(jQuery)); }(jQuery));
(function ($) { (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 ($) {
$(function () { $(function () {
initCore(); initCore();
@ -365,5 +421,11 @@ function initRepository() {
if ($('.repo-nav').length) { if ($('.repo-nav').length) {
initRepository(); initRepository();
} }
if ($('#install-card').length) {
initInstall();
}
if ($('#issue').length) {
initIssue();
}
}); });
})(jQuery); })(jQuery);

View file

@ -5,13 +5,22 @@
package repo package repo
import ( import (
"container/list"
"path"
"github.com/codegangsta/martini" "github.com/codegangsta/martini"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/middleware" "github.com/gogits/gogs/modules/middleware"
) )
func Commits(ctx *middleware.Context, params martini.Params) { 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 { if err != nil {
ctx.Handle(200, "repo.Commits", err) ctx.Handle(200, "repo.Commits", err)
return return
@ -20,38 +29,70 @@ func Commits(ctx *middleware.Context, params martini.Params) {
return return
} }
ctx.Data["IsRepoToolbarCommits"] = true var commits *list.List
commits, err := models.GetCommits(params["username"], if models.IsBranchExist(userName, repoName, branchName) {
params["reponame"], params["branchname"]) commits, err = models.GetCommitsByBranch(userName, repoName, branchName)
} else {
commits, err = models.GetCommitsByCommitId(userName, repoName, branchName)
}
if err != nil { if err != nil {
ctx.Handle(404, "repo.Commits", nil) ctx.Handle(404, "repo.Commits", err)
return 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["CommitCount"] = commits.Len()
ctx.Data["Commits"] = commits ctx.Data["Commits"] = commits
ctx.Data["IsRepoToolbarCommits"] = true
ctx.HTML(200, "repo/commits") ctx.HTML(200, "repo/commits")
} }
func Diff(ctx *middleware.Context, params martini.Params) { 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 { if err != nil {
ctx.Handle(404, "repo.Diff", err) ctx.Handle(404, "repo.Diff", err)
return 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 { if err != nil {
ctx.Handle(404, "repo.Diff", err) ctx.Handle(404, "repo.Diff", err)
return 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["Title"] = commit.Message() + " · " + shortSha
ctx.Data["Commit"] = commit ctx.Data["Commit"] = commit
ctx.Data["ShortSha"] = shortSha ctx.Data["ShortSha"] = shortSha
ctx.Data["Diff"] = diff ctx.Data["Diff"] = diff
ctx.Data["IsRepoToolbarCommits"] = true 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") ctx.HTML(200, "repo/diff")
} }

View file

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

View file

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

View file

@ -286,6 +286,85 @@ func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
func Issues(ctx *middleware.Context) { func Issues(ctx *middleware.Context) {
ctx.Data["Title"] = "Your Issues" 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") ctx.HTML(200, "issue/user")
} }

View file

@ -2,69 +2,198 @@
<div id="body" class="container"> <div id="body" class="container">
<form action="/install" method="post" class="form-horizontal card" id="install-card"> <form action="/install" method="post" class="form-horizontal card" id="install-card">
{{.CsrfTokenHtml}} {{.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> <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> <p class="help-block text-center">Gogs requires MySQL or PostgreSQL based on your choice</p>
<div class="form-group {{if .Err_User}}has-error has-feedback{{end}}"> <div class="form-group">
<label class="col-md-3 control-label"><strong>MySQL </strong>Host: </label> <label class="col-md-3 control-label">Database Type: </label>
<div class="col-md-8">
<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="server-sql">
<div class="form-group">
<label class="col-md-3 control-label">Host: </label>
<div class="col-md-8"> <div class="col-md-8">
<input name="host" class="form-control" placeholder="Type mysql server ip or domain" value="localhost" required="required"> <input name="host" class="form-control" placeholder="Type mysql server ip or domain" value="localhost" required="required">
</div> </div>
</div> </div>
<div class="form-group {{if .Err_User}}has-error has-feedback{{end}}"> <div class="form-group">
<label class="col-md-3 control-label">Port: </label> <label class="col-md-3 control-label">Port: </label>
<div class="col-md-8"> <div class="col-md-8">
<input name="port" class="form-control" placeholder="Type mysql server port" value="3306" required="required"> <input name="port" class="form-control" placeholder="Type mysql server port" value="3306" required="required">
</div> </div>
</div> </div>
<div class="form-group {{if .Err_User}}has-error has-feedback{{end}}"> <div class="form-group">
<label class="col-md-3 control-label">User: </label> <label class="col-md-3 control-label">User: </label>
<div class="col-md-8"> <div class="col-md-8">
<input name="user" class="form-control" placeholder="Type mysql username" required="required"> <input name="user" class="form-control" placeholder="Type mysql username" required="required">
</div> </div>
</div> </div>
<div class="form-group {{if .Err_Password}}has-error has-feedback{{end}}"> <div class="form-group">
<label class="col-md-3 control-label">Password: </label> <label class="col-md-3 control-label">Password: </label>
<div class="col-md-8"> <div class="col-md-8">
<input name="passwd" type="password" class="form-control" placeholder="Type mysql password" required="required"> <input name="passwd" type="password" class="form-control" placeholder="Type mysql password" required="required">
</div> </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="form-group">
<label class="col-md-3 control-label">Database Name: </label>
<div class="col-md-8"> <div class="col-md-8">
<input name="database" type="text" class="form-control" placeholder="Type mysql database name" value="gogs" required="required"> <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> <p class="help-block">Recommend use INNODB engine with utf8_general_ci charset.</p>
</div> </div>
</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="sqlite-setting hide">
<div class="form-group"> <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="col-md-8 col-md-offset-3"> <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> </div> -->
<hr/> <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> <label class="col-md-3 control-label">Repository Path: </label>
<div class="col-md-8"> <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"> <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> <p class="help-block">The git copy of each repository is saved in this directory.</p>
</div> </div>
</div> </div>
<div class="form-group {{if .Err_Password}}has-error has-feedback{{end}}"> <div class="form-group">
<label class="col-md-3 control-label">System User: </label> <label class="col-md-3 control-label">Run User: </label>
<div class="col-md-8"> <div class="col-md-8">
<input name="system-user" type="text" class="form-control" placeholder="Type mysql password" value="root" required="required"> <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">
<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>
</div> </div>
<hr/> <hr/>
<div class="form-group text-center"> <p class="text-center help-block">Notification Settings</p>
<a class="btn btn-danger btn-lg">Install GoGits</a>
<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>
<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> </form>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

View file

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

View file

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

View file

@ -4,12 +4,16 @@
{{template "repo/toolbar" .}} {{template "repo/toolbar" .}}
<div id="body" class="container"> <div id="body" class="container">
<div id="issue"> <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="issue-head clearfix">
<div class="number pull-right">#{{.Issue.Index}}</div> <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> <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> <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"> <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> <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 <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 <span class="time">{{TimeSince .Issue.Created}}</span> · {{.Issue.NumComments}} comments
@ -18,18 +22,24 @@
<div class="issue-main"> <div class="issue-main">
<div class="panel panel-default issue-content"> <div class="panel panel-default issue-content">
<div class="panel-body markdown"> <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>
</div> </div>
{{range .Comments}} {{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> <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="issue-content panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<a href="/user/{{.Poster.Name}}" class="user">{{.Poster.Name}}</a> commented <span class="time">{{TimeSince .Created}}</span> <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>
<div class="panel-body markdown"> <div class="panel-body markdown">
<p>{{.Content}}</p> {{str2html .Content}}
</div> </div>
</div> </div>
</div> </div>
@ -52,7 +62,7 @@
<div class="tab-pane" id="issue-textarea"> <div class="tab-pane" id="issue-textarea">
<div class="form-group"> <div class="form-group">
<input type="hidden" value="{{.Issue.Index}}" name="issueIndex"/> <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> </div>
<div class="tab-pane" id="issue-preview">preview</div> <div class="tab-pane" id="issue-preview">preview</div>
@ -61,7 +71,9 @@
<div class="text-right"> <div class="text-right">
<div class="form-group"> <div class="form-group">
<input type="hidden" value="id" name="repo-id"/> <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> </div>
</div> </div>

View file

@ -27,7 +27,7 @@
{{range $r}} {{range $r}}
<tr> <tr>
<td class="author"><img class="avatar" src="{{AvatarLink .Committer.Email}}" alt=""/><a href="/user/{{.Committer.Name}}">{{.Committer.Name}}</a></td> <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="message">{{.Message}} </td>
<td class="date">{{TimeSince .Committer.When}}</td> <td class="date">{{TimeSince .Committer.When}}</td>
</tr> </tr>

View file

@ -6,7 +6,7 @@
<div id="source"> <div id="source">
<div class="panel panel-info diff-box diff-head-box"> <div class="panel panel-info diff-box diff-head-box">
<div class="panel-heading"> <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> <h4>{{.Commit.Message}}</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
@ -57,10 +57,16 @@
</span> </span>
<span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span> <span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span>
</div> </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> <span class="file">{{.Name}}</span>
</div> </div>
{{$isImage := (call $.IsImageFile .Name)}}
<div class="panel-body file-body file-code code-view code-diff"> <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> <table>
<tbody> <tbody>
{{range .Sections}} {{range .Sections}}
@ -201,6 +207,7 @@
</tr> --> </tr> -->
</tbody> </tbody>
</table> </table>
{{end}}
</div> </div>
</div> </div>
{{end}} {{end}}

View file

@ -11,7 +11,7 @@
{{ $n := len .Treenames}} {{ $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}} {{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"> <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> <b class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{{range .Branches}} {{range .Branches}}

View file

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

View file

@ -5,7 +5,7 @@
<ul class="nav navbar-nav"> <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="/{{.RepositoryLink}}">Source</a></li>
{{if not .IsBareRepo}} {{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 .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 .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 .IsRepoToolbarIssues}}active{{end}}"><a href="/{{.RepositoryLink}}/issues">Issues <!--<span class="badge">42</span>--></a></li>

8
web.go
View file

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