diff --git a/.github/actions/pgsql-shard/action.yml b/.github/actions/pgsql-shard/action.yml index 9a5a03ae0de..08bf1d10c26 100644 --- a/.github/actions/pgsql-shard/action.yml +++ b/.github/actions/pgsql-shard/action.yml @@ -34,7 +34,7 @@ runs: env: # pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower. GOTEST_FLAGS: -race -timeout=40m - TAGS: bindata gogit + TAGS: bindata TEST_LDAP: 1 TEST_SHARD: ${{ inputs.shard }} TEST_TOTAL_SHARDS: ${{ inputs.total-shards }} diff --git a/.github/workflows/cache-seeder.yml b/.github/workflows/cache-seeder.yml index 4a597b67863..6aaaf9cf355 100644 --- a/.github/workflows/cache-seeder.yml +++ b/.github/workflows/cache-seeder.yml @@ -37,22 +37,15 @@ jobs: - uses: ./.github/actions/go-setup - run: make deps-backend deps-tools - run: TAGS="bindata" make backend - - run: TAGS="bindata gogit" make backend - name: warm test compile cache (bindata) env: TAGS: bindata GOTEST_FLAGS: -race -list=^$$ -count=1 run: make test-backend - - name: warm test compile cache (bindata gogit) - env: - TAGS: bindata gogit - GOTEST_FLAGS: -race -list=^$$ -count=1 - run: make test-backend - name: warm integration compile cache run: | TAGS="bindata" make test-integration-compile - TAGS="bindata gogit" make test-integration-compile - TAGS="bindata gogit" GOTEST_FLAGS="-race" make test-integration-compile + TAGS="bindata" GOTEST_FLAGS="-race" make test-integration-compile lint: runs-on: ubuntu-latest diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index be133fb70a3..6e6aa080667 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -93,13 +93,13 @@ jobs: env: GOOS: linux GOARCH: arm64 - TAGS: bindata gogit + TAGS: bindata - name: build-backend-windows run: go build -ldflags '-s -w' -o gitea-windows env: GOOS: windows GOARCH: amd64 - TAGS: bindata gogit + TAGS: bindata - name: build-backend-386 run: go build -ldflags '-s -w' -o gitea-linux-386 env: diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index 616452731ce..df683574807 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -95,17 +95,17 @@ jobs: - run: make deps-backend - run: make backend env: - TAGS: bindata gogit + TAGS: bindata - run: GITEA_TEST_DATABASE=sqlite make test-migration env: - TAGS: bindata gogit + TAGS: bindata - name: run tests run: GITEA_TEST_DATABASE=sqlite make test-integration timeout-minutes: 50 env: # sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow GOTEST_FLAGS: -timeout=40m - TAGS: bindata gogit + TAGS: bindata test-unit: if: needs.files-changed.outputs.backend == 'true' @@ -163,13 +163,6 @@ jobs: GOTEST_FLAGS: -race -timeout=20m TAGS: bindata GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }} - - name: unit-tests-gogit - run: make test-backend - env: - GOTEST_FLAGS: -race -timeout=20m - TAGS: bindata gogit - GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }} - GITEA_TEST_CI_SKIP_EXTERNAL: true - run: make test-check test-mysql: diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 984ea780ea5..6c5eaee0818 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -780,8 +780,6 @@ LEVEL = Info ;; Respond to pushes to a non-default branch with a URL for creating a Pull Request (if the repository has them enabled) ;PULL_REQUEST_PUSH_MESSAGE = true ;; -;; (Go-Git only) Don't cache objects greater than this in memory. (Set to 0 to disable.) -;LARGE_OBJECT_THRESHOLD = 1048576 ;; Set to true to forcibly set core.protectNTFS=false ;DISABLE_CORE_PROTECT_NTFS=false ;; Disable the usage of using partial clones for git. diff --git a/docs/build-source.md b/docs/build-source.md index 43558571f28..6c31eae7a5f 100644 --- a/docs/build-source.md +++ b/docs/build-source.md @@ -25,7 +25,6 @@ Depending on requirements, the following build tags can be included. - `bindata`: Build a single monolithic binary, with all assets included. Required for distribution and production build. - `pam`: Enable support for PAM (Linux Pluggable Authentication Modules). Can be used to authenticate local users or extend authentication to methods available to PAM. -- `gogit`: (EXPERIMENTAL) Use go-git variants of Git commands. To include all assets, use the `bindata` tag: @@ -33,13 +32,6 @@ To include all assets, use the `bindata` tag: TAGS="bindata" make build ``` -Tag `gogit` is used to try to resolve some Windows-specific performance problems, POSIX systems don't need it. -You can build a Windows binary by: - -```bash -GOOS=windows TAGS="bindata gogit" make build -``` - ## Changing default paths Gitea will search for a number of things from the _`CustomPath`_. diff --git a/go.mod b/go.mod index e1eda7eb9e0..28f106f4978 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,6 @@ require ( github.com/dustin/go-humanize v1.1.0 github.com/editorconfig/editorconfig-core-go/v2 v2.6.5 github.com/emersion/go-imap v1.2.1 - github.com/emirpasic/gods v1.18.1 github.com/felixge/fgprof v0.9.5 github.com/fsnotify/fsnotify v1.10.1 github.com/getkin/kin-openapi v0.149.0 @@ -40,8 +39,6 @@ require ( github.com/go-chi/cors v1.2.2 github.com/go-co-op/gocron/v2 v2.22.0 github.com/go-enry/go-enry/v2 v2.9.6 - github.com/go-git/go-billy/v5 v5.9.1 - github.com/go-git/go-git/v5 v5.19.2 github.com/go-ldap/ldap/v3 v3.4.14 github.com/go-redsync/redsync/v4 v4.18.0 github.com/go-sql-driver/mysql v1.10.1 @@ -111,10 +108,8 @@ require ( require ( cloud.google.com/go/compute/metadata v0.9.0 // indirect - dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.2.0 // indirect github.com/DataDog/zstd v1.5.7 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect github.com/RoaringBitmap/roaring/v2 v2.18.2 // indirect github.com/STARRY-S/zip v0.2.3 // indirect github.com/andybalholm/brotli v1.2.1 // indirect @@ -152,7 +147,6 @@ require ( github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect - github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect @@ -161,7 +155,6 @@ require ( github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-openapi/jsonpointer v0.23.1 // indirect github.com/go-openapi/swag/jsonname v0.26.1 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect @@ -169,7 +162,6 @@ require ( github.com/goccy/go-json v0.10.6 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/go-querystring v1.2.0 // indirect github.com/google/go-tpm v0.9.8 // indirect @@ -180,10 +172,8 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/inbucket/html2text v1.0.0 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.6.0 // indirect github.com/klauspost/cpuid/v2 v2.4.0 // indirect github.com/klauspost/crc32 v1.3.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect @@ -211,9 +201,9 @@ require ( github.com/olekukonko/ll v0.1.8 // indirect github.com/olekukonko/tablewriter v1.1.4 // indirect github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.34.1 // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/pierrec/lz4/v4 v4.1.27 // indirect - github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect @@ -224,14 +214,12 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.10.2 // indirect - github.com/skeema/knownhosts v1.3.2 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/stangelandcl/ppmd v0.1.1 // indirect github.com/tinylib/msgp v1.6.4 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/zeebo/blake3 v0.2.4 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect @@ -242,10 +230,10 @@ require ( go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect go4.org v0.0.0-20260112195520-a5071408f32f // indirect + golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.49.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect modernc.org/libc v1.75.7 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.12.1 // indirect diff --git a/go.sum b/go.sum index 80628ee795a..49bb8fad6fc 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= connectrpc.com/connect v1.21.0 h1:LhqSJt7jHf5NJBo9Jq/t/9FjcYAideif0mg+qe2jCUs= connectrpc.com/connect v1.21.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= -dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= -dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw= @@ -38,9 +36,6 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/ github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 h1:3JhDl+JysaO8nhNU1XMaw35VSGjV4IEQAefaG4Lyok4= github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347/go.mod h1:2ErI0aycD43Ufr6CFK5lT/NrHGmoZuVbn1nlPThw69o= github.com/ProtonMail/go-crypto v1.5.0 h1:sKmuvjOgsnrtpMvZ+84MCnTJCpjxZ1qCFn076lz1yT0= @@ -67,10 +62,6 @@ github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eT github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/cascadia v1.3.4 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg= github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -167,8 +158,6 @@ github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNU github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= -github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -190,8 +179,6 @@ github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2 github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI= github.com/editorconfig/editorconfig-core-go/v2 v2.6.5 h1:MTcuJQkIFRLfNn9FfAvdO0p3FhNyaCf+IGTwy0TxX6E= github.com/editorconfig/editorconfig-core-go/v2 v2.6.5/go.mod h1:SizrS3EM1vFF0v/JZlJ5LfK6tXhwln2823YZwNh/vBE= -github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= -github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA= github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= @@ -199,8 +186,6 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTe github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk= github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= @@ -215,8 +200,6 @@ github.com/getkin/kin-openapi v0.149.0 h1:ZbhmVJ4yq5RZDUsyP8lcBcGMsjsaTqXEFt6isd github.com/getkin/kin-openapi v0.149.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A= -github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= -github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ= github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= @@ -230,14 +213,6 @@ github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUD github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.9.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA= -github.com/go-git/go-billy/v5 v5.9.1/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY= -github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs= @@ -282,8 +257,6 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -361,8 +334,6 @@ github.com/huandu/xstrings v1.6.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs= github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -384,8 +355,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= -github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= @@ -503,8 +472,6 @@ github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= -github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= -github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -550,11 +517,8 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= -github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= -github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik= github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= @@ -598,8 +562,6 @@ github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pc github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= @@ -650,7 +612,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= @@ -667,7 +628,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= @@ -687,18 +647,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -749,8 +705,6 @@ gopkg.in/ini.v1 v1.67.3 h1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw= gopkg.in/ini.v1 v1.67.3/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/models/renderhelper/commit_checker.go b/models/renderhelper/commit_checker.go index ff0717777dc..fa1079794dd 100644 --- a/models/renderhelper/commit_checker.go +++ b/models/renderhelper/commit_checker.go @@ -50,7 +50,7 @@ func (c *commitChecker) IsCommitIDExisting(commitID string) bool { c.gitRepo, c.gitRepoCloser = r, closer } - exist = c.gitRepo.IsReferenceExist(c.ctx, commitID) // Don't use IsObjectExist since it doesn't support short hashes with gogit edition. + exist = c.gitRepo.IsReferenceExist(c.ctx, commitID) c.commitCache[commitID] = exist return exist } diff --git a/models/unittest/fscopy.go b/models/unittest/fscopy.go index 62743b6acab..7775e75f616 100644 --- a/models/unittest/fscopy.go +++ b/models/unittest/fscopy.go @@ -60,7 +60,6 @@ func SyncDirs(srcPath, destPath string) error { } // the keep file is used to keep the directory in a git repository, it doesn't need to be synced - // and go-git doesn't work with the ".keep" file (it would report errors like "ref is empty") const keepFile = ".keep" // find and delete all untracked files diff --git a/modules/git/blame_sha256_test.go b/modules/git/blame_sha256_test.go index 3a17f8d0de6..5b6004cff20 100644 --- a/modules/git/blame_sha256_test.go +++ b/modules/git/blame_sha256_test.go @@ -15,11 +15,6 @@ func TestReadingBlameOutputSha256(t *testing.T) { setting.AppDataPath = t.TempDir() ctx := t.Context() - if DefaultFeatures().UsingGogit { - t.Skip("Skipping test since gogit does not support sha256") - return - } - t.Run("Without .git-blame-ignore-revs", func(t *testing.T) { storage := mockRepository("repo5_pulls_sha256") repo, err := OpenRepository(ctx, storage) diff --git a/modules/git/blob.go b/modules/git/blob.go index 8118eadccf1..59825ebc3b9 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -12,11 +12,10 @@ import ( "io" "strings" + "gitea.dev/modules/log" "gitea.dev/modules/util" ) -// This file contains common functions between the gogit and !gogit variants for git Blobs - // Name returns name of the tree entry this blob object was created from (or empty string) func (b *Blob) Name() string { return b.name @@ -114,3 +113,98 @@ loop: _ = encoder.Close() return base64buf.String(), nil } + +// Blob represents a Git object. +type Blob struct { + ID ObjectID + + gotSize bool + size int64 + name string + repo *Repository +} + +// DataAsync gets a ReadCloser for the contents of a blob without reading it all. +// Calling the Close function on the result will discard all unread output. +func (b *Blob) DataAsync(ctx context.Context) (_ io.ReadCloser, retErr error) { + batch, cancel, err := b.repo.CatFileBatch() + if err != nil { + return nil, err + } + defer func() { + // if there was an error, cancel the batch right away, + // otherwise let the caller close it + if retErr != nil { + cancel() + } + }() + + info, contentReader, err := batch.QueryContent(b.ID.String()) + if err != nil { + return nil, err + } + b.gotSize = true + b.size = info.Size + return &blobReader{ + rd: contentReader, + n: info.Size, + cancel: cancel, + }, nil +} + +// Size returns the uncompressed size of the blob +func (b *Blob) Size(ctx context.Context) int64 { + if b.gotSize { + return b.size + } + + batch, cancel, err := b.repo.CatFileBatch() + if err != nil { + log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.LogString(), err) + return 0 + } + defer cancel() + info, err := batch.QueryInfo(b.ID.String()) + if err != nil { + log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.LogString(), err) + return 0 + } + b.gotSize = true + b.size = info.Size + return b.size +} + +type blobReader struct { + rd BufferedReader + n int64 + cancel func() +} + +func (b *blobReader) Read(p []byte) (n int, err error) { + if b.n <= 0 { + return 0, io.EOF + } + if int64(len(p)) > b.n { + p = p[0:b.n] + } + n, err = b.rd.Read(p) + b.n -= int64(n) + return n, err +} + +// Close implements io.Closer +func (b *blobReader) Close() error { + if b.rd == nil { + return nil + } + + defer b.cancel() + + if err := DiscardFull(b.rd, b.n+1); err != nil { + return err + } + + b.rd = nil + + return nil +} diff --git a/modules/git/blob_gogit.go b/modules/git/blob_gogit.go deleted file mode 100644 index e67b6c2a39f..00000000000 --- a/modules/git/blob_gogit.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - "io" - - "gitea.dev/modules/log" - - "github.com/go-git/go-git/v5/plumbing" -) - -// Blob represents a Git object. -type Blob struct { - ID ObjectID - repo *Repository - name string -} - -func (b *Blob) gogitEncodedObj() (plumbing.EncodedObject, error) { - return b.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, plumbing.Hash(b.ID.RawValue())) -} - -// DataAsync gets a ReadCloser for the contents of a blob without reading it all. -// Calling the Close function on the result will discard all unread output. -func (b *Blob) DataAsync(_ context.Context) (io.ReadCloser, error) { - obj, err := b.gogitEncodedObj() - if err != nil { - return nil, err - } - return obj.Reader() -} - -// Size returns the uncompressed size of the blob -func (b *Blob) Size(_ context.Context) int64 { - obj, err := b.gogitEncodedObj() - if err != nil { - log.Error("Error getting gogit encoded object for blob %s(%s): %v", b.name, b.ID.String(), err) - return 0 - } - return obj.Size() -} diff --git a/modules/git/blob_nogogit.go b/modules/git/blob_nogogit.go deleted file mode 100644 index 10978002bfa..00000000000 --- a/modules/git/blob_nogogit.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "context" - "io" - - "gitea.dev/modules/log" -) - -// Blob represents a Git object. -type Blob struct { - ID ObjectID - - gotSize bool - size int64 - name string - repo *Repository -} - -// DataAsync gets a ReadCloser for the contents of a blob without reading it all. -// Calling the Close function on the result will discard all unread output. -func (b *Blob) DataAsync(ctx context.Context) (_ io.ReadCloser, retErr error) { - batch, cancel, err := b.repo.CatFileBatch() - if err != nil { - return nil, err - } - defer func() { - // if there was an error, cancel the batch right away, - // otherwise let the caller close it - if retErr != nil { - cancel() - } - }() - - info, contentReader, err := batch.QueryContent(b.ID.String()) - if err != nil { - return nil, err - } - b.gotSize = true - b.size = info.Size - return &blobReader{ - rd: contentReader, - n: info.Size, - cancel: cancel, - }, nil -} - -// Size returns the uncompressed size of the blob -func (b *Blob) Size(ctx context.Context) int64 { - if b.gotSize { - return b.size - } - - batch, cancel, err := b.repo.CatFileBatch() - if err != nil { - log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.LogString(), err) - return 0 - } - defer cancel() - info, err := batch.QueryInfo(b.ID.String()) - if err != nil { - log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.LogString(), err) - return 0 - } - b.gotSize = true - b.size = info.Size - return b.size -} - -type blobReader struct { - rd BufferedReader - n int64 - cancel func() -} - -func (b *blobReader) Read(p []byte) (n int, err error) { - if b.n <= 0 { - return 0, io.EOF - } - if int64(len(p)) > b.n { - p = p[0:b.n] - } - n, err = b.rd.Read(p) - b.n -= int64(n) - return n, err -} - -// Close implements io.Closer -func (b *blobReader) Close() error { - if b.rd == nil { - return nil - } - - defer b.cancel() - - if err := DiscardFull(b.rd, b.n+1); err != nil { - return err - } - - b.rd = nil - - return nil -} diff --git a/modules/git/commit_convert_gogit.go b/modules/git/commit_convert_gogit.go deleted file mode 100644 index 767fbe74900..00000000000 --- a/modules/git/commit_convert_gogit.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2018 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "fmt" - "strings" - - "github.com/go-git/go-git/v5/plumbing/object" -) - -func convertPGPSignature(c *object.Commit) *CommitSignature { - if c.PGPSignature == "" { - return nil - } - - var w strings.Builder - var err error - - if _, err = fmt.Fprintf(&w, "tree %s\n", c.TreeHash.String()); err != nil { - return nil - } - - for _, parent := range c.ParentHashes { - if _, err = fmt.Fprintf(&w, "parent %s\n", parent.String()); err != nil { - return nil - } - } - - if _, err = fmt.Fprint(&w, "author "); err != nil { - return nil - } - - if err = c.Author.Encode(&w); err != nil { - return nil - } - - if _, err = fmt.Fprint(&w, "\ncommitter "); err != nil { - return nil - } - - if err = c.Committer.Encode(&w); err != nil { - return nil - } - - if c.Encoding != "" && c.Encoding != "UTF-8" { - if _, err = fmt.Fprintf(&w, "\nencoding %s\n", c.Encoding); err != nil { - return nil - } - } - - if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil { - return nil - } - - return &CommitSignature{ - Signature: c.PGPSignature, - Payload: w.String(), - } -} - -func convertCommit(c *object.Commit) *Commit { - return &Commit{ - ID: ParseGogitHash(c.Hash), - TreeID: ParseGogitHash(c.TreeHash), - CommitMessage: CommitMessage{MessageRaw: c.Message}, - Committer: &c.Committer, - Author: &c.Author, - Signature: convertPGPSignature(c), - Parents: ParseGogitHashArray(c.ParentHashes), - } -} diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index b34ed7716de..b8aa906e4b5 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -113,3 +113,36 @@ func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string return results, unHitEntryPaths, nil } + +// GetLastCommitForPaths returns last commit information +func GetLastCommitForPaths(ctx context.Context, gitRepo *Repository, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) { + // We read backwards from the commit to obtain all of the commits + revs, err := walkGitLog(ctx, gitRepo, commit, treePath, paths...) + if err != nil { + return nil, err + } + + commitsMap := map[string]*Commit{} + commitsMap[commit.ID.String()] = commit + + commitCommits := map[string]*Commit{} + for path, commitID := range revs { + if len(commitID) == 0 { + continue + } + + c, ok := commitsMap[commitID] + if ok { + commitCommits[path] = c + continue + } + + c, err := gitRepo.GetCommit(ctx, commitID) // Ensure the commit exists in the repository + if err != nil { + return nil, err + } + commitCommits[path] = c + } + + return commitCommits, nil +} diff --git a/modules/git/commit_info_gogit.go b/modules/git/commit_info_gogit.go deleted file mode 100644 index 13ad029cc51..00000000000 --- a/modules/git/commit_info_gogit.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - "path" - - "github.com/emirpasic/gods/trees/binaryheap" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph" -) - -type commitAndPaths struct { - commit cgobject.CommitNode - // Paths that are still on the branch represented by commit - paths []string - // Set of hashes for the paths - hashes map[string]plumbing.Hash -} - -func getCommitTree(c cgobject.CommitNode, treePath string) (*object.Tree, error) { - tree, err := c.Tree() - if err != nil { - return nil, err - } - - // Optimize deep traversals by focusing only on the specific tree - if treePath != "" { - tree, err = tree.Tree(treePath) - if err != nil { - return nil, err - } - } - - return tree, nil -} - -func getFileHashes(c cgobject.CommitNode, treePath string, paths []string) (map[string]plumbing.Hash, error) { - tree, err := getCommitTree(c, treePath) - if err == object.ErrDirectoryNotFound { - // The whole tree didn't exist, so return empty map - return make(map[string]plumbing.Hash), nil - } - if err != nil { - return nil, err - } - - hashes := make(map[string]plumbing.Hash) - for _, path := range paths { - if path != "" { - entry, err := tree.FindEntry(path) - if err == nil { - hashes[path] = entry.Hash - } - } else { - hashes[path] = tree.Hash - } - } - - return hashes, nil -} - -// GetLastCommitForPaths returns last commit information -func GetLastCommitForPaths(ctx context.Context, gitRepo *Repository, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) { - commitNodeIndex, closer := gitRepo.CommitNodeIndex() - defer closer() - - c, err := commitNodeIndex.Get(plumbing.Hash(commit.ID.RawValue())) - if err != nil { - return nil, err - } - return getLastCommitForPathsByCommitNode(ctx, gitRepo, c, treePath, paths) -} - -func getLastCommitForPathsByCommitNode(ctx context.Context, gitRepo *Repository, c cgobject.CommitNode, treePath string, paths []string) (map[string]*Commit, error) { - refSha := c.ID().String() - - // We do a tree traversal with nodes sorted by commit time - heap := binaryheap.NewWith(func(a, b any) int { - if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) { //nolint:forcetypeassert // this heap only ever holds *commitAndPaths - return 1 - } - return -1 - }) - - resultNodes := make(map[string]cgobject.CommitNode) - initialHashes, err := getFileHashes(c, treePath, paths) - if err != nil { - return nil, err - } - - // Start search from the root commit and with full set of paths - heap.Push(&commitAndPaths{c, paths, initialHashes}) -heaploop: - for { - select { - case <-ctx.Done(): - if ctx.Err() == context.DeadlineExceeded { - break heaploop - } - return nil, ctx.Err() - default: - } - cIn, ok := heap.Pop() - if !ok { - break - } - current := cIn.(*commitAndPaths) //nolint:forcetypeassert // this heap only ever holds *commitAndPaths - - // Load the parent commits for the one we are currently examining - numParents := current.commit.NumParents() - var parents []cgobject.CommitNode - for i := range numParents { - parent, err := current.commit.ParentNode(i) - if err != nil { - break - } - parents = append(parents, parent) - } - - // Examine the current commit and set of interesting paths - pathUnchanged := make([]bool, len(current.paths)) - parentHashes := make([]map[string]plumbing.Hash, len(parents)) - for j, parent := range parents { - parentHashes[j], err = getFileHashes(parent, treePath, current.paths) - if err != nil { - break - } - - for i, path := range current.paths { - if parentHashes[j][path] == current.hashes[path] { - pathUnchanged[i] = true - } - } - } - - var remainingPaths []string - for i, pth := range current.paths { - // The results could already contain some newer change for the same path, - // so don't override that and bail out on the file early. - if resultNodes[pth] == nil { - if pathUnchanged[i] { - // The path existed with the same hash in at least one parent so it could - // not have been changed in this commit directly. - remainingPaths = append(remainingPaths, pth) - } else { - // There are few possible cases how can we get here: - // - The path didn't exist in any parent, so it must have been created by - // this commit. - // - The path did exist in the parent commit, but the hash of the file has - // changed. - // - We are looking at a merge commit and the hash of the file doesn't - // match any of the hashes being merged. This is more common for directories, - // but it can also happen if a file is changed through conflict resolution. - resultNodes[pth] = current.commit - if err := gitRepo.LastCommitCache.Put(refSha, path.Join(treePath, pth), current.commit.ID().String()); err != nil { - return nil, err - } - } - } - } - - if len(remainingPaths) > 0 { - // Add the parent nodes along with remaining paths to the heap for further - // processing. - for j, parent := range parents { - // Combine remainingPath with paths available on the parent branch - // and make union of them - remainingPathsForParent := make([]string, 0, len(remainingPaths)) - newRemainingPaths := make([]string, 0, len(remainingPaths)) - for _, path := range remainingPaths { - if parentHashes[j][path] == current.hashes[path] { - remainingPathsForParent = append(remainingPathsForParent, path) - } else { - newRemainingPaths = append(newRemainingPaths, path) - } - } - - if remainingPathsForParent != nil { - heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]}) - } - - if len(newRemainingPaths) == 0 { - break - } - remainingPaths = newRemainingPaths - } - } - } - - // Post-processing - result := make(map[string]*Commit) - for path, commitNode := range resultNodes { - commit, err := commitNode.Commit() - if err != nil { - return nil, err - } - result[path] = convertCommit(commit) - } - - return result, nil -} diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go deleted file mode 100644 index c6ceb78d916..00000000000 --- a/modules/git/commit_info_nogogit.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "context" -) - -// GetLastCommitForPaths returns last commit information -func GetLastCommitForPaths(ctx context.Context, gitRepo *Repository, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) { - // We read backwards from the commit to obtain all of the commits - revs, err := walkGitLog(ctx, gitRepo, commit, treePath, paths...) - if err != nil { - return nil, err - } - - commitsMap := map[string]*Commit{} - commitsMap[commit.ID.String()] = commit - - commitCommits := map[string]*Commit{} - for path, commitID := range revs { - if len(commitID) == 0 { - continue - } - - c, ok := commitsMap[commitID] - if ok { - commitCommits[path] = c - continue - } - - c, err := gitRepo.GetCommit(ctx, commitID) // Ensure the commit exists in the repository - if err != nil { - return nil, err - } - commitCommits[path] = c - } - - return commitCommits, nil -} diff --git a/modules/git/commit_info_nogogit_test.go b/modules/git/commit_info_nogogit_test.go deleted file mode 100644 index 19bab4d4166..00000000000 --- a/modules/git/commit_info_nogogit_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "context" - "path/filepath" - "testing" - "time" - - "gitea.dev/modules/git/gitrepo" - "gitea.dev/modules/test" - "gitea.dev/modules/util" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) { - repoPath, _ := filepath.Abs(filepath.Join(testReposDir, "repo1_bare")) - repo, err := OpenRepository(t.Context(), gitrepo.RepositoryManaged("dummy", repoPath)) - require.NoError(t, err) - defer repo.Close() - - commit, err := repo.GetCommit(t.Context(), "feaf4ba6bc635fec442f46ddd4512416ec43c2c2") - require.NoError(t, err) - entries, err := commit.Tree().ListEntries(t.Context(), repo) - require.NoError(t, err) - - countCommitInfosCommit := func(infos []CommitInfo) (nilCommits, nonNilCommits int) { - for _, info := range infos { - nilCommits += util.Iif(info.Commit == nil, 1, 0) - nonNilCommits += util.Iif(info.Commit != nil, 1, 0) - } - return nilCommits, nonNilCommits - } - - ctx, cancel := context.WithCancel(t.Context()) - defer test.MockVariableValue(&walkGitLogDebugBeforeNext)() - - walkGitLogDebugBeforeNext = cancel - commitInfos, _, err := entries.GetCommitsInfo(ctx, time.Second, "/any/repo-link", repo, commit, "") - assert.NoError(t, err) - nilCommits, nonNilCommits := countCommitInfosCommit(commitInfos) - assert.Equal(t, 0, nonNilCommits) // no commit info due to canceled (or deadline-exceeded) context - assert.Equal(t, 3, nilCommits) - - walkGitLogDebugBeforeNext = nil - commitInfos, _, err = entries.GetCommitsInfo(t.Context(), time.Second, "/any/repo-link", repo, commit, "") - assert.NoError(t, err) - nilCommits, nonNilCommits = countCommitInfosCommit(commitInfos) - assert.Equal(t, 3, nonNilCommits) - assert.Equal(t, 0, nilCommits) -} diff --git a/modules/git/commit_info_test.go b/modules/git/commit_info_test.go index e29d689b8c3..0c30bc939bc 100644 --- a/modules/git/commit_info_test.go +++ b/modules/git/commit_info_test.go @@ -4,11 +4,14 @@ package git import ( + "context" "path/filepath" "testing" "time" "gitea.dev/modules/git/gitrepo" + "gitea.dev/modules/test" + "gitea.dev/modules/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -164,3 +167,40 @@ func TestEntries_GetCommitsInfo(t *testing.T) { assert.Nil(t, cisf.SubmoduleWebLinkTree(t.Context())) }) } + +func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) { + repoPath, _ := filepath.Abs(filepath.Join(testReposDir, "repo1_bare")) + repo, err := OpenRepository(t.Context(), gitrepo.RepositoryManaged("dummy", repoPath)) + require.NoError(t, err) + defer repo.Close() + + commit, err := repo.GetCommit(t.Context(), "feaf4ba6bc635fec442f46ddd4512416ec43c2c2") + require.NoError(t, err) + entries, err := commit.Tree().ListEntries(t.Context(), repo) + require.NoError(t, err) + + countCommitInfosCommit := func(infos []CommitInfo) (nilCommits, nonNilCommits int) { + for _, info := range infos { + nilCommits += util.Iif(info.Commit == nil, 1, 0) + nonNilCommits += util.Iif(info.Commit != nil, 1, 0) + } + return nilCommits, nonNilCommits + } + + ctx, cancel := context.WithCancel(t.Context()) + defer test.MockVariableValue(&walkGitLogDebugBeforeNext)() + + walkGitLogDebugBeforeNext = cancel + commitInfos, _, err := entries.GetCommitsInfo(ctx, time.Second, "/any/repo-link", repo, commit, "") + assert.NoError(t, err) + nilCommits, nonNilCommits := countCommitInfosCommit(commitInfos) + assert.Equal(t, 0, nonNilCommits) // no commit info due to canceled (or deadline-exceeded) context + assert.Equal(t, 3, nilCommits) + + walkGitLogDebugBeforeNext = nil + commitInfos, _, err = entries.GetCommitsInfo(t.Context(), time.Second, "/any/repo-link", repo, commit, "") + assert.NoError(t, err) + nilCommits, nonNilCommits = countCommitInfosCommit(commitInfos) + assert.Equal(t, 3, nonNilCommits) + assert.Equal(t, 0, nilCommits) +} diff --git a/modules/git/commit_sha256_test.go b/modules/git/commit_sha256_test.go index e30f559c4f6..e725eb2b38e 100644 --- a/modules/git/commit_sha256_test.go +++ b/modules/git/commit_sha256_test.go @@ -1,8 +1,6 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !gogit - package git import ( diff --git a/modules/git/git.go b/modules/git/git.go index d2ab08061b4..acecfee1632 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -29,7 +29,6 @@ const RequiredVersion = "2.25.0" // the minimum Git version required type Features struct { gitVersion *version.Version - UsingGogit bool SupportProcReceive bool // >= 2.29 SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’ SupportedObjectFormats []ObjectFormat // sha1, sha256 @@ -79,9 +78,9 @@ func loadGitVersionFeatures() (*Features, error) { return nil, err } - features := &Features{gitVersion: ver, UsingGogit: isGogit} + features := &Features{gitVersion: ver} features.SupportProcReceive = features.CheckVersionAtLeast("2.29") - features.SupportHashSha256 = features.CheckVersionAtLeast("2.42") && !isGogit + features.SupportHashSha256 = features.CheckVersionAtLeast("2.42") features.SupportedObjectFormats = []ObjectFormat{Sha1ObjectFormat} if features.SupportHashSha256 { features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat) diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go index 03fe1295319..e3130f4efde 100644 --- a/modules/git/last_commit_cache.go +++ b/modules/git/last_commit_cache.go @@ -78,3 +78,43 @@ func (c *LastCommitCache) GetCommitByPath(ctx context.Context, entryCommitID Obj return lastCommit, nil } + +// CacheCommit will cache the commit from the gitRepository +func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error { + return c.recursiveCache(ctx, gitRepo, c.Tree(), "", 1) +} + +func (c *Commit) recursiveCache(ctx context.Context, gitRepo *Repository, tree *Tree, treePath string, level int) error { + if level == 0 { + return nil + } + entries, err := tree.ListEntries(ctx, gitRepo) + if err != nil { + return err + } + + entryPaths := make([]string, len(entries)) + for i, entry := range entries { + entryPaths[i] = entry.Name() + } + + _, err = walkGitLog(ctx, gitRepo, c, treePath, entryPaths...) + if err != nil { + return err + } + + for _, treeEntry := range entries { + // entryMap won't contain "" therefore skip this. + if treeEntry.IsDir() { + subTree, err := tree.SubTree(ctx, gitRepo, treeEntry.Name()) + if err != nil { + return err + } + if err := c.recursiveCache(ctx, gitRepo, subTree, treeEntry.Name(), level-1); err != nil { + return err + } + } + } + + return nil +} diff --git a/modules/git/last_commit_cache_gogit.go b/modules/git/last_commit_cache_gogit.go deleted file mode 100644 index 063bd9a8f7f..00000000000 --- a/modules/git/last_commit_cache_gogit.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - - "github.com/go-git/go-git/v5/plumbing" - cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph" -) - -// CacheCommit will cache the commit from the gitRepository -func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error { - commitNodeIndex, closer := gitRepo.CommitNodeIndex() - defer closer() - - index, err := commitNodeIndex.Get(plumbing.Hash(c.ID.RawValue())) - if err != nil { - return err - } - - return c.recursiveCache(ctx, gitRepo, index, c.Tree(), "", 1) -} - -func (c *Commit) recursiveCache(ctx context.Context, gitRepo *Repository, index cgobject.CommitNode, tree *Tree, treePath string, level int) error { - if level == 0 { - return nil - } - - entries, err := tree.ListEntries(ctx, gitRepo) - if err != nil { - return err - } - - entryPaths := make([]string, len(entries)) - entryMap := make(map[string]*TreeEntry) - for i, entry := range entries { - entryPaths[i] = entry.Name() - entryMap[entry.Name()] = entry - } - - commits, err := getLastCommitForPathsByCommitNode(ctx, gitRepo, index, treePath, entryPaths) - if err != nil { - return err - } - - for entry := range commits { - if entryMap[entry].IsDir() { - subTree, err := tree.SubTree(ctx, gitRepo, entry) - if err != nil { - return err - } - if err := c.recursiveCache(ctx, gitRepo, index, subTree, entry, level-1); err != nil { - return err - } - } - } - - return nil -} diff --git a/modules/git/last_commit_cache_nogogit.go b/modules/git/last_commit_cache_nogogit.go deleted file mode 100644 index 68d80690dcf..00000000000 --- a/modules/git/last_commit_cache_nogogit.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "context" -) - -// CacheCommit will cache the commit from the gitRepository -func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error { - return c.recursiveCache(ctx, gitRepo, c.Tree(), "", 1) -} - -func (c *Commit) recursiveCache(ctx context.Context, gitRepo *Repository, tree *Tree, treePath string, level int) error { - if level == 0 { - return nil - } - entries, err := tree.ListEntries(ctx, gitRepo) - if err != nil { - return err - } - - entryPaths := make([]string, len(entries)) - for i, entry := range entries { - entryPaths[i] = entry.Name() - } - - _, err = walkGitLog(ctx, gitRepo, c, treePath, entryPaths...) - if err != nil { - return err - } - - for _, treeEntry := range entries { - // entryMap won't contain "" therefore skip this. - if treeEntry.IsDir() { - subTree, err := tree.SubTree(ctx, gitRepo, treeEntry.Name()) - if err != nil { - return err - } - if err := c.recursiveCache(ctx, gitRepo, subTree, treeEntry.Name(), level-1); err != nil { - return err - } - } - } - - return nil -} diff --git a/modules/git/log_name_status_nogogit.go b/modules/git/log_name_status.go similarity index 99% rename from modules/git/log_name_status_nogogit.go rename to modules/git/log_name_status.go index a6469fadaa6..d84f70f2d30 100644 --- a/modules/git/log_name_status_nogogit.go +++ b/modules/git/log_name_status.go @@ -1,8 +1,6 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !gogit - package git import ( diff --git a/modules/git/object_id_gogit.go b/modules/git/object_id_gogit.go deleted file mode 100644 index db4c4ae0bd2..00000000000 --- a/modules/git/object_id_gogit.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT -//go:build gogit - -package git - -import ( - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/hash" -) - -func ParseGogitHash(h plumbing.Hash) ObjectID { - switch hash.Size { - case 20: - return Sha1ObjectFormat.MustID(h[:]) - case 32: - return Sha256ObjectFormat.MustID(h[:]) - } - - return nil -} - -func ParseGogitHashArray(objectIDs []plumbing.Hash) []ObjectID { - ret := make([]ObjectID, len(objectIDs)) - for i, h := range objectIDs { - ret[i] = ParseGogitHash(h) - } - - return ret -} diff --git a/modules/git/parse_treeentry.go b/modules/git/parse_treeentry.go index 4a00cc1fb1f..23d59c19233 100644 --- a/modules/git/parse_treeentry.go +++ b/modules/git/parse_treeentry.go @@ -43,8 +43,6 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { return entries, nil } -var _ = catBatchParseTreeEntries // bypass "unused" lint because it is only used by "nogogit" - func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd BufferedReader, sz int64) ([]*TreeEntry, error) { entries := make([]*TreeEntry, 0, 10) diff --git a/modules/git/pipeline/namerev.go b/modules/git/pipeline/namerev.go index c39ccd0ef53..054b1b2bbbd 100644 --- a/modules/git/pipeline/namerev.go +++ b/modules/git/pipeline/namerev.go @@ -16,7 +16,6 @@ import ( ) func fillResultNameRev(ctx context.Context, repo git.RepositoryFacade, 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").WithRepo(repo) stdin, stdinClose := cmd.MakeStdinPipe() diff --git a/modules/git/repo_base_nogogit.go b/modules/git/repo_base.go similarity index 88% rename from modules/git/repo_base_nogogit.go rename to modules/git/repo_base.go index 850a4d031c5..fd304ad5f03 100644 --- a/modules/git/repo_base_nogogit.go +++ b/modules/git/repo_base.go @@ -2,12 +2,8 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !gogit - package git -const isGogit = false - type Repository struct { RepositoryBase } diff --git a/modules/git/repo_base_gogit.go b/modules/git/repo_base_gogit.go deleted file mode 100644 index 83415005f1f..00000000000 --- a/modules/git/repo_base_gogit.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "errors" - "io" - "os" - "path/filepath" - "slices" - "strings" - - "gitea.dev/modules/container" - "gitea.dev/modules/git/gitrepo" - "gitea.dev/modules/setting" - - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/osfs" - gogit "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/cache" - "github.com/go-git/go-git/v5/storage/filesystem" - "github.com/go-git/go-git/v5/storage/filesystem/dotgit" -) - -const isGogit = true - -type Repository struct { - RepositoryBase - - gogitRepo *gogit.Repository - gogitStorage *reindexingStorage -} - -// reindexingStorage reloads the pack index when git added or removed packs after go-git loaded it -// https://github.com/go-git/go-git/issues/2439 https://github.com/go-git/go-git/issues/1623 -// FIXME: gogit workaround, remove with the gogit build -type reindexingStorage struct { - *filesystem.Storage - packs []plumbing.Hash -} - -func isRepackError(err error) bool { - return errors.Is(err, plumbing.ErrObjectNotFound) || errors.Is(err, dotgit.ErrPackfileNotFound) || errors.Is(err, os.ErrNotExist) -} - -// retry reruns fn while a concurrent repack keeps changing the packs -func (s *reindexingStorage) retry(fn func() error) error { - for { - err := fn() - if !isRepackError(err) { - return err - } - packs, _ := s.ObjectPacks() - if slices.Equal(packs, s.packs) { - return err - } - s.packs = packs - s.Reindex() - } -} - -func (s *reindexingStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (obj plumbing.EncodedObject, err error) { - err = s.retry(func() (err error) { - obj, err = s.Storage.EncodedObject(t, h) - return err - }) - if err != nil { - return nil, err - } - if _, ok := obj.(*plumbing.MemoryObject); ok { - return obj, nil - } - return &lazyObject{EncodedObject: obj, storage: s}, nil -} - -// lazyObject looks up a large object again when its file got removed before Reader reopened it -// FIXME: gogit workaround, remove with the gogit build -type lazyObject struct { - plumbing.EncodedObject - storage *reindexingStorage -} - -func (o *lazyObject) Reader() (rc io.ReadCloser, err error) { - rc, err = o.EncodedObject.Reader() - if !isRepackError(err) { - return rc, err - } - err = o.storage.retry(func() error { - obj, err := o.storage.Storage.EncodedObject(o.Type(), o.Hash()) - if err == nil { - o.EncodedObject = obj - rc, err = obj.Reader() - } - return err - }) - return rc, err -} - -// packIdxFS lists packs like git, only while their .idx exists -// FIXME: gogit workaround, remove with the gogit build -type packIdxFS struct { - billy.Filesystem -} - -func (f packIdxFS) ReadDir(dir string) ([]os.FileInfo, error) { - if dir != f.Join("objects", "pack") { - return f.Filesystem.ReadDir(dir) - } - dirFile, err := os.Open(filepath.Join(f.Root(), dir)) - if err != nil { - return nil, err - } - defer dirFile.Close() - infos, err := dirFile.Readdir(-1) // skips files removed before their lstat, unlike billy's ReadDir - if err != nil { - return nil, err - } - names := make(container.Set[string], len(infos)) - for _, info := range infos { - names.Add(info.Name()) - } - return slices.DeleteFunc(infos, func(info os.FileInfo) bool { - base, isPack := strings.CutSuffix(info.Name(), ".pack") - return isPack && !names.Contains(base+".idx") - }), nil -} - -func openRepositoryInternal(gitRepo *Repository) error { - repoPath := gitrepo.RepoLocalPath(gitRepo) - fs := osfs.New(repoPath) - _, err := fs.Stat(".git") - if err == nil { - fs, err = fs.Chroot(".git") - if err != nil { - return err - } - } - // the "clone --shared" repo doesn't work well with go-git AlternativeFS, https://github.com/go-git/go-git/issues/1006 - // so use "/" for AlternatesFS, I guess it is the same behavior as current nogogit (no limitation or check for the "objects/info/alternates" paths), trust the "clone" command executed by the server. - var altFs billy.Filesystem - if setting.IsWindows { - altFs = osfs.New(filepath.VolumeName(setting.RepoRootPath) + "\\") // TODO: does it really work for Windows? Need some time to check. - } else { - altFs = osfs.New("/") - } - gitRepo.objectFormatCache = ParseGogitHash(plumbing.ZeroHash).Type() - storage := filesystem.NewStorageWithOptions(packIdxFS{fs}, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true, LargeObjectThreshold: setting.Git.LargeObjectThreshold, AlternatesFS: altFs}) - packs, _ := storage.ObjectPacks() - gitRepo.gogitStorage = &reindexingStorage{Storage: storage, packs: packs} - gitRepo.gogitRepo, err = gogit.Open(gitRepo.gogitStorage, fs) - if err != nil { - _ = gitRepo.gogitStorage.Close() - return err - } - return nil -} - -func (repo *Repository) closeInternal() error { - if repo.gogitStorage == nil { - return nil - } - err := repo.gogitStorage.Close() - repo.gogitStorage = nil - return err -} - -// GoGitRepo gets the go-git repo representation -func (repo *Repository) GoGitRepo() *gogit.Repository { - return repo.gogitRepo -} diff --git a/modules/git/repo_base_nogogit_test.go b/modules/git/repo_base_test.go similarity index 97% rename from modules/git/repo_base_nogogit_test.go rename to modules/git/repo_base_test.go index ebd8d1b766e..0f494022065 100644 --- a/modules/git/repo_base_nogogit_test.go +++ b/modules/git/repo_base_test.go @@ -1,8 +1,6 @@ // Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !gogit - package git import ( diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 554099498d4..73b11b03029 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -5,9 +5,13 @@ package git import ( + "bufio" "context" + "io" + "strings" "gitea.dev/modules/git/gitcmd" + "gitea.dev/modules/log" ) // BranchPrefix base dir of the branch information file store on git @@ -24,3 +28,170 @@ func (repo *Repository) AddRemote(ctx context.Context, name, url string, fetch b RunStdString(ctx) return err } + +// IsObjectExist returns true if the given object exists in the repository. +// FIXME: this function doesn't seem right, it is only used by GarbageCollectLFSMetaObjectsForRepo +func (repo *Repository) IsObjectExist(ctx context.Context, name string) bool { + if name == "" { + return false + } + + batch, cancel, err := repo.CatFileBatch() + if err != nil { + log.Debug("Error opening CatFileBatch %v", err) + return false + } + defer cancel() + info, err := batch.QueryInfo(name) + if err != nil { + log.Debug("Error checking object info %v", err) + return false + } + return strings.HasPrefix(info.ID, name) // FIXME: this logic doesn't seem right, why "HasPrefix" +} + +// IsReferenceExist returns true if given reference exists in the repository. +func (repo *Repository) IsReferenceExist(ctx context.Context, name string) bool { + if name == "" { + return false + } + + batch, cancel, err := repo.CatFileBatch() + if err != nil { + log.Error("Error opening CatFileBatch %v", err) + return false + } + defer cancel() + _, err = batch.QueryInfo(name) + return err == nil +} + +// IsBranchExist returns true if given branch exists in current repository. +func (repo *Repository) IsBranchExist(ctx context.Context, name string) bool { + if repo == nil || name == "" { + return false + } + + return repo.IsReferenceExist(ctx, BranchPrefix+name) +} + +// GetBranchNames returns branches from the repository, skipping "skip" initial branches and +// returning at most "limit" branches, or all branches if "limit" is 0. +func (repo *Repository) GetBranchNames(ctx context.Context, skip, limit int) ([]string, int, error) { + return callShowRef(ctx, repo, BranchPrefix, gitcmd.TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit) +} + +// WalkReferences walks all the references from the repository +// refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty. +func (repo *Repository) WalkReferences(ctx context.Context, refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { + var args gitcmd.TrustedCmdArgs + switch refType { + case ObjectTag: + args = gitcmd.TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"} + case ObjectBranch: + args = gitcmd.TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"} + } + + return WalkShowRef(ctx, repo, args, skip, limit, walkfn) +} + +// callShowRef return refs, if limit = 0 it will not limit +func callShowRef(ctx context.Context, repo RepositoryFacade, trimPrefix string, extraArgs gitcmd.TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) { + countAll, err = WalkShowRef(ctx, repo, extraArgs, skip, limit, func(_, branchName string) error { + branchName = strings.TrimPrefix(branchName, trimPrefix) + branchNames = append(branchNames, branchName) + + return nil + }) + return branchNames, countAll, err +} + +func WalkShowRef(ctx context.Context, repo RepositoryFacade, extraArgs gitcmd.TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { + i := 0 + args := gitcmd.TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} + args = append(args, extraArgs...) + cmd := gitcmd.NewCommand(args...) + stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe() + defer stdoutReaderClose() + cmd.WithRepo(repo). + WithPipelineFunc(func(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: + // SP 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, ok := gitcmd.UnwrapPipelineError(err); ok { + return i, errPipeline // keep the old behavior: return pipeline error directly + } + return i, err +} + +// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash +func (repo *Repository) GetRefsBySha(ctx context.Context, sha, prefix string) ([]string, error) { + var revList []string + _, err := WalkShowRef(ctx, repo, nil, 0, 0, func(walkSha, refname string) error { + if walkSha == sha && strings.HasPrefix(refname, prefix) { + revList = append(revList, refname) + } + return nil + }) + return revList, err +} diff --git a/modules/git/repo_branch_gogit.go b/modules/git/repo_branch_gogit.go deleted file mode 100644 index a791c72b181..00000000000 --- a/modules/git/repo_branch_gogit.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2018 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - "sort" - "strings" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/storer" -) - -// IsObjectExist returns true if the given object exists in the repository. -// FIXME: Inconsistent behavior with nogogit edition -// Unlike the implementation of IsObjectExist in nogogit edition, it does not support short hashes here. -// For example, IsObjectExist("153f451") will return false, but it will return true in nogogit edition. -// To fix this, the solution could be adding support for short hashes in gogit edition if it's really needed. -func (repo *Repository) IsObjectExist(_ context.Context, name string) bool { - if name == "" { - return false - } - - _, err := repo.gogitRepo.Object(plumbing.AnyObject, plumbing.NewHash(name)) - return err == nil -} - -// IsReferenceExist returns true if given reference exists in the repository. -// FIXME: Inconsistent behavior with nogogit edition -// Unlike the implementation of IsObjectExist in nogogit edition, it does not support blob hashes here. -// For example, IsObjectExist([existing_blob_hash]) will return false, but it will return true in nogogit edition. -// To fix this, the solution could be refusing to support blob hashes in nogogit edition since a blob hash is not a reference. -func (repo *Repository) IsReferenceExist(_ context.Context, name string) bool { - if name == "" { - return false - } - - _, err := repo.gogitRepo.ResolveRevision(plumbing.Revision(name)) - - return err == nil -} - -// IsBranchExist returns true if given branch exists in current repository. -func (repo *Repository) IsBranchExist(_ context.Context, name string) bool { - if name == "" { - return false - } - reference, err := repo.gogitRepo.Reference(plumbing.ReferenceName(BranchPrefix+name), true) - if err != nil { - return false - } - return reference.Type() != plumbing.InvalidReference -} - -// GetBranches returns branches from the repository, skipping "skip" initial branches and -// returning at most "limit" branches, or all branches if "limit" is 0. -// Branches are returned with sort of `-committerdate` as the nogogit -// implementation. This requires full fetch, sort and then the -// skip/limit applies later as gogit returns in undefined order. -func (repo *Repository) GetBranchNames(_ context.Context, skip, limit int) ([]string, int, error) { - type BranchData struct { - name string - committerDate int64 - } - var branchData []BranchData - - branchIter, err := repo.gogitRepo.Branches() - if err != nil { - return nil, 0, err - } - - _ = branchIter.ForEach(func(branch *plumbing.Reference) error { - obj, err := repo.gogitRepo.CommitObject(branch.Hash()) - if err != nil { - // skip branch if can't find commit - return nil - } - - branchData = append(branchData, BranchData{strings.TrimPrefix(branch.Name().String(), BranchPrefix), obj.Committer.When.Unix()}) - return nil - }) - - sort.Slice(branchData, func(i, j int) bool { - return !(branchData[i].committerDate < branchData[j].committerDate) - }) - - var branchNames []string - maxPos := len(branchData) - if limit > 0 { - maxPos = min(skip+limit, maxPos) - } - for i := skip; i < maxPos; i++ { - branchNames = append(branchNames, branchData[i].name) - } - - return branchNames, len(branchData), nil -} - -// WalkReferences walks all the references from the repository -func (repo *Repository) WalkReferences(ctx context.Context, arg ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { - i := 0 - var iter storer.ReferenceIter - var err error - switch arg { - case ObjectTag: - iter, err = repo.gogitRepo.Tags() - case ObjectBranch: - iter, err = repo.gogitRepo.Branches() - default: - iter, err = repo.gogitRepo.References() - } - if err != nil { - return i, err - } - defer iter.Close() - - err = iter.ForEach(func(ref *plumbing.Reference) error { - if i < skip { - i++ - return nil - } - err := walkfn(ref.Hash().String(), string(ref.Name())) - i++ - if err != nil { - return err - } - if limit != 0 && i >= skip+limit { - return storer.ErrStop - } - return ctx.Err() - }) - return i, err -} - -// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash -func (repo *Repository) GetRefsBySha(ctx context.Context, sha, prefix string) ([]string, error) { - var revList []string - iter, err := repo.gogitRepo.References() - if err != nil { - return nil, err - } - err = iter.ForEach(func(ref *plumbing.Reference) error { - if ref.Hash().String() == sha && strings.HasPrefix(string(ref.Name()), prefix) { - revList = append(revList, string(ref.Name())) - } - return ctx.Err() - }) - return revList, err -} diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go deleted file mode 100644 index 21fe52eba75..00000000000 --- a/modules/git/repo_branch_nogogit.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2018 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "bufio" - "context" - "io" - "strings" - - "gitea.dev/modules/git/gitcmd" - "gitea.dev/modules/log" -) - -// IsObjectExist returns true if the given object exists in the repository. -// FIXME: this function doesn't seem right, it is only used by GarbageCollectLFSMetaObjectsForRepo -func (repo *Repository) IsObjectExist(ctx context.Context, name string) bool { - if name == "" { - return false - } - - batch, cancel, err := repo.CatFileBatch() - if err != nil { - log.Debug("Error opening CatFileBatch %v", err) - return false - } - defer cancel() - info, err := batch.QueryInfo(name) - if err != nil { - log.Debug("Error checking object info %v", err) - return false - } - return strings.HasPrefix(info.ID, name) // FIXME: this logic doesn't seem right, why "HasPrefix" -} - -// IsReferenceExist returns true if given reference exists in the repository. -func (repo *Repository) IsReferenceExist(ctx context.Context, name string) bool { - if name == "" { - return false - } - - batch, cancel, err := repo.CatFileBatch() - if err != nil { - log.Error("Error opening CatFileBatch %v", err) - return false - } - defer cancel() - _, err = batch.QueryInfo(name) - return err == nil -} - -// IsBranchExist returns true if given branch exists in current repository. -func (repo *Repository) IsBranchExist(ctx context.Context, name string) bool { - if repo == nil || name == "" { - return false - } - - return repo.IsReferenceExist(ctx, BranchPrefix+name) -} - -// GetBranchNames returns branches from the repository, skipping "skip" initial branches and -// returning at most "limit" branches, or all branches if "limit" is 0. -func (repo *Repository) GetBranchNames(ctx context.Context, skip, limit int) ([]string, int, error) { - return callShowRef(ctx, repo, BranchPrefix, gitcmd.TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit) -} - -// WalkReferences walks all the references from the repository -// refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty. -func (repo *Repository) WalkReferences(ctx context.Context, refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { - var args gitcmd.TrustedCmdArgs - switch refType { - case ObjectTag: - args = gitcmd.TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"} - case ObjectBranch: - args = gitcmd.TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"} - } - - return WalkShowRef(ctx, repo, args, skip, limit, walkfn) -} - -// callShowRef return refs, if limit = 0 it will not limit -func callShowRef(ctx context.Context, repo RepositoryFacade, trimPrefix string, extraArgs gitcmd.TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) { - countAll, err = WalkShowRef(ctx, repo, extraArgs, skip, limit, func(_, branchName string) error { - branchName = strings.TrimPrefix(branchName, trimPrefix) - branchNames = append(branchNames, branchName) - - return nil - }) - return branchNames, countAll, err -} - -func WalkShowRef(ctx context.Context, repo RepositoryFacade, extraArgs gitcmd.TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { - i := 0 - args := gitcmd.TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} - args = append(args, extraArgs...) - cmd := gitcmd.NewCommand(args...) - stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe() - defer stdoutReaderClose() - cmd.WithRepo(repo). - WithPipelineFunc(func(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: - // SP 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, ok := gitcmd.UnwrapPipelineError(err); ok { - return i, errPipeline // keep the old behavior: return pipeline error directly - } - return i, err -} - -// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash -func (repo *Repository) GetRefsBySha(ctx context.Context, sha, prefix string) ([]string, error) { - var revList []string - _, err := WalkShowRef(ctx, repo, nil, 0, 0, func(walkSha, refname string) error { - if walkSha == sha && strings.HasPrefix(refname, prefix) { - revList = append(revList, refname) - } - return nil - }) - return revList, err -} diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go index 83ad43c2af3..f7335d1e688 100644 --- a/modules/git/repo_branch_test.go +++ b/modules/git/repo_branch_test.go @@ -4,15 +4,9 @@ package git import ( - "fmt" - "os" "path/filepath" - "strings" "testing" - "gitea.dev/modules/git/gitcmd" - "gitea.dev/modules/setting" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -45,41 +39,6 @@ func TestRepository_GetBranches(t *testing.T) { assert.ElementsMatch(t, []string{}, branches) } -// FIXME: covers the gogit workarounds in repo_base_gogit.go, remove with the gogit build -func TestReadsAfterConcurrentRepack(t *testing.T) { - repoDir := t.TempDir() - require.NoError(t, gitcmd.NewCommand("init", "--bare").AddDynamicArguments(repoDir).Run(t.Context())) - content := strings.Repeat("a", int(setting.Git.LargeObjectThreshold)+1) - for _, from := range []string{"", "from refs/heads/main^0\n"} { - stdin := fmt.Sprintf("commit refs/heads/main\ncommitter a 0 +0000\ndata 0\n%sM 100644 inline f\ndata %d\n%s\n", from, len(content), content) - require.NoError(t, gitcmd.NewCommand("fast-import").WithDir(repoDir).WithStdinBytes([]byte(stdin)).Run(t.Context())) - require.NoError(t, gitcmd.NewCommand("repack", "-d").WithDir(repoDir).Run(t.Context())) - } - - repo, err := OpenRepositoryLocal(t.Context(), repoDir) - require.NoError(t, err) - defer repo.Close() - require.False(t, repo.IsObjectExist(t.Context(), "0000000000000000000000000000000000000001")) - blobRepo, err := OpenRepositoryLocal(t.Context(), repoDir) - require.NoError(t, err) - defer blobRepo.Close() - commit, err := blobRepo.GetBranchCommit(t.Context(), "main") - require.NoError(t, err) - readBlob := func() string { - data, err := commit.GetFileContent(t.Context(), blobRepo, "f", len(content)) - require.NoError(t, err) - return data - } - require.Equal(t, content, readBlob()) - require.NoError(t, gitcmd.NewCommand("repack", "-a", "-d").WithDir(repoDir).Run(t.Context())) - require.NoError(t, os.WriteFile(filepath.Join(repoDir, "objects", "pack", "pack-"+strings.Repeat("1", 40)+".pack"), nil, 0o644)) - - branches, _, err := repo.GetBranchNames(t.Context(), 0, 0) - require.NoError(t, err) - assert.Equal(t, []string{"main"}, branches) - assert.Equal(t, content, readBlob()) -} - func BenchmarkRepository_GetBranches(b *testing.B) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepositoryLocal(b.Context(), bareRepo1Path) @@ -143,10 +102,6 @@ func TestRepository_IsObjectExist(t *testing.T) { require.NoError(t, err) defer repo.Close() - // FIXME: Inconsistent behavior between gogit and nogogit editions - // See the comment of IsObjectExist in gogit edition for more details. - supportShortHash := !isGogit - tests := []struct { name string arg string @@ -170,7 +125,7 @@ func TestRepository_IsObjectExist(t *testing.T) { { name: "short commit hash", arg: "ce06481", - want: supportShortHash, + want: true, }, { name: "blob hash", @@ -180,7 +135,7 @@ func TestRepository_IsObjectExist(t *testing.T) { { name: "short blob hash", arg: "153f451", - want: supportShortHash, + want: true, }, } for _, tt := range tests { @@ -196,10 +151,6 @@ func TestRepository_IsReferenceExist(t *testing.T) { require.NoError(t, err) defer repo.Close() - // FIXME: Inconsistent behavior between gogit and nogogit editions - // See the comment of IsReferenceExist in gogit edition for more details. - supportBlobHash := !isGogit - tests := []struct { name string arg string @@ -228,12 +179,12 @@ func TestRepository_IsReferenceExist(t *testing.T) { { name: "blob hash", arg: "153f451b9ee7fa1da317ab17a127e9fd9d384310", - want: supportBlobHash, + want: true, }, { name: "short blob hash", arg: "153f451", - want: supportBlobHash, + want: true, }, } for _, tt := range tests { diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 54f5fb76719..10bc38af3c4 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -7,6 +7,7 @@ package git import ( "bytes" "context" + "errors" "io" "strconv" "strings" @@ -458,3 +459,124 @@ func (repo *Repository) GetCommitBranchStart(ctx context.Context, env []string, return "", nil } + +// GetRefCommitID returns the last commit ID string of given reference (branch or tag). +func (repo *Repository) GetRefCommitID(ctx context.Context, name string) (string, error) { + batch, cancel, err := repo.CatFileBatch() + if err != nil { + return "", err + } + defer cancel() + info, err := batch.QueryInfo(name) + if IsErrNotExist(err) { + return "", ErrNotExist{name, ""} + } else if err != nil { + return "", err + } + return info.ID, nil +} + +func (repo *Repository) getCommit(_ context.Context, id ObjectID) (*Commit, error) { + batch, cancel, err := repo.CatFileBatch() + if err != nil { + return nil, err + } + defer cancel() + return repo.getCommitWithBatch(batch, id) +} + +func limitDiscardReader(rd BufferedReader, full, limit int64) (io.Reader, func() error) { + return io.LimitReader(rd, min(full, limit)), func() error { + if full > limit { + return DiscardFull(rd, full-limit) + } + return nil + } +} + +func (repo *Repository) getCommitWithBatch(batch CatFileBatch, id ObjectID) (*Commit, error) { + info, rd, err := batch.QueryContent(id.String()) + if err != nil { + if errors.Is(err, io.EOF) || IsErrNotExist(err) { + return nil, ErrNotExist{ID: id.String()} + } + return nil, err + } + + switch info.Type { + case "missing": + return nil, ErrNotExist{ID: id.String()} + case "tag": + limitReader, limitDiscard := limitDiscardReader(rd, info.Size, MaxGitObjectSize) + data, err := io.ReadAll(limitReader) + if err != nil { + return nil, err + } + if err = limitDiscard(); err != nil { + return nil, err + } + _, err = rd.Discard(1) + if err != nil { + return nil, err + } + tag, err := parseTagData(id.Type(), data) + if err != nil { + return nil, err + } + return repo.getCommitWithBatch(batch, tag.Object) + case "commit": + limitReader, limitDiscard := limitDiscardReader(rd, info.Size, MaxGitObjectSize) + commit, err := CommitFromReader(id, limitReader) + if err != nil { + return nil, err + } + if err = limitDiscard(); err != nil { + return nil, err + } + _, err = rd.Discard(1) + if err != nil { + return nil, err + } + + return commit, nil + default: + if info.Type != "blob" && info.Type != "tree" { + setting.PanicInDevOrTesting("Unknown cat-file object type %s for object %s in repo %s", info.Type, id.String(), repo.LogString()) + } + if err := DiscardFull(rd, info.Size+1); err != nil { + return nil, err + } + return nil, ErrNotExist{ + ID: id.String(), + } + } +} + +// ConvertToGitID returns a git object ID from the git ref, it doesn't guarantee the returned ID really exists +func (repo *Repository) ConvertToGitID(ctx context.Context, ref string) (ObjectID, error) { + objectFormat, err := repo.GetObjectFormat(ctx) + if err != nil { + return nil, err + } + if IsStringValidObjectID(objectFormat, ref) { + id, err := NewIDFromString(ref) + if err == nil { + return id, nil + } + } + + batch, cancel, err := repo.CatFileBatch() + if err != nil { + return nil, err + } + defer cancel() + info, err := batch.QueryInfo(ref) + if err != nil { + if IsErrNotExist(err) { + return nil, ErrNotExist{ref, ""} + } + return nil, err + } + + return MustIDFromString(info.ID), nil +} diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go deleted file mode 100644 index 0014417f57f..00000000000 --- a/modules/git/repo_commit_gogit.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - "strings" - - "gitea.dev/modules/git/gitcmd" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" -) - -// GetRefCommitID returns the last commit ID string of given reference. -func (repo *Repository) GetRefCommitID(_ context.Context, name string) (string, error) { - if plumbing.IsHash(name) { - return name, nil - } - refName := plumbing.ReferenceName(name) - if err := refName.Validate(); err != nil { - // Match the nogogit behavior: an unresolvable/invalid ref name - // is reported as not-existing rather than a generic validation error, - // so callers can rely on IsErrNotExist regardless of build tag. - return "", ErrNotExist{ID: name} - } - ref, err := repo.gogitRepo.Reference(refName, true) - if err != nil { - if err == plumbing.ErrReferenceNotFound { - return "", ErrNotExist{ - ID: name, - } - } - return "", err - } - - return ref.Hash().String(), nil -} - -// ConvertToHash returns a Hash object from a potential ID string -func (repo *Repository) ConvertToGitID(ctx context.Context, commitID string) (ObjectID, error) { - objectFormat, err := repo.GetObjectFormat(ctx) - if err != nil { - return nil, err - } - if IsStringValidObjectID(objectFormat, commitID) { - ID, err := NewIDFromString(commitID) - if err == nil { - return ID, nil - } - } - - actualCommitID, _, err := gitcmd.NewCommand("rev-parse", "--verify"). - AddDynamicArguments(commitID). - WithRepo(repo). - RunStdString(ctx) - actualCommitID = strings.TrimSpace(actualCommitID) - if err != nil { - if strings.Contains(err.Error(), "unknown revision or path") || - strings.Contains(err.Error(), "fatal: Needed a single revision") { - return objectFormat.EmptyObjectID(), ErrNotExist{commitID, ""} - } - return objectFormat.EmptyObjectID(), err - } - - return NewIDFromString(actualCommitID) -} - -func (repo *Repository) getCommit(_ context.Context, id ObjectID) (*Commit, error) { - var tagObject *object.Tag - - commitID := plumbing.Hash(id.RawValue()) - gogitCommit, err := repo.gogitRepo.CommitObject(commitID) - if err == plumbing.ErrObjectNotFound { - tagObject, err = repo.gogitRepo.TagObject(commitID) - if err == plumbing.ErrObjectNotFound { - return nil, ErrNotExist{ - ID: id.String(), - } - } - if err == nil { - gogitCommit, err = repo.gogitRepo.CommitObject(tagObject.Target) - } - // if we get a plumbing.ErrObjectNotFound here then the repository is broken and it should be 500 - } - if err != nil { - return nil, err - } - - commit := convertCommit(gogitCommit) - - tree, err := gogitCommit.Tree() - if err != nil { - return nil, err - } - - commit.TreeID = ParseGogitHash(tree.Hash) - commit.Tree().resolvedGogitTreeObject = tree - - return commit, nil -} diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go deleted file mode 100644 index 9100c61706f..00000000000 --- a/modules/git/repo_commit_nogogit.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "context" - "errors" - "io" - - "gitea.dev/modules/setting" -) - -// GetRefCommitID returns the last commit ID string of given reference (branch or tag). -func (repo *Repository) GetRefCommitID(ctx context.Context, name string) (string, error) { - batch, cancel, err := repo.CatFileBatch() - if err != nil { - return "", err - } - defer cancel() - info, err := batch.QueryInfo(name) - if IsErrNotExist(err) { - return "", ErrNotExist{name, ""} - } else if err != nil { - return "", err - } - return info.ID, nil -} - -func (repo *Repository) getCommit(ctx context.Context, id ObjectID) (*Commit, error) { - batch, cancel, err := repo.CatFileBatch() - if err != nil { - return nil, err - } - defer cancel() - return repo.getCommitWithBatch(batch, id) -} - -func limitDiscardReader(rd BufferedReader, full, limit int64) (io.Reader, func() error) { - return io.LimitReader(rd, min(full, limit)), func() error { - if full > limit { - return DiscardFull(rd, full-limit) - } - return nil - } -} - -func (repo *Repository) getCommitWithBatch(batch CatFileBatch, id ObjectID) (*Commit, error) { - info, rd, err := batch.QueryContent(id.String()) - if err != nil { - if errors.Is(err, io.EOF) || IsErrNotExist(err) { - return nil, ErrNotExist{ID: id.String()} - } - return nil, err - } - - switch info.Type { - case "missing": - return nil, ErrNotExist{ID: id.String()} - case "tag": - limitReader, limitDiscard := limitDiscardReader(rd, info.Size, MaxGitObjectSize) - data, err := io.ReadAll(limitReader) - if err != nil { - return nil, err - } - if err = limitDiscard(); err != nil { - return nil, err - } - _, err = rd.Discard(1) - if err != nil { - return nil, err - } - tag, err := parseTagData(id.Type(), data) - if err != nil { - return nil, err - } - return repo.getCommitWithBatch(batch, tag.Object) - case "commit": - limitReader, limitDiscard := limitDiscardReader(rd, info.Size, MaxGitObjectSize) - commit, err := CommitFromReader(id, limitReader) - if err != nil { - return nil, err - } - if err = limitDiscard(); err != nil { - return nil, err - } - _, err = rd.Discard(1) - if err != nil { - return nil, err - } - - return commit, nil - default: - if info.Type != "blob" && info.Type != "tree" { - setting.PanicInDevOrTesting("Unknown cat-file object type %s for object %s in repo %s", info.Type, id.String(), repo.LogString()) - } - if err := DiscardFull(rd, info.Size+1); err != nil { - return nil, err - } - return nil, ErrNotExist{ - ID: id.String(), - } - } -} - -// ConvertToGitID returns a git object ID from the git ref, it doesn't guarantee the returned ID really exists -func (repo *Repository) ConvertToGitID(ctx context.Context, ref string) (ObjectID, error) { - objectFormat, err := repo.GetObjectFormat(ctx) - if err != nil { - return nil, err - } - if IsStringValidObjectID(objectFormat, ref) { - id, err := NewIDFromString(ref) - if err == nil { - return id, nil - } - } - - batch, cancel, err := repo.CatFileBatch() - if err != nil { - return nil, err - } - defer cancel() - info, err := batch.QueryInfo(ref) - if err != nil { - if IsErrNotExist(err) { - return nil, ErrNotExist{ref, ""} - } - return nil, err - } - - return MustIDFromString(info.ID), nil -} diff --git a/modules/git/repo_commitgraph_gogit.go b/modules/git/repo_commitgraph_gogit.go deleted file mode 100644 index ecfda61be27..00000000000 --- a/modules/git/repo_commitgraph_gogit.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2019 The Gitea Authors. -// All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "os" - "path/filepath" - - "gitea.dev/modules/git/gitrepo" - "gitea.dev/modules/log" - - commitgraph "github.com/go-git/go-git/v5/plumbing/format/commitgraph/v2" - cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph" -) - -// CommitNodeIndex returns the index for walking commit graph -func (repo *Repository) CommitNodeIndex() (_ cgobject.CommitNodeIndex, closer func()) { - indexPath := filepath.Join(gitrepo.RepoLocalPath(repo), "objects", "info", "commit-graph") - file, err := os.Open(indexPath) - if err == nil { - var index commitgraph.Index - index, err = commitgraph.OpenFileIndex(file) - if err == nil { - return cgobject.NewGraphCommitNodeIndex(index, repo.gogitRepo.Storer), func() { _ = file.Close() } - } - _ = file.Close() - } - - if !os.IsNotExist(err) { - log.Warn("Unable to read commit-graph for %s: %v", repo.LogString(), err) - } - - return cgobject.NewObjectCommitNodeIndex(repo.gogitRepo.Storer), func() {} -} diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go index 35f018692e5..f355ad4b24f 100644 --- a/modules/git/repo_ref.go +++ b/modules/git/repo_ref.go @@ -4,7 +4,9 @@ package git import ( + "bufio" "context" + "io" "strings" "gitea.dev/modules/git/gitcmd" @@ -96,3 +98,64 @@ func (repo *Repository) UnstableGuessRefByShortName(ctx context.Context, shortNa } return "" } + +// GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with. +func (repo *Repository) GetRefsFiltered(ctx context.Context, pattern string) ([]*Reference, error) { + refs := make([]*Reference, 0) + cmd := gitcmd.NewCommand("for-each-ref") + stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe() + defer stdoutReaderClose() + err := cmd.WithRepo(repo). + WithPipelineFunc(func(context gitcmd.Context) error { + bufReader := bufio.NewReader(stdoutReader) + for { + // The output of for-each-ref is simply a list: + // SP TAB 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 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 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 + } + + if pattern == "" || strings.HasPrefix(refName, pattern) { + r := &Reference{ + Name: refName, + Object: MustIDFromString(sha), + Type: typ, + repo: repo, + } + refs = append(refs, r) + } + } + return nil + }).RunWithStderr(ctx) + return refs, err +} diff --git a/modules/git/repo_ref_gogit.go b/modules/git/repo_ref_gogit.go deleted file mode 100644 index c66644c5499..00000000000 --- a/modules/git/repo_ref_gogit.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - "strings" - - "github.com/go-git/go-git/v5/plumbing" -) - -// GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with. -func (repo *Repository) GetRefsFiltered(ctx context.Context, pattern string) ([]*Reference, error) { - r := repo.gogitRepo - - refsIter, err := r.References() - if err != nil { - return nil, err - } - refs := make([]*Reference, 0) - if err = refsIter.ForEach(func(ref *plumbing.Reference) error { - if ref.Name() != plumbing.HEAD && !ref.Name().IsRemote() && - (pattern == "" || strings.HasPrefix(ref.Name().String(), pattern)) { - refType := string(ObjectCommit) - if ref.Name().IsTag() { - // tags can be of type `commit` (lightweight) or `tag` (annotated) - if tagType, _ := repo.GetTagType(ctx, ParseGogitHash(ref.Hash())); err == nil { - refType = tagType - } - } - r := &Reference{ - Name: ref.Name().String(), - Object: ParseGogitHash(ref.Hash()), - Type: refType, - repo: repo, - } - refs = append(refs, r) - } - return nil - }); err != nil { - return nil, err - } - - return refs, nil -} diff --git a/modules/git/repo_ref_nogogit.go b/modules/git/repo_ref_nogogit.go deleted file mode 100644 index 54f55a265eb..00000000000 --- a/modules/git/repo_ref_nogogit.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "bufio" - "context" - "io" - "strings" - - "gitea.dev/modules/git/gitcmd" -) - -// GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with. -func (repo *Repository) GetRefsFiltered(ctx context.Context, pattern string) ([]*Reference, error) { - refs := make([]*Reference, 0) - cmd := gitcmd.NewCommand("for-each-ref") - stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe() - defer stdoutReaderClose() - err := cmd.WithRepo(repo). - WithPipelineFunc(func(context gitcmd.Context) error { - bufReader := bufio.NewReader(stdoutReader) - for { - // The output of for-each-ref is simply a list: - // SP TAB 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 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 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 - } - - if pattern == "" || strings.HasPrefix(refName, pattern) { - r := &Reference{ - Name: refName, - Object: MustIDFromString(sha), - Type: typ, - repo: repo, - } - refs = append(refs, r) - } - } - return nil - }).RunWithStderr(ctx) - return refs, err -} diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index ee2da5f43e3..2bb90afda68 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -6,13 +6,16 @@ package git import ( "context" + "errors" "fmt" + "io" "strconv" "strings" "time" "gitea.dev/modules/git/foreachref" "gitea.dev/modules/git/gitcmd" + "gitea.dev/modules/log" "gitea.dev/modules/util" ) @@ -232,3 +235,122 @@ func (repo *Repository) GetAnnotatedTag(ctx context.Context, sha string) (*Tag, } return tag, nil } + +// IsTagExist returns true if given tag exists in the repository. +func (repo *Repository) IsTagExist(ctx context.Context, name string) bool { + if repo == nil || name == "" { + return false + } + + return repo.IsReferenceExist(ctx, TagPrefix+name) +} + +// GetTagType gets the type of the tag, either commit (simple) or tag (annotated) +func (repo *Repository) GetTagType(ctx context.Context, id ObjectID) (string, error) { + batch, cancel, err := repo.CatFileBatch() + if err != nil { + return "", err + } + defer cancel() + info, err := batch.QueryInfo(id.String()) + if err != nil { + if IsErrNotExist(err) { + return "", ErrNotExist{ID: id.String()} + } + return "", err + } + return info.Type, nil +} + +func (repo *Repository) getTag(ctx context.Context, tagID ObjectID, name string) (*Tag, error) { + t, ok := repo.tagCache.Get(tagID.String()) + if ok { + log.Debug("Hit cache: %s", tagID) + tagClone := *t + tagClone.Name = name // This is necessary because lightweight tags may have same id + return &tagClone, nil + } + + tp, err := repo.GetTagType(ctx, tagID) + if err != nil { + return nil, err + } + + // Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object + commitIDStr, err := repo.GetTagCommitID(ctx, name) + if err != nil { + // every tag should have a commit ID so return all errors + return nil, err + } + commitID, err := NewIDFromString(commitIDStr) + if err != nil { + return nil, err + } + + // If type is "commit, the tag is a lightweight tag + if ObjectType(tp) == ObjectCommit { + commit, err := repo.GetCommit(ctx, commitIDStr) + if err != nil { + return nil, err + } + tag := &Tag{ + Name: name, + ID: tagID, + Object: commitID, + Type: tp, + Tagger: commit.Committer, + CommitMessage: commit.CommitMessage, + } + + repo.tagCache.Set(tagID.String(), tag) + return tag, nil + } + + // The tag is an annotated tag with a message. + batch, cancel, err := repo.CatFileBatch() + if err != nil { + return nil, err + } + defer cancel() + + info, rd, err := batch.QueryContent(tagID.String()) + if err != nil { + if errors.Is(err, io.EOF) || IsErrNotExist(err) { + return nil, ErrNotExist{ID: tagID.String()} + } + return nil, err + } + typ, size := info.Type, info.Size + if typ != "tag" { + if err := DiscardFull(rd, size+1); err != nil { + return nil, err + } + return nil, ErrNotExist{ID: tagID.String()} + } + + // then we need to parse the tag and load the commit + limitReader, limitDiscard := limitDiscardReader(rd, info.Size, MaxGitObjectSize) + data, err := io.ReadAll(limitReader) + if err != nil { + return nil, err + } + if err = limitDiscard(); err != nil { + return nil, err + } + _, err = rd.Discard(1) + if err != nil { + return nil, err + } + + tag, err := parseTagData(tagID.Type(), data) + if err != nil { + return nil, err + } + + tag.Name = name + tag.ID = tagID + tag.Type = tp + + repo.tagCache.Set(tagID.String(), tag) + return tag, nil +} diff --git a/modules/git/repo_tag_gogit.go b/modules/git/repo_tag_gogit.go deleted file mode 100644 index 4e256189c78..00000000000 --- a/modules/git/repo_tag_gogit.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - - "gitea.dev/modules/log" - - "github.com/go-git/go-git/v5/plumbing" -) - -// IsTagExist returns true if given tag exists in the repository. -func (repo *Repository) IsTagExist(_ context.Context, name string) bool { - _, err := repo.gogitRepo.Reference(plumbing.ReferenceName(TagPrefix+name), true) - return err == nil -} - -// GetTagType gets the type of the tag, either commit (simple) or tag (annotated) -func (repo *Repository) GetTagType(_ context.Context, id ObjectID) (string, error) { - // Get tag type - obj, err := repo.gogitRepo.Object(plumbing.AnyObject, plumbing.Hash(id.RawValue())) - if err != nil { - if err == plumbing.ErrReferenceNotFound { - return "", &ErrNotExist{ID: id.String()} - } - return "", err - } - - return obj.Type().String(), nil -} - -func (repo *Repository) getTag(ctx context.Context, tagID ObjectID, name string) (*Tag, error) { - t, ok := repo.tagCache.Get(tagID.String()) - if ok { - log.Debug("Hit cache: %s", tagID) - tagClone := *t - tagClone.Name = name // This is necessary because lightweight tags may have same id - return &tagClone, nil - } - - tp, err := repo.GetTagType(ctx, tagID) - if err != nil { - return nil, err - } - - // Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object - commitIDStr, err := repo.GetTagCommitID(ctx, name) - if err != nil { - // every tag should have a commit ID so return all errors - return nil, err - } - commitID, err := NewIDFromString(commitIDStr) - if err != nil { - return nil, err - } - - // If type is "commit, the tag is a lightweight tag - if ObjectType(tp) == ObjectCommit { - commit, err := repo.GetCommit(ctx, commitIDStr) - if err != nil { - return nil, err - } - tag := &Tag{ - Name: name, - ID: tagID, - Object: commitID, - Type: tp, - Tagger: commit.Committer, - CommitMessage: CommitMessage{MessageRaw: commit.CommitMessage.MessageRaw}, - } - - repo.tagCache.Set(tagID.String(), tag) - return tag, nil - } - - gogitTag, err := repo.gogitRepo.TagObject(plumbing.Hash(tagID.RawValue())) - if err != nil { - if err == plumbing.ErrReferenceNotFound { - return nil, &ErrNotExist{ID: tagID.String()} - } - - return nil, err - } - - tag := &Tag{ - Name: name, - ID: tagID, - Object: commitID.Type().MustID(gogitTag.Target[:]), - Type: tp, - Tagger: &gogitTag.Tagger, - CommitMessage: CommitMessage{MessageRaw: gogitTag.Message}, - } - - repo.tagCache.Set(tagID.String(), tag) - return tag, nil -} diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go deleted file mode 100644 index c6026fcd3f9..00000000000 --- a/modules/git/repo_tag_nogogit.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "context" - "errors" - "io" - - "gitea.dev/modules/log" -) - -// IsTagExist returns true if given tag exists in the repository. -func (repo *Repository) IsTagExist(ctx context.Context, name string) bool { - if repo == nil || name == "" { - return false - } - - return repo.IsReferenceExist(ctx, TagPrefix+name) -} - -// GetTagType gets the type of the tag, either commit (simple) or tag (annotated) -func (repo *Repository) GetTagType(ctx context.Context, id ObjectID) (string, error) { - batch, cancel, err := repo.CatFileBatch() - if err != nil { - return "", err - } - defer cancel() - info, err := batch.QueryInfo(id.String()) - if err != nil { - if IsErrNotExist(err) { - return "", ErrNotExist{ID: id.String()} - } - return "", err - } - return info.Type, nil -} - -func (repo *Repository) getTag(ctx context.Context, tagID ObjectID, name string) (*Tag, error) { - t, ok := repo.tagCache.Get(tagID.String()) - if ok { - log.Debug("Hit cache: %s", tagID) - tagClone := *t - tagClone.Name = name // This is necessary because lightweight tags may have same id - return &tagClone, nil - } - - tp, err := repo.GetTagType(ctx, tagID) - if err != nil { - return nil, err - } - - // Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object - commitIDStr, err := repo.GetTagCommitID(ctx, name) - if err != nil { - // every tag should have a commit ID so return all errors - return nil, err - } - commitID, err := NewIDFromString(commitIDStr) - if err != nil { - return nil, err - } - - // If type is "commit, the tag is a lightweight tag - if ObjectType(tp) == ObjectCommit { - commit, err := repo.GetCommit(ctx, commitIDStr) - if err != nil { - return nil, err - } - tag := &Tag{ - Name: name, - ID: tagID, - Object: commitID, - Type: tp, - Tagger: commit.Committer, - CommitMessage: commit.CommitMessage, - } - - repo.tagCache.Set(tagID.String(), tag) - return tag, nil - } - - // The tag is an annotated tag with a message. - batch, cancel, err := repo.CatFileBatch() - if err != nil { - return nil, err - } - defer cancel() - - info, rd, err := batch.QueryContent(tagID.String()) - if err != nil { - if errors.Is(err, io.EOF) || IsErrNotExist(err) { - return nil, ErrNotExist{ID: tagID.String()} - } - return nil, err - } - typ, size := info.Type, info.Size - if typ != "tag" { - if err := DiscardFull(rd, size+1); err != nil { - return nil, err - } - return nil, ErrNotExist{ID: tagID.String()} - } - - // then we need to parse the tag and load the commit - limitReader, limitDiscard := limitDiscardReader(rd, info.Size, MaxGitObjectSize) - data, err := io.ReadAll(limitReader) - if err != nil { - return nil, err - } - if err = limitDiscard(); err != nil { - return nil, err - } - _, err = rd.Discard(1) - if err != nil { - return nil, err - } - - tag, err := parseTagData(tagID.Type(), data) - if err != nil { - return nil, err - } - - tag.Name = name - tag.ID = tagID - tag.Type = tp - - repo.tagCache.Set(tagID.String(), tag) - return tag, nil -} diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index f0fce10a94d..9820084bb26 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -7,6 +7,7 @@ package git import ( "bytes" "context" + "io" "os" "strings" "time" @@ -68,3 +69,87 @@ func (repo *Repository) CommitTree(ctx context.Context, author, committer *Signa } return NewIDFromString(strings.TrimSpace(stdout)) } + +func (repo *Repository) getTree(ctx context.Context, id ObjectID) (*Tree, error) { + batch, cancel, err := repo.CatFileBatch() + if err != nil { + return nil, err + } + defer cancel() + + info, rd, err := batch.QueryContent(id.String()) + if err != nil { + return nil, err + } + + switch info.Type { + case "tag": + data, err := io.ReadAll(io.LimitReader(rd, info.Size)) + if err != nil { + return nil, err + } + tag, err := parseTagData(id.Type(), data) + if err != nil { + return nil, err + } + + commit, err := repo.getCommitWithBatch(batch, tag.Object) + if err != nil { + return nil, err + } + tree := commit.Tree() + return tree, nil + case "commit": + commit, err := CommitFromReader(id, io.LimitReader(rd, info.Size)) + if err != nil { + return nil, err + } + if _, err := rd.Discard(1); err != nil { + return nil, err + } + tree := commit.Tree() + return tree, nil + case "tree": + tree := newTree(id) + objectFormat, err := repo.GetObjectFormat(ctx) + if err != nil { + return nil, err + } + tree.entries, err = catBatchParseTreeEntries(objectFormat, tree, rd, info.Size) + if err != nil { + return nil, err + } + tree.entriesParsed = true + return tree, nil + default: + if err := DiscardFull(rd, info.Size+1); err != nil { + return nil, err + } + return nil, ErrNotExist{ + ID: id.String(), + } + } +} + +// GetTree find the tree object in the repository. +func (repo *Repository) GetTree(ctx context.Context, idStr string) (*Tree, error) { + objectFormat, err := repo.GetObjectFormat(ctx) + if err != nil { + return nil, err + } + if len(idStr) != objectFormat.FullLength() { + res, err := repo.GetRefCommitID(ctx, idStr) + if err != nil { + return nil, err + } + if len(res) > 0 { + idStr = res + } + } + id, err := NewIDFromString(idStr) + if err != nil { + return nil, err + } + + return repo.getTree(ctx, id) +} diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go deleted file mode 100644 index b455ffdbb60..00000000000 --- a/modules/git/repo_tree_gogit.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - "errors" - - "gitea.dev/modules/git/gitcmd" - - "github.com/go-git/go-git/v5/plumbing" -) - -func (repo *Repository) getTree(_ context.Context, id ObjectID) (*Tree, error) { - gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id.RawValue())) - if err != nil { - if errors.Is(err, plumbing.ErrObjectNotFound) { - return nil, ErrNotExist{ - ID: id.String(), - } - } - return nil, err - } - - tree := newTree(id) - tree.resolvedGogitTreeObject = gogitTree - return tree, nil -} - -// GetTree find the tree object in the repository. -func (repo *Repository) GetTree(ctx context.Context, idStr string) (*Tree, error) { - objectFormat, err := repo.GetObjectFormat(ctx) - if err != nil { - return nil, err - } - - if len(idStr) != objectFormat.FullLength() { - res, _, err := gitcmd.NewCommand("rev-parse", "--verify"). - AddDynamicArguments(idStr). - WithRepo(repo). - RunStdString(ctx) - if err != nil { - return nil, err - } - if len(res) > 0 { - idStr = res[:len(res)-1] - } - } - id, err := NewIDFromString(idStr) - if err != nil { - return nil, err - } - commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id.RawValue())) - if err == nil { - id = ParseGogitHash(commitObject.TreeHash) - } - treeObject, err := repo.getTree(ctx, id) - if err != nil { - return nil, err - } - return treeObject, nil -} diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go deleted file mode 100644 index fd7ae7f698b..00000000000 --- a/modules/git/repo_tree_nogogit.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "context" - "io" -) - -func (repo *Repository) getTree(ctx context.Context, id ObjectID) (*Tree, error) { - batch, cancel, err := repo.CatFileBatch() - if err != nil { - return nil, err - } - defer cancel() - - info, rd, err := batch.QueryContent(id.String()) - if err != nil { - return nil, err - } - - switch info.Type { - case "tag": - data, err := io.ReadAll(io.LimitReader(rd, info.Size)) - if err != nil { - return nil, err - } - tag, err := parseTagData(id.Type(), data) - if err != nil { - return nil, err - } - - commit, err := repo.getCommitWithBatch(batch, tag.Object) - if err != nil { - return nil, err - } - tree := commit.Tree() - return tree, nil - case "commit": - commit, err := CommitFromReader(id, io.LimitReader(rd, info.Size)) - if err != nil { - return nil, err - } - if _, err := rd.Discard(1); err != nil { - return nil, err - } - tree := commit.Tree() - return tree, nil - case "tree": - tree := newTree(id) - objectFormat, err := repo.GetObjectFormat(ctx) - if err != nil { - return nil, err - } - tree.entries, err = catBatchParseTreeEntries(objectFormat, tree, rd, info.Size) - if err != nil { - return nil, err - } - tree.entriesParsed = true - return tree, nil - default: - if err := DiscardFull(rd, info.Size+1); err != nil { - return nil, err - } - return nil, ErrNotExist{ - ID: id.String(), - } - } -} - -// GetTree find the tree object in the repository. -func (repo *Repository) GetTree(ctx context.Context, idStr string) (*Tree, error) { - objectFormat, err := repo.GetObjectFormat(ctx) - if err != nil { - return nil, err - } - if len(idStr) != objectFormat.FullLength() { - res, err := repo.GetRefCommitID(ctx, idStr) - if err != nil { - return nil, err - } - if len(res) > 0 { - idStr = res - } - } - id, err := NewIDFromString(idStr) - if err != nil { - return nil, err - } - - return repo.getTree(ctx, id) -} diff --git a/modules/git/signature.go b/modules/git/signature.go index 231b2e25cd1..45b0c14f256 100644 --- a/modules/git/signature.go +++ b/modules/git/signature.go @@ -5,11 +5,14 @@ package git import ( + "fmt" + "io" "strconv" "strings" "time" "gitea.dev/modules/log" + "gitea.dev/modules/util" ) // Helper to get a signature from the commit line, which looks like: @@ -47,3 +50,25 @@ func parseSignatureFromCommitLine(line string) *Signature { } return sig } + +// Signature represents the Author, Committer or Tagger information. +type Signature struct { + Name string // the committer name, it can be anything + Email string // the committer email, it can be anything + When time.Time // the timestamp of the signature +} + +func (s *Signature) String() string { + return fmt.Sprintf("%s <%s>", s.Name, s.Email) +} + +// Encode writes the signature for git commit object +func (s *Signature) Encode(w io.Writer) error { + _, err := fmt.Fprintf(w, "%s <%s> %d %s", s.Name, s.Email, max(0, s.When.Unix()), s.When.Format("-0700")) + return err +} + +// Decode parses the signature for git commit object +func (s *Signature) Decode(b []byte) { + *s = *parseSignatureFromCommitLine(util.UnsafeBytesToString(b)) +} diff --git a/modules/git/signature_gogit.go b/modules/git/signature_gogit.go deleted file mode 100644 index 1fc6aabceb4..00000000000 --- a/modules/git/signature_gogit.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "github.com/go-git/go-git/v5/plumbing/object" -) - -// Signature represents the Author or Committer information. -type Signature = object.Signature diff --git a/modules/git/signature_nogogit.go b/modules/git/signature_nogogit.go deleted file mode 100644 index 7a61d8d7eb0..00000000000 --- a/modules/git/signature_nogogit.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "fmt" - "io" - "time" - - "gitea.dev/modules/util" -) - -// Signature represents the Author, Committer or Tagger information. -type Signature struct { - Name string // the committer name, it can be anything - Email string // the committer email, it can be anything - When time.Time // the timestamp of the signature -} - -func (s *Signature) String() string { - return fmt.Sprintf("%s <%s>", s.Name, s.Email) -} - -// Encode writes the signature for git commit object (same as gogit's object.Signature Encode method) -func (s *Signature) Encode(w io.Writer) error { - _, err := fmt.Fprintf(w, "%s <%s> %d %s", s.Name, s.Email, max(0, s.When.Unix()), s.When.Format("-0700")) - return err -} - -// Decode parses the signature for git commit object (same as gogit's object.Signature Decode method) -func (s *Signature) Decode(b []byte) { - *s = *parseSignatureFromCommitLine(util.UnsafeBytesToString(b)) -} diff --git a/modules/git/tree.go b/modules/git/tree.go index a397452d9c3..3122a76a4f3 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -7,6 +7,7 @@ package git import ( "bytes" "context" + "io" "strings" "gitea.dev/modules/git/gitcmd" @@ -76,3 +77,97 @@ func (repo *Repository) GetTreePathLatestCommit(ctx context.Context, refName, tr } return repo.GetCommit(ctx, strings.TrimSpace(stdout)) } + +// Tree represents a flat directory listing. +type Tree struct { + TreeCommon + + entries Entries + entriesParsed bool +} + +// ListEntries returns all entries of current tree. +func (t *Tree) ListEntries(ctx context.Context, gitRepo *Repository) (Entries, error) { + if t.entriesParsed { + return t.entries, nil + } + + batch, cancel, err := gitRepo.CatFileBatch() + if err != nil { + return nil, err + } + defer cancel() + + info, rd, err := batch.QueryContent(t.ID.String()) + if err != nil { + return nil, err + } + + if info.Type == "commit" { + treeID, err := ReadTreeID(rd, info.Size) + if err != nil && err != io.EOF { + return nil, err + } + info, rd, err = batch.QueryContent(treeID) + if err != nil { + return nil, err + } + } + if info.Type == "tree" { + t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, info.Size) + if err != nil { + return nil, err + } + t.entriesParsed = true + return t.entries, nil + } + + // Not a tree just use ls-tree instead + if err := DiscardFull(rd, info.Size+1); err != nil { + return nil, err + } + + stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithRepo(gitRepo).RunStdBytes(ctx) + if runErr != nil { + if gitcmd.IsStderr(runErr, gitcmd.StderrNotValidObjectName) || gitcmd.IsStderr(runErr, gitcmd.StderrNotTreeObject) { + return nil, ErrNotExist{ + ID: t.ID.String(), + } + } + return nil, runErr + } + + t.entries, err = parseTreeEntries(stdout, t) + if err == nil { + t.entriesParsed = true + } + + return t.entries, err +} + +// listEntriesRecursive returns all entries of current tree recursively including all subtrees +// extraArgs could be "-l" to get the size, which is slower +func (t *Tree) listEntriesRecursive(ctx context.Context, gitRepo *Repository, extraArgs gitcmd.TrustedCmdArgs) (Entries, error) { + stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-t", "-r"). + AddArguments(extraArgs...). + AddDynamicArguments(t.ID.String()). + WithRepo(gitRepo). + RunStdBytes(ctx) + if runErr != nil { + return nil, runErr + } + + // FIXME: the "name" field is abused, here it is a full path + // FIXME: this ptree is not right, fortunately it isn't really used + return parseTreeEntries(stdout, t) +} + +// ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size +func (t *Tree) ListEntriesRecursiveFast(ctx context.Context, gitRepo *Repository) (Entries, error) { + return t.listEntriesRecursive(ctx, gitRepo, nil) +} + +// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size +func (t *Tree) ListEntriesRecursiveWithSize(ctx context.Context, gitRepo *Repository) (Entries, error) { + return t.listEntriesRecursive(ctx, gitRepo, gitcmd.TrustedCmdArgs{"--long"}) +} diff --git a/modules/git/tree_blob.go b/modules/git/tree_blob.go index 8f36bce35b0..e44e61c51d8 100644 --- a/modules/git/tree_blob.go +++ b/modules/git/tree_blob.go @@ -4,7 +4,11 @@ package git -import "context" +import ( + "context" + "path" + "strings" +) // GetBlobByPath get the blob object according the path func (t *Tree) GetBlobByPath(ctx context.Context, gitRepo *Repository, relpath string) (*Blob, error) { @@ -19,3 +23,38 @@ func (t *Tree) GetBlobByPath(ctx context.Context, gitRepo *Repository, relpath s return nil, ErrNotExist{"", relpath} } + +// GetTreeEntryByPath get the tree entries according the sub dir +func (t *Tree) GetTreeEntryByPath(ctx context.Context, gitRepo *Repository, relpath string) (_ *TreeEntry, err error) { + if len(relpath) == 0 { + return &TreeEntry{ + ptree: t, + ID: t.ID, + name: "", + entryMode: EntryModeTree, + }, nil + } + + relpath = path.Clean(relpath) + parts := strings.Split(relpath, "/") + + tree := t + for _, name := range parts[:len(parts)-1] { + tree, err = tree.SubTree(ctx, gitRepo, name) + if err != nil { + return nil, err + } + } + + name := parts[len(parts)-1] + entries, err := tree.ListEntries(ctx, gitRepo) + if err != nil { + return nil, err + } + for _, v := range entries { + if v.Name() == name { + return v, nil + } + } + return nil, ErrNotExist{"", relpath} +} diff --git a/modules/git/tree_blob_gogit.go b/modules/git/tree_blob_gogit.go deleted file mode 100644 index 84f08c2e662..00000000000 --- a/modules/git/tree_blob_gogit.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - "path" - "strings" - - "github.com/go-git/go-git/v5/plumbing" -) - -// GetTreeEntryByPath get the tree entries according the sub dir -func (t *Tree) GetTreeEntryByPath(ctx context.Context, gitRepo *Repository, relpath string) (*TreeEntry, error) { - if len(relpath) == 0 { - return &TreeEntry{ - ID: t.ID, - ptree: t, - name: "", - entryMode: EntryModeTree, - }, nil - } - - relpath = path.Clean(relpath) - parts := strings.Split(relpath, "/") - var err error - tree := t - for i, name := range parts { - if i == len(parts)-1 { - entries, err := tree.ListEntries(ctx, gitRepo) - if err != nil { - if err == plumbing.ErrObjectNotFound { - return nil, ErrNotExist{ - RelPath: relpath, - } - } - return nil, err - } - for _, v := range entries { - if v.Name() == name { - return v, nil - } - } - } else { - tree, err = tree.SubTree(ctx, gitRepo, name) - if err != nil { - if err == plumbing.ErrObjectNotFound { - return nil, ErrNotExist{ - RelPath: relpath, - } - } - return nil, err - } - } - } - return nil, ErrNotExist{"", relpath} -} diff --git a/modules/git/tree_blob_nogogit.go b/modules/git/tree_blob_nogogit.go deleted file mode 100644 index 26830728181..00000000000 --- a/modules/git/tree_blob_nogogit.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "context" - "path" - "strings" -) - -// GetTreeEntryByPath get the tree entries according the sub dir -func (t *Tree) GetTreeEntryByPath(ctx context.Context, gitRepo *Repository, relpath string) (_ *TreeEntry, err error) { - if len(relpath) == 0 { - return &TreeEntry{ - ptree: t, - ID: t.ID, - name: "", - entryMode: EntryModeTree, - }, nil - } - - relpath = path.Clean(relpath) - parts := strings.Split(relpath, "/") - - tree := t - for _, name := range parts[:len(parts)-1] { - tree, err = tree.SubTree(ctx, gitRepo, name) - if err != nil { - return nil, err - } - } - - name := parts[len(parts)-1] - entries, err := tree.ListEntries(ctx, gitRepo) - if err != nil { - return nil, err - } - for _, v := range entries { - if v.Name() == name { - return v, nil - } - } - return nil, ErrNotExist{"", relpath} -} diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 5d1c0e3d774..904de7c7153 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -10,6 +10,7 @@ import ( "slices" "strings" + "gitea.dev/modules/log" "gitea.dev/modules/util" ) @@ -169,3 +170,38 @@ func (tes Entries) CustomSort(cmp func(s1, s2 string) int) { return cmp(a.Name(), b.Name()) }) } + +func (te *TreeEntry) GetSize(ctx context.Context, gitRepo *Repository) int64 { + if te.IsDir() { + return 0 + } else if te.sized { + return te.size + } + + batch, cancel, err := gitRepo.CatFileBatch() + if err != nil { + log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), gitRepo.LogString(), err) + return 0 + } + defer cancel() + info, err := batch.QueryInfo(te.ID.String()) + if err != nil { + log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), gitRepo.LogString(), err) + return 0 + } + + te.size = info.Size + te.sized = true + return te.size +} + +// Blob returns the blob object the entry +func (te *TreeEntry) Blob(gitRepo *Repository) *Blob { + return &Blob{ + ID: te.ID, + name: te.Name(), + size: te.size, + gotSize: te.sized, + repo: gitRepo, + } +} diff --git a/modules/git/tree_entry_gogit.go b/modules/git/tree_entry_gogit.go deleted file mode 100644 index f19d2dc1645..00000000000 --- a/modules/git/tree_entry_gogit.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/filemode" - "github.com/go-git/go-git/v5/plumbing/object" -) - -// gogitFileModeToEntryMode converts go-git filemode to EntryMode -func gogitFileModeToEntryMode(mode filemode.FileMode) EntryMode { - return EntryMode(mode) -} - -func entryModeToGogitFileMode(mode EntryMode) filemode.FileMode { - return filemode.FileMode(mode) -} - -func (te *TreeEntry) toGogitTreeEntry() *object.TreeEntry { - return &object.TreeEntry{ - Name: te.name, - Mode: entryModeToGogitFileMode(te.entryMode), - Hash: plumbing.Hash(te.ID.RawValue()), - } -} - -// GetSize returns the size of the entry -func (te *TreeEntry) GetSize(ctx context.Context, gitRepo *Repository) int64 { - if te.IsDir() { - return 0 - } else if te.sized { - return te.size - } - - ptreeGogitTree, err := te.ptree.gogitTreeObject(gitRepo) - if err != nil { - return 0 - } - file, err := ptreeGogitTree.TreeEntryFile(te.toGogitTreeEntry()) - if err != nil { - return 0 - } - - te.sized = true - te.size = file.Size - return te.size -} - -// Blob returns the blob object the entry -func (te *TreeEntry) Blob(gitRepo *Repository) *Blob { - return &Blob{ - ID: te.ID, - repo: gitRepo, - name: te.Name(), - } -} diff --git a/modules/git/tree_entry_gogit_test.go b/modules/git/tree_entry_gogit_test.go deleted file mode 100644 index 7727b08cb91..00000000000 --- a/modules/git/tree_entry_gogit_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "testing" - - "github.com/go-git/go-git/v5/plumbing/filemode" - "github.com/stretchr/testify/assert" -) - -func TestEntryGogit(t *testing.T) { - cases := map[EntryMode]filemode.FileMode{ - EntryModeBlob: filemode.Regular, - EntryModeCommit: filemode.Submodule, - EntryModeExec: filemode.Executable, - EntryModeSymlink: filemode.Symlink, - EntryModeTree: filemode.Dir, - } - for emode, fmode := range cases { - assert.Equal(t, fmode, entryModeToGogitFileMode(emode)) - assert.Equal(t, emode, gogitFileModeToEntryMode(fmode)) - } -} diff --git a/modules/git/tree_entry_nogogit.go b/modules/git/tree_entry_nogogit.go deleted file mode 100644 index cf7a10ab2ee..00000000000 --- a/modules/git/tree_entry_nogogit.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "context" - - "gitea.dev/modules/log" -) - -func (te *TreeEntry) GetSize(ctx context.Context, gitRepo *Repository) int64 { - if te.IsDir() { - return 0 - } else if te.sized { - return te.size - } - - batch, cancel, err := gitRepo.CatFileBatch() - if err != nil { - log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), gitRepo.LogString(), err) - return 0 - } - defer cancel() - info, err := batch.QueryInfo(te.ID.String()) - if err != nil { - log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), gitRepo.LogString(), err) - return 0 - } - - te.size = info.Size - te.sized = true - return te.size -} - -// Blob returns the blob object the entry -func (te *TreeEntry) Blob(gitRepo *Repository) *Blob { - return &Blob{ - ID: te.ID, - name: te.Name(), - size: te.size, - gotSize: te.sized, - repo: gitRepo, - } -} diff --git a/modules/git/tree_gogit.go b/modules/git/tree_gogit.go deleted file mode 100644 index 2255b10f593..00000000000 --- a/modules/git/tree_gogit.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "context" - "io" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" -) - -// Tree represents a flat directory listing. -type Tree struct { - TreeCommon - - resolvedGogitTreeObject *object.Tree -} - -func (t *Tree) gogitTreeObject(gitRepo *Repository) (_ *object.Tree, err error) { - if t.resolvedGogitTreeObject == nil { - t.resolvedGogitTreeObject, err = gitRepo.gogitRepo.TreeObject(plumbing.Hash(t.ID.RawValue())) - if err != nil { - return nil, err - } - } - return t.resolvedGogitTreeObject, nil -} - -// ListEntries returns all entries of current tree. -func (t *Tree) ListEntries(_ context.Context, gitRepo *Repository) (Entries, error) { - gogitTree, err := t.gogitTreeObject(gitRepo) - if err != nil { - return nil, err - } - entries := make([]*TreeEntry, len(gogitTree.Entries)) - for i, gogitTreeEntry := range gogitTree.Entries { - entries[i] = &TreeEntry{ - ID: ParseGogitHash(gogitTreeEntry.Hash), - ptree: t, - name: gogitTreeEntry.Name, - entryMode: gogitFileModeToEntryMode(gogitTreeEntry.Mode), - } - } - - return entries, nil -} - -// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees -func (t *Tree) ListEntriesRecursiveWithSize(_ context.Context, gitRepo *Repository) (entries Entries, _ error) { - gogitTree, err := t.gogitTreeObject(gitRepo) - if err != nil { - return nil, err - } - - walker := object.NewTreeWalker(gogitTree, true, nil) - for { - fullName, gogitTreeEntry, err := walker.Next() - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - convertedEntry := &TreeEntry{ - ID: ParseGogitHash(gogitTreeEntry.Hash), - name: fullName, // FIXME: the "name" field is abused, here it is a full path - ptree: t, // FIXME: this ptree is not right, fortunately it isn't really used - entryMode: gogitFileModeToEntryMode(gogitTreeEntry.Mode), - } - entries = append(entries, convertedEntry) - } - return entries, nil -} - -// ListEntriesRecursiveFast is the alias of ListEntriesRecursiveWithSize for the gogit version -func (t *Tree) ListEntriesRecursiveFast(ctx context.Context, gitRepo *Repository) (Entries, error) { - return t.ListEntriesRecursiveWithSize(ctx, gitRepo) -} diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go deleted file mode 100644 index c7b943982fa..00000000000 --- a/modules/git/tree_nogogit.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "context" - "io" - - "gitea.dev/modules/git/gitcmd" -) - -// Tree represents a flat directory listing. -type Tree struct { - TreeCommon - - entries Entries - entriesParsed bool -} - -// ListEntries returns all entries of current tree. -func (t *Tree) ListEntries(ctx context.Context, gitRepo *Repository) (Entries, error) { - if t.entriesParsed { - return t.entries, nil - } - - batch, cancel, err := gitRepo.CatFileBatch() - if err != nil { - return nil, err - } - defer cancel() - - info, rd, err := batch.QueryContent(t.ID.String()) - if err != nil { - return nil, err - } - - if info.Type == "commit" { - treeID, err := ReadTreeID(rd, info.Size) - if err != nil && err != io.EOF { - return nil, err - } - info, rd, err = batch.QueryContent(treeID) - if err != nil { - return nil, err - } - } - if info.Type == "tree" { - t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, info.Size) - if err != nil { - return nil, err - } - t.entriesParsed = true - return t.entries, nil - } - - // Not a tree just use ls-tree instead - if err := DiscardFull(rd, info.Size+1); err != nil { - return nil, err - } - - stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithRepo(gitRepo).RunStdBytes(ctx) - if runErr != nil { - if gitcmd.IsStderr(runErr, gitcmd.StderrNotValidObjectName) || gitcmd.IsStderr(runErr, gitcmd.StderrNotTreeObject) { - return nil, ErrNotExist{ - ID: t.ID.String(), - } - } - return nil, runErr - } - - t.entries, err = parseTreeEntries(stdout, t) - if err == nil { - t.entriesParsed = true - } - - return t.entries, err -} - -// listEntriesRecursive returns all entries of current tree recursively including all subtrees -// extraArgs could be "-l" to get the size, which is slower -func (t *Tree) listEntriesRecursive(ctx context.Context, gitRepo *Repository, extraArgs gitcmd.TrustedCmdArgs) (Entries, error) { - stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-t", "-r"). - AddArguments(extraArgs...). - AddDynamicArguments(t.ID.String()). - WithRepo(gitRepo). - RunStdBytes(ctx) - if runErr != nil { - return nil, runErr - } - - // FIXME: the "name" field is abused, here it is a full path - // FIXME: this ptree is not right, fortunately it isn't really used - return parseTreeEntries(stdout, t) -} - -// ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size -func (t *Tree) ListEntriesRecursiveFast(ctx context.Context, gitRepo *Repository) (Entries, error) { - return t.listEntriesRecursive(ctx, gitRepo, nil) -} - -// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size -func (t *Tree) ListEntriesRecursiveWithSize(ctx context.Context, gitRepo *Repository) (Entries, error) { - return t.listEntriesRecursive(ctx, gitRepo, gitcmd.TrustedCmdArgs{"--long"}) -} diff --git a/modules/lfs/pointer_scanner_nogogit.go b/modules/lfs/pointer_scanner.go similarity index 99% rename from modules/lfs/pointer_scanner_nogogit.go rename to modules/lfs/pointer_scanner.go index 052c878a8db..bcb3cc04566 100644 --- a/modules/lfs/pointer_scanner_nogogit.go +++ b/modules/lfs/pointer_scanner.go @@ -1,8 +1,6 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !gogit - package lfs import ( diff --git a/modules/lfs/pointer_scanner_gogit.go b/modules/lfs/pointer_scanner_gogit.go deleted file mode 100644 index 2e2e5800a6b..00000000000 --- a/modules/lfs/pointer_scanner_gogit.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package lfs - -import ( - "context" - "fmt" - - "gitea.dev/modules/git" - - "github.com/go-git/go-git/v5/plumbing/object" -) - -// SearchPointerBlobs scans the whole repository for LFS pointer files -func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan chan<- PointerBlob) error { - gitRepo := repo.GoGitRepo() - - err := func() error { - blobs, err := gitRepo.BlobObjects() - if err != nil { - return fmt.Errorf("lfs.SearchPointerBlobs BlobObjects: %w", err) - } - - return blobs.ForEach(func(blob *object.Blob) error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - if blob.Size > MetaFileMaxSize { - return nil - } - - reader, err := blob.Reader() - if err != nil { - return fmt.Errorf("lfs.SearchPointerBlobs blob.Reader: %w", err) - } - defer reader.Close() - - pointer, _ := ReadPointer(reader) - if pointer.IsValid() { - pointerChan <- PointerBlob{Hash: blob.Hash.String(), Pointer: pointer} - } - - return nil - }) - }() - - close(pointerChan) - return err -} diff --git a/modules/setting/git.go b/modules/setting/git.go index 744a8af6a99..15572d086d0 100644 --- a/modules/setting/git.go +++ b/modules/setting/git.go @@ -28,7 +28,6 @@ var Git = struct { GCArgs []string `ini:"GC_ARGS" delim:" "` EnableAutoGitWireProtocol bool PullRequestPushMessage bool - LargeObjectThreshold int64 DisableCoreProtectNTFS bool DisablePartialClone bool DiffRenameSimilarityThreshold string @@ -49,7 +48,6 @@ var Git = struct { GCArgs: []string{}, EnableAutoGitWireProtocol: true, PullRequestPushMessage: true, - LargeObjectThreshold: 1024 * 1024, DisablePartialClone: false, DiffRenameSimilarityThreshold: "50%", Timeout: struct { diff --git a/routers/init.go b/routers/init.go index 7770243e434..f59adb6e196 100644 --- a/routers/init.go +++ b/routers/init.go @@ -23,7 +23,6 @@ import ( "gitea.dev/modules/svg" "gitea.dev/modules/system" "gitea.dev/modules/translation" - "gitea.dev/modules/util" "gitea.dev/modules/web" "gitea.dev/modules/web/routing" actions_router "gitea.dev/routers/api/actions" @@ -116,7 +115,7 @@ func InitWebInstalled(ctx context.Context) { mustInit(git.InitFull) log.Info("Git version: %s (home: %s)", git.DefaultFeatures().VersionInfo(), gitcmd.HomeDir()) if !git.DefaultFeatures().SupportHashSha256 { - log.Warn("sha256 hash support is disabled - requires Git >= 2.42." + util.Iif(git.DefaultFeatures().UsingGogit, " Gogit is currently unsupported.", "")) + log.Warn("sha256 hash support is disabled - requires Git >= 2.42.") } // Setup i18n diff --git a/tests/integration/api_repo_git_tags_test.go b/tests/integration/api_repo_git_tags_test.go index 4442ae1ba4f..50f20fb3613 100644 --- a/tests/integration/api_repo_git_tags_test.go +++ b/tests/integration/api_repo_git_tags_test.go @@ -57,15 +57,13 @@ func TestAPIGitTags(t *testing.T) { assert.Equal(t, user.Email, tag.Tagger.Email) assert.Equal(t, repo.APIURL()+"/git/tags/"+aTag.ID.String(), tag.URL) - if !git.DefaultFeatures().UsingGogit { - defer test.MockVariableValue(&git.MaxGitObjectSize, 1024)() - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - tag = DecodeJSON(t, resp, &api.AnnotatedTag{}) - assert.True(t, strings.HasPrefix(aTagMessage, tag.Message)) - assert.Less(t, len(tag.Message), len(aTagMessage)) - assert.Less(t, len(tag.Message), 1024) - } + defer test.MockVariableValue(&git.MaxGitObjectSize, 1024)() + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + tag = DecodeJSON(t, resp, &api.AnnotatedTag{}) + assert.True(t, strings.HasPrefix(aTagMessage, tag.Message)) + assert.Less(t, len(tag.Message), len(aTagMessage)) + assert.Less(t, len(tag.Message), 1024) // Should NOT work for lightweight tags badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, commit.ID.String()). diff --git a/tools/build-release.sh b/tools/build-release.sh index 1004c07d624..89804193048 100755 --- a/tools/build-release.sh +++ b/tools/build-release.sh @@ -13,12 +13,9 @@ RELEASE_PLATFORMS_DEFAULT=( freebsd/amd64 ) -RELEASE_PLATFORMS_GOGIT=(windows/amd64 windows/arm64) - build() { echo "building ${*} ..." local target="${1:?}" - local variant="${2:-}" local goos="${target%%/*}" local goarch="${target##*/}" @@ -28,21 +25,12 @@ build() { goarch="arm" fi - local tags="${RELEASE_TAGS}" - local suffix - if [[ "$variant" == "gogit" ]]; then - tags="gogit${tags:+ ${tags}}" - suffix="-gogit-${target//\//-}" - else - suffix="-${target//\//-}" - fi - - local output="${RELEASE_PATH_PREFIX}${suffix}" + local output="${RELEASE_PATH_PREFIX}-${target//\//-}" local args=() if [[ "$goos" == "windows" ]]; then output="${output}.exe" fi - args+=(-tags "$tags") + args+=(-tags "$RELEASE_TAGS") args+=(-ldflags "$RELEASE_LDFLAGS") echo " args: ${args[*]}" echo " output: ${output}" @@ -61,16 +49,6 @@ main() { build "$target" fi done - - # Only build with gogit for main-nightly, disable gogit for stable releases since there seems no real requirement for them. - # If no real requirement (no user feedbacks), we can completely remove gogit support in the future. - if [ "$VERSION" = "main-nightly" ]; then - for target in "${RELEASE_PLATFORMS_GOGIT[@]}"; do - if [[ -z "$platform" || "$target" == "$platform/"* ]]; then - build "$target" "gogit" - fi - done - fi } main "$@" diff --git a/tools/lint-go-all.go b/tools/lint-go-all.go index 9d6b4156570..51eff26a1f7 100644 --- a/tools/lint-go-all.go +++ b/tools/lint-go-all.go @@ -122,7 +122,7 @@ func main() { if os.Getenv("CI") != "" { // only lint for other platforms when in CI, to keep local lint fast _, _ = fmt.Fprintln(os.Stdout, "lint for windows ...") - succeed = runCmd([]string{"GOOS=windows", "TAGS=gogit"}, "golangci-lint", append([]string{"run", "--build-tags=windows,gogit"}, os.Args[1:]...)) && succeed + succeed = runCmd([]string{"GOOS=windows"}, "golangci-lint", append([]string{"run", "--build-tags=windows"}, os.Args[1:]...)) && succeed } if !succeed {