1
1
mirror of https://github.com/go-gitea/gitea synced 2025-12-07 13:28:25 +00:00

Merge branch 'master' into fix-6409

This commit is contained in:
Nicolas Gourdon
2019-07-11 15:38:13 +02:00
1152 changed files with 117647 additions and 29965 deletions
+2 -10
View File
@@ -214,10 +214,8 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool)
if err = models.UpdateAccessToken(token); err != nil {
log.Error("UpdateAccessToken: %v", err)
}
} else {
if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) {
log.Error("GetAccessTokenBySha: %v", err)
}
} else if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) {
log.Error("GetAccessTokenBySha: %v", err)
}
if u == nil {
@@ -301,12 +299,6 @@ func GetInclude(field reflect.StructField) string {
return getRuleBody(field, "Include(")
}
// FIXME: struct contains a struct
func validateStruct(obj interface{}) binding.Errors {
return nil
}
func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaron.Locale) binding.Errors {
if errs.Len() == 0 {
return errs
+1 -2
View File
@@ -220,8 +220,7 @@ func GetDefaultProfileURL(provider string) string {
// GetDefaultEmailURL return the default email url for the given provider
func GetDefaultEmailURL(provider string) string {
switch provider {
case "github":
if provider == "github" {
return github.EmailURL
}
return ""
+1 -1
View File
@@ -39,7 +39,7 @@ func TestTimedDiscoveryCache(t *testing.T) {
t.Errorf("Expected nil, got %v", di)
}
// Sleep one second and try retrive again
// Sleep one second and try retrieve again
time.Sleep(1 * time.Second)
if di := dc.Get("foo"); di != nil {
+4 -2
View File
@@ -23,6 +23,7 @@ type InstallForm struct {
DbPasswd string
DbName string
SSLMode string
Charset string `binding:"Required;In(utf8,utf8mb4)"`
DbPath string
AppName string `binding:"Required" locale:"install.app_name"`
@@ -78,7 +79,7 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin
type RegisterForm struct {
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"Required;MaxSize(255)"`
Password string `binding:"MaxSize(255)"`
Retype string
GRecaptchaResponse string `form:"g-recaptcha-response"`
}
@@ -128,6 +129,7 @@ func (f *MustChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Err
// SignInForm form for signing in with user/password
type SignInForm struct {
UserName string `binding:"Required;MaxSize(254)"`
// TODO remove required from password for SecondFactorAuthentication
Password string `binding:"Required;MaxSize(255)"`
Remember bool
}
@@ -252,7 +254,7 @@ func (f UpdateThemeForm) IsThemeExists() bool {
var exists bool
for _, v := range setting.UI.Themes {
if strings.ToLower(v) == strings.ToLower(f.Theme) {
if strings.EqualFold(v, f.Theme) {
exists = true
break
}
+50
View File
@@ -5,13 +5,20 @@
package avatar
import (
"bytes"
"fmt"
"image"
"image/color/palette"
// Enable PNG support:
_ "image/png"
"math/rand"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/issue9/identicon"
"github.com/nfnt/resize"
"github.com/oliamb/cutter"
)
// AvatarSize returns avatar's size
@@ -42,3 +49,46 @@ func RandomImageSize(size int, data []byte) (image.Image, error) {
func RandomImage(data []byte) (image.Image, error) {
return RandomImageSize(AvatarSize, data)
}
// Prepare accepts a byte slice as input, validates it contains an image of an
// acceptable format, and crops and resizes it appropriately.
func Prepare(data []byte) (*image.Image, error) {
imgCfg, _, err := image.DecodeConfig(bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("DecodeConfig: %v", err)
}
if imgCfg.Width > setting.AvatarMaxWidth {
return nil, fmt.Errorf("Image width is too large: %d > %d", imgCfg.Width, setting.AvatarMaxWidth)
}
if imgCfg.Height > setting.AvatarMaxHeight {
return nil, fmt.Errorf("Image height is too large: %d > %d", imgCfg.Height, setting.AvatarMaxHeight)
}
img, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("Decode: %v", err)
}
if imgCfg.Width != imgCfg.Height {
var newSize, ax, ay int
if imgCfg.Width > imgCfg.Height {
newSize = imgCfg.Height
ax = (imgCfg.Width - imgCfg.Height) / 2
} else {
newSize = imgCfg.Width
ay = (imgCfg.Height - imgCfg.Width) / 2
}
img, err = cutter.Crop(img, cutter.Config{
Width: newSize,
Height: newSize,
Anchor: image.Point{ax, ay},
})
if err != nil {
return nil, err
}
}
img = resize.Resize(AvatarSize, AvatarSize, img, resize.NearestNeighbor)
return &img, nil
}
+49
View File
@@ -5,8 +5,11 @@
package avatar
import (
"io/ioutil"
"testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
@@ -17,3 +20,49 @@ func Test_RandomImage(t *testing.T) {
_, err = RandomImageSize(0, []byte("gogs@local"))
assert.Error(t, err)
}
func Test_PrepareWithPNG(t *testing.T) {
setting.AvatarMaxWidth = 4096
setting.AvatarMaxHeight = 4096
data, err := ioutil.ReadFile("testdata/avatar.png")
assert.NoError(t, err)
imgPtr, err := Prepare(data)
assert.NoError(t, err)
assert.Equal(t, 290, (*imgPtr).Bounds().Max.X)
assert.Equal(t, 290, (*imgPtr).Bounds().Max.Y)
}
func Test_PrepareWithJPEG(t *testing.T) {
setting.AvatarMaxWidth = 4096
setting.AvatarMaxHeight = 4096
data, err := ioutil.ReadFile("testdata/avatar.jpeg")
assert.NoError(t, err)
imgPtr, err := Prepare(data)
assert.NoError(t, err)
assert.Equal(t, 290, (*imgPtr).Bounds().Max.X)
assert.Equal(t, 290, (*imgPtr).Bounds().Max.Y)
}
func Test_PrepareWithInvalidImage(t *testing.T) {
setting.AvatarMaxWidth = 5
setting.AvatarMaxHeight = 5
_, err := Prepare([]byte{})
assert.EqualError(t, err, "DecodeConfig: image: unknown format")
}
func Test_PrepareWithInvalidImageSize(t *testing.T) {
setting.AvatarMaxWidth = 5
setting.AvatarMaxHeight = 5
data, err := ioutil.ReadFile("testdata/avatar.png")
assert.NoError(t, err)
_, err = Prepare(data)
assert.EqualError(t, err, "Image width is too large: 10 > 5")
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

-3
View File
@@ -4,9 +4,6 @@
package base
// DocURL api doc url
const DocURL = "https://godoc.org/github.com/go-gitea/go-sdk/gitea"
type (
// TplName template relative path type
TplName string
+25 -35
View File
@@ -44,21 +44,21 @@ var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'}
// EncodeMD5 encodes string to md5 hex value.
func EncodeMD5(str string) string {
m := md5.New()
m.Write([]byte(str))
_, _ = m.Write([]byte(str))
return hex.EncodeToString(m.Sum(nil))
}
// EncodeSha1 string to sha1 hex value.
func EncodeSha1(str string) string {
h := sha1.New()
h.Write([]byte(str))
_, _ = h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
// EncodeSha256 string to sha1 hex value.
func EncodeSha256(str string) string {
h := sha256.New()
h.Write([]byte(str))
_, _ = h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
@@ -193,7 +193,7 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string
// create sha1 encode string
sh := sha1.New()
sh.Write([]byte(data + setting.SecretKey + startStr + endStr + com.ToStr(minutes)))
_, _ = sh.Write([]byte(data + setting.SecretKey + startStr + endStr + com.ToStr(minutes)))
encoded := hex.EncodeToString(sh.Sum(nil))
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
@@ -425,16 +425,6 @@ const (
EByte = PByte * 1024
)
var bytesSizeTable = map[string]uint64{
"b": Byte,
"kb": KByte,
"mb": MByte,
"gb": GByte,
"tb": TByte,
"pb": PByte,
"eb": EByte,
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
@@ -465,41 +455,41 @@ func Subtract(left interface{}, right interface{}) interface{} {
var rleft, rright int64
var fleft, fright float64
var isInt = true
switch left := left.(type) {
switch v := left.(type) {
case int:
rleft = int64(left)
rleft = int64(v)
case int8:
rleft = int64(left)
rleft = int64(v)
case int16:
rleft = int64(left)
rleft = int64(v)
case int32:
rleft = int64(left)
rleft = int64(v)
case int64:
rleft = left
rleft = v
case float32:
fleft = float64(left)
fleft = float64(v)
isInt = false
case float64:
fleft = left
fleft = v
isInt = false
}
switch right := right.(type) {
switch v := right.(type) {
case int:
rright = int64(right)
rright = int64(v)
case int8:
rright = int64(right)
rright = int64(v)
case int16:
rright = int64(right)
rright = int64(v)
case int32:
rright = int64(right)
rright = int64(v)
case int64:
rright = right
rright = v
case float32:
fright = float64(right)
fright = float64(v)
isInt = false
case float64:
fright = right
fright = v
isInt = false
}
@@ -582,27 +572,27 @@ func IsTextFile(data []byte) bool {
if len(data) == 0 {
return true
}
return strings.Index(http.DetectContentType(data), "text/") != -1
return strings.Contains(http.DetectContentType(data), "text/")
}
// IsImageFile detects if data is an image format
func IsImageFile(data []byte) bool {
return strings.Index(http.DetectContentType(data), "image/") != -1
return strings.Contains(http.DetectContentType(data), "image/")
}
// IsPDFFile detects if data is a pdf format
func IsPDFFile(data []byte) bool {
return strings.Index(http.DetectContentType(data), "application/pdf") != -1
return strings.Contains(http.DetectContentType(data), "application/pdf")
}
// IsVideoFile detects if data is an video format
func IsVideoFile(data []byte) bool {
return strings.Index(http.DetectContentType(data), "video/") != -1
return strings.Contains(http.DetectContentType(data), "video/")
}
// IsAudioFile detects if data is an video format
func IsAudioFile(data []byte) bool {
return strings.Index(http.DetectContentType(data), "audio/") != -1
return strings.Contains(http.DetectContentType(data), "audio/")
}
// EntryIcon returns the octicon class for displaying files/directories
+15 -16
View File
@@ -287,40 +287,39 @@ func TestHtmlTimeSince(t *testing.T) {
}
func TestFileSize(t *testing.T) {
var size int64
size = 512
var size int64 = 512
assert.Equal(t, "512B", FileSize(size))
size = size * 1024
size *= 1024
assert.Equal(t, "512KB", FileSize(size))
size = size * 1024
size *= 1024
assert.Equal(t, "512MB", FileSize(size))
size = size * 1024
size *= 1024
assert.Equal(t, "512GB", FileSize(size))
size = size * 1024
size *= 1024
assert.Equal(t, "512TB", FileSize(size))
size = size * 1024
size *= 1024
assert.Equal(t, "512PB", FileSize(size))
size = size * 4
size *= 4
assert.Equal(t, "2.0EB", FileSize(size))
}
func TestSubtract(t *testing.T) {
toFloat64 := func(n interface{}) float64 {
switch n := n.(type) {
switch v := n.(type) {
case int:
return float64(n)
return float64(v)
case int8:
return float64(n)
return float64(v)
case int16:
return float64(n)
return float64(v)
case int32:
return float64(n)
return float64(v)
case int64:
return float64(n)
return float64(v)
case float32:
return float64(n)
return float64(v)
case float64:
return n
return v
default:
return 0.0
}
+9 -3
View File
@@ -43,7 +43,10 @@ func GetInt(key string, getFunc func() (int, error)) (int, error) {
if value, err = getFunc(); err != nil {
return value, err
}
conn.Put(key, value, int64(setting.CacheService.TTL.Seconds()))
err = conn.Put(key, value, int64(setting.CacheService.TTL.Seconds()))
if err != nil {
return 0, err
}
}
switch value := conn.Get(key).(type) {
case int:
@@ -72,7 +75,10 @@ func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
if value, err = getFunc(); err != nil {
return value, err
}
conn.Put(key, value, int64(setting.CacheService.TTL.Seconds()))
err = conn.Put(key, value, int64(setting.CacheService.TTL.Seconds()))
if err != nil {
return 0, err
}
}
switch value := conn.Get(key).(type) {
case int64:
@@ -93,5 +99,5 @@ func Remove(key string) {
if conn == nil {
return
}
conn.Delete(key)
_ = conn.Delete(key)
}
+43 -25
View File
@@ -8,13 +8,11 @@ package context
import (
"fmt"
"net/url"
"path"
"strings"
"github.com/go-macaron/csrf"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -76,27 +74,53 @@ func (ctx *APIContext) Error(status int, title string, obj interface{}) {
ctx.JSON(status, APIError{
Message: message,
URL: base.DocURL,
URL: setting.API.SwaggerURL,
})
}
func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
page := NewPagination(total, pageSize, curPage, 0)
paginater := page.Paginater
links := make([]string, 0, 4)
if paginater.HasNext() {
u := *curURL
queries := u.Query()
queries.Set("page", fmt.Sprintf("%d", paginater.Next()))
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"next\"", setting.AppURL, u.RequestURI()[1:]))
}
if !paginater.IsLast() {
u := *curURL
queries := u.Query()
queries.Set("page", fmt.Sprintf("%d", paginater.TotalPages()))
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"last\"", setting.AppURL, u.RequestURI()[1:]))
}
if !paginater.IsFirst() {
u := *curURL
queries := u.Query()
queries.Set("page", "1")
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"first\"", setting.AppURL, u.RequestURI()[1:]))
}
if paginater.HasPrevious() {
u := *curURL
queries := u.Query()
queries.Set("page", fmt.Sprintf("%d", paginater.Previous()))
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"prev\"", setting.AppURL, u.RequestURI()[1:]))
}
return links
}
// SetLinkHeader sets pagination link header by given total number and page size.
func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
page := NewPagination(total, pageSize, ctx.QueryInt("page"), 0)
paginater := page.Paginater
links := make([]string, 0, 4)
if paginater.HasNext() {
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"next\"", setting.AppURL, ctx.Req.URL.Path[1:], paginater.Next()))
}
if !paginater.IsLast() {
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"last\"", setting.AppURL, ctx.Req.URL.Path[1:], paginater.TotalPages()))
}
if !paginater.IsFirst() {
links = append(links, fmt.Sprintf("<%s%s?page=1>; rel=\"first\"", setting.AppURL, ctx.Req.URL.Path[1:]))
}
if paginater.HasPrevious() {
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"prev\"", setting.AppURL, ctx.Req.URL.Path[1:], paginater.Previous()))
}
links := genAPILinks(ctx.Req.URL, total, pageSize, ctx.QueryInt("page"))
if len(links) > 0 {
ctx.Header().Set("Link", strings.Join(links, ","))
@@ -180,15 +204,9 @@ func (ctx *APIContext) NotFound(objs ...interface{}) {
}
}
u, err := url.Parse(setting.AppURL)
if err != nil {
ctx.Error(500, "Invalid AppURL", err)
return
}
u.Path = path.Join(u.Path, "api", "swagger")
ctx.JSON(404, map[string]interface{}{
"message": message,
"documentation_url": u.String(),
"documentation_url": setting.API.SwaggerURL,
"errors": errors,
})
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2019 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 context
import (
"net/url"
"strconv"
"testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
func TestGenAPILinks(t *testing.T) {
setting.AppURL = "http://localhost:3000/"
var kases = map[string][]string{
"api/v1/repos/jerrykan/example-repo/issues?state=all": {
`<http://localhost:3000/api/v1/repos/jerrykan/example-repo/issues?page=2&state=all>; rel="next"`,
`<http://localhost:3000/api/v1/repos/jerrykan/example-repo/issues?page=5&state=all>; rel="last"`,
},
"api/v1/repos/jerrykan/example-repo/issues?state=all&page=1": {
`<http://localhost:3000/api/v1/repos/jerrykan/example-repo/issues?page=2&state=all>; rel="next"`,
`<http://localhost:3000/api/v1/repos/jerrykan/example-repo/issues?page=5&state=all>; rel="last"`,
},
"api/v1/repos/jerrykan/example-repo/issues?state=all&page=2": {
`<http://localhost:3000/api/v1/repos/jerrykan/example-repo/issues?page=3&state=all>; rel="next"`,
`<http://localhost:3000/api/v1/repos/jerrykan/example-repo/issues?page=5&state=all>; rel="last"`,
`<http://localhost:3000/api/v1/repos/jerrykan/example-repo/issues?page=1&state=all>; rel="first"`,
`<http://localhost:3000/api/v1/repos/jerrykan/example-repo/issues?page=1&state=all>; rel="prev"`,
},
"api/v1/repos/jerrykan/example-repo/issues?state=all&page=5": {
`<http://localhost:3000/api/v1/repos/jerrykan/example-repo/issues?page=1&state=all>; rel="first"`,
`<http://localhost:3000/api/v1/repos/jerrykan/example-repo/issues?page=4&state=all>; rel="prev"`,
},
}
for req, response := range kases {
u, err := url.Parse(setting.AppURL + req)
assert.NoError(t, err)
p := u.Query().Get("page")
curPage, _ := strconv.Atoi(p)
links := genAPILinks(u, 100, 20, curPage)
assert.EqualValues(t, links, response)
}
}
+10 -3
View File
@@ -130,7 +130,6 @@ func (ctx *Context) RedirectToFirst(location ...string) {
}
ctx.Redirect(setting.AppSubURL + "/")
return
}
// HTML calls Context.HTML and converts template name to string.
@@ -257,16 +256,23 @@ func Contexter() macaron.Handler {
branchName = repo.DefaultBranch
}
prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName))
appURL, _ := url.Parse(setting.AppURL)
insecure := ""
if appURL.Scheme == string(setting.HTTP) {
insecure = "--insecure "
}
c.Header().Set("Content-Type", "text/html")
c.WriteHeader(http.StatusOK)
c.Write([]byte(com.Expand(`<!doctype html>
_, _ = c.Write([]byte(com.Expand(`<!doctype html>
<html>
<head>
<meta name="go-import" content="{GoGetImport} git {CloneLink}">
<meta name="go-source" content="{GoGetImport} _ {GoDocDirectory} {GoDocFile}">
</head>
<body>
go get {GoGetImport}
go get {Insecure}{GoGetImport}
</body>
</html>
`, map[string]string{
@@ -274,6 +280,7 @@ func Contexter() macaron.Handler {
"CloneLink": models.ComposeHTTPSCloneURL(ownerName, repoName),
"GoDocDirectory": prefix + "{/dir}",
"GoDocFile": prefix + "{/dir}/{file}#L{line}",
"Insecure": insecure,
})))
return
}
+1 -1
View File
@@ -39,7 +39,7 @@ func (p *Pagination) AddParam(ctx *Context, paramKey string, ctxKey string) {
// GetParams returns the configured URL params
func (p *Pagination) GetParams() template.URL {
return template.URL(strings.Join(p.urlParams[:], "&"))
return template.URL(strings.Join(p.urlParams, "&"))
}
// SetDefaultParams sets common pagination params that are often used
+10 -9
View File
@@ -188,7 +188,10 @@ func RetrieveBaseRepo(ctx *Context, repo *models.Repository) {
// ComposeGoGetImport returns go-get-import meta content.
func ComposeGoGetImport(owner, repo string) string {
return path.Join(setting.Domain, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo))
/// setting.AppUrl is guaranteed to be parse as url
appURL, _ := url.Parse(setting.AppURL)
return path.Join(appURL.Host, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo))
}
// EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200
@@ -452,15 +455,13 @@ func RepoAssignment() macaron.Handler {
ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
ctx.Repo.PullRequest.Allowed = true
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
} else {
} else if repo.AllowsPulls() {
// Or, this is repository accepts pull requests between branches.
if repo.AllowsPulls() {
ctx.Data["BaseRepo"] = repo
ctx.Repo.PullRequest.BaseRepo = repo
ctx.Repo.PullRequest.Allowed = true
ctx.Repo.PullRequest.SameRepo = true
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
}
ctx.Data["BaseRepo"] = repo
ctx.Repo.PullRequest.BaseRepo = repo
ctx.Repo.PullRequest.Allowed = true
ctx.Repo.PullRequest.SameRepo = true
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
}
}
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
+123
View File
@@ -0,0 +1,123 @@
// Copyright 2019 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 git
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"code.gitea.io/gitea/modules/process"
)
// BlamePart represents block of blame - continuous lines with one sha
type BlamePart struct {
Sha string
Lines []string
}
// BlameReader returns part of file blame one by one
type BlameReader struct {
cmd *exec.Cmd
pid int64
output io.ReadCloser
scanner *bufio.Scanner
lastSha *string
}
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
// NextPart returns next part of blame (sequencial code lines with the same commit)
func (r *BlameReader) NextPart() (*BlamePart, error) {
var blamePart *BlamePart
scanner := r.scanner
if r.lastSha != nil {
blamePart = &BlamePart{*r.lastSha, make([]string, 0)}
}
for scanner.Scan() {
line := scanner.Text()
// Skip empty lines
if len(line) == 0 {
continue
}
lines := shaLineRegex.FindStringSubmatch(line)
if lines != nil {
sha1 := lines[1]
if blamePart == nil {
blamePart = &BlamePart{sha1, make([]string, 0)}
}
if blamePart.Sha != sha1 {
r.lastSha = &sha1
return blamePart, nil
}
} else if line[0] == '\t' {
code := line[1:]
blamePart.Lines = append(blamePart.Lines, code)
}
}
r.lastSha = nil
return blamePart, nil
}
// Close BlameReader - don't run NextPart after invoking that
func (r *BlameReader) Close() error {
process.GetManager().Remove(r.pid)
if err := r.cmd.Wait(); err != nil {
return fmt.Errorf("Wait: %v", err)
}
return nil
}
// CreateBlameReader creates reader for given repository, commit and file
func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) {
_, err := OpenRepository(repoPath)
if err != nil {
return nil, err
}
return createBlameReader(repoPath, GitExecutable, "blame", commitID, "--porcelain", "--", file)
}
func createBlameReader(dir string, command ...string) (*BlameReader, error) {
cmd := exec.Command(command[0], command[1:]...)
cmd.Dir = dir
cmd.Stderr = os.Stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("StdoutPipe: %v", err)
}
if err = cmd.Start(); err != nil {
return nil, fmt.Errorf("Start: %v", err)
}
pid := process.GetManager().Add(fmt.Sprintf("GetBlame [repo_path: %s]", dir), cmd)
scanner := bufio.NewScanner(stdout)
return &BlameReader{
cmd,
pid,
stdout,
scanner,
nil,
}, nil
}
+141
View File
@@ -0,0 +1,141 @@
// Copyright 2019 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 git
import (
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
)
const exampleBlame = `
4b92a6c2df28054ad766bc262f308db9f6066596 1 1 1
author Unknown
author-mail <joe2010xtmf@163.com>
author-time 1392833071
author-tz -0500
committer Unknown
committer-mail <joe2010xtmf@163.com>
committer-time 1392833071
committer-tz -0500
summary Add code of delete user
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
filename gogs.go
// Copyright 2014 The Gogs Authors. All rights reserved.
ce21ed6c3490cdfad797319cbb1145e2330a8fef 2 2 1
author Joubert RedRat
author-mail <eu+github@redrat.com.br>
author-time 1482322397
author-tz -0200
committer Lunny Xiao
committer-mail <xiaolunwen@gmail.com>
committer-time 1482322397
committer-tz +0800
summary Remove remaining Gogs reference on locales and cmd (#430)
previous 618407c018cdf668ceedde7454c42fb22ba422d8 main.go
filename main.go
// Copyright 2016 The Gitea Authors. All rights reserved.
4b92a6c2df28054ad766bc262f308db9f6066596 2 3 2
author Unknown
author-mail <joe2010xtmf@163.com>
author-time 1392833071
author-tz -0500
committer Unknown
committer-mail <joe2010xtmf@163.com>
committer-time 1392833071
committer-tz -0500
summary Add code of delete user
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
filename gogs.go
// Use of this source code is governed by a MIT-style
4b92a6c2df28054ad766bc262f308db9f6066596 3 4
author Unknown
author-mail <joe2010xtmf@163.com>
author-time 1392833071
author-tz -0500
committer Unknown
committer-mail <joe2010xtmf@163.com>
committer-time 1392833071
committer-tz -0500
summary Add code of delete user
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
filename gogs.go
// license that can be found in the LICENSE file.
e2aa991e10ffd924a828ec149951f2f20eecead2 6 6 2
author Lunny Xiao
author-mail <xiaolunwen@gmail.com>
author-time 1478872595
author-tz +0800
committer Sandro Santilli
committer-mail <strk@kbt.io>
committer-time 1478872595
committer-tz +0100
summary ask for go get from code.gitea.io/gitea and change gogs to gitea on main file (#146)
previous 5fc370e332171b8658caed771b48585576f11737 main.go
filename main.go
// Gitea (git with a cup of tea) is a painless self-hosted Git Service.
e2aa991e10ffd924a828ec149951f2f20eecead2 7 7
package main // import "code.gitea.io/gitea"
`
func TestReadingBlameOutput(t *testing.T) {
tempFile, err := ioutil.TempFile("", ".txt")
if err != nil {
panic(err)
}
defer tempFile.Close()
if _, err = tempFile.WriteString(exampleBlame); err != nil {
panic(err)
}
blameReader, err := createBlameReader("", "cat", tempFile.Name())
if err != nil {
panic(err)
}
defer blameReader.Close()
parts := []*BlamePart{
{
"4b92a6c2df28054ad766bc262f308db9f6066596",
[]string{
"// Copyright 2014 The Gogs Authors. All rights reserved.",
},
},
{
"ce21ed6c3490cdfad797319cbb1145e2330a8fef",
[]string{
"// Copyright 2016 The Gitea Authors. All rights reserved.",
},
},
{
"4b92a6c2df28054ad766bc262f308db9f6066596",
[]string{
"// Use of this source code is governed by a MIT-style",
"// license that can be found in the LICENSE file.",
"",
},
},
{
"e2aa991e10ffd924a828ec149951f2f20eecead2",
[]string{
"// Gitea (git with a cup of tea) is a painless self-hosted Git Service.",
"package main // import \"code.gitea.io/gitea\"",
},
},
nil,
}
for _, part := range parts {
actualPart, err := blameReader.NextPart()
if err != nil {
panic(err)
}
assert.Equal(t, part, actualPart)
}
}
+16 -3
View File
@@ -37,6 +37,19 @@ func (b *Blob) Name() string {
return b.name
}
// GetBlobContent Gets the content of the blob as raw text
func (b *Blob) GetBlobContent() (string, error) {
dataRc, err := b.DataAsync()
if err != nil {
return "", err
}
defer dataRc.Close()
buf := make([]byte, 1024)
n, _ := dataRc.Read(buf)
buf = buf[:n]
return string(buf), nil
}
// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string
func (b *Blob) GetBlobContentBase64() (string, error) {
dataRc, err := b.DataAsync()
@@ -50,12 +63,12 @@ func (b *Blob) GetBlobContentBase64() (string, error) {
go func() {
_, err := io.Copy(encoder, dataRc)
encoder.Close()
_ = encoder.Close()
if err != nil {
pw.CloseWithError(err)
_ = pw.CloseWithError(err)
} else {
pw.Close()
_ = pw.Close()
}
}()
+5
View File
@@ -12,6 +12,8 @@ import (
"os/exec"
"strings"
"time"
"code.gitea.io/gitea/modules/process"
)
var (
@@ -84,6 +86,9 @@ func (c *Command) RunInDirTimeoutEnvFullPipeline(env []string, timeout time.Dura
return err
}
pid := process.GetManager().Add(fmt.Sprintf("%s %s %s [repo_path: %s]", GitExecutable, c.name, strings.Join(c.args, " "), dir), cmd)
defer process.GetManager().Remove(pid)
if err := cmd.Wait(); err != nil {
return err
}
+3 -4
View File
@@ -133,7 +133,7 @@ func (c *Commit) ParentCount() int {
func isImageFile(data []byte) (string, bool) {
contentType := http.DetectContentType(data)
if strings.Index(contentType, "image/") != -1 {
if strings.Contains(contentType, "image/") {
return contentType, true
}
return contentType, false
@@ -206,8 +206,7 @@ func CommitChanges(repoPath string, opts CommitChangesOptions) error {
}
func commitsCount(repoPath, revision, relpath string) (int64, error) {
var cmd *Command
cmd = NewCommand("rev-list", "--count")
cmd := NewCommand("rev-list", "--count")
cmd.AddArguments(revision)
if len(relpath) > 0 {
cmd.AddArguments("--", relpath)
@@ -263,7 +262,7 @@ type SearchCommitsOptions struct {
All bool
}
// NewSearchCommitsOptions contruct a SearchCommitsOption from a space-delimited search string
// NewSearchCommitsOptions construct a SearchCommitsOption from a space-delimited search string
func NewSearchCommitsOptions(searchString string, forAllRefs bool) SearchCommitsOptions {
var keywords, authors, committers []string
var after, before string
+34 -22
View File
@@ -8,6 +8,7 @@ import (
"github.com/emirpasic/gods/trees/binaryheap"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object"
cgobject "gopkg.in/src-d/go-git.v4/plumbing/object/commitgraph"
)
// GetCommitsInfo gets information of all commits that are corresponding to these entries
@@ -19,7 +20,12 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom
entryPaths[i+1] = entry.Name()
}
c, err := commit.repo.gogitRepo.CommitObject(plumbing.Hash(commit.ID))
commitNodeIndex, commitGraphFile := commit.repo.CommitNodeIndex()
if commitGraphFile != nil {
defer commitGraphFile.Close()
}
c, err := commitNodeIndex.Get(plumbing.Hash(commit.ID))
if err != nil {
return nil, nil, err
}
@@ -37,7 +43,13 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom
entryCommit := convertCommit(rev)
if entry.IsSubModule() {
subModuleURL := ""
if subModule, err := commit.GetSubModule(entry.Name()); err != nil {
var fullPath string
if len(treePath) > 0 {
fullPath = treePath + "/" + entry.Name()
} else {
fullPath = entry.Name()
}
if subModule, err := commit.GetSubModule(fullPath); err != nil {
return nil, nil, err
} else if subModule != nil {
subModuleURL = subModule.URL
@@ -63,14 +75,14 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom
}
type commitAndPaths struct {
commit *object.Commit
commit cgobject.CommitNode
// Paths that are still on the branch represented by commit
paths []string
// Set of hashes for the paths
hashes map[string]plumbing.Hash
}
func getCommitTree(c *object.Commit, treePath string) (*object.Tree, error) {
func getCommitTree(c cgobject.CommitNode, treePath string) (*object.Tree, error) {
tree, err := c.Tree()
if err != nil {
return nil, err
@@ -87,17 +99,7 @@ func getCommitTree(c *object.Commit, treePath string) (*object.Tree, error) {
return tree, nil
}
func getFullPath(treePath, path string) string {
if treePath != "" {
if path != "" {
return treePath + "/" + path
}
return treePath
}
return path
}
func getFileHashes(c *object.Commit, treePath string, paths []string) (map[string]plumbing.Hash, error) {
func getFileHashes(c cgobject.CommitNode, treePath string, paths []string) (map[string]plumbing.Hash, error) {
tree, err := getCommitTree(c, treePath)
if err == object.ErrDirectoryNotFound {
// The whole tree didn't exist, so return empty map
@@ -122,16 +124,16 @@ func getFileHashes(c *object.Commit, treePath string, paths []string) (map[strin
return hashes, nil
}
func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (map[string]*object.Commit, error) {
func getLastCommitForPaths(c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) {
// We do a tree traversal with nodes sorted by commit time
heap := binaryheap.NewWith(func(a, b interface{}) int {
if a.(*commitAndPaths).commit.Committer.When.Before(b.(*commitAndPaths).commit.Committer.When) {
if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) {
return 1
}
return -1
})
result := make(map[string]*object.Commit)
resultNodes := make(map[string]cgobject.CommitNode)
initialHashes, err := getFileHashes(c, treePath, paths)
if err != nil {
return nil, err
@@ -149,9 +151,9 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m
// Load the parent commits for the one we are currently examining
numParents := current.commit.NumParents()
var parents []*object.Commit
var parents []cgobject.CommitNode
for i := 0; i < numParents; i++ {
parent, err := current.commit.Parent(i)
parent, err := current.commit.ParentNode(i)
if err != nil {
break
}
@@ -178,7 +180,7 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m
for i, path := range current.paths {
// The results could already contain some newer change for the same path,
// so don't override that and bail out on the file early.
if result[path] == nil {
if resultNodes[path] == nil {
if pathUnchanged[i] {
// The path existed with the same hash in at least one parent so it could
// not have been changed in this commit directly.
@@ -192,7 +194,7 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m
// - We are looking at a merge commit and the hash of the file doesn't
// match any of the hashes being merged. This is more common for directories,
// but it can also happen if a file is changed through conflict resolution.
result[path] = current.commit
resultNodes[path] = current.commit
}
}
}
@@ -226,5 +228,15 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m
}
}
// Post-processing
result := make(map[string]*object.Commit)
for path, commitNode := range resultNodes {
var err error
result[path], err = commitNode.Commit()
if err != nil {
return nil, err
}
}
return result, nil
}
+51 -6
View File
@@ -11,6 +11,8 @@ import (
"strings"
"time"
"code.gitea.io/gitea/modules/process"
"github.com/mcuadros/go-version"
)
@@ -31,6 +33,8 @@ var (
// GitExecutable is the command name of git
// Could be updated to an absolute path while initialization
GitExecutable = "git"
gitVersion string
)
func log(format string, args ...interface{}) {
@@ -46,8 +50,6 @@ func log(format string, args ...interface{}) {
}
}
var gitVersion string
// BinVersion returns current Git version from shell.
func BinVersion() (string, error) {
if len(gitVersion) > 0 {
@@ -75,20 +77,63 @@ func BinVersion() (string, error) {
return gitVersion, nil
}
func init() {
// SetExecutablePath changes the path of git executable and checks the file permission and version.
func SetExecutablePath(path string) error {
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
if path != "" {
GitExecutable = path
}
absPath, err := exec.LookPath(GitExecutable)
if err != nil {
panic(fmt.Sprintf("Git not found: %v", err))
return fmt.Errorf("Git not found: %v", err)
}
GitExecutable = absPath
gitVersion, err := BinVersion()
if err != nil {
panic(fmt.Sprintf("Git version missing: %v", err))
return fmt.Errorf("Git version missing: %v", err)
}
if version.Compare(gitVersion, GitVersionRequired, "<") {
panic(fmt.Sprintf("Git version not supported. Requires version > %v", GitVersionRequired))
return fmt.Errorf("Git version not supported. Requires version > %v", GitVersionRequired)
}
return nil
}
// Init initializes git module
func Init() error {
// Git requires setting user.name and user.email in order to commit changes.
for configKey, defaultValue := range map[string]string{"user.name": "Gitea", "user.email": "gitea@fake.local"} {
if stdout, stderr, err := process.GetManager().Exec("git.Init(get setting)", GitExecutable, "config", "--get", configKey); err != nil || strings.TrimSpace(stdout) == "" {
// ExitError indicates this config is not set
if _, ok := err.(*exec.ExitError); ok || strings.TrimSpace(stdout) == "" {
if _, stderr, gerr := process.GetManager().Exec("git.Init(set "+configKey+")", "git", "config", "--global", configKey, defaultValue); gerr != nil {
return fmt.Errorf("Failed to set git %s(%s): %s", configKey, gerr, stderr)
}
} else {
return fmt.Errorf("Failed to get git %s(%s): %s", configKey, err, stderr)
}
}
}
// Set git some configurations.
if _, stderr, err := process.GetManager().Exec("git.Init(git config --global core.quotepath false)",
GitExecutable, "config", "--global", "core.quotepath", "false"); err != nil {
return fmt.Errorf("Failed to execute 'git config --global core.quotepath false': %s", stderr)
}
if version.Compare(gitVersion, "2.18", ">=") {
if _, stderr, err := process.GetManager().Exec("git.Init(git config --global core.commitGraph true)",
GitExecutable, "config", "--global", "core.commitGraph", "true"); err != nil {
return fmt.Errorf("Failed to execute 'git config --global core.commitGraph true': %s", stderr)
}
if _, stderr, err := process.GetManager().Exec("git.Init(git config --global gc.writeCommitGraph true)",
GitExecutable, "config", "--global", "gc.writeCommitGraph", "true"); err != nil {
return fmt.Errorf("Failed to execute 'git config --global gc.writeCommitGraph true': %s", stderr)
}
}
return nil
}
// Fsck verifies the connectivity and validity of the objects in the database
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2019 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 git
import (
"fmt"
"os"
"testing"
)
func fatalTestError(fmtStr string, args ...interface{}) {
fmt.Fprintf(os.Stderr, fmtStr, args...)
os.Exit(1)
}
func TestMain(m *testing.M) {
if err := Init(); err != nil {
fatalTestError("Init failed: %v", err)
}
exitStatus := m.Run()
os.Exit(exitStatus)
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright 2019 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 git
import (
"io/ioutil"
"gopkg.in/src-d/go-git.v4/plumbing"
)
// NotesRef is the git ref where Gitea will look for git-notes data.
// The value ("refs/notes/commits") is the default ref used by git-notes.
const NotesRef = "refs/notes/commits"
// Note stores information about a note created using git-notes.
type Note struct {
Message []byte
Commit *Commit
}
// GetNote retrieves the git-notes data for a given commit.
func GetNote(repo *Repository, commitID string, note *Note) error {
notes, err := repo.GetCommit(NotesRef)
if err != nil {
return err
}
entry, err := notes.GetTreeEntryByPath(commitID)
if err != nil {
return err
}
blob := entry.Blob()
dataRc, err := blob.DataAsync()
if err != nil {
return err
}
defer dataRc.Close()
d, err := ioutil.ReadAll(dataRc)
if err != nil {
return err
}
note.Message = d
commit, err := repo.gogitRepo.CommitObject(plumbing.Hash(notes.ID))
if err != nil {
return err
}
commitNodeIndex, commitGraphFile := repo.CommitNodeIndex()
if commitGraphFile != nil {
defer commitGraphFile.Close()
}
commitNode, err := commitNodeIndex.Get(commit.Hash)
if err != nil {
return nil
}
lastCommits, err := getLastCommitForPaths(commitNode, "", []string{commitID})
if err != nil {
return err
}
note.Commit = convertCommit(lastCommits[commitID])
return nil
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2019 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 git
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetNotes(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
assert.NoError(t, err)
note := Note{}
err = GetNote(bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", &note)
assert.NoError(t, err)
assert.Equal(t, []byte("Note contents\n"), note.Message)
assert.Equal(t, "Vladimir Panteleev", note.Commit.Author.Name)
}
+20 -6
View File
@@ -58,21 +58,21 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, err
// IsRepoURLAccessible checks if given repository URL is accessible.
func IsRepoURLAccessible(url string) bool {
_, err := NewCommand("ls-remote", "-q", "-h", url, "HEAD").Run()
if err != nil {
return false
}
return true
return err == nil
}
// InitRepository initializes a new Git repository.
func InitRepository(repoPath string, bare bool) error {
os.MkdirAll(repoPath, os.ModePerm)
err := os.MkdirAll(repoPath, os.ModePerm)
if err != nil {
return err
}
cmd := NewCommand("init")
if bare {
cmd.AddArguments("--bare")
}
_, err := cmd.RunInDir(repoPath)
_, err = cmd.RunInDir(repoPath)
return err
}
@@ -107,6 +107,20 @@ func OpenRepository(repoPath string) (*Repository, error) {
}, nil
}
// IsEmpty Check if repository is empty.
func (repo *Repository) IsEmpty() (bool, error) {
var errbuf strings.Builder
if err := NewCommand("log", "-1").RunInDirPipeline(repo.Path, nil, &errbuf); err != nil {
if strings.Contains(errbuf.String(), "fatal: bad default revision 'HEAD'") ||
strings.Contains(errbuf.String(), "fatal: your current branch 'master' does not have any commits yet") {
return true, nil
}
return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
}
return false, nil
}
// CloneRepoOptions options when clone a repository
type CloneRepoOptions struct {
Timeout time.Duration
+2 -5
View File
@@ -29,10 +29,7 @@ func IsBranchExist(repoPath, name string) bool {
// IsBranchExist returns true if given branch exists in current repository.
func (repo *Repository) IsBranchExist(name string) bool {
_, err := repo.gogitRepo.Reference(plumbing.ReferenceName(BranchPrefix+name), true)
if err != nil {
return false
}
return true
return err == nil
}
// Branch represents a Git branch.
@@ -77,7 +74,7 @@ func (repo *Repository) GetBranches() ([]string, error) {
return nil, err
}
branches.ForEach(func(branch *plumbing.Reference) error {
_ = branches.ForEach(func(branch *plumbing.Reference) error {
branchNames = append(branchNames, strings.TrimPrefix(branch.Name().String(), BranchPrefix))
return nil
})
+7
View File
@@ -27,6 +27,13 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
return ref.Hash().String(), nil
}
// IsCommitExist returns true if given commit exists in current repository.
func (repo *Repository) IsCommitExist(name string) bool {
hash := plumbing.NewHash(name)
_, err := repo.gogitRepo.CommitObject(hash)
return err == nil
}
// GetBranchCommitID returns last commit ID string of given branch.
func (repo *Repository) GetBranchCommitID(name string) (string, error) {
return repo.GetRefCommitID(BranchPrefix + name)
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2019 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 git
import (
"os"
"path"
gitealog "code.gitea.io/gitea/modules/log"
"gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph"
cgobject "gopkg.in/src-d/go-git.v4/plumbing/object/commitgraph"
)
// CommitNodeIndex returns the index for walking commit graph
func (r *Repository) CommitNodeIndex() (cgobject.CommitNodeIndex, *os.File) {
indexPath := path.Join(r.Path, "objects", "info", "commit-graph")
file, err := os.Open(indexPath)
if err == nil {
var index commitgraph.Index
index, err = commitgraph.OpenFileIndex(file)
if err == nil {
return cgobject.NewGraphCommitNodeIndex(index, r.gogitRepo.Storer), file
}
}
if !os.IsNotExist(err) {
gitealog.Warn("Unable to read commit-graph for %s: %v", r.Path, err)
}
return cgobject.NewObjectCommitNodeIndex(r.gogitRepo.Storer), nil
}
+112
View File
@@ -0,0 +1,112 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2019 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 git
import (
"bytes"
"container/list"
"fmt"
"io"
"strconv"
"strings"
"time"
logger "code.gitea.io/gitea/modules/log"
)
// CompareInfo represents needed information for comparing references.
type CompareInfo struct {
MergeBase string
Commits *list.List
NumFiles int
}
// GetMergeBase checks and returns merge base of two branches and the reference used as base.
func (repo *Repository) GetMergeBase(tmpRemote string, base, head string) (string, string, error) {
if tmpRemote == "" {
tmpRemote = "origin"
}
if tmpRemote != "origin" {
tmpBaseName := "refs/remotes/" + tmpRemote + "/tmp_" + base
// Fetch commit into a temporary branch in order to be able to handle commits and tags
_, err := NewCommand("fetch", tmpRemote, base+":"+tmpBaseName).RunInDir(repo.Path)
if err == nil {
base = tmpBaseName
}
}
stdout, err := NewCommand("merge-base", base, head).RunInDir(repo.Path)
return strings.TrimSpace(stdout), base, err
}
// GetCompareInfo generates and returns compare information between base and head branches of repositories.
func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string) (_ *CompareInfo, err error) {
var (
remoteBranch string
tmpRemote string
)
// We don't need a temporary remote for same repository.
if repo.Path != basePath {
// Add a temporary remote
tmpRemote = strconv.FormatInt(time.Now().UnixNano(), 10)
if err = repo.AddRemote(tmpRemote, basePath, true); err != nil {
return nil, fmt.Errorf("AddRemote: %v", err)
}
defer func() {
if err := repo.RemoveRemote(tmpRemote); err != nil {
logger.Error("GetPullRequestInfo: RemoveRemote: %v", err)
}
}()
}
compareInfo := new(CompareInfo)
compareInfo.MergeBase, remoteBranch, err = repo.GetMergeBase(tmpRemote, baseBranch, headBranch)
if err == nil {
// We have a common base
logs, err := NewCommand("log", compareInfo.MergeBase+"..."+headBranch, prettyLogFormat).RunInDirBytes(repo.Path)
if err != nil {
return nil, err
}
compareInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
if err != nil {
return nil, fmt.Errorf("parsePrettyFormatLogToList: %v", err)
}
} else {
compareInfo.Commits = list.New()
compareInfo.MergeBase, err = GetFullCommitID(repo.Path, remoteBranch)
if err != nil {
compareInfo.MergeBase = remoteBranch
}
}
// Count number of changed files.
stdout, err := NewCommand("diff", "--name-only", remoteBranch+"..."+headBranch).RunInDir(repo.Path)
if err != nil {
return nil, err
}
compareInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1
return compareInfo, nil
}
// GetPatch generates and returns patch data between given revisions.
func (repo *Repository) GetPatch(base, head string) ([]byte, error) {
return NewCommand("diff", "-p", "--binary", base, head).RunInDirBytes(repo.Path)
}
// GetFormatPatch generates and returns format-patch data between given revisions.
func (repo *Repository) GetFormatPatch(base, head string) (io.Reader, error) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
if err := NewCommand("format-patch", "--binary", "--stdout", base+"..."+head).
RunInDirPipeline(repo.Path, stdout, stderr); err != nil {
return nil, concatenateError(err, stderr.String())
}
return stdout, nil
}
+17
View File
@@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 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.
@@ -22,6 +23,8 @@ const (
ObjectBlob ObjectType = "blob"
// ObjectTag tag object type
ObjectTag ObjectType = "tag"
// ObjectBranch branch object type
ObjectBranch ObjectType = "branch"
)
// HashObject takes a reader and returns SHA1 hash for that reader
@@ -44,3 +47,17 @@ func (repo *Repository) hashObject(reader io.Reader) (string, error) {
}
return strings.TrimSpace(stdout.String()), nil
}
// GetRefType gets the type of the ref based on the string
func (repo *Repository) GetRefType(ref string) ObjectType {
if repo.IsTagExist(ref) {
return ObjectTag
} else if repo.IsBranchExist(ref) {
return ObjectBranch
} else if repo.IsCommitExist(ref) {
return ObjectCommit
} else if _, err := repo.GetBlob(ref); err == nil {
return ObjectBlob
}
return ObjectType("invalid")
}
-94
View File
@@ -1,94 +0,0 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"bytes"
"container/list"
"fmt"
"io"
"strconv"
"strings"
"time"
)
// PullRequestInfo represents needed information for a pull request.
type PullRequestInfo struct {
MergeBase string
Commits *list.List
NumFiles int
}
// GetMergeBase checks and returns merge base of two branches.
func (repo *Repository) GetMergeBase(base, head string) (string, error) {
stdout, err := NewCommand("merge-base", base, head).RunInDir(repo.Path)
return strings.TrimSpace(stdout), err
}
// GetPullRequestInfo generates and returns pull request information
// between base and head branches of repositories.
func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (_ *PullRequestInfo, err error) {
var remoteBranch string
// We don't need a temporary remote for same repository.
if repo.Path != basePath {
// Add a temporary remote
tmpRemote := strconv.FormatInt(time.Now().UnixNano(), 10)
if err = repo.AddRemote(tmpRemote, basePath, true); err != nil {
return nil, fmt.Errorf("AddRemote: %v", err)
}
defer repo.RemoveRemote(tmpRemote)
remoteBranch = "remotes/" + tmpRemote + "/" + baseBranch
} else {
remoteBranch = baseBranch
}
prInfo := new(PullRequestInfo)
prInfo.MergeBase, err = repo.GetMergeBase(remoteBranch, headBranch)
if err == nil {
// We have a common base
logs, err := NewCommand("log", prInfo.MergeBase+"..."+headBranch, prettyLogFormat).RunInDirBytes(repo.Path)
if err != nil {
return nil, err
}
prInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
if err != nil {
return nil, fmt.Errorf("parsePrettyFormatLogToList: %v", err)
}
} else {
prInfo.Commits = list.New()
prInfo.MergeBase, err = GetFullCommitID(repo.Path, remoteBranch)
if err != nil {
prInfo.MergeBase = remoteBranch
}
}
// Count number of changed files.
stdout, err := NewCommand("diff", "--name-only", remoteBranch+"..."+headBranch).RunInDir(repo.Path)
if err != nil {
return nil, err
}
prInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1
return prInfo, nil
}
// GetPatch generates and returns patch data between given revisions.
func (repo *Repository) GetPatch(base, head string) ([]byte, error) {
return NewCommand("diff", "-p", "--binary", base, head).RunInDirBytes(repo.Path)
}
// GetFormatPatch generates and returns format-patch data between given revisions.
func (repo *Repository) GetFormatPatch(base, head string) (io.Reader, error) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
if err := NewCommand("format-patch", "--binary", "--stdout", base+"..."+head).
RunInDirPipeline(repo.Path, stdout, stderr); err != nil {
return nil, concatenateError(err, stderr.String())
}
return stdout, nil
}
+8 -4
View File
@@ -31,15 +31,19 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
if err = refsIter.ForEach(func(ref *plumbing.Reference) error {
if ref.Name() != plumbing.HEAD && !ref.Name().IsRemote() &&
(pattern == "" || strings.HasPrefix(ref.Name().String(), pattern)) {
refType := string(ObjectCommit)
if ref.Name().IsTag() {
// tags can be of type `commit` (lightweight) or `tag` (annotated)
if tagType, _ := repo.GetTagType(SHA1(ref.Hash())); err == nil {
refType = tagType
}
}
r := &Reference{
Name: ref.Name().String(),
Object: SHA1(ref.Hash()),
Type: string(ObjectCommit),
Type: refType,
repo: repo,
}
if ref.Name().IsTag() {
r.Type = string(ObjectTag)
}
refs = append(refs, r)
}
return nil
+2 -1
View File
@@ -19,13 +19,14 @@ func TestRepository_GetRefs(t *testing.T) {
refs, err := bareRepo1.GetRefs()
assert.NoError(t, err)
assert.Len(t, refs, 4)
assert.Len(t, refs, 5)
expectedRefs := []string{
BranchPrefix + "branch1",
BranchPrefix + "branch2",
BranchPrefix + "master",
TagPrefix + "test",
NotesRef,
}
for _, ref := range refs {
+137 -18
View File
@@ -6,6 +6,7 @@
package git
import (
"fmt"
"strings"
"github.com/mcuadros/go-version"
@@ -23,10 +24,7 @@ func IsTagExist(repoPath, name string) bool {
// IsTagExist returns true if given tag exists in the repository.
func (repo *Repository) IsTagExist(name string) bool {
_, err := repo.gogitRepo.Reference(plumbing.ReferenceName(TagPrefix+name), true)
if err != nil {
return false
}
return true
return err == nil
}
// CreateTag create one tag in the repository
@@ -35,34 +33,78 @@ func (repo *Repository) CreateTag(name, revision string) error {
return err
}
// CreateAnnotatedTag create one annotated tag in the repository
func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error {
_, err := NewCommand("tag", "-a", "-m", message, name, revision).RunInDir(repo.Path)
return err
}
func (repo *Repository) getTag(id SHA1) (*Tag, error) {
t, ok := repo.tagCache.Get(id.String())
if ok {
log("Hit cache: %s", id)
return t.(*Tag), nil
tagClone := *t.(*Tag)
return &tagClone, nil
}
// Get tag type
tp, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
// Get tag name
name, err := repo.GetTagNameBySHA(id.String())
if err != nil {
return nil, err
}
tp = strings.TrimSpace(tp)
// Tag is a commit.
tp, err := repo.GetTagType(id)
if err != nil {
return nil, err
}
// Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object
commitIDStr, err := repo.GetTagCommitID(name)
if err != nil {
// every tag should have a commit ID so return all errors
return nil, err
}
commitID, err := NewIDFromString(commitIDStr)
if err != nil {
return nil, err
}
// tagID defaults to the commit ID as the tag ID and then tries to get a tag ID (only annotated tags)
tagID := commitID
if tagIDStr, err := repo.GetTagID(name); err != nil {
// if the err is NotExist then we can ignore and just keep tagID as ID (is lightweight tag)
// all other errors we return
if !IsErrNotExist(err) {
return nil, err
}
} else {
tagID, err = NewIDFromString(tagIDStr)
if err != nil {
return nil, err
}
}
// If type is "commit, the tag is a lightweight tag
if ObjectType(tp) == ObjectCommit {
commit, err := repo.GetCommit(id.String())
if err != nil {
return nil, err
}
tag := &Tag{
ID: id,
Object: id,
Type: string(ObjectCommit),
repo: repo,
Name: name,
ID: tagID,
Object: commitID,
Type: string(ObjectCommit),
Tagger: commit.Committer,
Message: commit.Message(),
repo: repo,
}
repo.tagCache.Set(id.String(), tag)
return tag, nil
}
// Tag with message.
// The tag is an annotated tag with a message.
data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path)
if err != nil {
return nil, err
@@ -73,16 +115,58 @@ func (repo *Repository) getTag(id SHA1) (*Tag, error) {
return nil, err
}
tag.Name = name
tag.ID = id
tag.repo = repo
tag.Type = tp
repo.tagCache.Set(id.String(), tag)
return tag, nil
}
// GetTagNameBySHA returns the name of a tag from its tag object SHA or commit SHA
func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
if len(sha) < 5 {
return "", fmt.Errorf("SHA is too short: %s", sha)
}
stdout, err := NewCommand("show-ref", "--tags", "-d").RunInDir(repo.Path)
if err != nil {
return "", err
}
tagRefs := strings.Split(stdout, "\n")
for _, tagRef := range tagRefs {
if len(strings.TrimSpace(tagRef)) > 0 {
fields := strings.Fields(tagRef)
if strings.HasPrefix(fields[0], sha) && strings.HasPrefix(fields[1], TagPrefix) {
name := fields[1][len(TagPrefix):]
// annotated tags show up twice, we should only return if is not the ^{} ref
if !strings.HasSuffix(name, "^{}") {
return name, nil
}
}
}
}
return "", ErrNotExist{ID: sha}
}
// GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
func (repo *Repository) GetTagID(name string) (string, error) {
stdout, err := NewCommand("show-ref", name).RunInDir(repo.Path)
if err != nil {
return "", err
}
fields := strings.Fields(stdout)
if len(fields) != 2 {
return "", ErrNotExist{ID: name}
}
return fields[0], nil
}
// GetTag returns a Git tag by given name.
func (repo *Repository) GetTag(name string) (*Tag, error) {
idStr, err := repo.GetTagCommitID(name)
idStr, err := repo.GetTagID(name)
if err != nil {
return nil, err
}
@@ -96,7 +180,6 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
if err != nil {
return nil, err
}
tag.Name = name
return tag, nil
}
@@ -108,7 +191,7 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
return nil, err
}
tagNames := strings.Split(stdout, "\n")
tagNames := strings.Split(strings.TrimRight(stdout, "\n"), "\n")
var tags = make([]*Tag, 0, len(tagNames))
for _, tagName := range tagNames {
tagName = strings.TrimSpace(tagName)
@@ -120,6 +203,7 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
if err != nil {
return nil, err
}
tag.Name = tagName
tags = append(tags, tag)
}
sortTagsByTime(tags)
@@ -135,7 +219,7 @@ func (repo *Repository) GetTags() ([]string, error) {
return nil, err
}
tags.ForEach(func(tag *plumbing.Reference) error {
_ = tags.ForEach(func(tag *plumbing.Reference) error {
tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix))
return nil
})
@@ -150,3 +234,38 @@ func (repo *Repository) GetTags() ([]string, error) {
return tagNames, nil
}
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
func (repo *Repository) GetTagType(id SHA1) (string, error) {
// Get tag type
stdout, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
if err != nil {
return "", err
}
if len(stdout) == 0 {
return "", ErrNotExist{ID: id.String()}
}
return strings.TrimSpace(stdout), nil
}
// GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) {
id, err := NewIDFromString(sha)
if err != nil {
return nil, err
}
// Tag type must be "tag" (annotated) and not a "commit" (lightweight) tag
if tagType, err := repo.GetTagType(id); err != nil {
return nil, err
} else if ObjectType(tagType) != ObjectTag {
// not an annotated tag
return nil, ErrNotExist{ID: id.String()}
}
tag, err := repo.getTag(id)
if err != nil {
return nil, err
}
return tag, nil
}
+74 -6
View File
@@ -21,8 +21,8 @@ func TestRepository_GetTags(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, tags, 1)
assert.EqualValues(t, "test", tags[0].Name)
assert.EqualValues(t, "37991dec2c8e592043f47155ce4808d4580f9123", tags[0].ID.String())
assert.EqualValues(t, "commit", tags[0].Type)
assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[0].ID.String())
assert.EqualValues(t, "tag", tags[0].Type)
}
func TestRepository_GetTag(t *testing.T) {
@@ -35,10 +35,78 @@ func TestRepository_GetTag(t *testing.T) {
bareRepo1, err := OpenRepository(clonedPath)
assert.NoError(t, err)
tag, err := bareRepo1.GetTag("test")
lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
lTagName := "lightweightTag"
bareRepo1.CreateTag(lTagName, lTagCommitID)
aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
aTagName := "annotatedTag"
aTagMessage := "my annotated message"
bareRepo1.CreateAnnotatedTag(aTagName, aTagMessage, aTagCommitID)
aTagID, _ := bareRepo1.GetTagID(aTagName)
lTag, err := bareRepo1.GetTag(lTagName)
lTag.repo = nil
assert.NoError(t, err)
assert.NotNil(t, lTag)
assert.EqualValues(t, lTagName, lTag.Name)
assert.EqualValues(t, lTagCommitID, lTag.ID.String())
assert.EqualValues(t, lTagCommitID, lTag.Object.String())
assert.EqualValues(t, "commit", lTag.Type)
aTag, err := bareRepo1.GetTag(aTagName)
assert.NoError(t, err)
assert.NotNil(t, aTag)
assert.EqualValues(t, aTagName, aTag.Name)
assert.EqualValues(t, aTagID, aTag.ID.String())
assert.NotEqual(t, aTagID, aTag.Object.String())
assert.EqualValues(t, aTagCommitID, aTag.Object.String())
assert.EqualValues(t, "tag", aTag.Type)
}
func TestRepository_GetAnnotatedTag(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestRepository_GetTag")
assert.NoError(t, err)
defer os.RemoveAll(clonedPath)
bareRepo1, err := OpenRepository(clonedPath)
assert.NoError(t, err)
lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
lTagName := "lightweightTag"
bareRepo1.CreateTag(lTagName, lTagCommitID)
aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
aTagName := "annotatedTag"
aTagMessage := "my annotated message"
bareRepo1.CreateAnnotatedTag(aTagName, aTagMessage, aTagCommitID)
aTagID, _ := bareRepo1.GetTagID(aTagName)
// Try an annotated tag
tag, err := bareRepo1.GetAnnotatedTag(aTagID)
assert.NoError(t, err)
assert.NotNil(t, tag)
assert.EqualValues(t, "test", tag.Name)
assert.EqualValues(t, "37991dec2c8e592043f47155ce4808d4580f9123", tag.ID.String())
assert.EqualValues(t, "commit", tag.Type)
assert.EqualValues(t, aTagName, tag.Name)
assert.EqualValues(t, aTagID, tag.ID.String())
assert.EqualValues(t, "tag", tag.Type)
// Annotated tag's Commit ID should fail
tag2, err := bareRepo1.GetAnnotatedTag(aTagCommitID)
assert.Error(t, err)
assert.True(t, IsErrNotExist(err))
assert.Nil(t, tag2)
// Annotated tag's name should fail
tag3, err := bareRepo1.GetAnnotatedTag(aTagName)
assert.Error(t, err)
assert.Errorf(t, err, "Length must be 40: %d", len(aTagName))
assert.Nil(t, tag3)
// Lightweight Tag should fail
tag4, err := bareRepo1.GetAnnotatedTag(lTagCommitID)
assert.Error(t, err)
assert.True(t, IsErrNotExist(err))
assert.Nil(t, tag4)
}
+10
View File
@@ -5,6 +5,7 @@
package git
import (
"path/filepath"
"testing"
"time"
@@ -24,3 +25,12 @@ func TestGetLatestCommitTime(t *testing.T) {
assert.NoError(t, err)
assert.True(t, lct.Unix() > refTime.Unix(), "%d not greater than %d", lct, refTime)
}
func TestRepoIsEmpty(t *testing.T) {
emptyRepo2Path := filepath.Join(testReposDir, "repo2_empty")
repo, err := OpenRepository(emptyRepo2Path)
assert.NoError(t, err)
isEmpty, err := repo.IsEmpty()
assert.NoError(t, err)
assert.True(t, isEmpty)
}
+63 -27
View File
@@ -1,10 +1,19 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import "strings"
import (
"fmt"
"net"
"net/url"
"regexp"
"strings"
)
var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
// SubModule submodule is a reference on git repository
type SubModule struct {
@@ -34,46 +43,73 @@ func getRefURL(refURL, urlPrefix, parentPath string) string {
return ""
}
url := strings.TrimSuffix(refURL, ".git")
refURI := strings.TrimSuffix(refURL, ".git")
// git://xxx/user/repo
if strings.HasPrefix(url, "git://") {
return "http://" + strings.TrimPrefix(url, "git://")
}
// http[s]://xxx/user/repo
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
return url
prefixURL, _ := url.Parse(urlPrefix)
urlPrefixHostname, _, err := net.SplitHostPort(prefixURL.Host)
if err != nil {
urlPrefixHostname = prefixURL.Host
}
// Relative url prefix check (according to git submodule documentation)
if strings.HasPrefix(url, "./") || strings.HasPrefix(url, "../") {
if strings.HasPrefix(refURI, "./") || strings.HasPrefix(refURI, "../") {
// ...construct and return correct submodule url here...
idx := strings.Index(parentPath, "/src/")
if idx == -1 {
return url
return refURI
}
return strings.TrimSuffix(urlPrefix, "/") + parentPath[:idx] + "/" + url
return strings.TrimSuffix(urlPrefix, "/") + parentPath[:idx] + "/" + refURI
}
// sysuser@xxx:user/repo
i := strings.Index(url, "@")
j := strings.LastIndex(url, ":")
if !strings.Contains(refURI, "://") {
// scp style syntax which contains *no* port number after the : (and is not parsed by net/url)
// ex: git@try.gitea.io:go-gitea/gitea
match := scpSyntax.FindAllStringSubmatch(refURI, -1)
if len(match) > 0 {
// Only process when i < j because git+ssh://git@git.forwardbias.in/npploader.git
if i > -1 && j > -1 && i < j {
// fix problem with reverse proxy works only with local server
if strings.Contains(urlPrefix, url[i+1:j]) {
return urlPrefix + url[j+1:]
m := match[0]
refHostname := m[2]
path := m[3]
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
if urlPrefixHostname == refHostname {
return prefixURL.Scheme + "://" + urlPrefixHostname + path
}
return "http://" + refHostname + path
}
if strings.HasPrefix(url, "ssh://") || strings.HasPrefix(url, "git+ssh://") {
k := strings.Index(url[j+1:], "/")
return "http://" + url[i+1:j] + "/" + url[j+1:][k+1:]
}
return "http://" + url[i+1:j] + "/" + url[j+1:]
}
return url
ref, err := url.Parse(refURI)
if err != nil {
return ""
}
refHostname, _, err := net.SplitHostPort(ref.Host)
if err != nil {
refHostname = ref.Host
}
supportedSchemes := []string{"http", "https", "git", "ssh", "git+ssh"}
for _, scheme := range supportedSchemes {
if ref.Scheme == scheme {
if urlPrefixHostname == refHostname {
return prefixURL.Scheme + "://" + prefixURL.Host + ref.Path
} else if ref.Scheme == "http" || ref.Scheme == "https" {
if len(ref.User.Username()) > 0 {
return ref.Scheme + "://" + fmt.Sprintf("%v", ref.User) + "@" + ref.Host + ref.Path
}
return ref.Scheme + "://" + ref.Host + ref.Path
} else {
return "http://" + refHostname + ref.Path
}
}
}
return ""
}
// RefURL guesses and returns reference URL.
+12 -1
View File
@@ -19,8 +19,19 @@ func TestGetRefURL(t *testing.T) {
}{
{"git://github.com/user1/repo1", "/", "/", "http://github.com/user1/repo1"},
{"https://localhost/user1/repo1.git", "/", "/", "https://localhost/user1/repo1"},
{"git@github.com/user1/repo1.git", "/", "/", "git@github.com/user1/repo1"},
{"http://localhost/user1/repo1.git", "/", "/", "http://localhost/user1/repo1"},
{"git@github.com:user1/repo1.git", "/", "/", "http://github.com/user1/repo1"},
{"ssh://git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "/", "http://git.zefie.net/zefie/lge_g6_kernel_scripts"},
{"git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "/", "http://git.zefie.net/2222/zefie/lge_g6_kernel_scripts"},
{"git@try.gitea.io:go-gitea/gitea", "https://try.gitea.io/go-gitea/gitea", "/", "https://try.gitea.io/go-gitea/gitea"},
{"ssh://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/go-gitea/gitea", "/", "https://try.gitea.io/go-gitea/gitea"},
{"git://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/go-gitea/log", "/", "https://try.gitea.io/go-gitea/gitea"},
{"ssh://git@127.0.0.1:9999/go-gitea/gitea", "https://127.0.0.1:3000/go-gitea/log", "/", "https://127.0.0.1:3000/go-gitea/gitea"},
{"https://gitea.com:3000/user1/repo1.git", "https://127.0.0.1:3000/go-gitea/gitea", "/", "https://gitea.com:3000/user1/repo1"},
{"https://username:password@github.com/username/repository.git", "/", "/", "https://username:password@github.com/username/repository"},
{"somethingbad", "https://127.0.0.1:3000/go-gitea/gitea", "/", ""},
{"git@localhost:user/repo", "https://localhost/user/repo2", "/", "https://localhost/user/repo"},
{"../path/to/repo.git/", "https://localhost/user/repo2/src/branch/master/test", "/", "../path/to/repo.git/"},
}
for _, kase := range kases {
+2 -1
View File
@@ -7,6 +7,7 @@ package git
import (
"bytes"
"sort"
"strings"
)
// Tag represents a Git tag.
@@ -59,7 +60,7 @@ l:
}
nextline += eol + 1
case eol == 0:
tag.Message = string(data[nextline+1:])
tag.Message = strings.TrimRight(string(data[nextline+1:]), "\n")
break l
default:
break l
@@ -0,0 +1,4 @@
x¥ŽM
Â0F]ç³ëB&&m"ž@\¹Of¦6ÐHG¥··ô
~Ë·xïÃy³€Ñþ …Œ?[—Œ¶èBÓ&
H<bÛyß™NGt­åÚ¨ø–~.ð"å1xÄIx`þÀå•å&=㚸,}¤ù{šX® ó¶ p¬·)ÜãÂjÔ}^ 1AZ¡ÚÀ´3¦,•ú½ÀI0
@@ -0,0 +1 @@
ca6b5ddf303169a72d2a2971acde4f6eea194e5c
+1
View File
@@ -0,0 +1 @@
ref: refs/heads/master
@@ -0,0 +1,6 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true
ignorecase = true
precomposeunicode = true
@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.
@@ -0,0 +1,15 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:
@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}
@@ -0,0 +1,114 @@
#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open2;
# An example hook script to integrate Watchman
# (https://facebook.github.io/watchman/) with git to speed up detecting
# new and modified files.
#
# The hook is passed a version (currently 1) and a time in nanoseconds
# formatted as a string and outputs to stdout all files that have been
# modified since the given time. Paths must be relative to the root of
# the working tree and separated by a single NUL.
#
# To enable this hook, rename this file to "query-watchman" and set
# 'git config core.fsmonitor .git/hooks/query-watchman'
#
my ($version, $time) = @ARGV;
# Check the hook interface version
if ($version == 1) {
# convert nanoseconds to seconds
$time = int $time / 1000000000;
} else {
die "Unsupported query-fsmonitor hook version '$version'.\n" .
"Falling back to scanning...\n";
}
my $git_work_tree;
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
$git_work_tree = Win32::GetCwd();
$git_work_tree =~ tr/\\/\//;
} else {
require Cwd;
$git_work_tree = Cwd::cwd();
}
my $retry = 1;
launch_watchman();
sub launch_watchman {
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
or die "open2() failed: $!\n" .
"Falling back to scanning...\n";
# In the query expression below we're asking for names of files that
# changed since $time but were not transient (ie created after
# $time but no longer exist).
#
# To accomplish this, we're using the "since" generator to use the
# recency index to select candidate nodes and "fields" to limit the
# output to file names only. Then we're using the "expression" term to
# further constrain the results.
#
# The category of transient files that we want to ignore will have a
# creation clock (cclock) newer than $time_t value and will also not
# currently exist.
my $query = <<" END";
["query", "$git_work_tree", {
"since": $time,
"fields": ["name"],
"expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]]
}]
END
print CHLD_IN $query;
close CHLD_IN;
my $response = do {local $/; <CHLD_OUT>};
die "Watchman: command returned no output.\n" .
"Falling back to scanning...\n" if $response eq "";
die "Watchman: command returned invalid output: $response\n" .
"Falling back to scanning...\n" unless $response =~ /^\{/;
my $json_pkg;
eval {
require JSON::XS;
$json_pkg = "JSON::XS";
1;
} or do {
require JSON::PP;
$json_pkg = "JSON::PP";
};
my $o = $json_pkg->new->utf8->decode($response);
if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
$retry--;
qx/watchman watch "$git_work_tree"/;
die "Failed to make watchman watch '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
# Watchman will always return all files on the first query so
# return the fast "everything is dirty" flag to git and do the
# Watchman query just to get it over with now so we won't pay
# the cost in git to look up each individual file.
print "/\0";
eval { launch_watchman() };
exit 0;
}
die "Watchman: $o->{error}.\n" .
"Falling back to scanning...\n" if $o->{error};
binmode STDOUT, ":utf8";
local $, = "\0";
print @{$o->{files}};
}
@@ -0,0 +1,8 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info
@@ -0,0 +1,14 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:
@@ -0,0 +1,49 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --
+53
View File
@@ -0,0 +1,53 @@
#!/bin/sh
# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local sha1> <remote ref> <remote sha1>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
remote="$1"
url="$2"
z40=0000000000000000000000000000000000000000
while read local_ref local_sha remote_ref remote_sha
do
if [ "$local_sha" = $z40 ]
then
# Handle delete
:
else
if [ "$remote_sha" = $z40 ]
then
# New branch, examine all commits
range="$local_sha"
else
# Update to existing branch, examine new commits
range="$remote_sha..$local_sha"
fi
# Check for WIP commit
commit=`git rev-list -n 1 --grep '^WIP' "$range"`
if [ -n "$commit" ]
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
fi
fi
done
exit 0
+169
View File
@@ -0,0 +1,169 @@
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
<<\DOC_END
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
DOC_END
@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".
if test -n "$GIT_PUSH_OPTION_COUNT"
then
i=0
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
esac
i=$((i + 1))
done
fi
@@ -0,0 +1,42 @@
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source. The hook's purpose is to edit the commit
# message file. If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".
# This hook includes three examples. The first one removes the
# "# Please enter the commit message..." help message.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output. It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited. This is rarely a good idea.
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
# case "$COMMIT_SOURCE,$SHA1" in
# ,|template,)
# /usr/bin/perl -i.bak -pe '
# print "\n" . `git diff --cached --name-status -r`
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
# *) ;;
# esac
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
# if test -z "$COMMIT_SOURCE"
# then
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
# fi
+128
View File
@@ -0,0 +1,128 @@
#!/bin/sh
#
# An example hook script to block unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
# hooks.allowdeletetag
# This boolean sets whether deleting tags will be allowed in the
# repository. By default they won't be.
# hooks.allowmodifytag
# This boolean sets whether a tag may be modified after creation. By default
# it won't be.
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
# hooks.denycreatebranch
# This boolean sets whether remotely creating branches will be denied
# in the repository. By default this is allowed.
#
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
# --- Config
allowunannotated=$(git config --bool hooks.allowunannotated)
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
denycreatebranch=$(git config --bool hooks.denycreatebranch)
allowdeletetag=$(git config --bool hooks.allowdeletetag)
allowmodifytag=$(git config --bool hooks.allowmodifytag)
# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
echo "*** Project description file hasn't been set" >&2
exit 1
;;
esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero="0000000000000000000000000000000000000000"
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git cat-file -t $newrev)
fi
case "$refname","$newrev_type" in
refs/tags/*,commit)
# un-annotated tag
short_refname=${refname##refs/tags/}
if [ "$allowunannotated" != "true" ]; then
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
exit 1
fi
;;
refs/tags/*,delete)
# delete tag
if [ "$allowdeletetag" != "true" ]; then
echo "*** Deleting a tag is not allowed in this repository" >&2
exit 1
fi
;;
refs/tags/*,tag)
# annotated tag
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
then
echo "*** Tag '$refname' already exists." >&2
echo "*** Modifying a tag is not allowed in this repository." >&2
exit 1
fi
;;
refs/heads/*,commit)
# branch
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
echo "*** Creating a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/heads/*,delete)
# delete branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/remotes/*,commit)
# tracking branch
;;
refs/remotes/*,delete)
# delete tracking branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
exit 1
fi
;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
exit 1
;;
esac
# --- Finished
exit 0
@@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
+1 -1
View File
@@ -60,7 +60,7 @@ func (t *Tree) GetBlobByPath(relpath string) (*Blob, error) {
return nil, err
}
if !entry.IsDir() {
if !entry.IsDir() && !entry.IsSubModule() {
return entry.Blob(), nil
}
-8
View File
@@ -7,7 +7,6 @@ package git
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
)
@@ -75,13 +74,6 @@ func concatenateError(err error, stderr string) error {
return fmt.Errorf("%v - %s", err, stderr)
}
// If the object is stored in its own file (i.e not in a pack file),
// this function returns the full path to the object file.
// It does not test if the file exists.
func filepathFromSHA1(rootdir, sha1 string) string {
return filepath.Join(rootdir, "objects", sha1[:2], sha1[2:])
}
// RefEndName return the end name of a ref name
func RefEndName(refStr string) string {
if strings.HasPrefix(refStr, BranchPrefix) {
+2 -4
View File
@@ -74,7 +74,6 @@ func (wp *WriterPool) Put(w *gzip.Writer) {
}
var writerPool WriterPool
var regex regexp.Regexp
// Options represents the configuration for the gzip middleware
type Options struct {
@@ -116,7 +115,7 @@ func Middleware(options ...Options) macaron.Handler {
if rangeHdr := ctx.Req.Header.Get(rangeHeader); rangeHdr != "" {
match := regex.FindStringSubmatch(rangeHdr)
if match != nil && len(match) > 1 {
if len(match) > 1 {
return
}
}
@@ -270,9 +269,8 @@ func (proxy *ProxyResponseWriter) Close() error {
if proxy.writer == nil {
err := proxy.startPlain()
if err != nil {
err = fmt.Errorf("GzipMiddleware: write to regular responseWriter at close gets error: %q", err.Error())
return fmt.Errorf("GzipMiddleware: write to regular responseWriter at close gets error: %q", err.Error())
}
}
+38 -35
View File
@@ -26,45 +26,48 @@ var (
// Extensions that are same as highlight classes.
highlightExts = map[string]struct{}{
".arm": {},
".as": {},
".sh": {},
".cs": {},
".cpp": {},
".c": {},
".css": {},
".cmake": {},
".bat": {},
".dart": {},
".patch": {},
".elixir": {},
".erlang": {},
".go": {},
".html": {},
".xml": {},
".hs": {},
".ini": {},
".json": {},
".java": {},
".js": {},
".less": {},
".lua": {},
".php": {},
".py": {},
".rb": {},
".scss": {},
".sql": {},
".scala": {},
".swift": {},
".ts": {},
".vb": {},
".yml": {},
".yaml": {},
".arm": {},
".as": {},
".sh": {},
".cs": {},
".cpp": {},
".c": {},
".css": {},
".cmake": {},
".bat": {},
".dart": {},
".patch": {},
".erl": {},
".go": {},
".html": {},
".xml": {},
".hs": {},
".ini": {},
".json": {},
".java": {},
".js": {},
".less": {},
".lua": {},
".php": {},
".py": {},
".rb": {},
".rs": {},
".scss": {},
".sql": {},
".scala": {},
".swift": {},
".ts": {},
".vb": {},
".yml": {},
".yaml": {},
}
// Extensions that are not same as highlight classes.
highlightMapping = map[string]string{
".txt": "nohighlight",
".txt": "nohighlight",
".escript": "erlang",
".ex": "elixir",
".exs": "elixir",
}
)
+17 -18
View File
@@ -263,7 +263,7 @@ func (r *Request) getResponse() (*http.Response, error) {
}
if r.req.Method == "GET" && len(paramBody) > 0 {
if strings.Index(r.url, "?") != -1 {
if strings.Contains(r.url, "?") {
r.url += "&" + paramBody
} else {
r.url = r.url + "?" + paramBody
@@ -290,10 +290,13 @@ func (r *Request) getResponse() (*http.Response, error) {
}
}
for k, v := range r.params {
bodyWriter.WriteField(k, v)
err := bodyWriter.WriteField(k, v)
if err != nil {
log.Fatal(err)
}
}
bodyWriter.Close()
pw.Close()
_ = bodyWriter.Close()
_ = pw.Close()
}()
r.Header("Content-Type", bodyWriter.FormDataContentType())
r.req.Body = ioutil.NopCloser(pr)
@@ -323,18 +326,15 @@ func (r *Request) getResponse() (*http.Response, error) {
Proxy: proxy,
Dial: TimeoutDialer(r.setting.ConnectTimeout, r.setting.ReadWriteTimeout),
}
} else {
// if r.transport is *http.Transport then set the settings.
if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil {
t.TLSClientConfig = r.setting.TLSClientConfig
}
if t.Proxy == nil {
t.Proxy = r.setting.Proxy
}
if t.Dial == nil {
t.Dial = TimeoutDialer(r.setting.ConnectTimeout, r.setting.ReadWriteTimeout)
}
} else if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil {
t.TLSClientConfig = r.setting.TLSClientConfig
}
if t.Proxy == nil {
t.Proxy = r.setting.Proxy
}
if t.Dial == nil {
t.Dial = TimeoutDialer(r.setting.ConnectTimeout, r.setting.ReadWriteTimeout)
}
}
@@ -461,7 +461,6 @@ func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, ad
if err != nil {
return nil, err
}
conn.SetDeadline(time.Now().Add(rwTimeout))
return conn, nil
return conn, conn.SetDeadline(time.Now().Add(rwTimeout))
}
}
-17
View File
@@ -5,7 +5,6 @@
package indexer
import (
"fmt"
"os"
"strconv"
@@ -24,15 +23,6 @@ func indexerID(id int64) string {
return strconv.FormatInt(id, 36)
}
// idOfIndexerID the integer id associated with an indexer id
func idOfIndexerID(indexerID string) (int64, error) {
id, err := strconv.ParseInt(indexerID, 36, 64)
if err != nil {
return 0, fmt.Errorf("Unexpected indexer ID %s: %v", indexerID, err)
}
return id, nil
}
// numericEqualityQuery a numeric equality query for the given value and field
func numericEqualityQuery(value int64, field string) *query.NumericRangeQuery {
f := float64(value)
@@ -42,13 +32,6 @@ func numericEqualityQuery(value int64, field string) *query.NumericRangeQuery {
return q
}
func newMatchPhraseQuery(matchPhrase, field, analyzer string) *query.MatchPhraseQuery {
q := bleve.NewMatchPhraseQuery(matchPhrase)
q.FieldVal = field
q.Analyzer = analyzer
return q
}
const unicodeNormalizeName = "unicodeNormalize"
func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error {
+9 -4
View File
@@ -101,7 +101,12 @@ func InitIssueIndexer(syncReindex bool) error {
return fmt.Errorf("Unsupported indexer queue type: %v", setting.Indexer.IssueQueueType)
}
go issueIndexerQueue.Run()
go func() {
err = issueIndexerQueue.Run()
if err != nil {
log.Error("issueIndexerQueue.Run: %v", err)
}
}()
if populate {
if syncReindex {
@@ -161,7 +166,7 @@ func UpdateIssueIndexer(issue *models.Issue) {
comments = append(comments, comment.Content)
}
}
issueIndexerQueue.Push(&IndexerData{
_ = issueIndexerQueue.Push(&IndexerData{
ID: issue.ID,
RepoID: issue.RepoID,
Title: issue.Title,
@@ -179,11 +184,11 @@ func DeleteRepoIssueIndexer(repo *models.Repository) {
return
}
if len(ids) <= 0 {
if len(ids) == 0 {
return
}
issueIndexerQueue.Push(&IndexerData{
_ = issueIndexerQueue.Push(&IndexerData{
IDs: ids,
IsDelete: true,
})
+3 -3
View File
@@ -34,20 +34,20 @@ func (c *ChannelQueue) Run() error {
select {
case data := <-c.queue:
if data.IsDelete {
c.indexer.Delete(data.IDs...)
_ = c.indexer.Delete(data.IDs...)
continue
}
datas = append(datas, data)
if len(datas) >= c.batchNumber {
c.indexer.Index(datas)
_ = c.indexer.Index(datas)
// TODO: save the point
datas = make([]*IndexerData, 0, c.batchNumber)
}
case <-time.After(time.Millisecond * 100):
i++
if i >= 3 && len(datas) > 0 {
c.indexer.Index(datas)
_ = c.indexer.Index(datas)
// TODO: save the point
datas = make([]*IndexerData, 0, c.batchNumber)
}
+2 -2
View File
@@ -44,7 +44,7 @@ func (l *LevelQueue) Run() error {
for {
i++
if len(datas) > l.batchNumber || (len(datas) > 0 && i > 3) {
l.indexer.Index(datas)
_ = l.indexer.Index(datas)
datas = make([]*IndexerData, 0, l.batchNumber)
i = 0
continue
@@ -59,7 +59,7 @@ func (l *LevelQueue) Run() error {
continue
}
if len(bs) <= 0 {
if len(bs) == 0 {
time.Sleep(time.Millisecond * 100)
continue
}
+2 -2
View File
@@ -96,12 +96,12 @@ func (r *RedisQueue) Run() error {
i++
if len(datas) > r.batchNumber || (len(datas) > 0 && i > 3) {
r.indexer.Index(datas)
_ = r.indexer.Index(datas)
datas = make([]*IndexerData, 0, r.batchNumber)
i = 0
}
if len(bs) <= 0 {
if len(bs) == 0 {
time.Sleep(time.Millisecond * 100)
continue
}
+88 -44
View File
@@ -11,12 +11,13 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
)
//checkIsValidRequest check if it a valid request in case of bad request it write the response to ctx.
func checkIsValidRequest(ctx *context.Context, post bool) bool {
func checkIsValidRequest(ctx *context.Context) bool {
if !setting.LFS.StartServer {
writeStatus(ctx, 404)
return false
@@ -34,17 +35,10 @@ func checkIsValidRequest(ctx *context.Context, post bool) bool {
}
ctx.User = user
}
if post {
mediaParts := strings.Split(ctx.Req.Header.Get("Content-Type"), ";")
if mediaParts[0] != metaMediaType {
writeStatus(ctx, 400)
return false
}
}
return true
}
func handleLockListOut(ctx *context.Context, lock *models.LFSLock, err error) {
func handleLockListOut(ctx *context.Context, repo *models.Repository, lock *models.LFSLock, err error) {
if err != nil {
if models.IsErrLFSLockNotExist(err) {
ctx.JSON(200, api.LFSLockList{
@@ -57,7 +51,7 @@ func handleLockListOut(ctx *context.Context, lock *models.LFSLock, err error) {
})
return
}
if ctx.Repo.Repository.ID != lock.RepoID {
if repo.ID != lock.RepoID {
ctx.JSON(200, api.LFSLockList{
Locks: []*api.LFSLock{},
})
@@ -70,22 +64,26 @@ func handleLockListOut(ctx *context.Context, lock *models.LFSLock, err error) {
// GetListLockHandler list locks
func GetListLockHandler(ctx *context.Context) {
if !checkIsValidRequest(ctx, false) {
if !checkIsValidRequest(ctx) {
return
}
ctx.Resp.Header().Set("Content-Type", metaMediaType)
err := models.CheckLFSAccessForRepo(ctx.User, ctx.Repo.Repository, models.AccessModeRead)
rv := unpack(ctx)
repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
if err != nil {
if models.IsErrLFSUnauthorizedAction(err) {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
ctx.JSON(401, api.LFSLockError{
Message: "You must have pull access to list locks : " + err.Error(),
})
return
}
ctx.JSON(500, api.LFSLockError{
Message: "unable to list lock : " + err.Error(),
log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
writeStatus(ctx, 404)
return
}
repository.MustOwner()
authenticated := authenticate(ctx, repository, rv.Authorization, false)
if !authenticated {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
ctx.JSON(401, api.LFSLockError{
Message: "You must have pull access to list locks",
})
return
}
@@ -100,19 +98,19 @@ func GetListLockHandler(ctx *context.Context) {
return
}
lock, err := models.GetLFSLockByID(int64(v))
handleLockListOut(ctx, lock, err)
handleLockListOut(ctx, repository, lock, err)
return
}
path := ctx.Query("path")
if path != "" { //Case where we request a specific id
lock, err := models.GetLFSLock(ctx.Repo.Repository, path)
handleLockListOut(ctx, lock, err)
lock, err := models.GetLFSLock(repository, path)
handleLockListOut(ctx, repository, lock, err)
return
}
//If no query params path or id
lockList, err := models.GetLFSLockByRepoID(ctx.Repo.Repository.ID)
lockList, err := models.GetLFSLockByRepoID(repository.ID)
if err != nil {
ctx.JSON(500, api.LFSLockError{
Message: "unable to list locks : " + err.Error(),
@@ -130,21 +128,41 @@ func GetListLockHandler(ctx *context.Context) {
// PostLockHandler create lock
func PostLockHandler(ctx *context.Context) {
if !checkIsValidRequest(ctx, false) {
if !checkIsValidRequest(ctx) {
return
}
ctx.Resp.Header().Set("Content-Type", metaMediaType)
userName := ctx.Params("username")
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
authorization := ctx.Req.Header.Get("Authorization")
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
if err != nil {
log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
writeStatus(ctx, 404)
return
}
repository.MustOwner()
authenticated := authenticate(ctx, repository, authorization, true)
if !authenticated {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
ctx.JSON(401, api.LFSLockError{
Message: "You must have push access to create locks",
})
return
}
var req api.LFSLockRequest
dec := json.NewDecoder(ctx.Req.Body().ReadCloser())
err := dec.Decode(&req)
if err != nil {
if err := dec.Decode(&req); err != nil {
writeStatus(ctx, 400)
return
}
lock, err := models.CreateLFSLock(&models.LFSLock{
Repo: ctx.Repo.Repository,
Repo: repository,
Path: req.Path,
Owner: ctx.User,
})
@@ -173,28 +191,34 @@ func PostLockHandler(ctx *context.Context) {
// VerifyLockHandler list locks for verification
func VerifyLockHandler(ctx *context.Context) {
if !checkIsValidRequest(ctx, false) {
if !checkIsValidRequest(ctx) {
return
}
ctx.Resp.Header().Set("Content-Type", metaMediaType)
err := models.CheckLFSAccessForRepo(ctx.User, ctx.Repo.Repository, models.AccessModeWrite)
userName := ctx.Params("username")
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
authorization := ctx.Req.Header.Get("Authorization")
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
if err != nil {
if models.IsErrLFSUnauthorizedAction(err) {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
ctx.JSON(401, api.LFSLockError{
Message: "You must have push access to verify locks : " + err.Error(),
})
return
}
ctx.JSON(500, api.LFSLockError{
Message: "unable to verify lock : " + err.Error(),
log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
writeStatus(ctx, 404)
return
}
repository.MustOwner()
authenticated := authenticate(ctx, repository, authorization, true)
if !authenticated {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
ctx.JSON(401, api.LFSLockError{
Message: "You must have push access to verify locks",
})
return
}
//TODO handle body json cursor and limit
lockList, err := models.GetLFSLockByRepoID(ctx.Repo.Repository.ID)
lockList, err := models.GetLFSLockByRepoID(repository.ID)
if err != nil {
ctx.JSON(500, api.LFSLockError{
Message: "unable to list locks : " + err.Error(),
@@ -218,15 +242,35 @@ func VerifyLockHandler(ctx *context.Context) {
// UnLockHandler delete locks
func UnLockHandler(ctx *context.Context) {
if !checkIsValidRequest(ctx, false) {
if !checkIsValidRequest(ctx) {
return
}
ctx.Resp.Header().Set("Content-Type", metaMediaType)
userName := ctx.Params("username")
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
authorization := ctx.Req.Header.Get("Authorization")
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
if err != nil {
log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
writeStatus(ctx, 404)
return
}
repository.MustOwner()
authenticated := authenticate(ctx, repository, authorization, true)
if !authenticated {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
ctx.JSON(401, api.LFSLockError{
Message: "You must have push access to delete locks",
})
return
}
var req api.LFSLockDeleteRequest
dec := json.NewDecoder(ctx.Req.Body().ReadCloser())
err := dec.Decode(&req)
if err != nil {
if err := dec.Decode(&req); err != nil {
writeStatus(ctx, 400)
return
}
+21 -24
View File
@@ -22,8 +22,7 @@ import (
)
const (
contentMediaType = "application/vnd.git-lfs"
metaMediaType = contentMediaType + "+json"
metaMediaType = "application/vnd.git-lfs+json"
)
// RequestVars contain variables from the HTTP request. Variables from routing, json body decoding, and
@@ -101,11 +100,10 @@ func ObjectOidHandler(ctx *context.Context) {
getMetaHandler(ctx)
return
}
if ContentMatcher(ctx.Req) || len(ctx.Params("filename")) > 0 {
getContentHandler(ctx)
return
}
} else if ctx.Req.Method == "PUT" && ContentMatcher(ctx.Req) {
getContentHandler(ctx)
return
} else if ctx.Req.Method == "PUT" {
PutHandler(ctx)
return
}
@@ -154,7 +152,7 @@ func getContentHandler(ctx *context.Context) {
if rangeHdr := ctx.Req.Header.Get("Range"); rangeHdr != "" {
regex := regexp.MustCompile(`bytes=(\d+)\-.*`)
match := regex.FindStringSubmatch(rangeHdr)
if match != nil && len(match) > 1 {
if len(match) > 1 {
statusCode = 206
fromByte, _ = strconv.ParseInt(match[1], 10, 32)
ctx.Resp.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", fromByte, meta.Size-1, meta.Size-fromByte))
@@ -180,8 +178,8 @@ func getContentHandler(ctx *context.Context) {
}
ctx.Resp.WriteHeader(statusCode)
io.Copy(ctx.Resp, content)
content.Close()
_, _ = io.Copy(ctx.Resp, content)
_ = content.Close()
logRequest(ctx.Req, statusCode)
}
@@ -198,7 +196,7 @@ func getMetaHandler(ctx *context.Context) {
if ctx.Req.Method == "GET" {
enc := json.NewEncoder(ctx.Resp)
enc.Encode(Represent(rv, meta, true, false))
_ = enc.Encode(Represent(rv, meta, true, false))
}
logRequest(ctx.Req, 200)
@@ -251,7 +249,7 @@ func PostHandler(ctx *context.Context) {
ctx.Resp.WriteHeader(sentStatus)
enc := json.NewEncoder(ctx.Resp)
enc.Encode(Represent(rv, meta, meta.Existing, true))
_ = enc.Encode(Represent(rv, meta, meta.Existing, true))
logRequest(ctx.Req, sentStatus)
}
@@ -315,7 +313,7 @@ func BatchHandler(ctx *context.Context) {
respobj := &BatchResponse{Objects: responseObjects}
enc := json.NewEncoder(ctx.Resp)
enc.Encode(respobj)
_ = enc.Encode(respobj)
logRequest(ctx.Req, 200)
}
@@ -348,7 +346,7 @@ func VerifyHandler(ctx *context.Context) {
return
}
if !ContentMatcher(ctx.Req) {
if !MetaMatcher(ctx.Req) {
writeStatus(ctx, 400)
return
}
@@ -385,7 +383,6 @@ func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload boo
}
header := make(map[string]string)
header["Accept"] = contentMediaType
if rv.Authorization == "" {
//https://github.com/github/git-lfs/issues/1088
@@ -404,20 +401,20 @@ func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload boo
if upload && !download {
// Force client side verify action while gitea lacks proper server side verification
rep.Actions["verify"] = &link{Href: rv.VerifyLink(), Header: header}
verifyHeader := make(map[string]string)
for k, v := range header {
verifyHeader[k] = v
}
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
verifyHeader["Accept"] = metaMediaType
rep.Actions["verify"] = &link{Href: rv.VerifyLink(), Header: verifyHeader}
}
return rep
}
// ContentMatcher provides a mux.MatcherFunc that only allows requests that contain
// an Accept header with the contentMediaType
func ContentMatcher(r macaron.Request) bool {
mediaParts := strings.Split(r.Header.Get("Accept"), ";")
mt := mediaParts[0]
return mt == contentMediaType
}
// MetaMatcher provides a mux.MatcherFunc that only allows requests that contain
// an Accept header with the metaMediaType
func MetaMatcher(r macaron.Request) bool {
+4 -4
View File
@@ -208,7 +208,7 @@ normalLoop:
if i > lasti {
written, err := c.w.Write(bytes[lasti:i])
totalWritten = totalWritten + written
totalWritten += written
if err != nil {
return totalWritten, err
}
@@ -243,7 +243,7 @@ normalLoop:
if bytes[j] == 'm' {
if c.mode == allowColor {
written, err := c.w.Write(bytes[i : j+1])
totalWritten = totalWritten + written
totalWritten += written
if err != nil {
return totalWritten, err
}
@@ -278,7 +278,7 @@ func ColorSprintf(format string, args ...interface{}) string {
}
return fmt.Sprintf(format, v...)
}
return fmt.Sprintf(format)
return format
}
// ColorFprintf will write to the provided writer similar to ColorSprintf
@@ -290,7 +290,7 @@ func ColorFprintf(w io.Writer, format string, args ...interface{}) (int, error)
}
return fmt.Fprintf(w, format, v...)
}
return fmt.Fprintf(w, format)
return fmt.Fprint(w, format)
}
// ColorFormatted structs provide their own colored string when formatted with ColorSprintf
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2019 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 log
import (
"time"
)
var statusToColor = map[int][]byte{
100: ColorBytes(Bold),
200: ColorBytes(FgGreen),
300: ColorBytes(FgYellow),
304: ColorBytes(FgCyan),
400: ColorBytes(Bold, FgRed),
401: ColorBytes(Bold, FgMagenta),
403: ColorBytes(Bold, FgMagenta),
500: ColorBytes(Bold, BgRed),
}
// ColoredStatus addes colors for HTTP status
func ColoredStatus(status int, s ...string) *ColoredValue {
color, ok := statusToColor[status]
if !ok {
color, ok = statusToColor[(status/100)*100]
}
if !ok {
color = fgBoldBytes
}
if len(s) > 0 {
return NewColoredValueBytes(s[0], &color)
}
return NewColoredValueBytes(status, &color)
}
var methodToColor = map[string][]byte{
"GET": ColorBytes(FgBlue),
"POST": ColorBytes(FgGreen),
"DELETE": ColorBytes(FgRed),
"PATCH": ColorBytes(FgCyan),
"PUT": ColorBytes(FgYellow, Faint),
"HEAD": ColorBytes(FgBlue, Faint),
}
// ColoredMethod addes colors for HtTP methos on log
func ColoredMethod(method string) *ColoredValue {
color, ok := methodToColor[method]
if !ok {
return NewColoredValueBytes(method, &fgBoldBytes)
}
return NewColoredValueBytes(method, &color)
}
var (
durations = []time.Duration{
10 * time.Millisecond,
100 * time.Millisecond,
1 * time.Second,
5 * time.Second,
10 * time.Second,
}
durationColors = [][]byte{
ColorBytes(FgGreen),
ColorBytes(Bold),
ColorBytes(FgYellow),
ColorBytes(FgRed, Bold),
ColorBytes(BgRed),
}
wayTooLong = ColorBytes(BgMagenta)
)
// ColoredTime addes colors for time on log
func ColoredTime(duration time.Duration) *ColoredValue {
for i, k := range durations {
if duration < k {
return NewColoredValueBytes(duration, &durationColors[i])
}
}
return NewColoredValueBytes(duration, &wayTooLong)
}
+4 -1
View File
@@ -67,7 +67,10 @@ func (i *connWriter) connect() error {
}
if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
err = tcpConn.SetKeepAlive(true)
if err != nil {
return err
}
}
i.innerWriter = conn
-1
View File
@@ -24,7 +24,6 @@ func listenReadAndClose(t *testing.T, l net.Listener, expected string) {
assert.NoError(t, err)
assert.Equal(t, expected, string(written))
return
}
func TestConnLogger(t *testing.T) {
+1 -3
View File
@@ -79,7 +79,7 @@ func (l *ChannelledLog) Start() {
return
}
l.loggerProvider.Flush()
case _, _ = <-l.close:
case <-l.close:
l.closeLogger()
return
}
@@ -104,7 +104,6 @@ func (l *ChannelledLog) closeLogger() {
l.loggerProvider.Flush()
l.loggerProvider.Close()
l.closed <- true
return
}
// Close this ChannelledLog
@@ -228,7 +227,6 @@ func (m *MultiChannelledLog) closeLoggers() {
}
m.mutex.Unlock()
m.closed <- true
return
}
// Start processing the MultiChannelledLog
+2 -2
View File
@@ -223,7 +223,7 @@ func compressOldLogFile(fname string, compressionLevel int) error {
func (log *FileLogger) deleteOldLog() {
dir := filepath.Dir(log.Filename)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
defer func() {
if r := recover(); r != nil {
returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r)
@@ -246,7 +246,7 @@ func (log *FileLogger) deleteOldLog() {
// there are no buffering messages in file logger in memory.
// flush file means sync file from disk.
func (log *FileLogger) Flush() {
log.mw.fd.Sync()
_ = log.mw.fd.Sync()
}
// GetName returns the default name for this implementation
+4 -4
View File
@@ -103,7 +103,7 @@ func TestFileLogger(t *testing.T) {
assert.Equal(t, expected, string(logData))
event.level = WARN
expected = expected + fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg)
expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg)
fileLogger.LogEvent(&event)
fileLogger.Flush()
logData, err = ioutil.ReadFile(filename)
@@ -130,7 +130,7 @@ func TestFileLogger(t *testing.T) {
err = realFileLogger.DoRotate()
assert.Error(t, err)
expected = expected + fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg)
expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg)
fileLogger.LogEvent(&event)
fileLogger.Flush()
logData, err = ioutil.ReadFile(filename)
@@ -138,7 +138,7 @@ func TestFileLogger(t *testing.T) {
assert.Equal(t, expected, string(logData))
// Should fail to rotate
expected = expected + fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg)
expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg)
fileLogger.LogEvent(&event)
fileLogger.Flush()
logData, err = ioutil.ReadFile(filename)
@@ -188,7 +188,7 @@ func TestCompressFileLogger(t *testing.T) {
assert.Equal(t, expected, string(logData))
event.level = WARN
expected = expected + fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg)
expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg)
fileLogger.LogEvent(&event)
fileLogger.Flush()
logData, err = ioutil.ReadFile(filename)
+1 -1
View File
@@ -57,7 +57,7 @@ func FlagsFromString(from string) int {
for _, flag := range strings.Split(strings.ToLower(from), ",") {
f, ok := flagFromString[strings.TrimSpace(flag)]
if ok {
flags = flags | f
flags |= f
}
}
return flags
+2 -19
View File
@@ -5,9 +5,7 @@
package log
import (
"fmt"
"os"
"path"
"runtime"
"strings"
)
@@ -17,9 +15,7 @@ var (
DEFAULT = "default"
// NamedLoggers map of named loggers
NamedLoggers = make(map[string]*Logger)
// GitLogger logger for git
GitLogger *Logger
prefix string
prefix string
)
// NewLogger create a logger for the default logger
@@ -72,19 +68,6 @@ func GetLogger(name string) *Logger {
return NamedLoggers[DEFAULT]
}
// NewGitLogger create a logger for git
// FIXME: use same log level as other loggers.
func NewGitLogger(logPath string) {
path := path.Dir(logPath)
if err := os.MkdirAll(path, os.ModePerm); err != nil {
Fatal("Failed to create dir %s: %v", path, err)
}
GitLogger = newLogger("git", 0)
GitLogger.SetLogger("file", "file", fmt.Sprintf(`{"level":"TRACE","filename":"%s","rotate":false}`, logPath))
}
// GetLevel returns the minimum logger level
func GetLevel() Level {
return NamedLoggers[DEFAULT].GetLevel()
@@ -235,7 +218,7 @@ func (l *LoggerAsWriter) Write(p []byte) (int, error) {
func (l *LoggerAsWriter) Log(msg string) {
for _, logger := range l.ourLoggers {
// Set the skip to reference the call just above this
logger.Log(1, l.level, msg)
_ = logger.Log(1, l.level, msg)
}
}
-103
View File
@@ -1,103 +0,0 @@
// Copyright 2019 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 log
import (
"net/http"
"time"
macaron "gopkg.in/macaron.v1"
)
var statusToColor = map[int][]byte{
100: ColorBytes(Bold),
200: ColorBytes(FgGreen),
300: ColorBytes(FgYellow),
304: ColorBytes(FgCyan),
400: ColorBytes(Bold, FgRed),
401: ColorBytes(Bold, FgMagenta),
403: ColorBytes(Bold, FgMagenta),
500: ColorBytes(Bold, BgRed),
}
func coloredStatus(status int, s ...string) *ColoredValue {
color, ok := statusToColor[status]
if !ok {
color, ok = statusToColor[(status/100)*100]
}
if !ok {
color = fgBoldBytes
}
if len(s) > 0 {
return NewColoredValueBytes(s[0], &color)
}
return NewColoredValueBytes(status, &color)
}
var methodToColor = map[string][]byte{
"GET": ColorBytes(FgBlue),
"POST": ColorBytes(FgGreen),
"DELETE": ColorBytes(FgRed),
"PATCH": ColorBytes(FgCyan),
"PUT": ColorBytes(FgYellow, Faint),
"HEAD": ColorBytes(FgBlue, Faint),
}
func coloredMethod(method string) *ColoredValue {
color, ok := methodToColor[method]
if !ok {
return NewColoredValueBytes(method, &fgBoldBytes)
}
return NewColoredValueBytes(method, &color)
}
var durations = []time.Duration{
10 * time.Millisecond,
100 * time.Millisecond,
1 * time.Second,
5 * time.Second,
10 * time.Second,
}
var durationColors = [][]byte{
ColorBytes(FgGreen),
ColorBytes(Bold),
ColorBytes(FgYellow),
ColorBytes(FgRed, Bold),
ColorBytes(BgRed),
}
var wayTooLong = ColorBytes(BgMagenta)
func coloredTime(duration time.Duration) *ColoredValue {
for i, k := range durations {
if duration < k {
return NewColoredValueBytes(duration, &durationColors[i])
}
}
return NewColoredValueBytes(duration, &wayTooLong)
}
// SetupRouterLogger will setup macaron to routing to the main gitea log
func SetupRouterLogger(m *macaron.Macaron, level Level) {
if GetLevel() <= level {
m.Use(RouterHandler(level))
}
}
// RouterHandler is a macaron handler that will log the routing to the default gitea log
func RouterHandler(level Level) func(ctx *macaron.Context) {
return func(ctx *macaron.Context) {
start := time.Now()
GetLogger("router").Log(0, level, "Started %s %s for %s", coloredMethod(ctx.Req.Method), ctx.Req.RequestURI, ctx.RemoteAddr())
rw := ctx.Resp.(macaron.ResponseWriter)
ctx.Next()
status := rw.Status()
GetLogger("router").Log(0, level, "Completed %s %s %v %s in %v", coloredMethod(ctx.Req.Method), ctx.Req.RequestURI, coloredStatus(status), coloredStatus(status, http.StatusText(rw.Status())), coloredTime(time.Since(start)))
}
}
-4
View File
@@ -11,10 +11,6 @@ import (
"strings"
)
const (
subjectPhrase = "Diagnostic message from server"
)
type smtpWriter struct {
owner *SMTPLogger
}
+1 -4
View File
@@ -252,10 +252,7 @@ func (logger *WriterLogger) Match(event *Event) bool {
mode: removeColor,
}).Write([]byte(event.msg))
msg = baw
if logger.regexp.Match(msg) {
return true
}
return false
return logger.regexp.Match(msg)
}
// Close the base logger
-112
View File
@@ -1,112 +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 log
import (
"fmt"
"github.com/go-xorm/core"
)
// XORMLogBridge a logger bridge from Logger to xorm
type XORMLogBridge struct {
showSQL bool
level core.LogLevel
}
var (
// XORMLogger the logger for xorm
XORMLogger *XORMLogBridge
)
// InitXORMLogger inits a log bridge for xorm
func InitXORMLogger(showSQL bool) {
XORMLogger = &XORMLogBridge{
showSQL: showSQL,
}
}
// GetGiteaLevel returns the minimum Gitea logger level
func (l *XORMLogBridge) GetGiteaLevel() Level {
return GetLogger("xorm").GetLevel()
}
// Log a message with defined skip and at logging level
func (l *XORMLogBridge) Log(skip int, level Level, format string, v ...interface{}) error {
return GetLogger("xorm").Log(skip+1, level, format, v...)
}
// Debug show debug log
func (l *XORMLogBridge) Debug(v ...interface{}) {
l.Log(2, DEBUG, fmt.Sprint(v...))
}
// Debugf show debug log
func (l *XORMLogBridge) Debugf(format string, v ...interface{}) {
l.Log(2, DEBUG, format, v...)
}
// Error show error log
func (l *XORMLogBridge) Error(v ...interface{}) {
l.Log(2, ERROR, fmt.Sprint(v...))
}
// Errorf show error log
func (l *XORMLogBridge) Errorf(format string, v ...interface{}) {
l.Log(2, ERROR, format, v...)
}
// Info show information level log
func (l *XORMLogBridge) Info(v ...interface{}) {
l.Log(2, INFO, fmt.Sprint(v...))
}
// Infof show information level log
func (l *XORMLogBridge) Infof(format string, v ...interface{}) {
l.Log(2, INFO, format, v...)
}
// Warn show warning log
func (l *XORMLogBridge) Warn(v ...interface{}) {
l.Log(2, WARN, fmt.Sprint(v...))
}
// Warnf show warnning log
func (l *XORMLogBridge) Warnf(format string, v ...interface{}) {
l.Log(2, WARN, format, v...)
}
// Level get logger level
func (l *XORMLogBridge) Level() core.LogLevel {
switch l.GetGiteaLevel() {
case TRACE, DEBUG:
return core.LOG_DEBUG
case INFO:
return core.LOG_INFO
case WARN:
return core.LOG_WARNING
case ERROR, CRITICAL:
return core.LOG_ERR
}
return core.LOG_OFF
}
// SetLevel set the logger level
func (l *XORMLogBridge) SetLevel(lvl core.LogLevel) {
}
// ShowSQL set if record SQL
func (l *XORMLogBridge) ShowSQL(show ...bool) {
if len(show) > 0 {
l.showSQL = show[0]
} else {
l.showSQL = true
}
}
// IsShowSQL if record SQL
func (l *XORMLogBridge) IsShowSQL() bool {
return l.showSQL
}
+6 -9
View File
@@ -258,15 +258,12 @@ func (s *dummySender) Send(from string, to []string, msg io.WriterTo) error {
}
func processMailQueue() {
for {
select {
case msg := <-mailQueue:
log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
if err := gomail.Send(Sender, msg.Message); err != nil {
log.Error("Failed to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
} else {
log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)
}
for msg := range mailQueue {
log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
if err := gomail.Send(Sender, msg.Message); err != nil {
log.Error("Failed to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
} else {
log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)
}
}
}
+2 -33
View File
@@ -108,24 +108,6 @@ func FindAllMentions(content string) []string {
return ret
}
// cutoutVerbosePrefix cutouts URL prefix including sub-path to
// return a clean unified string of request URL path.
func cutoutVerbosePrefix(prefix string) string {
if len(prefix) == 0 || prefix[0] != '/' {
return prefix
}
count := 0
for i := 0; i < len(prefix); i++ {
if prefix[i] == '/' {
count++
}
if count >= 3+setting.AppSubURLDepth {
return prefix[:i]
}
}
return prefix
}
// IsSameDomain checks if given url string has the same hostname as current Gitea instance
func IsSameDomain(s string) bool {
if strings.HasPrefix(s, "/") {
@@ -146,7 +128,7 @@ type postProcessError struct {
}
func (p *postProcessError) Error() string {
return "PostProcess: " + p.context + ", " + p.Error()
return "PostProcess: " + p.context + ", " + p.err.Error()
}
type processor func(ctx *postProcessCtx, node *html.Node)
@@ -304,20 +286,6 @@ func (ctx *postProcessCtx) visitNode(node *html.Node) {
// ignore everything else
}
func (ctx *postProcessCtx) visitNodeForShortLinks(node *html.Node) {
switch node.Type {
case html.TextNode:
shortLinkProcessorFull(ctx, node, true)
case html.ElementNode:
if node.Data == "code" || node.Data == "pre" || node.Data == "a" {
return
}
for n := node.FirstChild; n != nil; n = n.NextSibling {
ctx.visitNodeForShortLinks(n)
}
}
}
// textNode runs the passed node through various processors, in order to handle
// all kinds of special links handled by the post-processing.
func (ctx *postProcessCtx) textNode(node *html.Node) {
@@ -355,6 +323,7 @@ func createCodeLink(href, content string) *html.Node {
code := &html.Node{
Type: html.ElementNode,
Data: atom.Code.String(),
Attr: []html.Attribute{{Key: "class", Val: "nohighlight"}},
}
code.AppendChild(text)
+4 -9
View File
@@ -29,11 +29,6 @@ func numericIssueLink(baseURL string, index int) string {
return link(util.URLJoin(baseURL, strconv.Itoa(index)), fmt.Sprintf("#%d", index))
}
// urlContentsLink an HTML link whose contents is the target URL
func urlContentsLink(href string) string {
return link(href, href)
}
// link an HTML link
func link(href, contents string) string {
return fmt.Sprintf("<a href=\"%s\">%s</a>", href, contents)
@@ -202,13 +197,13 @@ func TestRender_AutoLink(t *testing.T) {
// render valid commit URLs
tmp := util.URLJoin(AppSubURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae")
test(tmp, "<a href=\""+tmp+"\"><code>d8a994ef24</code></a>")
test(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">d8a994ef24</code></a>")
tmp += "#diff-2"
test(tmp, "<a href=\""+tmp+"\"><code>d8a994ef24 (diff-2)</code></a>")
test(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>")
// render other commit URLs
tmp = "https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2"
test(tmp, "<a href=\""+tmp+"\"><code>d8a994ef24 (diff-2)</code></a>")
tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2"
test(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>")
}
func TestRender_FullIssueURLs(t *testing.T) {
+8
View File
@@ -15,6 +15,14 @@ import (
func Init() {
getIssueFullPattern()
NewSanitizer()
// since setting maybe changed extensions, this will reload all parser extensions mapping
extParsers = make(map[string]Parser)
for _, parser := range parsers {
for _, ext := range parser.Extensions() {
extParsers[strings.ToLower(ext)] = parser
}
}
}
// Parser defines an interface for parsering markup file to HTML
+2
View File
@@ -9,6 +9,8 @@ import "time"
// Comment is a standard comment information
type Comment struct {
IssueIndex int64
PosterID int64
PosterName string
PosterEmail string
Created time.Time
+2 -2
View File
@@ -11,9 +11,9 @@ type Downloader interface {
GetMilestones() ([]*Milestone, error)
GetReleases() ([]*Release, error)
GetLabels() ([]*Label, error)
GetIssues(start, limit int) ([]*Issue, error)
GetIssues(page, perPage int) ([]*Issue, bool, error)
GetComments(issueNumber int64) ([]*Comment, error)
GetPullRequests(start, limit int) ([]*PullRequest, error)
GetPullRequests(page, perPage int) ([]*PullRequest, error)
}
// DownloaderFactory defines an interface to match a downloader implementation and create a downloader
+1
View File
@@ -10,6 +10,7 @@ import "time"
// Issue is a standard issue information
type Issue struct {
Number int64
PosterID int64
PosterName string
PosterEmail string
Title string
+10 -10
View File
@@ -12,15 +12,15 @@ type MigrateOptions struct {
AuthPassword string
Name string
Description string
OriginalURL string
Wiki bool
Issues bool
Milestones bool
Labels bool
Releases bool
Comments bool
PullRequests bool
Private bool
Mirror bool
IgnoreIssueAuthor bool // if true will not add original author information before issues or comments content.
Wiki bool
Issues bool
Milestones bool
Labels bool
Releases bool
Comments bool
PullRequests bool
Private bool
Mirror bool
}
+1
View File
@@ -15,6 +15,7 @@ type PullRequest struct {
Number int64
Title string
PosterName string
PosterID int64
PosterEmail string
Content string
Milestone string

Some files were not shown because too many files have changed in this diff Show More