Compare commits

..

24 Commits

Author SHA1 Message Date
Lauris BH
86d61bbb10 Changelog for release v1.3.3 (#3538)
Signed-off-by: Lauris Bukšis-Haberkorns <lauris@nix.lv>
2018-02-19 04:22:14 +01:00
Lauris BH
bfe13a28c2 Fix escaping changed title in comments (#3530) (#3535)
* Fix escaping of wiki page titile

Signed-off-by: Lauris Bukšis-Haberkorns <lauris@nix.lv>
2018-02-18 20:28:08 -06:00
Jonas Franz
ed27da4b0a Escape search query (Backport 1.3) (#3489)
* Escape search query

Signed-off-by: Jonas Franz <info@jonasfranz.de>

(cherry picked from commit 2970889)

* Reordered imports

Signed-off-by: Jonas Franz <info@jonasfranz.de>
2018-02-11 18:24:53 +02:00
Ethan Koenig
88c363f933 Fix repo-transfer-and-team-repo-count bug (#3241) (#3244) 2017-12-20 10:21:10 +02:00
Sandro Santilli
f33cd3c676 Open external tracker in blank window, consistently with wiki (#3228)
Closes #3216
2017-12-19 07:14:32 +02:00
Sandro Santilli
fecf9390ef Change SSL Mode from checkbox to string in admin page (#3211)
Closes #3207

Use a string, not a checkbox because "require", "verify-full",
"verify-ca" and "disable" values are supported ...
2017-12-16 23:28:12 +02:00
Lunny Xiao
bdf1856011 Add changelog for v1.3.2 (#3191) 2017-12-14 11:29:47 +02:00
Lunny Xiao
6037f183cf fix run web with -p push failed (#3154) (#3179) 2017-12-13 17:39:14 +08:00
Lauris BH
4b187f5167 Fix migration order v1.3 (#3157)
* FIx migration order

* Fix migration function file names to match version

* Fix typo

* Add note about ignored errors

* Fix typo, remove wrong comment
2017-12-12 19:15:26 +08:00
Lunny Xiao
aee45d072e Fix source download link when no code unit allowed (#3166) (#3169) 2017-12-12 15:54:39 +08:00
Lauris BH
e893aced89 Allow adding collaborators with (fullname) (#3103) (#3168)
* Allow adding collaborators with (fullname)

Signed-off-by: Sasha Varlamov <sasha@sashavarlamov.com>

* Refactor username suffix to utils pkg

Signed-off-by: Sasha Varlamov <sasha@sashavarlamov.com>
2017-12-12 15:23:08 +08:00
Lauris BH
d63ca66623 Fix repo links (#3093) (#3163) 2017-12-12 08:42:24 +02:00
Lauris BH
59afb62ab2 Fix Uninitialized variable in ParsePatch (#3156) (#3162) 2017-12-12 09:08:48 +08:00
Ethan Koenig
8a19c6b9a2 Fix avatar URLs (#3069) (#3143)
* Fix avatar URLs

* import order
2017-12-11 13:36:38 +08:00
Lunny Xiao
81fd8c8fb6 Comment backport test and add missing drone test (#3127)
* comment backport test since the test reference many changes

* fix missing drone test on release/*

* remove test coverage on release/*
2017-12-09 12:16:59 +02:00
Lauris BH
fd7686171e Changelog for version 1.3.1 (#3119) 2017-12-08 16:22:42 +01:00
Ethan Koenig
ec6718ef40 Sanitize logs for mirror sync (#3057, #3082) (#3078)
* Sanitize logs for mirror sync

* Fix error message sanitiziation (#3082)
2017-12-08 17:12:47 +02:00
Ethan Koenig
8f7054a864 Fix missing branch in release bug (#3108) (#3117) 2017-12-08 16:01:46 +08:00
Ethan Koenig
84352316a9 Fix repo indexer and submodule bug (#3107) (#3110)
* Fix repo indexer and submodule bug (#3107)

* Empty commit to re-trigger CI
2017-12-08 14:52:18 +08:00
Ethan Koenig
237df2f339 Fix legacy URL redirects (#3100) (#3106) 2017-12-07 08:29:14 +02:00
Lunny Xiao
b9abcb3b61 fix redis cache failed (#3086) (#3089) 2017-12-05 00:06:04 +02:00
Lunny Xiao
9832b9509d fix issue list branch link broken (#3061) (#3070) 2017-12-03 10:20:43 +08:00
Lunny Xiao
3d688bd2cc Fix missing password length check when change password (#3039) (#3071)
* fix missing password length check when change password

* add tests for change password
2017-12-03 09:49:25 +08:00
Stephan Sachse
ce4a52c22c sendmail: correct option to set envelope-sender (#3044)
mailer doesn't set the correct options while calling sendmail. if
``mailer.FROM`` is set to ``"Gitea Webservice" <user@example.com>``
sendmail is called like this

```
Sending with: sendmail [-F user@example.com -i alice@example.com]
```

and doesn't set the envelope-sender. the option ``-F string`` set the
sender full name and is used only with messages that has no ``From``
message header.

set the envelope sender address with ``-f sender`` (lowercase ``f``)
works for me (fedora 27, x86_64, postfix-3.2.4, go1.9.1, gitea-1.3.0)
2017-12-02 11:06:49 +08:00
52 changed files with 782 additions and 400 deletions

View File

@@ -86,6 +86,19 @@ pipeline:
event: [ push, pull_request ]
branch: [ master ]
test:
image: webhippie/golang:edge
pull: true
group: test
environment:
TAGS: bindata sqlite
GOPATH: /srv/app
commands:
- make test
when:
event: [ push, pull_request ]
branch: [ release/* ]
test:
image: webhippie/golang:edge
pull: true

View File

@@ -1,5 +1,34 @@
# Changelog
## [1.3.3](https://github.com/go-gitea/gitea/releases/tag/v1.3.3) - 2018-02-15
* SECURITY
* Fix escaping changed title in comments (#3530) (#3535)
* Escape search query display (#3486) (#3489)
* BUGFIXES
* Fix repo-transfer-and-team-repo-count bug (#3241) (#3244)
* Open external tracker in blank window, consistently with wiki (#3227) (#3228)
* Change SSL Mode from checkbox to string in admin page (#3208) (#3211)
## [1.3.2](https://github.com/go-gitea/gitea/releases/tag/v1.3.2) - 2017-12-14
* BUGFIXES
* fix run web with -p push failed (#3154) (#3179)
* Fix source download link when no code unit allowed (#3166) (#3169)
* Allow adding collaborators with (fullname) (#3103) (#3168)
* Fix repo links (#3093) (#3163)
* Fix Uninitialized variable in ParsePatch (#3156) (#3162)
* Fix migration order v1.3 (#3157)
* Fix avatar URLs (#3069) (#3143)
## [1.3.1](https://github.com/go-gitea/gitea/releases/tag/v1.3.1) - 2017-12-08
* BUGFIXES
* Sanitize logs for mirror sync (#3057, #3082) (#3078)
* Fix missing branch in release bug (#3108) (#3117)
* Fix repo indexer and submodule bug (#3107) (#3110)
* Fix legacy URL redirects (#3100) (#3106)
* Fix redis session failed (#3086) (#3089)
* Fix issue list branch link broken (#3061) (#3070)
* Fix missing password length check when change password (#3039) (#3071)
## [1.3.0](https://github.com/go-gitea/gitea/releases/tag/v1.3.0) - 2017-11-29
* BREAKING
* Make URL scheme unambiguous (#2408)

View File

@@ -19,8 +19,10 @@ import (
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/routes"
"github.com/Unknwon/com"
context2 "github.com/gorilla/context"
"github.com/urfave/cli"
ini "gopkg.in/ini.v1"
)
// CmdWeb represents the available web sub-command.
@@ -69,6 +71,34 @@ func runWeb(ctx *cli.Context) error {
if ctx.IsSet("port") {
setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, ctx.String("port"), 1)
setting.HTTPPort = ctx.String("port")
switch setting.Protocol {
case setting.UnixSocket:
case setting.FCGI:
default:
// Save LOCAL_ROOT_URL if port changed
cfg := ini.Empty()
if com.IsFile(setting.CustomConf) {
// Keeps custom settings if there is already something.
if err := cfg.Append(setting.CustomConf); err != nil {
return fmt.Errorf("Failed to load custom conf '%s': %v", setting.CustomConf, err)
}
}
defaultLocalURL := string(setting.Protocol) + "://"
if setting.HTTPAddr == "0.0.0.0" {
defaultLocalURL += "localhost"
} else {
defaultLocalURL += setting.HTTPAddr
}
defaultLocalURL += ":" + setting.HTTPPort + "/"
cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
if err := cfg.SaveTo(setting.CustomConf); err != nil {
return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err)
}
}
}
var listenAddr string

View File

@@ -46,8 +46,10 @@ func TestRedirectsNoLogin(t *testing.T) {
prepareTestEnv(t)
var redirects = map[string]string{
"/user2/repo1/commits/master": "/user2/repo1/commits/branch/master",
"/user2/repo1/src/master": "/user2/repo1/src/branch/master",
"/user2/repo1/commits/master": "/user2/repo1/commits/branch/master",
"/user2/repo1/src/master": "/user2/repo1/src/branch/master",
"/user2/repo1/src/master/file.txt": "/user2/repo1/src/branch/master/file.txt",
"/user2/repo1/src/master/directory/file.txt": "/user2/repo1/src/branch/master/directory/file.txt",
}
for link, redirectLink := range redirects {
req := NewRequest(t, "GET", link)

View File

@@ -13,7 +13,7 @@ import (
"github.com/stretchr/testify/assert"
)
func testPullCreate(t *testing.T, session *TestSession, user, repo, branch string) *TestResponse {
func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, title string) *TestResponse {
req := NewRequest(t, "GET", path.Join(user, repo))
resp := session.MakeRequest(t, req, http.StatusOK)
@@ -34,7 +34,7 @@ func testPullCreate(t *testing.T, session *TestSession, user, repo, branch strin
assert.True(t, exists, "The template has changed")
req = NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"title": "This is a pull title",
"title": title,
})
resp = session.MakeRequest(t, req, http.StatusFound)
@@ -48,5 +48,40 @@ func TestPullCreate(t *testing.T) {
session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
testPullCreate(t, session, "user1", "repo1", "master")
testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")
}
func TestPullCreate_TitleEscape(t *testing.T) {
prepareTestEnv(t)
session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master", "<i>XSS PR</i>")
// check the redirected URL
url := RedirectURL(t, resp)
assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)
// Edit title
req := NewRequest(t, "GET", url)
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
editTestTitleURL, exists := htmlDoc.doc.Find("#save-edit-title").First().Attr("data-update-url")
assert.True(t, exists, "The template has changed")
req = NewRequestWithValues(t, "POST", editTestTitleURL, map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"title": "<u>XSS PR</u>",
})
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", url)
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
titleHTML, err := htmlDoc.doc.Find(".comments .event .text b").First().Html()
assert.NoError(t, err)
assert.Equal(t, "&lt;i&gt;XSS PR&lt;/i&gt;", titleHTML)
titleHTML, err = htmlDoc.doc.Find(".comments .event .text b").Next().Html()
assert.NoError(t, err)
assert.Equal(t, "&lt;u&gt;XSS PR&lt;/u&gt;", titleHTML)
}

View File

@@ -51,7 +51,7 @@ func TestPullMerge(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master")
resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")
elem := strings.Split(RedirectURL(t, resp), "/")
assert.EqualValues(t, "pulls", elem[3])
@@ -64,7 +64,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "feature/test")
resp := testPullCreate(t, session, "user1", "repo1", "feature/test", "This is a pull title")
elem := strings.Split(RedirectURL(t, resp), "/")
assert.EqualValues(t, "pulls", elem[3])

View File

@@ -19,16 +19,16 @@ func TestRepoActivity(t *testing.T) {
// Create PRs (1 merged & 2 proposed)
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master")
resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")
elem := strings.Split(RedirectURL(t, resp), "/")
assert.EqualValues(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4])
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n")
testPullCreate(t, session, "user1", "repo1", "feat/better_readme")
testPullCreate(t, session, "user1", "repo1", "feat/better_readme", "This is a pull title")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/much_better_readme", "README.md", "Hello, World (Edited More)\n")
testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme")
testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme", "This is a pull title")
// Create issues (3 new issues)
testNewIssue(t, session, "user2", "repo1", "Issue 1", "Description 1")

View File

@@ -238,7 +238,7 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
var (
diff = &Diff{Files: make([]*DiffFile, 0)}
curFile *DiffFile
curFile = &DiffFile{}
curSection = &DiffSection{
Lines: make([]*DiffLine, 0, 10),
}

View File

@@ -59,6 +59,10 @@ type Version struct {
Version int64
}
func emptyMigration(x *xorm.Engine) error {
return nil
}
// This is a sequence of migrations. Add new migrations to the bottom of the list.
// If you want to "retire" a migration, remove it from the top of the list and
// update minDBVersion accordingly
@@ -127,17 +131,17 @@ var migrations = []Migration{
// v38 -> v39
NewMigration("remove commits and settings unit types", removeCommitsUnitType),
// v39 -> v40
NewMigration("adds time tracking and stopwatches", addTimetracking),
// v40 -> v41
NewMigration("migrate protected branch struct", migrateProtectedBranchStruct),
// v41 -> v42
NewMigration("add default value to user prohibit_login", addDefaultValueToUserProhibitLogin),
// v42 -> v43
NewMigration("add tags to releases and sync existing repositories", releaseAddColumnIsTagAndSyncTags),
// v43 -> v44
NewMigration("fix protected branch can push value to false", fixProtectedBranchCanPushValue),
// v44 -> v45
// v40 -> v41
NewMigration("add tags to releases and sync existing repositories", releaseAddColumnIsTagAndSyncTags),
// v41 -> v42
NewMigration("remove duplicate unit types", removeDuplicateUnitTypes),
// v42 -> v43
NewMigration("empty step", emptyMigration),
// v43 -> v44
NewMigration("empty step", emptyMigration),
// v44 -> v45
NewMigration("empty step", emptyMigration),
// v45 -> v46
NewMigration("remove index column from repo_unit table", removeIndexColumnFromRepoUnitTable),
// v46 -> v47
@@ -146,6 +150,12 @@ var migrations = []Migration{
NewMigration("add deleted branches", addDeletedBranch),
// v48 -> v49
NewMigration("add repo indexer status", addRepoIndexerStatus),
// v49 -> v50
NewMigration("adds time tracking and stopwatches", addTimetracking),
// v50 -> v51
NewMigration("migrate protected branch struct", migrateProtectedBranchStruct),
// v51 -> v52
NewMigration("add default value to user prohibit_login", addDefaultValueToUserProhibitLogin),
}
// Migrate database to current version

View File

@@ -6,69 +6,21 @@ package migrations
import (
"fmt"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func addTimetracking(x *xorm.Engine) error {
// RepoUnit describes all units of a repository
type RepoUnit struct {
ID int64
RepoID int64 `xorm:"INDEX(s)"`
Type int `xorm:"INDEX(s)"`
Index int
Config map[string]interface{} `xorm:"JSON"`
CreatedUnix int64 `xorm:"INDEX CREATED"`
Created time.Time `xorm:"-"`
func fixProtectedBranchCanPushValue(x *xorm.Engine) error {
type ProtectedBranch struct {
CanPush bool `xorm:"NOT NULL DEFAULT false"`
}
// Stopwatch see models/issue_stopwatch.go
type Stopwatch struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"`
UserID int64 `xorm:"INDEX"`
Created time.Time `xorm:"-"`
CreatedUnix int64
}
// TrackedTime see models/issue_tracked_time.go
type TrackedTime struct {
ID int64 `xorm:"pk autoincr" json:"id"`
IssueID int64 `xorm:"INDEX" json:"issue_id"`
UserID int64 `xorm:"INDEX" json:"user_id"`
Created time.Time `xorm:"-" json:"created"`
CreatedUnix int64 `json:"-"`
Time int64 `json:"time"`
}
if err := x.Sync2(new(Stopwatch)); err != nil {
if err := x.Sync2(new(ProtectedBranch)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
if err := x.Sync2(new(TrackedTime)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
//Updating existing issue units
units := make([]*RepoUnit, 0, 100)
err := x.Where("`type` = ?", V16UnitTypeIssues).Find(&units)
if err != nil {
return fmt.Errorf("Query repo units: %v", err)
}
for _, unit := range units {
if unit.Config == nil {
unit.Config = make(map[string]interface{})
}
if _, ok := unit.Config["EnableTimetracker"]; !ok {
unit.Config["EnableTimetracker"] = setting.Service.DefaultEnableTimetracking
}
if _, ok := unit.Config["AllowOnlyContributorsToTrackTime"]; !ok {
unit.Config["AllowOnlyContributorsToTrackTime"] = setting.Service.DefaultAllowOnlyContributorsToTrackTime
}
if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
return err
}
}
return nil
_, err := x.Cols("can_push").Update(&ProtectedBranch{
CanPush: false,
})
return err
}

View File

@@ -6,50 +6,52 @@ package migrations
import (
"fmt"
"time"
"code.gitea.io/git"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func migrateProtectedBranchStruct(x *xorm.Engine) error {
type ProtectedBranch struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"`
BranchName string `xorm:"UNIQUE(s)"`
CanPush bool
Created time.Time `xorm:"-"`
CreatedUnix int64
Updated time.Time `xorm:"-"`
UpdatedUnix int64
// ReleaseV39 describes the added field for Release
type ReleaseV39 struct {
IsTag bool `xorm:"NOT NULL DEFAULT false"`
}
// TableName will be invoked by XORM to customrize the table name
func (*ReleaseV39) TableName() string {
return "release"
}
func releaseAddColumnIsTagAndSyncTags(x *xorm.Engine) error {
if err := x.Sync2(new(ReleaseV39)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
var pbs []ProtectedBranch
err := x.Find(&pbs)
if err != nil {
return err
}
// For the sake of SQLite3, we can't use x.Iterate here.
offset := 0
pageSize := 20
for {
repos := make([]*models.Repository, 0, pageSize)
if err := x.Table("repository").Asc("id").Limit(pageSize, offset).Find(&repos); err != nil {
return fmt.Errorf("select repos [offset: %d]: %v", offset, err)
}
for _, repo := range repos {
gitRepo, err := git.OpenRepository(repo.RepoPath())
if err != nil {
log.Warn("OpenRepository: %v", err)
continue
}
for _, pb := range pbs {
if pb.CanPush {
if _, err = x.ID(pb.ID).Delete(new(ProtectedBranch)); err != nil {
return err
if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
log.Warn("SyncReleasesWithTags: %v", err)
}
}
}
switch {
case setting.UseSQLite3:
log.Warn("Unable to drop columns in SQLite")
case setting.UseMySQL, setting.UsePostgreSQL, setting.UseMSSQL, setting.UseTiDB:
if _, err := x.Exec("ALTER TABLE protected_branch DROP COLUMN can_push"); err != nil {
return fmt.Errorf("DROP COLUMN can_push: %v", err)
if len(repos) < pageSize {
break
}
default:
log.Fatal(4, "Unrecognized DB")
offset += pageSize
}
return nil
}

View File

@@ -7,36 +7,63 @@ package migrations
import (
"fmt"
"code.gitea.io/gitea/models"
"github.com/go-xorm/xorm"
)
func addDefaultValueToUserProhibitLogin(x *xorm.Engine) (err error) {
user := &models.User{
ProhibitLogin: false,
func removeDuplicateUnitTypes(x *xorm.Engine) error {
// RepoUnit describes all units of a repository
type RepoUnit struct {
RepoID int64
Type int
}
if _, err := x.Where("`prohibit_login` IS NULL").Cols("prohibit_login").Update(user); err != nil {
// Enumerate all the unit types
const (
UnitTypeCode = iota + 1 // 1 code
UnitTypeIssues // 2 issues
UnitTypePullRequests // 3 PRs
UnitTypeReleases // 4 Releases
UnitTypeWiki // 5 Wiki
UnitTypeExternalWiki // 6 ExternalWiki
UnitTypeExternalTracker // 7 ExternalTracker
)
var externalIssueRepoUnits []RepoUnit
err := x.Where("type = ?", UnitTypeExternalTracker).Find(&externalIssueRepoUnits)
if err != nil {
return fmt.Errorf("Query repositories: %v", err)
}
var externalWikiRepoUnits []RepoUnit
err = x.Where("type = ?", UnitTypeExternalWiki).Find(&externalWikiRepoUnits)
if err != nil {
return fmt.Errorf("Query repositories: %v", err)
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
dialect := x.Dialect().DriverName()
switch dialect {
case "mysql":
_, err = x.Exec("ALTER TABLE user MODIFY `prohibit_login` tinyint(1) NOT NULL DEFAULT 0")
case "postgres":
_, err = x.Exec("ALTER TABLE \"user\" ALTER COLUMN `prohibit_login` SET NOT NULL, ALTER COLUMN `prohibit_login` SET DEFAULT false")
case "mssql":
// xorm already set DEFAULT 0 for data type BIT in mssql
_, err = x.Exec(`ALTER TABLE [user] ALTER COLUMN "prohibit_login" BIT NOT NULL`)
case "sqlite3":
for _, repoUnit := range externalIssueRepoUnits {
if _, err = sess.Delete(&RepoUnit{
RepoID: repoUnit.RepoID,
Type: UnitTypeIssues,
}); err != nil {
return fmt.Errorf("Delete repo unit: %v", err)
}
}
if err != nil {
return fmt.Errorf("Error changing user prohibit_login column definition: %v", err)
for _, repoUnit := range externalWikiRepoUnits {
if _, err = sess.Delete(&RepoUnit{
RepoID: repoUnit.RepoID,
Type: UnitTypeWiki,
}); err != nil {
return fmt.Errorf("Delete repo unit: %v", err)
}
}
return err
return sess.Commit()
}

View File

@@ -1,57 +0,0 @@
// Copyright 2017 The Gitea 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 migrations
import (
"fmt"
"code.gitea.io/git"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"github.com/go-xorm/xorm"
)
// ReleaseV39 describes the added field for Release
type ReleaseV39 struct {
IsTag bool `xorm:"NOT NULL DEFAULT false"`
}
// TableName will be invoked by XORM to customrize the table name
func (*ReleaseV39) TableName() string {
return "release"
}
func releaseAddColumnIsTagAndSyncTags(x *xorm.Engine) error {
if err := x.Sync2(new(ReleaseV39)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
// For the sake of SQLite3, we can't use x.Iterate here.
offset := 0
pageSize := 20
for {
repos := make([]*models.Repository, 0, pageSize)
if err := x.Table("repository").Asc("id").Limit(pageSize, offset).Find(&repos); err != nil {
return fmt.Errorf("select repos [offset: %d]: %v", offset, err)
}
for _, repo := range repos {
gitRepo, err := git.OpenRepository(repo.RepoPath())
if err != nil {
log.Warn("OpenRepository: %v", err)
continue
}
if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
log.Warn("SyncReleasesWithTags: %v", err)
}
}
if len(repos) < pageSize {
break
}
offset += pageSize
}
return nil
}

View File

@@ -1,26 +0,0 @@
// Copyright 2017 The Gitea 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 migrations
import (
"fmt"
"github.com/go-xorm/xorm"
)
func fixProtectedBranchCanPushValue(x *xorm.Engine) error {
type ProtectedBranch struct {
CanPush bool `xorm:"NOT NULL DEFAULT false"`
}
if err := x.Sync2(new(ProtectedBranch)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
_, err := x.Cols("can_push").Update(&ProtectedBranch{
CanPush: false,
})
return err
}

View File

@@ -1,69 +0,0 @@
// Copyright 2017 The Gitea 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 migrations
import (
"fmt"
"github.com/go-xorm/xorm"
)
func removeDuplicateUnitTypes(x *xorm.Engine) error {
// RepoUnit describes all units of a repository
type RepoUnit struct {
RepoID int64
Type int
}
// Enumerate all the unit types
const (
UnitTypeCode = iota + 1 // 1 code
UnitTypeIssues // 2 issues
UnitTypePullRequests // 3 PRs
UnitTypeReleases // 4 Releases
UnitTypeWiki // 5 Wiki
UnitTypeExternalWiki // 6 ExternalWiki
UnitTypeExternalTracker // 7 ExternalTracker
)
var externalIssueRepoUnits []RepoUnit
err := x.Where("type = ?", UnitTypeExternalTracker).Find(&externalIssueRepoUnits)
if err != nil {
return fmt.Errorf("Query repositories: %v", err)
}
var externalWikiRepoUnits []RepoUnit
err = x.Where("type = ?", UnitTypeExternalWiki).Find(&externalWikiRepoUnits)
if err != nil {
return fmt.Errorf("Query repositories: %v", err)
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
for _, repoUnit := range externalIssueRepoUnits {
if _, err = sess.Delete(&RepoUnit{
RepoID: repoUnit.RepoID,
Type: UnitTypeIssues,
}); err != nil {
return fmt.Errorf("Delete repo unit: %v", err)
}
}
for _, repoUnit := range externalWikiRepoUnits {
if _, err = sess.Delete(&RepoUnit{
RepoID: repoUnit.RepoID,
Type: UnitTypeWiki,
}); err != nil {
return fmt.Errorf("Delete repo unit: %v", err)
}
}
return sess.Commit()
}

73
models/migrations/v49.go Normal file
View File

@@ -0,0 +1,73 @@
// Copyright 2017 The Gitea 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 migrations
import (
"fmt"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func addTimetracking(x *xorm.Engine) error {
// RepoUnit describes all units of a repository
type RepoUnit struct {
ID int64
RepoID int64 `xorm:"INDEX(s)"`
Type int `xorm:"INDEX(s)"`
Config map[string]interface{} `xorm:"JSON"`
CreatedUnix int64 `xorm:"INDEX CREATED"`
Created time.Time `xorm:"-"`
}
// Stopwatch see models/issue_stopwatch.go
type Stopwatch struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"`
UserID int64 `xorm:"INDEX"`
Created time.Time `xorm:"-"`
CreatedUnix int64
}
// TrackedTime see models/issue_tracked_time.go
type TrackedTime struct {
ID int64 `xorm:"pk autoincr" json:"id"`
IssueID int64 `xorm:"INDEX" json:"issue_id"`
UserID int64 `xorm:"INDEX" json:"user_id"`
Created time.Time `xorm:"-" json:"created"`
CreatedUnix int64 `json:"-"`
Time int64 `json:"time"`
}
if err := x.Sync2(new(Stopwatch)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
if err := x.Sync2(new(TrackedTime)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
//Updating existing issue units
units := make([]*RepoUnit, 0, 100)
err := x.Where("`type` = ?", V16UnitTypeIssues).Find(&units)
if err != nil {
return fmt.Errorf("Query repo units: %v", err)
}
for _, unit := range units {
if unit.Config == nil {
unit.Config = make(map[string]interface{})
}
if _, ok := unit.Config["EnableTimetracker"]; !ok {
unit.Config["EnableTimetracker"] = setting.Service.DefaultEnableTimetracking
}
if _, ok := unit.Config["AllowOnlyContributorsToTrackTime"]; !ok {
unit.Config["AllowOnlyContributorsToTrackTime"] = setting.Service.DefaultAllowOnlyContributorsToTrackTime
}
if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
return err
}
}
return nil
}

55
models/migrations/v50.go Normal file
View File

@@ -0,0 +1,55 @@
// Copyright 2017 The Gitea 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 migrations
import (
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func migrateProtectedBranchStruct(x *xorm.Engine) error {
type ProtectedBranch struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"`
BranchName string `xorm:"UNIQUE(s)"`
CanPush bool
Created time.Time `xorm:"-"`
CreatedUnix int64
Updated time.Time `xorm:"-"`
UpdatedUnix int64
}
var pbs []ProtectedBranch
err := x.Find(&pbs)
if err != nil {
return err
}
for _, pb := range pbs {
if pb.CanPush {
if _, err = x.ID(pb.ID).Delete(new(ProtectedBranch)); err != nil {
return err
}
}
}
switch {
case setting.UseSQLite3:
log.Warn("Unable to drop columns in SQLite")
case setting.UseMySQL, setting.UsePostgreSQL, setting.UseMSSQL, setting.UseTiDB:
if _, err := x.Exec("ALTER TABLE protected_branch DROP COLUMN can_push"); err != nil {
// Ignoring this error in case we run this migration second time (after migration reordering)
log.Warn("DROP COLUMN can_push: %v", err)
}
default:
log.Fatal(4, "Unrecognized DB")
}
return nil
}

42
models/migrations/v51.go Normal file
View File

@@ -0,0 +1,42 @@
// Copyright 2017 The Gitea 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 migrations
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"github.com/go-xorm/xorm"
)
func addDefaultValueToUserProhibitLogin(x *xorm.Engine) (err error) {
user := &models.User{
ProhibitLogin: false,
}
if _, err := x.Where("`prohibit_login` IS NULL").Cols("prohibit_login").Update(user); err != nil {
return err
}
dialect := x.Dialect().DriverName()
switch dialect {
case "mysql":
_, err = x.Exec("ALTER TABLE user MODIFY `prohibit_login` tinyint(1) NOT NULL DEFAULT 0")
case "postgres":
_, err = x.Exec("ALTER TABLE \"user\" ALTER COLUMN `prohibit_login` SET NOT NULL, ALTER COLUMN `prohibit_login` SET DEFAULT false")
case "mssql":
// xorm already set DEFAULT 0 for data type BIT in mssql
_, err = x.Exec(`ALTER TABLE [user] ALTER COLUMN "prohibit_login" BIT NOT NULL`)
case "sqlite3":
}
if err != nil {
// Ignoring this error in case we run this migration second time (after migration reordering)
log.Warn("Error changing user prohibit_login column definition: %v", err)
}
return nil
}

View File

@@ -605,9 +605,14 @@ func (repo *Repository) RepoPath() string {
return repo.repoPath(x)
}
// GitConfigPath returns the path to a repository's git config/ directory
func GitConfigPath(repoPath string) string {
return filepath.Join(repoPath, "config")
}
// GitConfigPath returns the repository git config path
func (repo *Repository) GitConfigPath() string {
return filepath.Join(repo.RepoPath(), "config")
return GitConfigPath(repo.RepoPath())
}
// RelLink returns the repository relative link
@@ -1496,20 +1501,6 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
// Remove old team-repository relations.
if owner.IsOrganization() {
if err = owner.getTeams(sess); err != nil {
return fmt.Errorf("getTeams: %v", err)
}
for _, t := range owner.Teams {
if !t.hasRepository(sess, repo.ID) {
continue
}
t.NumRepos--
if _, err := sess.ID(t.ID).Cols("num_repos").Update(t); err != nil {
return fmt.Errorf("decrease team repository count '%d': %v", t.ID, err)
}
}
if err = owner.removeOrgRepo(sess, repo.ID); err != nil {
return fmt.Errorf("removeOrgRepo: %v", err)
}

View File

@@ -100,10 +100,6 @@ func populateRepoIndexer() error {
}
}
type updateBatch struct {
updates []indexer.RepoIndexerUpdate
}
func updateRepoIndexer(repo *Repository) error {
changes, err := getRepoChanges(repo)
if err != nil {
@@ -163,6 +159,10 @@ func addUpdate(filename string, repo *Repository, batch *indexer.Batch) error {
return err
} else if stat.Size() > setting.Indexer.MaxIndexerFileSize {
return nil
} else if stat.IsDir() {
// file could actually be a directory, if it is the root of a submodule.
// We do not index submodule contents, so don't do anything.
return nil
}
fileContents, err := ioutil.ReadFile(filepath)
if err != nil {

View File

@@ -6,18 +6,18 @@ package models
import (
"fmt"
"strings"
"time"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"gopkg.in/ini.v1"
"code.gitea.io/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"gopkg.in/ini.v1"
)
// MirrorQueue holds an UniqueQueue object of the mirror
@@ -76,41 +76,41 @@ func (m *Mirror) ScheduleNextUpdate() {
m.NextUpdate = time.Now().Add(m.Interval)
}
func remoteAddress(repoPath string) (string, error) {
cfg, err := ini.Load(GitConfigPath(repoPath))
if err != nil {
return "", err
}
return cfg.Section("remote \"origin\"").Key("url").Value(), nil
}
func (m *Mirror) readAddress() {
if len(m.address) > 0 {
return
}
cfg, err := ini.Load(m.Repo.GitConfigPath())
var err error
m.address, err = remoteAddress(m.Repo.RepoPath())
if err != nil {
log.Error(4, "Load: %v", err)
return
log.Error(4, "remoteAddress: %v", err)
}
m.address = cfg.Section("remote \"origin\"").Key("url").Value()
}
// HandleCloneUserCredentials replaces user credentials from HTTP/HTTPS URL
// with placeholder <credentials>.
// It will fail for any other forms of clone addresses.
func HandleCloneUserCredentials(url string, mosaics bool) string {
i := strings.Index(url, "@")
if i == -1 {
return url
// sanitizeOutput sanitizes output of a command, replacing occurrences of the
// repository's remote address with a sanitized version.
func sanitizeOutput(output, repoPath string) (string, error) {
remoteAddr, err := remoteAddress(repoPath)
if err != nil {
// if we're unable to load the remote address, then we're unable to
// sanitize.
return "", err
}
start := strings.Index(url, "://")
if start == -1 {
return url
}
if mosaics {
return url[:start+3] + "<credentials>" + url[i:]
}
return url[:start+3] + url[i+1:]
return util.SanitizeMessage(output, remoteAddr), nil
}
// Address returns mirror address from Git repository config without credentials.
func (m *Mirror) Address() string {
m.readAddress()
return HandleCloneUserCredentials(m.address, false)
return util.SanitizeURLCredentials(m.address, false)
}
// FullAddress returns mirror address from Git repository config.
@@ -145,7 +145,14 @@ func (m *Mirror) runSync() bool {
if _, stderr, err := process.GetManager().ExecDir(
timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath),
"git", gitArgs...); err != nil {
desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, stderr)
// sanitize the output, since it may contain the remote address, which may
// contain a password
message, err := sanitizeOutput(stderr, repoPath)
if err != nil {
log.Error(4, "sanitizeOutput: %v", err)
return false
}
desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, message)
log.Error(4, desc)
if err = CreateRepositoryNotice(desc); err != nil {
log.Error(4, "CreateRepositoryNotice: %v", err)
@@ -170,7 +177,14 @@ func (m *Mirror) runSync() bool {
if _, stderr, err := process.GetManager().ExecDir(
timeout, wikiPath, fmt.Sprintf("Mirror.runSync: %s", wikiPath),
"git", "remote", "update", "--prune"); err != nil {
desc := fmt.Sprintf("Failed to update mirror wiki repository '%s': %s", wikiPath, stderr)
// sanitize the output, since it may contain the remote address, which may
// contain a password
message, err := sanitizeOutput(stderr, wikiPath)
if err != nil {
log.Error(4, "sanitizeOutput: %v", err)
return false
}
desc := fmt.Sprintf("Failed to update mirror wiki repository '%s': %s", wikiPath, message)
log.Error(4, desc)
if err = CreateRepositoryNotice(desc); err != nil {
log.Error(4, "CreateRepositoryNotice: %v", err)

View File

@@ -153,3 +153,26 @@ func TestRepoLocalCopyPath(t *testing.T) {
setting.Repository.Local.LocalCopyPath = tempPath
assert.Equal(t, expected, repo.LocalCopyPath())
}
func TestTransferOwnership(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
repo.Owner = AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User)
assert.NoError(t, TransferOwnership(doer, "user2", repo))
transferredRepo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
assert.EqualValues(t, 2, transferredRepo.OwnerID)
assert.False(t, com.IsExist(RepoPath("user3", "repo3")))
assert.True(t, com.IsExist(RepoPath("user2", "repo3")))
AssertExistsAndLoadBean(t, &Action{
OpType: ActionTransferRepo,
ActUserID: 2,
RepoID: 3,
Content: "user3/repo3",
})
CheckConsistencyFor(t, &Repository{}, &User{}, &Team{})
}

View File

@@ -315,10 +315,9 @@ func (u *User) generateRandomAvatar(e Engine) error {
return nil
}
// RelAvatarLink returns relative avatar link to the site domain,
// which includes app sub-url as prefix. However, it is possible
// to return full URL if user enables Gravatar-like service.
func (u *User) RelAvatarLink() string {
// SizedRelAvatarLink returns a relative link to the user's avatar. When
// applicable, the link is for an avatar of the indicated size (in pixels).
func (u *User) SizedRelAvatarLink(size int) string {
if u.ID == -1 {
return base.DefaultAvatarLink()
}
@@ -338,7 +337,14 @@ func (u *User) RelAvatarLink() string {
return setting.AppSubURL + "/avatars/" + u.Avatar
}
return base.AvatarLink(u.AvatarEmail)
return base.SizedAvatarLink(u.AvatarEmail, size)
}
// RelAvatarLink returns a relative link to the user's avatar. The link
// may either be a sub-URL to this site, or a full URL to an external avatar
// service.
func (u *User) RelAvatarLink() string {
return u.SizedRelAvatarLink(base.DefaultAvatarSize)
}
// AvatarLink returns user avatar absolute link.

View File

@@ -16,6 +16,8 @@ import (
"math"
"math/big"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
@@ -197,24 +199,59 @@ func DefaultAvatarLink() string {
return setting.AppSubURL + "/img/avatar_default.png"
}
// DefaultAvatarSize is a sentinel value for the default avatar size, as
// determined by the avatar-hosting service.
const DefaultAvatarSize = -1
// libravatarURL returns the URL for the given email. This function should only
// be called if a federated avatar service is enabled.
func libravatarURL(email string) (*url.URL, error) {
urlStr, err := setting.LibravatarService.FromEmail(email)
if err != nil {
log.Error(4, "LibravatarService.FromEmail(email=%s): error %v", email, err)
return nil, err
}
u, err := url.Parse(urlStr)
if err != nil {
log.Error(4, "Failed to parse libravatar url(%s): error %v", urlStr, err)
return nil, err
}
return u, nil
}
// SizedAvatarLink returns a sized link to the avatar for the given email
// address.
func SizedAvatarLink(email string, size int) string {
var avatarURL *url.URL
if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
var err error
avatarURL, err = libravatarURL(email)
if err != nil {
return DefaultAvatarLink()
}
} else if !setting.DisableGravatar {
// copy GravatarSourceURL, because we will modify its Path.
copyOfGravatarSourceURL := *setting.GravatarSourceURL
avatarURL = &copyOfGravatarSourceURL
avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email))
} else {
return DefaultAvatarLink()
}
vals := avatarURL.Query()
vals.Set("d", "identicon")
if size != DefaultAvatarSize {
vals.Set("s", strconv.Itoa(size))
}
avatarURL.RawQuery = vals.Encode()
return avatarURL.String()
}
// AvatarLink returns relative avatar link to the site domain by given email,
// which includes app sub-url as prefix. However, it is possible
// to return full URL if user enables Gravatar-like service.
func AvatarLink(email string) string {
if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
url, err := setting.LibravatarService.FromEmail(email)
if err != nil {
log.Error(4, "LibravatarService.FromEmail(email=%s): error %v", email, err)
return DefaultAvatarLink()
}
return url
}
if !setting.DisableGravatar {
return setting.GravatarSource + HashEmail(email) + "?d=identicon"
}
return DefaultAvatarLink()
return SizedAvatarLink(email, DefaultAvatarSize)
}
// Seconds-based time units

View File

@@ -1,11 +1,13 @@
package base
import (
"net/url"
"os"
"testing"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/i18n"
macaroni18n "github.com/go-macaron/i18n"
"github.com/stretchr/testify/assert"
@@ -126,16 +128,40 @@ func TestHashEmail(t *testing.T) {
)
}
func TestAvatarLink(t *testing.T) {
const gravatarSource = "https://secure.gravatar.com/avatar/"
func disableGravatar() {
setting.EnableFederatedAvatar = false
setting.LibravatarService = nil
setting.DisableGravatar = true
}
assert.Equal(t, "/img/avatar_default.png", AvatarLink(""))
func enableGravatar(t *testing.T) {
setting.DisableGravatar = false
var err error
setting.GravatarSourceURL, err = url.Parse(gravatarSource)
assert.NoError(t, err)
}
func TestSizedAvatarLink(t *testing.T) {
disableGravatar()
assert.Equal(t, "/img/avatar_default.png",
SizedAvatarLink("gitea@example.com", 100))
enableGravatar(t)
assert.Equal(t,
"353cbad9b58e69c96154ad99f92bedc7?d=identicon",
"https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100",
SizedAvatarLink("gitea@example.com", 100),
)
}
func TestAvatarLink(t *testing.T) {
disableGravatar()
assert.Equal(t, "/img/avatar_default.png", AvatarLink("gitea@example.com"))
enableGravatar(t)
assert.Equal(t,
"https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon",
AvatarLink("gitea@example.com"),
)
}

View File

@@ -618,7 +618,11 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
if refType == RepoRefLegacy {
// redirect from old URL scheme to new URL scheme
ctx.Redirect(path.Join(setting.AppSubURL, strings.TrimSuffix(ctx.Req.URL.String(), ctx.Params("*")), ctx.Repo.BranchNameSubURL()))
ctx.Redirect(path.Join(
setting.AppSubURL,
strings.TrimSuffix(ctx.Req.URL.String(), ctx.Params("*")),
ctx.Repo.BranchNameSubURL(),
ctx.Repo.TreePath))
return
}
}

View File

@@ -208,7 +208,7 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
var closeError error
var waitError error
args := []string{"-F", from, "-i"}
args := []string{"-f", from, "-i"}
args = append(args, setting.MailService.SendmailArgs...)
args = append(args, to...)
log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args)

View File

@@ -325,6 +325,7 @@ var (
// Picture settings
AvatarUploadPath string
GravatarSource string
GravatarSourceURL *url.URL
DisableGravatar bool
EnableFederatedAvatar bool
LibravatarService *libravatar.Libravatar
@@ -1026,18 +1027,22 @@ func NewContext() {
if DisableGravatar {
EnableFederatedAvatar = false
}
if EnableFederatedAvatar || !DisableGravatar {
GravatarSourceURL, err = url.Parse(GravatarSource)
if err != nil {
log.Fatal(4, "Failed to parse Gravatar URL(%s): %v",
GravatarSource, err)
}
}
if EnableFederatedAvatar {
LibravatarService = libravatar.New()
parts := strings.Split(GravatarSource, "/")
if len(parts) >= 3 {
if parts[0] == "https:" {
LibravatarService.SetUseHTTPS(true)
LibravatarService.SetSecureFallbackHost(parts[2])
} else {
LibravatarService.SetUseHTTPS(false)
LibravatarService.SetFallbackHost(parts[2])
}
if GravatarSourceURL.Scheme == "https" {
LibravatarService.SetUseHTTPS(true)
LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host)
} else {
LibravatarService.SetUseHTTPS(false)
LibravatarService.SetFallbackHost(GravatarSourceURL.Host)
}
}
@@ -1392,7 +1397,7 @@ func newSessionService() {
SessionConfig.Provider = Cfg.Section("session").Key("PROVIDER").In("memory",
[]string{"memory", "file", "redis", "mysql"})
SessionConfig.ProviderConfig = strings.Trim(Cfg.Section("session").Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ")
if !filepath.IsAbs(SessionConfig.ProviderConfig) {
if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)
}
SessionConfig.CookieName = Cfg.Section("session").Key("COOKIE_NAME").MustString("i_like_gitea")

View File

@@ -9,6 +9,7 @@ import (
"container/list"
"encoding/json"
"fmt"
"html"
"html/template"
"mime"
"path/filepath"
@@ -162,6 +163,7 @@ func NewFuncMap() []template.FuncMap {
"UnescapeLocale": func(str string) string {
return strings.NewReplacer("\\;", ";", "\\#", "#").Replace(str)
},
"Escape": Escape,
}}
}
@@ -180,6 +182,11 @@ func Str2html(raw string) template.HTML {
return template.HTML(markup.Sanitize(raw))
}
// Escape escapes a HTML string
func Escape(raw string) string {
return html.EscapeString(raw)
}
// List traversings the list
func List(l *list.List) chan interface{} {
e := l.Front()

View File

@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/context"
"github.com/go-macaron/session"
"github.com/stretchr/testify/assert"
macaron "gopkg.in/macaron.v1"
)
@@ -33,6 +34,9 @@ func MockContext(t *testing.T) *context.Context {
macaronContext.Render = &mockRender{ResponseWriter: macaronContext.Resp}
return &context.Context{
Context: macaronContext,
Flash: &session.Flash{
Values: make(url.Values),
},
}
}

48
modules/util/sanitize.go Normal file
View File

@@ -0,0 +1,48 @@
// Copyright 2017 The Gitea 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 util
import (
"net/url"
"strings"
)
// urlSafeError wraps an error whose message may contain a sensitive URL
type urlSafeError struct {
err error
unsanitizedURL string
}
func (err urlSafeError) Error() string {
return SanitizeMessage(err.err.Error(), err.unsanitizedURL)
}
// URLSanitizedError returns the sanitized version an error whose message may
// contain a sensitive URL
func URLSanitizedError(err error, unsanitizedURL string) error {
return urlSafeError{err: err, unsanitizedURL: unsanitizedURL}
}
// SanitizeMessage sanitizes a message which may contains a sensitive URL
func SanitizeMessage(message, unsanitizedURL string) string {
sanitizedURL := SanitizeURLCredentials(unsanitizedURL, true)
return strings.Replace(message, unsanitizedURL, sanitizedURL, -1)
}
// SanitizeURLCredentials sanitizes a url, either removing user credentials
// or replacing them with a placeholder.
func SanitizeURLCredentials(unsanitizedURL string, usePlaceholder bool) string {
u, err := url.Parse(unsanitizedURL)
if err != nil {
// don't log the error, since it might contain unsanitized URL.
return "(unparsable url)"
}
if u.User != nil && usePlaceholder {
u.User = url.User("<credentials>")
} else {
u.User = nil
}
return u.String()
}

View File

@@ -9,8 +9,6 @@ import (
"net/http"
"strings"
api "code.gitea.io/sdk/gitea"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
@@ -18,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/convert"
api "code.gitea.io/sdk/gitea"
)
// Search repositories via options
@@ -322,12 +321,13 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
RemoteAddr: remoteAddr,
})
if err != nil {
err = util.URLSanitizedError(err, remoteAddr)
if repo != nil {
if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
log.Error(4, "DeleteRepository: %v", errDelete)
}
}
ctx.Error(500, "MigrateRepository", models.HandleCloneUserCredentials(err.Error(), true))
ctx.Error(500, "MigrateRepository", err)
return
}

View File

@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/utils"
)
const (
@@ -76,11 +77,7 @@ func TeamsAction(ctx *context.Context) {
ctx.Error(404)
return
}
uname := ctx.Query("uname")
// uname may be formatted as "username (fullname)"
if strings.Contains(uname, "(") && strings.HasSuffix(uname, ")") {
uname = strings.TrimSpace(strings.Split(uname, "(")[0])
}
uname := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("uname")))
var u *models.User
u, err = models.GetUserByName(uname)
if err != nil {

View File

@@ -191,6 +191,7 @@ func NewReleasePost(ctx *context.Context, form auth.NewReleaseForm) {
rel.Title = form.Title
rel.Note = form.Content
rel.Target = form.Target
rel.IsDraft = len(form.Draft) > 0
rel.IsPrerelease = form.Prerelease
rel.PublisherID = ctx.User.ID

View File

@@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
const (
@@ -232,6 +233,9 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
return
}
// remoteAddr may contain credentials, so we sanitize it
err = util.URLSanitizedError(err, remoteAddr)
if repo != nil {
if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
log.Error(4, "DeleteRepository: %v", errDelete)
@@ -241,11 +245,11 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
if strings.Contains(err.Error(), "Authentication failed") ||
strings.Contains(err.Error(), "could not read Username") {
ctx.Data["Err_Auth"] = true
ctx.RenderWithErr(ctx.Tr("form.auth_failed", models.HandleCloneUserCredentials(err.Error(), true)), tplMigrate, &form)
ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tplMigrate, &form)
return
} else if strings.Contains(err.Error(), "fatal:") {
ctx.Data["Err_CloneAddr"] = true
ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", models.HandleCloneUserCredentials(err.Error(), true)), tplMigrate, &form)
ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tplMigrate, &form)
return
}

View File

@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/utils"
)
const (
@@ -366,7 +367,7 @@ func Collaboration(ctx *context.Context) {
// CollaborationPost response for actions for a collaboration of a repository
func CollaborationPost(ctx *context.Context) {
name := strings.ToLower(ctx.Query("collaborator"))
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("collaborator")))
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
return

View File

@@ -223,7 +223,9 @@ func SettingsSecurityPost(ctx *context.Context, form auth.ChangePasswordForm) {
return
}
if ctx.User.IsPasswordSet() && !ctx.User.ValidatePassword(form.OldPassword) {
if len(form.Password) < setting.MinPasswordLength {
ctx.Flash.Error(ctx.Tr("auth.password_too_short", setting.MinPasswordLength))
} else if ctx.User.IsPasswordSet() && !ctx.User.ValidatePassword(form.OldPassword) {
ctx.Flash.Error(ctx.Tr("settings.password_incorrect"))
} else if form.Password != form.Retype {
ctx.Flash.Error(ctx.Tr("form.password_not_match"))

View File

@@ -0,0 +1,56 @@
// Copyright 2017 The Gitea 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 user
/*func TestChangePassword(t *testing.T) {
oldPassword := "password"
setting.MinPasswordLength = 6
for _, req := range []struct {
OldPassword string
NewPassword string
Retype string
Message string
}{
{
OldPassword: oldPassword,
NewPassword: "123456",
Retype: "123456",
Message: "",
},
{
OldPassword: oldPassword,
NewPassword: "12345",
Retype: "12345",
Message: "auth.password_too_short",
},
{
OldPassword: "12334",
NewPassword: "123456",
Retype: "123456",
Message: "settings.password_incorrect",
},
{
OldPassword: oldPassword,
NewPassword: "123456",
Retype: "12345",
Message: "form.password_not_match",
},
} {
models.PrepareTestEnv(t)
ctx := test.MockContext(t, "user/settings/security")
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
SettingsSecurityPost(ctx, auth.ChangePasswordForm{
OldPassword: req.OldPassword,
Password: req.NewPassword,
Retype: req.Retype,
})
assert.EqualValues(t, req.Message, ctx.Flash.ErrorMsg)
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
}
}*/

17
routers/utils/utils.go Normal file
View File

@@ -0,0 +1,17 @@
// Copyright 2017 The Gitea 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 utils
import (
"strings"
)
// RemoveUsernameParameterSuffix returns the username parameter without the (fullname) suffix - leaving just the username
func RemoveUsernameParameterSuffix(name string) string {
if index := strings.Index(name, " ("); index >= 0 {
name = name[:index]
}
return name
}

View File

@@ -0,0 +1,17 @@
// Copyright 2017 The Gitea 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 utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRemoveUsernameParameterSuffix(t *testing.T) {
assert.Equal(t, "foobar", RemoveUsernameParameterSuffix("foobar (Foo Bar)"))
assert.Equal(t, "foobar", RemoveUsernameParameterSuffix("foobar"))
assert.Equal(t, "", RemoveUsernameParameterSuffix(""))
}

View File

@@ -99,7 +99,7 @@
<dt>{{.i18n.Tr "admin.config.db_user"}}</dt>
<dd>{{if .DbCfg.User}}{{.DbCfg.User}}{{else}}-{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.db_ssl_mode"}}</dt>
<dd><i class="fa fa{{if .DbCfg.SSLMode}}-check{{end}}-square-o"></i> {{.i18n.Tr "admin.config.db_ssl_mode_helper"}}</dd>
<dd>{{if .DbCfg.SSLMode}}{{.DbCfg.SSLMode}}{{else}}-{{end}} {{.i18n.Tr "admin.config.db_ssl_mode_helper"}}</dd>
<dt>{{.i18n.Tr "admin.config.db_path"}}</dt>
<dd>{{if .DbCfg.Path}}{{.DbCfg.Path}}{{else}}-{{end}} {{.i18n.Tr "admin.config.db_path_helper"}}</dd>
</dl>

View File

@@ -2,7 +2,7 @@
{{range .Repos}}
<div class="item">
<div class="ui header">
<a class="name" href="{{AppSubUrl}}/{{if .Owner}}{{.Owner.Name}}{{else if $.Org}}{{$.Org.Name}}{{else}}{{$.Owner.Name}}{{end}}/{{.Name}}">{{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}}</a>
<a class="name" href="{{.HTMLURL}}">{{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}}</a>
{{if .IsPrivate}}
<span class="text gold"><i class="octicon octicon-lock"></i></span>
{{else if .IsFork}}

View File

@@ -3,7 +3,7 @@
<div class="ui vertically grid head">
<div class="column">
<div class="ui header">
<img class="ui image" src="{{.RelAvatarLink}}?s=100">
<img class="ui image" src="{{.SizedRelAvatarLink 100}}">
<span class="text thin grey"><a href="{{.HomeLink}}">{{.DisplayName}}</a></span>
<div class="ui right">

View File

@@ -3,7 +3,7 @@
<div class="ui container">
<div class="ui grid">
<div class="ui sixteen wide column">
<img class="ui left" id="org-avatar" src="{{.Org.RelAvatarLink}}?s=140"/>
<img class="ui left" id="org-avatar" src="{{.Org.SizedRelAvatarLink 140}}"/>
<div id="org-info">
<div class="ui header">
{{.Org.DisplayName}}

View File

@@ -8,7 +8,7 @@
{{range .Members}}
<div class="item ui grid">
<div class="ui one wide column">
<img class="ui avatar" src="{{.RelAvatarLink}}?s=48">
<img class="ui avatar" src="{{.SizedRelAvatarLink 48}}">
</div>
<div class="ui three wide column">
<div class="meta"><a href="{{.HomeLink}}">{{.Name}}</a></div>

View File

@@ -62,7 +62,7 @@
{{end}}
{{if .Repository.UnitEnabled $.UnitTypeExternalTracker}}
<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues">
<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues" target="_blank">
<i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} </span>
</a>
{{end}}

View File

@@ -172,7 +172,7 @@
<a class="title has-emoji" href="{{$.Link}}/{{.Index}}">{{.Title}}</a>
{{if .Ref}}
<a class="ui label" href="{{$.RepoLink}}/src/commit/{{.Ref}}">{{.Ref}}</a>
<a class="ui label" href="{{$.RepoLink}}/src/branch/{{.Ref}}">{{.Ref}}</a>
{{end}}
{{range .Labels}}
<a class="ui label" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}">{{.Name}}</a>

View File

@@ -96,7 +96,7 @@
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | UnescapeLocale | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | UnescapeLocale | Safe}}{{end}}</span>
{{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|Escape) $createdStr | UnescapeLocale | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|Escape) $createdStr | UnescapeLocale | Safe}}{{end}}</span>
</div>
{{end}}
{{else if eq .Type 8}}
@@ -106,7 +106,7 @@
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{if gt .OldMilestoneID 0}}{{if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.change_milestone_at" .OldMilestone.Name .Milestone.Name $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_milestone_at" .OldMilestone.Name $createdStr | Safe}}{{end}}{{else if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.add_milestone_at" .Milestone.Name $createdStr | Safe}}{{end}}</span>
{{if gt .OldMilestoneID 0}}{{if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.change_milestone_at" (.OldMilestone.Name|Escape) (.Milestone.Name|Escape) $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_milestone_at" (.OldMilestone.Name|Escape) $createdStr | Safe}}{{end}}{{else if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.add_milestone_at" (.Milestone.Name|Escape) $createdStr | Safe}}{{end}}</span>
</div>
{{else if eq .Type 9}}
<div class="event">
@@ -124,23 +124,23 @@
{{else if eq .Type 10}}
<div class="event">
<span class="octicon octicon-primitive-dot"></span>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{$.i18n.Tr "repo.issues.change_title_at" (.OldTitle|Escape) (.NewTitle|Escape) $createdStr | Safe}}
</span>
</div>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{$.i18n.Tr "repo.issues.change_title_at" .OldTitle .NewTitle $createdStr | Safe}}
</span>
{{else if eq .Type 11}}
<div class="event">
<span class="octicon octicon-primitive-dot"></span>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{$.i18n.Tr "repo.issues.delete_branch_at" .CommitSHA $createdStr | Safe}}
</span>
</div>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{$.i18n.Tr "repo.issues.delete_branch_at" .CommitSHA $createdStr | Safe}}
</span>
{{else if eq .Type 12}}
<div class="event">
<span class="octicon octicon-primitive-dot"></span>

View File

@@ -41,9 +41,11 @@
<a href="{{$.RepoLink}}/src/tag/{{.TagName}}" rel="nofollow"><i class="tag icon"></i> {{.TagName}}</a>
</h4>
<div class="download">
{{if $.Repository.UnitEnabled $.UnitTypeCode}}
<a href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow"><i class="code icon"></i> {{ShortSha .Sha1}}</a>
<a href="{{$.RepoLink}}/archive/{{.TagName}}.zip" rel="nofollow"><i class="octicon octicon-file-zip"></i> ZIP</a>
<a href="{{$.RepoLink}}/archive/{{.TagName}}.tar.gz"><i class="octicon octicon-file-zip"></i> TAR.GZ</a>
{{end}}
</div>
{{else}}
<h3>
@@ -64,12 +66,14 @@
<div class="download">
<h2>{{$.i18n.Tr "repo.release.downloads"}}</h2>
<ul class="list">
{{if $.Repository.UnitEnabled $.UnitTypeCode}}
<li>
<a href="{{$.RepoLink}}/archive/{{.TagName}}.zip" rel="nofollow"><i class="octicon octicon-file-zip"></i> {{$.i18n.Tr "repo.release.source_code"}} (ZIP)</a>
</li>
<li>
<a href="{{$.RepoLink}}/archive/{{.TagName}}.tar.gz"><i class="octicon octicon-file-zip"></i> {{$.i18n.Tr "repo.release.source_code"}} (TAR.GZ)</a>
</li>
{{end}}
{{if .Attachments}}
{{range .Attachments}}
<li>

View File

@@ -14,7 +14,7 @@
</div>
{{if .Keyword}}
<h3>
{{.i18n.Tr "repo.search.results" .Keyword .RepoLink .RepoName | Str2html}}
{{.i18n.Tr "repo.search.results" (.Keyword|Escape) .RepoLink .RepoName | Str2html }}
</h3>
<div class="repository search">
{{range $result := .SearchResults}}

View File

@@ -98,7 +98,7 @@
{{.i18n.Tr "repo.wiki.delete_page_button"}}
</div>
<div class="content">
<p>{{.i18n.Tr "repo.wiki.delete_page_notice_1" $title | Safe}}</p>
<p>{{.i18n.Tr "repo.wiki.delete_page_notice_1" ($title|Escape) | Safe}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>

View File

@@ -6,11 +6,11 @@
<div class="ui card">
{{if eq .SignedUserName .Owner.Name}}
<a class="image poping up" href="{{AppSubUrl}}/user/settings/avatar" id="profile-avatar" data-content="{{.i18n.Tr "user.change_avatar"}}" data-variation="inverted tiny" data-position="bottom center">
<img src="{{.Owner.RelAvatarLink}}?s=290" title="{{.Owner.Name}}"/>
<img src="{{.Owner.SizedRelAvatarLink 290}}" title="{{.Owner.Name}}"/>
</a>
{{else}}
<span class="image">
<img src="{{.Owner.RelAvatarLink}}?s=290" title="{{.Owner.Name}}"/>
<img src="{{.Owner.SizedRelAvatarLink 290}}" title="{{.Owner.Name}}"/>
</span>
{{end}}
<div class="content">