Compare commits

..

No commits in common. "8030614386b5d3fa02dc294446a344d274b04a26" and "d5b2bf9044caad9fb473312c4816f6c6caad4698" have entirely different histories.

42 changed files with 316 additions and 783 deletions

View File

@ -31,7 +31,7 @@ vscode:
- golang.go
- stylelint.vscode-stylelint
- DavidAnson.vscode-markdownlint
- Vue.volar
- johnsoncodehk.volar
- ms-azuretools.vscode-docker
- zixuanchen.vitest-explorer
- alexcvzz.vscode-sqlite

View File

@ -1,20 +0,0 @@
---
date: "2023-03-02T21:00:00+05:00"
title: "Usage: Gitea Profile READMEs"
slug: "profile-readme"
weight: 12
toc: false
draft: false
menu:
sidebar:
parent: "usage"
name: "Gitea Profile READMEs"
weight: 12
identifier: "profile-readme"
---
# Gitea Profile READMEs
To display a markdown file in your Gitea profile page, simply make a repository named ".profile" and edit the README.md file inside. Gitea will automatically pull this file in and display it above your repositories.
Note. You are welcome to make this repository private. Doing so will hide your source files from public viewing and allow you to privitize certain files. However, the README.md file will be the only file present on your profile. If you wish to have an entirely private .profile repository, remove or rename the README.md file.

View File

@ -108,31 +108,3 @@
is_prerelease: false
is_tag: false
created_unix: 946684803
- id: 9
repo_id: 57
publisher_id: 2
tag_name: "non-existing-target-branch"
lower_tag_name: "non-existing-target-branch"
target: "non-existing"
title: "non-existing-target-branch"
sha1: "cef06e48f2642cd0dc9597b4bea09f4b3f74aad6"
num_commits: 5
is_draft: false
is_prerelease: false
is_tag: false
created_unix: 946684803
- id: 10
repo_id: 57
publisher_id: 2
tag_name: "empty-target-branch"
lower_tag_name: "empty-target-branch"
target: ""
title: "empty-target-branch"
sha1: "cef06e48f2642cd0dc9597b4bea09f4b3f74aad6"
num_commits: 5
is_draft: false
is_prerelease: false
is_tag: false
created_unix: 946684803

View File

@ -72,7 +72,6 @@ type Release struct {
OriginalAuthorID int64 `xorm:"index"`
LowerTagName string
Target string
TargetBehind string `xorm:"-"` // to handle non-existing or empty target
Title string
Sha1 string `xorm:"VARCHAR(40)"`
NumCommits int64

View File

@ -4,20 +4,71 @@
package context
import (
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/typesniffer"
)
type ServeHeaderOptions httplib.ServeHeaderOptions
type ServeHeaderOptions struct {
ContentType string // defaults to "application/octet-stream"
ContentTypeCharset string
ContentLength *int64
Disposition string // defaults to "attachment"
Filename string
CacheDuration time.Duration // defaults to 5 minutes
LastModified time.Time
}
func (ctx *Context) SetServeHeaders(opt *ServeHeaderOptions) {
httplib.ServeSetHeaders(ctx.Resp, (*httplib.ServeHeaderOptions)(opt))
// SetServeHeaders sets necessary content serve headers
func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
header := ctx.Resp.Header()
contentType := typesniffer.ApplicationOctetStream
if opts.ContentType != "" {
if opts.ContentTypeCharset != "" {
contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
} else {
contentType = opts.ContentType
}
}
header.Set("Content-Type", contentType)
header.Set("X-Content-Type-Options", "nosniff")
if opts.ContentLength != nil {
header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
}
if opts.Filename != "" {
disposition := opts.Disposition
if disposition == "" {
disposition = "attachment"
}
backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \"
header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename)))
header.Set("Access-Control-Expose-Headers", "Content-Disposition")
}
duration := opts.CacheDuration
if duration == 0 {
duration = 5 * time.Minute
}
httpcache.SetCacheControlInHeader(header, duration)
if !opts.LastModified.IsZero() {
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
}
}
// ServeContent serves content to http request
func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
httplib.ServeSetHeaders(ctx.Resp, (*httplib.ServeHeaderOptions)(opts))
ctx.SetServeHeaders(opts)
http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r)
}

View File

@ -7,16 +7,32 @@ import (
"net/http"
"testing"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
type mockResponseWriter struct {
header http.Header
}
func (m *mockResponseWriter) Header() http.Header {
return m.header
}
func (m *mockResponseWriter) Write(bytes []byte) (int, error) {
panic("implement me")
}
func (m *mockResponseWriter) WriteHeader(statusCode int) {
panic("implement me")
}
func TestRemoveSessionCookieHeader(t *testing.T) {
w := httplib.NewMockResponseWriter()
w.Header().Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "foo"}).String())
w.Header().Add("Set-Cookie", (&http.Cookie{Name: "other", Value: "bar"}).String())
w := &mockResponseWriter{}
w.header = http.Header{}
w.header.Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "foo"}).String())
w.header.Add("Set-Cookie", (&http.Cookie{Name: "other", Value: "bar"}).String())
assert.Len(t, w.Header().Values("Set-Cookie"), 2)
removeSessionCookieHeader(w)
assert.Len(t, w.Header().Values("Set-Cookie"), 1)

View File

@ -1,35 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package httplib
import (
"bytes"
"net/http"
)
type MockResponseWriter struct {
header http.Header
StatusCode int
BodyBuffer bytes.Buffer
}
func (m *MockResponseWriter) Header() http.Header {
return m.header
}
func (m *MockResponseWriter) Write(bytes []byte) (int, error) {
if m.StatusCode == 0 {
m.StatusCode = http.StatusOK
}
return m.BodyBuffer.Write(bytes)
}
func (m *MockResponseWriter) WriteHeader(statusCode int) {
m.StatusCode = statusCode
}
func NewMockResponseWriter() *MockResponseWriter {
return &MockResponseWriter{header: http.Header{}}
}

View File

