Merge remote-tracking branch 'upstream/main' into limit-repo-size

This commit is contained in:
DmitryFrolovTri committed 2026-01-22 12:30:13 +00:00
commit fee277fc41
81 files changed
+835 -1095

No files matched your search

+8 -32
View File
@@ -100,7 +100,7 @@ GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
# Enable typescript support in Node.js before 22.18
# TODO: Remove this once we can raise the minimum Node.js version to 22.18 (alpine >= 3.23)
NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v 2>/dev/null | cut -c2- | tr '.' ' '))
NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v 2>/dev/null | cut -c2- | sed 's/-.*//' | tr '.' ' '))
ifeq ($(shell test "$(NODE_VERSION)" -lt "022018000"; echo $$?),0)
NODE_VARS := NODE_OPTIONS="--experimental-strip-types"
else
@@ -211,16 +211,6 @@ help: Makefile ## print Makefile help information.
@printf " \033[36m%-46s\033[0m %s\n" "test[#TestSpecificName]" "run unit test"
@printf " \033[36m%-46s\033[0m %s\n" "test-sqlite[#TestSpecificName]" "run integration test for sqlite"
.PHONY: go-check
go-check:
$(eval MIN_GO_VERSION_STR := $(shell grep -Eo '^go\s+[0-9]+\.[0-9]+' go.mod | cut -d' ' -f2))
$(eval MIN_GO_VERSION := $(shell printf "%03d%03d" $(shell echo '$(MIN_GO_VERSION_STR)' | tr '.' ' ')))
$(eval GO_VERSION := $(shell printf "%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9]+' | tr '.' ' ');))
@if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \
echo "Gitea requires Go $(MIN_GO_VERSION_STR) or greater to build. You can get it at https://go.dev/dl/"; \
exit 1; \
fi
.PHONY: git-check
git-check:
@if git lfs >/dev/null 2>&1 ; then : ; else \
@@ -228,20 +218,6 @@ git-check:
exit 1; \
fi
.PHONY: node-check
node-check:
$(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p'))
$(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
$(eval PNPM_MISSING := $(shell hash pnpm > /dev/null 2>&1 || echo 1))
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" ]; then \
echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater to build. You can get it at https://nodejs.org/en/download/"; \
exit 1; \
fi
@if [ "$(PNPM_MISSING)" = "1" ]; then \
echo "Gitea requires pnpm to build. You can install it at https://pnpm.io/installation"; \
exit 1; \
fi
.PHONY: clean-all
clean-all: clean ## delete backend, frontend and integration files
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
@@ -426,12 +402,12 @@ watch: ## watch everything and continuously rebuild
@bash tools/watch.sh
.PHONY: watch-frontend
watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild
watch-frontend: node_modules ## watch frontend files and continuously rebuild
@rm -rf $(WEBPACK_DEST_ENTRIES)
NODE_ENV=development $(NODE_VARS) pnpm exec webpack --watch --progress --disable-interpret
.PHONY: watch-backend
watch-backend: go-check ## watch backend files and continuously rebuild
watch-backend: ## watch backend files and continuously rebuild
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
.PHONY: test
@@ -749,7 +725,7 @@ build: frontend backend ## build everything
frontend: $(WEBPACK_DEST) ## build frontend files
.PHONY: backend
backend: go-check generate-backend $(EXECUTABLE) ## build backend files
backend: generate-backend $(EXECUTABLE) ## build backend files
# We generate the backend before the frontend in case we in future we want to generate things in the frontend from generated files in backend
.PHONY: generate
@@ -860,7 +836,7 @@ node_modules: pnpm-lock.yaml
update: update-js update-py ## update js and py dependencies
.PHONY: update-js
update-js: node-check | node_modules ## update js dependencies
update-js: node_modules ## update js dependencies
$(NODE_VARS) pnpm exec updates -u -f package.json
rm -rf node_modules pnpm-lock.yaml
$(NODE_VARS) pnpm install
@@ -869,7 +845,7 @@ update-js: node-check | node_modules ## update js dependencies
@touch node_modules
.PHONY: update-py
update-py: node-check | node_modules ## update py dependencies
update-py: node_modules ## update py dependencies
$(NODE_VARS) pnpm exec updates -u -f pyproject.toml
rm -rf .venv uv.lock
uv sync
@@ -879,14 +855,14 @@ update-py: node-check | node_modules ## update py dependencies
webpack: $(WEBPACK_DEST) ## build webpack files
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) pnpm-lock.yaml
@$(MAKE) -s node-check node_modules
@$(MAKE) -s node_modules
@rm -rf $(WEBPACK_DEST_ENTRIES)
@echo "Running webpack..."
@BROWSERSLIST_IGNORE_OLD_DATA=true $(NODE_VARS) pnpm exec webpack --disable-interpret
@touch $(WEBPACK_DEST)
.PHONY: svg
svg: node-check | node_modules ## build svg files
svg: node_modules ## build svg files
rm -rf $(SVG_DEST_DIR)
node tools/generate-svg.ts
+1 -1
View File
@@ -46,7 +46,7 @@
`build` 目标分为两个子目标:
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本以及 [pnpm](https://pnpm.io/installation)。
需要互联网连接来下载 go 和 npm 模块。从包含预构建前端文件的官方源代码压缩包构建时,不会触发 `frontend` 目标,因此可以在没有 Node.js 的情况下构建。
+1 -1
View File
@@ -46,7 +46,7 @@
`build` 目標分為兩個子目標:
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本以及 [pnpm](https://pnpm.io/installation)。
需要互聯網連接來下載 go 和 npm 模塊。從包含預構建前端文件的官方源代碼壓縮包構建時,不會觸發 `frontend` 目標,因此可以在沒有 Node.js 的情況下構建。
-10
View File
@@ -409,16 +409,6 @@
"path": "github.com/dimiro1/reply/LICENSE",
"licenseText": "MIT License\n\nCopyright (c) Discourse\nCopyright (c) Claudemiro\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"name": "github.com/djherbis/buffer",
"path": "github.com/djherbis/buffer/LICENSE.txt",
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Dustin H\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
{
"name": "github.com/djherbis/nio/v3",
"path": "github.com/djherbis/nio/v3/LICENSE.txt",
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Dustin H\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
{
"name": "github.com/dlclark/regexp2",
"path": "github.com/dlclark/regexp2/LICENSE",
+5 -5
View File
@@ -596,8 +596,8 @@ Gitea or set your environment appropriately.`, "")
hookOptions.RefFullNames = make([]git.RefName, 0, hookBatchSize)
for {
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
// note: pktLineTypeUnknown means pktLineTypeFlush and pktLineTypeData all allowed
rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
if err != nil {
return err
}
@@ -616,7 +616,7 @@ Gitea or set your environment appropriately.`, "")
if hasPushOptions {
for {
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
if err != nil {
return err
}
@@ -713,8 +713,8 @@ Gitea or set your environment appropriately.`, "")
type pktLineType int64
const (
// UnKnow type
pktLineTypeUnknow pktLineType = 0
// Unknown type
pktLineTypeUnknown pktLineType = 0
// flush-pkt "0000"
pktLineTypeFlush pktLineType = iota
// data line
+1 -3
View File
@@ -28,7 +28,7 @@ require (
github.com/ProtonMail/go-crypto v1.3.0
github.com/PuerkitoBio/goquery v1.10.3
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0
github.com/alecthomas/chroma/v2 v2.22.0
github.com/alecthomas/chroma/v2 v2.23.0
github.com/aws/aws-sdk-go-v2/credentials v1.18.10
github.com/aws/aws-sdk-go-v2/service/codecommit v1.32.2
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
@@ -39,8 +39,6 @@ require (
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21
github.com/chi-middleware/proxy v1.1.1
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
github.com/dustin/go-humanize v1.0.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
+2 -7
View File
@@ -98,8 +98,8 @@ github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0/go.mod h1:1HmmMEVsr+0R
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.22.0 h1:PqEhf+ezz5F5owoDeOUKFzW+W3ZJDShNCaHg4sZuItI=
github.com/alecthomas/chroma/v2 v2.22.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/chroma/v2 v2.23.0 h1:u/Orux1J0eLuZDeQ44froV8smumheieI0EofhbyKhhk=
github.com/alecthomas/chroma/v2 v2.23.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
@@ -260,11 +260,6 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 h1:PdsjTl0Cg+ZJgOx/CFV5NNgO1ThTreqdgKYiDCMHJwA=
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21/go.mod h1:xJvkyD6Y2rZapGvPJLYo9dyx1s5dxBEDPa8T3YTuOk0=
github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o=
github.com/djherbis/buffer v1.2.0 h1:PH5Dd2ss0C7CRRhQCZ2u7MssF+No9ide8Ye71nPHcrQ=
github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQqiAiUyE=
github.com/djherbis/nio/v3 v3.0.1 h1:6wxhnuppteMa6RHA4L81Dq7ThkZH8SwnDzXDYy95vB4=
github.com/djherbis/nio/v3 v3.0.1/go.mod h1:Ng4h80pbZFMla1yKzm61cF0tqqilXZYrogmWgZxOcmg=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+1 -1
View File
@@ -114,7 +114,7 @@ func TestFindRenamedBranch(t *testing.T) {
assert.True(t, exist)
assert.Equal(t, "master", branch.To)
_, exist, err = git_model.FindRenamedBranch(t.Context(), 1, "unknow")
_, exist, err = git_model.FindRenamedBranch(t.Context(), 1, "unknown")
assert.NoError(t, err)
assert.False(t, exist)
}
+12 -17
View File
@@ -7,7 +7,7 @@ import (
"bytes"
"context"
"fmt"
"os"
"io"
"path/filepath"
"time"
@@ -20,7 +20,7 @@ import (
type BatchChecker struct {
attributesNum int
repo *git.Repository
stdinWriter *os.File
stdinWriter io.WriteCloser
stdOut *nulSeparatedAttributeWriter
ctx context.Context
cancel context.CancelFunc
@@ -60,10 +60,7 @@ func NewBatchChecker(repo *git.Repository, treeish string, attributes []string)
},
}
stdinReader, stdinWriter, err := os.Pipe()
if err != nil {
return nil, err
}
stdinWriter, stdinWriterClose := cmd.MakeStdinPipe()
checker.stdinWriter = stdinWriter
lw := new(nulSeparatedAttributeWriter)
@@ -71,21 +68,19 @@ func NewBatchChecker(repo *git.Repository, treeish string, attributes []string)
lw.closed = make(chan struct{})
checker.stdOut = lw
go func() {
defer func() {
_ = stdinReader.Close()
_ = lw.Close()
}()
err := cmd.WithEnv(envs).
WithDir(repo.Path).
WithStdin(stdinReader).
WithStdout(lw).
RunWithStderr(ctx)
cmd.WithEnv(envs).
WithDir(repo.Path).
WithStdoutCopy(lw)
go func() {
defer stdinWriterClose()
defer checker.cancel()
defer lw.Close()
err := cmd.RunWithStderr(ctx)
if err != nil && !gitcmd.IsErrorCanceledOrKilled(err) {
log.Error("Attribute checker for commit %s exits with error: %v", treeish, err)
}
checker.cancel()
}()
return checker, nil
+4 -5
View File
@@ -68,15 +68,14 @@ func CheckAttributes(ctx context.Context, gitRepo *git.Repository, treeish strin
}
defer cancel()
stdOut := new(bytes.Buffer)
if err := cmd.WithEnv(append(os.Environ(), envs...)).
stdout, _, err := cmd.WithEnv(append(os.Environ(), envs...)).
WithDir(gitRepo.Path).
WithStdout(stdOut).
RunWithStderr(ctx); err != nil {
RunStdBytes(ctx)
if err != nil {
return nil, fmt.Errorf("failed to run check-attr: %w", err)
}
fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
fields := bytes.Split(stdout, []byte{'\000'})
if len(fields)%3 != 1 {
return nil, errors.New("wrong number of fields in return from check-attr")
}
+5 -5
View File
@@ -39,23 +39,23 @@ func (b *catFileBatchCommand) getBatch() *catFileBatchCommunicator {
}
func (b *catFileBatchCommand) QueryContent(obj string) (*CatFileObject, BufferedReader, error) {
_, err := b.getBatch().writer.Write([]byte("contents " + obj + "\n"))
_, err := b.getBatch().reqWriter.Write([]byte("contents " + obj + "\n"))
if err != nil {
return nil, nil, err
}
info, err := catFileBatchParseInfoLine(b.getBatch().reader)
info, err := catFileBatchParseInfoLine(b.getBatch().respReader)
if err != nil {
return nil, nil, err
}
return info, b.getBatch().reader, nil
return info, b.getBatch().respReader, nil
}
func (b *catFileBatchCommand) QueryInfo(obj string) (*CatFileObject, error) {
_, err := b.getBatch().writer.Write([]byte("info " + obj + "\n"))
_, err := b.getBatch().reqWriter.Write([]byte("info " + obj + "\n"))
if err != nil {
return nil, err
}
return catFileBatchParseInfoLine(b.getBatch().reader)
return catFileBatchParseInfoLine(b.getBatch().respReader)
}
func (b *catFileBatchCommand) Close() {
+5 -5
View File
@@ -50,23 +50,23 @@ func (b *catFileBatchLegacy) getBatchCheck() *catFileBatchCommunicator {
}
func (b *catFileBatchLegacy) QueryContent(obj string) (*CatFileObject, BufferedReader, error) {
_, err := io.WriteString(b.getBatchContent().writer, obj+"\n")
_, err := io.WriteString(b.getBatchContent().reqWriter, obj+"\n")
if err != nil {
return nil, nil, err
}
info, err := catFileBatchParseInfoLine(b.getBatchContent().reader)
info, err := catFileBatchParseInfoLine(b.getBatchContent().respReader)
if err != nil {
return nil, nil, err
}
return info, b.getBatchContent().reader, nil
return info, b.getBatchContent().respReader, nil
}
func (b *catFileBatchLegacy) QueryInfo(obj string) (*CatFileObject, error) {
_, err := io.WriteString(b.getBatchCheck().writer, obj+"\n")
_, err := io.WriteString(b.getBatchCheck().reqWriter, obj+"\n")
if err != nil {
return nil, err
}
return catFileBatchParseInfoLine(b.getBatchCheck().reader)
return catFileBatchParseInfoLine(b.getBatchCheck().respReader)
}
func (b *catFileBatchLegacy) Close() {
+18 -33
View File
@@ -18,46 +18,40 @@ import (
)
type catFileBatchCommunicator struct {
cancel context.CancelFunc
reader *bufio.Reader
writer io.Writer
cancel context.CancelFunc
reqWriter io.Writer
respReader *bufio.Reader
debugGitCmd *gitcmd.Command
}
func (b *catFileBatchCommunicator) Close() {
if b.cancel != nil {
b.cancel()
b.reader = nil
b.writer = nil
b.cancel = nil
}
}
// newCatFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function
func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Command) *catFileBatchCommunicator {
// We often want to feed the commits in order into cat-file --batch, followed by their trees and subtrees as necessary.
func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Command) (ret *catFileBatchCommunicator) {
ctx, ctxCancel := context.WithCancelCause(ctx)
var batchStdinWriter io.WriteCloser
var batchStdoutReader io.ReadCloser
cmdCatFile = cmdCatFile.
WithDir(repoPath).
WithStdinWriter(&batchStdinWriter).
WithStdoutReader(&batchStdoutReader)
// We often want to feed the commits in order into cat-file --batch, followed by their trees and subtrees as necessary.
stdinWriter, stdoutReader, pipeClose := cmdCatFile.MakeStdinStdoutPipe()
ret = &catFileBatchCommunicator{
debugGitCmd: cmdCatFile,
cancel: func() { ctxCancel(nil) },
reqWriter: stdinWriter,
respReader: bufio.NewReaderSize(stdoutReader, 32*1024), // use a buffered reader for rich operations
}
err := cmdCatFile.StartWithStderr(ctx)
err := cmdCatFile.WithDir(repoPath).StartWithStderr(ctx)
if err != nil {
log.Error("Unable to start git command %v: %v", cmdCatFile.LogString(), err)
// ideally here it should return the error, but it would require refactoring all callers
// so just return a dummy communicator that does nothing, almost the same behavior as before, not bad
return &catFileBatchCommunicator{
writer: io.Discard,
reader: bufio.NewReader(bytes.NewReader(nil)),
cancel: func() {
ctxCancel(err)
},
}
ctxCancel(err)
pipeClose()
return ret
}
go func() {
@@ -66,19 +60,10 @@ func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Co
log.Error("cat-file --batch command failed in repo %s, error: %v", repoPath, err)
}
ctxCancel(err)
pipeClose()
}()
// use a buffered reader to read from the cat-file --batch (StringReader.ReadString)
batchReader := bufio.NewReaderSize(batchStdoutReader, 32*1024)
return &catFileBatchCommunicator{
writer: batchStdinWriter,
reader: batchReader,
cancel: func() {
ctxCancel(nil)
},
debugGitCmd: cmdCatFile,
}
return ret
}
// catFileBatchParseInfoLine reads the header line from cat-file --batch
+4 -4
View File
@@ -66,10 +66,10 @@ func testCatFileBatch(t *testing.T) {
switch b := batch.(type) {
case *catFileBatchLegacy:
c = b.batchCheck
_, _ = c.writer.Write([]byte("in-complete-line-"))
_, _ = c.reqWriter.Write([]byte("in-complete-line-"))
case *catFileBatchCommand:
c = b.batch
_, _ = c.writer.Write([]byte("info"))
_, _ = c.reqWriter.Write([]byte("info"))
default:
t.FailNow()
return
@@ -78,8 +78,8 @@ func testCatFileBatch(t *testing.T) {
wg := sync.WaitGroup{}
wg.Go(func() {
buf := make([]byte, 100)
_, _ = c.reader.Read(buf)
n, errRead := c.reader.Read(buf)
_, _ = c.respReader.Read(buf)
n, errRead := c.respReader.Read(buf)
assert.Zero(t, n)
assert.ErrorIs(t, errRead, io.EOF) // the pipe is closed due to command being killed
})
+5 -6
View File
@@ -78,7 +78,7 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
}
return cmd.WithDir(repo.Path).
WithStdout(writer).
WithStdoutCopy(writer).
RunWithStderr(repo.Ctx)
}
@@ -286,11 +286,10 @@ func GetAffectedFiles(repo *Repository, branchName, oldCommitID, newCommitID str
affectedFiles := make([]string, 0, 32)
// Run `git diff --name-only` to get the names of the changed files
var stdoutReader io.ReadCloser
err := gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID).
WithEnv(env).
WithDir(repo.Path).
WithStdoutReader(&stdoutReader).
cmd := gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID)
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
defer stdoutReaderClose()
err := cmd.WithEnv(env).WithDir(repo.Path).
WithPipelineFunc(func(ctx gitcmd.Context) error {
// Now scan the output from the command
scanner := bufio.NewScanner(stdoutReader)
+1 -1
View File
@@ -21,7 +21,7 @@ import (
"github.com/hashicorp/go-version"
)
const RequiredVersion = "2.0.0" // the minimum Git version required
const RequiredVersion = "2.6.0" // the minimum Git version required
type Features struct {
gitVersion *version.Version
+99 -75
View File
@@ -28,9 +28,6 @@ import (
// In most cases, it shouldn't be used. Use AddXxx function instead
type TrustedCmdArgs []internal.CmdArg
// DefaultLocale is the default LC_ALL to run git commands in.
const DefaultLocale = "C"
// Command represents a command with its subcommands or arguments.
type Command struct {
callerInfo string
@@ -47,9 +44,14 @@ type Command struct {
cmdFinished process.FinishedFunc
cmdStartTime time.Time
cmdStdinWriter *io.WriteCloser
cmdStdoutReader *io.ReadCloser
cmdStderrReader *io.ReadCloser
parentPipeFiles []*os.File
childrenPipeFiles []*os.File
// only os.Pipe and in-memory buffers can work with Stdin safely, see https://github.com/golang/go/issues/77227 if the command would exit unexpectedly
cmdStdin io.Reader
cmdStdout io.Writer
cmdStderr io.Writer
cmdManagedStderr *bytes.Buffer
}
@@ -215,19 +217,6 @@ type runOpts struct {
// The correct approach is to use `--git-dir" global argument
Dir string
Stdout io.Writer
// Stdin is used for passing input to the command
// The caller must make sure the Stdin writer is closed properly to finish the Run function.
// Otherwise, the Run function may hang for long time or forever, especially when the Git's context deadline is not the same as the caller's.
// Some common mistakes:
// * `defer stdinWriter.Close()` then call `cmd.Run()`: the Run() would never return if the command is killed by timeout
// * `go { case <- parentContext.Done(): stdinWriter.Close() }` with `cmd.Run(DefaultTimeout)`: the command would have been killed by timeout but the Run doesn't return until stdinWriter.Close()
// * `go { if stdoutReader.Read() err != nil: stdinWriter.Close() }` with `cmd.Run()`: the stdoutReader may never return error if the command is killed by timeout
// In the future, ideally the git module itself should have full control of the stdin, to avoid such problems and make it easier to refactor to a better architecture.
// Use new functions like WithStdinWriter to avoid such problems.
Stdin io.Reader
PipelineFunc func(Context) error
}
@@ -259,7 +248,7 @@ func commonBaseEnvs() []string {
// CommonGitCmdEnvs returns the common environment variables for a "git" command.
func CommonGitCmdEnvs() []string {
return append(commonBaseEnvs(), []string{
"LC_ALL=" + DefaultLocale,
"LC_ALL=C", // ensure git output is in English, error messages are parsed in English
"GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3
}...)
}
@@ -286,30 +275,76 @@ func (c *Command) WithTimeout(timeout time.Duration) *Command {
return c
}
func (c *Command) WithStdoutReader(r *io.ReadCloser) *Command {
c.cmdStdoutReader = r
func (c *Command) makeStdoutStderr(w *io.Writer) (PipeReader, func()) {
pr, pw, err := os.Pipe()
if err != nil {
c.preErrors = append(c.preErrors, err)
return &pipeNull{err}, func() {}
}
c.childrenPipeFiles = append(c.childrenPipeFiles, pw)
c.parentPipeFiles = append(c.parentPipeFiles, pr)
*w /* stdout, stderr */ = pw
return &pipeReader{f: pr}, func() { pr.Close() }
}
// MakeStdinPipe creates a writer for the command's stdin.
// The returned closer function must be called by the caller to close the pipe.
func (c *Command) MakeStdinPipe() (writer PipeWriter, closer func()) {
pr, pw, err := os.Pipe()
if err != nil {
c.preErrors = append(c.preErrors, err)
return &pipeNull{err}, func() {}
}
c.childrenPipeFiles = append(c.childrenPipeFiles, pr)
c.parentPipeFiles = append(c.parentPipeFiles, pw)
c.cmdStdin = pr
return &pipeWriter{pw}, func() { pw.Close() }
}
// MakeStdoutPipe creates a reader for the command's stdout.
// The returned closer function must be called by the caller to close the pipe.
// After the pipe reader is closed, the unread data will be discarded.
func (c *Command) MakeStdoutPipe() (reader PipeReader, closer func()) {
return c.makeStdoutStderr(&c.cmdStdout)
}
// MakeStderrPipe is like MakeStdoutPipe, but for stderr.
func (c *Command) MakeStderrPipe() (reader PipeReader, closer func()) {
return c.makeStdoutStderr(&c.cmdStderr)
}
func (c *Command) MakeStdinStdoutPipe() (stdin PipeWriter, stdout PipeReader, closer func()) {
stdin, stdinClose := c.MakeStdinPipe()
stdout, stdoutClose := c.MakeStdoutPipe()
return stdin, stdout, func() {
stdinClose()
stdoutClose()
}
}
func (c *Command) WithStdinBytes(stdin []byte) *Command {
c.cmdStdin = bytes.NewReader(stdin)
return c
}
// WithStdout is deprecated, use WithStdoutReader instead
func (c *Command) WithStdout(stdout io.Writer) *Command {
c.opts.Stdout = stdout
func (c *Command) WithStdoutBuffer(w PipeBufferWriter) *Command {
c.cmdStdout = w
return c
}
func (c *Command) WithStderrReader(r *io.ReadCloser) *Command {
c.cmdStderrReader = r
// WithStdinCopy and WithStdoutCopy are general functions that accept any io.Reader / io.Writer.
// In this case, Golang exec.Cmd will start new internal goroutines to do io.Copy between pipes and provided Reader/Writer.
// If the reader or writer is blocked and never returns, then the io.Copy won't finish, then exec.Cmd.Wait won't return, which may cause deadlocks.
// A typical deadlock example is:
// * `r,w:=io.Pipe(); cmd.Stdin=r; defer w.Close(); cmd.Run()`: the Run() will never return because stdin reader is blocked forever and w.Close() will never be called.
// If the reader/writer won't block forever (for example: read from a file or buffer), then these functions are safe to use.
func (c *Command) WithStdinCopy(w io.Reader) *Command {
c.cmdStdin = w
return c
}
func (c *Command) WithStdinWriter(w *io.WriteCloser) *Command {
c.cmdStdinWriter = w
return c
}
// WithStdin is deprecated, use WithStdinWriter instead
func (c *Command) WithStdin(stdin io.Reader) *Command {
c.opts.Stdin = stdin
func (c *Command) WithStdoutCopy(w io.Writer) *Command {
c.cmdStdout = w
return c
}
@@ -348,9 +383,10 @@ func (c *Command) Start(ctx context.Context) (retErr error) {
}
defer func() {
c.closePipeFiles(c.childrenPipeFiles)
if retErr != nil {
// release the pipes to avoid resource leak
c.closeStdioPipes()
// release the pipes to avoid resource leak since the command failed to start
c.closePipeFiles(c.parentPipeFiles)
// if error occurs, we must also finish the task, otherwise, cmdFinished will be called in "Wait" function
if c.cmdFinished != nil {
c.cmdFinished()
@@ -359,7 +395,7 @@ func (c *Command) Start(ctx context.Context) (retErr error) {
}()
if len(c.preErrors) != 0 {
// In most cases, such error shouldn't happen. If it happens, it must be a programming error, so we log it as error level with more details
// In most cases, such error shouldn't happen. If it happens, log it as error level with more details
err := errors.Join(c.preErrors...)
log.Error("git command: %s, error: %s", c.LogString(), err)
return err
@@ -386,7 +422,7 @@ func (c *Command) Start(ctx context.Context) (retErr error) {
c.cmdStartTime = time.Now()
c.cmd = exec.CommandContext(ctx, c.prog, append(c.configArgs, c.args...)...)
c.cmd = exec.CommandContext(c.cmdCtx, c.prog, append(c.configArgs, c.args...)...)
if c.opts.Env == nil {
c.cmd.Env = os.Environ()
} else {
@@ -396,52 +432,38 @@ func (c *Command) Start(ctx context.Context) (retErr error) {
process.SetSysProcAttribute(c.cmd)
c.cmd.Env = append(c.cmd.Env, CommonGitCmdEnvs()...)
c.cmd.Dir = c.opts.Dir
c.cmd.Stdout = c.opts.Stdout
c.cmd.Stdin = c.opts.Stdin
if _, err := safeAssignPipe(c.cmdStdinWriter, c.cmd.StdinPipe); err != nil {
return err
}
if _, err := safeAssignPipe(c.cmdStdoutReader, c.cmd.StdoutPipe); err != nil {
return err
}
if _, err := safeAssignPipe(c.cmdStderrReader, c.cmd.StderrPipe); err != nil {
return err
}
if c.cmdManagedStderr != nil {
if c.cmd.Stderr != nil {
panic("CombineStderr needs managed (but not caller-provided) stderr pipe")
}
c.cmd.Stderr = c.cmdManagedStderr
}
c.cmd.Stdout = c.cmdStdout
c.cmd.Stdin = c.cmdStdin
c.cmd.Stderr = c.cmdStderr
return c.cmd.Start()
}
func (c *Command) closeStdioPipes() {
safeClosePtrCloser(c.cmdStdoutReader)
safeClosePtrCloser(c.cmdStderrReader)
safeClosePtrCloser(c.cmdStdinWriter)
func (c *Command) closePipeFiles(files []*os.File) {
for _, f := range files {
_ = f.Close()
}
}
func (c *Command) Wait() error {
defer func() {
c.closeStdioPipes()
// The reader in another goroutine might be still reading the stdout, so we shouldn't close the pipes here
// MakeStdoutPipe returns a closer function to force callers to close the pipe correctly
// Here we only need to mark the command as finished
c.cmdFinished()
}()
if c.opts.PipelineFunc != nil {
errCallback := c.opts.PipelineFunc(&cmdContext{Context: c.cmdCtx, cmd: c})
// after the pipeline function returns, we can safely cancel the command context and close the stdio pipes
c.cmdCancel(errCallback)
c.closeStdioPipes()
errPipeline := c.opts.PipelineFunc(&cmdContext{Context: c.cmdCtx, cmd: c})
// after the pipeline function returns, we can safely cancel the command context and close the pipes, the data in pipes should have been consumed
c.cmdCancel(errPipeline)
c.closePipeFiles(c.parentPipeFiles)
errWait := c.cmd.Wait()
errCause := context.Cause(c.cmdCtx)
// the pipeline function should be able to know whether it succeeds or fails
if errCallback == nil && (errCause == nil || errors.Is(errCause, context.Canceled)) {
if errPipeline == nil && (errCause == nil || errors.Is(errCause, context.Canceled)) {
return nil
}
return errors.Join(errCallback, errCause, errWait)
return errors.Join(wrapPipelineError(errPipeline), errCause, errWait)
}
// there might be other goroutines using the context or pipes, so we just wait for the command to finish
@@ -460,7 +482,11 @@ func (c *Command) Wait() error {
}
func (c *Command) StartWithStderr(ctx context.Context) RunStdError {
if c.cmdStderr != nil {
panic("caller-provided stderr receiver doesn't work with managed stderr buffer")
}
c.cmdManagedStderr = &bytes.Buffer{}
c.cmdStderr = c.cmdManagedStderr
err := c.Start(ctx)
if err != nil {
return &runStdError{err: err}
@@ -470,7 +496,7 @@ func (c *Command) StartWithStderr(ctx context.Context) RunStdError {
func (c *Command) WaitWithStderr() RunStdError {
if c.cmdManagedStderr == nil {
panic("CombineStderr needs managed (but not caller-provided) stderr pipe")
panic("managed stderr buffer is not initialized")
}
errWait := c.Wait()
if errWait == nil {
@@ -506,14 +532,12 @@ func (c *Command) RunStdBytes(ctx context.Context) (stdout, stderr []byte, runEr
}
func (c *Command) runStdBytes(ctx context.Context) ([]byte, []byte, RunStdError) {
if c.opts.Stdout != nil || c.cmdStdoutReader != nil || c.cmdStderrReader != nil {
// we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug
if c.cmdStdout != nil || c.cmdStderr != nil {
// it must panic here, otherwise there would be bugs if developers set other Stdin/Stderr by mistake, and it would be very difficult to debug
panic("stdout and stderr field must be nil when using RunStdBytes")
}
stdoutBuf := &bytes.Buffer{}
err := c.WithParentCallerInfo().
WithStdout(stdoutBuf).
RunWithStderr(ctx)
err := c.WithParentCallerInfo().WithStdoutBuffer(stdoutBuf).RunWithStderr(ctx)
return stdoutBuf.Bytes(), c.cmdManagedStderr.Bytes(), err
}
+4 -1
View File
@@ -121,7 +121,10 @@ func TestRunWithContextTimeout(t *testing.T) {
require.NoError(t, err)
})
t.Run("WithTimeout", func(t *testing.T) {
err := NewCommand("hash-object", "--stdin").WithTimeout(1 * time.Millisecond).Run(t.Context())
cmd := NewCommand("hash-object", "--stdin")
_, _, pipeClose := cmd.MakeStdinStdoutPipe()
defer pipeClose()
err := cmd.WithTimeout(1 * time.Millisecond).Run(t.Context())
require.ErrorIs(t, err, context.DeadlineExceeded)
})
}
+23
View File
@@ -76,3 +76,26 @@ func IsErrorCanceledOrKilled(err error) bool {
// TODO: in the future, we need to use unified error type from gitcmd.Run to check whether it is manually canceled
return errors.Is(err, context.Canceled) || IsErrorSignalKilled(err)
}
type pipelineError struct {
error
}
func (e pipelineError) Unwrap() error {
return e.error
}
func wrapPipelineError(err error) error {
if err == nil {
return nil
}
return pipelineError{err}
}
func ErrorAsPipeline(err error) error {
var pipelineErr pipelineError
if errors.As(err, &pipelineErr) {
return pipelineErr.error
}
return nil
}
+79
View File
@@ -0,0 +1,79 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitcmd
import (
"io"
"os"
)
type PipeBufferReader interface {
Read(p []byte) (n int, err error)
Bytes() []byte
}
type PipeBufferWriter interface {
Write(p []byte) (n int, err error)
Bytes() []byte
}
type PipeReader interface {
io.ReadCloser
internalOnly()
}
type pipeReader struct {
f *os.File
}
func (r *pipeReader) internalOnly() {}
func (r *pipeReader) Read(p []byte) (n int, err error) {
return r.f.Read(p)
}
func (r *pipeReader) Close() error {
return r.f.Close()
}
type PipeWriter interface {
io.WriteCloser
internalOnly()
}
type pipeWriter struct {
f *os.File
}
func (w *pipeWriter) internalOnly() {}
func (w *pipeWriter) Close() error {
return w.f.Close()
}
func (w *pipeWriter) Write(p []byte) (n int, err error) {
return w.f.Write(p)
}
func (w *pipeWriter) DrainBeforeClose() error {
return nil
}
type pipeNull struct {
err error
}
func (p *pipeNull) internalOnly() {}
func (p *pipeNull) Read([]byte) (n int, err error) {
return 0, p.err
}
func (p *pipeNull) Write([]byte) (n int, err error) {
return 0, p.err
}
func (p *pipeNull) Close() error {
return nil
}
-35
View File
@@ -1,35 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitcmd
import (
"io"
)
func safeClosePtrCloser[T *io.ReadCloser | *io.WriteCloser](c T) {
switch v := any(c).(type) {
case *io.ReadCloser:
if v != nil && *v != nil {
_ = (*v).Close()
}
case *io.WriteCloser:
if v != nil && *v != nil {
_ = (*v).Close()
}
default:
panic("unsupported type")
}
}
func safeAssignPipe[T any](p *T, fn func() (T, error)) (bool, error) {
if p == nil {
return false, nil
}
v, err := fn()
if err != nil {
return false, err
}
*p = v
return true, nil
}
+2 -3
View File
@@ -8,7 +8,6 @@ import (
"context"
"errors"
"fmt"
"io"
"slices"
"strconv"
"strings"
@@ -74,9 +73,9 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
cmd.AddDashesAndList(opts.PathspecList...)
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
var stdoutReader io.ReadCloser
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
defer stdoutReaderClose()
err := cmd.WithDir(repo.Path).
WithStdoutReader(&stdoutReader).
WithPipelineFunc(func(ctx gitcmd.Context) error {
isInBlock := false
rd := bufio.NewReaderSize(stdoutReader, util.IfZero(opts.MaxLineLength, 16*1024))
+11 -19
View File
@@ -15,25 +15,12 @@ import (
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git/gitcmd"
"github.com/djherbis/buffer"
"github.com/djherbis/nio/v3"
"code.gitea.io/gitea/modules/log"
)
// LogNameStatusRepo opens git log --raw in the provided repo and returns a stdin pipe, a stdout reader and cancel function
func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, paths ...string) (*bufio.Reader, func()) {
// We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
// so let's create a batch stdin and stdout
stdoutReader, stdoutWriter := nio.Pipe(buffer.New(32 * 1024))
// Lets also create a context so that we can absolutely ensure that the command should die when we're done
ctx, ctxCancel := context.WithCancel(ctx)
cancel := func() {
ctxCancel()
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}
cmd := gitcmd.NewCommand()
cmd.AddArguments("log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z").AddDynamicArguments(head)
@@ -63,16 +50,21 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p
}
cmd.AddDashesAndList(files...)
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
ctx, ctxCancel := context.WithCancel(ctx)
go func() {
err := cmd.WithDir(repository).
WithStdout(stdoutWriter).
RunWithStderr(ctx)
_ = stdoutWriter.CloseWithError(err)
err := cmd.WithDir(repository).RunWithStderr(ctx)
if err != nil && !errors.Is(err, context.Canceled) {
log.Error("Unable to run git command %v: %v", cmd.LogString(), err)
}
}()
bufReader := bufio.NewReaderSize(stdoutReader, 32*1024)
return bufReader, cancel
return bufReader, func() {
ctxCancel()
stdoutReaderClose()
}
}
// LogNameStatusRepoParser parses a git log raw output from LogRawRepo
+13 -47
View File
@@ -6,67 +6,33 @@ package pipeline
import (
"bufio"
"context"
"fmt"
"io"
"strconv"
"strings"
"sync"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// CatFileBatchCheck runs cat-file with --batch-check
func CatFileBatchCheck(ctx context.Context, shasToCheckReader *io.PipeReader, catFileCheckWriter *io.PipeWriter, wg *sync.WaitGroup, tmpBasePath string) {
defer wg.Done()
defer shasToCheckReader.Close()
defer catFileCheckWriter.Close()
cmd := gitcmd.NewCommand("cat-file", "--batch-check")
if err := cmd.WithDir(tmpBasePath).
WithStdin(shasToCheckReader).
WithStdout(catFileCheckWriter).
RunWithStderr(ctx); err != nil {
_ = catFileCheckWriter.CloseWithError(fmt.Errorf("git cat-file --batch-check [%s]: %w", tmpBasePath, err))
}
func CatFileBatchCheck(ctx context.Context, cmd *gitcmd.Command, tmpBasePath string) error {
cmd.AddArguments("cat-file", "--batch-check")
return cmd.WithDir(tmpBasePath).RunWithStderr(ctx)
}
// CatFileBatchCheckAllObjects runs cat-file with --batch-check --batch-all
func CatFileBatchCheckAllObjects(ctx context.Context, catFileCheckWriter *io.PipeWriter, wg *sync.WaitGroup, tmpBasePath string, errChan chan<- error) {
defer wg.Done()
defer catFileCheckWriter.Close()
cmd := gitcmd.NewCommand("cat-file", "--batch-check", "--batch-all-objects")
if err := cmd.WithDir(tmpBasePath).
WithStdout(catFileCheckWriter).
RunWithStderr(ctx); err != nil {
_ = catFileCheckWriter.CloseWithError(fmt.Errorf("git cat-file --batch-check --batch-all-object [%s]: %w", tmpBasePath, err))
errChan <- err
}
func CatFileBatchCheckAllObjects(ctx context.Context, cmd *gitcmd.Command, tmpBasePath string) error {
return cmd.AddArguments("cat-file", "--batch-check", "--batch-all-objects").WithDir(tmpBasePath).RunWithStderr(ctx)
}
// CatFileBatch runs cat-file --batch
func CatFileBatch(ctx context.Context, shasToBatchReader *io.PipeReader, catFileBatchWriter *io.PipeWriter, wg *sync.WaitGroup, tmpBasePath string) {
defer wg.Done()
defer shasToBatchReader.Close()
defer catFileBatchWriter.Close()
if err := gitcmd.NewCommand("cat-file", "--batch").
WithDir(tmpBasePath).
WithStdin(shasToBatchReader).
WithStdout(catFileBatchWriter).
RunWithStderr(ctx); err != nil {
_ = shasToBatchReader.CloseWithError(fmt.Errorf("git rev-list [%s]: %w", tmpBasePath, err))
}
func CatFileBatch(ctx context.Context, cmd *gitcmd.Command, tmpBasePath string) error {
return cmd.AddArguments("cat-file", "--batch").WithDir(tmpBasePath).RunWithStderr(ctx)
}
// BlobsLessThan1024FromCatFileBatchCheck reads a pipeline from cat-file --batch-check and returns the blobs <1024 in size
func BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader *io.PipeReader, shasToBatchWriter *io.PipeWriter, wg *sync.WaitGroup) {
defer wg.Done()
defer catFileCheckReader.Close()
scanner := bufio.NewScanner(catFileCheckReader)
defer func() {
_ = shasToBatchWriter.CloseWithError(scanner.Err())
}()
func BlobsLessThan1024FromCatFileBatchCheck(in io.ReadCloser, out io.WriteCloser) error {
defer out.Close()
scanner := bufio.NewScanner(in)
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 {
@@ -82,12 +48,12 @@ func BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader *io.PipeReader, s
}
toWrite := []byte(fields[0] + "\n")
for len(toWrite) > 0 {
n, err := shasToBatchWriter.Write(toWrite)
n, err := out.Write(toWrite)
if err != nil {
_ = catFileCheckReader.CloseWithError(err)
break
return err
}
toWrite = toWrite[n:]
}
}
return scanner.Err()
}
-5
View File
@@ -4,7 +4,6 @@
package pipeline
import (
"fmt"
"time"
"code.gitea.io/gitea/modules/git"
@@ -26,7 +25,3 @@ type lfsResultSlice []*LFSResult
func (a lfsResultSlice) Len() int { return len(a) }
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
func lfsError(msg string, err error) error {
return fmt.Errorf("LFS error occurred, %s: err: %w", msg, err)
}
+5 -66
View File
@@ -6,11 +6,10 @@
package pipeline
import (
"bufio"
"fmt"
"io"
"sort"
"strings"
"sync"
"code.gitea.io/gitea/modules/git"
@@ -24,7 +23,6 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
resultsMap := map[string]*LFSResult{}
results := make([]*LFSResult, 0)
basePath := repo.Path
gogitRepo := repo.GoGitRepo()
commitsIter, err := gogitRepo.Log(&gogit.LogOptions{
@@ -32,7 +30,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
All: true,
})
if err != nil {
return nil, lfsError("failed to get GoGit CommitsIter", err)
return nil, fmt.Errorf("LFS error occurred, failed to get GoGit CommitsIter: err: %w", err)
}
err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
@@ -66,7 +64,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
return nil
})
if err != nil && err != io.EOF {
return nil, lfsError("failure in CommitIter.ForEach", err)
return nil, fmt.Errorf("LFS error occurred, failure in CommitIter.ForEach: %w", err)
}
for _, result := range resultsMap {
@@ -82,65 +80,6 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
}
sort.Sort(lfsResultSlice(results))
// Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
shasToNameReader, shasToNameWriter := io.Pipe()
nameRevStdinReader, nameRevStdinWriter := io.Pipe()
errChan := make(chan error, 1)
wg := sync.WaitGroup{}
wg.Add(3)
go func() {
defer wg.Done()
scanner := bufio.NewScanner(nameRevStdinReader)
i := 0
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 {
continue
}
result := results[i]
result.FullCommitName = line
result.BranchName = strings.Split(line, "~")[0]
i++
}
}()
go NameRevStdin(repo.Ctx, shasToNameReader, nameRevStdinWriter, &wg, basePath)
go func() {
defer wg.Done()
defer shasToNameWriter.Close()
for _, result := range results {
i := 0
if i < len(result.SHA) {
n, err := shasToNameWriter.Write([]byte(result.SHA)[i:])
if err != nil {
errChan <- err
break
}
i += n
}
n := 0
for n < 1 {
n, err = shasToNameWriter.Write([]byte{'\n'})
if err != nil {
errChan <- err
break
}
}
}
}()
wg.Wait()
select {
case err, has := <-errChan:
if has {
return nil, lfsError("unable to obtain name for LFS files", err)
}
default:
}
return results, nil
err = fillResultNameRev(repo.Ctx, repo.Path, results)
return results, err
}
+15 -72
View File
@@ -12,34 +12,27 @@ import (
"io"
"sort"
"strings"
"sync"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// FindLFSFile finds commits that contain a provided pointer file hash
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) (results []*LFSResult, _ error) {
cmd := gitcmd.NewCommand("rev-list", "--all")
revListReader, revListReaderClose := cmd.MakeStdoutPipe()
defer revListReaderClose()
err := cmd.WithDir(repo.Path).
WithPipelineFunc(func(context gitcmd.Context) (err error) {
results, err = findLFSFileFunc(repo, objectID, revListReader)
return err
}).RunWithStderr(repo.Ctx)
return results, err
}
func findLFSFileFunc(repo *git.Repository, objectID git.ObjectID, revListReader io.Reader) ([]*LFSResult, error) {
resultsMap := map[string]*LFSResult{}
results := make([]*LFSResult, 0)
basePath := repo.Path
// Use rev-list to provide us with all commits in order
revListReader, revListWriter := io.Pipe()
defer func() {
_ = revListWriter.Close()
_ = revListReader.Close()
}()
go func() {
err := gitcmd.NewCommand("rev-list", "--all").
WithDir(repo.Path).
WithStdout(revListWriter).
RunWithStderr(repo.Ctx)
_ = revListWriter.CloseWithError(err)
}()
// Next feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
// so let's create a batch stdin and stdout
batch, cancel, err := repo.CatFileBatch(repo.Ctx)
@@ -158,56 +151,6 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
}
sort.Sort(lfsResultSlice(results))
// Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
shasToNameReader, shasToNameWriter := io.Pipe()
nameRevStdinReader, nameRevStdinWriter := io.Pipe()
errChan := make(chan error, 1)
wg := sync.WaitGroup{}
wg.Add(3)
go func() {
defer wg.Done()
scanner := bufio.NewScanner(nameRevStdinReader)
i := 0
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 {
continue
}
result := results[i]
result.FullCommitName = line
result.BranchName = strings.Split(line, "~")[0]
i++
}
}()
go NameRevStdin(repo.Ctx, shasToNameReader, nameRevStdinWriter, &wg, basePath)
go func() {
defer wg.Done()
defer shasToNameWriter.Close()
for _, result := range results {
_, err := shasToNameWriter.Write([]byte(result.SHA))
if err != nil {
errChan <- err
break
}
_, err = shasToNameWriter.Write([]byte{'\n'})
if err != nil {
errChan <- err
break
}
}
}()
wg.Wait()
select {
case err, has := <-errChan:
if has {
return nil, lfsError("unable to obtain name for LFS files", err)
}
default:
}
return results, nil
err = fillResultNameRev(repo.Ctx, repo.Path, results)
return results, err
}
+44 -15
View File
@@ -4,25 +4,54 @@
package pipeline
import (
"bufio"
"context"
"fmt"
"io"
"sync"
"errors"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"golang.org/x/sync/errgroup"
)
// NameRevStdin runs name-rev --stdin
func NameRevStdin(ctx context.Context, shasToNameReader *io.PipeReader, nameRevStdinWriter *io.PipeWriter, wg *sync.WaitGroup, tmpBasePath string) {
defer wg.Done()
defer shasToNameReader.Close()
defer nameRevStdinWriter.Close()
func fillResultNameRev(ctx context.Context, basePath string, results []*LFSResult) error {
// Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
wg := errgroup.Group{}
cmd := gitcmd.NewCommand("name-rev", "--stdin", "--name-only", "--always").WithDir(basePath)
stdin, stdinClose := cmd.MakeStdinPipe()
stdout, stdoutClose := cmd.MakeStdoutPipe()
defer stdinClose()
defer stdoutClose()
if err := gitcmd.NewCommand("name-rev", "--stdin", "--name-only", "--always").
WithDir(tmpBasePath).
WithStdin(shasToNameReader).
WithStdout(nameRevStdinWriter).
RunWithStderr(ctx); err != nil {
_ = shasToNameReader.CloseWithError(fmt.Errorf("git name-rev [%s]: %w", tmpBasePath, err))
}
wg.Go(func() error {
scanner := bufio.NewScanner(stdout)
i := 0
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 {
continue
}
result := results[i]
result.FullCommitName = line
result.BranchName = strings.Split(line, "~")[0]
i++
}
return scanner.Err()
})
wg.Go(func() error {
defer stdinClose()
for _, result := range results {
_, err := stdin.Write([]byte(result.SHA))
if err != nil {
return err
}
_, err = stdin.Write([]byte{'\n'})
if err != nil {
return err
}
}
return nil
})
err := cmd.RunWithStderr(ctx)
return errors.Join(err, wg.Wait())
}
+9 -36
View File
@@ -6,52 +6,25 @@ package pipeline
import (
"bufio"
"context"
"fmt"
"io"
"strings"
"sync"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// RevListAllObjects runs rev-list --objects --all and writes to a pipewriter
func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.WaitGroup, basePath string, errChan chan<- error) {
defer wg.Done()
defer revListWriter.Close()
cmd := gitcmd.NewCommand("rev-list", "--objects", "--all")
if err := cmd.WithDir(basePath).
WithStdout(revListWriter).
RunWithStderr(ctx); err != nil {
_ = revListWriter.CloseWithError(fmt.Errorf("git rev-list --objects --all [%s]: %w", basePath, err))
errChan <- err
}
}
// RevListObjects run rev-list --objects from headSHA to baseSHA
func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.WaitGroup, tmpBasePath, headSHA, baseSHA string, errChan chan<- error) {
defer wg.Done()
defer revListWriter.Close()
cmd := gitcmd.NewCommand("rev-list", "--objects").AddDynamicArguments(headSHA)
func RevListObjects(ctx context.Context, cmd *gitcmd.Command, tmpBasePath, headSHA, baseSHA string) error {
cmd.AddArguments("rev-list", "--objects").AddDynamicArguments(headSHA)
if baseSHA != "" {
cmd = cmd.AddArguments("--not").AddDynamicArguments(baseSHA)
}
if err := cmd.WithDir(tmpBasePath).
WithStdout(revListWriter).
RunWithStderr(ctx); err != nil {
errChan <- fmt.Errorf("git rev-list [%s]: %w", tmpBasePath, err)
}
return cmd.WithDir(tmpBasePath).RunWithStderr(ctx)
}
// BlobsFromRevListObjects reads a RevListAllObjects and only selects blobs
func BlobsFromRevListObjects(revListReader *io.PipeReader, shasToCheckWriter *io.PipeWriter, wg *sync.WaitGroup) {
defer wg.Done()
defer revListReader.Close()
scanner := bufio.NewScanner(revListReader)
defer func() {
_ = shasToCheckWriter.CloseWithError(scanner.Err())
}()
func BlobsFromRevListObjects(in io.ReadCloser, out io.WriteCloser) error {
defer out.Close()
scanner := bufio.NewScanner(in)
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 {
@@ -63,12 +36,12 @@ func BlobsFromRevListObjects(revListReader *io.PipeReader, shasToCheckWriter *io
}
toWrite := []byte(fields[0] + "\n")
for len(toWrite) > 0 {
n, err := shasToCheckWriter.Write(toWrite)
n, err := out.Write(toWrite)
if err != nil {
_ = revListReader.CloseWithError(err)
break
return err
}
toWrite = toWrite[n:]
}
}
return scanner.Err()
}
-68
View File
@@ -1,68 +0,0 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"context"
"fmt"
"io"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// ArchiveType archive types
type ArchiveType int
const (
ArchiveUnknown ArchiveType = iota
ArchiveZip // 1
ArchiveTarGz // 2
ArchiveBundle // 3
)
// String converts an ArchiveType to string: the extension of the archive file without prefix dot
func (a ArchiveType) String() string {
switch a {
case ArchiveZip:
return "zip"
case ArchiveTarGz:
return "tar.gz"
case ArchiveBundle:
return "bundle"
}
return "unknown"
}
func SplitArchiveNameType(s string) (string, ArchiveType) {
switch {
case strings.HasSuffix(s, ".zip"):
return strings.TrimSuffix(s, ".zip"), ArchiveZip
case strings.HasSuffix(s, ".tar.gz"):
return strings.TrimSuffix(s, ".tar.gz"), ArchiveTarGz
case strings.HasSuffix(s, ".bundle"):
return strings.TrimSuffix(s, ".bundle"), ArchiveBundle
}
return s, ArchiveUnknown
}
// CreateArchive create archive content to the target path
func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, target io.Writer, usePrefix bool, commitID string) error {
if format.String() == "unknown" {
return fmt.Errorf("unknown format: %v", format)
}
cmd := gitcmd.NewCommand("archive")
if usePrefix {
cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.Path, ".git"))+"/")
}
cmd.AddOptionFormat("--format=%s", format.String())
cmd.AddDynamicArguments(commitID)
return cmd.WithDir(repo.Path).
WithStdout(target).
RunWithStderr(ctx)
}
+73 -76
View File
@@ -94,84 +94,81 @@ func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs git
}
func WalkShowRef(ctx context.Context, repoPath string, extraArgs gitcmd.TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) {
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
go func() {
args := gitcmd.TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"}
args = append(args, extraArgs...)
err := gitcmd.NewCommand(args...).
WithDir(repoPath).
WithStdout(stdoutWriter).
RunWithStderr(ctx)
_ = stdoutWriter.CloseWithError(err)
}()
i := 0
bufReader := bufio.NewReader(stdoutReader)
for i < skip {
_, isPrefix, err := bufReader.ReadLine()
if err == io.EOF {
return i, nil
}
if err != nil {
return 0, err
}
if !isPrefix {
i++
}
args := gitcmd.TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"}
args = append(args, extraArgs...)
cmd := gitcmd.NewCommand(args...)
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
defer stdoutReaderClose()
cmd.WithDir(repoPath).
WithPipelineFunc(func(c gitcmd.Context) error {
bufReader := bufio.NewReader(stdoutReader)
for i < skip {
_, isPrefix, err := bufReader.ReadLine()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if !isPrefix {
i++
}
}
for limit == 0 || i < skip+limit {
// The output of show-ref is simply a list:
// <sha> SP <ref> LF
sha, err := bufReader.ReadString(' ')
if err == io.EOF {
return nil
}
if err != nil {
return err
}
branchName, err := bufReader.ReadString('\n')
if err == io.EOF {
// This shouldn't happen... but we'll tolerate it for the sake of peace
return nil
}
if err != nil {
return err
}
if len(branchName) > 0 {
branchName = branchName[:len(branchName)-1]
}
if len(sha) > 0 {
sha = sha[:len(sha)-1]
}
err = walkfn(sha, branchName)
if err != nil {
return err
}
i++
}
// count all refs
for limit != 0 {
_, isPrefix, err := bufReader.ReadLine()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if !isPrefix {
i++
}
}
return nil
})
err = cmd.RunWithStderr(ctx)
if errPipeline := gitcmd.ErrorAsPipeline(err); errPipeline != nil {
return i, errPipeline // keep the old behavior: return pipeline error directly
}
for limit == 0 || i < skip+limit {
// The output of show-ref is simply a list:
// <sha> SP <ref> LF
sha, err := bufReader.ReadString(' ')
if err == io.EOF {
return i, nil
}
if err != nil {
return 0, err
}
branchName, err := bufReader.ReadString('\n')
if err == io.EOF {
// This shouldn't happen... but we'll tolerate it for the sake of peace
return i, nil
}
if err != nil {
return i, err
}
if len(branchName) > 0 {
branchName = branchName[:len(branchName)-1]
}
if len(sha) > 0 {
sha = sha[:len(sha)-1]
}
err = walkfn(sha, branchName)
if err != nil {
return i, err
}
i++
}
// count all refs
for limit != 0 {
_, isPrefix, err := bufReader.ReadLine()
if err == io.EOF {
return i, nil
}
if err != nil {
return 0, err
}
if !isPrefix {
i++
}
}
return i, nil
return i, err
}
// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash
+45 -50
View File
@@ -226,60 +226,55 @@ type CommitsByFileAndRangeOptions struct {
// CommitsByFileAndRange return the commits according revision file and the page
func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) {
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
go func() {
gitCmd := gitcmd.NewCommand("rev-list").
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
gitCmd.AddDynamicArguments(opts.Revision)
gitCmd := gitcmd.NewCommand("rev-list").
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
gitCmd.AddDynamicArguments(opts.Revision)
if opts.Not != "" {
gitCmd.AddOptionValues("--not", opts.Not)
}
if opts.Since != "" {
gitCmd.AddOptionFormat("--since=%s", opts.Since)
}
if opts.Until != "" {
gitCmd.AddOptionFormat("--until=%s", opts.Until)
}
gitCmd.AddDashesAndList(opts.File)
err := gitCmd.WithDir(repo.Path).
WithStdout(stdoutWriter).
RunWithStderr(repo.Ctx)
_ = stdoutWriter.CloseWithError(err)
}()
objectFormat, err := repo.GetObjectFormat()
if err != nil {
return nil, err
if opts.Not != "" {
gitCmd.AddOptionValues("--not", opts.Not)
}
if opts.Since != "" {
gitCmd.AddOptionFormat("--since=%s", opts.Since)
}
if opts.Until != "" {
gitCmd.AddOptionFormat("--until=%s", opts.Until)
}
gitCmd.AddDashesAndList(opts.File)
length := objectFormat.FullLength()
commits := []*Commit{}
shaline := make([]byte, length+1)
for {
n, err := io.ReadFull(stdoutReader, shaline)
if err != nil || n < length {
if err == io.EOF {
err = nil
var commits []*Commit
stdoutReader, stdoutReaderClose := gitCmd.MakeStdoutPipe()
defer stdoutReaderClose()
err := gitCmd.WithDir(repo.Path).
WithPipelineFunc(func(context gitcmd.Context) error {
objectFormat, err := repo.GetObjectFormat()
if err != nil {
return err
}
return commits, err
}
objectID, err := NewIDFromString(string(shaline[0:length]))
if err != nil {
return nil, err
}
commit, err := repo.getCommit(objectID)
if err != nil {
return nil, err
}
commits = append(commits, commit)
}
length := objectFormat.FullLength()
shaline := make([]byte, length+1)
for {
n, err := io.ReadFull(stdoutReader, shaline)
if err != nil || n < length {
if err == io.EOF {
err = nil
}
return err
}
objectID, err := NewIDFromString(string(shaline[0:length]))
if err != nil {
return err
}
commit, err := repo.getCommit(objectID)
if err != nil {
return err
}
commits = append(commits, commit)
}
}).
RunWithStderr(repo.Ctx)
return commits, err
}
// FilesCountBetween return the number of files changed between two commits
+5 -5
View File
@@ -45,7 +45,7 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
AddDynamicArguments(base + separator + head).
AddArguments("--").
WithDir(repo.Path).
WithStdout(w).
WithStdoutCopy(w).
RunWithStderr(repo.Ctx); err != nil {
if strings.Contains(err.Stderr(), "no merge base") {
// git >= 2.28 now returns an error if base and head have become unrelated.
@@ -55,7 +55,7 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
AddDynamicArguments(base, head).
AddArguments("--").
WithDir(repo.Path).
WithStdout(w).
WithStdoutCopy(w).
RunWithStderr(repo.Ctx); err == nil {
return w.numLines, nil
}
@@ -71,7 +71,7 @@ var patchCommits = regexp.MustCompile(`^From\s(\w+)\s`)
func (repo *Repository) GetDiff(compareArg string, w io.Writer) error {
return gitcmd.NewCommand("diff", "-p").AddDynamicArguments(compareArg).
WithDir(repo.Path).
WithStdout(w).
WithStdoutCopy(w).
Run(repo.Ctx)
}
@@ -80,7 +80,7 @@ func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error {
return gitcmd.NewCommand("diff", "-p", "--binary", "--histogram").
AddDynamicArguments(compareArg).
WithDir(repo.Path).
WithStdout(w).
WithStdoutCopy(w).
Run(repo.Ctx)
}
@@ -88,7 +88,7 @@ func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error {
func (repo *Repository) GetPatch(compareArg string, w io.Writer) error {
return gitcmd.NewCommand("format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg).
WithDir(repo.Path).
WithStdout(w).
WithStdoutCopy(w).
Run(repo.Ctx)
}
+2 -2
View File
@@ -110,7 +110,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
}
return cmd.
WithDir(repo.Path).
WithStdin(bytes.NewReader(input.Bytes())).
WithStdinBytes(input.Bytes()).
RunWithStderr(repo.Ctx)
}
@@ -130,7 +130,7 @@ func (repo *Repository) AddObjectsToIndex(objects ...IndexObjectInfo) error {
}
return cmd.
WithDir(repo.Path).
WithStdin(bytes.NewReader(input.Bytes())).
WithStdinBytes(input.Bytes()).
RunWithStderr(repo.Ctx)
}
+6 -13
View File
@@ -5,7 +5,6 @@
package git
import (
"io"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
@@ -32,18 +31,12 @@ func (o ObjectType) Bytes() []byte {
return []byte(o)
}
type EmptyReader struct{}
func (EmptyReader) Read(p []byte) (int, error) {
return 0, io.EOF
}
func (repo *Repository) GetObjectFormat() (ObjectFormat, error) {
if repo != nil && repo.objectFormat != nil {
return repo.objectFormat, nil
}
str, err := repo.hashObject(EmptyReader{}, false)
str, err := repo.hashObjectBytes(nil, false)
if err != nil {
return nil, err
}
@@ -57,16 +50,16 @@ func (repo *Repository) GetObjectFormat() (ObjectFormat, error) {
return repo.objectFormat, nil
}
// HashObject takes a reader and returns hash for that reader
func (repo *Repository) HashObject(reader io.Reader) (ObjectID, error) {
idStr, err := repo.hashObject(reader, true)
// HashObjectBytes returns hash for the content
func (repo *Repository) HashObjectBytes(buf []byte) (ObjectID, error) {
idStr, err := repo.hashObjectBytes(buf, true)
if err != nil {
return nil, err
}
return NewIDFromString(idStr)
}
func (repo *Repository) hashObject(reader io.Reader, save bool) (string, error) {
func (repo *Repository) hashObjectBytes(buf []byte, save bool) (string, error) {
var cmd *gitcmd.Command
if save {
cmd = gitcmd.NewCommand("hash-object", "-w", "--stdin")
@@ -75,7 +68,7 @@ func (repo *Repository) hashObject(reader io.Reader, save bool) (string, error)
}
stdout, _, err := cmd.
WithDir(repo.Path).
WithStdin(reader).
WithStdinBytes(buf).
RunStdString(repo.Ctx)
if err != nil {
return "", err
+51 -59
View File
@@ -15,69 +15,61 @@ import (
// GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with.
func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
go func() {
err := gitcmd.NewCommand("for-each-ref").
WithDir(repo.Path).
WithStdout(stdoutWriter).
Run(repo.Ctx)
_ = stdoutWriter.CloseWithError(err)
}()
refs := make([]*Reference, 0)
bufReader := bufio.NewReader(stdoutReader)
for {
// The output of for-each-ref is simply a list:
// <sha> SP <type> TAB <ref> LF
sha, err := bufReader.ReadString(' ')
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
sha = sha[:len(sha)-1]
cmd := gitcmd.NewCommand("for-each-ref")
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
defer stdoutReaderClose()
err := cmd.WithDir(repo.Path).
WithPipelineFunc(func(context gitcmd.Context) error {
bufReader := bufio.NewReader(stdoutReader)
for {
// The output of for-each-ref is simply a list:
// <sha> SP <type> TAB <ref> LF
sha, err := bufReader.ReadString(' ')
if err == io.EOF {
break
}
if err != nil {
return err
}
sha = sha[:len(sha)-1]
typ, err := bufReader.ReadString('\t')
if err == io.EOF {
// This should not happen, but we'll tolerate it
break
}
if err != nil {
return nil, err
}
typ = typ[:len(typ)-1]
typ, err := bufReader.ReadString('\t')
if err == io.EOF {
// This should not happen, but we'll tolerate it
break
}
if err != nil {
return err
}
typ = typ[:len(typ)-1]
refName, err := bufReader.ReadString('\n')
if err == io.EOF {
// This should not happen, but we'll tolerate it
break
}
if err != nil {
return nil, err
}
refName = refName[:len(refName)-1]
refName, err := bufReader.ReadString('\n')
if err == io.EOF {
// This should not happen, but we'll tolerate it
break
}
if err != nil {
return err
}
refName = refName[:len(refName)-1]
// refName cannot be HEAD but can be remotes or stash
if strings.HasPrefix(refName, RemotePrefix) || refName == "/refs/stash" {
continue
}
// refName cannot be HEAD but can be remotes or stash
if strings.HasPrefix(refName, RemotePrefix) || refName == "/refs/stash" {
continue
}
if pattern == "" || strings.HasPrefix(refName, pattern) {
r := &Reference{
Name: refName,
Object: MustIDFromString(sha),
Type: typ,
repo: repo,
if pattern == "" || strings.HasPrefix(refName, pattern) {
r := &Reference{
Name: refName,
Object: MustIDFromString(sha),
Type: typ,
repo: repo,
}
refs = append(refs, r)
}
}
refs = append(refs, r)
}
}
return refs, nil
return nil
}).RunWithStderr(repo.Ctx)
return refs, err
}
+2 -5
View File
@@ -6,7 +6,6 @@ package git
import (
"bufio"
"fmt"
"io"
"sort"
"strconv"
"strings"
@@ -62,10 +61,10 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
gitCmd.AddArguments("--first-parent").AddDynamicArguments(branch)
}
var stdoutReader io.ReadCloser
stdoutReader, stdoutReaderClose := gitCmd.MakeStdoutPipe()
defer stdoutReaderClose()
err = gitCmd.
WithDir(repo.Path).
WithStdoutReader(&stdoutReader).
WithPipelineFunc(func(ctx gitcmd.Context) error {
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)
@@ -117,7 +116,6 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
}
}
if err = scanner.Err(); err != nil {
_ = stdoutReader.Close()
return fmt.Errorf("GetCodeActivityStats scan: %w", err)
}
a := make([]*CodeActivityAuthor, 0, len(authors))
@@ -131,7 +129,6 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
stats.AuthorCount = int64(len(authors))
stats.ChangedFiles = int64(len(files))
stats.Authors = a
_ = stdoutReader.Close()
return nil
}).
RunWithStderr(repo.Ctx)
+32 -36
View File
@@ -6,7 +6,6 @@ package git
import (
"fmt"
"io"
"strings"
"code.gitea.io/gitea/modules/git/foreachref"
@@ -115,45 +114,42 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
// https://git-scm.com/docs/git-for-each-ref#Documentation/git-for-each-ref.txt-refname
forEachRefFmt := foreachref.NewFormat("objecttype", "refname:lstrip=2", "object", "objectname", "creator", "contents", "contents:signature")
stdoutReader, stdoutWriter := io.Pipe()
defer stdoutReader.Close()
defer stdoutWriter.Close()
go func() {
err := gitcmd.NewCommand("for-each-ref").
AddOptionFormat("--format=%s", forEachRefFmt.Flag()).
AddArguments("--sort", "-*creatordate", "refs/tags").
WithDir(repo.Path).
WithStdout(stdoutWriter).
RunWithStderr(repo.Ctx)
_ = stdoutWriter.CloseWithError(err)
}()
var tags []*Tag
parser := forEachRefFmt.Parser(stdoutReader)
for {
ref := parser.Next()
if ref == nil {
break
}
var tagsTotal int
cmd := gitcmd.NewCommand("for-each-ref")
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
defer stdoutReaderClose()
err := cmd.AddOptionFormat("--format=%s", forEachRefFmt.Flag()).
AddArguments("--sort", "-*creatordate", "refs/tags").
WithDir(repo.Path).
WithPipelineFunc(func(context gitcmd.Context) error {
parser := forEachRefFmt.Parser(stdoutReader)
for {
ref := parser.Next()
if ref == nil {
break
}
tag, err := parseTagRef(ref)
if err != nil {
return nil, 0, fmt.Errorf("GetTagInfos: parse tag: %w", err)
}
tags = append(tags, tag)
}
if err := parser.Err(); err != nil {
return nil, 0, fmt.Errorf("GetTagInfos: parse output: %w", err)
}
tag, err := parseTagRef(ref)
if err != nil {
return fmt.Errorf("GetTagInfos: parse tag: %w", err)
}
tags = append(tags, tag)
}
if err := parser.Err(); err != nil {
return fmt.Errorf("GetTagInfos: parse output: %w", err)
}
sortTagsByTime(tags)
tagsTotal := len(tags)
if page != 0 {
tags = util.PaginateSlice(tags, page, pageSize).([]*Tag)
}
sortTagsByTime(tags)
tagsTotal = len(tags)
if page != 0 {
tags = util.PaginateSlice(tags, page, pageSize).([]*Tag)
}
return nil
}).
RunWithStderr(repo.Ctx)
return tags, tagsTotal, nil
return tags, tagsTotal, err
}
// parseTagRef parses a tag from a 'git for-each-ref'-produced reference.
+1 -1
View File
@@ -60,7 +60,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
stdout, _, err := cmd.WithEnv(env).
WithDir(repo.Path).
WithStdin(messageBytes).
WithStdinBytes(messageBytes.Bytes()).
RunStdString(repo.Ctx)
if err != nil {
return nil, err
+4 -5
View File
@@ -7,7 +7,6 @@ import (
"bufio"
"context"
"fmt"
"io"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
@@ -21,10 +20,10 @@ type TemplateSubmoduleCommit struct {
// GetTemplateSubmoduleCommits returns a list of submodules paths and their commits from a repository
// This function is only for generating new repos based on existing template, the template couldn't be too large.
func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submoduleCommits []TemplateSubmoduleCommit, _ error) {
var stdoutReader io.ReadCloser
err := gitcmd.NewCommand("ls-tree", "-r", "--", "HEAD").
WithDir(repoPath).
WithStdoutReader(&stdoutReader).
cmd := gitcmd.NewCommand("ls-tree", "-r", "--", "HEAD")
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
defer stdoutReaderClose()
err := cmd.WithDir(repoPath).
WithPipelineFunc(func(ctx gitcmd.Context) error {
scanner := bufio.NewScanner(stdoutReader)
for scanner.Scan() {
+1 -1
View File
@@ -36,7 +36,7 @@ func CreateArchive(ctx context.Context, repo Repository, format string, target i
paths[i] = path.Clean(paths[i])
}
cmd.AddDynamicArguments(paths...)
return RunCmdWithStderr(ctx, repo, cmd.WithStdout(target))
return RunCmdWithStderr(ctx, repo, cmd.WithStdoutCopy(target))
}
// CreateBundle create bundle content to the target path
+25 -36
View File
@@ -8,7 +8,6 @@ import (
"bytes"
"context"
"io"
"os"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
@@ -34,8 +33,6 @@ type BlamePart struct {
// BlameReader returns part of file blame one by one
type BlameReader struct {
output io.WriteCloser
reader io.ReadCloser
bufferedReader *bufio.Reader
done chan error
lastSha *string
@@ -131,34 +128,42 @@ func (r *BlameReader) Close() error {
err := <-r.done
r.bufferedReader = nil
_ = r.reader.Close()
_ = r.output.Close()
for _, cleanup := range r.cleanupFuncs {
if cleanup != nil {
cleanup()
}
}
r.cleanup()
return err
}
func (r *BlameReader) cleanup() {
for _, cleanup := range r.cleanupFuncs {
cleanup()
}
}
// CreateBlameReader creates reader for given repository, commit and file
func CreateBlameReader(ctx context.Context, objectFormat git.ObjectFormat, repo Repository, commit *git.Commit, file string, bypassBlameIgnore bool) (rd *BlameReader, err error) {
var ignoreRevsFileName string
var ignoreRevsFileCleanup func()
func CreateBlameReader(ctx context.Context, objectFormat git.ObjectFormat, repo Repository, commit *git.Commit, file string, bypassBlameIgnore bool) (rd *BlameReader, retErr error) {
defer func() {
if err != nil && ignoreRevsFileCleanup != nil {
ignoreRevsFileCleanup()
if retErr != nil {
rd.cleanup()
}
}()
rd = &BlameReader{
done: make(chan error, 1),
objectFormat: objectFormat,
}
cmd := gitcmd.NewCommand("blame", "--porcelain")
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
rd.bufferedReader = bufio.NewReader(stdoutReader)
rd.cleanupFuncs = append(rd.cleanupFuncs, stdoutReaderClose)
if git.DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
ignoreRevsFileName, ignoreRevsFileCleanup, err = tryCreateBlameIgnoreRevsFile(commit)
ignoreRevsFileName, ignoreRevsFileCleanup, err := tryCreateBlameIgnoreRevsFile(commit)
if err != nil && !git.IsErrNotExist(err) {
return nil, err
}
if ignoreRevsFileName != "" {
} else if err == nil {
rd.ignoreRevsFile = ignoreRevsFileName
rd.cleanupFuncs = append(rd.cleanupFuncs, ignoreRevsFileCleanup)
// Possible improvement: use --ignore-revs-file /dev/stdin on unix
// There is no equivalent on Windows. May be implemented if Gitea uses an external git backend.
cmd.AddOptionValues("--ignore-revs-file", ignoreRevsFileName)
@@ -167,28 +172,12 @@ func CreateBlameReader(ctx context.Context, objectFormat git.ObjectFormat, repo
cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file)
done := make(chan error, 1)
reader, stdout, err := os.Pipe()
if err != nil {
return nil, err
}
go func() {
// TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close"
err := RunCmdWithStderr(ctx, repo, cmd.WithStdout(stdout))
done <- err
_ = stdout.Close()
rd.done <- RunCmdWithStderr(ctx, repo, cmd)
}()
bufferedReader := bufio.NewReader(reader)
return &BlameReader{
output: stdout,
reader: reader,
bufferedReader: bufferedReader,
done: done,
ignoreRevsFile: ignoreRevsFileName,
objectFormat: objectFormat,
cleanupFuncs: []func(){ignoreRevsFileCleanup},
}, nil
return rd, nil
}
func tryCreateBlameIgnoreRevsFile(commit *git.Commit) (string, func(), error) {
+4 -6
View File
@@ -67,20 +67,18 @@ func parseCommitFileStatus(fileStatus *CommitFileStatus, stdout io.Reader) {
// GetCommitFileStatus returns file status of commit in given repository.
func GetCommitFileStatus(ctx context.Context, repo Repository, commitID string) (*CommitFileStatus, error) {
stdout, w := io.Pipe()
cmd := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1")
stdout, stdoutClose := cmd.MakeStdoutPipe()
defer stdoutClose()
done := make(chan struct{})
fileStatus := NewCommitFileStatus()
go func() {
parseCommitFileStatus(fileStatus, stdout)
close(done)
}()
err := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").
AddDynamicArguments(commitID).
err := cmd.AddDynamicArguments(commitID).
WithDir(repoPath(repo)).
WithStdout(w).
RunWithStderr(ctx)
_ = w.Close() // Close writer to exit parsing goroutine
if err != nil {
return nil, err
}
+1 -1
View File
@@ -66,6 +66,6 @@ func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int,
func GetReverseRawDiff(ctx context.Context, repo Repository, commitID string, writer io.Writer) error {
return RunCmdWithStderr(ctx, repo, gitcmd.NewCommand("show", "--pretty=format:revert %H%n", "-R").
AddDynamicArguments(commitID).
WithStdout(writer),
WithStdoutCopy(writer),
)
}
+2 -9
View File
@@ -15,7 +15,7 @@ import (
)
// SearchPointerBlobs scans the whole repository for LFS pointer files
func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan chan<- PointerBlob, errChan chan<- error) {
func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan chan<- PointerBlob) error {
gitRepo := repo.GoGitRepo()
err := func() error {
@@ -49,14 +49,7 @@ func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan c
return nil
})
}()
if err != nil {
select {
case <-ctx.Done():
default:
errChan <- err
}
}
close(pointerChan)
close(errChan)
return err
}
+33 -46
View File
@@ -8,96 +8,84 @@ package lfs
import (
"bufio"
"context"
"errors"
"io"
"strconv"
"strings"
"sync"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/git/pipeline"
"code.gitea.io/gitea/modules/util"
"golang.org/x/sync/errgroup"
)
// SearchPointerBlobs scans the whole repository for LFS pointer files
func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan chan<- PointerBlob, errChan chan<- error) {
basePath := repo.Path
func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan chan<- PointerBlob) error {
cmd1AllObjs, cmd3BatchContent := gitcmd.NewCommand(), gitcmd.NewCommand()
catFileCheckReader, catFileCheckWriter := io.Pipe()
shasToBatchReader, shasToBatchWriter := io.Pipe()
catFileBatchReader, catFileBatchWriter := io.Pipe()
cmd1AllObjsStdout, cmd1AllObjsStdoutClose := cmd1AllObjs.MakeStdoutPipe()
defer cmd1AllObjsStdoutClose()
wg := sync.WaitGroup{}
wg.Add(4)
// Create the go-routines in reverse order.
cmd3BatchContentIn, cmd3BatchContentOut, cmd3BatchContentClose := cmd3BatchContent.MakeStdinStdoutPipe()
defer cmd3BatchContentClose()
// Create the go-routines in reverse order (update: the order is not needed any more, the pipes are properly prepared)
wg := errgroup.Group{}
// 4. Take the output of cat-file --batch and check if each file in turn
// to see if they're pointers to files in the LFS store
go createPointerResultsFromCatFileBatch(ctx, catFileBatchReader, &wg, pointerChan)
wg.Go(func() error {
return createPointerResultsFromCatFileBatch(cmd3BatchContentOut, pointerChan)
})
// 3. Take the shas of the blobs and batch read them
go pipeline.CatFileBatch(ctx, shasToBatchReader, catFileBatchWriter, &wg, basePath)
wg.Go(func() error {
return pipeline.CatFileBatch(ctx, cmd3BatchContent, repo.Path)
})
// 2. From the provided objects restrict to blobs <=1k
go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg)
wg.Go(func() error {
return pipeline.BlobsLessThan1024FromCatFileBatchCheck(cmd1AllObjsStdout, cmd3BatchContentIn)
})
// 1. Run batch-check on all objects in the repository
if !git.DefaultFeatures().CheckVersionAtLeast("2.6.0") {
revListReader, revListWriter := io.Pipe()
shasToCheckReader, shasToCheckWriter := io.Pipe()
wg.Add(2)
go pipeline.CatFileBatchCheck(ctx, shasToCheckReader, catFileCheckWriter, &wg, basePath)
go pipeline.BlobsFromRevListObjects(revListReader, shasToCheckWriter, &wg)
go pipeline.RevListAllObjects(ctx, revListWriter, &wg, basePath, errChan)
} else {
go pipeline.CatFileBatchCheckAllObjects(ctx, catFileCheckWriter, &wg, basePath, errChan)
}
wg.Wait()
wg.Go(func() error {
return pipeline.CatFileBatchCheckAllObjects(ctx, cmd1AllObjs, repo.Path)
})
err := wg.Wait()
close(pointerChan)
close(errChan)
return err
}
func createPointerResultsFromCatFileBatch(ctx context.Context, catFileBatchReader *io.PipeReader, wg *sync.WaitGroup, pointerChan chan<- PointerBlob) {
defer wg.Done()
func createPointerResultsFromCatFileBatch(catFileBatchReader io.ReadCloser, pointerChan chan<- PointerBlob) error {
defer catFileBatchReader.Close()
bufferedReader := bufio.NewReader(catFileBatchReader)
buf := make([]byte, 1025)
loop:
for {
select {
case <-ctx.Done():
break loop
default:
}
// File descriptor line: sha
sha, err := bufferedReader.ReadString(' ')
if err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return util.Iif(errors.Is(err, io.EOF), nil, err)
}
sha = strings.TrimSpace(sha)
// Throw away the blob
if _, err := bufferedReader.ReadString(' '); err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return err
}
sizeStr, err := bufferedReader.ReadString('\n')
if err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return err
}
size, err := strconv.Atoi(sizeStr[:len(sizeStr)-1])
if err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return err
}
pointerBuf := buf[:size+1]
if _, err := io.ReadFull(bufferedReader, pointerBuf); err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return err
}
pointerBuf = pointerBuf[:size]
// Now we need to check if the pointerBuf is an LFS pointer
@@ -105,7 +93,6 @@ loop:
if !pointer.IsValid() {
continue
}
pointerChan <- PointerBlob{Hash: sha, Pointer: pointer}
}
}
+6 -5
View File
@@ -62,7 +62,9 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
pointerChan := make(chan lfs.PointerBlob)
errChan := make(chan error, 1)
go lfs.SearchPointerBlobs(ctx, gitRepo, pointerChan, errChan)
go func() {
errChan <- lfs.SearchPointerBlobs(ctx, gitRepo, pointerChan)
}()
downloadObjects := func(pointers []lfs.Pointer) error {
err := lfsClient.Download(ctx, pointers, func(p lfs.Pointer, content io.ReadCloser, objectError error) error {
@@ -150,13 +152,12 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
}
}
err, has := <-errChan
if has {
err := <-errChan
if err != nil {
log.Error("Repo[%-v]: Error enumerating LFS objects for repository: %v", repo, err)
return err
}
return nil
return err
}
// shortRelease to reduce load memory, this struct can replace repo_model.Release
+3 -3
View File
@@ -172,11 +172,11 @@ func getStorageSectionByType(rootCfg ConfigProvider, typ string) (ConfigSection,
targetType := targetSec.Key("STORAGE_TYPE").String()
if targetType == "" {
if !IsValidStorageType(StorageType(typ)) {
return nil, 0, fmt.Errorf("unknow storage type %q", typ)
return nil, 0, fmt.Errorf("unknown storage type %q", typ)
}
targetSec.Key("STORAGE_TYPE").SetValue(typ)
} else if !IsValidStorageType(StorageType(targetType)) {
return nil, 0, fmt.Errorf("unknow storage type %q for section storage.%v", targetType, typ)
return nil, 0, fmt.Errorf("unknown storage type %q for section storage.%v", targetType, typ)
}
return targetSec, targetSecIsTyp, nil
@@ -202,7 +202,7 @@ func getStorageTargetSection(rootCfg ConfigProvider, name, typ string, sec Confi
}
}
// check stoarge name thirdly
// check storage name thirdly
targetSec, _ := rootCfg.GetSection(storageSectionName + "." + name)
if targetSec != nil {
targetType := targetSec.Key("STORAGE_TYPE").String()
+10
View File
@@ -3495,6 +3495,10 @@
"..cursor": "folder-cursor",
"_.cursor": "folder-cursor",
"__.cursor__": "folder-cursor",
".gemini": "folder-gemini-ai",
"..gemini": "folder-gemini-ai",
"_.gemini": "folder-gemini-ai",
"__.gemini__": "folder-gemini-ai",
"input": "folder-input",
".input": "folder-input",
"_input": "folder-input",
@@ -7068,6 +7072,10 @@
"..cursor": "folder-cursor-open",
"_.cursor": "folder-cursor-open",
"__.cursor__": "folder-cursor-open",
".gemini": "folder-gemini-ai-open",
"..gemini": "folder-gemini-ai-open",
"_.gemini": "folder-gemini-ai-open",
"__.gemini__": "folder-gemini-ai-open",
"input": "folder-input-open",
".input": "folder-input-open",
"_input": "folder-input-open",
@@ -7860,6 +7868,7 @@
"tfvars": "terraform",
"tfstate": "terraform",
"tfbackend": "terraform",
"terraformignore": "terraform",
"tofu": "opentofu",
"blade.php": "laravel",
"inky.php": "laravel",
@@ -8599,6 +8608,7 @@
".ruff.toml": "ruff",
"uv.toml": "uv",
".uv.toml": "uv",
"uv.lock": "uv",
"sconstruct": "scons",
"sconscript": "scons",
"scsub": "scons",
+8 -6
View File
@@ -67,7 +67,7 @@
"biome": "<svg viewBox='0 0 74 74'><path fill='#42a5f5' d='M37 9 22.745 33.69a32.2 32.2 0 0 1 16.869-.584l4.818 1.137-4.533 19.22-4.825-1.137c-5.93-1.399-11.628 1.716-14.036 6.685l-4.46-2.158c3.404-7.029 11.425-11.285 19.637-9.347l2.259-9.58A27.23 27.23 0 0 0 5 64.424l64 .001z'/></svg>",
"bitbucket": "<svg viewBox='0 0 24 24'><defs><linearGradient id='a' x1='64.01' x2='32.99' y1='65.26' y2='89.48' gradientUnits='userSpaceOnUse'><stop offset='.18' stop-color='#1565c0'/><stop offset='1' stop-color='#1e88e5'/></linearGradient></defs><path fill='#1e88e5' d='M2.985 3.333a.618.618 0 0 0-.617.716l2.621 15.914a.84.84 0 0 0 .822.701h12.576a.62.62 0 0 0 .618-.519l2.627-16.09a.618.618 0 0 0-.617-.716zm11.039 11.501H10.01L8.923 9.16h6.074z'/><path fill='url(#a)' d='M59.67 60.12H40.9L37.75 78.5h-13L9.4 96.73a2.7 2.7 0 0 0 1.75.66h40.74a2 2 0 0 0 2-1.68z' transform='translate(2.368 -9.404)scale(.30877)'/></svg>",
"bithound": "<svg fill-opacity='.05' viewBox='0 0 400 400'><g fill='#e53935' fill-opacity='1'><path d='M350.738 186.163c-1.32-13.024-4.224-26.312-8.36-38.72-11.88-35.464-33.968-71.808-61.864-96.888-1.232-1.056-5.896-3.872-7.656-2.904-4.576 2.552 4.048 20.064 5.104 23.232 6.512 19.36 10.648 39.864 5.984 60.104-6.248 26.752-26.752 45.496-54.12 47.784-15.048 1.232-30.184-.44-45.232 1.32-22.528 2.64-45.496 10.384-59.84 28.864-1.672 2.112-3.168 4.488-4.576 6.952h-.352c-5.544.616-11.088-1.76-13.816-3.256-.704-.44-1.408-.792-1.936-1.056-16.72-9.24-29.04-29.92-36.608-46.992-3.432-7.92-6.336-16.192-8.184-24.552-.88-3.784-.968-7.744-1.144-11.616-.088-2.376.264-5.72-1.056-7.832-2.904-4.576-6.6-.176-7.216 3.52-.968 6.072-1.848 12.056-1.584 18.216.44 10.384 3.344 20.68 7.04 30.36 5.456 14.256 13.112 27.368 23.056 39.072 4.136 4.84 8.536 9.328 13.288 13.464 4.224 3.784 9.592 6.776 12.76 11.616 3.696 5.544 4.312 12.408 3.96 18.832-.88 16.984-1.408 32.912 3.432 49.456 4.224 14.696 9.504 29.744 18.304 42.328 4.4 6.248 9.856 12.848 15.84 17.512 4.048 3.168 11.704 3.52 7.304-8.096-9.768-25.784-10.648-52.536 4.576-76.648 12.76-20.064 35.288-37.928 60.72-34.76 37.4 4.664 63.448 38.984 61.6 75.68-.528 10.296-.88 19.096-4.136 28.776-1.32 3.872-2.288 8.8-1.32 12.848 1.584 6.864 9.24 4.312 12.584-.176 9.064-12.32 18.568-24.288 27.104-36.96 27.808-41.536 41.36-89.584 36.344-139.48'/><path d='M141.21 85.051c.616 2.024 1.232 4.224 1.672 6.6.088.968.352 2.024.88 2.992 2.288 5.984 7.832 9.24 13.024 12.32 3.168 1.936 8.888 3.784 12.408 5.192 4.576 1.848 14.432-.528 19.096-.88 10.736-.88 20.68-4.664 30.536 1.056-50.512 59.224-2.816 72.424 34.144 43.912 42.24-32.56 2.464-109.384 2.464-109.384s-.88-5.984-16.896-9.504a52 52 0 0 0-4.488-2.112c-15.84-7.304-30.096 4.664-41.536 14.432-3.344 2.816-6.6 5.632-10.12 8.272-4.752 3.52-9.856 6.424-15.224 8.976-5.632 2.64-12.32 5.632-18.568 5.896-.88 0-2.552.176-4.312.528-2.728.264-4.136.968-4.752 2.2-1.056.88-1.76 2.112-1.584 3.696.176 2.2 1.232 4.048 2.376 5.456.352.088.616.264.88.352'/></g></svg>",
"blender": "<svg viewBox='0 0 32 32'><g fill='#ff9800' fill-rule='evenodd'><path d='M14.004 19a5 5 0 0 1 4.993-5.001 5 5 0 0 1 5.008 4.985A5 5 0 0 1 19.028 24a5 5 0 0 1-5.024-4.97z' paint-order='stroke fill markers'/><path d='m23.148 7.152-4.355-4.451s-1.488-1.534-3.022-.046-.046 3.022-.046 3.022l.165.264c.693.69 1.412 1.368 2.105 2.058h-12s-2 0-2 2 2 2 2 2h4l-6 6-1.332 1.41s-1.503 1.503 0 3.007c1.503 1.503 3.006 0 3.006 0l2.327-2.417.063.19q.06.555.173 1.09a11.02 11.02 0 0 0 8.599 8.508 11 11 0 0 0 2.216.213c6.068-.028 10.967-4.965 10.949-11.034 0-5.452-4-8.967-6.848-11.815zm-4.162 4.847a7 7 0 1 1-6.99 7.044V19a7 7 0 0 1 6.99-7'/></g></svg>",
"blender": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='m26.726 12.017.013-.016-9.773-7.584a1.987 1.987 0 0 0-2.788.352l-.016.02a1.987 1.987 0 0 0 .352 2.788l3.02 2.344q-.168.037-.334.079H8.013a2 2 0 1 0 0 4h3.493l-.067.101-8.64 6.595a2 2 0 1 0 2.427 3.18l4.902-3.742C10.827 24.58 14.98 28 20.013 28c5.522 0 10-4.114 10-9.188a8.84 8.84 0 0 0-3.287-6.795M20.013 24a6 6 0 1 1 6-6 6 6 0 0 1-6 6'/><circle cx='20.012' cy='18' r='4' fill='#ff9800'/></svg>",
"blink": "<svg viewBox='0 0 256 256'><path fill='#f9a825' d='M130.974 23.383c57.809 1.624 103.262 49.782 101.638 107.591s-49.782 103.262-107.59 101.639C67.303 230.989 21.85 183.01 23.383 125.293c1.533-57.81 49.602-103.443 107.41-101.91zm-.541 10.823c-51.766-1.353-94.875 39.59-96.137 91.447-1.353 51.766 39.59 94.875 91.357 96.137 51.766 1.353 94.875-39.59 96.137-91.357 1.443-51.856-39.5-94.875-91.357-96.227.09 0 0 0 0 0'/><path fill='#f9a825' d='M137.9 93.403c-4.149 3.968-2.525 12.806 3.878 19.57s15.241 8.838 19.209 4.78 2.706-12.987-3.788-19.751-15.422-8.748-19.57-4.78zm52.217-25.162c8.207 8.568 14.43 18.758 18.398 29.851 0 0 2.706 7.395-5.862 7.395H181.73s-6.674-.54-6.944 5.14c-.451 7.035-.27 12.988-.27 12.988.27 4.058 1.803 8.026 4.328 11.183l21.554 22.727a9.184 9.184 0 0 1 1.082 12.355c-10.912 18.578-28.408 32.286-49.06 38.509-6.314 1.894-6.945-3.247-6.765-6.764 0-2.255 2.616-52.397 2.616-52.397.721-5.411-.992-10.912-4.78-14.881-6.764-7.215-11.003-11.814-11.003-11.814s-4.78-4.78-11.634-11.904c-3.788-3.878-9.018-5.862-14.43-5.501H54.027c-3.427 0-8.658-.812-6.403-7.035 7.305-20.292 21.915-37.066 41.124-46.896 3.878-2.705 9.199-1.984 12.265 1.714l21.554 22.727c3.066 2.705 6.944 4.329 11.003 4.78 0 0 5.862.45 12.987.36 5.591 0 5.501-6.764 5.501-6.764s.902-13.888 1.173-20.923c-.27-2.976 1.894-5.591 4.78-5.862.991-.09 1.893.09 2.795.451 11.003 4.69 21.013 11.634 29.22 20.292z'/></svg>",
"blink_light": "<svg viewBox='0 0 256 256'><circle cx='128' cy='128' r='97.4' fill='#37474f'/><path fill='#f9a825' d='M130.974 23.383c57.809 1.624 103.262 49.782 101.638 107.591s-49.782 103.262-107.59 101.639C67.303 230.989 21.85 183.01 23.383 125.293c1.533-57.81 49.602-103.443 107.41-101.91zm-.541 10.823c-51.766-1.353-94.875 39.59-96.137 91.447-1.353 51.766 39.59 94.875 91.357 96.137 51.766 1.353 94.875-39.59 96.137-91.357 1.443-51.856-39.5-94.875-91.357-96.227.09 0 0 0 0 0'/><path fill='#f9a825' d='M137.9 93.403c-4.149 3.968-2.525 12.806 3.878 19.57s15.241 8.838 19.209 4.78 2.706-12.987-3.788-19.751-15.422-8.748-19.57-4.78zm52.217-25.162c8.207 8.568 14.43 18.758 18.398 29.851 0 0 2.706 7.395-5.862 7.395H181.73s-6.674-.54-6.944 5.14c-.451 7.035-.27 12.988-.27 12.988.27 4.058 1.803 8.026 4.328 11.183l21.554 22.727a9.184 9.184 0 0 1 1.082 12.355c-10.912 18.578-28.408 32.286-49.06 38.509-6.314 1.894-6.945-3.247-6.765-6.764 0-2.255 2.616-52.397 2.616-52.397.721-5.411-.992-10.912-4.78-14.881-6.764-7.215-11.003-11.814-11.003-11.814s-4.78-4.78-11.634-11.904c-3.788-3.878-9.018-5.862-14.43-5.501H54.027c-3.427 0-8.658-.812-6.403-7.035 7.305-20.292 21.915-37.066 41.124-46.896 3.878-2.705 9.199-1.984 12.265 1.714l21.554 22.727c3.066 2.705 6.944 4.329 11.003 4.78 0 0 5.862.45 12.987.36 5.591 0 5.501-6.764 5.501-6.764s.902-13.888 1.173-20.923c-.27-2.976 1.894-5.591 4.78-5.862.991-.09 1.893.09 2.795.451 11.003 4.69 21.013 11.634 29.22 20.292z'/></svg>",
"blitz": "<svg viewBox='0 0 24 24'><path fill='#7c4dff' d='M8.613 11.997c1.333 0 2.588.621 3.389 1.677l3.454 4.552a.28.28 0 0 1 .025.297l-1.991 3.825a.284.284 0 0 1-.477.04l-7.901-10.39zm2.375-10.385 7.9 10.39h-3.5a4.25 4.25 0 0 1-3.39-1.676L8.546 5.774a.28.28 0 0 1-.025-.297l1.99-3.825a.284.284 0 0 1 .478-.04z'/></svg>",
@@ -102,7 +102,7 @@
"circleci_light": "<svg viewBox='0 0 32 32'><circle cx='16' cy='16' r='4' fill='#424242'/><path fill='#424242' d='M17.73 2.104a14 14 0 0 0-14.927 9.234.504.504 0 0 0 .48.662h5.525a.49.49 0 0 0 .416-.235 8 8 0 1 1 0 8.47A.49.49 0 0 0 8.81 20H3.28a.503.503 0 0 0-.479.66 14 14 0 1 0 14.93-18.556Z'/></svg>",
"citation": "<svg viewBox='0 0 1024 1024'><path fill='#1e88e5' d='M256 192c-106.039 0-192 85.961-192 192 .189 103.43 82.273 188.122 185.646 191.545L128 832h128l173.473-365.715C441.746 440.412 447.323 412.44 448 384c0-106.039-85.961-192-192-192m512 0c-106.039 0-192 85.961-192 192 .189 103.43 82.273 188.122 185.646 191.545L640 832h128l173.473-365.715C953.746 440.412 959.323 412.44 960 384c0-106.039-85.961-192-192-192'/></svg>",
"clangd": "<svg viewBox='0 0 16 16'><path fill='#4caf50' d='M10 4H7.5C4.75 4 2 5.379 2 9.5c0 4.12 2.75 5.51 5.53 5.5H10v-3H7.667C7.665 11.973 5 12.289 5 9.478 5 6.672 7.395 7.028 7.52 7H10z'/><path fill='#2979ff' d='M10 1v6H7.52C7.452 7.03 5 6.659 5 9.478 5 12.295 7.618 11.97 7.668 12H13V1h-2.725z'/></svg>",
"claude": "<svg viewBox='0 0 4.233 4.233'><path fill='#ff7043' d='m1 2.73.726-.408.012-.036-.012-.02h-.035l-.122-.007-.415-.011-.36-.015-.35-.019-.087-.018-.082-.109.008-.054.074-.05.106.01.233.016.351.024.255.015.376.04h.06l.009-.025-.02-.015-.017-.015-.363-.246-.392-.26-.206-.15-.111-.075L.58 1.23l-.024-.155.101-.111.136.01.034.009.138.105.293.228.384.282.056.047.022-.016.003-.011-.025-.042-.209-.377-.222-.384-.1-.159-.025-.095a.5.5 0 0 1-.016-.112l.115-.157.063-.02.154.02.064.057.096.217.154.344.24.466.07.139.037.128.014.04h.024V1.66l.02-.263.036-.323L2.25.66l.012-.117.058-.14.115-.076.09.043.074.106-.01.068-.045.285-.086.447-.056.3h.033l.037-.038.152-.2.254-.319.113-.126.13-.14.085-.066h.159l.117.174-.053.18-.164.207-.135.176-.195.262-.121.21.011.016.029-.002.44-.094.237-.043.283-.049.129.06.014.061-.05.124-.304.075-.355.071-.53.126-.006.004.007.01.239.022.102.006h.25l.464.034.122.08.073.099-.012.075-.187.095-.253-.06-.59-.14-.201-.05h-.028v.016l.168.165.309.279.386.359.02.089-.05.07-.052-.008-.34-.255-.13-.115-.297-.25h-.02v.026l.068.1.361.543.02.166-.027.055-.094.032-.102-.018-.212-.297-.218-.334-.175-.299-.022.012-.104 1.118-.048.057-.113.043-.093-.071-.05-.115.05-.228.06-.296.048-.236.044-.292.026-.098-.001-.006-.022.003-.22.303-.336.453-.266.284-.064.026-.11-.057.01-.102.062-.091.368-.468.221-.29.143-.167v-.024h-.009l-.976.634-.174.022-.075-.07.01-.115.035-.037z'/></svg>",
"claude": "<svg viewBox='0 0 16 16'><g fill='#ff7043'><path d='m14.375 6.48.49.28v.209l-.14.489-5.937 1.397-.558-1.387zm0 0'/><path d='m12.155 2.373.683.143.182.224.173.535-.072.342-3.983 5.447L7.81 7.737l3.673-4.82z'/><path d='m8.719 1.522.419-.28.349.14.349.49-.957 5.748-.65-.441-.279-.769.49-4.33z'/><path d='m4.239 1.614.43-.55L4.95 1l.558.081.275.216 2.004 4.442.724 2.11-.848.471-3.231-5.864z'/><path d='m2.154 4.665-.14-.56.42-.488.488.07h.14l2.933 2.165.908.698 1.257.978-.698 1.187-.629-.489-.419-.419-4.05-2.863z'/><path d='M1.316 8.296 1 7.946v-.31l.316-.108 3.562.21 3.491.279-.113.695-6.66-.346z'/><path d='M3.411 11.931h-.698l-.278-.32v-.382l1.186-.838 4.82-3.068.487.833z'/><path d='m4.738 13.883-.28.07-.418-.21.07-.35 4.12-5.446.558.768-3.072 4.05z'/><path d='m8.23 14.581-.21.28-.419.14-.349-.28-.21-.42L8.09 8.646l.629.07z'/><path d='M11.791 13.045v.558l-.07.21-.279.14-.489-.066-3.356-4.996 1.331-1.014 1.117 2.025.105.733z'/><path d='m13.398 12.207.07.349-.21.279-.21-.07-1.187-.838-1.815-1.606-1.397-.978.419-1.326.698.419.42.768z'/><path d='m12.49 8.645 1.746.14.419.28.279.418v.302l-.768.327-3.911-.978-1.606-.07.419-1.466 1.117.838z'/></g></svg>",
"cline": "<svg viewBox='0 0 16 16'><path fill='#42a5f5' d='M8 1a2 2 0 0 0-2 2H5C3.338 3 2 4.338 2 6v1L1 9l1 2v1c0 1.662 1.338 3 3 3h6c1.662 0 3-1.338 3-3v-1l1-2-1-2V6c0-1.662-1.338-3-3-3h-1a2 2 0 0 0-2-2M6 7c.554 0 1 .446 1 1v2c0 .554-.446 1-1 1s-1-.446-1-1V8c0-.554.446-1 1-1m4 0c.554 0 1 .446 1 1v2c0 .554-.446 1-1 1s-1-.446-1-1V8c0-.554.446-1 1-1'/></svg>",
"clojure": "<svg viewBox='0 0 256 256'><path fill='#64dd17' d='M123.456 129.975a507 507 0 0 0-3.54 7.846c-4.406 9.981-9.284 22.127-11.066 29.908-.64 2.77-1.037 6.205-1.03 10.013 0 1.506.081 3.09.21 4.702a58.1 58.1 0 0 0 19.98 3.559 58.2 58.2 0 0 0 18.29-2.98c-1.352-1.237-2.642-2.554-3.816-4.038-7.796-9.942-12.146-24.512-19.028-49.01m-28.784-49.39C79.782 91.08 70.039 108.387 70.002 128c.037 19.32 9.487 36.403 24.002 46.94 3.56-14.83 12.485-28.41 25.868-55.63a219 219 0 0 0-2.714-7.083c-3.708-9.3-9.059-20.102-13.834-24.993-2.435-2.555-5.389-4.763-8.652-6.648'/><path fill='#7cb342' d='M178.532 194.535c-7.683-.963-14.023-2.124-19.57-4.081a69.4 69.4 0 0 1-30.958 7.249c-38.491 0-69.693-31.198-69.698-69.7 0-20.891 9.203-39.62 23.764-52.392-3.895-.94-7.956-1.49-12.104-1.482-20.45.193-42.037 11.51-51.025 42.075-.84 4.45-.64 7.813-.64 11.8 0 60.591 49.12 109.715 109.705 109.715 37.104 0 69.882-18.437 89.732-46.633-10.736 2.675-21.06 3.955-29.902 3.982-3.314 0-6.425-.177-9.305-.53'/><path fill='#29b6f6' d='M157.922 173.271c.678.336 2.213.884 4.35 1.49 14.375-10.553 23.717-27.552 23.754-46.764h-.005c-.055-32.03-25.974-57.945-58.011-58.009a58.2 58.2 0 0 0-18.213 2.961c11.779 13.426 17.443 32.613 22.922 53.6l.01.025c.01.017 1.752 5.828 4.743 13.538 2.97 7.7 7.203 17.231 11.818 24.178 3.03 4.655 6.363 8 8.632 8.981'/><path fill='#1e88e5' d='M128.009 18.29c-36.746 0-69.25 18.089-89.16 45.826 10.361-6.49 20.941-8.83 30.174-8.747 12.753.037 22.779 3.991 27.589 6.696a51 51 0 0 1 3.345 2.131 69.4 69.4 0 0 1 28.049-5.894c38.496.004 69.703 31.202 69.709 69.698h-.006c0 19.409-7.938 36.957-20.736 49.594 3.142.352 6.492.571 9.912.554 12.15.006 25.284-2.675 35.13-10.956 6.42-5.408 11.798-13.327 14.78-25.199.584-4.586.92-9.247.92-13.991 0-60.588-49.116-109.715-109.705-109.715'/></svg>",
"cloudfoundry": "<svg viewBox='0 0 24 24'><path fill='#0288d1' d='M14.13 10.1c.6-.5.98-1.19.97-1.96-.01-1.47-1.4-2.66-3.1-2.66S8.91 6.66 8.9 8.13c-.01.77.37 1.47.97 1.96.75.61 1.36 1.84 1.4 3.18.04 1.49-.06 3.22-.12 4.1-.8.11-1.36.38-1.36.69 0 .41.99.89 2.22.89s2.22-.48 2.22-.89c0-.31-.56-.58-1.36-.69-.06-.88-.16-2.6-.12-4.1.04-1.34.65-2.57 1.4-3.18z'/><path fill='#78909c' d='M17.89 18.77a2 2 0 0 1 .37-.11 2.3 2.3 0 0 1 .43-.05l-.02-.12c0-.07-.04-.16-.07-.26l-.05-.13c-.02-.04-.04-.08-.06-.11l-.06-.11a4 4 0 0 1-.44-.1 3 3 0 0 1-.4-.14c-.22-.09-.43-.2-.54-.32a.41.41 0 0 1-.16-.34.3.3 0 0 1 .07-.15.5.5 0 0 1 .16-.12l-.13-.07c-.04-.02-.08-.05-.14-.07-.05-.03-.11-.05-.17-.08-.11-.05-.22-.11-.32-.14l-.15-.06a3 3 0 0 1-.34.02c-.12 0-.25 0-.37-.01-.24-.02-.49-.05-.7-.12a2 2 0 0 1-.28-.1 1.3 1.3 0 0 1-.21-.11c-.11-.08-.19-.16-.19-.25l-.17-.03c-.1-.02-.33-.05-.47-.07 0 .02-.02.05-.02.09l-.04.17c-.07.35.13.7.47.8.83.24 1.46.64 1.64 1.13.37.99-1.18 1.96-3.52 1.96-2.35 0-3.89-.97-3.53-1.96.18-.48.78-.87 1.59-1.11a.72.72 0 0 0 .49-.84l-.05-.24-.31.04c-.14.02-.27.04-.37.06l-.17.03c.01.08-.05.17-.16.25a1.4 1.4 0 0 1-.2.12 2 2 0 0 1-.27.1 3 3 0 0 1-.33.08 3 3 0 0 1-.36.05c-.25.02-.5.02-.73 0l-.04.02c-.02.01-.07.03-.11.05l-.31.15c-.11.05-.21.12-.29.16l-.1.05-.03.02c.16.07.24.17.26.28.04.11-.01.23-.12.35a1.5 1.5 0 0 1-.22.18 2 2 0 0 1-.32.17 3.4 3.4 0 0 1-.83.26.5.5 0 0 0-.05.11l-.09.25-.04.26a.4.4 0 0 0-.01.12c.32 0 .61.06.83.14.25.08.4.21.52.36.09.16.13.33.05.53-.06.19-.22.41-.47.6l.04.03.12.08c.1.07.23.17.38.26l.44.24c.06.03.12.06.16.07l.06.03c.36-.15.75-.23 1.14-.28a3.3 3.3 0 0 1 .57-.01c.19.01.38.02.55.07.18.05.35.09.5.16a3 3 0 0 1 .42.24c.26.17.45.42.54.68l.33.01c.2.01.46.02.73.01l.72-.03.32-.03a1 1 0 0 1 .17-.38 1 1 0 0 1 .14-.16 1.6 1.6 0 0 1 .55-.39 2.3 2.3 0 0 1 .48-.17c.18-.04.35-.07.54-.08q.285-.015.57 0a4 4 0 0 1 1.15.24l.2-.11c.11-.07.28-.15.41-.25s.27-.19.35-.26l.13-.13c-.27-.19-.43-.39-.52-.58a.64.64 0 0 1-.06-.27.4.4 0 0 1 .06-.25c.07-.16.25-.27.45-.37M9.95 13.9s.35-1.32-1.17-3.03l-.05-.05a4.2 4.2 0 0 1-.98-2.7c0-2.32 1.89-4.2 4.2-4.2h.1a4.2 4.2 0 0 1 4.2 4.2 4.2 4.2 0 0 1-.99 2.7c-.01.01-.02.03-.05.05-1.34 1.35-1.17 3.03-1.17 3.03a6.14 6.14 0 0 0 4.13-5.75c.02-3.24-2.6-6-5.83-6.15h-.7c-3.23.16-5.85 2.92-5.83 6.15.01 2.59 1.67 4.9 4.13 5.75z'/></svg>",
@@ -237,8 +237,8 @@
"folder-bibliography": "<svg viewBox='0 0 1024 1024'><path fill='#a1887f' d='m443.008 241.152-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h768a64 64 0 0 0 64-64V320a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848'/><path fill='#8d6e63' d='M576 704v128c0 35.456 28.544 64 64 64h352c17.728 0 32-14.272 32-32V704z'/><path fill='#ffe082' d='M896 768v64h64v-64z'/><path fill='#fff8e1' d='M640 768c0 35.456 28.544 64 64 64h256c-35.346 0-64-28.654-64-64z'/><path fill='#ff1744' d='M704 800h64v160h-64z'/><path fill='#d7ccc8' d='M640 320c-35.456 0-64 28.544-64 64v448c0-35.346 28.654-64 64-64h352c17.728 0 32-14.272 32-32V352c0-17.728-14.272-32-32-32z'/><path fill='#5d4037' d='M640 384v32h32c32 0 32 7.163 32 16v224c0 8.837 0 16-32 16h-32v32h192c70.692 0 128-39.399 128-88-.014-40.302-39.848-75.446-96.688-85.305C884.346 514.563 895.986 493.665 896 472c0-48.601-57.308-88-128-88zm143.873 33.736C811.541 423.914 832 445.866 832 472c0 30.928-28.654 56-64 56v-96c0-8.837 7.266-16.186 15.873-14.264M768 560h64c35.346 0 64 25.072 64 56s-28.654 56-64 56h-48a16 16 0 0 1-16-16z'/></svg>",
"folder-bicep-open": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fff9c4' d='M15 25s1.15-11.115 4-15l4 1-1 3h-2v7h2c1.9-2.915 5.381-4.255 7.755-3.19 3.134 1.453 2.85 5.831 0 7.769C27.475 27.137 20.699 28.885 15 25'/></svg>",
"folder-bicep": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#fff9c4' d='M15 25s1.15-11.115 4-15l4 1-1 3h-2v7h2c1.9-2.915 5.381-4.255 7.755-3.19 3.134 1.453 2.85 5.831 0 7.769C27.475 27.137 20.699 28.885 15 25'/></svg>",
"folder-blender-open": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><g fill='#ffe0b2' fill-rule='evenodd'><path d='M19.716 20.926a3.215 3.215 0 0 1 3.21-3.216 3.215 3.215 0 0 1 3.22 3.206 3.215 3.215 0 0 1-3.2 3.224 3.215 3.215 0 0 1-3.23-3.195z' paint-order='stroke fill markers'/><path d='m25.595 13.309-2.8-2.862s-.957-.987-1.943-.03-.03 1.943-.03 1.943l.107.17c.445.444.907.88 1.353 1.324h-7.715s-1.286 0-1.286 1.285c0 1.286 1.286 1.286 1.286 1.286h2.572l-3.858 3.858-.857.907s-.967.966 0 1.933c.966.966 1.933 0 1.933 0l1.496-1.554.04.122q.039.357.112.701a7.08 7.08 0 0 0 5.528 5.47q.692.14 1.425.137a7.073 7.073 0 0 0 7.04-7.094c0-3.505-2.572-5.766-4.403-7.597zm-2.676 3.116a4.5 4.5 0 1 1-4.494 4.529v-.028a4.5 4.5 0 0 1 4.494-4.5'/></g></svg>",
"folder-blender": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><g fill='#ffe0b2' fill-rule='evenodd'><path d='M19.716 20.926a3.215 3.215 0 0 1 3.21-3.216 3.215 3.215 0 0 1 3.22 3.206 3.215 3.215 0 0 1-3.2 3.224 3.215 3.215 0 0 1-3.23-3.195z' paint-order='stroke fill markers'/><path d='m25.595 13.309-2.8-2.862s-.957-.987-1.943-.03-.03 1.943-.03 1.943l.107.17c.445.444.907.88 1.353 1.324h-7.715s-1.286 0-1.286 1.285c0 1.286 1.286 1.286 1.286 1.286h2.572l-3.858 3.858-.857.907s-.967.966 0 1.933c.966.966 1.933 0 1.933 0l1.496-1.554.04.122q.039.357.112.701a7.08 7.08 0 0 0 5.528 5.47q.692.14 1.425.137a7.073 7.073 0 0 0 7.04-7.094c0-3.505-2.572-5.766-4.403-7.597zm-2.676 3.116a4.5 4.5 0 1 1-4.494 4.529v-.028a4.5 4.5 0 0 1 4.494-4.5'/></g></svg>",
"folder-blender-open": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffe0b2' d='M29.955 19.686a6.4 6.4 0 0 0-2.146-3.87c-.377-.321-5.496-4.25-6.75-5.184l-.358-.276a1.045 1.045 0 1 0-1.274 1.656l2.52 1.939.062.05L12.996 14a.993.993 0 0 0-.081 1.984l.006.003.031.004c.016 0 .028.009.044.009h.068l.03.001h4.413l-7.734 5.957a1.56 1.56 0 0 0-.525 1.215 1 1 0 0 0 .44.825 1.47 1.47 0 0 0 1.516.023 3.1 3.1 0 0 0 .761-.554c2.098-1.725 3.674-3.008 3.684-2.999a1.5 1.5 0 0 1-.013.267 3.5 3.5 0 0 0 .089 1.259 6.2 6.2 0 0 0 1.08 2.288 7.28 7.28 0 0 0 4.58 2.76c.252.044.426.066.875.11.161.016 1.184-.008 1.379-.031a7.5 7.5 0 0 0 4.382-2.018 6.4 6.4 0 0 0 1.924-3.6 8.2 8.2 0 0 0 .01-1.817m-2.803.93a3.8 3.8 0 0 1-1.091 2.048 4.1 4.1 0 0 1-1.237.882 4.8 4.8 0 0 1-1.611.435 4.283 4.283 0 0 1-4.836-3.811 3.9 3.9 0 0 1 2.082-3.569 4.754 4.754 0 0 1 5.692.802 3.7 3.7 0 0 1 .65.933 3.5 3.5 0 0 1 .34.978 4.5 4.5 0 0 1 .011 1.303m-1.549-1.334a1.7 1.7 0 0 1 .059.56 1.8 1.8 0 0 1-.05.546 2.4 2.4 0 0 1-.724 1.216 2.85 2.85 0 0 1-1.758.772 2 2 0 0 1-.55-.001 2.79 2.79 0 0 1-2.265-1.51 3 3 0 0 1-.155-.392 2.36 2.36 0 0 1 .603-2.276 2.85 2.85 0 0 1 1.505-.824 3.36 3.36 0 0 1 1.532.085 2.68 2.68 0 0 1 1.803 1.824'/></svg>",
"folder-blender": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffe0b2' d='M29.955 19.686a6.4 6.4 0 0 0-2.146-3.87c-.377-.321-5.496-4.25-6.75-5.184l-.358-.276a1.045 1.045 0 1 0-1.274 1.656l2.52 1.939.062.05L12.996 14a.993.993 0 0 0-.081 1.984l.006.003.031.004c.016 0 .028.009.044.009h.068l.03.001h4.413l-7.734 5.957a1.56 1.56 0 0 0-.525 1.215 1 1 0 0 0 .44.825 1.47 1.47 0 0 0 1.516.023 3.1 3.1 0 0 0 .761-.554c2.098-1.725 3.674-3.008 3.684-2.999a1.5 1.5 0 0 1-.013.267 3.5 3.5 0 0 0 .089 1.259 6.2 6.2 0 0 0 1.08 2.288 7.28 7.28 0 0 0 4.58 2.76c.252.044.426.066.875.11.161.016 1.184-.008 1.379-.031a7.5 7.5 0 0 0 4.382-2.018 6.4 6.4 0 0 0 1.924-3.6 8.2 8.2 0 0 0 .01-1.817m-2.803.93a3.8 3.8 0 0 1-1.091 2.048 4.1 4.1 0 0 1-1.237.882 4.8 4.8 0 0 1-1.611.435 4.283 4.283 0 0 1-4.836-3.811 3.9 3.9 0 0 1 2.082-3.569 4.754 4.754 0 0 1 5.692.802 3.7 3.7 0 0 1 .65.933 3.5 3.5 0 0 1 .34.978 4.5 4.5 0 0 1 .011 1.303m-1.549-1.334a1.7 1.7 0 0 1 .059.56 1.8 1.8 0 0 1-.05.546 2.4 2.4 0 0 1-.724 1.216 2.85 2.85 0 0 1-1.758.772 2 2 0 0 1-.55-.001 2.79 2.79 0 0 1-2.265-1.51 3 3 0 0 1-.155-.392 2.36 2.36 0 0 1 .603-2.276 2.85 2.85 0 0 1 1.505-.824 3.36 3.36 0 0 1 1.532.085 2.68 2.68 0 0 1 1.803 1.824'/></svg>",
"folder-bloc-open": "<svg fill='none' viewBox='0 0 32 32'><path fill='#26a69a' d='M29 12H9.4c-.9 0-1.6.6-1.9 1.4L4 24V10h24c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.2-1.3-.5l-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h22l4.8-11.2c.4-1 0-2.2-1-2.6-.3-.1-.6-.2-.8-.2'/><path fill='#b2dfdb' d='m25 12 7 4.198v8.103L25 28.5l-7-4.212V16.21z'/></svg>",
"folder-bloc": "<svg fill='none' viewBox='0 0 32 32'><path fill='#26a69a' d='m13.8 7.5-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h24c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.1-1.3-.5'/><path fill='#b2dfdb' d='m25 12 7 4.198v8.103L25 28.5l-7-4.212V16.21z'/></svg>",
"folder-bower-open": "<svg viewBox='0 0 32 32'><path fill='#8d6e63' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#5d4037' d='M31.598 20.789c-1.028-1.012-6.166-1.644-7.786-1.827a5 5 0 0 0 .2-.589 6 6 0 0 1 .707-.269c.03.092.171.439.251.605a3.165 3.165 0 0 0 3.533-2.78 3 3 0 0 0 .027-.407 4 4 0 0 1 1.241-2.574c-1.666-.498-4.062.77-4.864 2.657a5 5 0 0 0-.902-.253A4.396 4.396 0 0 0 19.75 12a8.07 8.07 0 0 0-7.746 8.364l.002.07c0 4.46 2.97 8.365 4.647 8.365a1.65 1.65 0 0 0 1.511-1.067c.124.346.505 1.42.63 1.694.184.404 1.04.755 1.414.335a1.355 1.355 0 0 0 1.843-.292 1.36 1.36 0 0 0 1.724-.89 1 1 0 0 0 .039-.149c.454-.026.678-.68.578-1.2a13 13 0 0 0-1.159-2.234c.604.503 2.132.645 2.317 0 .973.782 2.488.372 2.61-.264 1.18.314 2.536-.376 2.314-1.213a1.63 1.63 0 0 0 1.525-1.719 1.66 1.66 0 0 0-.401-1.011'/><path fill='#03a9f4' d='M26.252 16.374a7.4 7.4 0 0 1 1.57-2.302 4.14 4.14 0 0 0-1.836 2.12 6 6 0 0 0-.646-.37 4.2 4.2 0 0 1 3.424-2.419c-.999.928-.644 2.855-1.464 3.876a11 11 0 0 0-1.048-.906Zm-.646 1.354a4 4 0 0 1 .035-.39 3.4 3.4 0 0 0-.6-.082 2.4 2.4 0 0 0 .208.889 3.05 3.05 0 0 0 1.629-.462 5.5 5.5 0 0 0-1.099-.318c-.04.085-.137.3-.173.362Z'/><path fill='#4caf50' d='M22.847 24.304v.003a11 11 0 0 1-.32-.805c.475.708 1.962.343 1.884-.292.728.562 2.226-.093 1.885-.88.73.348 1.561-.352 1.375-.657 1.243.245 2.434.49 2.808.588a1.43 1.43 0 0 1-1.667.505c.46.643-.434 1.414-1.68.99.274.63-.835 1.2-2.096.541.016.632-1.565.705-2.19.007Zm2.465-3.193c1.443.113 3.828.334 5.305.545-.093-.492-.348-.633-1.15-.854-.862.095-3.05.315-4.155.309'/><path fill='#ffca28' d='M24.41 23.21c.729.562 2.227-.093 1.886-.88.73.348 1.561-.352 1.375-.657-1.47-.29-3.012-.582-3.361-.633a53 53 0 0 1 1.002.07c1.106.007 3.293-.213 4.156-.307a41 41 0 0 0-6.216-1.023 3 3 0 0 1-.55.615 4.82 4.82 0 0 1-4.15 3.108 5.5 5.5 0 0 1-1.697-.293 3.54 3.54 0 0 1-3.432.074 6.94 6.94 0 0 0 6.356 4.321c2.335 0 3.37-2.443 3.143-3.09-.055-.156-.272-.677-.394-1.013.474.708 1.961.343 1.883-.291Z'/><path fill='#e0e0e0' d='M22.996 18.294a6.7 6.7 0 0 1 1.597-.724q-.016-.116-.023-.233c-.446.11-1.285.478-1.766-.03 1.015.314 1.521-.28 2.268-.28a4.7 4.7 0 0 1 1.579.329 5.27 5.27 0 0 0-3.355-1.534 2.45 2.45 0 0 0-.3 2.472'/><path fill='#f4511e' d='M16.855 23.21a5.5 5.5 0 0 0 1.698.293 4.82 4.82 0 0 0 4.148-3.109 5.25 5.25 0 0 1-3.473 1.011 5.42 5.42 0 0 0 3.54-2.294 3.14 3.14 0 0 1 .314-3.836 3.52 3.52 0 0 0-3.333-2.4 7.174 7.174 0 0 0-6.893 7.431l.004.127a7.4 7.4 0 0 0 .563 2.85 3.54 3.54 0 0 0 3.432-.074Z'/><path fill='#ffca28' d='M17.985 16.619a1.74 1.74 0 1 0 1.74-1.783 1.76 1.76 0 0 0-1.74 1.783'/><path fill='#5d4037' d='M18.683 16.619a1.042 1.042 0 1 0 1.042-1.068 1.055 1.055 0 0 0-1.042 1.068'/><ellipse cx='19.725' cy='16.145' fill='#fafafa' rx='.607' ry='.387'/></svg>",
@@ -255,8 +255,8 @@
"folder-circleci": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#eceff1' d='M22.954 10a9 9 0 0 0-8.555 6.367.5.5 0 0 0 .486.633h3.23a.5.5 0 0 0 .442-.28 5 5 0 1 1-.001 4.557.5.5 0 0 0-.439-.277h-3.232a.5.5 0 0 0-.486.633 8.997 8.997 0 0 0 17.597-2.892A9.103 9.103 0 0 0 22.954 10'/><circle cx='23' cy='19' r='2' fill='#eceff1'/></svg>",
"folder-class-open": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M16 12v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V12a2 2 0 0 0-2-2H18a2 2 0 0 0-2 2m14 14H18v-4h12Zm0-6H18v-4h12Z'/></svg>",
"folder-class": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='M16 12v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V12a2 2 0 0 0-2-2H18a2 2 0 0 0-2 2m14 14H18v-4h12Zm0-6H18v-4h12Z'/></svg>",
"folder-claude-open": "<svg viewBox='0 0 4.233 4.233'><path fill='#ff7043' d='M3.832 1.587H1.249a.265.265 0 0 0-.251.181L.529 3.175V1.323h3.175a.265.265 0 0 0-.264-.265H2a.27.27 0 0 1-.169-.061l-.17-.142a.27.27 0 0 0-.17-.061H.53a.265.265 0 0 0-.264.264v2.117a.265.265 0 0 0 .264.265h2.91l.636-1.484a.265.265 0 0 0-.243-.369'/><path fill='#ffccbc' d='m2.103 3.091.517-.29.009-.025-.009-.014h-.025l-.086-.006-.296-.008-.256-.01-.248-.014-.063-.013-.058-.077.006-.039.052-.035.075.007.167.01.25.018.18.01.268.029h.043l.006-.017-.015-.011-.01-.01-.26-.176-.279-.185-.146-.106-.08-.054-.04-.05-.016-.111.071-.08.097.007.025.007.097.075.21.162.272.2.04.034.016-.011.002-.008-.018-.03-.148-.268-.159-.273-.07-.113-.019-.068a.3.3 0 0 1-.011-.08l.082-.111.045-.015.11.015.045.04.068.155.11.244.17.332.05.098.027.092.01.028h.017V2.33l.014-.187.026-.23.025-.295.009-.084.041-.1.082-.053.064.03.052.075-.007.049-.031.203-.061.318-.04.213h.023l.027-.027.107-.143.181-.226.08-.09.093-.099.06-.047h.113l.084.124-.038.127-.116.148-.097.125-.138.187-.087.149.008.012.021-.002.313-.067.169-.03.201-.035.092.042.01.044-.036.088-.216.053-.253.051-.377.09-.004.003.005.006.17.016.072.004h.178l.33.025.087.057.052.07-.008.053-.133.068-.18-.043-.42-.1-.143-.035h-.02v.012l.12.117.22.198.274.256.014.063-.035.05-.037-.006-.242-.181-.093-.082-.211-.178h-.014v.019l.048.071.257.386.014.119-.019.038-.067.023-.073-.013-.15-.21-.155-.238-.125-.213-.016.008-.074.796-.034.04-.08.03-.067-.05-.035-.082.035-.161.043-.211.035-.168.03-.208.02-.07-.002-.004-.015.002-.157.215-.239.323-.189.202-.045.018-.079-.04.008-.073.043-.064.262-.333.158-.206.102-.12-.001-.017h-.006l-.695.451-.124.016-.053-.05.007-.081.025-.027.209-.144Z'/></svg>",
"folder-claude": "<svg viewBox='0 0 4.233 4.233'><path fill='#ff7043' d='m1.831.997-.17-.142a.27.27 0 0 0-.17-.061H.53a.265.265 0 0 0-.264.264v2.117a.265.265 0 0 0 .264.265h3.175a.265.265 0 0 0 .265-.265V1.323a.265.265 0 0 0-.265-.265H2.001a.27.27 0 0 1-.17-.061'/><path fill='#ffccbc' d='m2.103 3.091.517-.29.009-.025-.009-.014h-.025l-.086-.006-.296-.008-.256-.01-.248-.014-.063-.013-.058-.077.006-.039.052-.035.075.007.167.01.25.018.18.01.268.029h.043l.006-.017-.015-.011-.01-.01-.26-.176-.279-.185-.146-.106-.08-.054-.04-.05-.016-.111.071-.08.097.007.025.007.097.075.21.162.272.2.04.034.016-.011.002-.008-.018-.03-.148-.268-.159-.273-.07-.113-.019-.068a.3.3 0 0 1-.011-.08l.082-.111.045-.015.11.015.045.04.068.155.11.244.17.332.05.098.027.092.01.028h.017V2.33l.014-.187.026-.23.025-.295.009-.084.041-.1.082-.053.064.03.052.075-.007.049-.031.203-.061.318-.04.213h.023l.027-.027.107-.143.181-.226.08-.09.093-.099.06-.047h.113l.084.124-.038.127-.116.148-.097.125-.138.187-.087.149.008.012.021-.002.313-.067.169-.03.201-.035.092.042.01.044-.036.088-.216.053-.253.051-.377.09-.004.003.005.006.17.016.072.004h.178l.33.025.087.057.052.07-.008.053-.133.068-.18-.043-.42-.1-.143-.035h-.02v.012l.12.117.22.198.274.256.014.063-.035.05-.037-.006-.242-.181-.093-.082-.211-.178h-.014v.019l.048.071.257.386.014.119-.019.038-.067.023-.073-.013-.15-.21-.155-.238-.125-.213-.016.008-.074.796-.034.04-.08.03-.067-.05-.035-.082.035-.161.043-.211.035-.168.03-.208.02-.07-.002-.004-.015.002-.157.215-.239.323-.189.202-.045.018-.079-.04.008-.073.043-.064.262-.333.158-.206.102-.12-.001-.017h-.006l-.695.451-.124.016-.053-.05.007-.081.025-.027.209-.144Z'/></svg>",
"folder-claude-open": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><g fill='#ffccbc'><path d='m31.186 17.724.691.395v.297l-.197.691-8.398 1.976-.79-1.962zm0 0'/><path d='m28.044 11.913.967.203.257.316.245.758-.102.483-5.635 7.707-1.878-1.878 5.197-6.82z'/><path d='m23.183 10.71.593-.396.494.198.494.691-1.354 8.134-.919-.625-.395-1.087.692-6.125z'/><path d='m16.845 10.84.609-.778.397-.091.788.115.39.305 2.834 6.284 1.025 2.986-1.2.667-4.572-8.297z'/><path d='m13.895 15.156-.197-.791.593-.692.691.1h.198l4.15 3.062 1.284.988 1.778 1.383-.988 1.68-.889-.692-.593-.593-5.73-4.05z'/><path d='m12.71 20.293-.447-.494v-.44l.447-.153 5.039.297 4.94.395-.16.983-9.424-.49z'/><path d='M15.674 25.436h-.988l-.393-.453v-.54l1.677-1.186 6.82-4.34.69 1.178z'/><path d='m17.551 28.197-.395.099-.593-.297.099-.494 5.83-7.706.79 1.087-4.348 5.73z'/><path d='m22.491 29.185-.296.395-.593.198-.494-.395-.296-.593 1.482-8.003.889.099z'/><path d='M27.53 27.011v.79l-.099.297-.395.198-.691-.092-4.75-7.068L23.48 19.7l1.58 2.865.15 1.038z'/><path d='m29.803 25.826.098.494-.296.395-.296-.099-1.68-1.186-2.569-2.272-1.976-1.383.593-1.877.988.593.593 1.086z'/><path d='m28.518 20.787 2.47.198.593.395.395.593v.426l-1.087.463-5.533-1.383-2.272-.1.593-2.074 1.58 1.186z'/></g></svg>",
"folder-claude": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><g fill='#ffccbc'><path d='m31.186 17.725.691.395v.296l-.197.692-8.398 1.976-.79-1.962zm0 0'/><path d='m28.044 11.913.967.203.257.316.245.758-.102.484-5.635 7.706-1.878-1.877 5.197-6.82z'/><path d='m23.183 10.71.593-.395.494.197.494.692-1.354 8.133-.919-.624-.395-1.087.692-6.126z'/><path d='m16.845 10.84.609-.778.397-.09.788.115.39.304 2.834 6.284 1.025 2.986-1.2.667-4.572-8.296z'/><path d='m13.895 15.156-.197-.79.593-.692.691.099h.198l4.15 3.062 1.284.988 1.778 1.384-.988 1.68-.889-.692-.593-.593-5.73-4.051z'/><path d='m12.71 20.293-.447-.494v-.44l.447-.152 5.039.296 4.94.395-.16.984-9.424-.49z'/><path d='M15.674 25.436h-.988l-.393-.452v-.541l1.677-1.186 6.82-4.34.69 1.179z'/><path d='m17.551 28.197-.395.1-.593-.297.099-.494 5.83-7.707.79 1.087-4.348 5.73z'/><path d='m22.491 29.185-.296.396-.593.197-.494-.395-.296-.593 1.482-8.003.889.1z'/><path d='M27.53 27.012v.79l-.099.297-.395.197-.691-.092-4.75-7.068 1.884-1.435 1.58 2.865.15 1.037z'/><path d='m29.803 25.826.098.494-.296.395-.296-.099-1.68-1.185-2.569-2.273-1.976-1.383.593-1.877.988.593.593 1.087z'/><path d='m28.518 20.787 2.47.198.593.395.395.593v.427l-1.087.462-5.533-1.383-2.272-.099.593-2.075 1.58 1.186z'/></g></svg>",
"folder-client-open": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M15 12a1 1 0 0 0-1 1v10.994h-4v4h12v-4h-6V14h14v-2.006Z'/><path fill='#b3e5fc' d='M31 16h-6a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V17a1 1 0 0 0-1-1m-1 8h-4v-6h4Z'/></svg>",
"folder-client": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M15 12a1 1 0 0 0-1 1v10.994h-4v4h12v-4h-6V14h14v-2.006Z'/><path fill='#b3e5fc' d='M31 16h-6a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V17a1 1 0 0 0-1-1m-1 8h-4v-6h4Z'/></svg>",
"folder-cline-open": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#bbdefb' d='M23 12a2 2 0 0 0-2 2h-1c-2.216 0-4 1.784-4 4l-2 3 2 3c0 2.216 1.784 4 4 4h6c2.216 0 4-1.784 4-4l2-3-2-3c0-2.216-1.784-4-4-4h-1a2 2 0 0 0-2-2m-2 6c.554 0 1 .446 1 1v4c0 .554-.446 1-1 1s-1-.446-1-1v-4c0-.554.446-1 1-1m4 0c.554 0 1 .446 1 1v4c0 .554-.446 1-1 1s-1-.446-1-1v-4c0-.554.446-1 1-1'/></svg>",
@@ -379,6 +379,8 @@
"folder-functions": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M24 16h-2.982l.14-1.676.002-.01a1.945 1.945 0 0 1 3.848-.485l1.475-1.687a3.9 3.9 0 0 0-3.01-2.126 4.143 4.143 0 0 0-4.263 4.105L19.048 16H16v2h2.87l-.529 5.874a2.05 2.05 0 0 1-1.348 1.776l-.026.009a1.92 1.92 0 0 1-2.451-1.465l-1.477 1.687a3.91 3.91 0 0 0 2.99 2.1 4.13 4.13 0 0 0 4.274-4.08L20.839 18H24Zm8 4.929-1.414-1.414-2.829 2.828-2.828-2.828-1.414 1.414 2.828 2.828-2.828 2.829L24.929 28l2.828-2.828L30.586 28 32 26.586l-2.828-2.829z'/></svg>",
"folder-gamemaker-open": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b2dfdb' d='m32 20-9.03-9.03L13.942 20l9.03 9.03 3.765-3.766.007-5.264Zm-9.513 2.526L19.96 20l3.01-3.01L25.98 20h-3.494Z'/></svg>",
"folder-gamemaker": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b2dfdb' d='m32 20-9.03-9.03L13.942 20l9.03 9.03 3.765-3.766.007-5.264Zm-9.513 2.526L19.96 20l3.01-3.01L25.98 20h-3.494Z'/></svg>",
"folder-gemini-ai-open": "<svg fill='none' viewBox='0 0 32 32'><path fill='#42a5f5' d='M29 12H9.4c-.9 0-1.6.6-1.9 1.4L4 24V10h24c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.2-1.3-.5l-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h22l4.8-11.2c.4-1 0-2.2-1-2.6-.3-.1-.6-.2-.8-.2'/><path fill='#bbdefb' d='M32 22.02A10.653 10.653 0 0 0 22.02 32h-.04A10.65 10.65 0 0 0 12 22.02v-.04A10.65 10.65 0 0 0 21.98 12h.04A10.653 10.653 0 0 0 32 21.98z'/></svg>",
"folder-gemini-ai": "<svg fill='none' viewBox='0 0 32 32'><path fill='#42a5f5' d='m13.8 7.5-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h24c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.1-1.3-.5'/><path fill='#bbdefb' d='M32 22.02A10.653 10.653 0 0 0 22.02 32h-.04A10.65 10.65 0 0 0 12 22.02v-.04A10.65 10.65 0 0 0 21.98 12h.04A10.653 10.653 0 0 0 32 21.98z'/></svg>",
"folder-generator-open": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M23.998 12.978v2.986l4-3.981-4-3.983v2.986A7.98 7.98 0 0 0 16 18.95v.002a7.9 7.9 0 0 0 1.239 4.24l1.46-1.455a5.86 5.86 0 0 1-.7-2.785 5.987 5.987 0 0 1 6-5.974m6.759 1.732-1.46 1.454a5.968 5.968 0 0 1-5.3 8.76v-2.985l-4 3.983 4 3.981v-2.986a7.98 7.98 0 0 0 7.999-7.964v-.001a7.87 7.87 0 0 0-1.24-4.24Z'/></svg>",
"folder-generator": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='M23.998 12.978v2.986l4-3.981-4-3.983v2.986A7.98 7.98 0 0 0 16 18.95v.002a7.9 7.9 0 0 0 1.239 4.24l1.46-1.455a5.86 5.86 0 0 1-.7-2.785 5.987 5.987 0 0 1 6-5.974m6.759 1.732-1.46 1.454a5.968 5.968 0 0 1-5.3 8.76v-2.985l-4 3.983 4 3.981v-2.986a7.98 7.98 0 0 0 7.999-7.964v-.001a7.87 7.87 0 0 0-1.24-4.24Z'/></svg>",
"folder-gh-workflows-open": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#eceff1' d='M24 22v2h-2a2 2 0 0 1-2-2v-4h2v-6h-6v6h2v4a4.004 4.004 0 0 0 4 4h2v2h6v-6Zm-6-6v-2h2v2Zm10 10h-2v-2h2Z'/></svg>",
-2
View File
@@ -3284,8 +3284,6 @@
"admin.config.git_gc_args": "Arguments de GC",
"admin.config.git_migrate_timeout": "Délai imparti pour une migration",
"admin.config.git_mirror_timeout": "Délai imparti pour mettre à jour le miroir",
"admin.config.git_clone_timeout": "Délai imparti pour l'opération \"Clone\"",
"admin.config.git_pull_timeout": "Délai imparti pour l'opération \"Pull\"",
"admin.config.git_gc_timeout": "Délai imparti pour l'opération \"GC\"",
"admin.config.log_config": "Configuration du journal",
"admin.config.logger_name_fmt": "Journal : %s",
+1 -1
View File
@@ -99,7 +99,7 @@
"happy-dom": "20.3.0",
"jiti": "2.6.1",
"markdownlint-cli": "0.47.0",
"material-icon-theme": "5.30.0",
"material-icon-theme": "5.31.0",
"nolyfill": "1.0.44",
"postcss-html": "1.8.1",
"spectral-cli-bundle": "1.0.3",
+5 -5
View File
@@ -301,8 +301,8 @@ importers:
specifier: 0.47.0
version: 0.47.0
material-icon-theme:
specifier: 5.30.0
version: 5.30.0
specifier: 5.31.0
version: 5.31.0
nolyfill:
specifier: 1.0.44
version: 1.0.44
@@ -3118,8 +3118,8 @@ packages:
engines: {node: '>= 12'}
hasBin: true
material-icon-theme@5.30.0:
resolution: {integrity: sha512-GAB9vZGEVBQWD7NfZpx6mzkV47IJ6MaSFRLTXIhk9eCKrHbT5BlCwkeieu1Db/hDJHrQ/n+RdMvGtZa70FCGCQ==}
material-icon-theme@5.31.0:
resolution: {integrity: sha512-PPeGSRa+8stQEKvCr2Xym9KIqf2SPwl1chc7cxbK+aY6ORpwOcowtARQEXstZBjQwXTE5GnfE0zg0MFFy+XPzA==}
engines: {vscode: ^1.55.0}
mathml-tag-names@4.0.0:
@@ -7113,7 +7113,7 @@ snapshots:
marked@4.3.0: {}
material-icon-theme@5.30.0:
material-icon-theme@5.31.0:
dependencies:
chroma-js: 3.2.0
events: 3.3.0
+6 -6
View File
@@ -14,7 +14,7 @@ import (
func GetRegistrationToken(ctx *context.APIContext) {
// swagger:operation GET /admin/runners/registration-token admin adminGetRunnerRegistrationToken
// ---
// summary: Get an global actions runner registration token
// summary: Get a global actions runner registration token
// produces:
// - application/json
// parameters:
@@ -29,7 +29,7 @@ func GetRegistrationToken(ctx *context.APIContext) {
func CreateRegistrationToken(ctx *context.APIContext) {
// swagger:operation POST /admin/actions/runners/registration-token admin adminCreateRunnerRegistrationToken
// ---
// summary: Get an global actions runner registration token
// summary: Get a global actions runner registration token
// produces:
// - application/json
// parameters:
@@ -57,11 +57,11 @@ func ListRunners(ctx *context.APIContext) {
shared.ListRunners(ctx, 0, 0)
}
// GetRunner get an global runner
// GetRunner get a global runner
func GetRunner(ctx *context.APIContext) {
// swagger:operation GET /admin/actions/runners/{runner_id} admin getAdminRunner
// ---
// summary: Get an global runner
// summary: Get a global runner
// produces:
// - application/json
// parameters:
@@ -80,11 +80,11 @@ func GetRunner(ctx *context.APIContext) {
shared.GetRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
}
// DeleteRunner delete an global runner
// DeleteRunner delete a global runner
func DeleteRunner(ctx *context.APIContext) {
// swagger:operation DELETE /admin/actions/runners/{runner_id} admin deleteAdminRunner
// ---
// summary: Delete an global runner
// summary: Delete a global runner
// produces:
// - application/json
// parameters:
+8 -6
View File
@@ -27,10 +27,11 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []
command = gitcmd.NewCommand("rev-list").AddDynamicArguments(oldCommitID + "..." + newCommitID)
}
// This is safe as force pushes are already forbidden
var stdoutReader io.ReadCloser
stdoutReader, stdoutReaderClose := command.MakeStdoutPipe()
defer stdoutReaderClose()
err := command.WithEnv(env).
WithDir(repo.Path).
WithStdoutReader(&stdoutReader).
WithPipelineFunc(func(ctx gitcmd.Context) error {
err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env)
return ctx.CancelWithCause(err)
@@ -56,11 +57,12 @@ func readAndVerifyCommitsFromShaReader(input io.ReadCloser, repo *git.Repository
func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error {
commitID := git.MustIDFromString(sha)
var stdoutReader io.ReadCloser
return gitcmd.NewCommand("cat-file", "commit").AddDynamicArguments(sha).
WithEnv(env).
cmd := gitcmd.NewCommand("cat-file", "commit").AddDynamicArguments(sha)
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
defer stdoutReaderClose()
return cmd.WithEnv(env).
WithDir(repo.Path).
WithStdoutReader(&stdoutReader).
WithPipelineFunc(func(ctx gitcmd.Context) error {
commit, err := git.CommitFromReader(repo, commitID, stdoutReader)
if err != nil {
+2 -2
View File
@@ -446,8 +446,8 @@ func serviceRPC(ctx *context.Context, service string) {
if err := gitrepo.RunCmdWithStderr(ctx, h.getStorageRepo(), cmd.AddArguments(".").
WithEnv(append(os.Environ(), h.environ...)).
WithStdin(reqBody).
WithStdout(ctx.Resp),
WithStdinCopy(reqBody).
WithStdoutCopy(ctx.Resp),
); err != nil {
if !gitcmd.IsErrorCanceledOrKilled(err) {
log.Error("Fail to serve RPC(%s) in %s: %v", service, h.getStorageRepo().RelativePath(), err)
+5 -7
View File
@@ -407,7 +407,9 @@ func LFSPointerFiles(ctx *context.Context) {
err = func() error {
pointerChan := make(chan lfs.PointerBlob)
errChan := make(chan error, 1)
go lfs.SearchPointerBlobs(ctx, ctx.Repo.GitRepo, pointerChan, errChan)
go func() {
errChan <- lfs.SearchPointerBlobs(ctx, ctx.Repo.GitRepo, pointerChan)
}()
numPointers := 0
var numAssociated, numNoExist, numAssociatable int
@@ -483,11 +485,6 @@ func LFSPointerFiles(ctx *context.Context) {
results = append(results, result)
}
err, has := <-errChan
if has {
return err
}
ctx.Data["Pointers"] = results
ctx.Data["NumPointers"] = numPointers
ctx.Data["NumAssociated"] = numAssociated
@@ -495,7 +492,8 @@ func LFSPointerFiles(ctx *context.Context) {
ctx.Data["NumNoExist"] = numNoExist
ctx.Data["NumNotAssociated"] = numPointers - numAssociated
return nil
err := <-errChan
return err
}()
if err != nil {
ctx.ServerError("LFSPointerFiles", err)
+2 -9
View File
@@ -1263,21 +1263,14 @@ func getDiffBasic(ctx context.Context, gitRepo *git.Repository, opts *DiffOption
cmdCtx, cmdCancel := context.WithCancel(ctx)
defer cmdCancel()
reader, writer := io.Pipe()
defer func() {
_ = reader.Close()
_ = writer.Close()
}()
reader, readerClose := cmdDiff.MakeStdoutPipe()
defer readerClose()
go func() {
if err := cmdDiff.
WithDir(repoPath).
WithStdout(writer).
RunWithStderr(cmdCtx); err != nil && !gitcmd.IsErrorCanceledOrKilled(err) {
log.Error("error during GetDiff(git diff dir: %s): %v", repoPath, err)
}
_ = writer.Close()
}()
diff, err := ParsePatch(cmdCtx, opts.MaxLines, opts.MaxLineCharacters, opts.MaxFiles, reader, parsePatchSkipToFile)
+1 -1
View File
@@ -896,7 +896,7 @@ func (g *GiteaLocalUploader) CreateReviews(ctx context.Context, reviews ...*base
comment.TreePath = util.PathJoinRel(comment.TreePath)
var patch string
reader, writer := io.Pipe()
reader, writer := io.Pipe() // FIXME: use os.Pipe to avoid deadlock
defer func() {
_ = reader.Close()
_ = writer.Close()
+6 -5
View File
@@ -192,7 +192,9 @@ func pushAllLFSObjects(ctx context.Context, gitRepo *git.Repository, lfsClient l
pointerChan := make(chan lfs.PointerBlob)
errChan := make(chan error, 1)
go lfs.SearchPointerBlobs(ctx, gitRepo, pointerChan, errChan)
go func() {
errChan <- lfs.SearchPointerBlobs(ctx, gitRepo, pointerChan)
}()
uploadObjects := func(pointers []lfs.Pointer) error {
err := lfsClient.Upload(ctx, pointers, func(p lfs.Pointer, objectError error) (io.ReadCloser, error) {
@@ -242,13 +244,12 @@ func pushAllLFSObjects(ctx context.Context, gitRepo *git.Repository, lfsClient l
}
}
err, has := <-errChan
if has {
err := <-errChan
if err != nil {
log.Error("Error enumerating LFS objects for repository: %v", err)
return err
}
return nil
return err
}
func syncPushMirrorWithSyncOnCommit(ctx context.Context, repoID int64) {
+45 -42
View File
@@ -7,15 +7,19 @@ package pull
import (
"bufio"
"context"
"errors"
"io"
"strconv"
"sync"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/git/pipeline"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
"golang.org/x/sync/errgroup"
)
// LFSPush pushes lfs objects referred to in new commits in the head repository from the base repository
@@ -26,81 +30,82 @@ func LFSPush(ctx context.Context, tmpBasePath, mergeHeadSHA, mergeBaseSHA string
// ensure only blobs and <=1k size then pass in to git cat-file --batch
// to read each sha and check each as a pointer
// Then if they are lfs -> add them to the baseRepo
revListReader, revListWriter := io.Pipe()
shasToCheckReader, shasToCheckWriter := io.Pipe()
catFileCheckReader, catFileCheckWriter := io.Pipe()
shasToBatchReader, shasToBatchWriter := io.Pipe()
catFileBatchReader, catFileBatchWriter := io.Pipe()
errChan := make(chan error, 1)
wg := sync.WaitGroup{}
wg.Add(6)
// Create the go-routines in reverse order.
cmd1RevList, cmd3BathCheck, cmd5BatchContent := gitcmd.NewCommand(), gitcmd.NewCommand(), gitcmd.NewCommand()
cmd1RevListOut, cmd1RevListClose := cmd1RevList.MakeStdoutPipe()
defer cmd1RevListClose()
cmd3BatchCheckIn, cmd3BatchCheckOut, cmd3BatchCheckClose := cmd3BathCheck.MakeStdinStdoutPipe()
defer cmd3BatchCheckClose()
cmd5BatchContentIn, cmd5BatchContentOut, cmd5BatchContentClose := cmd5BatchContent.MakeStdinStdoutPipe()
defer cmd5BatchContentClose()
// Create the go-routines in reverse order (update: the order is not needed any more, the pipes are properly prepared)
wg := &errgroup.Group{}
// 6. Take the output of cat-file --batch and check if each file in turn
// to see if they're pointers to files in the LFS store associated with
// the head repo and add them to the base repo if so
go createLFSMetaObjectsFromCatFileBatch(ctx, catFileBatchReader, &wg, pr)
wg.Go(func() error {
return createLFSMetaObjectsFromCatFileBatch(ctx, cmd5BatchContentOut, pr)
})
// 5. Take the shas of the blobs and batch read them
go pipeline.CatFileBatch(ctx, shasToBatchReader, catFileBatchWriter, &wg, tmpBasePath)
wg.Go(func() error {
return pipeline.CatFileBatch(ctx, cmd5BatchContent, tmpBasePath)
})
// 4. From the provided objects restrict to blobs <=1k
go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg)
wg.Go(func() error {
return pipeline.BlobsLessThan1024FromCatFileBatchCheck(cmd3BatchCheckOut, cmd5BatchContentIn)
})
// 3. Run batch-check on the objects retrieved from rev-list
go pipeline.CatFileBatchCheck(ctx, shasToCheckReader, catFileCheckWriter, &wg, tmpBasePath)
wg.Go(func() error {
return pipeline.CatFileBatchCheck(ctx, cmd3BathCheck, tmpBasePath)
})
// 2. Check each object retrieved rejecting those without names as they will be commits or trees
go pipeline.BlobsFromRevListObjects(revListReader, shasToCheckWriter, &wg)
wg.Go(func() error {
return pipeline.BlobsFromRevListObjects(cmd1RevListOut, cmd3BatchCheckIn)
})
// 1. Run rev-list objects from mergeHead to mergeBase
go pipeline.RevListObjects(ctx, revListWriter, &wg, tmpBasePath, mergeHeadSHA, mergeBaseSHA, errChan)
wg.Go(func() error {
return pipeline.RevListObjects(ctx, cmd1RevList, tmpBasePath, mergeHeadSHA, mergeBaseSHA)
})
wg.Wait()
select {
case err, has := <-errChan:
if has {
return err
}
default:
}
return nil
return wg.Wait()
}
func createLFSMetaObjectsFromCatFileBatch(ctx context.Context, catFileBatchReader *io.PipeReader, wg *sync.WaitGroup, pr *issues_model.PullRequest) {
defer wg.Done()
func createLFSMetaObjectsFromCatFileBatch(ctx context.Context, catFileBatchReader io.ReadCloser, pr *issues_model.PullRequest) error {
defer catFileBatchReader.Close()
contentStore := lfs.NewContentStore()
bufferedReader := bufio.NewReader(catFileBatchReader)
buf := make([]byte, 1025)
for {
// File descriptor line: sha
_, err := bufferedReader.ReadString(' ')
if err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return util.Iif(errors.Is(err, io.EOF), nil, err)
}
// Throw away the blob
if _, err := bufferedReader.ReadString(' '); err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return err
}
sizeStr, err := bufferedReader.ReadString('\n')
if err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return err
}
size, err := strconv.Atoi(sizeStr[:len(sizeStr)-1])
if err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return err
}
pointerBuf := buf[:size+1]
if _, err := io.ReadFull(bufferedReader, pointerBuf); err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return err
}
pointerBuf = pointerBuf[:size]
// Now we need to check if the pointerBuf is an LFS pointer
@@ -120,15 +125,13 @@ func createLFSMetaObjectsFromCatFileBatch(ctx context.Context, catFileBatchReade
log.Warn("During merge of: %d in %-v, there is a pointer to LFS Oid: %s which although present in the LFS store is not associated with the head repo %-v", pr.Index, pr.BaseRepo, pointer.Oid, pr.HeadRepo)
continue
}
_ = catFileBatchReader.CloseWithError(err)
break
return err
}
// OK we have a pointer that is associated with the head repo
// and is actually a file in the LFS
// Therefore it should be associated with the base repo
if _, err := git_model.NewLFSMetaObject(ctx, pr.BaseRepoID, pointer); err != nil {
_ = catFileBatchReader.CloseWithError(err)
break
return err
}
}
}
+5 -5
View File
@@ -41,7 +41,7 @@ func (ctx *mergeContext) PrepareGitCmd(cmd *gitcmd.Command) *gitcmd.Command {
return cmd.WithEnv(ctx.env).
WithDir(ctx.tmpBasePath).
WithParentCallerInfo().
WithStdout(ctx.outbuf)
WithStdoutBuffer(ctx.outbuf)
}
// ErrSHADoesNotMatch represents a "SHADoesNotMatch" kind of error.
@@ -219,11 +219,11 @@ func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string, o
return 0, nil, nil
}
var diffOutReader io.ReadCloser
err := gitcmd.NewCommand("diff-tree", "--no-commit-id", "--name-only", "-r", "-r", "-z", "--root").
AddDynamicArguments(baseBranch, headBranch).
cmd := gitcmd.NewCommand("diff-tree", "--no-commit-id", "--name-only", "-r", "-r", "-z", "--root")
diffOutReader, diffOutReaderClose := cmd.MakeStdoutPipe()
defer diffOutReaderClose()
err := cmd.AddDynamicArguments(baseBranch, headBranch).
WithDir(repoPath).
WithStdoutReader(&diffOutReader).
WithPipelineFunc(func(ctx gitcmd.Context) error {
// Now scan the output from the command
scanner := bufio.NewScanner(diffOutReader)
+2 -2
View File
@@ -414,13 +414,13 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
// - alternatively we can do the equivalent of:
// `git apply --check ... | grep ...`
// meaning we don't store all the conflicts unnecessarily.
var stderrReader io.ReadCloser
stderrReader, stderrReaderClose := cmdApply.MakeStderrPipe()
defer stderrReaderClose()
// 8. Run the check command
conflict = false
err = cmdApply.
WithDir(tmpBasePath).
WithStderrReader(&stderrReader).
WithPipelineFunc(func(ctx gitcmd.Context) error {
const prefix = "error: patch failed:"
const errorPrefix = "error: "
+4 -4
View File
@@ -59,10 +59,10 @@ func readUnmergedLsFileLines(ctx context.Context, tmpBasePath string, outputChan
close(outputChan)
}()
var lsFilesReader io.ReadCloser
err := gitcmd.NewCommand("ls-files", "-u", "-z").
WithDir(tmpBasePath).
WithStdoutReader(&lsFilesReader).
cmd := gitcmd.NewCommand("ls-files", "-u", "-z")
lsFilesReader, lsFilesReaderClose := cmd.MakeStdoutPipe()
defer lsFilesReaderClose()
err := cmd.WithDir(tmpBasePath).
WithPipelineFunc(func(_ gitcmd.Context) error {
bufferedReader := bufio.NewReader(lsFilesReader)
+2 -2
View File
@@ -526,9 +526,9 @@ func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest,
cmd := gitcmd.NewCommand("diff", "--name-only", "-z").AddDynamicArguments(newCommitID, oldCommitID, mergeBase)
var stdoutReader io.ReadCloser
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
defer stdoutReaderClose()
if err := cmd.WithDir(prCtx.tmpBasePath).
WithStdoutReader(&stdoutReader).
WithPipelineFunc(func(ctx gitcmd.Context) error {
return util.IsEmptyReader(stdoutReader)
}).
+1 -1
View File
@@ -274,7 +274,7 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo
if len(commitID) == 0 {
commitID = headCommitID
}
reader, writer := io.Pipe()
reader, writer := io.Pipe() // FIXME: use os.Pipe to avoid deadlock
defer func() {
_ = reader.Close()
_ = writer.Close()
+4 -4
View File
@@ -5,11 +5,11 @@
package pull
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strings"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
@@ -32,7 +32,7 @@ type prTmpRepoContext struct {
context.Context
tmpBasePath string
pr *issues_model.PullRequest
outbuf *strings.Builder // we keep these around to help reduce needless buffer recreation, any use should be preceded by a Reset and preferably after use
outbuf *bytes.Buffer // we keep these around to help reduce needless buffer recreation, any use should be preceded by a Reset and preferably after use
}
// PrepareGitCmd prepares a git command with the correct directory, environment, and output buffers
@@ -40,7 +40,7 @@ type prTmpRepoContext struct {
// Do NOT use it with gitcmd.RunStd*() functions, otherwise it will panic
func (ctx *prTmpRepoContext) PrepareGitCmd(cmd *gitcmd.Command) *gitcmd.Command {
ctx.outbuf.Reset()
return cmd.WithDir(ctx.tmpBasePath).WithStdout(ctx.outbuf)
return cmd.WithDir(ctx.tmpBasePath).WithStdoutBuffer(ctx.outbuf)
}
// createTemporaryRepoForPR creates a temporary repo with "base" for pr.BaseBranch and "tracking" for pr.HeadBranch
@@ -82,7 +82,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest)
Context: ctx,
tmpBasePath: tmpBasePath,
pr: pr,
outbuf: &strings.Builder{},
outbuf: &bytes.Buffer{},
}
baseRepoPath := pr.BaseRepo.RepoPath()
+1 -1
View File
@@ -82,7 +82,7 @@ func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullReques
pr.Index,
)).
WithDir(mergeCtx.tmpBasePath).
WithStdout(mergeCtx.outbuf).
WithStdoutBuffer(mergeCtx.outbuf).
RunWithStderr(ctx); err != nil {
if strings.Contains(err.Stderr(), "non-fast-forward") {
return &git.ErrPushOutOfDate{
+2 -3
View File
@@ -264,12 +264,12 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri
return git_model.ErrBranchAlreadyExists{
BranchName: name,
}
// If branchRefName like a/b but we want to create a branch named a then we have a conflict
// If branchRefName like "a/b" but we want to create a branch named a then we have a conflict
case strings.HasPrefix(branchRefName, name+"/"):
return git_model.ErrBranchNameConflict{
BranchName: branchRefName,
}
// Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict
// Conversely if branchRefName like "a" but we want to create a branch named "a/b" then we also have a conflict
case strings.HasPrefix(name, branchRefName+"/"):
return git_model.ErrBranchNameConflict{
BranchName: branchRefName,
@@ -281,7 +281,6 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri
}
return nil
})
return err
}
+3 -3
View File
@@ -8,7 +8,6 @@ import (
"context"
"errors"
"fmt"
"io"
"strconv"
"strings"
"sync"
@@ -122,10 +121,11 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
// AddOptionFormat("--max-count=%d", limit)
gitCmd.AddDynamicArguments(baseCommit.ID.String())
var stdoutReader io.ReadCloser
stdoutReader, stdoutReaderClose := gitCmd.MakeStdoutPipe()
defer stdoutReaderClose()
var extendedCommitStats []*ExtendedCommitStats
err = gitCmd.WithDir(repo.Path).
WithStdoutReader(&stdoutReader).
WithPipelineFunc(func(ctx gitcmd.Context) error {
scanner := bufio.NewScanner(stdoutReader)
+1 -1
View File
@@ -170,7 +170,7 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
}
if err := cmdApply.WithDir(t.basePath).
WithStdin(strings.NewReader(opts.Content)).
WithStdinBytes([]byte(opts.Content)).
RunWithStderr(ctx); err != nil {
return nil, fmt.Errorf("git apply error: %w", err)
}
+11 -10
View File
@@ -117,7 +117,7 @@ func (t *TemporaryUploadRepository) LsFiles(ctx context.Context, filenames ...st
stdOut := new(bytes.Buffer)
if err := gitcmd.NewCommand("ls-files", "-z").AddDashesAndList(filenames...).
WithDir(t.basePath).
WithStdout(stdOut).
WithStdoutBuffer(stdOut).
RunWithStderr(ctx); err != nil {
return nil, fmt.Errorf("unable to run git ls-files for temporary repo of: %s, error: %w", t.repo.FullName(), err)
}
@@ -155,7 +155,7 @@ func (t *TemporaryUploadRepository) RemoveFilesFromIndex(ctx context.Context, fi
if err := gitcmd.NewCommand("update-index", "--remove", "-z", "--index-info").
WithDir(t.basePath).
WithStdin(stdIn).
WithStdinBytes(stdIn.Bytes()).
RunWithStderr(ctx); err != nil {
return fmt.Errorf("unable to update-index for temporary repo: %q, error: %w", t.repo.FullName(), err)
}
@@ -167,8 +167,8 @@ func (t *TemporaryUploadRepository) HashObjectAndWrite(ctx context.Context, cont
stdOut := new(bytes.Buffer)
if err := gitcmd.NewCommand("hash-object", "-w", "--stdin").
WithDir(t.basePath).
WithStdout(stdOut).
WithStdin(content).
WithStdoutBuffer(stdOut).
WithStdinCopy(content).
RunWithStderr(ctx); err != nil {
return "", fmt.Errorf("unable to hash-object to temporary repo: %s, error: %w", t.repo.FullName(), err)
}
@@ -330,8 +330,8 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit
if err := cmdCommitTree.
WithEnv(env).
WithDir(t.basePath).
WithStdout(stdout).
WithStdin(messageBytes).
WithStdoutBuffer(stdout).
WithStdinBytes(messageBytes.Bytes()).
RunWithStderr(ctx); err != nil {
return "", fmt.Errorf("unable to commit-tree in temporary repo: %s Error: %w", t.repo.FullName(), err)
}
@@ -363,11 +363,12 @@ func (t *TemporaryUploadRepository) Push(ctx context.Context, doer *user_model.U
// DiffIndex returns a Diff of the current index to the head
func (t *TemporaryUploadRepository) DiffIndex(ctx context.Context) (*gitdiff.Diff, error) {
var diff *gitdiff.Diff
var stdoutReader io.ReadCloser
err := gitcmd.NewCommand("diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD").
WithTimeout(30 * time.Second).
cmd := gitcmd.NewCommand("diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD")
stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe()
defer stdoutReaderClose()
err := cmd.WithTimeout(30 * time.Second).
WithDir(t.basePath).
WithStdoutReader(&stdoutReader).
WithPipelineFunc(func(ctx gitcmd.Context) error {
var diffErr error
diff, diffErr = gitdiff.ParsePatch(ctx, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader, "")
+2 -3
View File
@@ -6,7 +6,6 @@ package gitgraph
import (
"bufio"
"bytes"
"io"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
@@ -45,10 +44,10 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
commitsToSkip := setting.UI.GraphMaxCommitNum * (page - 1)
var stdoutReader io.ReadCloser
stdoutReader, stdoutReaderClose := graphCmd.MakeStdoutPipe()
defer stdoutReaderClose()
if err := graphCmd.
WithDir(r.Path).
WithStdoutReader(&stdoutReader).
WithPipelineFunc(func(ctx gitcmd.Context) error {
scanner := bufio.NewScanner(stdoutReader)
parser := &Parser{}
+1 -1
View File
@@ -170,7 +170,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
// FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here
objectHash, err := gitRepo.HashObject(strings.NewReader(content))
objectHash, err := gitRepo.HashObjectBytes([]byte(content))
if err != nil {
log.Error("HashObject failed: %v", err)
return err
+1 -1
View File
@@ -23,7 +23,7 @@
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.project_kind")}}
</form>
<div class="list-header-filters">
<div class="list-header-filters ui secondary menu tw-m-0">
<!-- Sort -->
<div class="item ui small dropdown jump">
<span class="text">
+4 -4
View File
@@ -149,7 +149,7 @@
"tags": [
"admin"
],
"summary": "Get an global actions runner registration token",
"summary": "Get a global actions runner registration token",
"operationId": "adminCreateRunnerRegistrationToken",
"responses": {
"200": {
@@ -166,7 +166,7 @@
"tags": [
"admin"
],
"summary": "Get an global runner",
"summary": "Get a global runner",
"operationId": "getAdminRunner",
"parameters": [
{
@@ -196,7 +196,7 @@
"tags": [
"admin"
],
"summary": "Delete an global runner",
"summary": "Delete a global runner",
"operationId": "deleteAdminRunner",
"parameters": [
{
@@ -624,7 +624,7 @@
"tags": [
"admin"
],
"summary": "Get an global actions runner registration token",
"summary": "Get a global actions runner registration token",
"operationId": "adminGetRunnerRegistrationToken",
"responses": {
"200": {
+1 -1
View File
@@ -62,7 +62,7 @@
)}}
</form>
<div class="list-header-filters">
<div class="list-header-filters ui secondary menu tw-m-0">
{{if $.Labels}}
{{template "repo/issue/filter_item_label" dict "Labels" .Labels "QueryLink" $queryLinkWithFilter "SupportArchivedLabel" true}}
{{end}}
+4 -4
View File
@@ -129,22 +129,22 @@ func TestUnknowUser(t *testing.T) {
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization)
req := NewRequest(t, "GET", "/api/v1/users/unknow/orgs/org25/permissions").
req := NewRequest(t, "GET", "/api/v1/users/unknown/orgs/org25/permissions").
AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusNotFound)
var apiError api.APIError
DecodeJSON(t, resp, &apiError)
assert.Equal(t, "user redirect does not exist [name: unknow]", apiError.Message)
assert.Equal(t, "user redirect does not exist [name: unknown]", apiError.Message)
}
func TestUnknowOrganization(t *testing.T) {
func TestUnknownOrganization(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization)
req := NewRequest(t, "GET", "/api/v1/users/user1/orgs/unknow/permissions").
req := NewRequest(t, "GET", "/api/v1/users/user1/orgs/unknown/permissions").
AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusNotFound)
var apiError api.APIError
+8 -13
View File
@@ -405,16 +405,13 @@ func TestCantMergeUnrelated(t *testing.T) {
err := gitcmd.NewCommand("read-tree", "--empty").WithDir(path).Run(t.Context())
assert.NoError(t, err)
stdin := strings.NewReader("Unrelated File")
var stdout strings.Builder
err = gitcmd.NewCommand("hash-object", "-w", "--stdin").
stdout, _, err := gitcmd.NewCommand("hash-object", "-w", "--stdin").
WithDir(path).
WithStdin(stdin).
WithStdout(&stdout).
Run(t.Context())
WithStdinBytes([]byte("Unrelated File")).
RunStdString(t.Context())
assert.NoError(t, err)
sha := strings.TrimSpace(stdout.String())
sha := strings.TrimSpace(stdout)
_, _, err = gitcmd.NewCommand("update-index", "--add", "--replace", "--cacheinfo").
AddDynamicArguments("100644", sha, "somewher-over-the-rainbow").
@@ -441,15 +438,13 @@ func TestCantMergeUnrelated(t *testing.T) {
_, _ = messageBytes.WriteString("Unrelated")
_, _ = messageBytes.WriteString("\n")
stdout.Reset()
err = gitcmd.NewCommand("commit-tree").AddDynamicArguments(treeSha).
stdout, _, err = gitcmd.NewCommand("commit-tree").AddDynamicArguments(treeSha).
WithEnv(env).
WithDir(path).
WithStdin(messageBytes).
WithStdout(&stdout).
Run(t.Context())
WithStdinBytes(messageBytes.Bytes()).
RunStdString(t.Context())
assert.NoError(t, err)
commitSha := strings.TrimSpace(stdout.String())
commitSha := strings.TrimSpace(stdout)
_, _, err = gitcmd.NewCommand("branch", "unrelated").
AddDynamicArguments(commitSha).
+5 -3
View File
@@ -95,8 +95,9 @@
}
}
/* make all issue filter dropdown menus popup leftward, to avoid go out the viewport (right side) */
.repository .filter.menu .ui.dropdown .menu {
/* make all issue filter dropdown (user dashboard, repo issue list) menus popup leftward, to avoid go out the viewport (right side) */
.list-header-filters .ui.dropdown .menu,
.issue-list-toolbar .ui.dropdown .menu {
max-height: 500px;
max-width: 300px;
overflow-x: hidden;
@@ -105,7 +106,8 @@
}
/* the label-filter is the first dropdown, it shouldn't be shown leftward, otherwise it may go out the viewport (left side) */
.repository .filter.menu .ui.dropdown.label-filter .menu {
.list-header-filters .ui.dropdown.label-filter .menu,
.issue-list-toolbar .ui.dropdown.label-filter .menu {
min-width: max-content;
right: unset;
left: 0;
@@ -186,6 +186,7 @@ export function markdownHandleIndention(tvs: TextareaValueSelection): MarkdownHa
}
function handleNewline(textarea: HTMLTextAreaElement, e: KeyboardEvent) {
if (e.isComposing) return;
const ret = markdownHandleIndention({value: textarea.value, selStart: textarea.selectionStart, selEnd: textarea.selectionEnd});
if (!ret.handled || !ret.valueSelection) return; // FIXME: the "handled" seems redundant, only valueSelection is enough (null for unhandled)
e.preventDefault();