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:
+2
-10
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 521 B |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 159 B |
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Vendored
+9
-3
@@ -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
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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", ¬e)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("Note contents\n"), note.Message)
|
||||
assert.Equal(t, "Vladimir Panteleev", note.Commit.Author.Name)
|
||||
}
|
||||
+20
-6
@@ -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
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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 --
|
||||
@@ -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
@@ -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
@@ -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]
|
||||
# *~
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
subjectPhrase = "Diagnostic message from server"
|
||||
)
|
||||
|
||||
type smtpWriter struct {
|
||||
owner *SMTPLogger
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user