![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true) | ![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true) | ||||
##### Current version: 0.8.56 | |||||
##### Current version: 0.8.57 | |||||
| Web | UI | Preview | | | Web | UI | Preview | | ||||
|:-------------:|:-------:|:-------:| | |:-------------:|:-------:|:-------:| |
// Check dependency version. | // Check dependency version. | ||||
checkers := []VerChecker{ | checkers := []VerChecker{ | ||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.4.4.1029"}, | |||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.5.2.0304"}, | |||||
{"github.com/go-macaron/binding", binding.Version, "0.2.1"}, | {"github.com/go-macaron/binding", binding.Version, "0.2.1"}, | ||||
{"github.com/go-macaron/cache", cache.Version, "0.1.2"}, | {"github.com/go-macaron/cache", cache.Version, "0.1.2"}, | ||||
{"github.com/go-macaron/csrf", csrf.Version, "0.0.3"}, | {"github.com/go-macaron/csrf", csrf.Version, "0.0.3"}, | ||||
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"}, | {"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"}, | ||||
{"gopkg.in/ini.v1", ini.Version, "1.8.4"}, | {"gopkg.in/ini.v1", ini.Version, "1.8.4"}, | ||||
{"gopkg.in/macaron.v1", macaron.Version, "0.8.0"}, | {"gopkg.in/macaron.v1", macaron.Version, "0.8.0"}, | ||||
{"github.com/gogits/git-module", git.Version, "0.2.8"}, | |||||
{"github.com/gogits/git-module", git.Version, "0.2.9"}, | |||||
{"github.com/gogits/go-gogs-client", gogs.Version, "0.7.3"}, | {"github.com/gogits/go-gogs-client", gogs.Version, "0.7.3"}, | ||||
} | } | ||||
for _, c := range checkers { | for _, c := range checkers { |
"github.com/gogits/gogs/modules/setting" | "github.com/gogits/gogs/modules/setting" | ||||
) | ) | ||||
const APP_VER = "0.8.56.0304" | |||||
const APP_VER = "0.8.57.0304" | |||||
func init() { | func init() { | ||||
runtime.GOMAXPROCS(runtime.NumCPU()) | runtime.GOMAXPROCS(runtime.NumCPU()) |
// FIXME: could fail after user force push head repo, should we always force push here? | // FIXME: could fail after user force push head repo, should we always force push here? | ||||
// FIXME: Only push branches that are actually updates? | // FIXME: Only push branches that are actually updates? | ||||
func (pr *PullRequest) PushToBaseRepo() (err error) { | func (pr *PullRequest) PushToBaseRepo() (err error) { | ||||
log.Trace("PushToBaseRepo[%[1]d]: pushing commits to base repo 'refs/pull/%[1]d/head'", pr.ID) | |||||
log.Trace("PushToBaseRepo[%d]: pushing commits to base repo 'refs/pull/%d/head'", pr.BaseRepoID, pr.Index) | |||||
headRepoPath := pr.HeadRepo.RepoPath() | headRepoPath := pr.HeadRepo.RepoPath() | ||||
headGitRepo, err := git.OpenRepository(headRepoPath) | headGitRepo, err := git.OpenRepository(headRepoPath) |
func (repo *Repository) AfterSet(colName string, _ xorm.Cell) { | func (repo *Repository) AfterSet(colName string, _ xorm.Cell) { | ||||
switch colName { | switch colName { | ||||
case "default_branch": | |||||
// FIXME: use models migration to solve all at once. | |||||
if len(repo.DefaultBranch) == 0 { | |||||
repo.DefaultBranch = "master" | |||||
} | |||||
case "num_closed_issues": | case "num_closed_issues": | ||||
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues | repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues | ||||
case "num_closed_pulls": | case "num_closed_pulls": |
// IsAdminOfRepo returns true if user has admin or higher access of repository. | // IsAdminOfRepo returns true if user has admin or higher access of repository. | ||||
func (u *User) IsAdminOfRepo(repo *Repository) bool { | func (u *User) IsAdminOfRepo(repo *Repository) bool { | ||||
if err := repo.GetOwner(); err != nil { | |||||
log.Error(3, "GetOwner: %v", err) | |||||
return false | |||||
} | |||||
if repo.Owner.IsOrganization() { | |||||
if repo.MustOwner().IsOrganization() { | |||||
has, err := HasAccess(u, repo, ACCESS_MODE_ADMIN) | has, err := HasAccess(u, repo, ACCESS_MODE_ADMIN) | ||||
if err != nil { | if err != nil { | ||||
log.Error(3, "HasAccess: %v", err) | log.Error(3, "HasAccess: %v", err) | ||||
return false | |||||
} | } | ||||
return has | return has | ||||
} | } | ||||
return repo.IsOwnedBy(u.Id) | return repo.IsOwnedBy(u.Id) | ||||
} | } | ||||
// CanWriteTo returns true if user has write access to given repository. | |||||
func (u *User) CanWriteTo(repo *Repository) bool { | |||||
has, err := HasAccess(u, repo, ACCESS_MODE_WRITE) | |||||
if err != nil { | |||||
log.Error(3, "HasAccess: %v", err) | |||||
} | |||||
return has | |||||
} | |||||
// IsOrganization returns true if user is actually a organization. | // IsOrganization returns true if user is actually a organization. | ||||
func (u *User) IsOrganization() bool { | func (u *User) IsOrganization() bool { | ||||
return u.Type == ORGANIZATION | return u.Type == ORGANIZATION |
// Copyright 2014 The Gogs Authors. All rights reserved. | |||||
// Use of this source code is governed by a MIT-style | |||||
// license that can be found in the LICENSE file. | |||||
package log | |||||
import ( | |||||
"encoding/json" | |||||
"github.com/go-xorm/xorm" | |||||
) | |||||
type Log struct { | |||||
Id int64 | |||||
Level int | |||||
Msg string `xorm:"TEXT"` | |||||
} | |||||
// DatabaseWriter implements LoggerInterface and is used to log into database. | |||||
type DatabaseWriter struct { | |||||
Driver string `json:"driver"` | |||||
Conn string `json:"conn"` | |||||
Level int `json:"level"` | |||||
x *xorm.Engine | |||||
} | |||||
func NewDatabase() LoggerInterface { | |||||
return &DatabaseWriter{Level: TRACE} | |||||
} | |||||
// init database writer with json config. | |||||
// config like: | |||||
// { | |||||
// "driver": "mysql" | |||||
// "conn":"root:root@tcp(127.0.0.1:3306)/gogs?charset=utf8", | |||||
// "level": 0 | |||||
// } | |||||
// connection string is based on xorm. | |||||
func (d *DatabaseWriter) Init(jsonconfig string) (err error) { | |||||
if err = json.Unmarshal([]byte(jsonconfig), d); err != nil { | |||||
return err | |||||
} | |||||
d.x, err = xorm.NewEngine(d.Driver, d.Conn) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
return d.x.Sync(new(Log)) | |||||
} | |||||
// write message in database writer. | |||||
func (d *DatabaseWriter) WriteMsg(msg string, skip, level int) error { | |||||
if level < d.Level { | |||||
return nil | |||||
} | |||||
_, err := d.x.Insert(&Log{Level: level, Msg: msg}) | |||||
return err | |||||
} | |||||
func (_ *DatabaseWriter) Flush() { | |||||
} | |||||
func (_ *DatabaseWriter) Destroy() { | |||||
} | |||||
func init() { | |||||
Register("database", NewDatabase) | |||||
} |
ctx.Handle(500, "BaseRepo.GetOwner", err) | ctx.Handle(500, "BaseRepo.GetOwner", err) | ||||
return | return | ||||
} | } | ||||
bsaeRepo := repo.BaseRepo | |||||
baseGitRepo, err := git.OpenRepository(models.RepoPath(bsaeRepo.Owner.Name, bsaeRepo.Name)) | |||||
if err != nil { | |||||
ctx.Handle(500, "OpenRepository", err) | |||||
return | |||||
} | |||||
if len(bsaeRepo.DefaultBranch) > 0 && baseGitRepo.IsBranchExist(bsaeRepo.DefaultBranch) { | |||||
ctx.Data["BaseDefaultBranch"] = bsaeRepo.DefaultBranch | |||||
} else { | |||||
baseBranches, err := baseGitRepo.GetBranches() | |||||
if err != nil { | |||||
ctx.Handle(500, "GetBranches", err) | |||||
return | |||||
} | |||||
if len(baseBranches) > 0 { | |||||
ctx.Data["BaseDefaultBranch"] = baseBranches[0] | |||||
} | |||||
} | |||||
} | } | ||||
func RepoAssignment(args ...bool) macaron.Handler { | func RepoAssignment(args ...bool) macaron.Handler { | ||||
ctx.Data["Tags"] = tags | ctx.Data["Tags"] = tags | ||||
ctx.Repo.Repository.NumTags = len(tags) | ctx.Repo.Repository.NumTags = len(tags) | ||||
ctx.Data["Title"] = owner.Name + "/" + repo.Name | |||||
ctx.Data["Repository"] = repo | |||||
ctx.Data["Owner"] = ctx.Repo.Repository.Owner | |||||
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner() | |||||
ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin() | |||||
ctx.Data["IsRepositoryPusher"] = ctx.Repo.IsPusher() | |||||
if repo.IsFork { | if repo.IsFork { | ||||
RetrieveBaseRepo(ctx, repo) | RetrieveBaseRepo(ctx, repo) | ||||
if ctx.Written() { | if ctx.Written() { | ||||
} | } | ||||
} | } | ||||
ctx.Data["Title"] = owner.Name + "/" + repo.Name | |||||
ctx.Data["Repository"] = repo | |||||
ctx.Data["Owner"] = ctx.Repo.Repository.Owner | |||||
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner() | |||||
ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin() | |||||
ctx.Data["IsRepositoryPusher"] = ctx.Repo.IsPusher() | |||||
ctx.Data["CanPullRequest"] = ctx.Repo.IsAdmin() && repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() | |||||
// People who have push access and propose a new pull request. | |||||
if ctx.Repo.IsPusher() { | |||||
// Pull request is allowed if this is a fork repository | |||||
// and base repository accepts pull requests. | |||||
if repo.BaseRepo != nil { | |||||
if repo.BaseRepo.AllowsPulls() { | |||||
ctx.Data["CanPullRequest"] = true | |||||
ctx.Data["BaseRepo"] = repo.BaseRepo | |||||
} | |||||
} else { | |||||
// Or, this is repository accepts pull requests between branches. | |||||
if repo.AllowsPulls() { | |||||
ctx.Data["CanPullRequest"] = true | |||||
ctx.Data["BaseRepo"] = repo | |||||
ctx.Data["IsBetweenBranches"] = true | |||||
} | |||||
} | |||||
} | |||||
ctx.Data["DisableSSH"] = setting.SSH.Disabled | ctx.Data["DisableSSH"] = setting.SSH.Disabled | ||||
ctx.Data["CloneLink"] = repo.CloneLink() | ctx.Data["CloneLink"] = repo.CloneLink() |
} | } | ||||
func ParseCompareInfo(ctx *middleware.Context) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) { | func ParseCompareInfo(ctx *middleware.Context) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) { | ||||
// Get compare branch information. | |||||
// Get compared branches information | |||||
// format: <base branch>...[<head repo>:]<head branch> | |||||
// base<-head: master...head:feature | |||||
// same repo: master...feature | |||||
infos := strings.Split(ctx.Params("*"), "...") | infos := strings.Split(ctx.Params("*"), "...") | ||||
if len(infos) != 2 { | if len(infos) != 2 { | ||||
log.Trace("Not enough compared branches information: %s", infos) | |||||
ctx.Handle(404, "CompareAndPullRequest", nil) | ctx.Handle(404, "CompareAndPullRequest", nil) | ||||
return nil, nil, nil, nil, "", "" | return nil, nil, nil, nil, "", "" | ||||
} | } | ||||
baseBranch := infos[0] | baseBranch := infos[0] | ||||
ctx.Data["BaseBranch"] = baseBranch | ctx.Data["BaseBranch"] = baseBranch | ||||
var ( | |||||
headUser *models.User | |||||
headBranch string | |||||
isSameRepo bool | |||||
err error | |||||
) | |||||
// If there is no head repository, it means pull request between same repository. | |||||
headInfos := strings.Split(infos[1], ":") | headInfos := strings.Split(infos[1], ":") | ||||
if len(headInfos) != 2 { | |||||
ctx.Handle(404, "CompareAndPullRequest", nil) | |||||
return nil, nil, nil, nil, "", "" | |||||
} | |||||
headUsername := headInfos[0] | |||||
headBranch := headInfos[1] | |||||
ctx.Data["HeadBranch"] = headBranch | |||||
if len(headInfos) == 1 { | |||||
isSameRepo = true | |||||
headUser = ctx.Repo.Owner | |||||
headBranch = headInfos[0] | |||||
headUser, err := models.GetUserByName(headUsername) | |||||
if err != nil { | |||||
if models.IsErrUserNotExist(err) { | |||||
ctx.Handle(404, "GetUserByName", nil) | |||||
} else { | |||||
ctx.Handle(500, "GetUserByName", err) | |||||
} else if len(headInfos) == 2 { | |||||
headUser, err = models.GetUserByName(headInfos[0]) | |||||
if err != nil { | |||||
if models.IsErrUserNotExist(err) { | |||||
ctx.Handle(404, "GetUserByName", nil) | |||||
} else { | |||||
ctx.Handle(500, "GetUserByName", err) | |||||
} | |||||
return nil, nil, nil, nil, "", "" | |||||
} | } | ||||
headBranch = headInfos[1] | |||||
} else { | |||||
ctx.Handle(404, "CompareAndPullRequest", nil) | |||||
return nil, nil, nil, nil, "", "" | return nil, nil, nil, nil, "", "" | ||||
} | } | ||||
ctx.Data["HeadUser"] = headUser | ctx.Data["HeadUser"] = headUser | ||||
ctx.Data["HeadBranch"] = headBranch | |||||
ctx.Data["IsBetweenBranches"] = isSameRepo | |||||
repo := ctx.Repo.Repository | repo := ctx.Repo.Repository | ||||
return nil, nil, nil, nil, "", "" | return nil, nil, nil, nil, "", "" | ||||
} | } | ||||
// Check if current user has fork of repository. | |||||
// Check if current user has fork of repository or in the same repository. | |||||
headRepo, has := models.HasForkedRepo(headUser.Id, repo.ID) | headRepo, has := models.HasForkedRepo(headUser.Id, repo.ID) | ||||
if !has || (!ctx.User.IsAdminOfRepo(headRepo) && !ctx.User.IsAdmin) { | |||||
if (!has && !isSameRepo) || (!ctx.User.CanWriteTo(headRepo) && !ctx.User.IsAdmin) { | |||||
ctx.Handle(404, "HasForkedRepo", nil) | ctx.Handle(404, "HasForkedRepo", nil) | ||||
return nil, nil, nil, nil, "", "" | return nil, nil, nil, nil, "", "" | ||||
} | } | ||||
headGitRepo, err := git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name)) | |||||
if err != nil { | |||||
ctx.Handle(500, "OpenRepository", err) | |||||
return nil, nil, nil, nil, "", "" | |||||
var headGitRepo *git.Repository | |||||
if isSameRepo { | |||||
headRepo = ctx.Repo.Repository | |||||
headGitRepo = ctx.Repo.GitRepo | |||||
} else { | |||||
headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name)) | |||||
if err != nil { | |||||
ctx.Handle(500, "OpenRepository", err) | |||||
return nil, nil, nil, nil, "", "" | |||||
} | |||||
} | } | ||||
// Check if head branch is valid. | // Check if head branch is valid. | ||||
if err := models.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, patch); err != nil { | if err := models.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, patch); err != nil { | ||||
ctx.Handle(500, "NewPullRequest", err) | ctx.Handle(500, "NewPullRequest", err) | ||||
return | return | ||||
} | |||||
if err := pullRequest.PushToBaseRepo(); err != nil { | |||||
} else if err := pullRequest.PushToBaseRepo(); err != nil { | |||||
ctx.Handle(500, "PushToBaseRepo", err) | ctx.Handle(500, "PushToBaseRepo", err) | ||||
return | return | ||||
} | } |
0.8.56.0304 | |||||
0.8.57.0304 |
<div class="ui secondary menu"> | <div class="ui secondary menu"> | ||||
{{if .CanPullRequest}} | {{if .CanPullRequest}} | ||||
<div class="fitted item"> | <div class="fitted item"> | ||||
{{ $baseRepo := .Repository.BaseRepo}} | |||||
<a href="{{AppSubUrl}}/{{$baseRepo.Owner.Name}}/{{$baseRepo.Name}}/compare/{{$.BaseDefaultBranch}}...{{$.Owner.Name}}:{{$.BranchName}}"> | |||||
<a href="{{.BaseRepo.RepoLink}}/compare/{{.BaseRepo.DefaultBranch}}...{{if not .IsBetweenBranches}}{{$.Owner.Name}}:{{end}}{{$.BranchName}}"> | |||||
<button class="ui green small button"><i class="octicon octicon-git-compare"></i></button> | <button class="ui green small button"><i class="octicon octicon-git-compare"></i></button> | ||||
</a> | </a> | ||||
</div> | </div> |
{{if .PageIsIssueList}} | {{if .PageIsIssueList}} | ||||
<a class="ui green button" href="{{.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a> | <a class="ui green button" href="{{.RepoLink}}/issues/new">{{.i18n.Tr "repo.issues.new"}}</a> | ||||
{{else}} | {{else}} | ||||
<a class="ui green button {{if not .HasForkedRepo}}disabled{{end}}" href="{{.RepoLink}}/compare/{{.BranchName}}...{{.SignedUserName}}:{{.BranchName}}">{{.i18n.Tr "repo.pulls.new"}}</a> | |||||
<a class="ui green button {{if not (or .CanPullRequest .HasForkedRepo)}}disabled{{end}}" href="{{.RepoLink}}/compare/{{.Repository.DefaultBranch}}...{{if not (eq .Owner.Name .SignedUserName)}}{{.SignedUserName}}:{{end}}{{.BranchName}}">{{.i18n.Tr "repo.pulls.new"}}</a> | |||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
</div> | </div> |
</div> | </div> | ||||
<div class="scrolling menu"> | <div class="scrolling menu"> | ||||
{{range .Branches}} | {{range .Branches}} | ||||
<div class="item {{if eq $.BaseBranch .}}selected{{end}}" data-url="{{$.RepoLink}}/compare/{{.}}...{{$.HeadUser.Name}}:{{$.HeadBranch}}">{{.}}</div> | |||||
<div class="item {{if eq $.BaseBranch .}}selected{{end}}" data-url="{{$.RepoLink}}/compare/{{.}}...{{if not $.IsBetweenBranches}}{{$.HeadUser.Name}}:{{end}}{{$.HeadBranch}}">{{.}}</div> | |||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="scrolling menu"> | <div class="scrolling menu"> | ||||
{{range .HeadBranches}} | {{range .HeadBranches}} | ||||
<div class="{{if eq $.HeadBranch .}}selected{{end}} item" data-url="{{$.RepoLink}}/compare/{{$.BaseBranch}}...{{$.HeadUser.Name}}:{{.}}">{{.}}</div> | |||||
<div class="{{if eq $.HeadBranch .}}selected{{end}} item" data-url="{{$.RepoLink}}/compare/{{$.BaseBranch}}...{{if not $.IsBetweenBranches}}{{$.HeadUser.Name}}:{{end}}{{.}}">{{.}}</div> | |||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
</div> | </div> |