Compare commits

...

14 Commits

Author SHA1 Message Date
Lauris BH
d81cf34e37 Changelog for release v1.2.2 (#2786) 2017-10-26 18:34:30 +03:00
Lauris BH
9902317679 Add checks for commits with missing author and time (#2771) (#2785)
* Add checks for commits with missing author and time

* Fix validate commits with emails if it has no Author
2017-10-26 22:24:11 +08:00
Paul Tötterman
33e164168f Fix sending mail with a non-latin display name. #2102 (#2559) (#2783)
* Fix sending mail with a non-latin display name. #2102

Signed-off-by: Rémi Saurel <contact@remi-saurel.com>

* Take into account the possibility that setting.MailService.From is in `name <email@address>` format. #2102

Signed-off-by: Rémi Saurel <contact@remi-saurel.com>
2017-10-26 10:44:20 +03:00
Michael Kuhn
f40ba68d57 Sync MaxGitDiffLineCharacters with conf/app.ini (#2779) (#2780)
MAX_GIT_DIFF_LINE_CHARACTERS was updated in #1845 but the corresponding
default value of MaxGitDiffLineCharacters was not changed. This can lead
to inconsistencies.
2017-10-25 12:45:29 +03:00
Lunny Xiao
cb0c8b8ae4 fix imports 2017-10-25 13:49:06 +08:00
Lunny Xiao
eba5945d2f some fixes 2017-10-25 13:28:47 +08:00
Lauris BH
4c67925531 Update vendor git (#2765) (#2772) 2017-10-24 02:04:22 +03:00
Lauris BH
3c60121ca7 Fix emojify image URL (#2769) (#2773) 2017-10-24 00:26:51 +02:00
Lauris BH
46bb1cf026 Changelog for v1.2.1 (#2716) 2017-10-16 16:27:22 +02:00
Lauris BH
13013e90f3 Fix PR, milestone and label functionality if issue unit is disabled (#2710) (#2714)
* Fix PR, milestone and label functionality if issue unit is disabled or not assigned to user

* Fix multi-actions in PR page

* Change error message

* Fix comment update and delete functionality in PR
2017-10-16 16:59:01 +03:00
Lunny Xiao
785ba171f4 Fix plain readme didn't render correctly on repo home page (#2705) (#2712)
* fix plain readme didn't render correctly on repo home page

* fix missing render

* remove unused template variables
2017-10-16 03:30:11 -05:00
Lauris BH
fb80265b52 Fix so that user can still fork his own repository to owned organizations (#2699) (#2707)
* Fix so that user can still fork his own repository to his organizations

* Fix to only use owned organizations

* Add integration test for forking own repository to owned organization
2017-10-15 19:40:33 +03:00
David Schneiderbauer
6fae585d28 fix .netrc authentication (#2700) (#2708)
* provide both possible authentication solutions

Signed-off-by: David Schneiderbauer <dschneiderbauer@gmail.com>
2017-10-15 18:50:28 +03:00
Lunny Xiao
670562a9c5 Fix slice out of bounds error in mailer (#2479) (#2696) 2017-10-13 22:59:03 +03:00
34 changed files with 327 additions and 205 deletions

View File

@@ -1,4 +1,19 @@
# Changelog # Changelog
## [1.2.2](https://github.com/go-gitea/gitea/releases/tag/v1.2.2) - 2017-10-26
* BUGFIXES
* Add checks for commits with missing author and time (#2771) (#2785)
* Fix sending mail with a non-latin display name (#2559) (#2783)
* Sync MaxGitDiffLineCharacters with conf/app.ini (#2779) (#2780)
* Update vendor git (#2765) (#2772)
* Fix emojify image URL (#2769) (#2773)
## [1.2.1](https://github.com/go-gitea/gitea/releases/tag/v1.2.1) - 2017-10-16
* BUGFIXES
* Fix PR, milestone and label functionality if issue unit is disabled (#2710) (#2714)
* Fix plain readme didn't render correctly on repo home page (#2705) (#2712)
* Fix so that user can still fork his own repository to his organizations (#2699) (#2707)
* Fix .netrc authentication (#2700) (#2708)
* Fix slice out of bounds error in mailer (#2479) (#2696)
## [1.2.0](https://github.com/go-gitea/gitea/releases/tag/v1.2.0) - 2017-10-10 ## [1.2.0](https://github.com/go-gitea/gitea/releases/tag/v1.2.0) - 2017-10-10
* SECURITY * SECURITY

View File

@@ -46,7 +46,7 @@ func testPullCreate(t *testing.T, session *TestSession, user, repo, branch strin
func TestPullCreate(t *testing.T) { func TestPullCreate(t *testing.T) {
prepareTestEnv(t) prepareTestEnv(t)
session := loginUser(t, "user1") session := loginUser(t, "user1")
testRepoFork(t, session) testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md") testEditFile(t, session, "user1", "repo1", "master", "README.md")
testPullCreate(t, session, "user1", "repo1", "master") testPullCreate(t, session, "user1", "repo1", "master")
} }

View File

@@ -48,7 +48,7 @@ func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum str
func TestPullMerge(t *testing.T) { func TestPullMerge(t *testing.T) {
prepareTestEnv(t) prepareTestEnv(t)
session := loginUser(t, "user1") session := loginUser(t, "user1")
testRepoFork(t, session) testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md") testEditFile(t, session, "user1", "repo1", "master", "README.md")
resp := testPullCreate(t, session, "user1", "repo1", "master") resp := testPullCreate(t, session, "user1", "repo1", "master")
@@ -61,7 +61,7 @@ func TestPullMerge(t *testing.T) {
func TestPullCleanUpAfterMerge(t *testing.T) { func TestPullCleanUpAfterMerge(t *testing.T) {
prepareTestEnv(t) prepareTestEnv(t)
session := loginUser(t, "user1") session := loginUser(t, "user1")
testRepoFork(t, session) testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md")
resp := testPullCreate(t, session, "user1", "repo1", "feature/test") resp := testPullCreate(t, session, "user1", "repo1", "feature/test")

View File

@@ -5,19 +5,24 @@
package integrations package integrations
import ( import (
"fmt"
"net/http" "net/http"
"testing" "testing"
"code.gitea.io/gitea/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func testRepoFork(t *testing.T, session *TestSession) *TestResponse { func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkOwnerName, forkRepoName string) *TestResponse {
forkOwner := models.AssertExistsAndLoadBean(t, &models.User{Name: forkOwnerName}).(*models.User)
// Step0: check the existence of the to-fork repo // Step0: check the existence of the to-fork repo
req := NewRequest(t, "GET", "/user1/repo1") req := NewRequestf(t, "GET", "/%s/%s", forkOwnerName, forkRepoName)
resp := session.MakeRequest(t, req, http.StatusNotFound) resp := session.MakeRequest(t, req, http.StatusNotFound)
// Step1: go to the main page of repo // Step1: go to the main page of repo
req = NewRequest(t, "GET", "/user2/repo1") req = NewRequestf(t, "GET", "/%s/%s", ownerName, repoName)
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
// Step2: click the fork button // Step2: click the fork button
@@ -31,15 +36,17 @@ func testRepoFork(t *testing.T, session *TestSession) *TestResponse {
htmlDoc = NewHTMLParser(t, resp.Body) htmlDoc = NewHTMLParser(t, resp.Body)
link, exists = htmlDoc.doc.Find("form.ui.form[action^=\"/repo/fork/\"]").Attr("action") link, exists = htmlDoc.doc.Find("form.ui.form[action^=\"/repo/fork/\"]").Attr("action")
assert.True(t, exists, "The template has changed") assert.True(t, exists, "The template has changed")
_, exists = htmlDoc.doc.Find(fmt.Sprintf(".owner.dropdown .item[data-value=\"%d\"]", forkOwner.ID)).Attr("data-value")
assert.True(t, exists, fmt.Sprintf("Fork owner '%s' is not present in select box", forkOwnerName))
req = NewRequestWithValues(t, "POST", link, map[string]string{ req = NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": htmlDoc.GetCSRF(), "_csrf": htmlDoc.GetCSRF(),
"uid": "1", "uid": fmt.Sprintf("%d", forkOwner.ID),
"repo_name": "repo1", "repo_name": forkRepoName,
}) })
resp = session.MakeRequest(t, req, http.StatusFound) resp = session.MakeRequest(t, req, http.StatusFound)
// Step4: check the existence of the forked repo // Step4: check the existence of the forked repo
req = NewRequest(t, "GET", "/user1/repo1") req = NewRequestf(t, "GET", "/%s/%s", forkOwnerName, forkRepoName)
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
return resp return resp
@@ -48,5 +55,19 @@ func testRepoFork(t *testing.T, session *TestSession) *TestResponse {
func TestRepoFork(t *testing.T) { func TestRepoFork(t *testing.T) {
prepareTestEnv(t) prepareTestEnv(t)
session := loginUser(t, "user1") session := loginUser(t, "user1")
testRepoFork(t, session) testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
}
func TestRepoForkToOrg(t *testing.T) {
prepareTestEnv(t)
session := loginUser(t, "user2")
testRepoFork(t, session, "user2", "repo1", "user3", "repo1")
// Check that no more forking is allowed as user2 owns repository
// and user3 organization that owner user2 is also now has forked this repository
req := NewRequest(t, "GET", "/user2/repo1")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
_, exists := htmlDoc.doc.Find("a.ui.button[href^=\"/repo/fork/\"]").Attr("href")
assert.False(t, exists, "Forking should not be allowed anymore")
} }

View File

@@ -166,7 +166,7 @@ func composeIssueCommentMessage(issue *Issue, doer *User, comment *Comment, tplN
log.Error(3, "Template: %v", err) log.Error(3, "Template: %v", err)
} }
msg := mailer.NewMessageFrom(tos, fmt.Sprintf(`"%s" <%s>`, doer.DisplayName(), setting.MailService.FromEmail), subject, content.String()) msg := mailer.NewMessageFrom(tos, doer.DisplayName(), setting.MailService.FromEmail, subject, content.String())
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
return msg return msg
} }

View File

@@ -409,6 +409,21 @@ func (repo *Repository) UnitEnabled(tp UnitType) bool {
return false return false
} }
// AnyUnitEnabled if this repository has the any of the given units enabled
func (repo *Repository) AnyUnitEnabled(tps ...UnitType) bool {
if err := repo.getUnits(x); err != nil {
log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error())
}
for _, unit := range repo.Units {
for _, tp := range tps {
if unit.Type == tp {
return true
}
}
}
return false
}
var ( var (
// ErrUnitNotExist organization does not exist // ErrUnitNotExist organization does not exist
ErrUnitNotExist = errors.New("Unit does not exist") ErrUnitNotExist = errors.New("Unit does not exist")
@@ -651,6 +666,25 @@ func (repo *Repository) CanBeForked() bool {
return !repo.IsBare && repo.UnitEnabled(UnitTypeCode) return !repo.IsBare && repo.UnitEnabled(UnitTypeCode)
} }
// CanUserFork returns true if specified user can fork repository.
func (repo *Repository) CanUserFork(user *User) (bool, error) {
if user == nil {
return false, nil
}
if repo.OwnerID != user.ID && !user.HasForkedRepo(repo.ID) {
return true, nil
}
if err := user.GetOwnedOrganizations(); err != nil {
return false, err
}
for _, org := range user.OwnedOrgs {
if repo.OwnerID != org.ID && !org.HasForkedRepo(repo.ID) {
return true, nil
}
}
return false, nil
}
// CanEnablePulls returns true if repository meets the requirements of accepting pulls. // CanEnablePulls returns true if repository meets the requirements of accepting pulls.
func (repo *Repository) CanEnablePulls() bool { func (repo *Repository) CanEnablePulls() bool {
return !repo.IsMirror && !repo.IsBare return !repo.IsMirror && !repo.IsBare

View File

@@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"os/exec" "os/exec"
"strings" "strings"
"time"
"code.gitea.io/git" "code.gitea.io/git"
@@ -119,12 +120,25 @@ func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string)
if err != nil { if err != nil {
return fmt.Errorf("Commit: %v", err) return fmt.Errorf("Commit: %v", err)
} }
tagCreatedUnix := commit.Author.When.Unix()
author, err := GetUserByEmail(commit.Author.Email) sig := tag.Tagger
if sig == nil {
sig = commit.Author
}
if sig == nil {
sig = commit.Committer
}
var author *User
var createdAt = time.Unix(1, 0)
if sig != nil {
author, err = GetUserByEmail(sig.Email)
if err != nil && !IsErrUserNotExist(err) { if err != nil && !IsErrUserNotExist(err) {
return fmt.Errorf("GetUserByEmail: %v", err) return fmt.Errorf("GetUserByEmail: %v", err)
} }
createdAt = sig.When
}
commitsCount, err := commit.CommitsCount() commitsCount, err := commit.CommitsCount()
if err != nil { if err != nil {
@@ -144,7 +158,8 @@ func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string)
IsDraft: false, IsDraft: false,
IsPrerelease: false, IsPrerelease: false,
IsTag: true, IsTag: true,
CreatedUnix: tagCreatedUnix, Created: createdAt,
CreatedUnix: createdAt.Unix(),
} }
if author != nil { if author != nil {
rel.PublisherID = author.ID rel.PublisherID = author.ID
@@ -155,7 +170,8 @@ func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string)
} }
} else { } else {
rel.Sha1 = commit.ID.String() rel.Sha1 = commit.ID.String()
rel.CreatedUnix = tagCreatedUnix rel.Created = createdAt
rel.CreatedUnix = createdAt.Unix()
rel.NumCommits = commitsCount rel.NumCommits = commitsCount
rel.IsDraft = false rel.IsDraft = false
if rel.IsTag && author != nil { if rel.IsTag && author != nil {

View File

@@ -1205,6 +1205,9 @@ type UserCommit struct {
// ValidateCommitWithEmail check if author's e-mail of commit is corresponding to a user. // ValidateCommitWithEmail check if author's e-mail of commit is corresponding to a user.
func ValidateCommitWithEmail(c *git.Commit) *User { func ValidateCommitWithEmail(c *git.Commit) *User {
if c.Author == nil {
return nil
}
u, err := GetUserByEmail(c.Author.Email) u, err := GetUserByEmail(c.Author.Email)
if err != nil { if err != nil {
return nil return nil
@@ -1223,12 +1226,16 @@ func ValidateCommitsWithEmails(oldCommits *list.List) *list.List {
for e != nil { for e != nil {
c := e.Value.(*git.Commit) c := e.Value.(*git.Commit)
if c.Author != nil {
if v, ok := emails[c.Author.Email]; !ok { if v, ok := emails[c.Author.Email]; !ok {
u, _ = GetUserByEmail(c.Author.Email) u, _ = GetUserByEmail(c.Author.Email)
emails[c.Author.Email] = u emails[c.Author.Email] = u
} else { } else {
u = v u = v
} }
} else {
u = nil
}
newCommits.PushBack(UserCommit{ newCommits.PushBack(UserCommit{
User: u, User: u,

View File

@@ -364,6 +364,11 @@ func RepoAssignment() macaron.Handler {
ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin() ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
ctx.Data["IsRepositoryWriter"] = ctx.Repo.IsWriter() ctx.Data["IsRepositoryWriter"] = ctx.Repo.IsWriter()
if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
ctx.Handle(500, "CanUserFork", err)
return
}
ctx.Data["DisableSSH"] = setting.SSH.Disabled ctx.Data["DisableSSH"] = setting.SSH.Disabled
ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
@@ -609,6 +614,15 @@ func CheckUnit(unitType models.UnitType) macaron.Handler {
} }
} }
// CheckAnyUnit will check whether any of the unit types are enabled
func CheckAnyUnit(unitTypes ...models.UnitType) macaron.Handler {
return func(ctx *Context) {
if !ctx.Repo.Repository.AnyUnitEnabled(unitTypes...) {
ctx.Handle(404, "CheckAnyUnit", fmt.Errorf("%s: %v", ctx.Tr("units.error.unit_not_allowed"), unitTypes))
}
}
}
// GitHookService checks if repository Git hooks service has been enabled. // GitHookService checks if repository Git hooks service has been enabled.
func GitHookService() macaron.Handler { func GitHookService() macaron.Handler {
return func(ctx *Context) { return func(ctx *Context) {

View File

@@ -16,6 +16,7 @@ import (
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@@ -30,18 +31,18 @@ type Message struct {
} }
// NewMessageFrom creates new mail message object with custom From header. // NewMessageFrom creates new mail message object with custom From header.
func NewMessageFrom(to []string, from, subject, body string) *Message { func NewMessageFrom(to []string, fromDisplayName, fromAddress, subject, body string) *Message {
log.Trace("NewMessageFrom (body):\n%s", body) log.Trace("NewMessageFrom (body):\n%s", body)
msg := gomail.NewMessage() msg := gomail.NewMessage()
msg.SetHeader("From", from) msg.SetAddressHeader("From", fromAddress, fromDisplayName)
msg.SetHeader("To", to...) msg.SetHeader("To", to...)
msg.SetHeader("Subject", subject) msg.SetHeader("Subject", subject)
msg.SetDateHeader("Date", time.Now()) msg.SetDateHeader("Date", time.Now())
plainBody, err := html2text.FromString(body) plainBody, err := html2text.FromString(body)
if err != nil || setting.MailService.SendAsPlainText { if err != nil || setting.MailService.SendAsPlainText {
if strings.Contains(body[:100], "<html>") { if strings.Contains(base.TruncateString(body, 100), "<html>") {
log.Warn("Mail contains HTML but configured to send as plain text.") log.Warn("Mail contains HTML but configured to send as plain text.")
} }
msg.SetBody("text/plain", plainBody) msg.SetBody("text/plain", plainBody)
@@ -57,7 +58,7 @@ func NewMessageFrom(to []string, from, subject, body string) *Message {
// NewMessage creates new mail message object with default From header. // NewMessage creates new mail message object with default From header.
func NewMessage(to []string, subject, body string) *Message { func NewMessage(to []string, subject, body string) *Message {
return NewMessageFrom(to, setting.MailService.From, subject, body) return NewMessageFrom(to, setting.MailService.FromName, setting.MailService.FromEmail, subject, body)
} }
type loginAuth struct { type loginAuth struct {

View File

@@ -432,7 +432,7 @@ var (
}{ }{
DisableDiffHighlight: false, DisableDiffHighlight: false,
MaxGitDiffLines: 1000, MaxGitDiffLines: 1000,
MaxGitDiffLineCharacters: 500, MaxGitDiffLineCharacters: 5000,
MaxGitDiffFiles: 100, MaxGitDiffFiles: 100,
GCArgs: []string{}, GCArgs: []string{},
Timeout: struct { Timeout: struct {
@@ -1281,6 +1281,7 @@ type Mailer struct {
QueueLength int QueueLength int
Name string Name string
From string From string
FromName string
FromEmail string FromEmail string
SendAsPlainText bool SendAsPlainText bool
@@ -1339,6 +1340,7 @@ func newMailService() {
if err != nil { if err != nil {
log.Fatal(4, "Invalid mailer.FROM (%s): %v", MailService.From, err) log.Fatal(4, "Invalid mailer.FROM (%s): %v", MailService.From, err)
} }
MailService.FromName = parsed.Name
MailService.FromEmail = parsed.Address MailService.FromEmail = parsed.Address
log.Info("Mail Service Enabled") log.Info("Mail Service Enabled")

View File

@@ -1456,7 +1456,7 @@ $(document).ready(function () {
// Emojify // Emojify
emojify.setConfig({ emojify.setConfig({
img_dir: suburl + '/plugins/emojify/images', img_dir: suburl + '/vendor/plugins/emojify/images',
ignore_emoticons: true ignore_emoticons: true
}); });
var hasEmoji = document.getElementsByClassName('has-emoji'); var hasEmoji = document.getElementsByClassName('has-emoji');

View File

@@ -7,12 +7,12 @@ package user
import ( import (
"strings" "strings"
"github.com/Unknwon/com"
api "code.gitea.io/sdk/gitea"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markdown"
api "code.gitea.io/sdk/gitea"
"github.com/Unknwon/com"
) )
// Search search users // Search search users
@@ -50,7 +50,7 @@ func Search(ctx *context.APIContext) {
ID: users[i].ID, ID: users[i].ID,
UserName: users[i].Name, UserName: users[i].Name,
AvatarURL: users[i].AvatarLink(), AvatarURL: users[i].AvatarLink(),
FullName: users[i].FullName, FullName: markdown.Sanitize(users[i].FullName),
} }
if ctx.IsSigned { if ctx.IsSigned {
results[i].Email = users[i].Email results[i].Email = users[i].Email

View File

@@ -139,8 +139,16 @@ func HTTP(ctx *context.Context) {
} }
if authUser == nil { if authUser == nil {
authUser, err = models.GetUserByName(authUsername) isUsernameToken := len(authPasswd) == 0 || authPasswd == "x-oauth-basic"
// Assume username is token
authToken := authUsername
if !isUsernameToken {
// Assume password is token
authToken = authPasswd
authUser, err = models.GetUserByName(authUsername)
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.HandleText(http.StatusUnauthorized, "invalid credentials") ctx.HandleText(http.StatusUnauthorized, "invalid credentials")
@@ -149,9 +157,10 @@ func HTTP(ctx *context.Context) {
} }
return return
} }
}
// Assume password is a token. // Assume password is a token.
token, err := models.GetAccessTokenBySHA(authPasswd) token, err := models.GetAccessTokenBySHA(authToken)
if err != nil { if err != nil {
if models.IsErrAccessTokenNotExist(err) || models.IsErrAccessTokenEmpty(err) { if models.IsErrAccessTokenNotExist(err) || models.IsErrAccessTokenEmpty(err) {
ctx.HandleText(http.StatusUnauthorized, "invalid credentials") ctx.HandleText(http.StatusUnauthorized, "invalid credentials")
@@ -161,7 +170,13 @@ func HTTP(ctx *context.Context) {
return return
} }
if authUser.ID != token.UID { if isUsernameToken {
authUser, err = models.GetUserByID(token.UID)
if err != nil {
ctx.Handle(http.StatusInternalServerError, "GetUserByID", err)
return
}
} else if authUser.ID != token.UID {
ctx.HandleText(http.StatusUnauthorized, "invalid credentials") ctx.HandleText(http.StatusUnauthorized, "invalid credentials")
return return
} }
@@ -170,7 +185,6 @@ func HTTP(ctx *context.Context) {
if err = models.UpdateAccessToken(token); err != nil { if err = models.UpdateAccessToken(token); err != nil {
ctx.Handle(http.StatusInternalServerError, "UpdateAccessToken", err) ctx.Handle(http.StatusInternalServerError, "UpdateAccessToken", err)
} }
} else { } else {
_, err = models.GetTwoFactorByUID(authUser.ID) _, err = models.GetTwoFactorByUID(authUser.ID)

View File

@@ -676,11 +676,16 @@ func ViewIssue(ctx *context.Context) {
func getActionIssue(ctx *context.Context) *models.Issue { func getActionIssue(ctx *context.Context) *models.Issue {
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { ctx.NotFoundOrServerError("GetIssueByIndex", models.IsErrIssueNotExist, err)
ctx.Error(404, "GetIssueByIndex") return nil
} else {
ctx.Handle(500, "GetIssueByIndex", err)
} }
if issue.IsPull && !ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) ||
!issue.IsPull && !ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues) {
ctx.Handle(404, "IssueOrPullRequestUnitNotAllowed", nil)
return nil
}
if err = issue.LoadAttributes(); err != nil {
ctx.Handle(500, "LoadAttributes", nil)
return nil return nil
} }
return issue return issue
@@ -705,6 +710,19 @@ func getActionIssues(ctx *context.Context) []*models.Issue {
ctx.Handle(500, "GetIssuesByIDs", err) ctx.Handle(500, "GetIssuesByIDs", err)
return nil return nil
} }
// Check access rights for all issues
issueUnitEnabled := ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues)
prUnitEnabled := ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests)
for _, issue := range issues {
if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled {
ctx.Handle(404, "IssueOrPullRequestUnitNotAllowed", nil)
return nil
}
if err = issue.LoadAttributes(); err != nil {
ctx.Handle(500, "LoadAttributes", nil)
return nil
}
}
return issues return issues
} }
@@ -840,9 +858,8 @@ func UpdateIssueStatus(ctx *context.Context) {
// NewComment create a comment for issue // NewComment create a comment for issue
func NewComment(ctx *context.Context, form auth.CreateCommentForm) { func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue := getActionIssue(ctx)
if err != nil { if ctx.Written() {
ctx.NotFoundOrServerError("GetIssueByIndex", models.IsErrIssueNotExist, err)
return return
} }
@@ -869,7 +886,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
if form.Status == "reopen" && issue.IsPull { if form.Status == "reopen" && issue.IsPull {
pull := issue.PullRequest pull := issue.PullRequest
pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch) pr, err := models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch)
if err != nil { if err != nil {
if !models.IsErrPullRequestNotExist(err) { if !models.IsErrPullRequestNotExist(err) {
ctx.Handle(500, "GetUnmergedPullRequest", err) ctx.Handle(500, "GetUnmergedPullRequest", err)
@@ -891,7 +908,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
if pr != nil { if pr != nil {
ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index)) ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index))
} else { } else {
if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, form.Status == "close"); err != nil { if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, form.Status == "close"); err != nil {
log.Error(4, "ChangeStatus: %v", err) log.Error(4, "ChangeStatus: %v", err)
} else { } else {
log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed) log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
@@ -918,7 +935,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
return return
} }
comment, err = models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments) comment, err := models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments)
if err != nil { if err != nil {
ctx.Handle(500, "CreateIssueComment", err) ctx.Handle(500, "CreateIssueComment", err)
return return
@@ -988,10 +1005,6 @@ func DeleteComment(ctx *context.Context) {
// Milestones render milestones page // Milestones render milestones page
func Milestones(ctx *context.Context) { func Milestones(ctx *context.Context) {
MustEnableIssues(ctx)
if ctx.Written() {
return
}
ctx.Data["Title"] = ctx.Tr("repo.milestones") ctx.Data["Title"] = ctx.Tr("repo.milestones")
ctx.Data["PageIsIssueList"] = true ctx.Data["PageIsIssueList"] = true
ctx.Data["PageIsMilestones"] = true ctx.Data["PageIsMilestones"] = true

View File

@@ -18,10 +18,6 @@ const (
// Labels render issue's labels page // Labels render issue's labels page
func Labels(ctx *context.Context) { func Labels(ctx *context.Context) {
MustEnableIssues(ctx)
if ctx.Written() {
return
}
ctx.Data["Title"] = ctx.Tr("repo.labels") ctx.Data["Title"] = ctx.Tr("repo.labels")
ctx.Data["PageIsIssueList"] = true ctx.Data["PageIsIssueList"] = true
ctx.Data["PageIsLabels"] = true ctx.Data["PageIsLabels"] = true

View File

@@ -21,10 +21,8 @@ func IssueWatch(c *context.Context) {
return return
} }
issueIndex := c.ParamsInt64("index") issue := getActionIssue(c)
issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) if c.Written() {
if err != nil {
c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err)
return return
} }
@@ -33,6 +31,6 @@ func IssueWatch(c *context.Context) {
return return
} }
url := fmt.Sprintf("%s/issues/%d", c.Repo.RepoLink, issueIndex) url := fmt.Sprintf("%s/issues/%d", c.Repo.RepoLink, issue.Index)
c.Redirect(url, http.StatusSeeOther) c.Redirect(url, http.StatusSeeOther)
} }

View File

@@ -61,6 +61,8 @@ func getForkRepository(ctx *context.Context) *models.Repository {
ctx.Data["repo_name"] = forkRepo.Name ctx.Data["repo_name"] = forkRepo.Name
ctx.Data["description"] = forkRepo.Description ctx.Data["description"] = forkRepo.Description
ctx.Data["IsPrivate"] = forkRepo.IsPrivate ctx.Data["IsPrivate"] = forkRepo.IsPrivate
canForkToUser := forkRepo.OwnerID != ctx.User.ID && !ctx.User.HasForkedRepo(forkRepo.ID)
ctx.Data["CanForkToUser"] = canForkToUser
if err = forkRepo.GetOwner(); err != nil { if err = forkRepo.GetOwner(); err != nil {
ctx.Handle(500, "GetOwner", err) ctx.Handle(500, "GetOwner", err)
@@ -69,11 +71,23 @@ func getForkRepository(ctx *context.Context) *models.Repository {
ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name
ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID
if err := ctx.User.GetOrganizations(true); err != nil { if err := ctx.User.GetOwnedOrganizations(); err != nil {
ctx.Handle(500, "GetOrganizations", err) ctx.Handle(500, "GetOwnedOrganizations", err)
return nil return nil
} }
ctx.Data["Orgs"] = ctx.User.Orgs var orgs []*models.User
for _, org := range ctx.User.OwnedOrgs {
if forkRepo.OwnerID != org.ID && !org.HasForkedRepo(forkRepo.ID) {
orgs = append(orgs, org)
}
}
ctx.Data["Orgs"] = orgs
if canForkToUser {
ctx.Data["ContextUser"] = ctx.User
} else if len(orgs) > 0 {
ctx.Data["ContextUser"] = orgs[0]
}
return forkRepo return forkRepo
} }
@@ -87,7 +101,6 @@ func Fork(ctx *context.Context) {
return return
} }
ctx.Data["ContextUser"] = ctx.User
ctx.HTML(200, tplFork) ctx.HTML(200, tplFork)
} }
@@ -95,15 +108,16 @@ func Fork(ctx *context.Context) {
func ForkPost(ctx *context.Context, form auth.CreateRepoForm) { func ForkPost(ctx *context.Context, form auth.CreateRepoForm) {
ctx.Data["Title"] = ctx.Tr("new_fork") ctx.Data["Title"] = ctx.Tr("new_fork")
ctxUser := checkContextUser(ctx, form.UID)
if ctx.Written() {
return
}
forkRepo := getForkRepository(ctx) forkRepo := getForkRepository(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
ctxUser := checkContextUser(ctx, form.UID)
if ctx.Written() {
return
}
ctx.Data["ContextUser"] = ctxUser ctx.Data["ContextUser"] = ctxUser
if ctx.HasError() { if ctx.HasError() {

View File

@@ -93,16 +93,12 @@ func renderDirectory(ctx *context.Context, treeLink string) {
if isTextFile { if isTextFile {
d, _ := ioutil.ReadAll(dataRc) d, _ := ioutil.ReadAll(dataRc)
buf = append(buf, d...) buf = append(buf, d...)
newbuf := markup.Render(readmeFile.Name(), buf, treeLink, ctx.Repo.Repository.ComposeMetas()) ctx.Data["IsRenderedHTML"] = true
if newbuf != nil { if markup.Type(readmeFile.Name()) != "" {
ctx.Data["IsMarkdown"] = true ctx.Data["FileContent"] = string(markup.Render(readmeFile.Name(), buf, treeLink, ctx.Repo.Repository.ComposeMetas()))
} else { } else {
// FIXME This is the only way to show non-markdown files ctx.Data["FileContent"] = string(bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1))
// instead of a broken "View Raw" link
ctx.Data["IsMarkdown"] = true
newbuf = bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1)
} }
ctx.Data["FileContent"] = string(newbuf)
} }
} }
@@ -188,15 +184,14 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
d, _ := ioutil.ReadAll(dataRc) d, _ := ioutil.ReadAll(dataRc)
buf = append(buf, d...) buf = append(buf, d...)
tp := markup.Type(blob.Name()) readmeExist := markup.IsReadmeFile(blob.Name())
isSupportedMarkup := tp != ""
// FIXME: currently set IsMarkdown for compatible
ctx.Data["IsMarkdown"] = isSupportedMarkup
readmeExist := isSupportedMarkup || markup.IsReadmeFile(blob.Name())
ctx.Data["ReadmeExist"] = readmeExist ctx.Data["ReadmeExist"] = readmeExist
if readmeExist && isSupportedMarkup { if markup.Type(blob.Name()) != "" {
ctx.Data["IsRenderedHTML"] = true
ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas())) ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
} else if readmeExist {
ctx.Data["IsRenderedHTML"] = true
ctx.Data["FileContent"] = string(bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1))
} else { } else {
// Building code view blocks with line number on server side. // Building code view blocks with line number on server side.
var fileContent string var fileContent string

View File

@@ -471,12 +471,13 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action) m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action)
m.Group("/:username/:reponame", func() { m.Group("/:username/:reponame", func() {
// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
// So they can apply their own enable/disable logic on routers.
m.Group("/issues", func() { m.Group("/issues", func() {
m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue). m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost) Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
}, context.CheckUnit(models.UnitTypeIssues))
// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
// So they can apply their own enable/disable logic on routers.
m.Group("/issues", func() {
m.Group("/:index", func() { m.Group("/:index", func() {
m.Post("/title", repo.UpdateIssueTitle) m.Post("/title", repo.UpdateIssueTitle)
m.Post("/content", repo.UpdateIssueContent) m.Post("/content", repo.UpdateIssueContent)
@@ -484,21 +485,21 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment) m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
}) })
m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter) m.Post("/labels", reqRepoWriter, repo.UpdateIssueLabel)
m.Post("/milestone", repo.UpdateIssueMilestone, reqRepoWriter) m.Post("/milestone", reqRepoWriter, repo.UpdateIssueMilestone)
m.Post("/assignee", repo.UpdateIssueAssignee, reqRepoWriter) m.Post("/assignee", reqRepoWriter, repo.UpdateIssueAssignee)
m.Post("/status", repo.UpdateIssueStatus, reqRepoWriter) m.Post("/status", reqRepoWriter, repo.UpdateIssueStatus)
}, context.CheckUnit(models.UnitTypeIssues)) })
m.Group("/comments/:id", func() { m.Group("/comments/:id", func() {
m.Post("", repo.UpdateCommentContent) m.Post("", repo.UpdateCommentContent)
m.Post("/delete", repo.DeleteComment) m.Post("/delete", repo.DeleteComment)
}, context.CheckUnit(models.UnitTypeIssues)) }, context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests))
m.Group("/labels", func() { m.Group("/labels", func() {
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel) m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel) m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
m.Post("/delete", repo.DeleteLabel) m.Post("/delete", repo.DeleteLabel)
m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels) m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels)
}, reqRepoWriter, context.RepoRef(), context.CheckUnit(models.UnitTypeIssues)) }, reqRepoWriter, context.RepoRef(), context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests))
m.Group("/milestones", func() { m.Group("/milestones", func() {
m.Combo("/new").Get(repo.NewMilestone). m.Combo("/new").Get(repo.NewMilestone).
Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost) Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
@@ -506,7 +507,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost) m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost)
m.Get("/:id/:action", repo.ChangeMilestonStatus) m.Get("/:id/:action", repo.ChangeMilestonStatus)
m.Post("/delete", repo.DeleteMilestone) m.Post("/delete", repo.DeleteMilestone)
}, reqRepoWriter, context.RepoRef(), context.CheckUnit(models.UnitTypeIssues)) }, reqRepoWriter, context.RepoRef(), context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests))
m.Combo("/compare/*", repo.MustAllowPulls, repo.SetEditorconfigIfExists). m.Combo("/compare/*", repo.MustAllowPulls, repo.SetEditorconfigIfExists).
Get(repo.CompareAndPullRequest). Get(repo.CompareAndPullRequest).
@@ -573,8 +574,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("", func() { m.Group("", func() {
m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues) m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue) m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
m.Get("/labels/", repo.RetrieveLabels, repo.Labels) m.Get("/labels/", context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests), repo.RetrieveLabels, repo.Labels)
m.Get("/milestones", repo.Milestones) m.Get("/milestones", context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests), repo.Milestones)
}, context.RepoRef()) }, context.RepoRef())
m.Group("/wiki", func() { m.Group("/wiki", func() {

View File

@@ -14,7 +14,8 @@
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="direct" {{if eq .commit_choice "direct"}}checked{{end}}> <input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="direct" {{if eq .commit_choice "direct"}}checked{{end}}>
<label> <label>
<i class="octicon octicon-git-commit" height="16" width="14"></i> <i class="octicon octicon-git-commit" height="16" width="14"></i>
{{.i18n.Tr "repo.editor.commit_directly_to_this_branch" .BranchName | Safe}} {{$branchName := .BranchName | Str2html}}
{{.i18n.Tr "repo.editor.commit_directly_to_this_branch" $branchName | Safe}}
</label> </label>
</div> </div>
</div> </div>

View File

@@ -32,7 +32,7 @@
</div> </div>
{{if .CanBeForked}} {{if .CanBeForked}}
<div class="ui compact labeled button" tabindex="0"> <div class="ui compact labeled button" tabindex="0">
<a class="ui compact button {{if eq .OwnerID $.SignedUserID}}poping up{{end}}" {{if not (eq .OwnerID $.SignedUserID)}}href="{{AppSubUrl}}/repo/fork/{{.ID}}"{{else}} data-content="{{$.i18n.Tr "repo.fork_from_self"}}" data-position="top center" data-variation="tiny"{{end}}> <a class="ui compact button {{if not $.CanSignedUserFork}}poping up{{end}}" {{if $.CanSignedUserFork}}href="{{AppSubUrl}}/repo/fork/{{.ID}}"{{else}} data-content="{{$.i18n.Tr "repo.fork_from_self"}}" data-position="top center" data-variation="tiny"{{end}}>
<i class="octicon octicon-repo-forked"></i>{{$.i18n.Tr "repo.fork"}} <i class="octicon octicon-repo-forked"></i>{{$.i18n.Tr "repo.fork"}}
</a> </a>
<a class="ui basic label" href="{{.Link}}/forks"> <a class="ui basic label" href="{{.Link}}/forks">

View File

@@ -103,8 +103,8 @@
</div> </div>
<div class="issue-actions"> <div class="issue-actions">
<div class="ui basic status buttons"> <div class="ui basic status buttons">
<div class="ui green active basic button issue-action" data-action="open" data-url="{{$.Link}}/status">{{.i18n.Tr "repo.issues.action_open"}}</div> <div class="ui green active basic button issue-action" data-action="open" data-url="{{$.RepoLink}}/issues/status">{{.i18n.Tr "repo.issues.action_open"}}</div>
<div class="ui red active basic button issue-action" data-action="close" data-url="{{$.Link}}/status">{{.i18n.Tr "repo.issues.action_close"}}</div> <div class="ui red active basic button issue-action" data-action="close" data-url="{{$.RepoLink}}/issues/status">{{.i18n.Tr "repo.issues.action_close"}}</div>
</div> </div>
<div class="ui secondary filter menu floated right"> <div class="ui secondary filter menu floated right">
@@ -116,7 +116,7 @@
</span> </span>
<div class="menu"> <div class="menu">
{{range .Labels}} {{range .Labels}}
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.Link}}/labels"> <div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
<span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | Sanitize}} <span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | Sanitize}}
</div> </div>
{{end}} {{end}}
@@ -134,7 +134,7 @@
{{.i18n.Tr "repo.issues.action_milestone_no_select"}} {{.i18n.Tr "repo.issues.action_milestone_no_select"}}
</div> </div>
{{range .Milestones}} {{range .Milestones}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.Link}}/milestone"> <div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/milestone">
{{.Name | Sanitize}} {{.Name | Sanitize}}
</div> </div>
{{end}} {{end}}
@@ -152,7 +152,7 @@
{{.i18n.Tr "repo.issues.action_assignee_no_select"}} {{.i18n.Tr "repo.issues.action_assignee_no_select"}}
</div> </div>
{{range .Assignees}} {{range .Assignees}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.Link}}/assignee"> <div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/assignee">
<img src="{{.RelAvatarLink}}"> {{.Name}} <img src="{{.RelAvatarLink}}"> {{.Name}}
</div> </div>
{{end}} {{end}}

View File

@@ -28,7 +28,7 @@
{{if .Issue.PullRequest.HasMerged}} {{if .Issue.PullRequest.HasMerged}}
{{ $mergedStr:= TimeSince .Issue.PullRequest.Merged $.Lang }} {{ $mergedStr:= TimeSince .Issue.PullRequest.Merged $.Lang }}
<a {{if gt .Issue.PullRequest.Merger.ID 0}}href="{{.Issue.PullRequest.Merger.HomeLink}}"{{end}}>{{.Issue.PullRequest.Merger.Name}}</a> <a {{if gt .Issue.PullRequest.Merger.ID 0}}href="{{.Issue.PullRequest.Merger.HomeLink}}"{{end}}>{{.Issue.PullRequest.Merger.Name}}</a>
<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Safe}}</span> <span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Str2html}}</span>
{{else}} {{else}}
<a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.Name}}</a> <a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.Name}}</a>
<span class="pull-desc">{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}</span> <span class="pull-desc">{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}</span>

View File

@@ -19,18 +19,18 @@
</span> </span>
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
<div class="menu"> <div class="menu">
{{if .CanForkToUser}}
<div class="item" data-value="{{.SignedUser.ID}}"> <div class="item" data-value="{{.SignedUser.ID}}">
<img class="ui mini image" src="{{.SignedUser.RelAvatarLink}}"> <img class="ui mini image" src="{{.SignedUser.RelAvatarLink}}">
{{.SignedUser.ShortName 20}} {{.SignedUser.ShortName 20}}
</div> </div>
{{end}}
{{range .Orgs}} {{range .Orgs}}
{{if and (.IsOwnedBy $.SignedUser.ID) (ne .ID $.ForkFromOwnerID)}}
<div class="item" data-value="{{.ID}}"> <div class="item" data-value="{{.ID}}">
<img class="ui mini image" src="{{.RelAvatarLink}}"> <img class="ui mini image" src="{{.RelAvatarLink}}">
{{.ShortName 20}} {{.ShortName 20}}
</div> </div>
{{end}} {{end}}
{{end}}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -36,8 +36,8 @@
{{end}} {{end}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<div class="file-view {{if .IsMarkdown}}markdown{{else if .IsTextFile}}code-view{{end}} has-emoji"> <div class="file-view {{if .IsRenderedHTML}}markdown{{else if .IsTextFile}}code-view{{end}} has-emoji">
{{if .IsMarkdown}} {{if .IsRenderedHTML}}
{{if .FileContent}}{{.FileContent | Str2html}}{{end}} {{if .FileContent}}{{.FileContent | Str2html}}{{end}}
{{else if not .IsTextFile}} {{else if not .IsTextFile}}
<div class="view-raw ui center"> <div class="view-raw ui center">

View File

@@ -7,12 +7,14 @@
{{if .LatestCommitUser.FullName}} {{if .LatestCommitUser.FullName}}
<a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommitUser.FullName}}</strong></a> <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
{{else}} {{else}}
<a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommit.Author.Name}}</strong></a> <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>
{{end}} {{end}}
{{else}} {{else}}
{{if .LatestCommit.Author}}
<img class="ui avatar image img-12" src="{{AvatarLink .LatestCommit.Author.Email}}" /> <img class="ui avatar image img-12" src="{{AvatarLink .LatestCommit.Author.Email}}" />
<strong>{{.LatestCommit.Author.Name}}</strong> <strong>{{.LatestCommit.Author.Name}}</strong>
{{end}} {{end}}
{{end}}
<a rel="nofollow" class="ui sha label {{if .LatestCommit.Signature}} isSigned {{if .LatestCommitVerification.Verified }} isVerified {{end}}{{end}}" href="{{.RepoLink}}/commit/{{.LatestCommit.ID}}"> <a rel="nofollow" class="ui sha label {{if .LatestCommit.Signature}} isSigned {{if .LatestCommitVerification.Verified }} isVerified {{end}}{{end}}" href="{{.RepoLink}}/commit/{{.LatestCommit.ID}}">
{{ShortSha .LatestCommit.ID.String}} {{ShortSha .LatestCommit.ID.String}}
{{if .LatestCommit.Signature}} {{if .LatestCommit.Signature}}
@@ -29,7 +31,7 @@
</th> </th>
<th class="nine wide"> <th class="nine wide">
</th> </th>
<th class="three wide text grey right age">{{TimeSince .LatestCommit.Author.When $.Lang}}</th> <th class="three wide text grey right age">{{if .LatestCommit.Author}}{{TimeSince .LatestCommit.Author.When $.Lang}}{{end}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@@ -15,3 +15,5 @@ Thomas Boerger <thomas@webhippie.de> (@tboerger)
Lauris Bukšis-Haberkorns <lauris@nix.lv> (@lafriks) Lauris Bukšis-Haberkorns <lauris@nix.lv> (@lafriks)
Antoine Girard <sapk@sapk.fr> (@sapk) Antoine Girard <sapk@sapk.fr> (@sapk)
Jonas Östanbäck <jonas.ostanback@gmail.com> (@cez81) Jonas Östanbäck <jonas.ostanback@gmail.com> (@cez81)
David Schneiderbauer <dschneiderbauer@gmail.com> (@daviian)
Peter Žeby <morlinest@gmail.com> (@morlinest)

11
vendor/code.gitea.io/git/commit.go generated vendored
View File

@@ -12,8 +12,6 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"github.com/mcuadros/go-version"
) )
// Commit represents a git commit. // Commit represents a git commit.
@@ -160,13 +158,7 @@ func CommitChanges(repoPath string, opts CommitChangesOptions) error {
func commitsCount(repoPath, revision, relpath string) (int64, error) { func commitsCount(repoPath, revision, relpath string) (int64, error) {
var cmd *Command var cmd *Command
isFallback := false
if version.Compare(gitVersion, "1.8.0", "<") {
isFallback = true
cmd = NewCommand("log", "--pretty=format:''")
} else {
cmd = NewCommand("rev-list", "--count") cmd = NewCommand("rev-list", "--count")
}
cmd.AddArguments(revision) cmd.AddArguments(revision)
if len(relpath) > 0 { if len(relpath) > 0 {
cmd.AddArguments("--", relpath) cmd.AddArguments("--", relpath)
@@ -177,9 +169,6 @@ func commitsCount(repoPath, revision, relpath string) (int64, error) {
return 0, err return 0, err
} }
if isFallback {
return int64(strings.Count(stdout, "\n")) + 1, nil
}
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
} }

View File

@@ -7,8 +7,6 @@ package git
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/mcuadros/go-version"
) )
// BranchPrefix base dir of the branch information file store on git // BranchPrefix base dir of the branch information file store on git
@@ -56,10 +54,6 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
// SetDefaultBranch sets default branch of repository. // SetDefaultBranch sets default branch of repository.
func (repo *Repository) SetDefaultBranch(name string) error { func (repo *Repository) SetDefaultBranch(name string) error {
if version.Compare(gitVersion, "1.7.10", "<") {
return ErrUnsupportedVersion{"1.7.10"}
}
_, err := NewCommand("symbolic-ref", "HEAD", BranchPrefix+name).RunInDir(repo.Path) _, err := NewCommand("symbolic-ref", "HEAD", BranchPrefix+name).RunInDir(repo.Path)
return err return err
} }

View File

@@ -10,8 +10,6 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/mcuadros/go-version"
) )
// getRefCommitID returns the last commit ID string of given reference (branch or tag). // getRefCommitID returns the last commit ID string of given reference (branch or tag).
@@ -248,37 +246,11 @@ func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (in
// CommitsBetween returns a list that contains commits between [last, before). // CommitsBetween returns a list that contains commits between [last, before).
func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) { func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) {
if version.Compare(gitVersion, "1.8.0", ">=") {
stdout, err := NewCommand("rev-list", before.ID.String()+"..."+last.ID.String()).RunInDirBytes(repo.Path) stdout, err := NewCommand("rev-list", before.ID.String()+"..."+last.ID.String()).RunInDirBytes(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout)) return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
}
// Fallback to stupid solution, which iterates all commits of the repository
// if before is not an ancestor of last.
l := list.New()
if last == nil || last.ParentCount() == 0 {
return l, nil
}
var err error
cur := last
for {
if cur.ID.Equal(before.ID) {
break
}
l.PushBack(cur)
if cur.ParentCount() == 0 {
break
}
cur, err = cur.Parent(0)
if err != nil {
return nil, err
}
}
return l, nil
} }
// CommitsBetweenIDs return commits between twoe commits // CommitsBetweenIDs return commits between twoe commits

View File

@@ -32,6 +32,7 @@ func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
sig.Email = string(line[emailStart+1 : emailEnd]) sig.Email = string(line[emailStart+1 : emailEnd])
// Check date format. // Check date format.
if len(line) > emailEnd+2 {
firstChar := line[emailEnd+2] firstChar := line[emailEnd+2]
if firstChar >= 48 && firstChar <= 57 { if firstChar >= 48 && firstChar <= 57 {
timestop := bytes.IndexByte(line[emailEnd+2:], ' ') timestop := bytes.IndexByte(line[emailEnd+2:], ' ')
@@ -44,5 +45,9 @@ func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
return nil, err return nil, err
} }
} }
} else {
// Fall back to unix 0 time
sig.When = time.Unix(0, 0)
}
return sig, nil return sig, nil
} }

View File

@@ -116,35 +116,51 @@ func (te *TreeEntry) GetSubJumpablePathName() string {
// Entries a list of entry // Entries a list of entry
type Entries []*TreeEntry type Entries []*TreeEntry
var sorter = []func(t1, t2 *TreeEntry) bool{ type customSortableEntries struct {
func(t1, t2 *TreeEntry) bool { Comparer func(s1, s2 string) bool
Entries
}
var sorter = []func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool{
func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule() return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
}, },
func(t1, t2 *TreeEntry) bool { func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
return t1.name < t2.name return cmp(t1.name, t2.name)
}, },
} }
func (tes Entries) Len() int { return len(tes) } func (ctes customSortableEntries) Len() int { return len(ctes.Entries) }
func (tes Entries) Swap(i, j int) { tes[i], tes[j] = tes[j], tes[i] }
func (tes Entries) Less(i, j int) bool { func (ctes customSortableEntries) Swap(i, j int) {
t1, t2 := tes[i], tes[j] ctes.Entries[i], ctes.Entries[j] = ctes.Entries[j], ctes.Entries[i]
}
func (ctes customSortableEntries) Less(i, j int) bool {
t1, t2 := ctes.Entries[i], ctes.Entries[j]
var k int var k int
for k = 0; k < len(sorter)-1; k++ { for k = 0; k < len(sorter)-1; k++ {
s := sorter[k] s := sorter[k]
switch { switch {
case s(t1, t2): case s(t1, t2, ctes.Comparer):
return true return true
case s(t2, t1): case s(t2, t1, ctes.Comparer):
return false return false
} }
} }
return sorter[k](t1, t2) return sorter[k](t1, t2, ctes.Comparer)
} }
// Sort sort the list of entry // Sort sort the list of entry
func (tes Entries) Sort() { func (tes Entries) Sort() {
sort.Sort(tes) sort.Sort(customSortableEntries{func(s1, s2 string) bool {
return s1 < s2
}, tes})
}
// CustomSort customizable string comparing sort entry list
func (tes Entries) CustomSort(cmp func(s1, s2 string) bool) {
sort.Sort(customSortableEntries{cmp, tes})
} }
type commitInfo struct { type commitInfo struct {

6
vendor/vendor.json vendored
View File

@@ -3,10 +3,10 @@
"ignore": "test appengine", "ignore": "test appengine",
"package": [ "package": [
{ {
"checksumSHA1": "fR5YDSoG7xYv2aLO23rne95gWps=", "checksumSHA1": "JN/re4+x/hCzMLGHmieUcykVDAg=",
"path": "code.gitea.io/git", "path": "code.gitea.io/git",
"revision": "479f87e5d189e7b8f1fd51dbcd25faa32b632cd2", "revision": "d47b98c44c9a6472e44ab80efe65235e11c6da2a",
"revisionTime": "2017-08-03T00:53:29Z" "revisionTime": "2017-10-23T00:52:09Z"
}, },
{ {
"checksumSHA1": "Zgp5RqU+20L2p9TNl1rSsUIWEEE=", "checksumSHA1": "Zgp5RqU+20L2p9TNl1rSsUIWEEE=",