@ -1,225 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package httplib
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"path"
"path/filepath"
"strconv"
"strings"
"time"
charsetModule "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
)
type ServeHeaderOptions struct {
ContentType string // defaults to "application/octet-stream"
ContentTypeCharset string
ContentLength *int64
Disposition string // defaults to "attachment"
Filename string
CacheDuration time.Duration // defaults to 5 minutes
LastModified time.Time
}
// ServeSetHeaders sets necessary content serve headers
func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
header := w.Header()
contentType := typesniffer.ApplicationOctetStream
if opts.ContentType != "" {
if opts.ContentTypeCharset != "" {
contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
} else {
contentType = opts.ContentType
}
}
header.Set("Content-Type", contentType)
header.Set("X-Content-Type-Options", "nosniff")
if opts.ContentLength != nil {
header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
}
if opts.Filename != "" {
disposition := opts.Disposition
if disposition == "" {
disposition = "attachment"
}
backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \"
header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename)))
header.Set("Access-Control-Expose-Headers", "Content-Disposition")
}
duration := opts.CacheDuration
if duration == 0 {
duration = 5 * time.Minute
}
httpcache.SetCacheControlInHeader(header, duration)
if !opts.LastModified.IsZero() {
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
}
}
// ServeData download file from io.Reader
func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, filePath string, mineBuf []byte) {
// do not set "Content-Length", because the length could only be set by callers, and it needs to support range requests
opts := &ServeHeaderOptions{
Filename: path.Base(filePath),
}
sniffedType := typesniffer.DetectContentType(mineBuf)
// the "render" parameter came from year 2016: 638dd24c, it doesn't have clear meaning, so I think it could be removed later
isPlain := sniffedType.IsText() || r.FormValue("render") != ""
if setting.MimeTypeMap.Enabled {
fileExtension := strings.ToLower(filepath.Ext(filePath))
opts.ContentType = setting.MimeTypeMap.Map[fileExtension]
}
if opts.ContentType == "" {
if sniffedType.IsBrowsableBinaryType() {
opts.ContentType = sniffedType.GetMimeType()
} else if isPlain {
opts.ContentType = "text/plain"
} else {
opts.ContentType = typesniffer.ApplicationOctetStream
}
}
if isPlain {
charset, err := charsetModule.DetectEncoding(mineBuf)
if err != nil {
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err)
charset = "utf-8"
}
opts.ContentTypeCharset = strings.ToLower(charset)
}
isSVG := sniffedType.IsSvgImage()
// serve types that can present a security risk with CSP
if isSVG {
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
} else if sniffedType.IsPDF() {
// no sandbox attribute for pdf as it breaks rendering in at least safari. this
// should generally be safe as scripts inside PDF can not escape the PDF document
// see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
}
opts.Disposition = "inline"
if isSVG && !setting.UI.SVG.Enabled {
opts.Disposition = "attachment"
}
ServeSetHeaders(w, opts)
}
const mimeDetectionBufferLen = 1024
func ServeContentByReader(r *http.Request, w http.ResponseWriter, filePath string, size int64, reader io.Reader) {
buf := make([]byte, mimeDetectionBufferLen)
n, err := util.ReadAtMost(reader, buf)
if err != nil {
http.Error(w, "serve content: unable to pre-read", http.StatusRequestedRangeNotSatisfiable)
return
}
if n >= 0 {
buf = buf[:n]
}
setServeHeadersByFile(r, w, filePath, buf)
// reset the reader to the beginning
reader = io.MultiReader(bytes.NewReader(buf), reader)
rangeHeader := r.Header.Get("Range")
// if no size or no supported range, serve as 200 (complete response)
if size <= 0 || !strings.HasPrefix(rangeHeader, "bytes=") {
if size >= 0 {
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
}
_, _ = io.Copy(w, reader) // just like http.ServeContent, not necessary to handle the error
return
}
// do our best to support the minimal "Range" request (no support for multiple range: "Range: bytes=0-50, 100-150")
//
// GET /...
// Range: bytes=0-1023
//
// HTTP/1.1 206 Partial Content
// Content-Range: bytes 0-1023/146515
// Content-Length: 1024
_, rangeParts, _ := strings.Cut(rangeHeader, "=")
rangeBytesStart, rangeBytesEnd, found := strings.Cut(rangeParts, "-")
start, err := strconv.ParseInt(rangeBytesStart, 10, 64)
if start < 0 || start >= size {
err = errors.New("invalid start range")
}
if err != nil {
http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
return
}
end, err := strconv.ParseInt(rangeBytesEnd, 10, 64)
if rangeBytesEnd == "" && found {
err = nil
end = size - 1
}
if end >= size {
end = size - 1
}
if end < start {
err = errors.New("invalid end range")
}
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
partialLength := end - start + 1
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, size))
w.Header().Set("Content-Length", strconv.FormatInt(partialLength, 10))
if _, err = io.CopyN(io.Discard, reader, start); err != nil {
http.Error(w, "serve content: unable to skip", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusPartialContent)
_, _ = io.CopyN(w, reader, partialLength) // just like http.ServeContent, not necessary to handle the error
}
func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, filePath string, modTime time.Time, reader io.ReadSeeker) {
buf := make([]byte, mimeDetectionBufferLen)
n, err := util.ReadAtMost(reader, buf)
if err != nil {
http.Error(w, "serve content: unable to read", http.StatusInternalServerError)
return
}
if _, err = reader.Seek(0, io.SeekStart); err != nil {
http.Error(w, "serve content: unable to seek", http.StatusInternalServerError)
return
}
if n >= 0 {
buf = buf[:n]
}
setServeHeadersByFile(r, w, filePath, buf)
http.ServeContent(w, r, path.Base(filePath), modTime, reader)
}

View File

