mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			24 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					cccd54999a | ||
| 
						 | 
					3e7cb5b639 | ||
| 
						 | 
					ec146b4200 | ||
| 
						 | 
					51fa86f0fa | ||
| 
						 | 
					e67b004535 | ||
| 
						 | 
					a4cc867401 | ||
| 
						 | 
					8e5aa8fb1e | ||
| 
						 | 
					046fc8684c | ||
| 
						 | 
					e3e705200a | ||
| 
						 | 
					a9d5ab8f88 | ||
| 
						 | 
					5546b4279c | ||
| 
						 | 
					4f6d09fb68 | ||
| 
						 | 
					4e5aca62ee | ||
| 
						 | 
					030ed9462d | ||
| 
						 | 
					60f175f7ff | ||
| 
						 | 
					260816d64a | ||
| 
						 | 
					a01f56a61a | ||
| 
						 | 
					c01459a504 | ||
| 
						 | 
					6e4e3ca012 | ||
| 
						 | 
					ed0e8865f3 | ||
| 
						 | 
					dc5adce70d | ||
| 
						 | 
					328ce0485f | ||
| 
						 | 
					39b6abf955 | ||
| 
						 | 
					3eda79647b | 
							
								
								
									
										26
									
								
								.github/workflows/release-nightly.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								.github/workflows/release-nightly.yml
									
									
									
									
										vendored
									
									
								
							@@ -59,6 +59,8 @@ jobs:
 | 
			
		||||
          aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
 | 
			
		||||
  nightly-docker-rootful:
 | 
			
		||||
    runs-on: namespace-profile-gitea-release-docker
 | 
			
		||||
    permissions:
 | 
			
		||||
      packages: write # to publish to ghcr.io
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      # fetch all commits instead of only the last as some branches are long lived and could have many between versions
 | 
			
		||||
@@ -85,17 +87,27 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKERHUB_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
			
		||||
      - name: Login to GHCR using PAT
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.repository_owner }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: fetch go modules
 | 
			
		||||
        run: make vendor
 | 
			
		||||
      - name: build rootful docker image
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          platforms: linux/amd64,linux/arm64,linux/riscv64
 | 
			
		||||
          push: true
 | 
			
		||||
          tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
 | 
			
		||||
          tags: |-
 | 
			
		||||
            gitea/gitea:${{ steps.clean_name.outputs.branch }}
 | 
			
		||||
            ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}
 | 
			
		||||
  nightly-docker-rootless:
 | 
			
		||||
    runs-on: namespace-profile-gitea-release-docker
 | 
			
		||||
    permissions:
 | 
			
		||||
      packages: write # to publish to ghcr.io
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      # fetch all commits instead of only the last as some branches are long lived and could have many between versions
 | 
			
		||||
@@ -122,6 +134,12 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKERHUB_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
			
		||||
      - name: Login to GHCR using PAT
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.repository_owner }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: fetch go modules
 | 
			
		||||
        run: make vendor
 | 
			
		||||
      - name: build rootless docker image
 | 
			
		||||
@@ -131,4 +149,6 @@ jobs:
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          push: true
 | 
			
		||||
          file: Dockerfile.rootless
 | 
			
		||||
          tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
 | 
			
		||||
          tags: |-
 | 
			
		||||
            gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
 | 
			
		||||
            ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								.github/workflows/release-tag-rc.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/release-tag-rc.yml
									
									
									
									
										vendored
									
									
								
							@@ -69,6 +69,8 @@ jobs:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
 | 
			
		||||
  docker-rootful:
 | 
			
		||||
    runs-on: namespace-profile-gitea-release-docker
 | 
			
		||||
    permissions:
 | 
			
		||||
      packages: write # to publish to ghcr.io
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      # fetch all commits instead of only the last as some branches are long lived and could have many between versions
 | 
			
		||||
@@ -79,7 +81,9 @@ jobs:
 | 
			
		||||
      - uses: docker/metadata-action@v5
 | 
			
		||||
        id: meta
 | 
			
		||||
        with:
 | 
			
		||||
          images: gitea/gitea
 | 
			
		||||
          images: |-
 | 
			
		||||
            gitea/gitea
 | 
			
		||||
            ghcr.io/go-gitea/gitea
 | 
			
		||||
          flavor: |
 | 
			
		||||
            latest=false
 | 
			
		||||
          # 1.2.3-rc0
 | 
			
		||||
@@ -90,16 +94,24 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKERHUB_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
			
		||||
      - name: Login to GHCR using PAT
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.repository_owner }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: build rootful docker image
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          platforms: linux/amd64,linux/arm64,linux/riscv64
 | 
			
		||||
          push: true
 | 
			
		||||
          tags: ${{ steps.meta.outputs.tags }}
 | 
			
		||||
          labels: ${{ steps.meta.outputs.labels }}
 | 
			
		||||
  docker-rootless:
 | 
			
		||||
    runs-on: namespace-profile-gitea-release-docker
 | 
			
		||||
    permissions:
 | 
			
		||||
      packages: write # to publish to ghcr.io
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      # fetch all commits instead of only the last as some branches are long lived and could have many between versions
 | 
			
		||||
@@ -110,7 +122,9 @@ jobs:
 | 
			
		||||
      - uses: docker/metadata-action@v5
 | 
			
		||||
        id: meta
 | 
			
		||||
        with:
 | 
			
		||||
          images: gitea/gitea
 | 
			
		||||
          images: |-
 | 
			
		||||
            gitea/gitea
 | 
			
		||||
            ghcr.io/go-gitea/gitea
 | 
			
		||||
          # each tag below will have the suffix of -rootless
 | 
			
		||||
          flavor: |
 | 
			
		||||
            latest=false
 | 
			
		||||
@@ -123,11 +137,17 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKERHUB_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
			
		||||
      - name: Login to GHCR using PAT
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.repository_owner }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: build rootless docker image
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          platforms: linux/amd64,linux/arm64,linux/riscv64
 | 
			
		||||
          push: true
 | 
			
		||||
          file: Dockerfile.rootless
 | 
			
		||||
          tags: ${{ steps.meta.outputs.tags }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								.github/workflows/release-tag-version.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/release-tag-version.yml
									
									
									
									
										vendored
									
									
								
							@@ -14,6 +14,8 @@ concurrency:
 | 
			
		||||
jobs:
 | 
			
		||||
  binary:
 | 
			
		||||
    runs-on: namespace-profile-gitea-release-binary
 | 
			
		||||
    permissions:
 | 
			
		||||
      packages: write # to publish to ghcr.io
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      # fetch all commits instead of only the last as some branches are long lived and could have many between versions
 | 
			
		||||
@@ -71,6 +73,8 @@ jobs:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
 | 
			
		||||
  docker-rootful:
 | 
			
		||||
    runs-on: namespace-profile-gitea-release-docker
 | 
			
		||||
    permissions:
 | 
			
		||||
      packages: write # to publish to ghcr.io
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      # fetch all commits instead of only the last as some branches are long lived and could have many between versions
 | 
			
		||||
@@ -81,7 +85,9 @@ jobs:
 | 
			
		||||
      - uses: docker/metadata-action@v5
 | 
			
		||||
        id: meta
 | 
			
		||||
        with:
 | 
			
		||||
          images: gitea/gitea
 | 
			
		||||
          images: |-
 | 
			
		||||
            gitea/gitea
 | 
			
		||||
            ghcr.io/go-gitea/gitea
 | 
			
		||||
          # this will generate tags in the following format:
 | 
			
		||||
          # latest
 | 
			
		||||
          # 1
 | 
			
		||||
@@ -96,11 +102,17 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKERHUB_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
			
		||||
      - name: Login to GHCR using PAT
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.repository_owner }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: build rootful docker image
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          platforms: linux/amd64,linux/arm64,linux/riscv64
 | 
			
		||||
          push: true
 | 
			
		||||
          tags: ${{ steps.meta.outputs.tags }}
 | 
			
		||||
          labels: ${{ steps.meta.outputs.labels }}
 | 
			
		||||
@@ -116,7 +128,9 @@ jobs:
 | 
			
		||||
      - uses: docker/metadata-action@v5
 | 
			
		||||
        id: meta
 | 
			
		||||
        with:
 | 
			
		||||
          images: gitea/gitea
 | 
			
		||||
          images: |-
 | 
			
		||||
            gitea/gitea
 | 
			
		||||
            ghcr.io/go-gitea/gitea
 | 
			
		||||
          # each tag below will have the suffix of -rootless
 | 
			
		||||
          flavor: |
 | 
			
		||||
            suffix=-rootless,onlatest=true
 | 
			
		||||