@ -1,109 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package httplib
import (
"fmt"
"net/http"
"net/url"
"os"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestServeContentByReader(t *testing.T) {
data := "0123456789abcdef"
test := func(t *testing.T, expectedStatusCode int, expectedContent string) {
_, rangeStr, _ := strings.Cut(t.Name(), "_range_")
r := &http.Request{Header: http.Header{}, Form: url.Values{}}
if rangeStr != "" {
r.Header.Set("Range", fmt.Sprintf("bytes=%s", rangeStr))
}
reader := strings.NewReader(data)
w := NewMockResponseWriter()
ServeContentByReader(r, w, "test", int64(len(data)), reader)
assert.Equal(t, expectedStatusCode, w.StatusCode)
if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK {
assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length"))
assert.Equal(t, expectedContent, w.BodyBuffer.String())
}
}
t.Run("_range_", func(t *testing.T) {
test(t, http.StatusOK, data)
})
t.Run("_range_0-", func(t *testing.T) {
test(t, http.StatusPartialContent, data)
})
t.Run("_range_0-15", func(t *testing.T) {
test(t, http.StatusPartialContent, data)
})
t.Run("_range_1-", func(t *testing.T) {
test(t, http.StatusPartialContent, data[1:])
})
t.Run("_range_1-3", func(t *testing.T) {
test(t, http.StatusPartialContent, data[1:3+1])
})
t.Run("_range_16-", func(t *testing.T) {
test(t, http.StatusRequestedRangeNotSatisfiable, "")
})
t.Run("_range_1-99999", func(t *testing.T) {
test(t, http.StatusPartialContent, data[1:])
})
}
func TestServeContentByReadSeeker(t *testing.T) {
data := "0123456789abcdef"
tmpFile := t.TempDir() + "/test"
err := os.WriteFile(tmpFile, []byte(data), 0o644)
assert.NoError(t, err)
test := func(t *testing.T, expectedStatusCode int, expectedContent string) {
_, rangeStr, _ := strings.Cut(t.Name(), "_range_")
r := &http.Request{Header: http.Header{}, Form: url.Values{}}
if rangeStr != "" {
r.Header.Set("Range", fmt.Sprintf("bytes=%s", rangeStr))
}
seekReader, err := os.OpenFile(tmpFile, os.O_RDONLY, 0o644)
if !assert.NoError(t, err) {
return
}
defer seekReader.Close()
w := NewMockResponseWriter()
ServeContentByReadSeeker(r, w, "test", time.Time{}, seekReader)
assert.Equal(t, expectedStatusCode, w.StatusCode)
if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK {
assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length"))
assert.Equal(t, expectedContent, w.BodyBuffer.String())
}
}
t.Run("_range_", func(t *testing.T) {
test(t, http.StatusOK, data)
})
t.Run("_range_0-", func(t *testing.T) {
test(t, http.StatusPartialContent, data)
})
t.Run("_range_0-15", func(t *testing.T) {
test(t, http.StatusPartialContent, data)
})
t.Run("_range_1-", func(t *testing.T) {
test(t, http.StatusPartialContent, data[1:])
})
t.Run("_range_1-3", func(t *testing.T) {
test(t, http.StatusPartialContent, data[1:3+1])
})
t.Run("_range_16-", func(t *testing.T) {
test(t, http.StatusRequestedRangeNotSatisfiable, "")
})
t.Run("_range_1-99999", func(t *testing.T) {
test(t, http.StatusPartialContent, data[1:])
})
}

View File

@ -18,9 +18,9 @@ import (
var (
// ErrHashMismatch occurs if the content has does not match OID
ErrHashMismatch = errors.New("content hash does not match OID")
ErrHashMismatch = errors.New("Content hash does not match OID")
// ErrSizeMismatch occurs if the content size does not match
ErrSizeMismatch = errors.New("content size does not match")
ErrSizeMismatch = errors.New("Content size does not match")
)
// ContentStore provides a simple file system based storage.
@ -105,7 +105,7 @@ func (s *ContentStore) Verify(pointer Pointer) (bool, error) {
}
// ReadMetaObject will read a git_model.LFSMetaObject and return a reader
func ReadMetaObject(pointer Pointer) (io.ReadSeekCloser, error) {
func ReadMetaObject(pointer Pointer) (io.ReadCloser, error) {
contentStore := NewContentStore()
return contentStore.Get(pointer)
}

View File

@ -249,16 +249,10 @@ type CreateBranchRepoOption struct {
// unique: true
BranchName string `json:"new_branch_name" binding:"Required;GitRefName;MaxSize(100)"`
// Deprecated: true
// Name of the old branch to create from
//
// unique: true
OldBranchName string `json:"old_branch_name" binding:"GitRefName;MaxSize(100)"`
// Name of the old branch/tag/commit to create from
//
// unique: true
OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"`
}
// TransferRepoOption options when transfer a repository's ownership

View File

@ -569,7 +569,6 @@ starred = Starred Repositories
watched = Watched Repositories
code = Code
projects = Projects
overview = Overview
following = Following
follow = Follow
unfollow = Unfollow

View File

@ -173,35 +173,11 @@ func CreateBranch(ctx *context.APIContext) {
return
}
var oldCommit *git.Commit
var err error
if len(opt.OldRefName) > 0 {
oldCommit, err = ctx.Repo.GitRepo.GetCommit(opt.OldRefName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}
} else if len(opt.OldBranchName) > 0 { //nolint
if ctx.Repo.GitRepo.IsBranchExist(opt.OldBranchName) { //nolint
oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
return
}
} else {
ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
return
}
} else {
oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
return
}
if len(opt.OldBranchName) == 0 {
opt.OldBranchName = ctx.Repo.Repository.DefaultBranch
}
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
err := repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, opt.OldBranchName, opt.BranchName)
if err != nil {
if models.IsErrBranchDoesNotExist(err) {
ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
@ -213,7 +189,7 @@ func CreateBranch(ctx *context.APIContext) {
} else if models.IsErrBranchNameConflict(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
} else {
ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err)
ctx.Error(http.StatusInternalServerError, "CreateRepoBranch", err)
}
return
}

View File

@ -42,18 +42,6 @@ func GetSingleCommit(ctx *context.APIContext) {
// description: a git ref or commit sha
// type: string
// required: true
// - name: stat
// in: query
// description: include diff stats for every commit (disable for speedup, default 'true')
// type: boolean
// - name: verification
// in: query
// description: include verification for every commit (disable for speedup, default 'true')
// type: boolean
// - name: files
// in: query
// description: include a list of affected files for every commit (disable for speedup, default 'true')
// type: boolean
// responses:
// "200":
// "$ref": "#/responses/Commit"
@ -67,11 +55,10 @@ func GetSingleCommit(ctx *context.APIContext) {
ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
return
}
getCommit(ctx, sha, convert.ParseCommitOptions(ctx))
getCommit(ctx, sha)
}
func getCommit(ctx *context.APIContext, identifier string, toCommitOpts convert.ToCommitOptions) {
func getCommit(ctx *context.APIContext, identifier string) {
commit, err := ctx.Repo.GitRepo.GetCommit(identifier)
if err != nil {
if git.IsErrNotExist(err) {
@ -82,7 +69,7 @@ func getCommit(ctx *context.APIContext, identifier string, toCommitOpts convert.
return
}
json, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, nil, toCommitOpts)
json, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, nil, convert.ToCommitOptions{Stat: true})
if err != nil {
ctx.Error(http.StatusInternalServerError, "toCommit", err)
return
@ -253,12 +240,24 @@ func GetAllCommits(ctx *context.APIContext) {
}
pageCount := int(math.Ceil(float64(commitsCountTotal) / float64(listOptions.PageSize)))
userCache := make(map[string]*user_model.User)
apiCommits := make([]*api.Commit, len(commits))
stat := ctx.FormString("stat") == "" || ctx.FormBool("stat")
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
files := ctx.FormString("files") == "" || ctx.FormBool("files")
for i, commit := range commits {
// Create json struct
apiCommits[i], err = convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, userCache, convert.ParseCommitOptions(ctx))
apiCommits[i], err = convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, userCache,
convert.ToCommitOptions{
Stat: stat,
Verification: verification,
Files: files,
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "toCommit", err)
return

View File

@ -150,7 +150,6 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
return
}
// FIXME: code from #19689, what if the file is large ... OOM ...
buf, err := io.ReadAll(dataRc)
if err != nil {
_ = dataRc.Close()
@ -165,7 +164,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
// Check if the blob represents a pointer
pointer, _ := lfs.ReadPointer(bytes.NewReader(buf))
// if it's not a pointer, just serve the data directly
// if its not a pointer just serve the data directly
if !pointer.IsValid() {
// First handle caching for the blob
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
@ -173,21 +172,25 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
}
// OK not cached - serve!
common.ServeContentByReader(ctx.Context, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
if err := common.ServeData(ctx.Context, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf)); err != nil {
ctx.ServerError("ServeBlob", err)
}
return
}
// Now check if there is a MetaObject for this pointer
// Now check if there is a meta object for this pointer
meta, err := git_model.GetLFSMetaObjectByOid(ctx, ctx.Repo.Repository.ID, pointer.Oid)
// If there isn't one, just serve the data directly
// If there isn't one just serve the data directly
if err == git_model.ErrLFSObjectNotExist {
// Handle caching for the blob SHA (not the LFS object OID)
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
return
}
common.ServeContentByReader(ctx.Context, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
if err := common.ServeData(ctx.Context, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf)); err != nil {
ctx.ServerError("ServeBlob", err)
}
return
} else if err != nil {
ctx.ServerError("GetLFSMetaObjectByOid", err)
@ -215,7 +218,9 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
}
defer lfsDataRc.Close()
common.ServeContentByReadSeeker(ctx.Context, ctx.Repo.TreePath, lastModified, lfsDataRc)
if err := common.ServeData(ctx.Context, ctx.Repo.TreePath, meta.Size, lfsDataRc); err != nil {
ctx.ServerError("ServeData", err)
}
}
func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEntry, lastModified time.Time) {

116
routers/common/repo.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common
import (
"io"
"path"
"path/filepath"
"strings"
"time"
charsetModule "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
)
// ServeBlob download a git.Blob
func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) error {
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
return nil
}
dataRc, err := blob.DataAsync()
if err != nil {
return err
}
defer func() {
if err = dataRc.Close(); err != nil {
log.Error("ServeBlob: Close: %v", err)
}
}()
return ServeData(ctx, ctx.Repo.TreePath, blob.Size(), dataRc)
}
// ServeData download file from io.Reader
func ServeData(ctx *context.Context, filePath string, size int64, reader io.Reader) error {
buf := make([]byte, 1024)
n, err := util.ReadAtMost(reader, buf)
if err != nil {
return err
}
if n >= 0 {
buf = buf[:n]
}
opts := &context.ServeHeaderOptions{
Filename: path.Base(filePath),
}
if size >= 0 {
opts.ContentLength = &size
} else {
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
}
sniffedType := typesniffer.DetectContentType(buf)
isPlain := sniffedType.IsText() || ctx.FormBool("render")
if setting.MimeTypeMap.Enabled {
fileExtension := strings.ToLower(filepath.Ext(filePath))
opts.ContentType = setting.MimeTypeMap.Map[fileExtension]
}
if opts.ContentType == "" {
if sniffedType.IsBrowsableBinaryType() {
opts.ContentType = sniffedType.GetMimeType()
} else if isPlain {
opts.ContentType = "text/plain"
} else {
opts.ContentType = typesniffer.ApplicationOctetStream
}
}
if isPlain {
var charset string
charset, err = charsetModule.DetectEncoding(buf)
if err != nil {
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err)
charset = "utf-8"
}
opts.ContentTypeCharset = strings.ToLower(charset)
}
isSVG := sniffedType.IsSvgImage()
// serve types that can present a security risk with CSP
if isSVG {
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
} else if sniffedType.IsPDF() {
// no sandbox attribute for pdf as it breaks rendering in at least safari. this
// should generally be safe as scripts inside PDF can not escape the PDF document
// see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
}
opts.Disposition = "inline"
if isSVG && !setting.UI.SVG.Enabled {
opts.Disposition = "attachment"
}
ctx.SetServeHeaders(opts)
_, err = ctx.Resp.Write(buf)
if err != nil {
return err
}
_, err = io.Copy(ctx.Resp, reader)
return err
}

View File

@ -1,43 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common
import (
"io"
"time"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
)
// ServeBlob download a git.Blob
func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) error {
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
return nil
}
dataRc, err := blob.DataAsync()
if err != nil {
return err
}
defer func() {
if err = dataRc.Close(); err != nil {
log.Error("ServeBlob: Close: %v", err)
}
}()
httplib.ServeContentByReader(ctx.Req, ctx.Resp, ctx.Repo.TreePath, blob.Size(), dataRc)
return nil
}
func ServeContentByReader(ctx *context.Context, filePath string, size int64, reader io.Reader) {
httplib.ServeContentByReader(ctx.Req, ctx.Resp, filePath, size, reader)
}
func ServeContentByReadSeeker(ctx *context.Context, filePath string, modTime time.Time, reader io.ReadSeeker) {
httplib.ServeContentByReadSeeker(ctx.Req, ctx.Resp, filePath, modTime, reader)
}

View File

@ -153,7 +153,10 @@ func ServeAttachment(ctx *context.Context, uuid string) {
}
defer fr.Close()
common.ServeContentByReadSeeker(ctx, attach.Name, attach.CreatedUnix.AsTime(), fr)
if err = common.ServeData(ctx, attach.Name, attach.Size, fr); err != nil {
ctx.ServerError("ServeData", err)
return
}
}
// GetAttachment serve attachments