@@ -134,11 +148,17 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKERHUB_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
			
		||||
      - name: Login to GHCR using PAT
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.repository_owner }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: build rootless docker image
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          platforms: linux/amd64,linux/arm64,linux/riscv64
 | 
			
		||||
          push: true
 | 
			
		||||
          file: Dockerfile.rootless
 | 
			
		||||
          tags: ${{ steps.meta.outputs.tags }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,6 +4,33 @@ This changelog goes through the changes that have been made in each release
 | 
			
		||||
without substantial changes to our git log; to see the highlights of what has
 | 
			
		||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
 | 
			
		||||
 | 
			
		||||
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
 | 
			
		||||
  * Update net package (#34228) (#34232)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix releases sidebar navigation link (#34436) #34439
 | 
			
		||||
  * Fix bug webhook milestone is not right. (#34419) #34429
 | 
			
		||||
  * Fix two missed null value checks on the wiki page. (#34205) (#34215)
 | 
			
		||||
  * Swift files can be passed either as file or as form value (#34068) (#34236)
 | 
			
		||||
  * Fix bug when API get pull changed files for deleted head repository (#34333) (#34368)
 | 
			
		||||
  * Upgrade github v61 -> v71 to fix migrating bug (#34389)
 | 
			
		||||
  * Fix bug when visiting comparation page (#34334) (#34364)
 | 
			
		||||
  * Fix wrong review requests when updating the pull request (#34286) (#34304)
 | 
			
		||||
  * Fix github migration error when using multiple tokens (#34144) (#34302)
 | 
			
		||||
  * Explicitly not update indexes when sync database schemas (#34281) (#34295)
 | 
			
		||||
  * Fix panic when comment is nil (#34257) (#34277)
 | 
			
		||||
  * Fix project board links to related Pull Requests (#34213) (#34222)
 | 
			
		||||
  * Don't assume the default wiki branch is master in the wiki API (#34244) (#34245)
 | 
			
		||||
* DOCUMENTATION
 | 
			
		||||
  * Update token creation API swagger documentation (#34288) (#34296)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Fix CI Build (#34315)
 | 
			
		||||
  * Add riscv64 support (#34199) (#34204)
 | 
			
		||||
  * Bump go version in go.mod (#34160)
 | 
			
		||||
  * remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
 | 
			
		||||
 | 
			
		||||
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
 | 
			
		||||
 | 
			
		||||
* Enhancements
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							@@ -109,7 +109,7 @@ endif
 | 
			
		||||
 | 
			
		||||
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
 | 
			
		||||
 | 
			
		||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
 | 
			
		||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/riscv64
 | 
			
		||||
 | 
			
		||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
 | 
			
		||||
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								assets/go-licenses.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								assets/go-licenses.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -17,7 +17,7 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-github/v61/github"
 | 
			
		||||
	"github.com/google/go-github/v71/github"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
	"gopkg.in/yaml.v3"
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -22,3 +22,8 @@ manifests:
 | 
			
		||||
      architecture: arm64
 | 
			
		||||
      os: linux
 | 
			
		||||
      variant: v8
 | 
			
		||||
  -
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}nightly{{/if}}-linux-riscv64-rootless
 | 
			
		||||
    platform:
 | 
			
		||||
      architecture: riscv64
 | 
			
		||||
      os: linux
 | 
			
		||||
 
 | 
			
		||||
@@ -22,3 +22,8 @@ manifests:
 | 
			
		||||
      architecture: arm64
 | 
			
		||||
      os: linux
 | 
			
		||||
      variant: v8
 | 
			
		||||
  -
 | 
			
		||||
    image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}nightly{{/if}}-linux-riscv64
 | 
			
		||||
    platform:
 | 
			
		||||
      architecture: riscv64
 | 
			
		||||
      os: linux
 | 
			
		||||
 
 | 
			
		||||
@@ -31,16 +31,19 @@ if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then
 | 
			
		||||
  SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -e /data/ssh/ssh_host_ed25519-cert.pub ]; then
 | 
			
		||||
  SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519-cert.pub"}
 | 
			
		||||
# In case someone wants to sign the `{keyname}.pub` key by `ssh-keygen -s ca -I identity ...` to
 | 
			
		||||
# make use of the ssh-key certificate authority feature (see ssh-keygen CERTIFICATES section),
 | 
			
		||||
# the generated key file name is `{keyname}-cert.pub`
 | 
			
		||||
if [ -e /data/ssh/ssh_host_ed25519_key-cert.pub ]; then
 | 
			
		||||
  SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_key-cert.pub"}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -e /data/ssh/ssh_host_rsa-cert.pub ]; then
 | 
			
		||||
  SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa-cert.pub"}
 | 
			
		||||
if [ -e /data/ssh/ssh_host_rsa_key-cert.pub ]; then
 | 
			
		||||
  SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_key-cert.pub"}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -e /data/ssh/ssh_host_ecdsa-cert.pub ]; then
 | 
			
		||||
  SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa-cert.pub"}
 | 
			
		||||
if [ -e /data/ssh/ssh_host_ecdsa_key-cert.pub ]; then
 | 
			
		||||
  SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_key-cert.pub"}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -d /etc/ssh ]; then
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
module code.gitea.io/gitea
 | 
			
		||||
 | 
			
		||||
go 1.23.6
 | 
			
		||||
go 1.23.8
 | 
			
		||||
 | 
			
		||||
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
 | 
			
		||||
// But some CAs use negative serial number, just relax the check. related:
 | 
			
		||||
@@ -65,7 +65,7 @@ require (
 | 
			
		||||
	github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
 | 
			
		||||
	github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
 | 
			
		||||
	github.com/golang-jwt/jwt/v5 v5.2.2
 | 
			
		||||
	github.com/google/go-github/v61 v61.0.0
 | 
			
		||||
	github.com/google/go-github/v71 v71.0.0
 | 
			
		||||
	github.com/google/licenseclassifier/v2 v2.0.0
 | 
			
		||||
	github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db
 | 
			
		||||
	github.com/google/uuid v1.6.0
 | 
			
		||||
@@ -120,7 +120,7 @@ require (
 | 
			
		||||
	github.com/yuin/goldmark-meta v1.1.0
 | 
			
		||||
	golang.org/x/crypto v0.36.0
 | 
			
		||||
	golang.org/x/image v0.21.0
 | 
			
		||||
	golang.org/x/net v0.37.0
 | 
			
		||||
	golang.org/x/net v0.38.0
 | 
			
		||||
	golang.org/x/oauth2 v0.27.0
 | 
			
		||||
	golang.org/x/sync v0.12.0
 | 
			
		||||
	golang.org/x/sys v0.31.0
 | 
			
		||||
@@ -325,6 +325,8 @@ replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-tra
 | 
			
		||||
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
 | 
			
		||||
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
 | 
			
		||||
 | 
			
		||||
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078
 | 
			
		||||
 | 
			
		||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
 | 
			
		||||
 | 
			
		||||
exclude github.com/gofrs/uuid v4.0.0+incompatible
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								go.sum
									
									
									
									
									
								
							@@ -14,12 +14,12 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
 | 
			
		||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 | 
			
		||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 | 
			
		||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 | 
			
		||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
 | 
			
		||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
 | 
			
		||||
gitea.com/gitea/act v0.261.3 h1:BhiYpGJQKGq0XMYYICCYAN4KnsEWHyLbA6dxhZwFcV4=
 | 
			
		||||
gitea.com/gitea/act v0.261.3/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
 | 
			
		||||
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
 | 
			
		||||
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
 | 
			
		||||
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
 | 
			
		||||
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
 | 
			
		||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
 | 
			
		||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
 | 
			
		||||
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
 | 
			
		||||
@@ -410,10 +410,11 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 | 
			
		||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 | 
			
		||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
			
		||||
github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go=
 | 
			
		||||
github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
 | 
			
		||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 | 
			
		||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 | 
			
		||||
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
 | 
			
		||||
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
 | 
			
		||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
 | 
			
		||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 | 
			
		||||
github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
 | 
			
		||||
@@ -870,8 +871,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 | 
			
		||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 | 
			
		||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 | 
			
		||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 | 
			
		||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
 | 
			
		||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
 | 
			
		||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
 | 
			
		||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
 | 
			
		||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
 | 
			
		||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
 | 
			
		||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
 
 | 
			
		||||
@@ -663,7 +663,7 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if review != nil {
 | 
			
		||||
		// skip it when reviewer hase been request to review
 | 
			
		||||
		// skip it when reviewer has been request to review
 | 
			
		||||
		if review.Type == ReviewTypeRequest {
 | 
			
		||||
			return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,5 +14,9 @@ func AddContentVersionToIssueAndComment(x *xorm.Engine) error {
 | 
			
		||||
		ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return x.Sync(new(Comment), new(Issue))
 | 
			
		||||
	_, err := x.SyncWithOptions(xorm.SyncOptions{
 | 
			
		||||
		IgnoreConstrains: true,
 | 
			
		||||
		IgnoreIndices:    true,
 | 
			
		||||
	}, new(Comment), new(Issue))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,5 +13,9 @@ func AddForcePushBranchProtection(x *xorm.Engine) error {
 | 
			
		||||
		ForcePushAllowlistTeamIDs    []int64 `xorm:"JSON TEXT"`
 | 
			
		||||
		ForcePushAllowlistDeployKeys bool    `xorm:"NOT NULL DEFAULT false"`
 | 
			
		||||
	}
 | 
			
		||||
	return x.Sync(new(ProtectedBranch))
 | 
			
		||||
	_, err := x.SyncWithOptions(xorm.SyncOptions{
 | 
			
		||||
		IgnoreConstrains: true,
 | 
			
		||||
		IgnoreIndices:    true,
 | 
			
		||||
	}, new(ProtectedBranch))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,5 +10,9 @@ func AddSkipSecondaryAuthColumnToOAuth2ApplicationTable(x *xorm.Engine) error {
 | 
			
		||||
	type oauth2Application struct {
 | 
			
		||||
		SkipSecondaryAuthorization bool `xorm:"NOT NULL DEFAULT FALSE"`
 | 
			
		||||
	}
 | 
			
		||||
	return x.Sync(new(oauth2Application))
 | 
			
		||||
	_, err := x.SyncWithOptions(xorm.SyncOptions{
 | 
			
		||||
		IgnoreConstrains: true,
 | 
			
		||||
		IgnoreIndices:    true,
 | 
			
		||||
	}, new(oauth2Application))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,5 +19,9 @@ func AddCommentMetaDataColumn(x *xorm.Engine) error {
 | 
			
		||||
		CommentMetaData *CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return x.Sync(new(Comment))
 | 
			
		||||
	_, err := x.SyncWithOptions(xorm.SyncOptions{
 | 
			
		||||
		IgnoreConstrains: true,
 | 
			
		||||
		IgnoreIndices:    true,
 | 
			
		||||
	}, new(Comment))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,5 +9,9 @@ func AddBlockAdminMergeOverrideBranchProtection(x *xorm.Engine) error {
 | 
			
		||||
	type ProtectedBranch struct {
 | 
			
		||||
		BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
 | 
			
		||||
	}
 | 
			
		||||
	return x.Sync(new(ProtectedBranch))
 | 
			
		||||
	_, err := x.SyncWithOptions(xorm.SyncOptions{
 | 
			
		||||
		IgnoreConstrains: true,
 | 
			
		||||
		IgnoreIndices:    true,
 | 
			
		||||
	}, new(ProtectedBranch))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,5 +12,9 @@ func AddPriorityToProtectedBranch(x *xorm.Engine) error {
 | 
			
		||||
		Priority int64 `xorm:"NOT NULL DEFAULT 0"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return x.Sync(new(ProtectedBranch))
 | 
			
		||||
	_, err := x.SyncWithOptions(xorm.SyncOptions{
 | 
			
		||||
		IgnoreConstrains: true,
 | 
			
		||||
		IgnoreIndices:    true,
 | 
			
		||||
	}, new(ProtectedBranch))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,9 @@ func AddTimeEstimateColumnToIssueTable(x *xorm.Engine) error {
 | 
			
		||||
	type Issue struct {
 | 
			
		||||
		TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return x.Sync(new(Issue))
 | 
			
		||||
	_, err := x.SyncWithOptions(xorm.SyncOptions{
 | 
			
		||||
		IgnoreConstrains: true,
 | 
			
		||||
		IgnoreIndices:    true,
 | 
			
		||||
	}, new(Issue))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -279,9 +279,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
 | 
			
		||||
	default:
 | 
			
		||||
		e.Desc("package_version.created_unix")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sort by id for stable order with duplicates in the other field
 | 
			
		||||
	e.Asc("package_version.id")
 | 
			
		||||
	e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchVersions gets all versions of packages matching the search options
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,11 @@ type AccessToken struct {
 | 
			
		||||
type AccessTokenList []*AccessToken
 | 
			
		||||
 | 
			
		||||
// CreateAccessTokenOption options when create access token
 | 
			
		||||
// swagger:model CreateAccessTokenOption
 | 
			
		||||
type CreateAccessTokenOption struct {
 | 
			
		||||
	// required: true
 | 
			
		||||
	Name   string   `json:"name" binding:"Required"`
 | 
			
		||||
	Name string `json:"name" binding:"Required"`
 | 
			
		||||
	// example: ["all", "read:activitypub","read:issue", "write:misc", "read:notification", "read:organization", "read:package", "read:repository", "read:user"]
 | 
			
		||||
	Scopes []string `json:"scopes"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
applet
 | 
			
		||||
application.linux-arm64
 | 
			
		||||
application.linux-armv6hf
 | 
			
		||||
application.linux-riscv64
 | 
			
		||||
application.linux32
 | 
			
		||||
application.linux64
 | 
			
		||||
application.windows32
 | 
			
		||||
 
 | 
			
		||||
@@ -290,7 +290,24 @@ func DownloadManifest(ctx *context.Context) {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
 | 
			
		||||
// formFileOptionalReadCloser returns (nil, nil) if the formKey is not present.
 | 
			
		||||
func formFileOptionalReadCloser(ctx *context.Context, formKey string) (io.ReadCloser, error) {
 | 
			
		||||
	multipartFile, _, err := ctx.Req.FormFile(formKey)
 | 
			
		||||
	if err != nil && !errors.Is(err, http.ErrMissingFile) {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if multipartFile != nil {
 | 
			
		||||
		return multipartFile, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	content := ctx.Req.FormValue(formKey)
 | 
			
		||||
	if content == "" {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	return io.NopCloser(strings.NewReader(ctx.Req.FormValue(formKey))), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UploadPackageFile refers to https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
 | 
			
		||||
func UploadPackageFile(ctx *context.Context) {
 | 
			
		||||
	packageScope := ctx.PathParam("scope")
 | 
			
		||||
	packageName := ctx.PathParam("name")
 | 
			
		||||
@@ -304,9 +321,9 @@ func UploadPackageFile(ctx *context.Context) {
 | 
			
		||||
 | 
			
		||||
	packageVersion := v.Core().String()
 | 
			
		||||
 | 
			
		||||
	file, _, err := ctx.Req.FormFile("source-archive")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		apiError(ctx, http.StatusBadRequest, err)
 | 
			
		||||
	file, err := formFileOptionalReadCloser(ctx, "source-archive")
 | 
			
		||||
	if file == nil || err != nil {
 | 
			
		||||
		apiError(ctx, http.StatusBadRequest, "unable to read source-archive file")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
@@ -318,10 +335,13 @@ func UploadPackageFile(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	defer buf.Close()
 | 
			
		||||
 | 
			
		||||
	var mr io.Reader
 | 
			
		||||
	metadata := ctx.Req.FormValue("metadata")
 | 
			
		||||
	if metadata != "" {
 | 
			
		||||
		mr = strings.NewReader(metadata)
 | 
			
		||||
	mr, err := formFileOptionalReadCloser(ctx, "metadata")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		apiError(ctx, http.StatusBadRequest, "unable to read metadata file")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if mr != nil {
 | 
			
		||||
		defer mr.Close()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pck, err := swift_module.ParsePackage(buf, buf.Size(), mr)
 | 
			
		||||
 
 | 
			
		||||
@@ -895,6 +895,15 @@ func EditIssue(ctx *context.APIContext) {
 | 
			
		||||
		issue.MilestoneID != *form.Milestone {
 | 
			
		||||
		oldMilestoneID := issue.MilestoneID
 | 
			
		||||
		issue.MilestoneID = *form.Milestone
 | 
			
		||||
		if issue.MilestoneID > 0 {
 | 
			
		||||
			issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, *form.Milestone)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			issue.Milestone = nil
 | 
			
		||||
		}
 | 
			
		||||
		if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
 | 
			
		||||
			return
 | 
			
		||||
 
 | 
			
		||||
@@ -694,6 +694,11 @@ func EditPullRequest(ctx *context.APIContext) {
 | 
			
		||||
		issue.MilestoneID != form.Milestone {
 | 
			
		||||
		oldMilestoneID := issue.MilestoneID
 | 
			
		||||
		issue.MilestoneID = form.Milestone
 | 
			
		||||
		issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
 | 
			
		||||
			return
 | 
			
		||||
@@ -1638,7 +1643,9 @@ func GetPullRequestFiles(ctx *context.APIContext) {
 | 
			
		||||
 | 
			
		||||
	apiFiles := make([]*api.ChangedFile, 0, limit)
 | 
			
		||||
	for i := start; i < start+limit; i++ {
 | 
			
		||||
		apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
 | 
			
		||||
		// refs/pull/1/head stores the HEAD commit ID, allowing all related commits to be found in the base repository.
 | 
			
		||||
		// The head repository might have been deleted, so we should not rely on it here.
 | 
			
		||||
		apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.BaseRepo, endCommitID))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
 | 
			
		||||
 
 | 
			
		||||
@@ -193,7 +193,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get commit count - wiki revisions
 | 
			
		||||
	commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
 | 
			
		||||
	commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
 | 
			
		||||
 | 
			
		||||
	// Get last change information.
 | 
			
		||||
	lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
 | 
			
		||||
@@ -432,7 +432,7 @@ func ListPageRevisions(ctx *context.APIContext) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get commit count - wiki revisions
 | 
			
		||||
	commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
 | 
			
		||||
	commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
 | 
			
		||||
 | 
			
		||||
	page := ctx.FormInt("page")
 | 
			
		||||
	if page <= 1 {
 | 
			
		||||
@@ -442,7 +442,7 @@ func ListPageRevisions(ctx *context.APIContext) {
 | 
			
		||||
	// get Commit Count
 | 
			
		||||
	commitsHistory, err := wikiRepo.CommitsByFileAndRange(
 | 
			
		||||
		git.CommitsByFileAndRangeOptions{
 | 
			
		||||
			Revision: "master",
 | 
			
		||||
			Revision: ctx.Repo.Repository.DefaultWikiBranch,
 | 
			
		||||
			File:     pageFilename,
 | 
			
		||||
			Page:     page,
 | 
			
		||||
		})
 | 
			
		||||
@@ -486,7 +486,7 @@ func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit)
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	commit, err := wikiRepo.GetBranchCommit("master")
 | 
			
		||||
	commit, err := wikiRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if git.IsErrNotExist(err) {
 | 
			
		||||
			ctx.NotFound(err)
 | 
			
		||||
 
 | 
			
		||||
@@ -81,6 +81,7 @@ func ServCommand(ctx *context.PrivateContext) {
 | 
			
		||||
	ownerName := ctx.PathParam(":owner")
 | 
			
		||||
	repoName := ctx.PathParam(":repo")
 | 
			
		||||
	mode := perm.AccessMode(ctx.FormInt("mode"))
 | 
			
		||||
	verb := ctx.FormString("verb")
 | 
			
		||||
 | 
			
		||||
	// Set the basic parts of the results to return
 | 
			
		||||
	results := private.ServCommandResults{
 | 
			
		||||
@@ -295,8 +296,11 @@ func ServCommand(ctx *context.PrivateContext) {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// Because of the special ref "refs/for" we will need to delay write permission check
 | 
			
		||||
			if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode {
 | 
			
		||||
			// Because of the special ref "refs/for" (AGit) we will need to delay write permission check,
 | 
			
		||||
			// AGit flow needs to write its own ref when the doer has "reader" permission (allowing to create PR).
 | 
			
		||||
			// The real permission check is done in HookPreReceive (routers/private/hook_pre_receive.go).
 | 
			
		||||
			// Here it should relax the permission check for "git push (git-receive-pack)", but not for others like LFS operations.
 | 
			
		||||
			if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode && verb == "git-receive-pack" {
 | 
			
		||||
				mode = perm.AccessModeRead
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -405,7 +405,6 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo {
 | 
			
		||||
			ctx.ServerError("OpenRepository", err)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		defer ci.HeadGitRepo.Close()
 | 
			
		||||
	} else {
 | 
			
		||||
		ctx.NotFound("ParseCompareInfo", nil)
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -708,7 +707,7 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
 | 
			
		||||
func CompareDiff(ctx *context.Context) {
 | 
			
		||||
	ci := ParseCompareInfo(ctx)
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if ci != nil && ci.HeadGitRepo != nil {
 | 
			
		||||
		if !ctx.Repo.PullRequest.SameRepo && ci != nil && ci.HeadGitRepo != nil {
 | 
			
		||||
			ci.HeadGitRepo.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 
 | 
			
		||||
@@ -418,6 +418,16 @@ func UpdateIssueMilestone(ctx *context.Context) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		issue.MilestoneID = milestoneID
 | 
			
		||||
		if milestoneID > 0 {
 | 
			
		||||
			var err error
 | 
			
		||||
			issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetMilestoneByRepoID", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			issue.Milestone = nil
 | 
			
		||||
		}
 | 
			
		||||
		if err := issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
 | 
			
		||||
			ctx.ServerError("ChangeMilestoneAssign", err)
 | 
			
		||||
			return
 | 
			
		||||
 
 | 
			
		||||
@@ -1263,7 +1263,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
 | 
			
		||||
 | 
			
		||||
	ci := ParseCompareInfo(ctx)
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if ci != nil && ci.HeadGitRepo != nil {
 | 
			
		||||
		if !ctx.Repo.PullRequest.SameRepo && ci != nil && ci.HeadGitRepo != nil {
 | 
			
		||||
			ci.HeadGitRepo.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 
 | 
			
		||||
@@ -48,10 +48,6 @@ func IsCodeOwnerFile(f string) bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
 | 
			
		||||
	return PullRequestCodeOwnersReviewSpecialCommits(ctx, pr, "", "") // no commit is provided, then it uses PR's base&head branch
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func PullRequestCodeOwnersReviewSpecialCommits(ctx context.Context, pr *issues_model.PullRequest, startCommitID, endCommitID string) ([]*ReviewRequestNotifier, error) {
 | 
			
		||||
	if err := pr.LoadIssue(ctx); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -100,19 +96,15 @@ func PullRequestCodeOwnersReviewSpecialCommits(ctx context.Context, pr *issues_m
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if startCommitID == "" && endCommitID == "" {
 | 
			
		||||
		// get the mergebase
 | 
			
		||||
		mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		startCommitID = mergeBase
 | 
			
		||||
		endCommitID = pr.GetGitRefName()
 | 
			
		||||
	// get the mergebase
 | 
			
		||||
	mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// https://github.com/go-gitea/gitea/issues/29763, we need to get the files changed
 | 
			
		||||
	// between the merge base and the head commit but not the base branch and the head commit
 | 
			
		||||
	changedFiles, err := repo.GetFilesChangedBetween(startCommitID, endCommitID)
 | 
			
		||||
	changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.GetGitRefName())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -138,13 +130,31 @@ func PullRequestCodeOwnersReviewSpecialCommits(ctx context.Context, pr *issues_m
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// load all reviews from database
 | 
			
		||||
	latestReivews, _, err := issues_model.GetReviewsByIssueID(ctx, pr.IssueID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contain := func(list issues_model.ReviewList, u *user_model.User) bool {
 | 
			
		||||
		for _, review := range list {
 | 
			
		||||
			if review.ReviewerTeamID == 0 && review.ReviewerID == u.ID {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, u := range uniqUsers {
 | 
			
		||||
		if u.ID != issue.Poster.ID {
 | 
			
		||||
		if u.ID != issue.Poster.ID && !contain(latestReivews, u) {
 | 
			
		||||
			comment, err := issues_model.AddReviewRequest(ctx, issue, u, issue.Poster)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			if comment == nil { // comment maybe nil if review type is ReviewTypeRequest
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			notifiers = append(notifiers, &ReviewRequestNotifier{
 | 
			
		||||
				Comment:  comment,
 | 
			
		||||
				IsAdd:    true,
 | 
			
		||||
@@ -152,12 +162,16 @@ func PullRequestCodeOwnersReviewSpecialCommits(ctx context.Context, pr *issues_m
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, t := range uniqTeams {
 | 
			
		||||
		comment, err := issues_model.AddTeamReviewRequest(ctx, issue, t, issue.Poster)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if comment == nil { // comment maybe nil if review type is ReviewTypeRequest
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		notifiers = append(notifiers, &ReviewRequestNotifier{
 | 
			
		||||
			Comment:    comment,
 | 
			
		||||
			IsAdd:      true,
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ package migrations
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-github/v61/github"
 | 
			
		||||
	"github.com/google/go-github/v71/github"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ErrRepoNotCreated returns the error that repository not created
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/proxy"
 | 
			
		||||
	"code.gitea.io/gitea/modules/structs"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-github/v61/github"
 | 
			
		||||
	"github.com/google/go-github/v71/github"
 | 
			
		||||
	"golang.org/x/oauth2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -135,7 +135,7 @@ func (g *GithubDownloaderV3) LogString() string {
 | 
			
		||||
func (g *GithubDownloaderV3) addClient(client *http.Client, baseURL string) {
 | 
			
		||||
	githubClient := github.NewClient(client)
 | 
			
		||||
	if baseURL != "https://github.com" {
 | 
			
		||||
		githubClient, _ = github.NewClient(client).WithEnterpriseURLs(baseURL, baseURL)
 | 
			
		||||
		githubClient, _ = githubClient.WithEnterpriseURLs(baseURL, baseURL)
 | 
			
		||||
	}
 | 
			
		||||
	g.clients = append(g.clients, githubClient)
 | 
			
		||||
	g.rates = append(g.rates, nil)
 | 
			
		||||
@@ -448,9 +448,11 @@ func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool,
 | 
			
		||||
		if !g.SkipReactions {
 | 
			
		||||
			for i := 1; ; i++ {
 | 
			
		||||
				g.waitAndPickClient()
 | 
			
		||||
				res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListOptions{
 | 
			
		||||
					Page:    i,
 | 
			
		||||
					PerPage: perPage,
 | 
			
		||||
				res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListReactionOptions{
 | 
			
		||||
					ListOptions: github.ListOptions{
 | 
			
		||||
						Page:    i,
 | 
			
		||||
						PerPage: perPage,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, false, err
 | 
			
		||||
@@ -534,9 +536,11 @@ func (g *GithubDownloaderV3) getComments(commentable base.Commentable) ([]*base.
 | 
			
		||||
			if !g.SkipReactions {
 | 
			
		||||
				for i := 1; ; i++ {
 | 
			
		||||
					g.waitAndPickClient()
 | 
			
		||||
					res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
 | 
			
		||||
						Page:    i,
 | 
			
		||||
						PerPage: g.maxPerPage,
 | 
			
		||||
					res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListReactionOptions{
 | 
			
		||||
						ListOptions: github.ListOptions{
 | 
			
		||||
							Page:    i,
 | 
			
		||||
							PerPage: g.maxPerPage,
 | 
			
		||||
						},
 | 
			
		||||
					})
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return nil, err
 | 
			
		||||
@@ -609,9 +613,11 @@ func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment,
 | 
			
		||||
		if !g.SkipReactions {
 | 
			
		||||
			for i := 1; ; i++ {
 | 
			
		||||
				g.waitAndPickClient()
 | 
			
		||||
				res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
 | 
			
		||||
					Page:    i,
 | 
			
		||||
					PerPage: g.maxPerPage,
 | 
			
		||||
				res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListReactionOptions{
 | 
			
		||||
					ListOptions: github.ListOptions{
 | 
			
		||||
						Page:    i,
 | 
			
		||||
						PerPage: g.maxPerPage,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, false, err
 | 
			
		||||
@@ -680,9 +686,11 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
 | 
			
		||||
		if !g.SkipReactions {
 | 
			
		||||
			for i := 1; ; i++ {
 | 
			
		||||
				g.waitAndPickClient()
 | 
			
		||||
				res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListOptions{
 | 
			
		||||
					Page:    i,
 | 
			
		||||
					PerPage: perPage,
 | 
			
		||||
				res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListReactionOptions{
 | 
			
		||||
					ListOptions: github.ListOptions{
 | 
			
		||||
						Page:    i,
 | 
			
		||||
						PerPage: perPage,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, false, err
 | 
			
		||||
@@ -767,9 +775,11 @@ func (g *GithubDownloaderV3) convertGithubReviewComments(cs []*github.PullReques
 | 
			
		||||
		if !g.SkipReactions {
 | 
			
		||||
			for i := 1; ; i++ {
 | 
			
		||||
				g.waitAndPickClient()
 | 
			
		||||
				res, resp, err := g.getClient().Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListOptions{
 | 
			
		||||
					Page:    i,
 | 
			
		||||
					PerPage: g.maxPerPage,
 | 
			
		||||
				res, resp, err := g.getClient().Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListReactionOptions{
 | 
			
		||||
					ListOptions: github.ListOptions{
 | 
			
		||||
						Page:    i,
 | 
			
		||||
						PerPage: g.maxPerPage,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
@@ -879,3 +889,18 @@ func (g *GithubDownloaderV3) GetReviews(reviewable base.Reviewable) ([]*base.Rev
 | 
			
		||||
	}
 | 
			
		||||
	return allReviews, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatCloneURL add authentication into remote URLs
 | 
			
		||||
func (g *GithubDownloaderV3) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) {
 | 
			
		||||
	u, err := url.Parse(remoteAddr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if len(opts.AuthToken) > 0 {
 | 
			
		||||
		// "multiple tokens" are used to benefit more "API rate limit quota"
 | 
			
		||||
		// git clone doesn't count for rate limits, so only use the first token.
 | 
			
		||||
		// source: https://github.com/orgs/community/discussions/44515
 | 
			
		||||
		u.User = url.UserPassword("oauth2", strings.Split(opts.AuthToken, ",")[0])
 | 
			
		||||
	}
 | 
			
		||||
	return u.String(), nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import (
 | 
			
		||||
	base "code.gitea.io/gitea/modules/migration"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGitHubDownloadRepo(t *testing.T) {
 | 
			
		||||
@@ -429,3 +430,36 @@ func TestGitHubDownloadRepo(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}, reviews)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGithubMultiToken(t *testing.T) {
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		desc             string
 | 
			
		||||
		token            string
 | 
			
		||||
		expectedCloneURL string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			desc:             "Single Token",
 | 
			
		||||
			token:            "single_token",
 | 
			
		||||
			expectedCloneURL: "https://oauth2:single_token@github.com",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			desc:             "Multi Token",
 | 
			
		||||
			token:            "token1,token2",
 | 
			
		||||
			expectedCloneURL: "https://oauth2:token1@github.com",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	factory := GithubDownloaderV3Factory{}
 | 
			
		||||
 | 
			
		||||
	for _, tC := range testCases {
 | 
			
		||||
		t.Run(tC.desc, func(t *testing.T) {
 | 
			
		||||
			opts := base.MigrateOptions{CloneAddr: "https://github.com/go-gitea/gitea", AuthToken: tC.token}
 | 
			
		||||
			client, err := factory.New(context.Background(), opts)
 | 
			
		||||
			require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
			cloneURL, err := client.FormatCloneURL(opts, "https://github.com")
 | 
			
		||||
			require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
			assert.Equal(t, tC.expectedCloneURL, cloneURL)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -440,12 +440,7 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if !pr.IsWorkInProgress(ctx) {
 | 
			
		||||
						var reviewNotifiers []*issue_service.ReviewRequestNotifier
 | 
			
		||||
						if opts.IsForcePush {
 | 
			
		||||
							reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(ctx, pr)
 | 
			
		||||
						} else {
 | 
			
		||||
							reviewNotifiers, err = issue_service.PullRequestCodeOwnersReviewSpecialCommits(ctx, pr, opts.OldCommitID, opts.NewCommitID)
 | 
			
		||||
						}
 | 
			
		||||
						reviewNotifiers, err := issue_service.PullRequestCodeOwnersReview(ctx, pr)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							log.Error("PullRequestCodeOwnersReview: %v", err)
 | 
			
		||||
						}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<button class="ui primary button js-btn-clone-panel">
 | 
			
		||||
	{{svg "octicon-code" 16}}
 | 
			
		||||
	<span>Code</span>
 | 
			
		||||
	<span>{{ctx.Locale.Tr "repo.code"}}</span>
 | 
			
		||||
	{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
</button>
 | 
			
		||||
<div class="clone-panel-popup tippy-target">
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
		<div class="flex-item">
 | 
			
		||||
			<div class="flex-item-main">
 | 
			
		||||
				<div class="flex-item-title">
 | 
			
		||||
					<a class="item muted" href="{{.Link}}/releases">
 | 
			
		||||
					<a class="item muted" href="{{.RepoLink}}/releases">
 | 
			
		||||
						{{ctx.Locale.Tr "repo.releases"}}
 | 
			
		||||
						<span class="ui small label">{{.NumReleases}}</span>
 | 
			
		||||
					</a>
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@
 | 
			
		||||
		{{if $.Page.LinkedPRs}}
 | 
			
		||||
		{{range index $.Page.LinkedPRs .ID}}
 | 
			
		||||
		<div class="meta tw-my-1">
 | 
			
		||||
			<a href="{{$.Issue.Repo.Link}}/pulls/{{.Index}}">
 | 
			
		||||
			<a href="{{.Repo.Link}}/pulls/{{.Index}}">
 | 
			
		||||
				<span class="tw-m-0 text {{if .PullRequest.HasMerged}}purple{{else if .IsClosed}}red{{else}}green{{end}}">{{svg "octicon-git-merge" 16 "tw-mr-1 tw-align-middle"}}</span>
 | 
			
		||||
				<span class="tw-align-middle">{{.Title}} <span class="text light grey">#{{.Index}}</span></span>
 | 
			
		||||
			</a>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
		<div class="ui stackable grid">
 | 
			
		||||
			<div class="ui eight wide column">
 | 
			
		||||
				<div class="ui header">
 | 
			
		||||
					<a class="file-revisions-btn ui basic button" title="{{ctx.Locale.Tr "repo.wiki.back_to_wiki"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}"><span>{{.revision}}</span> {{svg "octicon-home"}}</a>
 | 
			
		||||
					<a class="file-revisions-btn ui basic button" title="{{ctx.Locale.Tr "repo.wiki.back_to_wiki"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}">{{if .revision}}<span>{{.revision}}</span> {{end}}{{svg "octicon-home"}}</a>
 | 
			
		||||
					{{$title}}
 | 
			
		||||
					<div class="ui sub header tw-break-anywhere">
 | 
			
		||||
						{{$timeSince := DateUtils.TimeSince .Author.When}}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
		<div class="ui dividing header">
 | 
			
		||||
			<div class="flex-text-block tw-flex-wrap tw-justify-end">
 | 
			
		||||
				<div class="flex-text-block tw-flex-1 tw-min-w-[300px]">
 | 
			
		||||
					<a class="file-revisions-btn ui basic button" title="{{ctx.Locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" ><span>{{.CommitCount}}</span> {{svg "octicon-history"}}</a>
 | 
			
		||||
					<a class="file-revisions-btn ui basic button" title="{{ctx.Locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" >{{if .CommitCount}}<span>{{.CommitCount}}</span> {{end}}{{svg "octicon-history"}}</a>
 | 
			
		||||
					<div class="tw-flex-1 gt-ellipsis">
 | 
			
		||||
						{{$title}}
 | 
			
		||||
						<div class="ui sub header gt-ellipsis">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							@@ -19616,7 +19616,18 @@
 | 
			
		||||
          "items": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "x-go-name": "Scopes"
 | 
			
		||||
          "x-go-name": "Scopes",
 | 
			
		||||
          "example": [
 | 
			
		||||
            "all",
 | 
			
		||||
            "read:activitypub",
 | 
			
		||||
            "read:issue",
 | 
			
		||||
            "write:misc",
 | 
			
		||||
            "read:notification",
 | 
			
		||||
            "read:organization",
 | 
			
		||||
            "read:package",
 | 
			
		||||
            "read:repository",
 | 
			
		||||
            "read:user"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/tests"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPackageSwift(t *testing.T) {
 | 
			
		||||
@@ -34,6 +35,7 @@ func TestPackageSwift(t *testing.T) {
 | 
			
		||||
	packageName := "test_package"
 | 
			
		||||
	packageID := packageScope + "." + packageName
 | 
			
		||||
	packageVersion := "1.0.3"
 | 
			
		||||
	packageVersion2 := "1.0.4"
 | 
			
		||||
	packageAuthor := "KN4CK3R"
 | 
			
		||||
	packageDescription := "Gitea Test Package"
 | 
			
		||||
	packageRepositoryURL := "https://gitea.io/gitea/gitea"
 | 
			
		||||
@@ -183,6 +185,94 @@ func TestPackageSwift(t *testing.T) {
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("UploadMultipart", func(t *testing.T) {
 | 
			
		||||
		defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
		uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) {
 | 
			
		||||
			var body bytes.Buffer
 | 
			
		||||
			mpw := multipart.NewWriter(&body)
 | 
			
		||||
 | 
			
		||||
			// Read the source archive content
 | 
			
		||||
			sourceContent, err := io.ReadAll(sr)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			mpw.WriteField("source-archive", string(sourceContent))
 | 
			
		||||
 | 
			
		||||
			if metadata != "" {
 | 
			
		||||
				mpw.WriteField("metadata", metadata)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			mpw.Close()
 | 
			
		||||
 | 
			
		||||
			req := NewRequestWithBody(t, "PUT", url, &body).
 | 
			
		||||
				SetHeader("Content-Type", mpw.FormDataContentType()).
 | 
			
		||||
				SetHeader("Accept", swift_router.AcceptJSON).
 | 
			
		||||
				AddBasicAuth(user.Name)
 | 
			
		||||
			MakeRequest(t, req, expectedStatus)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		createArchive := func(files map[string]string) *bytes.Buffer {
 | 
			
		||||
			var buf bytes.Buffer
 | 
			
		||||
			zw := zip.NewWriter(&buf)
 | 
			
		||||
			for filename, content := range files {
 | 
			
		||||
				w, _ := zw.Create(filename)
 | 
			
		||||
				w.Write([]byte(content))
 | 
			
		||||
			}
 | 
			
		||||
			zw.Close()
 | 
			
		||||
			return &buf
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2)
 | 
			
		||||
 | 
			
		||||
		req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{}))
 | 
			
		||||
		MakeRequest(t, req, http.StatusUnauthorized)
 | 
			
		||||
 | 
			
		||||
		// Test with metadata as form field
 | 
			
		||||
		uploadPackage(
 | 
			
		||||
			t,
 | 
			
		||||
			uploadURL,
 | 
			
		||||
			http.StatusCreated,
 | 
			
		||||
			createArchive(map[string]string{
 | 
			
		||||
				"Package.swift":           contentManifest1,
 | 
			
		||||
				"Package@swift-5.6.swift": contentManifest2,
 | 
			
		||||
			}),
 | 
			
		||||
			`{"name":"`+packageName+`","version":"`+packageVersion2+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeSwift)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		require.Len(t, pvs, 2) // ATTENTION: many subtests are unable to run separately, they depend on the results of previous tests
 | 
			
		||||
		thisPackageVersion := pvs[0]
 | 
			
		||||
		pd, err := packages.GetPackageDescriptor(db.DefaultContext, thisPackageVersion)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.NotNil(t, pd.SemVer)
 | 
			
		||||
		assert.Equal(t, packageID, pd.Package.Name)
 | 
			
		||||
		assert.Equal(t, packageVersion2, pd.Version.Version)
 | 
			
		||||
		assert.IsType(t, &swift_module.Metadata{}, pd.Metadata)
 | 
			
		||||
		metadata := pd.Metadata.(*swift_module.Metadata)
 | 
			
		||||
		assert.Equal(t, packageDescription, metadata.Description)
 | 
			
		||||
		assert.Len(t, metadata.Manifests, 2)
 | 
			
		||||
		assert.Equal(t, contentManifest1, metadata.Manifests[""].Content)
 | 
			
		||||
		assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content)
 | 
			
		||||
		assert.Len(t, pd.VersionProperties, 1)
 | 
			
		||||
		assert.Equal(t, packageRepositoryURL, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL))
 | 
			
		||||
 | 
			
		||||
		pfs, err := packages.GetFilesByVersionID(db.DefaultContext, thisPackageVersion.ID)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.Len(t, pfs, 1)
 | 
			
		||||
		assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion2), pfs[0].Name)
 | 
			
		||||
		assert.True(t, pfs[0].IsLead)
 | 
			
		||||
 | 
			
		||||
		uploadPackage(
 | 
			
		||||
			t,
 | 
			
		||||
			uploadURL,
 | 
			
		||||
			http.StatusConflict,
 | 
			
		||||
			createArchive(map[string]string{
 | 
			
		||||
				"Package.swift": contentManifest1,
 | 
			
		||||
			}),
 | 
			
		||||
			"",
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("Download", func(t *testing.T) {
 | 
			
		||||
		defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
@@ -211,7 +301,7 @@ func TestPackageSwift(t *testing.T) {
 | 
			
		||||
			SetHeader("Accept", swift_router.AcceptJSON)
 | 
			
		||||
		resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
		versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion)
 | 
			
		||||
		versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion2)
 | 
			
		||||
 | 
			
		||||
		assert.Equal(t, "1", resp.Header().Get("Content-Version"))
 | 
			
		||||
		assert.Equal(t, fmt.Sprintf(`<%s>; rel="latest-version"`, versionURL), resp.Header().Get("Link"))
 | 
			
		||||
@@ -221,9 +311,9 @@ func TestPackageSwift(t *testing.T) {
 | 
			
		||||
		var result *swift_router.EnumeratePackageVersionsResponse
 | 
			
		||||
		DecodeJSON(t, resp, &result)
 | 
			
		||||
 | 
			
		||||
		assert.Len(t, result.Releases, 1)
 | 
			
		||||
		assert.Contains(t, result.Releases, packageVersion)
 | 
			
		||||
		assert.Equal(t, versionURL, result.Releases[packageVersion].URL)
 | 
			
		||||
		assert.Len(t, result.Releases, 2)
 | 
			
		||||
		assert.Contains(t, result.Releases, packageVersion2)
 | 
			
		||||
		assert.Equal(t, versionURL, result.Releases[packageVersion2].URL)
 | 
			
		||||
 | 
			
		||||
		req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.json", url, packageScope, packageName)).
 | 
			
		||||
			AddBasicAuth(user.Name)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,10 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	auth_model "code.gitea.io/gitea/models/auth"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
@@ -18,11 +21,15 @@ import (
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
	"code.gitea.io/gitea/services/forms"
 | 
			
		||||
	"code.gitea.io/gitea/services/gitdiff"
 | 
			
		||||
	issue_service "code.gitea.io/gitea/services/issue"
 | 
			
		||||
	pull_service "code.gitea.io/gitea/services/pull"
 | 
			
		||||
	files_service "code.gitea.io/gitea/services/repository/files"
 | 
			
		||||
	"code.gitea.io/gitea/tests"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
@@ -425,3 +432,94 @@ func TestAPICommitPullRequest(t *testing.T) {
 | 
			
		||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token)
 | 
			
		||||
	ctx.Session.MakeRequest(t, req, http.StatusNotFound)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIViewPullFilesWithHeadRepoDeleted(t *testing.T) {
 | 
			
		||||
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | 
			
		||||
		baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
			
		||||
		user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 | 
			
		||||
 | 
			
		||||
		ctx := NewAPITestContext(t, "user1", baseRepo.Name, auth_model.AccessTokenScopeAll)
 | 
			
		||||
 | 
			
		||||
		doAPIForkRepository(ctx, "user2")(t)
 | 
			
		||||
 | 
			
		||||
		forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ForkID: baseRepo.ID, OwnerName: "user1"})
 | 
			
		||||
 | 
			
		||||
		// add a new file to the forked repo
 | 
			
		||||
		addFileToForkedResp, err := files_service.ChangeRepoFiles(git.DefaultContext, forkedRepo, user1, &files_service.ChangeRepoFilesOptions{
 | 
			
		||||
			Files: []*files_service.ChangeRepoFile{
 | 
			
		||||
				{
 | 
			
		||||
					Operation:     "create",
 | 
			
		||||
					TreePath:      "file_1.txt",
 | 
			
		||||
					ContentReader: strings.NewReader("file1"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Message:   "add file1",
 | 
			
		||||
			OldBranch: "master",
 | 
			
		||||
			NewBranch: "fork-branch-1",
 | 
			
		||||
			Author: &files_service.IdentityOptions{
 | 
			
		||||
				Name:  user1.Name,
 | 
			
		||||
				Email: user1.Email,
 | 
			
		||||
			},
 | 
			
		||||
			Committer: &files_service.IdentityOptions{
 | 
			
		||||
				Name:  user1.Name,
 | 
			
		||||
				Email: user1.Email,
 | 
			
		||||
			},
 | 
			
		||||
			Dates: &files_service.CommitDateOptions{
 | 
			
		||||
				Author:    time.Now(),
 | 
			
		||||
				Committer: time.Now(),
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.NotEmpty(t, addFileToForkedResp)
 | 
			
		||||
 | 
			
		||||
		// create Pull
 | 
			
		||||
		pullIssue := &issues_model.Issue{
 | 
			
		||||
			RepoID:   baseRepo.ID,
 | 
			
		||||
			Title:    "Test pull-request-target-event",
 | 
			
		||||
			PosterID: user1.ID,
 | 
			
		||||
			Poster:   user1,
 | 
			
		||||
			IsPull:   true,
 | 
			
		||||
		}
 | 
			
		||||
		pullRequest := &issues_model.PullRequest{
 | 
			
		||||
			HeadRepoID: forkedRepo.ID,
 | 
			
		||||
			BaseRepoID: baseRepo.ID,
 | 
			
		||||
			HeadBranch: "fork-branch-1",
 | 
			
		||||
			BaseBranch: "master",
 | 
			
		||||
			HeadRepo:   forkedRepo,
 | 
			
		||||
			BaseRepo:   baseRepo,
 | 
			
		||||
			Type:       issues_model.PullRequestGitea,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		prOpts := &pull_service.NewPullRequestOptions{Repo: baseRepo, Issue: pullIssue, PullRequest: pullRequest}
 | 
			
		||||
		err = pull_service.NewPullRequest(git.DefaultContext, prOpts)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		pr := convert.ToAPIPullRequest(context.Background(), pullRequest, user1)
 | 
			
		||||
 | 
			
		||||
		ctx = NewAPITestContext(t, "user2", baseRepo.Name, auth_model.AccessTokenScopeAll)
 | 
			
		||||
		doAPIGetPullFiles(ctx, pr, func(t *testing.T, files []*api.ChangedFile) {
 | 
			
		||||
			if assert.Len(t, files, 1) {
 | 
			
		||||
				assert.Equal(t, "file_1.txt", files[0].Filename)
 | 
			
		||||
				assert.Empty(t, files[0].PreviousFilename)
 | 
			
		||||
				assert.Equal(t, 1, files[0].Additions)
 | 
			
		||||
				assert.Equal(t, 1, files[0].Changes)
 | 
			
		||||
				assert.Equal(t, 0, files[0].Deletions)
 | 
			
		||||
				assert.Equal(t, "added", files[0].Status)
 | 
			
		||||
			}
 | 
			
		||||
		})(t)
 | 
			
		||||
 | 
			
		||||
		// delete the head repository of the pull request
 | 
			
		||||
		forkCtx := NewAPITestContext(t, "user1", forkedRepo.Name, auth_model.AccessTokenScopeAll)
 | 
			
		||||
		doAPIDeleteRepository(forkCtx)(t)
 | 
			
		||||
 | 
			
		||||
		doAPIGetPullFiles(ctx, pr, func(t *testing.T, files []*api.ChangedFile) {
 | 
			
		||||
			if assert.Len(t, files, 1) {
 | 
			
		||||
				assert.Equal(t, "file_1.txt", files[0].Filename)
 | 
			
		||||
				assert.Empty(t, files[0].PreviousFilename)
 | 
			
		||||
				assert.Equal(t, 1, files[0].Additions)
 | 
			
		||||
				assert.Equal(t, 1, files[0].Changes)
 | 
			
		||||
				assert.Equal(t, 0, files[0].Deletions)
 | 
			
		||||
				assert.Equal(t, "added", files[0].Status)
 | 
			
		||||
			}
 | 
			
		||||
		})(t)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,10 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -31,7 +33,9 @@ import (
 | 
			
		||||
	gitea_context "code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/tests"
 | 
			
		||||
 | 
			
		||||
	"github.com/kballard/go-shellquote"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@@ -105,7 +109,12 @@ func testGitGeneral(t *testing.T, u *url.URL) {
 | 
			
		||||
 | 
			
		||||
		// Setup key the user ssh key
 | 
			
		||||
		withKeyFile(t, keyname, func(keyFile string) {
 | 
			
		||||
			t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile))
 | 
			
		||||
			var keyID int64
 | 
			
		||||
			t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile, func(t *testing.T, key api.PublicKey) {
 | 
			
		||||
				keyID = key.ID
 | 
			
		||||
			}))
 | 
			
		||||
			assert.NotZero(t, keyID)
 | 
			
		||||
			t.Run("LFSAccessTest", doSSHLFSAccessTest(sshContext, keyID))
 | 
			
		||||
 | 
			
		||||
			// Setup remote link
 | 
			
		||||
			// TODO: get url from api
 | 
			
		||||
@@ -136,6 +145,36 @@ func testGitGeneral(t *testing.T, u *url.URL) {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doSSHLFSAccessTest(_ APITestContext, keyID int64) func(*testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		sshCommand := os.Getenv("GIT_SSH_COMMAND")       // it is set in withKeyFile
 | 
			
		||||
		sshCmdParts, err := shellquote.Split(sshCommand) // and parse the ssh command to construct some mocked arguments
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		t.Run("User2AccessOwned", func(t *testing.T) {
 | 
			
		||||
			sshCmdUser2Self := append(slices.Clone(sshCmdParts),
 | 
			
		||||
				"-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost,
 | 
			
		||||
				"git-lfs-authenticate", "user2/repo1.git", "upload", // accessible to own repo
 | 
			
		||||
			)
 | 
			
		||||
			cmd := exec.CommandContext(git.DefaultContext, sshCmdUser2Self[0], sshCmdUser2Self[1:]...)
 | 
			
		||||
			_, err := cmd.Output()
 | 
			
		||||
			assert.NoError(t, err) // accessible, no error
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		t.Run("User2AccessOther", func(t *testing.T) {
 | 
			
		||||
			sshCmdUser2Other := append(slices.Clone(sshCmdParts),
 | 
			
		||||
				"-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost,
 | 
			
		||||
				"git-lfs-authenticate", "user5/repo4.git", "upload", // inaccessible to other's (user5/repo4)
 | 
			
		||||
			)
 | 
			
		||||
			cmd := exec.CommandContext(git.DefaultContext, sshCmdUser2Other[0], sshCmdUser2Other[1:]...)
 | 
			
		||||
			_, err := cmd.Output()
 | 
			
		||||
			var errExit *exec.ExitError
 | 
			
		||||
			require.ErrorAs(t, err, &errExit) // inaccessible, error
 | 
			
		||||
			assert.Contains(t, string(errExit.Stderr), fmt.Sprintf("User: 2:user2 with Key: %d:test-key is not authorized to write to user5/repo4.", keyID))
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ensureAnonymousClone(t *testing.T, u *url.URL) {
 | 
			
		||||
	dstLocalPath := t.TempDir()
 | 
			
		||||
	t.Run("CloneAnonymous", doGitClone(dstLocalPath, u))
 | 
			
		||||
 
 | 
			
		||||
@@ -185,6 +185,15 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content,
 | 
			
		||||
	return int64(id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testIssueChangeMilestone(t *testing.T, session *TestSession, repoLink string, issueID, milestoneID int64) {
 | 
			
		||||
	req := NewRequestWithValues(t, "POST", fmt.Sprintf(repoLink+"/issues/milestone?issue_ids=%d", issueID), map[string]string{
 | 
			
		||||
		"_csrf": GetUserCSRFToken(t, session),
 | 
			
		||||
		"id":    strconv.FormatInt(milestoneID, 10),
 | 
			
		||||
	})
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	assert.Equal(t, `{"ok":true}`, strings.TrimSpace(resp.Body.String()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewIssue(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
	session := loginUser(t, "user2")
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import (
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"path"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
@@ -76,7 +75,7 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
		t.Run("First Pull Request", func(t *testing.T) {
 | 
			
		||||
			// create a new branch to prepare for pull request
 | 
			
		||||
			resp1, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
			
		||||
			_, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
			
		||||
				NewBranch: "codeowner-basebranch",
 | 
			
		||||
				Files: []*files_service.ChangeRepoFile{
 | 
			
		||||
					{
 | 
			
		||||
@@ -96,13 +95,8 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
			
		||||
			unittest.AssertExistsIf(t, true, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 5})
 | 
			
		||||
			assert.NoError(t, pr.LoadIssue(db.DefaultContext))
 | 
			
		||||
 | 
			
		||||
			reviewNotifiers, err := issue_service.PullRequestCodeOwnersReview(db.DefaultContext, pr)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.Len(t, reviewNotifiers, 1)
 | 
			
		||||
			assert.EqualValues(t, 5, reviewNotifiers[0].Reviewer.ID)
 | 
			
		||||
 | 
			
		||||
			// update the file on the pr branch
 | 
			
		||||
			resp2, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
			
		||||
			_, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
			
		||||
				OldBranch: "codeowner-basebranch",
 | 
			
		||||
				Files: []*files_service.ChangeRepoFile{
 | 
			
		||||
					{
 | 
			
		||||
@@ -114,14 +108,7 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
			
		||||
			})
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
			reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(db.DefaultContext, pr)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.Len(t, reviewNotifiers, 2)
 | 
			
		||||
			reviewerIDs := []int64{reviewNotifiers[0].Reviewer.ID, reviewNotifiers[1].Reviewer.ID}
 | 
			
		||||
			sort.Slice(reviewerIDs, func(i, j int) bool { return reviewerIDs[i] < reviewerIDs[j] })
 | 
			
		||||
			assert.EqualValues(t, []int64{5, 8}, reviewerIDs)
 | 
			
		||||
 | 
			
		||||
			reviewNotifiers, err = issue_service.PullRequestCodeOwnersReviewSpecialCommits(db.DefaultContext, pr, resp1.Commit.SHA, resp2.Commit.SHA)
 | 
			
		||||
			reviewNotifiers, err := issue_service.PullRequestCodeOwnersReview(db.DefaultContext, pr)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.Len(t, reviewNotifiers, 1)
 | 
			
		||||
			assert.EqualValues(t, 8, reviewNotifiers[0].Reviewer.ID)
 | 
			
		||||
@@ -171,11 +158,6 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
			pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch2"})
 | 
			
		||||
			unittest.AssertExistsAndLoadBean(t, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
 | 
			
		||||
 | 
			
		||||
			reviewNotifiers, err := issue_service.PullRequestCodeOwnersReview(db.DefaultContext, pr)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.Len(t, reviewNotifiers, 1)
 | 
			
		||||
			assert.EqualValues(t, 8, reviewNotifiers[0].Reviewer.ID)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		t.Run("Forked Repo Pull Request", func(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -353,6 +353,78 @@ func Test_WebhookIssue(t *testing.T) {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_WebhookIssueMilestone(t *testing.T) {
 | 
			
		||||
	var payloads []api.IssuePayload
 | 
			
		||||
	var triggeredEvent string
 | 
			
		||||
	provider := newMockWebhookProvider(func(r *http.Request) {
 | 
			
		||||
		content, _ := io.ReadAll(r.Body)
 | 
			
		||||
		var payload api.IssuePayload
 | 
			
		||||
		err := json.Unmarshal(content, &payload)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		payloads = append(payloads, payload)
 | 
			
		||||
		triggeredEvent = "issues"
 | 
			
		||||
	}, http.StatusOK)
 | 
			
		||||
	defer provider.Close()
 | 
			
		||||
 | 
			
		||||
	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
 | 
			
		||||
		// create a new webhook with special webhook for repo1
 | 
			
		||||
		session := loginUser(t, "user2")
 | 
			
		||||
		repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
 | 
			
		||||
		testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_milestone")
 | 
			
		||||
 | 
			
		||||
		t.Run("assign a milestone", func(t *testing.T) {
 | 
			
		||||
			// trigger the webhook
 | 
			
		||||
			testIssueChangeMilestone(t, session, repo1.Link(), 1, 1)
 | 
			
		||||
 | 
			
		||||
			// validate the webhook is triggered
 | 
			
		||||
			assert.Equal(t, "issues", triggeredEvent)
 | 
			
		||||
			assert.Len(t, payloads, 1)
 | 
			
		||||
			assert.Equal(t, "milestoned", string(payloads[0].Action))
 | 
			
		||||
			assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
 | 
			
		||||
			assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
 | 
			
		||||
			assert.Equal(t, "issue1", payloads[0].Issue.Title)
 | 
			
		||||
			assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
 | 
			
		||||
			assert.EqualValues(t, 1, payloads[0].Issue.Milestone.ID)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		t.Run("change a milestong", func(t *testing.T) {
 | 
			
		||||
			// trigger the webhook again
 | 
			
		||||
			triggeredEvent = ""
 | 
			
		||||
			payloads = make([]api.IssuePayload, 0, 1)
 | 
			
		||||
			// change milestone to 2
 | 
			
		||||
			testIssueChangeMilestone(t, session, repo1.Link(), 1, 2)
 | 
			
		||||
 | 
			
		||||
			// validate the webhook is triggered
 | 
			
		||||
			assert.Equal(t, "issues", triggeredEvent)
 | 
			
		||||
			assert.Len(t, payloads, 1)
 | 
			
		||||
			assert.Equal(t, "milestoned", string(payloads[0].Action))
 | 
			
		||||
			assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
 | 
			
		||||
			assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
 | 
			
		||||
			assert.Equal(t, "issue1", payloads[0].Issue.Title)
 | 
			
		||||
			assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
 | 
			
		||||
			assert.EqualValues(t, 2, payloads[0].Issue.Milestone.ID)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		t.Run("remove a milestone", func(t *testing.T) {
 | 
			
		||||
			// trigger the webhook again
 | 
			
		||||
			triggeredEvent = ""
 | 
			
		||||
			payloads = make([]api.IssuePayload, 0, 1)
 | 
			
		||||
			// change milestone to 0
 | 
			
		||||
			testIssueChangeMilestone(t, session, repo1.Link(), 1, 0)
 | 
			
		||||
 | 
			
		||||
			// validate the webhook is triggered
 | 
			
		||||
			assert.Equal(t, "issues", triggeredEvent)
 | 
			
		||||
			assert.Len(t, payloads, 1)
 | 
			
		||||
			assert.Equal(t, "demilestoned", string(payloads[0].Action))
 | 
			
		||||
			assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
 | 
			
		||||
			assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
 | 
			
		||||
			assert.Equal(t, "issue1", payloads[0].Issue.Title)
 | 
			
		||||
			assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
 | 
			
		||||
			assert.Nil(t, payloads[0].Issue.Milestone)
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_WebhookPullRequest(t *testing.T) {
 | 
			
		||||
	var payloads []api.PullRequestPayload
 | 
			
		||||
	var triggeredEvent string
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,12 @@
 | 
			
		||||
  flex: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.clone-buttons-combo input {
 | 
			
		||||
  border-left: none !important;
 | 
			
		||||
  border-radius: 0 !important;
 | 
			
		||||
.clone-buttons-combo > .ui.button:not(:last-child) {
 | 
			
		||||
  border-right: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ui.action.input.clone-buttons-combo input {
 | 
			
		||||
  border-radius: 0; /* override fomantic border-radius for ".ui.input > input" */
 | 
			
		||||
  height: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user