View File

@ -71,8 +71,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified time.Time
log.Error("ServeBlobOrLFS: Close: %v", err)
}
}()
common.ServeContentByReadSeeker(ctx, ctx.Repo.TreePath, lastModified, lfsDataRc)
return nil
return common.ServeData(ctx, ctx.Repo.TreePath, meta.Size, lfsDataRc)
}
if err = dataRc.Close(); err != nil {
log.Error("ServeBlobOrLFS: Close: %v", err)

View File

@ -5,7 +5,6 @@
package repo
import (
"errors"
"fmt"
"net/http"
"strings"
@ -17,7 +16,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
@ -38,32 +36,24 @@ const (
// calReleaseNumCommitsBehind calculates given release has how many commits behind release target.
func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model.Release, countCache map[string]int64) error {
target := release.Target
if target == "" {
target = repoCtx.Repository.DefaultBranch
}
// Get count if not cached
if _, ok := countCache[target]; !ok {
commit, err := repoCtx.GitRepo.GetBranchCommit(target)
if err != nil {
var errNotExist git.ErrNotExist
if target == repoCtx.Repository.DefaultBranch || !errors.As(err, &errNotExist) {
// Get count if not exists
if _, ok := countCache[release.Target]; !ok {
// short-circuit for the default branch
if repoCtx.Repository.DefaultBranch == release.Target || repoCtx.GitRepo.IsBranchExist(release.Target) {
commit, err := repoCtx.GitRepo.GetBranchCommit(release.Target)
if err != nil {
return fmt.Errorf("GetBranchCommit: %w", err)
}
// fallback to default branch
target = repoCtx.Repository.DefaultBranch
commit, err = repoCtx.GitRepo.GetBranchCommit(target)
countCache[release.Target], err = commit.CommitsCount()
if err != nil {
return fmt.Errorf("GetBranchCommit(DefaultBranch): %w", err)
return fmt.Errorf("CommitsCount: %w", err)
}
}
countCache[target], err = commit.CommitsCount()
if err != nil {
return fmt.Errorf("CommitsCount: %w", err)
} else {
// Use NumCommits of the newest release on that target
countCache[release.Target] = release.NumCommits
}
}
release.NumCommitsBehind = countCache[target] - release.NumCommits
release.TargetBehind = target
release.NumCommitsBehind = countCache[release.Target] - release.NumCommits
return nil
}

View File

@ -11,8 +11,6 @@ import (
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms"
"github.com/stretchr/testify/assert"
)
func TestNewReleasePost(t *testing.T) {
@ -64,48 +62,3 @@ func TestNewReleasePost(t *testing.T) {
ctx.Repo.GitRepo.Close()
}
}
func TestNewReleasesList(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx := test.MockContext(t, "user2/repo-release/releases")
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 57)
test.LoadGitRepo(t, ctx)
t.Cleanup(func() { ctx.Repo.GitRepo.Close() })
Releases(ctx)
releases := ctx.Data["Releases"].([]*repo_model.Release)
type computedFields struct {
NumCommitsBehind int64
TargetBehind string
}
expectedComputation := map[string]computedFields{
"v1.0": {
NumCommitsBehind: 3,
TargetBehind: "main",
},
"v1.1": {
NumCommitsBehind: 1,
TargetBehind: "main",
},
"v2.0": {
NumCommitsBehind: 0,
TargetBehind: "main",
},
"non-existing-target-branch": {
NumCommitsBehind: 1,
TargetBehind: "main",
},
"empty-target-branch": {
NumCommitsBehind: 1,
TargetBehind: "main",
},
}
for _, r := range releases {
actual := computedFields{
NumCommitsBehind: r.NumCommitsBehind,
TargetBehind: r.TargetBehind,
}
assert.Equal(t, expectedComputation[r.TagName], actual, "wrong computed fields for %s: %#v", r.TagName, r)
}
}

View File

@ -4,9 +4,7 @@
package user
import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
)
@ -15,24 +13,4 @@ func RenderUserHeader(ctx *context.Context) {
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["ContextUser"] = ctx.ContextUser
tab := ctx.FormString("tab")
ctx.Data["TabName"] = tab
repo, err := repo_model.GetRepositoryByName(ctx.ContextUser.ID, ".profile")
if err == nil && !repo.IsEmpty {
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
return
}
defer gitRepo.Close()
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
if err != nil {
ctx.ServerError("GetBranchCommit", err)
return
}
blob, err := commit.GetBlobByPath("README.md")
if err == nil && blob != nil {
ctx.Data["ProfileReadme"] = true
}
}
}

View File

@ -16,7 +16,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
@ -92,38 +91,6 @@ func Profile(ctx *context.Context) {
ctx.Data["RenderedDescription"] = content
}
repo, err := repo_model.GetRepositoryByName(ctx.ContextUser.ID, ".profile")
if err == nil && !repo.IsEmpty {
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
return
}
defer gitRepo.Close()
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
if err != nil {
ctx.ServerError("GetBranchCommit", err)
return
}
blob, err := commit.GetBlobByPath("README.md")
if err == nil {
bytes, err := blob.GetBlobContent()
if err != nil {
ctx.ServerError("GetBlobContent", err)
return
}
profileContent, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
GitRepo: gitRepo,
}, bytes)
if err != nil {
ctx.ServerError("RenderString", err)
return
}
ctx.Data["ProfileReadme"] = profileContent
}
}
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
orgs, err := organization.FindOrgs(organization.FindOrgOptions{

View File

@ -10,7 +10,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
ctx "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
@ -79,14 +78,6 @@ type ToCommitOptions struct {
Files bool
}
func ParseCommitOptions(ctx *ctx.APIContext) ToCommitOptions {
return ToCommitOptions{
Stat: ctx.FormString("stat") == "" || ctx.FormBool("stat"),
Files: ctx.FormString("files") == "" || ctx.FormBool("files"),
Verification: ctx.FormString("verification") == "" || ctx.FormBool("verification"),
}
}
// ToCommit convert a git.Commit to api.Commit
func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, commit *git.Commit, userCache map[string]*user_model.User, opts ToCommitOptions) (*api.Commit, error) {
var apiAuthor, apiCommitter *api.User

View File

@ -73,7 +73,7 @@
</div>
<div class="ui container fluid padded" id="project-board">
<div class="board {{if .CanWriteProjects}}sortable{{end}}">
<div class="board">
{{range $board := .Boards}}
<div class="ui segment board-column" style="background: {{.Color}} !important;" data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}">

View File

@ -2,7 +2,7 @@
{{range .Runs}}
<li class="item gt-df gt-py-3 gt-ab">
<div class="issue-item-left gt-df gt-mr-2">
{{template "repo/actions/status" (dict "status" .Status.String "locale" $.locale)}}
{{template "repo/actions/status" (dict "status" .Status.String)}}
</div>
<div class="issue-item-main action-item-main gt-f1 gt-fc gt-df gt-mr-3">
<div class="issue-item-top-row">

View File

@ -11,7 +11,6 @@
{{- $className = .className -}}
{{- end -}}
<span data-tooltip-content="{{.locale.Tr (printf "actions.status.%s" .status)}}">
{{if eq .status "success"}}
{{svg "octicon-check-circle-fill" $size (printf "text green %s" $className)}}
{{else if eq .status "skipped"}}
@ -25,4 +24,3 @@
{{else}}
{{svg "octicon-x-circle-fill" $size (printf "text red %s" $className)}}
{{end}}
</span>

View File

@ -9,14 +9,6 @@
data-locale-approve="{{.locale.Tr "repo.diff.review.approve"}}"
data-locale-cancel="{{.locale.Tr "cancel"}}"
data-locale-rerun="{{.locale.Tr "rerun"}}"
data-locale-status-unknown="{{.locale.Tr "actions.status.unknown"}}"
data-locale-status-waiting="{{.locale.Tr "actions.status.waiting"}}"
data-locale-status-running="{{.locale.Tr "actions.status.running"}}"
data-locale-status-success="{{.locale.Tr "actions.status.success"}}"
data-locale-status-failure="{{.locale.Tr "actions.status.failure"}}"
data-locale-status-cancelled="{{.locale.Tr "actions.status.cancelled"}}"
data-locale-status-skipped="{{.locale.Tr "actions.status.skipped"}}"
data-locale-status-blocked="{{.locale.Tr "actions.status.blocked"}}"
>
</div>
</div>

View File

@ -1,6 +1,6 @@
{{if .DiffNotAvailable}}
<div>
<div class="diff-detail-box diff-box sticky">
<div class="diff-detail-box diff-box sticky">
<div>
<div class="ui right">
{{template "repo/diff/whitespace_dropdown" .}}
{{template "repo/diff/options_dropdown" .}}

View File

@ -1,8 +1,8 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository diff {{if .PageIsComparePull}}compare pull{{end}}">
{{template "repo/header" .}}
{{$showDiffBox := false}}
<div class="ui container">
<div class="ui container fluid padded">
<h2 class="ui header">
{{if and $.PageIsComparePull $.IsSigned (not .Repository.IsArchived)}}
{{.locale.Tr "repo.pulls.compare_changes"}}
@ -34,6 +34,11 @@
{{- if eq $.HeadRepo.OwnerName .RootRepo.OwnerName -}}
{{- $HeadCompareName = printf "%s/%s" $.HeadRepo.OwnerName $.HeadRepo.Name -}}
{{- end -}}
{{- if .OwnForkRepo -}}
{{- if eq $.OwnForkRepo.OwnerName .RootRepo.OwnerName -}}
{{- $OwnForkRepoCompareName = printf "%s/%s" $.OwnForkRepo.OwnerName $.OwnForkRepo.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
<div class="ui segment choose branch">
<a href="{{$.HeadRepo.Link}}/compare/{{PathEscapeSegments $.HeadBranch}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.BaseName}}/{{PathEscape $.Repository.Name}}:{{end}}{{PathEscapeSegments $.BaseBranch}}" title="{{.locale.Tr "repo.pulls.switch_head_and_base"}}">{{svg "octicon-git-compare"}}</a>
@ -198,15 +203,14 @@
<span class="index">#{{.PullRequest.Issue.Index}}</span>
</h1>
</div>
<div class="four wide column middle aligned text right">
<div class="four wide right middle aligned column">
{{- if .PullRequest.HasMerged -}}
<a href="{{Escape $.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui button purple show-form">{{svg "octicon-git-merge" 16}} {{.locale.Tr "repo.pulls.view"}}</a>
{{else if .Issue.IsClosed}}
<a href="{{Escape $.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui button red show-form">{{svg "octicon-issue-closed" 16}} {{.locale.Tr "repo.pulls.view"}}</a>
{{else}}
<a href="{{Escape $.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui button green show-form">{{svg "octicon-git-pull-request" 16}} {{.locale.Tr "repo.pulls.view"}}</a>
{{end}}
</div>
{{end}}</div>
</div>
{{else}}
{{if and $.IsSigned (not .Repository.IsArchived)}}
@ -227,20 +231,13 @@
{{template "repo/issue/new_form" .}}
</div>
{{end}}
{{$showDiffBox = true}}
{{template "repo/commits_table" .}}
{{template "repo/diff/box" .}}
{{end}}
{{else}}
{{$showDiffBox = true}}
{{end}}
</div>
{{if $showDiffBox}}
<div class="ui container">
{{template "repo/commits_table" .}}
</div>
<div class="ui container fluid padded">
{{template "repo/diff/box" .}}
</div>
{{end}}
</div>
</div>
{{template "base/footer" .}}

View File

@ -5,7 +5,7 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository view issue pull files diff">
{{template "repo/header" .}}
<div class="ui container fluid padded">
<div class="ui container">
{{template "repo/issue/view_title" .}}
{{template "repo/pulls/tab_menu" .}}
{{template "repo/diff/box" .}}

View File

@ -56,7 +56,7 @@
{{end}}
|
{{end}}
<span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.TargetBehind | PathEscapeSegments}}">{{$.locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{$.locale.Tr "repo.tag.ahead.target" .TargetBehind}}</span>
<span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}{{if .Target}}...{{.Target | PathEscapeSegments}}{{end}}">{{$.locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{$.locale.Tr "repo.tag.ahead.target" $.DefaultBranch}}</span>
</p>
{{else}}
<p class="text grey">
@ -77,7 +77,7 @@
<span class="time">{{TimeSinceUnix .CreatedUnix $.locale}}</span>
{{end}}
{{if not .IsDraft}}
| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.TargetBehind | PathEscapeSegments}}">{{$.locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{$.locale.Tr "repo.release.ahead.target" .TargetBehind}}</span>
| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.Target | PathEscapeSegments}}">{{$.locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{$.locale.Tr "repo.release.ahead.target" .Target}}</span>
{{end}}
</p>
{{end}}

View File

@ -4498,24 +4498,6 @@
"name": "sha",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "include diff stats for every commit (disable for speedup, default 'true')",
"name": "stat",
"in": "query"
},
{
"type": "boolean",
"description": "include verification for every commit (disable for speedup, default 'true')",
"name": "verification",
"in": "query"
},
{
"type": "boolean",
"description": "include a list of affected files for every commit (disable for speedup, default 'true')",
"name": "files",
"in": "query"
}
],
"responses": {
@ -16163,16 +16145,10 @@
"x-go-name": "BranchName"
},
"old_branch_name": {
"description": "Deprecated: true\nName of the old branch to create from",
"description": "Name of the old branch to create from",
"type": "string",
"uniqueItems": true,
"x-go-name": "OldBranchName"
},
"old_ref_name": {
"description": "Name of the old branch/tag/commit to create from",
"type": "string",
"uniqueItems": true,
"x-go-name": "OldRefName"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"

View File

@ -1,7 +1,6 @@
<!-- TODO: make template org and user can share -->
{{if or (.IsPackagesPage) (.PageIsViewProjects)}}
{{with .ContextUser}}
<div class="ui container">
{{with .ContextUser}}
<div class="ui container">
<div class="ui vertically grid head">
<div class="column">
<div class="ui header">
@ -15,17 +14,11 @@
</div>
</div>
</div>
{{end}}
{{end}}
<div class="ui tabs container">
<div class="ui secondary stackable pointing menu">
{{if .ProfileReadme}}
<a class='{{if or (eq .TabName "overview") (and (eq .TabName "") (not .IsPackagesPage) (not .PageIsViewProjects))}}active {{end}}item' href="{{.ContextUser.HomeLink}}?tab=overview">
{{svg "octicon-info"}} {{.locale.Tr "user.overview"}}
</a>
{{end}}
<a class="{{if or (eq .TabName "repositories") (and (eq .TabName "") (not .IsPackagesPage) (not .PageIsViewProjects) (not .ProfileReadme))}}active {{end}} item" href="{{.ContextUser.HomeLink}}?tab=repositories">
<a class="item" href="{{.ContextUser.HomeLink}}">
{{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}}
{{if .ContextUser.NumRepos}}
<div class="ui small label">{{.ContextUser.NumRepos}}</div>

View File

@ -121,7 +121,40 @@
</div>
<div class="ui eleven wide column">
<div class="ui secondary stackable pointing tight menu">
{{template "user/overview/header" .}}
<a class='{{if and (ne .TabName "activity") (ne .TabName "following") (ne .TabName "followers") (ne .TabName "stars") (ne .TabName "watching") (ne .TabName "projects") (ne .TabName "code")}}active {{end}}item' href="{{.ContextUser.HomeLink}}">
{{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}}
{{if .ContextUser.NumRepos}}
<div class="ui small label">{{.ContextUser.NumRepos}}</div>
{{end}}
</a>
<a href="{{.ContextUser.HomeLink}}/-/projects" class="{{if eq .TabName "projects"}}active {{end}}item">
{{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}}
</a>
{{if .IsPackageEnabled}}
<a class='{{if eq .TabName "packages"}}active {{end}}item' href="{{.ContextUser.HomeLink}}/-/packages">
{{svg "octicon-package"}} {{.locale.Tr "packages.title"}}
</a>
{{end}}
{{if and (not $.UnitTypeCode.UnitGlobalDisabled) .IsRepoIndexerEnabled}}
<a class='{{if eq .TabName "code"}}active {{end}}item' href="{{.ContextUser.HomeLink}}/-/code">
{{svg "octicon-code"}} {{.locale.Tr "user.code"}}
</a>
{{end}}
<a class='{{if eq .TabName "activity"}}active {{end}}item' href="{{.ContextUser.HomeLink}}?tab=activity">
{{svg "octicon-rss"}} {{.locale.Tr "user.activity"}}
</a>
{{if not .DisableStars}}
<a class='{{if eq .TabName "stars"}}active {{end}}item' href="{{.ContextUser.HomeLink}}?tab=stars">
{{svg "octicon-star"}} {{.locale.Tr "user.starred"}}
{{if .ContextUser.NumStars}}
<div class="ui small label">{{.ContextUser.NumStars}}</div>
{{end}}
</a>
{{else}}
<a class='{{if eq .TabName "watching"}}active {{end}}item' href="{{.ContextUser.HomeLink}}?tab=watching">
{{svg "octicon-eye"}} {{.locale.Tr "user.watched"}}
</a>
{{end}}
</div>
{{if eq .TabName "activity"}}
@ -144,12 +177,10 @@
{{template "repo/user_cards" .}}
{{else if eq .TabName "followers"}}
{{template "repo/user_cards" .}}
{{else if or (eq .TabName "repositories") (not .ProfileReadme)}}
{{else}}
{{template "explore/repo_search" .}}
{{template "explore/repo_list" .}}
{{template "base/paginate" .}}
{{else if .ProfileReadme}}
<div id="readme_profile" class="render-content markup"> {{$.ProfileReadme|Str2html}} </div>
{{end}}
</div>
</div>

View File

@ -143,10 +143,10 @@ func TestViewReleaseListNoLogin(t *testing.T) {
htmlDoc := NewHTMLParser(t, rsp.Body)
releases := htmlDoc.Find("#release-list li.ui.grid")
assert.Equal(t, 5, releases.Length())
assert.Equal(t, 3, releases.Length())
links := make([]string, 0, 5)
commitsToMain := make([]string, 0, 5)
links := make([]string, 0, 3)
commitsToMain := make([]string, 0, 3)
releases.Each(func(i int, s *goquery.Selection) {
link, exist := s.Find(".release-list-title a").Attr("href")
if !exist {
@ -158,15 +158,11 @@ func TestViewReleaseListNoLogin(t *testing.T) {
})
assert.EqualValues(t, []string{
"/user2/repo-release/releases/tag/empty-target-branch",
"/user2/repo-release/releases/tag/non-existing-target-branch",
"/user2/repo-release/releases/tag/v2.0",
"/user2/repo-release/releases/tag/v1.1",
"/user2/repo-release/releases/tag/v1.0",
}, links)
assert.EqualValues(t, []string{
"1 commits", // like v1.1
"1 commits", // like v1.1
"0 commits",
"1 commits", // should be 3 commits ahead and 2 commits behind, but not implemented yet
"3 commits",

View File

@ -6,10 +6,6 @@
margin: 0 0.5em;
}
.board.sortable .board-card {
cursor: move;
}
.board-column {
background-color: var(--color-project-board-bg) !important;
border: 1px solid var(--color-secondary) !important;
@ -84,6 +80,7 @@
.board-card {
margin: 4px 2px !important;
border-radius: 5px !important;
cursor: move;
width: calc(100% - 4px) !important;
padding: 0.5rem !important;
min-height: auto !important;

View File

@ -140,13 +140,6 @@
border: none;
}
#readme_profile {
padding: 10px;
border-radius: 0.28571429rem;
background: var(--color-card);
border: 1px solid var(--color-secondary);
}
#notification_table tr {
cursor: default;
}

View File

@ -2,14 +2,12 @@
Please also update the template file above if this vue is modified.
-->
<template>
<span :data-tooltip-content="localeStatus">
<SvgIcon name="octicon-check-circle-fill" class="text green" :size="size" :class-name="className" v-if="status === 'success'"/>
<SvgIcon name="octicon-skip" class="text grey" :size="size" :class-name="className" v-else-if="status === 'skipped'"/>
<SvgIcon name="octicon-clock" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'waiting'"/>
<SvgIcon name="octicon-blocked" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'blocked'"/>
<SvgIcon name="octicon-meter" class="text yellow" :size="size" :class-name="'job-status-rotate ' + className" v-else-if="status === 'running'"/>
<SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else/>
</span>
<SvgIcon name="octicon-check-circle-fill" class="text green" :size="size" :class-name="className" v-if="status === 'success'"/>
<SvgIcon name="octicon-skip" class="text grey" :size="size" :class-name="className" v-else-if="status === 'skipped'"/>
<SvgIcon name="octicon-clock" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'waiting'"/>
<SvgIcon name="octicon-blocked" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'blocked'"/>
<SvgIcon name="octicon-meter" class="text yellow" :size="size" :class-name="'job-status-rotate ' + className" v-else-if="status === 'running'"/>
<SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else/>
</template>
<script>
@ -29,10 +27,6 @@ export default {
className: {
type: String,
default: ''
},
localeStatus: {
type: String,
default: ''
}
},
};

View File

@ -2,7 +2,7 @@
<div class="action-view-container">
<div class="action-view-header">
<div class="action-info-summary gt-ac">
<ActionRunStatus :locale-status="locale.status[run.status]" :status="run.status" :size="20"/>
<ActionRunStatus :status="run.status" :size="20"/>
<div class="action-title">
{{ run.title }}
</div>
@ -32,7 +32,7 @@
<div class="job-brief-list">
<div class="job-brief-item" v-for="(job, index) in run.jobs" :key="job.id">
<a class="job-brief-link" :href="run.link+'/jobs/'+index">
<ActionRunStatus :locale-status="locale.status[job.status]" :status="job.status"/>
<ActionRunStatus :status="job.status"/>
<span class="ui text gt-mx-3">{{ job.name }}</span>
</a>
<span class="step-summary-duration">{{ job.duration }}</span>
@ -319,16 +319,6 @@ export function initRepositoryActionView() {
approve: el.getAttribute('data-locale-approve'),
cancel: el.getAttribute('data-locale-cancel'),
rerun: el.getAttribute('data-locale-rerun'),
status: {
unknown: el.getAttribute('data-locale-status-unknown'),
waiting: el.getAttribute('data-locale-status-waiting'),
running: el.getAttribute('data-locale-status-running'),
success: el.getAttribute('data-locale-status-success'),
failure: el.getAttribute('data-locale-status-failure'),
cancelled: el.getAttribute('data-locale-status-cancelled'),
skipped: el.getAttribute('data-locale-status-skipped'),
blocked: el.getAttribute('data-locale-status-blocked'),
}
}
});
view.mount(el);

View File

@ -36,7 +36,7 @@ function moveIssue({item, from, to, oldIndex}) {
}
async function initRepoProjectSortable() {
const els = document.querySelectorAll('#project-board > .board.sortable');
const els = document.querySelectorAll('#project-board > .board');
if (!els.length) return;
const {Sortable} = await import(/* webpackChunkName: "sortable" */'sortablejs');