mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			205 Commits
		
	
	
		
			v1.24.0-de
			...
			v1.23.8
		
	
	| 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 | ||
| 
						 | 
					97171be1b4 | ||
| 
						 | 
					9ef2a338d8 | ||
| 
						 | 
					66ccae8b90 | ||
| 
						 | 
					0e6489317e | ||
| 
						 | 
					67dc1ff926 | ||
| 
						 | 
					4ee4c06b07 | ||
| 
						 | 
					3063e37802 | ||
| 
						 | 
					a40e15a116 | ||
| 
						 | 
					8f75f61b64 | ||
| 
						 | 
					25e409e025 | ||
| 
						 | 
					f8f24d83cf | ||
| 
						 | 
					15e93a751c | ||
| 
						 | 
					5a9b3bfa50 | ||
| 
						 | 
					dd901983c0 | ||
| 
						 | 
					7f962a16c9 | ||
| 
						 | 
					5d81f6d73f | ||
| 
						 | 
					eee4a752a5 | ||
| 
						 | 
					b3516767fb | ||
| 
						 | 
					8d1be2a9c5 | ||
| 
						 | 
					e46f9ff534 | ||
| 
						 | 
					690e810bcc | ||
| 
						 | 
					35983ac0a8 | ||
| 
						 | 
					4b3400bd9c | ||
| 
						 | 
					7758df4264 | ||
| 
						 | 
					f994f3cac6 | ||
| 
						 | 
					f514b2651e | ||
| 
						 | 
					347101f2a8 | ||
| 
						 | 
					b5f8c4a510 | ||
| 
						 | 
					d6cee7c596 | ||
| 
						 | 
					987219ab3c | ||
| 
						 | 
					92280637a4 | ||
| 
						 | 
					5fadcf997e | ||
| 
						 | 
					be94f7bc07 | ||
| 
						 | 
					9054a6670c | ||
| 
						 | 
					fc82204fca | ||
| 
						 | 
					6f8e62fa9c | ||
| 
						 | 
					a2c6ecc093 | ||
| 
						 | 
					523a84e5d0 | ||
| 
						 | 
					869ee4fc38 | ||
| 
						 | 
					16a332464d | ||
| 
						 | 
					d03e7fd65e | ||
| 
						 | 
					92f2d904f0 | ||
| 
						 | 
					9c3511f0b1 | ||
| 
						 | 
					69d35ee911 | ||
| 
						 | 
					b8a7c20474 | ||
| 
						 | 
					2cc76009dc | ||
| 
						 | 
					730742230f | ||
| 
						 | 
					8939c3845a | ||
| 
						 | 
					d634e7576f | ||
| 
						 | 
					7ded86f5af | ||
| 
						 | 
					27a60fd91b | ||
| 
						 | 
					e3021fae79 | ||
| 
						 | 
					81126daf53 | ||
| 
						 | 
					1c7339e385 | ||
| 
						 | 
					039924aa2a | ||
| 
						 | 
					b5007c6154 | ||
| 
						 | 
					ae595aa913 | ||
| 
						 | 
					aeeccc9642 | ||
| 
						 | 
					37e99d9b34 | ||
| 
						 | 
					9da6d4ea85 | ||
| 
						 | 
					8844e62cb4 | ||
| 
						 | 
					de7026528b | ||
| 
						 | 
					ee3f5e8fac | ||
| 
						 | 
					b2707bcd18 | ||
| 
						 | 
					0512b02b01 | ||
| 
						 | 
					99545ae2fd | ||
| 
						 | 
					7697df9f93 | ||
| 
						 | 
					d17f8ffcc1 | ||
| 
						 | 
					5e9cc919cf | ||
| 
						 | 
					cc6ec56738 | ||
| 
						 | 
					76bd60fc1d | ||
| 
						 | 
					744f7c8200 | ||
| 
						 | 
					da33b708af | ||
| 
						 | 
					8fa3925874 | ||
| 
						 | 
					7794ff0874 | ||
| 
						 | 
					7c17d0a73e | ||
| 
						 | 
					a014d071e4 | ||
| 
						 | 
					312565e3c2 | ||
| 
						 | 
					58daaf66e8 | ||
| 
						 | 
					f076ada601 | ||
| 
						 | 
					92436b8b2a | ||
| 
						 | 
					2df7d0835a | ||
| 
						 | 
					200cb6140d | ||
| 
						 | 
					2746c6f1aa | ||
| 
						 | 
					23971a77a0 | ||
| 
						 | 
					ebac324ff2 | ||
| 
						 | 
					7df1204795 | ||
| 
						 | 
					159544a950 | ||
| 
						 | 
					9780da583d | ||
| 
						 | 
					a8eaf43f97 | ||
| 
						 | 
					b6fd8741ee | ||
| 
						 | 
					2674d27fb8 | ||
| 
						 | 
					6f3837284d | ||
| 
						 | 
					c30f4f4be5 | ||
| 
						 | 
					4578288ea3 | ||
| 
						 | 
					826fffb59e | ||
| 
						 | 
					2196ba5e42 | ||
| 
						 | 
					12347f07ae | ||
| 
						 | 
					a3c5358d35 | ||
| 
						 | 
					987d014468 | ||
| 
						 | 
					e08eed9040 | ||
| 
						 | 
					4ffa49aa04 | ||
| 
						 | 
					72837530bf | ||
| 
						 | 
					eef635523a | ||
| 
						 | 
					8f45a11919 | ||
| 
						 | 
					e72d001708 | ||
| 
						 | 
					8d9ea68f19 | ||
| 
						 | 
					bf664c2e85 | ||
| 
						 | 
					52d298890b | ||
| 
						 | 
					c09e43acf5 | ||
| 
						 | 
					3b4af01633 | ||
| 
						 | 
					b4e2d5e8ee | ||
| 
						 | 
					2984a7c121 | ||
| 
						 | 
					80cc87b3d8 | ||
| 
						 | 
					10b6047498 | ||
| 
						 | 
					2c47b06869 | ||
| 
						 | 
					31f2a325dc | ||
| 
						 | 
					fcbbc24cc4 | ||
| 
						 | 
					1454e1b6eb | ||
| 
						 | 
					d70348836b | ||
| 
						 | 
					940a930d13 | ||
| 
						 | 
					45d21a0d5c | ||
| 
						 | 
					15ad001aef | ||
| 
						 | 
					ed1828ca92 | ||
| 
						 | 
					3cfff5af0d | ||
| 
						 | 
					6f6c66a07d | ||
| 
						 | 
					d65af69c2b | ||
| 
						 | 
					12c24c2189 | ||
| 
						 | 
					a330f42f01 | ||
| 
						 | 
					531f36ea4a | ||
| 
						 | 
					b4f0eed969 | ||
| 
						 | 
					63b3a33bf2 | ||
| 
						 | 
					9899989ece | ||
| 
						 | 
					0fad40dd8c | ||
| 
						 | 
					e637008fe3 | ||
| 
						 | 
					fd281518ae | ||
| 
						 | 
					e10d222434 | ||
| 
						 | 
					7a35f90b29 | ||
| 
						 | 
					d371aa3031 | ||
| 
						 | 
					81768675d4 | ||
| 
						 | 
					39cc72562b | ||
| 
						 | 
					bc83fb26ef | ||
| 
						 | 
					68736ec292 | ||
| 
						 | 
					3df11c07a8 | ||
| 
						 | 
					96fff862dc | ||
| 
						 | 
					968c04c7da | ||
| 
						 | 
					27de60381d | ||
| 
						 | 
					d2d763318c | ||
| 
						 | 
					610b2fb88d | ||
| 
						 | 
					0858a36016 | ||
| 
						 | 
					fef364e7d6 | ||
| 
						 | 
					ce6a60a38b | ||
| 
						 | 
					74159a8855 | ||
| 
						 | 
					ce6464123f | ||
| 
						 | 
					7f0050cf39 | ||
| 
						 | 
					c102e344f3 | ||
| 
						 | 
					f27128bf94 | ||
| 
						 | 
					f35ab5cd52 | ||
| 
						 | 
					0137bc4e5c | ||
| 
						 | 
					eed0968c37 | ||
| 
						 | 
					a0b65ed17f | ||
| 
						 | 
					ad1b76540e | ||
| 
						 | 
					6636b37a9c | ||
| 
						 | 
					af5e5e8f00 | ||
| 
						 | 
					0e0ebf68d7 | ||
| 
						 | 
					90bd08ceef | ||
| 
						 | 
					0c581106d2 | ||
| 
						 | 
					e18e31d557 | ||
| 
						 | 
					e1026feddc | ||
| 
						 | 
					d670820722 | ||
| 
						 | 
					a8f98fd3be | ||
| 
						 | 
					c442c682ef | ||
| 
						 | 
					57868c2315 | ||
| 
						 | 
					b1c21880c1 | ||
| 
						 | 
					1e71ad89ce | ||
| 
						 | 
					c20642fa99 | ||
| 
						 | 
					a4291fd553 | ||
| 
						 | 
					fa5a064559 | ||
| 
						 | 
					cb42232080 | ||
| 
						 | 
					c8ffe777cf | ||
| 
						 | 
					e98dd6ee5b | 
							
								
								
									
										7
									
								
								.github/workflows/pull-db-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/pull-db-tests.yml
									
									
									
									
										vendored
									
									
								
							@@ -202,12 +202,11 @@ jobs:
 | 
			
		||||
  test-mssql:
 | 
			
		||||
    if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
 | 
			
		||||
    needs: files-changed
 | 
			
		||||
    # specifying the version of ubuntu in use as mssql fails on newer kernels
 | 
			
		||||
    # pending resolution from vendor
 | 
			
		||||
    runs-on: ubuntu-20.04
 | 
			
		||||
    # NOTE: mssql-2017 docker image will panic when run on hosts that have Ubuntu newer than 20.04
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    services:
 | 
			
		||||
      mssql:
 | 
			
		||||
        image: mcr.microsoft.com/mssql/server:2017-latest
 | 
			
		||||
        image: mcr.microsoft.com/mssql/server:2019-latest
 | 
			
		||||
        env:
 | 
			
		||||
          ACCEPT_EULA: Y
 | 
			
		||||
          MSSQL_PID: Standard
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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 }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								.github/workflows/release-tag-version.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								.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,26 +85,34 @@ 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
 | 
			
		||||
          # 1.2
 | 
			
		||||
          # 1.2.3
 | 
			
		||||
          tags: |
 | 
			
		||||
            type=semver,pattern={{version}}
 | 
			
		||||
            type=semver,pattern={{major}}
 | 
			
		||||
            type=semver,pattern={{major}}.{{minor}}
 | 
			
		||||
            type=semver,pattern={{version}}
 | 
			
		||||
      - name: Login to Docker Hub
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        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
 | 
			
		||||
@@ -126,19 +140,25 @@ jobs:
 | 
			
		||||
          # 1.2
 | 
			
		||||
          # 1.2.3
 | 
			
		||||
          tags: |
 | 
			
		||||
            type=semver,pattern={{version}}
 | 
			
		||||
            type=semver,pattern={{major}}
 | 
			
		||||
            type=semver,pattern={{major}}.{{minor}}
 | 
			
		||||
            type=semver,pattern={{version}}
 | 
			
		||||
      - name: Login to Docker Hub
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        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 }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										577
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										577
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,6 +4,583 @@ 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
 | 
			
		||||
  * Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
 | 
			
		||||
  * Also check default ssh-cert location for host (#34099) (#34100) (#34116)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix discord webhook 400 status code when description limit is exceeded (#34084) (#34124)
 | 
			
		||||
  * Get changed files based on merge base when checking `pull_request` actions trigger (#34106) (#34120)
 | 
			
		||||
  * Fix invalid version in RPM package path (#34112) (#34115)
 | 
			
		||||
  * Return default avatar url when user id is zero rather than updating database (#34094) (#34095)
 | 
			
		||||
  * Add additional ReplaceAll in pathsep to cater for different pathsep (#34061) (#34070)
 | 
			
		||||
  * Try to fix check-attr bug (#34029) (#34033)
 | 
			
		||||
  * Git client will follow 301 but 307 (#34005) (#34010)
 | 
			
		||||
  * Fix block expensive for 1.23 (#34127)
 | 
			
		||||
  * Fix markdown frontmatter rendering (#34102) (#34107)
 | 
			
		||||
  * Add new CLI flags to set name and scopes when creating a user with access token (#34080) (#34103)
 | 
			
		||||
  * Do not show 500 error when default branch doesn't exist (#34096) (#34097)
 | 
			
		||||
  * Hide activity contributors, recent commits and code frequrency left tabs if there is no code permission (#34053) (#34065)
 | 
			
		||||
  * Simplify emoji rendering (#34048) (#34049)
 | 
			
		||||
  * Adjust the layout of the toolbar on the Issues/Projects page (#33667) (#34047)
 | 
			
		||||
  * Pull request updates will also trigger code owners review requests (#33744) (#34045)
 | 
			
		||||
  * Fix org repo creation being limited by user limits (#34030) (#34044)
 | 
			
		||||
  * Fix git client accessing renamed repo (#34034) (#34043)
 | 
			
		||||
  * Fix the issue with error message logging for the `check-attr` command on Windows OS. (#34035) (#34036)
 | 
			
		||||
  * Polyfill WeakRef (#34025) (#34028)
 | 
			
		||||
 | 
			
		||||
## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Fix LFS URL (#33840) (#33843)
 | 
			
		||||
  * Update jwt and redis packages (#33984) (#33987)
 | 
			
		||||
  * Update golang crypto and net (#33989)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Drop timeout for requests made to the internal hook api (#33947) (#33970)
 | 
			
		||||
  * Fix maven panic when no package exists (#33888) (#33889)
 | 
			
		||||
  * Fix markdown render (#33870) (#33875)
 | 
			
		||||
  * Fix auto concurrency cancellation skips commit status updates (#33764) (#33849)
 | 
			
		||||
  * Fix oauth2 auth (#33961) (#33962)
 | 
			
		||||
  * Fix incorrect 1.23 translations (#33932)
 | 
			
		||||
  * Try to figure out attribute checker problem (#33901) (#33902)
 | 
			
		||||
  * Ignore trivial errors when updating push data (#33864) (#33887)
 | 
			
		||||
  * Fix some UI problems for 1.23 (#33856)
 | 
			
		||||
  * Removing unwanted ui container (#33833) (#33835)
 | 
			
		||||
  * Support disable passkey auth (#33348) (#33819)
 | 
			
		||||
  * Do not call "git diff" when listing PRs (#33817)
 | 
			
		||||
  * Try to fix ACME (3rd) (#33807) (#33808)
 | 
			
		||||
  * Fix incorrect code search indexer options (#33992) #33999
 | 
			
		||||
 | 
			
		||||
## [1.23.5](https://github.com/go-gitea/gitea/releases/tag/v1.23.5) - 2025-03-03
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Bump x/oauth2 & x/crypto (#33704) (#33727)
 | 
			
		||||
* PERFORMANCE
 | 
			
		||||
  * Optimize user dashboard loading (#33686) (#33708)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix navbar dropdown item align (#33782)
 | 
			
		||||
  * Fix inconsistent closed issue list icon (#33722) (#33728)
 | 
			
		||||
  * Fix for Maven Package Naming Convention Handling (#33678) (#33679)
 | 
			
		||||
  * Improve Open-with URL encoding (#33666) (#33680)
 | 
			
		||||
  * Deleting repository should unlink all related packages (#33653) (#33673)
 | 
			
		||||
  * Fix omitempty bug (#33663) (#33670)
 | 
			
		||||
  * Upgrade go-crypto from 1.1.4 to 1.1.6 (#33745) (#33754)
 | 
			
		||||
  * Fix OCI image.version annotation for releases to use full semver (#33698) (#33701)
 | 
			
		||||
  * Try to fix ACME path when renew (#33668) (#33693)
 | 
			
		||||
  * Fix mCaptcha bug (#33659) (#33661)
 | 
			
		||||
  * Git graph: don't show detached commits (#33645) (#33650)
 | 
			
		||||
  * Use MatchPhraseQuery for bleve code search (#33628)
 | 
			
		||||
  * Adjust appearence of commit status webhook (#33778) #33789
 | 
			
		||||
 | 
			
		||||
## [1.23.4](https://github.com/go-gitea/gitea/releases/tag/v1.23.4) - 2025-02-16
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Enhance routers for the Actions variable operations (#33547) (#33553)
 | 
			
		||||
  * Enhance routers for the Actions runner operations (#33549) (#33555)
 | 
			
		||||
  * Fix project issues list and counting (#33594) #33619
 | 
			
		||||
* PERFORMANCES
 | 
			
		||||
  * Performance optimization for pull request files loading comments attachments (#33585) (#33592)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Add a transaction to `pickTask` (#33543) (#33563)
 | 
			
		||||
  * Fix mirror bug (#33597) (#33607)
 | 
			
		||||
  * Use default Git timeout when checking repo health (#33593) (#33598)
 | 
			
		||||
  * Fix PR's target branch dropdown (#33589) (#33591)
 | 
			
		||||
  * Fix various problems (artifact order, api empty slice, assignee check, fuzzy prompt, mirror proxy, adopt git) (#33569) (#33577)
 | 
			
		||||
  * Rework suggestion backend (#33538) (#33546)
 | 
			
		||||
  * Fix context usage (#33554) (#33557)
 | 
			
		||||
  * Only show the latest version in the Arch index (#33262) (#33580)
 | 
			
		||||
  * Skip deletion error for action artifacts (#33476) (#33568)
 | 
			
		||||
  * Make actions URL in commit status webhooks absolute (#33620) #33632
 | 
			
		||||
  * Add missing locale (#33641) #33642
 | 
			
		||||
 | 
			
		||||
## [1.23.3](https://github.com/go-gitea/gitea/releases/tag/v1.23.3) - 2025-02-06
 | 
			
		||||
 | 
			
		||||
* Security
 | 
			
		||||
  * Build Gitea with Golang v1.23.6 to fix security bugs
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix a bug caused by status webhook template #33512
 | 
			
		||||
 | 
			
		||||
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-04
 | 
			
		||||
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Add tests for webhook and fix some webhook bugs (#33396) (#33442)
 | 
			
		||||
    * Package webhook’s Organization was incorrectly used as the User struct. This PR fixes the issue.
 | 
			
		||||
    * This changelog is just a hint. The change is not really breaking because most fields are the same, most users are not affected.
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Clone button enhancements (#33362) (#33404)
 | 
			
		||||
  * Repo homepage styling tweaks (#33289) (#33381)
 | 
			
		||||
  * Add a confirm dialog for "sync fork" (#33270) (#33273)
 | 
			
		||||
  * Make tracked time representation display as hours (#33315) (#33334)
 | 
			
		||||
  * Improve sync fork behavior (#33319) (#33332)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix code button alignment (#33345) (#33351)
 | 
			
		||||
  * Correct bot label `vertical-align` (#33477) (#33480)
 | 
			
		||||
  * Fix SSH LFS memory usage (#33455) (#33460)
 | 
			
		||||
  * Fix issue sidebar dropdown keyboard support (#33447) (#33450)
 | 
			
		||||
  * Fix user avatar (#33439)
 | 
			
		||||
  * Fix `GetCommitBranchStart` bug (#33298) (#33421)
 | 
			
		||||
  * Add pubdate for repository rss and add some tests (#33411) (#33416)
 | 
			
		||||
  * Add missed auto merge feed message on dashboard (#33309) (#33405)
 | 
			
		||||
  * Fix issue suggestion bug (#33389) (#33391)
 | 
			
		||||
  * Make issue suggestion work for all editors (#33340) (#33342)
 | 
			
		||||
  * Fix issue count (#33338) (#33341)
 | 
			
		||||
  * Fix Account linking page (#33325) (#33327)
 | 
			
		||||
  * Fix closed dependency title (#33285) (#33287)
 | 
			
		||||
  * Fix sidebar milestone link (#33269) (#33272)
 | 
			
		||||
  * Fix missing license when sync mirror (#33255) (#33258)
 | 
			
		||||
  * Fix upload file form (#33230) (#33233)
 | 
			
		||||
  * Fix mirror bug (#33224) (#33225)
 | 
			
		||||
  * Fix system admin cannot fork or get private fork with API (#33401) (#33417)
 | 
			
		||||
  * Fix push message behavior (#33215) (#33317)
 | 
			
		||||
  * Trivial fixes (#33304) (#33312)
 | 
			
		||||
  * Fix "stop time tracking button" on navbar (#33084) (#33300)
 | 
			
		||||
  * Fix tag route and empty repo (#33253)
 | 
			
		||||
  * Fix cache test triggered by non memory cache (#33220) (#33221)
 | 
			
		||||
  * Revert empty lfs ref name (#33454) (#33457)
 | 
			
		||||
  * Fix flex width (#33414) (#33418)
 | 
			
		||||
  * Fix commit status events (#33320) #33493
 | 
			
		||||
  * Fix unnecessary comment when moving issue on the same project column (#33496) #33499
 | 
			
		||||
  * Add timetzdata build tag to binary releases (#33463) #33503
 | 
			
		||||
* MISC
 | 
			
		||||
  * Use ProtonMail/go-crypto to replace keybase/go-crypto (#33402) (#33410)
 | 
			
		||||
  * Update katex to latest version (#33361)
 | 
			
		||||
  * Update go tool dependencies (#32916) (#33355)
 | 
			
		||||
 | 
			
		||||
## [1.23.1](https://github.com/go-gitea/gitea/releases/tag/v1.23.1) - 2025-01-09
 | 
			
		||||
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Move repo size to sidebar (#33155) (#33182)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Use updated path to s6-svscan after alpine upgrade (#33185) (#33188)
 | 
			
		||||
  * Fix fuzz test (#33156) (#33158)
 | 
			
		||||
  * Fix raw file API ref handling (#33172) (#33189)
 | 
			
		||||
  * Fix ACME panic (#33178) (#33186)
 | 
			
		||||
  * Fix branch dropdown not display ref name (#33159) (#33183)
 | 
			
		||||
  * Fix assignee list overlapping in Issue sidebar (#33176) (#33181)
 | 
			
		||||
  * Fix sync fork for consistency (#33147) #33192
 | 
			
		||||
  * Fix editor markdown not incrementing in a numbered list (#33187) #33193
 | 
			
		||||
 | 
			
		||||
## [1.23.0](https://github.com/go-gitea/gitea/releases/tag/v1.23.0) - 2025-01-08
 | 
			
		||||
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Rename config option `[camo].Allways` to `[camo].Always` (#32097)
 | 
			
		||||
  * Remove SHA1 for support for ssh rsa signing (#31857)
 | 
			
		||||
  * Use UTC as default timezone when schedule Actions cron tasks (#31742)
 | 
			
		||||
  * Delete Actions logs older than 1 year by default (#31735)
 | 
			
		||||
  * Make OIDC introspection authentication strictly require Client ID and secret (#31632)
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Include file extension checks in attachment API (#32151)
 | 
			
		||||
  * Include all security fixes which have been backported to v1.22
 | 
			
		||||
 | 
			
		||||
* FEATURES
 | 
			
		||||
  * Allow to fork repository into the same owner (#32819)
 | 
			
		||||
  * Support "merge upstream branch" (Sync fork) (#32741)
 | 
			
		||||
  * Add Arch package registry (#32692)
 | 
			
		||||
  * Allow to disable the password-based login (sign-in) form (#32687)
 | 
			
		||||
  * Allow cropping an avatar before setting it (#32565)
 | 
			
		||||
  * Support quote selected comments to reply (#32431)
 | 
			
		||||
  * Add reviewers selection to new pull request (#32403)
 | 
			
		||||
  * Suggestions for issues (#32327)
 | 
			
		||||
  * Add priority to protected branch (#32286)
 | 
			
		||||
  * Included tag search capabilities (#32045)
 | 
			
		||||
  * Add option to filter board cards by labels and assignees (#31999)
 | 
			
		||||
  * Add automatic light/dark option for the colorblind theme (#31997)
 | 
			
		||||
  * Support migration from AWS CodeCommit (#31981)
 | 
			
		||||
  * Introduce globallock as distributed locks (#31908 & #31813)
 | 
			
		||||
  * Support compression for Actions logs & enable by default (#31761 & #32013)
 | 
			
		||||
  * Add pure SSH LFS support (#31516)
 | 
			
		||||
  * Add Passkey login support (#31504)
 | 
			
		||||
  * Actions support workflow dispatch event (#28163)
 | 
			
		||||
  * Support repo license (#24872)
 | 
			
		||||
  * Issue time estimate, meaningful time tracking (#23113)
 | 
			
		||||
  * GitHub like repo home page (#32213 & #32847)
 | 
			
		||||
  * Rearrange Clone Panel (#31142)
 | 
			
		||||
  * Enhancing Gitea OAuth2 Provider with Granular Scopes for Resource Access (#32573)
 | 
			
		||||
  * Use env GITEA_RUNNER_REGISTRATION_TOKEN as global runner token (#32946) #32964
 | 
			
		||||
  * Update i18n.go - Language Picker (#32933) #32935
 | 
			
		||||
 | 
			
		||||
* PERFORMANCE
 | 
			
		||||
  * Perf: add extra index to notification table (#32395)
 | 
			
		||||
  * Introduce OrgList and add LoadTeams, optimaze Load teams for orgs (#32543)
 | 
			
		||||
  * Improve performance of diffs (#32393)
 | 
			
		||||
  * Make LFS http_client parallel within a batch. (#32369)
 | 
			
		||||
  * Add new index for action to resolve the performance problem (#32333)
 | 
			
		||||
  * Improve get feed with pagination (#31821)
 | 
			
		||||
  * Performance improvements for pull request list API (#30490)
 | 
			
		||||
  * Use batch database operations instead of one by one to optimze api pulls (#32680)
 | 
			
		||||
  * Use gitrepo.GetTreePathLatestCommit to get file lastest commit instead from latest commit cache (#32987) #33046
 | 
			
		||||
 | 
			
		||||
* ENHANCEMENTS
 | 
			
		||||
  * Code
 | 
			
		||||
    * Remove unnecessary border in repo home page sidebar (#32767)
 | 
			
		||||
    * Add 'Copy path' button to file view (#32584)
 | 
			
		||||
    * Improve diff file tree (#32658)
 | 
			
		||||
    * Add new [lfs_client].BATCH_SIZE and [server].LFS_MAX_BATCH_SIZE config settings. (#32307)
 | 
			
		||||
    * Updated tokenizer to better matching when search for code snippets (#32261)
 | 
			
		||||
    * Change the code search to sort results by relevance (#32134)
 | 
			
		||||
    * Support migrating GitHub/GitLab PR draft status (#32242)
 | 
			
		||||
    * Move lock icon position and add additional tooltips to branch list page (#31839)
 | 
			
		||||
    * Add tag name in the commits list (#31082)
 | 
			
		||||
    * Add `MAX_ROWS` option for CSV rendering (#30268)
 | 
			
		||||
    * Allow code search by filename (#32210)
 | 
			
		||||
    * Make git push options accept short name (#32245)
 | 
			
		||||
    * Repo file list enhancements (#32835)
 | 
			
		||||
 | 
			
		||||
  * Markdown & Editor
 | 
			
		||||
    * Refactor markdown math render, add dollor-backquote syntax support (#32831)
 | 
			
		||||
    * Make Monaco theme follow browser, fully type codeeditor.ts (#32756)
 | 
			
		||||
    * Refactor markdown editor and use it for milestone description editor (#32688)
 | 
			
		||||
    * Add some handy markdown editor features (#32400)
 | 
			
		||||
    * Improve markdown textarea for indentation and lists (#31406)
 | 
			
		||||
 | 
			
		||||
  * Issue
 | 
			
		||||
    * Add label/author/assignee filters to the user/org home issue list (#32779)
 | 
			
		||||
    * Refactor issue filter (labels, poster, assignee) (#32771)
 | 
			
		||||
    * Style unification for the issue_management area (#32605)
 | 
			
		||||
    * Add "View all branches/tags" entry to Branch Selector (#32653)
 | 
			
		||||
    * Improve textarea paste (#31948)
 | 
			
		||||
    * Add avif image file support (#32508)
 | 
			
		||||
    * Prevent from submitting issue/comment on uploading (#32263)
 | 
			
		||||
    * Issue Templates: add option to have dropdown printed list (#31577)
 | 
			
		||||
    * Allow searching issues by ID (#31479)
 | 
			
		||||
    * Add `is_archived` option for issue indexer (#32735)
 | 
			
		||||
    * Improve attachment upload methods (#30513)
 | 
			
		||||
    * Support issue template assignees (#31083)
 | 
			
		||||
    * Prevent simultaneous editing of comments and issues (#31053)
 | 
			
		||||
    * Add issue comment when moving issues from one column to another of the project (#29311)
 | 
			
		||||
 | 
			
		||||
  * Pull Request
 | 
			
		||||
    * Display head branch more comfortable on pull request view (#32000)
 | 
			
		||||
    * Simplify review UI (#31062)
 | 
			
		||||
    * Allow force push to protected branches (#28086)
 | 
			
		||||
    * Add line-through for deleted branch on pull request view page (#32500)
 | 
			
		||||
    * Support requested_reviewers data in comment webhook events (#26178)
 | 
			
		||||
    * Allow maintainers to view and edit files of private repos when "Allow maintainers to edit" is enabled (#32215)
 | 
			
		||||
    * Allow including `Reviewed-on`/`Reviewed-by` lines for custom merge messages (#31211)
 | 
			
		||||
 | 
			
		||||
  * Actions
 | 
			
		||||
    * Render job title as commit message (#32748)
 | 
			
		||||
    * Refactor RepoActionView.vue, add `::group::` support (#32713)
 | 
			
		||||
    * Make RepoActionView.vue support `##[group]` (#32770)
 | 
			
		||||
    * Support `pull_request_target` event for commit status (#31703)
 | 
			
		||||
    * Detect whether action view branch was deleted (#32764)
 | 
			
		||||
    * Allow users with write permission to run actions (#32644)
 | 
			
		||||
    * Show latest run when visit /run/latest (#31808)
 | 
			
		||||
 | 
			
		||||
  * Packages
 | 
			
		||||
    * Improve rubygems package registry (#31357)
 | 
			
		||||
    * Add support for npm bundleDependencies (#30751)
 | 
			
		||||
    * Add signature support for the RPM module (#27069)
 | 
			
		||||
    * Extract and display readme and comments for Composer packages (#30927)
 | 
			
		||||
 | 
			
		||||
  * Project
 | 
			
		||||
    * Add title to project view page (#32747)
 | 
			
		||||
    * Set the columns height to hug all its contents (#31726)
 | 
			
		||||
    * Rename project `board` -> `column` to make the UI less confusing (#30170)
 | 
			
		||||
 | 
			
		||||
  * User & Organazition
 | 
			
		||||
    * Use better name for userinfo structure (#32544)
 | 
			
		||||
    * Use user.FullName in Oauth2 id_token response (#32542)
 | 
			
		||||
    * Limit org member view of restricted users (#32211)
 | 
			
		||||
    * Allow disabling authentication related user features (#31535)
 | 
			
		||||
    * Add option to change mail from user display name (#31528)
 | 
			
		||||
    * Use FullName in Emails to address the recipient if possible (#31527)
 | 
			
		||||
 | 
			
		||||
  * Administration
 | 
			
		||||
    * Add support for a credentials chain for minio access (#31051)
 | 
			
		||||
    * Move admin routers from /admin to /-/admin (#32189)
 | 
			
		||||
    * Add cache test for admins (#31265)
 | 
			
		||||
    * Add option for mailer to override mail headers (#27860)
 | 
			
		||||
    * Azure blob storage support (#30995)
 | 
			
		||||
    * Supports forced use of S3 virtual-hosted style (#30969)
 | 
			
		||||
    * Move repository visibility to danger zone in the settings area (#31126)
 | 
			
		||||
 | 
			
		||||
  * Others
 | 
			
		||||
    * Remove urls from translations (#31950)
 | 
			
		||||
    * Simplify 404/500 page (#31409)
 | 
			
		||||
    * Optimize installation-page experience (#32558)
 | 
			
		||||
    * Refactor login page (#31530)
 | 
			
		||||
    * Add new event commit status creation and webhook implementation (#27151)
 | 
			
		||||
    * Repo Activity: count new issues that were closed (#31776)
 | 
			
		||||
    * Set manual `tabindex`es on login page (#31689)
 | 
			
		||||
    * Add `YEAR`, `MONTH`, `MONTH_ENGLISH`, `DAY` variables for template repos (#31584)
 | 
			
		||||
    * Add typescript guideline and typescript-specific eslint plugins and fix issues (#31521)
 | 
			
		||||
    * Make toast support preventDuplicates (#31501)
 | 
			
		||||
    * Fix tautological conditions (#30735)
 | 
			
		||||
    * Issue change title notifications (#33050) #33065
 | 
			
		||||
 | 
			
		||||
* API
 | 
			
		||||
  * Implement update branch API (#32433)
 | 
			
		||||
  * Fix missing outputs for jobs with matrix (#32823)
 | 
			
		||||
  * Make API "compare" accept commit IDs (#32801)
 | 
			
		||||
  * Add github compatible tarball download API endpoints (#32572)
 | 
			
		||||
  * Harden runner updateTask and updateLog api (#32462)
 | 
			
		||||
  * Add `DISABLE_ORGANIZATIONS_PAGE` and `DISABLE_CODE_PAGE` settings for explore pages and fix an issue related to user search (#32288)
 | 
			
		||||
  * Make admins adhere to branch protection rules (#32248)
 | 
			
		||||
  * Calculate `PublicOnly` for org membership only once (#32234)
 | 
			
		||||
  * Allow filtering PRs by poster in the ListPullRequests API (#32209)
 | 
			
		||||
  * Return 404 instead of error when commit not exist (#31977)
 | 
			
		||||
  * Save initial signup information for users to aid in spam prevention (#31852)
 | 
			
		||||
  * Fix upload maven pacakge parallelly (#31851)
 | 
			
		||||
  * Fix null requested_reviewer from API (#31773)
 | 
			
		||||
  * Add permission description for API to add repo collaborator (#31744)
 | 
			
		||||
  * Add return type to GetRawFileOrLFS and GetRawFile (#31680)
 | 
			
		||||
  * Add skip secondary authorization option for public oauth2 clients (#31454)
 | 
			
		||||
  * Add tag protection via rest api #17862 (#31295)
 | 
			
		||||
  * Document possible action types for the user activity feed API (#31196)
 | 
			
		||||
  * Add topics for repository API (#31127)
 | 
			
		||||
  * Add support for searching users by email (#30908)
 | 
			
		||||
  * Add API endpoints for getting action jobs status (#26673)
 | 
			
		||||
 | 
			
		||||
* REFACTOR
 | 
			
		||||
  * Update JS and PY dependencies (#31940)
 | 
			
		||||
  * Enable `no-jquery/no-parse-html-literal` and fix violation (#31684)
 | 
			
		||||
  * Refactor image diff (#31444)
 | 
			
		||||
  * Refactor CSRF token (#32216)
 | 
			
		||||
  * Fix some typescript issues (#32586)
 | 
			
		||||
  * Refactor names (#31405)
 | 
			
		||||
  * Use per package global lock for container uploads instead of memory lock (#31860)
 | 
			
		||||
  * Move team related functions to service layer (#32537)
 | 
			
		||||
  * Move GetFeeds to service layer (#32526)
 | 
			
		||||
  * Resolve lint for unused parameter and unnecessary type arguments (#30750)
 | 
			
		||||
  * Reimplement GetUserOrgsList to make it simple and clear (#32486)
 | 
			
		||||
  * Move some functions from issue.go to standalone files (#32468)
 | 
			
		||||
  * Refactor sidebar assignee&milestone&project selectors (#32465)
 | 
			
		||||
  * Refactor sidebar label selector (#32460)
 | 
			
		||||
  * Fix a number of typescript issues (#32459)
 | 
			
		||||
  * Refactor language menu and dom utils (#32450)
 | 
			
		||||
  * Refactor issue page info (#32445)
 | 
			
		||||
  * Split issue sidebar into small templates (#32444)
 | 
			
		||||
  * Refactor template ctx and render utils (#32422)
 | 
			
		||||
  * Refactor repo legacy (#32404)
 | 
			
		||||
  * Refactor markup package (#32399)
 | 
			
		||||
  * Refactor markup render system (#32533 & #32589 & #32612)
 | 
			
		||||
  * Refactor the DB migration system slightly (#32344)
 | 
			
		||||
  * Remove jQuery import from some files (#32512)
 | 
			
		||||
  * Strict pagination check (#32548)
 | 
			
		||||
  * Split mail sender sub package from mailer service package (#32618)
 | 
			
		||||
  * Remove outdated code about fixture generation (#32708)
 | 
			
		||||
  * Refactor RepoBranchTagSelector (#32681)
 | 
			
		||||
  * Refactor issue list (#32755)
 | 
			
		||||
  * Refactor LabelEdit (#32752)
 | 
			
		||||
  * Split issue/pull view router function as multiple smaller functions (#32749)
 | 
			
		||||
  * Refactor some LDAP code (#32849)
 | 
			
		||||
  * Unify repo search order by logic (#30876)
 | 
			
		||||
  * Remove duplicate empty repo check in delete branch API (#32569)
 | 
			
		||||
  * Replace deprecated `math/rand` functions (#30733)
 | 
			
		||||
  * Remove fomantic dimmer module (#30723)
 | 
			
		||||
  * Add types to fetch,toast,bootstrap,svg (#31627)
 | 
			
		||||
  * Refactor webhook (#31587)
 | 
			
		||||
  * Move AddCollabrator and CreateRepositoryByExample to service layer (#32419)
 | 
			
		||||
  * Refactor RepoRefByType (#32413)
 | 
			
		||||
  * Refactor: remove redundant err declarations (#32381)
 | 
			
		||||
  * Refactor markup code (#31399)
 | 
			
		||||
  * Refactor render system (orgmode) (#32671)
 | 
			
		||||
  * Refactor render system (#32492)
 | 
			
		||||
  * Refactor markdown render (#32736 & #32728)
 | 
			
		||||
  * Refactor repo unit "disabled" check (#31389)
 | 
			
		||||
  * Refactor route path normalization (#31381)
 | 
			
		||||
  * Refactor to use UnsafeStringToBytes (#31358)
 | 
			
		||||
  * Migrate vue components to setup (#32329)
 | 
			
		||||
  * Refactor globallock (#31933)
 | 
			
		||||
  * Use correct function name (#31887)
 | 
			
		||||
  * Use a common message template instead of a special one (#31878)
 | 
			
		||||
  * Fix a number of Typescript issues (#31877)
 | 
			
		||||
  * Refactor dropzone (#31482)
 | 
			
		||||
  * Move custom `tw-` helpers to tailwind plugin (#31184)
 | 
			
		||||
  * Replace `gt-word-break` with `tw-break-anywhere` (#31183)
 | 
			
		||||
  * Drop `IDOrderDesc` for listing Actions task and always order by `id DESC` (#31150)
 | 
			
		||||
  * Split common-global.js into separate files (#31438)
 | 
			
		||||
  * Improve detecting empty files (#31332)
 | 
			
		||||
  * Use `querySelector` over alternative DOM methods (#31280)
 | 
			
		||||
  * Remove jQuery `.text()` (#30506)
 | 
			
		||||
  * Use repo as of renderctx's member rather than a repoPath on metas (#29222)
 | 
			
		||||
  * Refactor some frontend problems (#32646)
 | 
			
		||||
  * Refactor DateUtils and merge TimeSince (#32409)
 | 
			
		||||
  * Replace DateTime with proper functions (#32402)
 | 
			
		||||
  * Replace DateTime with DateUtils (#32383)
 | 
			
		||||
  * Convert frontend code to typescript (#31559)
 | 
			
		||||
  * Refactor maven package registry (#33049) #33057
 | 
			
		||||
  * Refactor testfixtures #33028
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix issues with inconsistent spacing in areas (#32607)
 | 
			
		||||
  * Fix incomplete Actions status aggregations (#32859)
 | 
			
		||||
  * In some lfs server implementations, they require the ref attribute. (#32838)
 | 
			
		||||
  * Update the list of watchers and stargazers when clicking watch/unwatch or star/unstar (#32570)
 | 
			
		||||
  * Fix `recentupdate` sorting bugs (#32505)
 | 
			
		||||
  * Fix incorrect "Target branch does not exist" in PR title (#32222)
 | 
			
		||||
  * Handle "close" actionable references for manual merges (#31879)
 | 
			
		||||
  * render plain text file if the LFS object doesn't exist (#31812)
 | 
			
		||||
  * Fix Null Pointer error for CommitStatusesHideActionsURL (#31731)
 | 
			
		||||
  * Fix loadRepository error when access user dashboard (#31719)
 | 
			
		||||
  * Hide the "Details" link of commit status when the user cannot access actions (#30156)
 | 
			
		||||
  * Fix duplicate dropdown dividers (#32760)
 | 
			
		||||
  * Fix SSPI button visibility when SSPI is the only enabled method (#32841)
 | 
			
		||||
  * Fix overflow on org header (#32837)
 | 
			
		||||
  * Exclude protected branches from recently pushed (#31748)
 | 
			
		||||
  * Fix large image overflow in comment page (#31740)
 | 
			
		||||
  * Fix milestone deadline and date related problems (#32339)
 | 
			
		||||
  * Fix markdown preview $$ support (#31514)
 | 
			
		||||
  * Fix a compilation error in the Gitpod environment (#32559)
 | 
			
		||||
  * Fix PR diff review form submit (#32596)
 | 
			
		||||
  * Fix a number of typescript issues (#32308)
 | 
			
		||||
  * Fix some function names in comment (#32300)
 | 
			
		||||
  * Fix absolute-date (#32375)
 | 
			
		||||
  * Clarify Actions resources ownership (#31724)
 | 
			
		||||
  * Try to fix ACME directory problem (#33072) #33077
 | 
			
		||||
  * Inherit submodules from template repository content (#16237) #33068
 | 
			
		||||
  * Use project's redirect url instead of composing url (#33058) #33064
 | 
			
		||||
  * Fix toggle commit body button ui when latest commit message is long (#32997) #33034
 | 
			
		||||
  * Fix package error handling and npm meta and empty repo guide #33112
 | 
			
		||||
  * Fix empty git repo handling logic and fix mobile view (#33101) #33102
 | 
			
		||||
  * Fix line-number and scroll bugs (#33094) #33095
 | 
			
		||||
  * Fix bleve fuzziness search (#33078) #33087
 | 
			
		||||
  * Fix broken forms #33082
 | 
			
		||||
  * Fix empty repo updated time (#33120) #33124
 | 
			
		||||
  * Add missing transaction when set merge #33113
 | 
			
		||||
  * Fix issue comment number (#30556) #33055
 | 
			
		||||
  * Fix duplicate co-author in squashed merge commit messages (#33020) #33054
 | 
			
		||||
  * Fix Agit pull request permission check (#32999) #33005
 | 
			
		||||
  * Fix scoped label ui when contains emoji (#33007) #33014
 | 
			
		||||
  * Fix bug on activities (#33008) #33016
 | 
			
		||||
  * Fix review code comment avatar alignment (#33031) #33032
 | 
			
		||||
  * Fix templating in pull request comparison (#33025) #33038
 | 
			
		||||
  * Fix bug automerge cannot be chosed when there is only 1 merge style (#33040) #33043
 | 
			
		||||
  * Fix settings not being loaded at CLI (#26402) #33048
 | 
			
		||||
  * Support for email addresses containing uppercase characters when activating user account (#32998) #33001
 | 
			
		||||
  * Support org labels when adding labels by label names (#32988) #32996
 | 
			
		||||
  * Do not render truncated links in markdown (#32980) #32983
 | 
			
		||||
  * Demilestone should not include milestone (#32923) #32979
 | 
			
		||||
  * Fix Azure blob object Seek (#32974) #32975
 | 
			
		||||
  * Fix maven pom inheritance (#32943) #32976
 | 
			
		||||
  * Fix textarea newline handle (#32966) #32977
 | 
			
		||||
  * Fix outdated tmpl code (#32953) #32961
 | 
			
		||||
  * Fix commit range paging (#32944) #32962
 | 
			
		||||
  * Fix repo avatar conflict (#32958) #32960
 | 
			
		||||
  * Fix trailing comma not matched in the case of alphanumeric issue (#32945)
 | 
			
		||||
  * Relax the version checking for Arch packages (#32908) #32913
 | 
			
		||||
  * Add more load functions to make sure the reference object loaded (#32901) #32912
 | 
			
		||||
  * Filter reviews of one pull request in memory instead of database to reduce slow response because of lacking database index (#33106) #33128
 | 
			
		||||
  * Fix git remote error check, fix dependencies, fix js error (#33129) #33133
 | 
			
		||||
 | 
			
		||||
* MISC
 | 
			
		||||
  * Optimize branch protection rule loading (#32280)
 | 
			
		||||
  * Bump to go 1.23 (#31855)
 | 
			
		||||
  * Remove unused call to $.HeadRepo in view_title template (#32317)
 | 
			
		||||
  * Do not display `attestation-manifest` and use short sha256 instead of full sha256 (#32851)
 | 
			
		||||
  * Upgrade htmx to 2.0.4 (#32834)
 | 
			
		||||
  * Improve JSX/TSX support in code editor (#32833)
 | 
			
		||||
  * Add User-Agent for gitea's self-implemented lfs client. (#32832)
 | 
			
		||||
  * Use errors.New to replace fmt.Errorf with no parameters (#32800)
 | 
			
		||||
  * Add "n commits" link to contributors in contributors graph page (#32799)
 | 
			
		||||
  * Update dependencies, tweak eslint (#32719)
 | 
			
		||||
  * Remove all "floated" CSS styles (#32691)
 | 
			
		||||
  * Show tag name on branch/tag selector if repo shown from tag ref (#32689)
 | 
			
		||||
  * Use new mail package instead of an unmintained one (#32682)
 | 
			
		||||
  * Optimize the styling of icon buttons within file-header-right (#32675)
 | 
			
		||||
  * Validate OAuth Redirect URIs (#32643)
 | 
			
		||||
  * Support optional/configurable IAMEndpoint for Minio Client (#32581) (#32581)
 | 
			
		||||
  * Make search box in issue sidebar dropdown list always show when scrolling (#32576)
 | 
			
		||||
  * Bump CI,Flake and Snap to Node 22 (#32487)
 | 
			
		||||
  * Update `github.com/meilisearch/meilisearch-go` (#32484)
 | 
			
		||||
  * Add `DEFAULT_MIRROR_REPO_UNITS` and `DEFAULT_TEMPLATE_REPO_UNITS` options (#32416)
 | 
			
		||||
  * Update go dependencies (#32389)
 | 
			
		||||
  * Update JS and PY dependencies (#32388)
 | 
			
		||||
  * Upgrade rollup to 4.24.0 (#32312)
 | 
			
		||||
  * Upgrade vue to 3.5.12 (#32311)
 | 
			
		||||
  * Improve the maintainblity of the reserved username list (#32229)
 | 
			
		||||
  * Upgrade htmx to 2.0.3 (#32192)
 | 
			
		||||
  * Count typescript files as frontend for labeling (#32088)
 | 
			
		||||
  * Only use Host header from reverse proxy (#32060)
 | 
			
		||||
  * Failed authentications are logged to level Warning (#32016)
 | 
			
		||||
  * Enhance USER_DISABLED_FEATURES to allow disabling change username or full name (#31959)
 | 
			
		||||
  * Distinguish official vs non-official reviews, add tool tips, and upgr… (#31924)
 | 
			
		||||
  * Update mermaid to v11 (#31913)
 | 
			
		||||
  * Bump relative-time-element to v4.4.3 (#31910)
 | 
			
		||||
  * Upgrade `htmx` to `2.0.2` (#31847)
 | 
			
		||||
  * Add warning message in merge instructions when `AutodetectManualMerge` was not enabled (#31805)
 | 
			
		||||
  * Add types to various low-level functions (#31781)
 | 
			
		||||
  * Update JS dependencies (#31766)
 | 
			
		||||
  * Remove unused code from models/repos/release.go (#31756)
 | 
			
		||||
  * Support delete user email in admin panel (#31690)
 | 
			
		||||
  * Add `username` to OIDC introspection response (#31688)
 | 
			
		||||
  * Use GetDisplayName() instead of DisplayName() to generate rss feeds (#31687)
 | 
			
		||||
  * Code editor theme enhancements (#31629)
 | 
			
		||||
  * Update JS dependencies (#31616)
 | 
			
		||||
  * Add types for js globals (#31586)
 | 
			
		||||
  * Add back esbuild-loader for .js files (#31585)
 | 
			
		||||
  * Don't show hidden labels when filling out an issue template (#31576)
 | 
			
		||||
  * Allow synchronizing user status from OAuth2 login providers (#31572)
 | 
			
		||||
  * Display app name in the registration email title (#31562)
 | 
			
		||||
  * Use stable version of fabric (#31526)
 | 
			
		||||
  * Support legacy _links LFS batch responses (#31513)
 | 
			
		||||
  * Fix JS error with disabled attachment and easymde (#31511)
 | 
			
		||||
  * Always use HTML attributes for avatar size (#31509)
 | 
			
		||||
  * Use nolyfill to remove some polyfills (#31468)
 | 
			
		||||
  * Disable issue/PR comment button given empty input (#31463)
 | 
			
		||||
  * Add simple JS init performance trace (#31459)
 | 
			
		||||
  * Bump htmx to 2.0.0 (#31413)
 | 
			
		||||
  * Update JS dependencies, remove `eslint-plugin-jquery` (#31402)
 | 
			
		||||
  * Split org Propfile README to a new tab `overview` (#31373)
 | 
			
		||||
  * Update nix flake and add gofumpt (#31320)
 | 
			
		||||
  * Code optimization (#31315)
 | 
			
		||||
  * Enable poetry non-package mode (#31282)
 | 
			
		||||
  * Optimize profile layout to enhance visual experience (#31278)
 | 
			
		||||
  * Update `golang.org/x/net` (#31260)
 | 
			
		||||
  * Bump `@github/relative-time-element` to v4.4.1 (#31232)
 | 
			
		||||
  * Remove unnecessary inline style for tab-size (#31224)
 | 
			
		||||
  * Update golangci-lint to v1.59.0 (#31221)
 | 
			
		||||
  * Update chroma to v2.14.0 (#31177)
 | 
			
		||||
  * Update JS dependencies (#31120)
 | 
			
		||||
  * Improve the handling of `jobs.<job_id>.if` (#31070)
 | 
			
		||||
  * Clean up revive linter config, tweak golangci output (#30980)
 | 
			
		||||
  * Use CSS `inset` shorthand (#30939)
 | 
			
		||||
  * Forbid deprecated `break-word` in CSS (#30934)
 | 
			
		||||
  * Remove obsolete monaco workaround (#30893)
 | 
			
		||||
  * Update JS dependencies, add new eslint rules (#30840)
 | 
			
		||||
  * Fix body margin shifting with modals, fix error on project column edit (#30831)
 | 
			
		||||
  * Remove disk-clean workflow (#30741)
 | 
			
		||||
  * Bump `github.com/google/go-github` to v61 (#30738)
 | 
			
		||||
  * Add built js files to eslint ignore (#30737)
 | 
			
		||||
  * Use `ProtonMail/go-crypto` for `opengpg` in tests (#30736)
 | 
			
		||||
  * Upgrade xorm to v1.3.9 and improve some migrations Sync (#29899)
 | 
			
		||||
  * Added default sorting milestones by name (#27084)
 | 
			
		||||
  * Enable `unparam` linter (#31277)
 | 
			
		||||
  * Use Alpine 3.21 for the docker images (#32924) #32951
 | 
			
		||||
  * Bump x/net (#32896) #32899
 | 
			
		||||
  * Use -s -w ldflags for release artifacts (#33041) #33042
 | 
			
		||||
  * Remove aws go sdk package dependency (#33029) #33047
 | 
			
		||||
 | 
			
		||||
## [1.22.4](https://github.com/go-gitea/gitea/releases/tag/v1.22.4) - 2024-11-14
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
# Build stage
 | 
			
		||||
FROM docker.io/library/golang:1.23-alpine3.20 AS build-env
 | 
			
		||||
FROM docker.io/library/golang:1.23-alpine3.21 AS build-env
 | 
			
		||||
 | 
			
		||||
ARG GOPROXY
 | 
			
		||||
ENV GOPROXY=${GOPROXY:-direct}
 | 
			
		||||
@@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
 | 
			
		||||
              /go/src/code.gitea.io/gitea/environment-to-ini
 | 
			
		||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
 | 
			
		||||
 | 
			
		||||
FROM docker.io/library/alpine:3.20
 | 
			
		||||
FROM docker.io/library/alpine:3.21
 | 
			
		||||
LABEL maintainer="maintainers@gitea.io"
 | 
			
		||||
 | 
			
		||||
EXPOSE 22 3000
 | 
			
		||||
@@ -78,7 +78,7 @@ ENV GITEA_CUSTOM=/data/gitea
 | 
			
		||||
VOLUME ["/data"]
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["/usr/bin/entrypoint"]
 | 
			
		||||
CMD ["/bin/s6-svscan", "/etc/s6"]
 | 
			
		||||
CMD ["/usr/bin/s6-svscan", "/etc/s6"]
 | 
			
		||||
 | 
			
		||||
COPY --from=build-env /tmp/local /
 | 
			
		||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
# Build stage
 | 
			
		||||
FROM docker.io/library/golang:1.23-alpine3.20 AS build-env
 | 
			
		||||
FROM docker.io/library/golang:1.23-alpine3.21 AS build-env
 | 
			
		||||
 | 
			
		||||
ARG GOPROXY
 | 
			
		||||
ENV GOPROXY=${GOPROXY:-direct}
 | 
			
		||||
@@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
 | 
			
		||||
              /go/src/code.gitea.io/gitea/environment-to-ini
 | 
			
		||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
 | 
			
		||||
 | 
			
		||||
FROM docker.io/library/alpine:3.20
 | 
			
		||||
FROM docker.io/library/alpine:3.21
 | 
			
		||||
LABEL maintainer="maintainers@gitea.io"
 | 
			
		||||
 | 
			
		||||
EXPOSE 2222 3000
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								Makefile
									
									
									
									
									
								
							@@ -26,17 +26,17 @@ COMMA := ,
 | 
			
		||||
XGO_VERSION := go-1.23.x
 | 
			
		||||
 | 
			
		||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
 | 
			
		||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
 | 
			
		||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.0.3
 | 
			
		||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
 | 
			
		||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
 | 
			
		||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
 | 
			
		||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
 | 
			
		||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
 | 
			
		||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0
 | 
			
		||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
 | 
			
		||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
 | 
			
		||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
 | 
			
		||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
 | 
			
		||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
 | 
			
		||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.15.3
 | 
			
		||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.0
 | 
			
		||||
 | 
			
		||||
DOCKER_IMAGE ?= gitea/gitea
 | 
			
		||||
DOCKER_TAG ?= latest
 | 
			
		||||
@@ -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/...)
 | 
			
		||||
@@ -508,7 +508,7 @@ unit-test-coverage:
 | 
			
		||||
tidy:
 | 
			
		||||
	$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
 | 
			
		||||
	$(GO) mod tidy -compat=$(MIN_GO_VERSION)
 | 
			
		||||
	@$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
 | 
			
		||||
	$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
 | 
			
		||||
 | 
			
		||||
vendor: go.mod go.sum
 | 
			
		||||
	$(GO) mod vendor
 | 
			
		||||
@@ -806,22 +806,22 @@ $(DIST_DIRS):
 | 
			
		||||
 | 
			
		||||
.PHONY: release-windows
 | 
			
		||||
release-windows: | $(DIST_DIRS)
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
 | 
			
		||||
ifeq (,$(findstring gogit,$(TAGS)))
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
.PHONY: release-linux
 | 
			
		||||
release-linux: | $(DIST_DIRS)
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
 | 
			
		||||
 | 
			
		||||
.PHONY: release-darwin
 | 
			
		||||
release-darwin: | $(DIST_DIRS)
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
 | 
			
		||||
 | 
			
		||||
.PHONY: release-freebsd
 | 
			
		||||
release-freebsd: | $(DIST_DIRS)
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
 | 
			
		||||
 | 
			
		||||
.PHONY: release-copy
 | 
			
		||||
release-copy: | $(DIST_DIRS)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										81
									
								
								assets/go-licenses.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										81
									
								
								assets/go-licenses.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -7,6 +7,7 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	auth_model "code.gitea.io/gitea/models/auth"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
@@ -61,6 +62,16 @@ var microcmdUserCreate = &cli.Command{
 | 
			
		||||
			Name:  "access-token",
 | 
			
		||||
			Usage: "Generate access token for the user",
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:  "access-token-name",
 | 
			
		||||
			Usage: `Name of the generated access token`,
 | 
			
		||||
			Value: "gitea-admin",
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:  "access-token-scopes",
 | 
			
		||||
			Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
 | 
			
		||||
			Value: "all",
 | 
			
		||||
		},
 | 
			
		||||
		&cli.BoolFlag{
 | 
			
		||||
			Name:  "restricted",
 | 
			
		||||
			Usage: "Make a restricted user account",
 | 
			
		||||
@@ -69,6 +80,10 @@ var microcmdUserCreate = &cli.Command{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runCreateUser(c *cli.Context) error {
 | 
			
		||||
	// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
 | 
			
		||||
	// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
 | 
			
		||||
	setting.LoadSettings()
 | 
			
		||||
 | 
			
		||||
	if err := argsSet(c, "email"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -158,23 +173,39 @@ func runCreateUser(c *cli.Context) error {
 | 
			
		||||
		IsRestricted: restricted,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var accessTokenName string
 | 
			
		||||
	var accessTokenScope auth_model.AccessTokenScope
 | 
			
		||||
	if c.IsSet("access-token") {
 | 
			
		||||
		accessTokenName = strings.TrimSpace(c.String("access-token-name"))
 | 
			
		||||
		if accessTokenName == "" {
 | 
			
		||||
			return errors.New("access-token-name cannot be empty")
 | 
			
		||||
		}
 | 
			
		||||
		var err error
 | 
			
		||||
		accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("invalid access token scope provided: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		if !accessTokenScope.HasPermissionScope() {
 | 
			
		||||
			return errors.New("access token does not have any permission")
 | 
			
		||||
		}
 | 
			
		||||
	} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
 | 
			
		||||
		return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// arguments should be prepared before creating the user & access token, in case there is anything wrong
 | 
			
		||||
 | 
			
		||||
	// create the user
 | 
			
		||||
	if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
 | 
			
		||||
		return fmt.Errorf("CreateUser: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Bool("access-token") {
 | 
			
		||||
		t := &auth_model.AccessToken{
 | 
			
		||||
			Name: "gitea-admin",
 | 
			
		||||
			UID:  u.ID,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	// create the access token
 | 
			
		||||
	if accessTokenScope != "" {
 | 
			
		||||
		t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
 | 
			
		||||
		if err := auth_model.NewAccessToken(ctx, t); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fmt.Printf("Access token was successfully created... %s\n", t.Token)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Printf("New user '%s' has been successfully created!\n", username)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,37 +8,97 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	auth_model "code.gitea.io/gitea/models/auth"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestAdminUserCreate(t *testing.T) {
 | 
			
		||||
	app := NewMainApp(AppVersion{})
 | 
			
		||||
 | 
			
		||||
	reset := func() {
 | 
			
		||||
		assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
 | 
			
		||||
		assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
 | 
			
		||||
		require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
 | 
			
		||||
		require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
 | 
			
		||||
		require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
 | 
			
		||||
	}
 | 
			
		||||
	t.Run("MustChangePassword", func(t *testing.T) {
 | 
			
		||||
		type check struct{ IsAdmin, MustChangePassword bool }
 | 
			
		||||
		createCheck := func(name, args string) check {
 | 
			
		||||
			assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
 | 
			
		||||
			u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
 | 
			
		||||
			return check{u.IsAdmin, u.MustChangePassword}
 | 
			
		||||
		}
 | 
			
		||||
		reset()
 | 
			
		||||
		assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u", ""), "first non-admin user doesn't need to change password")
 | 
			
		||||
 | 
			
		||||
		reset()
 | 
			
		||||
		assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u", "--admin"), "first admin user doesn't need to change password")
 | 
			
		||||
 | 
			
		||||
		reset()
 | 
			
		||||
		assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u", "--admin --must-change-password"))
 | 
			
		||||
		assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u2", "--admin"))
 | 
			
		||||
		assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u3", "--admin --must-change-password=false"))
 | 
			
		||||
		assert.Equal(t, check{IsAdmin: false, MustChangePassword: true}, createCheck("u4", ""))
 | 
			
		||||
		assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	createUser := func(name, args string) error {
 | 
			
		||||
		return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args)))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type createCheck struct{ IsAdmin, MustChangePassword bool }
 | 
			
		||||
	createUser := func(name, args string) createCheck {
 | 
			
		||||
		assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
 | 
			
		||||
		u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
 | 
			
		||||
		return createCheck{u.IsAdmin, u.MustChangePassword}
 | 
			
		||||
	}
 | 
			
		||||
	reset()
 | 
			
		||||
	assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password")
 | 
			
		||||
	t.Run("AccessToken", func(t *testing.T) {
 | 
			
		||||
		// no generated access token
 | 
			
		||||
		reset()
 | 
			
		||||
		assert.NoError(t, createUser("u", "--random-password"))
 | 
			
		||||
		assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
 | 
			
		||||
		assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
 | 
			
		||||
 | 
			
		||||
	reset()
 | 
			
		||||
	assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password")
 | 
			
		||||
		// using "--access-token" only means "all" access
 | 
			
		||||
		reset()
 | 
			
		||||
		assert.NoError(t, createUser("u", "--random-password --access-token"))
 | 
			
		||||
		assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
 | 
			
		||||
		assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
 | 
			
		||||
		accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
 | 
			
		||||
		hasScopes, err := accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.True(t, hasScopes)
 | 
			
		||||
 | 
			
		||||
	reset()
 | 
			
		||||
	assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password"))
 | 
			
		||||
	assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin"))
 | 
			
		||||
	assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false"))
 | 
			
		||||
	assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", ""))
 | 
			
		||||
	assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false"))
 | 
			
		||||
		// using "--access-token" with name & scopes
 | 
			
		||||
		reset()
 | 
			
		||||
		assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user"))
 | 
			
		||||
		assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
 | 
			
		||||
		assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
 | 
			
		||||
		accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
 | 
			
		||||
		hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.True(t, hasScopes)
 | 
			
		||||
		hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.False(t, hasScopes)
 | 
			
		||||
 | 
			
		||||
		// using "--access-token-name" without "--access-token"
 | 
			
		||||
		reset()
 | 
			
		||||
		err = createUser("u", "--random-password --access-token-name new-token-name")
 | 
			
		||||
		assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
 | 
			
		||||
		assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
 | 
			
		||||
		assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
 | 
			
		||||
 | 
			
		||||
		// using "--access-token-scopes" without "--access-token"
 | 
			
		||||
		reset()
 | 
			
		||||
		err = createUser("u", "--random-password --access-token-scopes read:issue")
 | 
			
		||||
		assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
 | 
			
		||||
		assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
 | 
			
		||||
		assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
 | 
			
		||||
 | 
			
		||||
		// empty permission
 | 
			
		||||
		reset()
 | 
			
		||||
		err = createUser("u", "--random-password --access-token --access-token-scopes public-only")
 | 
			
		||||
		assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
 | 
			
		||||
		assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
 | 
			
		||||
		assert.ErrorContains(t, err, "access token does not have any permission")
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:  "scopes",
 | 
			
		||||
			Value: "",
 | 
			
		||||
			Usage: "Comma separated list of scopes to apply to access token",
 | 
			
		||||
			Value: "all",
 | 
			
		||||
			Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	Action: runGenerateAccessToken,
 | 
			
		||||
@@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
 | 
			
		||||
 | 
			
		||||
func runGenerateAccessToken(c *cli.Context) error {
 | 
			
		||||
	if !c.IsSet("username") {
 | 
			
		||||
		return errors.New("You must provide a username to generate a token for")
 | 
			
		||||
		return errors.New("you must provide a username to generate a token for")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx, cancel := installSignals()
 | 
			
		||||
@@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("invalid access token scope provided: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	if !accessTokenScope.HasPermissionScope() {
 | 
			
		||||
		return errors.New("access token does not have any permission")
 | 
			
		||||
	}
 | 
			
		||||
	t.Scope = accessTokenScope
 | 
			
		||||
 | 
			
		||||
	// create the token
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ import (
 | 
			
		||||
var CmdMigrate = &cli.Command{
 | 
			
		||||
	Name:        "migrate",
 | 
			
		||||
	Usage:       "Migrate the database",
 | 
			
		||||
	Description: "This is a command for migrating the database, so that you can run gitea admin create-user before starting the server.",
 | 
			
		||||
	Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`,
 | 
			
		||||
	Action:      runMigrate,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								cmd/web.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								cmd/web.go
									
									
									
									
									
								
							@@ -12,6 +12,7 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	_ "net/http/pprof" // Used for debugging if enabled and a web server is running
 | 
			
		||||
 | 
			
		||||
@@ -115,6 +116,16 @@ func showWebStartupMessage(msg string) {
 | 
			
		||||
	log.Info("* CustomPath: %s", setting.CustomPath)
 | 
			
		||||
	log.Info("* ConfigFile: %s", setting.CustomConf)
 | 
			
		||||
	log.Info("%s", msg) // show startup message
 | 
			
		||||
 | 
			
		||||
	if setting.CORSConfig.Enabled {
 | 
			
		||||
		log.Info("CORS Service Enabled")
 | 
			
		||||
	}
 | 
			
		||||
	if setting.DefaultUILocation != time.Local {
 | 
			
		||||
		log.Info("Default UI Location is %v", setting.DefaultUILocation.String())
 | 
			
		||||
	}
 | 
			
		||||
	if setting.MailService != nil {
 | 
			
		||||
		log.Info("Mail Service Enabled: RegisterEmailConfirm=%v, Service.EnableNotifyMail=%v", setting.Service.RegisterEmailConfirm, setting.Service.EnableNotifyMail)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func serveInstall(ctx *cli.Context) error {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/process"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/caddyserver/certmagic"
 | 
			
		||||
)
 | 
			
		||||
@@ -54,8 +55,6 @@ func runACME(listenAddr string, m http.Handler) error {
 | 
			
		||||
		altTLSALPNPort = p
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	magic := certmagic.NewDefault()
 | 
			
		||||
	magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
 | 
			
		||||
	// Try to use private CA root if provided, otherwise defaults to system's trust
 | 
			
		||||
	var certPool *x509.CertPool
 | 
			
		||||
	if setting.AcmeCARoot != "" {
 | 
			
		||||
@@ -65,8 +64,20 @@ func runACME(listenAddr string, m http.Handler) error {
 | 
			
		||||
			log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
 | 
			
		||||
		CA:                      setting.AcmeURL,
 | 
			
		||||
	// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
 | 
			
		||||
	// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
 | 
			
		||||
	// And one more thing, no idea why we should set the global default variables here
 | 
			
		||||
	// But it seems that the current ACME code needs these global variables to make renew work.
 | 
			
		||||
	// Otherwise, "renew" will use incorrect storage path
 | 
			
		||||
	oldDefaultACME := certmagic.DefaultACME
 | 
			
		||||
	certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
 | 
			
		||||
	certmagic.DefaultACME = certmagic.ACMEIssuer{
 | 
			
		||||
		// try to use the default values provided by DefaultACME
 | 
			
		||||
		CA:        util.IfZero(setting.AcmeURL, oldDefaultACME.CA),
 | 
			
		||||
		TestCA:    oldDefaultACME.TestCA,
 | 
			
		||||
		Logger:    oldDefaultACME.Logger,
 | 
			
		||||
		HTTPProxy: oldDefaultACME.HTTPProxy,
 | 
			
		||||
 | 
			
		||||
		TrustedRoots:            certPool,
 | 
			
		||||
		Email:                   setting.AcmeEmail,
 | 
			
		||||
		Agreed:                  setting.AcmeTOS,
 | 
			
		||||
@@ -75,8 +86,10 @@ func runACME(listenAddr string, m http.Handler) error {
 | 
			
		||||
		ListenHost:              setting.HTTPAddr,
 | 
			
		||||
		AltTLSALPNPort:          altTLSALPNPort,
 | 
			
		||||
		AltHTTPPort:             altHTTPPort,
 | 
			
		||||
	})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	magic := certmagic.NewDefault()
 | 
			
		||||
	myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
 | 
			
		||||
	magic.Issuers = []certmagic.Issuer{myACME}
 | 
			
		||||
 | 
			
		||||
	// this obtains certificates or renews them if necessary
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -774,6 +774,9 @@ LEVEL = Info
 | 
			
		||||
;ALLOW_ONLY_EXTERNAL_REGISTRATION = false
 | 
			
		||||
;;
 | 
			
		||||
;; User must sign in to view anything.
 | 
			
		||||
;; After 1.23.7, it could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources,
 | 
			
		||||
;; for example: block anonymous AI crawlers from accessing repo code pages.
 | 
			
		||||
;; The "expensive" mode is experimental and subject to change.
 | 
			
		||||
;REQUIRE_SIGNIN_VIEW = false
 | 
			
		||||
;;
 | 
			
		||||
;; Mail notification
 | 
			
		||||
@@ -784,10 +787,13 @@ LEVEL = Info
 | 
			
		||||
;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
 | 
			
		||||
;ENABLE_BASIC_AUTHENTICATION = true
 | 
			
		||||
;;
 | 
			
		||||
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 login methods.
 | 
			
		||||
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 or passkey login methods if they are enabled.
 | 
			
		||||
;; If you set it to false, maybe it also needs to set ENABLE_BASIC_AUTHENTICATION to false to completely disable password-based authentication.
 | 
			
		||||
;ENABLE_PASSWORD_SIGNIN_FORM = true
 | 
			
		||||
;;
 | 
			
		||||
;; Allow users to sign-in with a passkey
 | 
			
		||||
;ENABLE_PASSKEY_AUTHENTICATION = true
 | 
			
		||||
;;
 | 
			
		||||
;; More detail: https://github.com/gogits/gogs/issues/165
 | 
			
		||||
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
 | 
			
		||||
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
 | 
			
		||||
@@ -1482,6 +1488,10 @@ LEVEL = Info
 | 
			
		||||
;REPO_INDEXER_EXCLUDE =
 | 
			
		||||
;;
 | 
			
		||||
;MAX_FILE_SIZE = 1048576
 | 
			
		||||
;;
 | 
			
		||||
;; Bleve engine has performance problems with fuzzy search, so we limit the fuzziness to 0 by default to disable it.
 | 
			
		||||
;; If you'd like to enable it, you can set it to a value between 0 and 2.
 | 
			
		||||
;TYPE_BLEVE_MAX_FUZZINESS = 0
 | 
			
		||||
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
 
 | 
			
		||||
@@ -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,6 +31,21 @@ if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then
 | 
			
		||||
  SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 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_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_key-cert.pub ]; then
 | 
			
		||||
  SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_key-cert.pub"}
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -d /etc/ssh ]; then
 | 
			
		||||
    SSH_PORT=${SSH_PORT:-"22"} \
 | 
			
		||||
    SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \
 | 
			
		||||
 
 | 
			
		||||
@@ -37,5 +37,5 @@ done
 | 
			
		||||
if [ $# -gt 0 ]; then
 | 
			
		||||
    exec "$@"
 | 
			
		||||
else
 | 
			
		||||
    exec /bin/s6-svscan /etc/s6
 | 
			
		||||
    exec /usr/bin/s6-svscan /etc/s6
 | 
			
		||||
fi
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										56
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								go.mod
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
module code.gitea.io/gitea
 | 
			
		||||
 | 
			
		||||
go 1.23
 | 
			
		||||
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:
 | 
			
		||||
@@ -24,11 +24,10 @@ require (
 | 
			
		||||
	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
 | 
			
		||||
	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
 | 
			
		||||
	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
 | 
			
		||||
	github.com/ProtonMail/go-crypto v1.0.0
 | 
			
		||||
	github.com/ProtonMail/go-crypto v1.1.6
 | 
			
		||||
	github.com/PuerkitoBio/goquery v1.10.0
 | 
			
		||||
	github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
 | 
			
		||||
	github.com/alecthomas/chroma/v2 v2.14.0
 | 
			
		||||
	github.com/aws/aws-sdk-go v1.55.5
 | 
			
		||||
	github.com/alecthomas/chroma/v2 v2.15.0
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/credentials v1.17.42
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/codecommit v1.27.3
 | 
			
		||||
	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
 | 
			
		||||
@@ -55,19 +54,18 @@ require (
 | 
			
		||||
	github.com/go-chi/cors v1.2.1
 | 
			
		||||
	github.com/go-co-op/gocron v1.37.0
 | 
			
		||||
	github.com/go-enry/go-enry/v2 v2.9.1
 | 
			
		||||
	github.com/go-git/go-billy/v5 v5.6.0
 | 
			
		||||
	github.com/go-git/go-git/v5 v5.12.0
 | 
			
		||||
	github.com/go-git/go-billy/v5 v5.6.1
 | 
			
		||||
	github.com/go-git/go-git/v5 v5.13.1
 | 
			
		||||
	github.com/go-ldap/ldap/v3 v3.4.8
 | 
			
		||||
	github.com/go-redsync/redsync/v4 v4.13.0
 | 
			
		||||
	github.com/go-sql-driver/mysql v1.8.1
 | 
			
		||||
	github.com/go-swagger/go-swagger v0.31.0
 | 
			
		||||
	github.com/go-testfixtures/testfixtures/v3 v3.11.0
 | 
			
		||||
	github.com/go-webauthn/webauthn v0.11.2
 | 
			
		||||
	github.com/gobwas/glob v0.2.3
 | 
			
		||||
	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.1
 | 
			
		||||
	github.com/google/go-github/v61 v61.0.0
 | 
			
		||||
	github.com/golang-jwt/jwt/v5 v5.2.2
 | 
			
		||||
	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
 | 
			
		||||
@@ -81,7 +79,6 @@ require (
 | 
			
		||||
	github.com/jhillyerd/enmime v1.3.0
 | 
			
		||||
	github.com/json-iterator/go v1.1.12
 | 
			
		||||
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
 | 
			
		||||
	github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
 | 
			
		||||
	github.com/klauspost/compress v1.17.11
 | 
			
		||||
	github.com/klauspost/cpuid/v2 v2.2.8
 | 
			
		||||
	github.com/lib/pq v1.10.9
 | 
			
		||||
@@ -103,13 +100,13 @@ require (
 | 
			
		||||
	github.com/pquerna/otp v1.4.0
 | 
			
		||||
	github.com/prometheus/client_golang v1.20.5
 | 
			
		||||
	github.com/quasoft/websspi v1.1.2
 | 
			
		||||
	github.com/redis/go-redis/v9 v9.7.0
 | 
			
		||||
	github.com/redis/go-redis/v9 v9.7.3
 | 
			
		||||
	github.com/robfig/cron/v3 v3.0.1
 | 
			
		||||
	github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
 | 
			
		||||
	github.com/sassoftware/go-rpmutils v0.4.0
 | 
			
		||||
	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
 | 
			
		||||
	github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
 | 
			
		||||
	github.com/stretchr/testify v1.9.0
 | 
			
		||||
	github.com/stretchr/testify v1.10.0
 | 
			
		||||
	github.com/syndtr/goleveldb v1.0.0
 | 
			
		||||
	github.com/tstranex/u2f v1.0.0
 | 
			
		||||
	github.com/ulikunitz/xz v0.5.12
 | 
			
		||||
@@ -121,14 +118,14 @@ require (
 | 
			
		||||
	github.com/yuin/goldmark v1.7.8
 | 
			
		||||
	github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
 | 
			
		||||
	github.com/yuin/goldmark-meta v1.1.0
 | 
			
		||||
	golang.org/x/crypto v0.31.0
 | 
			
		||||
	golang.org/x/crypto v0.36.0
 | 
			
		||||
	golang.org/x/image v0.21.0
 | 
			
		||||
	golang.org/x/net v0.30.0
 | 
			
		||||
	golang.org/x/oauth2 v0.23.0
 | 
			
		||||
	golang.org/x/sync v0.10.0
 | 
			
		||||
	golang.org/x/sys v0.28.0
 | 
			
		||||
	golang.org/x/text v0.21.0
 | 
			
		||||
	golang.org/x/tools v0.26.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
 | 
			
		||||
	golang.org/x/text v0.23.0
 | 
			
		||||
	golang.org/x/tools v0.29.0
 | 
			
		||||
	google.golang.org/grpc v1.67.1
 | 
			
		||||
	google.golang.org/protobuf v1.35.1
 | 
			
		||||
	gopkg.in/ini.v1 v1.67.0
 | 
			
		||||
@@ -145,8 +142,6 @@ require (
 | 
			
		||||
	filippo.io/edwards25519 v1.1.0 // indirect
 | 
			
		||||
	git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
 | 
			
		||||
	github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
 | 
			
		||||
	github.com/ClickHouse/ch-go v0.63.1 // indirect
 | 
			
		||||
	github.com/ClickHouse/clickhouse-go/v2 v2.24.0 // indirect
 | 
			
		||||
	github.com/DataDog/zstd v1.5.6 // indirect
 | 
			
		||||
	github.com/Masterminds/goutils v1.1.1 // indirect
 | 
			
		||||
	github.com/Masterminds/semver/v3 v3.3.0 // indirect
 | 
			
		||||
@@ -191,7 +186,7 @@ require (
 | 
			
		||||
	github.com/couchbase/gomemcached v0.3.2 // indirect
 | 
			
		||||
	github.com/couchbase/goutils v0.1.2 // indirect
 | 
			
		||||
	github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
 | 
			
		||||
	github.com/cyphar/filepath-securejoin v0.3.4 // indirect
 | 
			
		||||
	github.com/cyphar/filepath-securejoin v0.3.6 // indirect
 | 
			
		||||
	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 | 
			
		||||
	github.com/davidmz/go-pageant v1.0.2 // indirect
 | 
			
		||||
	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 | 
			
		||||
@@ -204,8 +199,6 @@ require (
 | 
			
		||||
	github.com/go-ap/errors v0.0.0-20240910140019-1e9d33cc1568 // indirect
 | 
			
		||||
	github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
 | 
			
		||||
	github.com/go-enry/go-oniguruma v1.2.1 // indirect
 | 
			
		||||
	github.com/go-faster/city v1.0.1 // indirect
 | 
			
		||||
	github.com/go-faster/errors v0.7.1 // indirect
 | 
			
		||||
	github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
 | 
			
		||||
	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 | 
			
		||||
	github.com/go-ini/ini v1.67.0 // indirect
 | 
			
		||||
@@ -222,11 +215,11 @@ require (
 | 
			
		||||
	github.com/go-openapi/validate v0.24.0 // indirect
 | 
			
		||||
	github.com/go-webauthn/x v0.1.15 // indirect
 | 
			
		||||
	github.com/goccy/go-json v0.10.3 // indirect
 | 
			
		||||
	github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
 | 
			
		||||
	github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
 | 
			
		||||
	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
 | 
			
		||||
	github.com/golang-sql/sqlexp v0.1.0 // indirect
 | 
			
		||||
	github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
 | 
			
		||||
	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 | 
			
		||||
	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
 | 
			
		||||
	github.com/golang/protobuf v1.5.4 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/google/btree v1.1.3 // indirect
 | 
			
		||||
@@ -261,6 +254,7 @@ require (
 | 
			
		||||
	github.com/mitchellh/copystructure v1.2.0 // indirect
 | 
			
		||||
	github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
			
		||||
	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 | 
			
		||||
	github.com/mmcloughlin/avo v0.6.0 // indirect
 | 
			
		||||
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
			
		||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
			
		||||
	github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
 | 
			
		||||
@@ -270,10 +264,9 @@ require (
 | 
			
		||||
	github.com/oklog/ulid v1.3.1 // indirect
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
			
		||||
	github.com/onsi/ginkgo v1.16.5 // indirect
 | 
			
		||||
	github.com/paulmach/orb v0.11.1 // indirect
 | 
			
		||||
	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 | 
			
		||||
	github.com/pierrec/lz4/v4 v4.1.21 // indirect
 | 
			
		||||
	github.com/pjbgf/sha1cd v0.3.0 // indirect
 | 
			
		||||
	github.com/pjbgf/sha1cd v0.3.1 // indirect
 | 
			
		||||
	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 | 
			
		||||
	github.com/prometheus/client_model v0.6.1 // indirect
 | 
			
		||||
	github.com/prometheus/common v0.60.1 // indirect
 | 
			
		||||
@@ -285,7 +278,6 @@ require (
 | 
			
		||||
	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
			
		||||
	github.com/sagikazarmark/locafero v0.6.0 // indirect
 | 
			
		||||
	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
 | 
			
		||||
	github.com/segmentio/asm v1.2.0 // indirect
 | 
			
		||||
	github.com/shopspring/decimal v1.4.0 // indirect
 | 
			
		||||
	github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
 | 
			
		||||
	github.com/sirupsen/logrus v1.9.3 // indirect
 | 
			
		||||
@@ -310,13 +302,11 @@ require (
 | 
			
		||||
	github.com/zeebo/blake3 v0.2.4 // indirect
 | 
			
		||||
	go.etcd.io/bbolt v1.3.11 // indirect
 | 
			
		||||
	go.mongodb.org/mongo-driver v1.17.1 // indirect
 | 
			
		||||
	go.opentelemetry.io/otel v1.31.0 // indirect
 | 
			
		||||
	go.opentelemetry.io/otel/trace v1.31.0 // indirect
 | 
			
		||||
	go.uber.org/atomic v1.11.0 // indirect
 | 
			
		||||
	go.uber.org/multierr v1.11.0 // indirect
 | 
			
		||||
	go.uber.org/zap v1.27.0 // indirect
 | 
			
		||||
	golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
 | 
			
		||||
	golang.org/x/mod v0.21.0 // indirect
 | 
			
		||||
	golang.org/x/mod v0.22.0 // indirect
 | 
			
		||||
	golang.org/x/time v0.7.0 // indirect
 | 
			
		||||
	google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
 | 
			
		||||
	gopkg.in/warnings.v0 v0.1.2 // indirect
 | 
			
		||||
@@ -335,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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										176
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										176
									
								
								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=
 | 
			
		||||
@@ -59,10 +59,6 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzS
 | 
			
		||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
 | 
			
		||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
github.com/ClickHouse/ch-go v0.63.1 h1:s2JyZvWLTCSAGdtjMBBmAgQQHMco6pawLJMOXi0FODM=
 | 
			
		||||
github.com/ClickHouse/ch-go v0.63.1/go.mod h1:I1kJJCL3WJcBMGe1m+HVK0+nREaG+JOYYBWjrDrF3R0=
 | 
			
		||||
github.com/ClickHouse/clickhouse-go/v2 v2.24.0 h1:L/n/pVVpk95KtkHOiKuSnO7cu2ckeW4gICbbOh5qs74=
 | 
			
		||||
github.com/ClickHouse/clickhouse-go/v2 v2.24.0/go.mod h1:iDTViXk2Fgvf1jn2dbJd1ys+fBkdD1UMRnXlwmhijhQ=
 | 
			
		||||
github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY=
 | 
			
		||||
github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
 | 
			
		||||
github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74=
 | 
			
		||||
@@ -75,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
 | 
			
		||||
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/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
 | 
			
		||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
 | 
			
		||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
 | 
			
		||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
 | 
			
		||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
 | 
			
		||||
@@ -85,11 +81,11 @@ github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv
 | 
			
		||||
github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
 | 
			
		||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 h1:BP0HiyNT3AQEYi+if3wkRcIdQFHtsw6xX3Kx0glckgA=
 | 
			
		||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3/go.mod h1:hMNtySovKkn2gdDuLqnqveP+mfhUSaBdoBcr2I7Zt0E=
 | 
			
		||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
 | 
			
		||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
 | 
			
		||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
 | 
			
		||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
 | 
			
		||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
 | 
			
		||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
 | 
			
		||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
 | 
			
		||||
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
 | 
			
		||||
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
 | 
			
		||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
 | 
			
		||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
 | 
			
		||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 | 
			
		||||
@@ -109,8 +105,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
 | 
			
		||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM=
 | 
			
		||||
@@ -194,7 +188,6 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
 | 
			
		||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
 | 
			
		||||
github.com/buildkite/terminal-to-html/v3 v3.16.3 h1:IGuJjboHjuMLWOGsKZKNxbbn41emOLiHzXPmQZk31fk=
 | 
			
		||||
github.com/buildkite/terminal-to-html/v3 v3.16.3/go.mod h1:r/J7cC9c3EzBzP3/wDz0RJLPwv5PUAMp+KF2w+ntMc0=
 | 
			
		||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 | 
			
		||||
github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0=
 | 
			
		||||
github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE=
 | 
			
		||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
 | 
			
		||||
@@ -211,7 +204,6 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA
 | 
			
		||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
 | 
			
		||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
 | 
			
		||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
 | 
			
		||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 | 
			
		||||
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
 | 
			
		||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
 | 
			
		||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
			
		||||
@@ -229,16 +221,14 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 | 
			
		||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
			
		||||
github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8=
 | 
			
		||||
github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM=
 | 
			
		||||
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
 | 
			
		||||
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
 | 
			
		||||
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=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
 | 
			
		||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 | 
			
		||||
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 h1:PdsjTl0Cg+ZJgOx/CFV5NNgO1ThTreqdgKYiDCMHJwA=
 | 
			
		||||
@@ -262,8 +252,8 @@ github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJ
 | 
			
		||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w=
 | 
			
		||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY=
 | 
			
		||||
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
 | 
			
		||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
 | 
			
		||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
 | 
			
		||||
github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ=
 | 
			
		||||
github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
 | 
			
		||||
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=
 | 
			
		||||
@@ -317,20 +307,16 @@ github.com/go-enry/go-enry/v2 v2.9.1 h1:G9iDteJ/Mc0F4Di5NeQknf83R2OkRbwY9cAYmcqV
 | 
			
		||||
github.com/go-enry/go-enry/v2 v2.9.1/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-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
 | 
			
		||||
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
 | 
			
		||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
 | 
			
		||||
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
 | 
			
		||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
 | 
			
		||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
 | 
			
		||||
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.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
 | 
			
		||||
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
 | 
			
		||||
github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA=
 | 
			
		||||
github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE=
 | 
			
		||||
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.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
 | 
			
		||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 | 
			
		||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 | 
			
		||||
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
 | 
			
		||||
@@ -372,8 +358,6 @@ github.com/go-swagger/go-swagger v0.31.0/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/
 | 
			
		||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 | 
			
		||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
 | 
			
		||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 | 
			
		||||
github.com/go-testfixtures/testfixtures/v3 v3.11.0 h1:XxQr8AnPORcZkyNd7go5UNLPD3dULN8ixYISlzrlfEQ=
 | 
			
		||||
github.com/go-testfixtures/testfixtures/v3 v3.11.0/go.mod h1:THmudHF1Ixq++J2/UodcJpxUphfyEd77m83TvDtryqE=
 | 
			
		||||
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
 | 
			
		||||
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
 | 
			
		||||
github.com/go-webauthn/x v0.1.15 h1:eG1OhggBJTkDE8gUeOlGRbRe8E/PSVG26YG4AyFbwkU=
 | 
			
		||||
@@ -385,23 +369,23 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
 | 
			
		||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
 | 
			
		||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
 | 
			
		||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 | 
			
		||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 | 
			
		||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
 | 
			
		||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
 | 
			
		||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
 | 
			
		||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
 | 
			
		||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
 | 
			
		||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 | 
			
		||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
 | 
			
		||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 | 
			
		||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
 | 
			
		||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 | 
			
		||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
 | 
			
		||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 | 
			
		||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
 | 
			
		||||
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/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
 | 
			
		||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
			
		||||
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.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 | 
			
		||||
@@ -410,7 +394,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 | 
			
		||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 | 
			
		||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 | 
			
		||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 | 
			
		||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 | 
			
		||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 | 
			
		||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
@@ -427,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=
 | 
			
		||||
@@ -497,22 +481,6 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
 | 
			
		||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 | 
			
		||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
			
		||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
 | 
			
		||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
 | 
			
		||||
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
 | 
			
		||||
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
 | 
			
		||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
 | 
			
		||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 | 
			
		||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 | 
			
		||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
 | 
			
		||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 | 
			
		||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
 | 
			
		||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
 | 
			
		||||
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
 | 
			
		||||
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 | 
			
		||||
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
 | 
			
		||||
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
 | 
			
		||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
 | 
			
		||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
 | 
			
		||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 | 
			
		||||
@@ -533,10 +501,6 @@ github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bB
 | 
			
		||||
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
 | 
			
		||||
github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw=
 | 
			
		||||
github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs=
 | 
			
		||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 | 
			
		||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 | 
			
		||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
			
		||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
			
		||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 | 
			
		||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 | 
			
		||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 | 
			
		||||
@@ -548,13 +512,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
 | 
			
		||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 | 
			
		||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
 | 
			
		||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 | 
			
		||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 h1:cTxwSmnaqLoo+4tLukHoB9iqHOu3LmLhRmgUxZo6Vp4=
 | 
			
		||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
 | 
			
		||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 | 
			
		||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
			
		||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 | 
			
		||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 | 
			
		||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 | 
			
		||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
 | 
			
		||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
 | 
			
		||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 | 
			
		||||
@@ -624,12 +583,13 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
 | 
			
		||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
			
		||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
 | 
			
		||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 | 
			
		||||
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
 | 
			
		||||
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 | 
			
		||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 | 
			
		||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
 | 
			
		||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
 | 
			
		||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
 | 
			
		||||
@@ -670,9 +630,6 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I
 | 
			
		||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
 | 
			
		||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
 | 
			
		||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
 | 
			
		||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
 | 
			
		||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
 | 
			
		||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
 | 
			
		||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 | 
			
		||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
 | 
			
		||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
 | 
			
		||||
@@ -680,8 +637,8 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG
 | 
			
		||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 | 
			
		||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
 | 
			
		||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.3.1 h1:Dh2GYdpJnO84lIw0LJwTFXjcNbasP/bklicSznyAaPI=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.3.1/go.mod h1:Y8t7jSB/dEI/lQE04A1HVKteqjj9bX5O4+Cex0TCu8s=
 | 
			
		||||
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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 | 
			
		||||
@@ -703,8 +660,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG
 | 
			
		||||
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
 | 
			
		||||
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
 | 
			
		||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 | 
			
		||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
 | 
			
		||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
 | 
			
		||||
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
 | 
			
		||||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
 | 
			
		||||
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
 | 
			
		||||
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
 | 
			
		||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
 | 
			
		||||
@@ -735,8 +692,6 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng
 | 
			
		||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
 | 
			
		||||
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
 | 
			
		||||
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
 | 
			
		||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
 | 
			
		||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
 | 
			
		||||
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
 | 
			
		||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 | 
			
		||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
 | 
			
		||||
@@ -783,21 +738,19 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 | 
			
		||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
			
		||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
			
		||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 | 
			
		||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
			
		||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
			
		||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
			
		||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 | 
			
		||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
			
		||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
 | 
			
		||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
 | 
			
		||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 | 
			
		||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 | 
			
		||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
 | 
			
		||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 | 
			
		||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 | 
			
		||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 | 
			
		||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
 | 
			
		||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
 | 
			
		||||
@@ -823,9 +776,6 @@ github.com/xanzy/go-gitlab v0.112.0 h1:6Z0cqEooCvBMfBIHw+CgO4AKGRV8na/9781xOb0+D
 | 
			
		||||
github.com/xanzy/go-gitlab v0.112.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
 | 
			
		||||
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/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 | 
			
		||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
 | 
			
		||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
 | 
			
		||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 | 
			
		||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
 | 
			
		||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 | 
			
		||||
@@ -842,9 +792,7 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
 | 
			
		||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
 | 
			
		||||
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
 | 
			
		||||
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
 | 
			
		||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 | 
			
		||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
			
		||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
			
		||||
@@ -863,13 +811,8 @@ github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l
 | 
			
		||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 | 
			
		||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
 | 
			
		||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
 | 
			
		||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
 | 
			
		||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
 | 
			
		||||
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
 | 
			
		||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
 | 
			
		||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 | 
			
		||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
 | 
			
		||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
 | 
			
		||||
@@ -886,16 +829,14 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 | 
			
		||||
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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 | 
			
		||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 | 
			
		||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 | 
			
		||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 | 
			
		||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 | 
			
		||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 | 
			
		||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 | 
			
		||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
 | 
			
		||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
 | 
			
		||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
 | 
			
		||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
 | 
			
		||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 | 
			
		||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
 | 
			
		||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
 | 
			
		||||
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
 | 
			
		||||
@@ -909,8 +850,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 | 
			
		||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 | 
			
		||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 | 
			
		||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 | 
			
		||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
 | 
			
		||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 | 
			
		||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
 | 
			
		||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
@@ -922,34 +863,31 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
 | 
			
		||||
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.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 | 
			
		||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
			
		||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
			
		||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 | 
			
		||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 | 
			
		||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 | 
			
		||||
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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
 | 
			
		||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
 | 
			
		||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
 | 
			
		||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 | 
			
		||||
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=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 | 
			
		||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 | 
			
		||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 | 
			
		||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 | 
			
		||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
 | 
			
		||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 | 
			
		||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
 | 
			
		||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
@@ -974,8 +912,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 | 
			
		||||
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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.3.0/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=
 | 
			
		||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
@@ -985,14 +921,12 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
 | 
			
		||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
 | 
			
		||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
			
		||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 | 
			
		||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 | 
			
		||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 | 
			
		||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 | 
			
		||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 | 
			
		||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 | 
			
		||||
@@ -1000,38 +934,34 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 | 
			
		||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
 | 
			
		||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 | 
			
		||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
 | 
			
		||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
 | 
			
		||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
 | 
			
		||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
 | 
			
		||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 | 
			
		||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 | 
			
		||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 | 
			
		||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 | 
			
		||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 | 
			
		||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 | 
			
		||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
 | 
			
		||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 | 
			
		||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
 | 
			
		||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
 | 
			
		||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
 | 
			
		||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
			
		||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 | 
			
		||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 | 
			
		||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 | 
			
		||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
 | 
			
		||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
 | 
			
		||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
 | 
			
		||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
@@ -1046,8 +976,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
 | 
			
		||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 | 
			
		||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
 | 
			
		||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
			
		||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 | 
			
		||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
			
		||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
 | 
			
		||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								main_timezones.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								main_timezones.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
//go:build windows
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
// Golang has the ability to load OS's timezone data from most UNIX systems (https://github.com/golang/go/blob/master/src/time/zoneinfo_unix.go)
 | 
			
		||||
// Even if the timezone data is missing, users could install the related packages to get it.
 | 
			
		||||
// But on Windows, although `zoneinfo_windows.go` tries to load the timezone data from Windows registry,
 | 
			
		||||
// some users still suffer from the issue that the timezone data is missing: https://github.com/go-gitea/gitea/issues/33235
 | 
			
		||||
// So we import the tzdata package to make sure the timezone data is included in the binary.
 | 
			
		||||
//
 | 
			
		||||
// For non-Windows package builders, they could still use the "TAGS=timetzdata" to include the tzdata package in the binary.
 | 
			
		||||
// If we decided to add the tzdata for other platforms, modify the "go:build" directive above.
 | 
			
		||||
import _ "time/tzdata"
 | 
			
		||||
@@ -114,6 +114,12 @@ type FindArtifactsOptions struct {
 | 
			
		||||
	Status       int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts FindArtifactsOptions) ToOrders() string {
 | 
			
		||||
	return "id"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ db.FindOptionsOrder = (*FindArtifactsOptions)(nil)
 | 
			
		||||
 | 
			
		||||
func (opts FindArtifactsOptions) ToConds() builder.Cond {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
	if opts.RepoID > 0 {
 | 
			
		||||
@@ -132,7 +138,7 @@ func (opts FindArtifactsOptions) ToConds() builder.Cond {
 | 
			
		||||
	return cond
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ActionArtifactMeta is the meta data of an artifact
 | 
			
		||||
// ActionArtifactMeta is the meta-data of an artifact
 | 
			
		||||
type ActionArtifactMeta struct {
 | 
			
		||||
	ArtifactName string
 | 
			
		||||
	FileSize     int64
 | 
			
		||||
 
 | 
			
		||||
@@ -194,7 +194,7 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
 | 
			
		||||
 | 
			
		||||
// CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event.
 | 
			
		||||
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
 | 
			
		||||
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
 | 
			
		||||
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) ([]*ActionRunJob, error) {
 | 
			
		||||
	// Find all runs in the specified repository, reference, and workflow with non-final status
 | 
			
		||||
	runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
 | 
			
		||||
		RepoID:       repoID,
 | 
			
		||||
@@ -204,14 +204,16 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
 | 
			
		||||
		Status:       []Status{StatusRunning, StatusWaiting, StatusBlocked},
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If there are no runs found, there's no need to proceed with cancellation, so return nil.
 | 
			
		||||
	if total == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cancelledJobs := make([]*ActionRunJob, 0, total)
 | 
			
		||||
 | 
			
		||||
	// Iterate over each found run and cancel its associated jobs.
 | 
			
		||||
	for _, run := range runs {
 | 
			
		||||
		// Find all jobs associated with the current run.
 | 
			
		||||
@@ -219,7 +221,7 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
 | 
			
		||||
			RunID: run.ID,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			return cancelledJobs, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Iterate over each job and attempt to cancel it.
 | 
			
		||||
@@ -238,27 +240,29 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
 | 
			
		||||
				// Update the job's status and stopped time in the database.
 | 
			
		||||
				n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
					return cancelledJobs, err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
 | 
			
		||||
				if n == 0 {
 | 
			
		||||
					return fmt.Errorf("job has changed, try again")
 | 
			
		||||
					return cancelledJobs, fmt.Errorf("job has changed, try again")
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				cancelledJobs = append(cancelledJobs, job)
 | 
			
		||||
				// Continue with the next job.
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// If the job has an associated task, try to stop the task, effectively cancelling the job.
 | 
			
		||||
			if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
				return cancelledJobs, err
 | 
			
		||||
			}
 | 
			
		||||
			cancelledJobs = append(cancelledJobs, job)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Return nil to indicate successful cancellation of all running and waiting jobs.
 | 
			
		||||
	return nil
 | 
			
		||||
	return cancelledJobs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InsertRun inserts a run
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ func TestAggregateJobStatus(t *testing.T) {
 | 
			
		||||
		{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
 | 
			
		||||
 | 
			
		||||
		// skipped with other status
 | 
			
		||||
		// TODO: need to clarify whether a PR with "skipped" job status is considered as "mergeable" or not.
 | 
			
		||||
		// "all skipped" is also considered as "mergeable" by "services/actions.toCommitStatus", the same as GitHub
 | 
			
		||||
		{[]Status{StatusSkipped}, StatusSkipped},
 | 
			
		||||
		{[]Status{StatusSkipped, StatusSuccess}, StatusSuccess},
 | 
			
		||||
		{[]Status{StatusSkipped, StatusFailure}, StatusFailure},
 | 
			
		||||
 
 | 
			
		||||
@@ -167,6 +167,7 @@ func init() {
 | 
			
		||||
 | 
			
		||||
type FindRunnerOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
	IDs           []int64
 | 
			
		||||
	RepoID        int64
 | 
			
		||||
	OwnerID       int64 // it will be ignored if RepoID is set
 | 
			
		||||
	Sort          string
 | 
			
		||||
@@ -178,6 +179,14 @@ type FindRunnerOptions struct {
 | 
			
		||||
func (opts FindRunnerOptions) ToConds() builder.Cond {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
 | 
			
		||||
	if len(opts.IDs) > 0 {
 | 
			
		||||
		if len(opts.IDs) == 1 {
 | 
			
		||||
			cond = cond.And(builder.Eq{"id": opts.IDs[0]})
 | 
			
		||||
		} else {
 | 
			
		||||
			cond = cond.And(builder.In("id", opts.IDs))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.RepoID > 0 {
 | 
			
		||||
		c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID})
 | 
			
		||||
		if opts.WithAvailable {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
)
 | 
			
		||||
@@ -51,7 +52,7 @@ func GetRunnerToken(ctx context.Context, token string) (*ActionRunnerToken, erro
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, fmt.Errorf("runner token %q: %w", token, util.ErrNotExist)
 | 
			
		||||
		return nil, fmt.Errorf(`runner token "%s...": %w`, base.TruncateString(token, 3), util.ErrNotExist)
 | 
			
		||||
	}
 | 
			
		||||
	return &runnerToken, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -68,19 +69,15 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRunnerToken creates a new active runner token and invalidate all old tokens
 | 
			
		||||
// NewRunnerTokenWithValue creates a new active runner token and invalidate all old tokens
 | 
			
		||||
// ownerID will be ignored and treated as 0 if repoID is non-zero.
 | 
			
		||||
func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
 | 
			
		||||
func NewRunnerTokenWithValue(ctx context.Context, ownerID, repoID int64, token string) (*ActionRunnerToken, error) {
 | 
			
		||||
	if ownerID != 0 && repoID != 0 {
 | 
			
		||||
		// It's trying to create a runner token that belongs to a repository, but OwnerID has been set accidentally.
 | 
			
		||||
		// Remove OwnerID to avoid confusion; it's not worth returning an error here.
 | 
			
		||||
		ownerID = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	token, err := util.CryptoRandomString(40)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	runnerToken := &ActionRunnerToken{
 | 
			
		||||
		OwnerID:  ownerID,
 | 
			
		||||
		RepoID:   repoID,
 | 
			
		||||
@@ -95,11 +92,19 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerTo
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_, err = db.GetEngine(ctx).Insert(runnerToken)
 | 
			
		||||
		_, err := db.GetEngine(ctx).Insert(runnerToken)
 | 
			
		||||
		return err
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
 | 
			
		||||
	token, err := util.CryptoRandomString(40)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return NewRunnerTokenWithValue(ctx, ownerID, repoID, token)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLatestRunnerToken returns the latest runner token
 | 
			
		||||
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
 | 
			
		||||
	if ownerID != 0 && repoID != 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -120,21 +120,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
 | 
			
		||||
	return committer.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
 | 
			
		||||
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([]*ActionRunJob, error) {
 | 
			
		||||
	// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
 | 
			
		||||
	// There is no other place we can do this because the app.ini will be changed manually
 | 
			
		||||
	if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
 | 
			
		||||
		return fmt.Errorf("DeleteCronTaskByRepo: %v", err)
 | 
			
		||||
		return nil, fmt.Errorf("DeleteCronTaskByRepo: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	// cancel running cron jobs of this repository and delete old schedules
 | 
			
		||||
	if err := CancelPreviousJobs(
 | 
			
		||||
	jobs, err := CancelPreviousJobs(
 | 
			
		||||
		ctx,
 | 
			
		||||
		repo.ID,
 | 
			
		||||
		repo.DefaultBranch,
 | 
			
		||||
		"",
 | 
			
		||||
		webhook_module.HookEventSchedule,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return fmt.Errorf("CancelPreviousJobs: %v", err)
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return jobs, fmt.Errorf("CancelPreviousJobs: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return jobs, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@ func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data strin
 | 
			
		||||
 | 
			
		||||
type FindVariablesOpts struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
	IDs     []int64
 | 
			
		||||
	RepoID  int64
 | 
			
		||||
	OwnerID int64 // it will be ignored if RepoID is set
 | 
			
		||||
	Name    string
 | 
			
		||||
@@ -65,6 +66,15 @@ type FindVariablesOpts struct {
 | 
			
		||||
 | 
			
		||||
func (opts FindVariablesOpts) ToConds() builder.Cond {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
 | 
			
		||||
	if len(opts.IDs) > 0 {
 | 
			
		||||
		if len(opts.IDs) == 1 {
 | 
			
		||||
			cond = cond.And(builder.Eq{"id": opts.IDs[0]})
 | 
			
		||||
		} else {
 | 
			
		||||
			cond = cond.And(builder.In("id", opts.IDs))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Since we now support instance-level variables,
 | 
			
		||||
	// there is no need to check for null values for `owner_id` and `repo_id`
 | 
			
		||||
	cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
 | 
			
		||||
@@ -85,12 +95,12 @@ func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariab
 | 
			
		||||
	return db.Find[ActionVariable](ctx, opts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
 | 
			
		||||
	count, err := db.GetEngine(ctx).ID(variable.ID).Cols("name", "data").
 | 
			
		||||
		Update(&ActionVariable{
 | 
			
		||||
			Name: variable.Name,
 | 
			
		||||
			Data: variable.Data,
 | 
			
		||||
		})
 | 
			
		||||
func UpdateVariableCols(ctx context.Context, variable *ActionVariable, cols ...string) (bool, error) {
 | 
			
		||||
	variable.Name = strings.ToUpper(variable.Name)
 | 
			
		||||
	count, err := db.GetEngine(ctx).
 | 
			
		||||
		ID(variable.ID).
 | 
			
		||||
		Cols(cols...).
 | 
			
		||||
		Update(variable)
 | 
			
		||||
	return count != 0, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -72,9 +72,9 @@ func (at ActionType) String() string {
 | 
			
		||||
	case ActionRenameRepo:
 | 
			
		||||
		return "rename_repo"
 | 
			
		||||
	case ActionStarRepo:
 | 
			
		||||
		return "star_repo"
 | 
			
		||||
		return "star_repo" // will not displayed in feeds.tmpl
 | 
			
		||||
	case ActionWatchRepo:
 | 
			
		||||
		return "watch_repo"
 | 
			
		||||
		return "watch_repo" // will not displayed in feeds.tmpl
 | 
			
		||||
	case ActionCommitRepo:
 | 
			
		||||
		return "commit_repo"
 | 
			
		||||
	case ActionCreateIssue:
 | 
			
		||||
@@ -454,6 +454,24 @@ func ActivityReadable(user, doer *user_model.User) bool {
 | 
			
		||||
		doer != nil && (doer.IsAdmin || user.ID == doer.ID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FeedDateCond(opts GetFeedsOptions) builder.Cond {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
	if opts.Date == "" {
 | 
			
		||||
		return cond
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
 | 
			
		||||
	} else {
 | 
			
		||||
		dateHigh := dateLow.Add(86399000000000) // 23h59m59s
 | 
			
		||||
 | 
			
		||||
		cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
 | 
			
		||||
		cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
 | 
			
		||||
	}
 | 
			
		||||
	return cond
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
 | 
			
		||||
@@ -534,17 +552,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
 | 
			
		||||
		cond = cond.And(builder.Eq{"is_deleted": false})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.Date != "" {
 | 
			
		||||
		dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
 | 
			
		||||
		} else {
 | 
			
		||||
			dateHigh := dateLow.Add(86399000000000) // 23h59m59s
 | 
			
		||||
 | 
			
		||||
			cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
 | 
			
		||||
			cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	cond = cond.And(FeedDateCond(opts))
 | 
			
		||||
 | 
			
		||||
	return cond, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -208,9 +208,31 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
 | 
			
		||||
		return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cond, err := ActivityQueryCondition(ctx, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
	var err error
 | 
			
		||||
	var cond builder.Cond
 | 
			
		||||
	// if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition
 | 
			
		||||
	if opts.Actor != nil && opts.RequestedUser != nil && (opts.Actor.IsAdmin || opts.Actor.ID == opts.RequestedUser.ID) {
 | 
			
		||||
		cond = builder.Eq{
 | 
			
		||||
			"user_id": opts.RequestedUser.ID,
 | 
			
		||||
		}.And(
 | 
			
		||||
			FeedDateCond(opts),
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		if !opts.IncludeDeleted {
 | 
			
		||||
			cond = cond.And(builder.Eq{"is_deleted": false})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !opts.IncludePrivate {
 | 
			
		||||
			cond = cond.And(builder.Eq{"is_private": false})
 | 
			
		||||
		}
 | 
			
		||||
		if opts.OnlyPerformedBy {
 | 
			
		||||
			cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		cond, err = ActivityQueryCondition(ctx, opts)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actions := make([]*Action, 0, opts.PageSize)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -337,8 +338,10 @@ func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *
 | 
			
		||||
func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
 | 
			
		||||
	sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
 | 
			
		||||
		And("issue.is_pull = ?", false).
 | 
			
		||||
		And("issue.created_unix >= ?", fromTime.Unix()).
 | 
			
		||||
		Or("issue.closed_unix >= ?", fromTime.Unix())
 | 
			
		||||
		And(builder.Or(
 | 
			
		||||
			builder.Gte{"issue.created_unix": fromTime.Unix()},
 | 
			
		||||
			builder.Gte{"issue.closed_unix": fromTime.Unix()},
 | 
			
		||||
		))
 | 
			
		||||
 | 
			
		||||
	return sess
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ func init() {
 | 
			
		||||
// TranslatableMessage represents JSON struct that can be translated with a Locale
 | 
			
		||||
type TranslatableMessage struct {
 | 
			
		||||
	Format string
 | 
			
		||||
	Args   []any `json:"omitempty"`
 | 
			
		||||
	Args   []any `json:",omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadRepo loads repository of the task
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,8 @@ import (
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp"
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp/packet"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -141,7 +141,11 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified
 | 
			
		||||
	// Parse Subkeys
 | 
			
		||||
	subkeys := make([]*GPGKey, len(e.Subkeys))
 | 
			
		||||
	for i, k := range e.Subkeys {
 | 
			
		||||
		subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, expiry)
 | 
			
		||||
		subkeyExpiry := expiry
 | 
			
		||||
		if k.Sig.KeyLifetimeSecs != nil {
 | 
			
		||||
			subkeyExpiry = k.PublicKey.CreationTime.Add(time.Duration(*k.Sig.KeyLifetimeSecs) * time.Second)
 | 
			
		||||
		}
 | 
			
		||||
		subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, subkeyExpiry)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, ErrGPGKeyParsing{ParseError: err}
 | 
			
		||||
		}
 | 
			
		||||
@@ -156,7 +160,7 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified
 | 
			
		||||
 | 
			
		||||
	emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
 | 
			
		||||
	for _, ident := range e.Identities {
 | 
			
		||||
		if ident.Revocation != nil {
 | 
			
		||||
		if ident.Revoked(time.Now()) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//   __________________  ________   ____  __.
 | 
			
		||||
@@ -83,12 +83,12 @@ func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature str
 | 
			
		||||
	verified := false
 | 
			
		||||
	// Handle provided signature
 | 
			
		||||
	if signature != "" {
 | 
			
		||||
		signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature))
 | 
			
		||||
		signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature), nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature))
 | 
			
		||||
			signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature), nil)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature))
 | 
			
		||||
			signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("Unable to validate token signature. Error: %v", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp/packet"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//   __________________  ________   ____  __.
 | 
			
		||||
 
 | 
			
		||||
@@ -13,9 +13,9 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp"
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp/armor"
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp/packet"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp/armor"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//   __________________  ________   ____  __.
 | 
			
		||||
@@ -80,7 +80,7 @@ func base64DecPubKey(content string) (*packet.PublicKey, error) {
 | 
			
		||||
	return pkey, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getExpiryTime extract the expire time of primary key based on sig
 | 
			
		||||
// getExpiryTime extract the expiry time of primary key based on sig
 | 
			
		||||
func getExpiryTime(e *openpgp.Entity) time.Time {
 | 
			
		||||
	expiry := time.Time{}
 | 
			
		||||
	// Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
 | 
			
		||||
@@ -88,12 +88,12 @@ func getExpiryTime(e *openpgp.Entity) time.Time {
 | 
			
		||||
	for _, ident := range e.Identities {
 | 
			
		||||
		if selfSig == nil {
 | 
			
		||||
			selfSig = ident.SelfSignature
 | 
			
		||||
		} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
 | 
			
		||||
		} else if ident.SelfSignature != nil && ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
 | 
			
		||||
			selfSig = ident.SelfSignature
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if selfSig.KeyLifetimeSecs != nil {
 | 
			
		||||
	if selfSig != nil && selfSig.KeyLifetimeSecs != nil {
 | 
			
		||||
		expiry = e.PrimaryKey.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
 | 
			
		||||
	}
 | 
			
		||||
	return expiry
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,8 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-crypto/openpgp/packet"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp"
 | 
			
		||||
	"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -403,3 +404,25 @@ func TestTryGetKeyIDFromSignature(t *testing.T) {
 | 
			
		||||
		IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseGPGKey(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	assert.NoError(t, db.Insert(db.DefaultContext, &user_model.EmailAddress{UID: 1, Email: "email1@example.com", IsActivated: true}))
 | 
			
		||||
 | 
			
		||||
	// create a key for test email
 | 
			
		||||
	e, err := openpgp.NewEntity("name", "comment", "email1@example.com", nil)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	k, err := parseGPGKey(db.DefaultContext, 1, e, true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NotEmpty(t, k.KeyID)
 | 
			
		||||
	assert.NotEmpty(t, k.Emails) // the key is valid, matches the email
 | 
			
		||||
 | 
			
		||||
	// then revoke the key
 | 
			
		||||
	for _, id := range e.Identities {
 | 
			
		||||
		id.Revocations = append(id.Revocations, &packet.Signature{RevocationReason: util.ToPointer(packet.KeyCompromised)})
 | 
			
		||||
	}
 | 
			
		||||
	k, err = parseGPGKey(db.DefaultContext, 1, e, true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NotEmpty(t, k.KeyID)
 | 
			
		||||
	assert.Empty(t, k.Emails) // the key is revoked, matches no email
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -283,6 +283,10 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
 | 
			
		||||
	return bitmap.toScope(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s AccessTokenScope) HasPermissionScope() bool {
 | 
			
		||||
	return s != "" && s != AccessTokenScopePublicOnly
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PublicOnly checks if this token scope is limited to public resources
 | 
			
		||||
func (s AccessTokenScope) PublicOnly() (bool, error) {
 | 
			
		||||
	bitmap, err := s.parse()
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@
 | 
			
		||||
  name: job2
 | 
			
		||||
  attempt: 1
 | 
			
		||||
  job_id: job2
 | 
			
		||||
  needs: [job1]
 | 
			
		||||
  needs: '["job1"]'
 | 
			
		||||
  task_id: 51
 | 
			
		||||
  status: 5
 | 
			
		||||
  started: 1683636528
 | 
			
		||||
 
 | 
			
		||||
@@ -96,3 +96,14 @@
 | 
			
		||||
  num_issues: 0
 | 
			
		||||
  num_closed_issues: 0
 | 
			
		||||
  archived_unix: 0
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 10
 | 
			
		||||
  repo_id: 3
 | 
			
		||||
  org_id: 0
 | 
			
		||||
  name: repo3label1
 | 
			
		||||
  color: '#112233'
 | 
			
		||||
  exclusive: false
 | 
			
		||||
  num_issues: 0
 | 
			
		||||
  num_closed_issues: 0
 | 
			
		||||
  archived_unix: 0
 | 
			
		||||
 
 | 
			
		||||
@@ -2,23 +2,23 @@
 | 
			
		||||
  id: 1
 | 
			
		||||
  repo_id: 4
 | 
			
		||||
  name_pattern: /v.+/
 | 
			
		||||
  allowlist_user_i_ds: []
 | 
			
		||||
  allowlist_team_i_ds: []
 | 
			
		||||
  allowlist_user_i_ds: "[]"
 | 
			
		||||
  allowlist_team_i_ds: "[]"
 | 
			
		||||
  created_unix: 1715596037
 | 
			
		||||
  updated_unix: 1715596037
 | 
			
		||||
-
 | 
			
		||||
  id: 2
 | 
			
		||||
  repo_id: 1
 | 
			
		||||
  name_pattern: v-*
 | 
			
		||||
  allowlist_user_i_ds: []
 | 
			
		||||
  allowlist_team_i_ds: []
 | 
			
		||||
  allowlist_user_i_ds: "[]"
 | 
			
		||||
  allowlist_team_i_ds: "[]"
 | 
			
		||||
  created_unix: 1715596037
 | 
			
		||||
  updated_unix: 1715596037
 | 
			
		||||
-
 | 
			
		||||
  id: 3
 | 
			
		||||
  repo_id: 1
 | 
			
		||||
  name_pattern: v-1.1
 | 
			
		||||
  allowlist_user_i_ds: [2]
 | 
			
		||||
  allowlist_team_i_ds: []
 | 
			
		||||
  allowlist_user_i_ds: "[2]"
 | 
			
		||||
  allowlist_team_i_ds: "[]"
 | 
			
		||||
  created_unix: 1715596037
 | 
			
		||||
  updated_unix: 1715596037
 | 
			
		||||
 
 | 
			
		||||
@@ -167,9 +167,24 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
 | 
			
		||||
			BranchName: branchName,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// FIXME: this design is not right: it doesn't check `branch.IsDeleted`, it doesn't make sense to make callers to check IsDeleted again and again.
 | 
			
		||||
	// It causes inconsistency with `GetBranches` and `git.GetBranch`, and will lead to strange bugs
 | 
			
		||||
	// In the future, there should be 2 functions: `GetBranchExisting` and `GetBranchWithDeleted`
 | 
			
		||||
	return &branch, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsBranchExist returns true if the branch exists in the repository.
 | 
			
		||||
func IsBranchExist(ctx context.Context, repoID int64, branchName string) (bool, error) {
 | 
			
		||||
	var branch Branch
 | 
			
		||||
	has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	return !branch.IsDeleted, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) {
 | 
			
		||||
	branches := make([]*Branch, 0, len(branchNames))
 | 
			
		||||
 | 
			
		||||
@@ -440,6 +455,8 @@ type FindRecentlyPushedNewBranchesOptions struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RecentlyPushedNewBranch struct {
 | 
			
		||||
	BranchRepo        *repo_model.Repository
 | 
			
		||||
	BranchName        string
 | 
			
		||||
	BranchDisplayName string
 | 
			
		||||
	BranchLink        string
 | 
			
		||||
	BranchCompareURL  string
 | 
			
		||||
@@ -540,7 +557,9 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
 | 
			
		||||
				branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
 | 
			
		||||
			}
 | 
			
		||||
			newBranches = append(newBranches, &RecentlyPushedNewBranch{
 | 
			
		||||
				BranchRepo:        branch.Repo,
 | 
			
		||||
				BranchDisplayName: branchDisplayName,
 | 
			
		||||
				BranchName:        branch.Name,
 | 
			
		||||
				BranchLink:        fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
 | 
			
		||||
				BranchCompareURL:  branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
 | 
			
		||||
				CommitTime:        branch.CommitTime,
 | 
			
		||||
 
 | 
			
		||||
@@ -197,6 +197,20 @@ func (t CommentType) HasMailReplySupport() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t CommentType) CountedAsConversation() bool {
 | 
			
		||||
	for _, ct := range ConversationCountedCommentType() {
 | 
			
		||||
		if t == ct {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConversationCountedCommentType returns the comment types that are counted as a conversation
 | 
			
		||||
func ConversationCountedCommentType() []CommentType {
 | 
			
		||||
	return []CommentType{CommentTypeComment, CommentTypeReview}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RoleInRepo presents the user's participation in the repo
 | 
			
		||||
type RoleInRepo string
 | 
			
		||||
 | 
			
		||||
@@ -893,7 +907,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case CommentTypeComment:
 | 
			
		||||
		if _, err = db.Exec(ctx, "UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil {
 | 
			
		||||
		if err := UpdateIssueNumComments(ctx, opts.Issue.ID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
@@ -1182,8 +1196,8 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if comment.Type == CommentTypeComment {
 | 
			
		||||
		if _, err := e.ID(comment.IssueID).Decr("num_comments").Update(new(Issue)); err != nil {
 | 
			
		||||
	if comment.Type.CountedAsConversation() {
 | 
			
		||||
		if err := UpdateIssueNumComments(ctx, comment.IssueID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -1300,6 +1314,21 @@ func (c *Comment) HasOriginalAuthor() bool {
 | 
			
		||||
	return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UpdateIssueNumCommentsBuilder(issueID int64) *builder.Builder {
 | 
			
		||||
	subQuery := builder.Select("COUNT(*)").From("`comment`").Where(
 | 
			
		||||
		builder.Eq{"issue_id": issueID}.And(
 | 
			
		||||
			builder.In("`type`", ConversationCountedCommentType()),
 | 
			
		||||
		))
 | 
			
		||||
 | 
			
		||||
	return builder.Update(builder.Eq{"num_comments": subQuery}).
 | 
			
		||||
		From("`issue`").Where(builder.Eq{"id": issueID})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UpdateIssueNumComments(ctx context.Context, issueID int64) error {
 | 
			
		||||
	_, err := db.GetEngine(ctx).Exec(UpdateIssueNumCommentsBuilder(issueID))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InsertIssueComments inserts many comments of issues.
 | 
			
		||||
func InsertIssueComments(ctx context.Context, comments []*Comment) error {
 | 
			
		||||
	if len(comments) == 0 {
 | 
			
		||||
@@ -1332,8 +1361,7 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, issueID := range issueIDs {
 | 
			
		||||
		if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?",
 | 
			
		||||
			issueID, CommentTypeComment, issueID); err != nil {
 | 
			
		||||
		if err := UpdateIssueNumComments(ctx, issueID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -86,8 +86,10 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
 | 
			
		||||
			ids = append(ids, comment.ReviewID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := e.In("id", ids).Find(&reviews); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	if len(ids) > 0 {
 | 
			
		||||
		if err := e.In("id", ids).Find(&reviews); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	n := 0
 | 
			
		||||
 
 | 
			
		||||
@@ -97,3 +97,12 @@ func TestMigrate_InsertIssueComments(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	unittest.CheckConsistencyFor(t, &issues_model.Issue{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_UpdateIssueNumComments(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, issues_model.UpdateIssueNumComments(db.DefaultContext, issue2.ID))
 | 
			
		||||
	issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
 | 
			
		||||
	assert.EqualValues(t, 1, issue2.NumComments)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ import (
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/container"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/optional"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
@@ -531,6 +532,45 @@ func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
 | 
			
		||||
	return issue, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isPullToCond(isPull optional.Option[bool]) builder.Cond {
 | 
			
		||||
	if isPull.Has() {
 | 
			
		||||
		return builder.Eq{"is_pull": isPull.Value()}
 | 
			
		||||
	}
 | 
			
		||||
	return builder.NewCond()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FindLatestUpdatedIssues(ctx context.Context, repoID int64, isPull optional.Option[bool], pageSize int) (IssueList, error) {
 | 
			
		||||
	issues := make([]*Issue, 0, pageSize)
 | 
			
		||||
	err := db.GetEngine(ctx).Where("repo_id = ?", repoID).
 | 
			
		||||
		And(isPullToCond(isPull)).
 | 
			
		||||
		OrderBy("updated_unix DESC").
 | 
			
		||||
		Limit(pageSize).
 | 
			
		||||
		Find(&issues)
 | 
			
		||||
	return issues, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], excludedID int64, pageSize int) (IssueList, error) {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
	if excludedID > 0 {
 | 
			
		||||
		cond = cond.And(builder.Neq{"`id`": excludedID})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// It seems that GitHub searches both title and content (maybe sorting by the search engine's ranking system?)
 | 
			
		||||
	// The first PR (https://github.com/go-gitea/gitea/pull/32327) uses "search indexer" to search "name(title) +  content"
 | 
			
		||||
	// But it seems that searching "content" (especially LIKE by DB engine) generates worse (unusable) results.
 | 
			
		||||
	// So now (https://github.com/go-gitea/gitea/pull/33538) it only searches "name(title)", leave the improvements to the future.
 | 
			
		||||
	cond = cond.And(db.BuildCaseInsensitiveLike("`name`", keyword))
 | 
			
		||||
 | 
			
		||||
	issues := make([]*Issue, 0, pageSize)
 | 
			
		||||
	err := db.GetEngine(ctx).Where("repo_id = ?", repoID).
 | 
			
		||||
		And(isPullToCond(isPull)).
 | 
			
		||||
		And(cond).
 | 
			
		||||
		OrderBy("updated_unix DESC, `index` DESC").
 | 
			
		||||
		Limit(pageSize).
 | 
			
		||||
		Find(&issues)
 | 
			
		||||
	return issues, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetIssueWithAttrsByIndex returns issue by index in a repository.
 | 
			
		||||
func GetIssueWithAttrsByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
 | 
			
		||||
	issue, err := GetIssueByIndex(ctx, repoID, index)
 | 
			
		||||
 
 | 
			
		||||
@@ -38,13 +38,30 @@ func (issue *Issue) projectID(ctx context.Context) int64 {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ProjectColumnID return project column id if issue was assigned to one
 | 
			
		||||
func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
 | 
			
		||||
func (issue *Issue) ProjectColumnID(ctx context.Context) (int64, error) {
 | 
			
		||||
	var ip project_model.ProjectIssue
 | 
			
		||||
	has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
 | 
			
		||||
	if err != nil || !has {
 | 
			
		||||
		return 0
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
	return ip.ProjectColumnID
 | 
			
		||||
	return ip.ProjectColumnID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoadProjectIssueColumnMap(ctx context.Context, projectID, defaultColumnID int64) (map[int64]int64, error) {
 | 
			
		||||
	issues := make([]project_model.ProjectIssue, 0)
 | 
			
		||||
	if err := db.GetEngine(ctx).Where("project_id=?", projectID).Find(&issues); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	result := make(map[int64]int64, len(issues))
 | 
			
		||||
	for _, issue := range issues {
 | 
			
		||||
		if issue.ProjectColumnID == 0 {
 | 
			
		||||
			issue.ProjectColumnID = defaultColumnID
 | 
			
		||||
		}
 | 
			
		||||
		result[issue.IssueID] = issue.ProjectColumnID
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadIssuesFromColumn load issues assigned to this column
 | 
			
		||||
@@ -59,11 +76,11 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *Is
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if b.Default {
 | 
			
		||||
		issues, err := Issues(ctx, &IssuesOptions{
 | 
			
		||||
			ProjectColumnID: db.NoConditionID,
 | 
			
		||||
			ProjectID:       b.ProjectID,
 | 
			
		||||
			SortType:        "project-column-sorting",
 | 
			
		||||
		})
 | 
			
		||||
		issues, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) {
 | 
			
		||||
			o.ProjectColumnID = db.NoConditionID
 | 
			
		||||
			o.ProjectID = b.ProjectID
 | 
			
		||||
			o.SortType = "project-column-sorting"
 | 
			
		||||
		}))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -77,19 +94,6 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *Is
 | 
			
		||||
	return issueList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadIssuesFromColumnList load issues assigned to the columns
 | 
			
		||||
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList, opts *IssuesOptions) (map[int64]IssueList, error) {
 | 
			
		||||
	issuesMap := make(map[int64]IssueList, len(bs))
 | 
			
		||||
	for i := range bs {
 | 
			
		||||
		il, err := LoadIssuesFromColumn(ctx, bs[i], opts)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		issuesMap[bs[i].ID] = il
 | 
			
		||||
	}
 | 
			
		||||
	return issuesMap, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IssueAssignOrRemoveProject changes the project associated with an issue
 | 
			
		||||
// If newProjectID is 0, the issue is removed from the project
 | 
			
		||||
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
 | 
			
		||||
@@ -110,7 +114,7 @@ func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_mo
 | 
			
		||||
				return util.NewPermissionDeniedErrorf("issue %d can't be accessed by project %d", issue.ID, newProject.ID)
 | 
			
		||||
			}
 | 
			
		||||
			if newColumnID == 0 {
 | 
			
		||||
				newDefaultColumn, err := newProject.GetDefaultColumn(ctx)
 | 
			
		||||
				newDefaultColumn, err := newProject.MustDefaultColumn(ctx)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -49,9 +49,9 @@ type IssuesOptions struct { //nolint
 | 
			
		||||
	// prioritize issues from this repo
 | 
			
		||||
	PriorityRepoID int64
 | 
			
		||||
	IsArchived     optional.Option[bool]
 | 
			
		||||
	Org            *organization.Organization // issues permission scope
 | 
			
		||||
	Team           *organization.Team         // issues permission scope
 | 
			
		||||
	User           *user_model.User           // issues permission scope
 | 
			
		||||
	Owner          *user_model.User   // issues permission scope, it could be an organization or a user
 | 
			
		||||
	Team           *organization.Team // issues permission scope
 | 
			
		||||
	Doer           *user_model.User   // issues permission scope
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Copy returns a copy of the options.
 | 
			
		||||
@@ -273,8 +273,12 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) {
 | 
			
		||||
 | 
			
		||||
	applyLabelsCondition(sess, opts)
 | 
			
		||||
 | 
			
		||||
	if opts.User != nil {
 | 
			
		||||
		sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()))
 | 
			
		||||
	if opts.Owner != nil {
 | 
			
		||||
		sess.And(repo_model.UserOwnedRepoCond(opts.Owner.ID))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.Doer != nil && !opts.Doer.IsAdmin {
 | 
			
		||||
		sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.Doer.ID, opts.Owner, opts.Team, opts.IsPull.Value()))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -321,20 +325,20 @@ func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Typ
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// issuePullAccessibleRepoCond userID must not be zero, this condition require join repository table
 | 
			
		||||
func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organization.Organization, team *organization.Team, isPull bool) builder.Cond {
 | 
			
		||||
func issuePullAccessibleRepoCond(repoIDstr string, userID int64, owner *user_model.User, team *organization.Team, isPull bool) builder.Cond {
 | 
			
		||||
	cond := builder.NewCond()
 | 
			
		||||
	unitType := unit.TypeIssues
 | 
			
		||||
	if isPull {
 | 
			
		||||
		unitType = unit.TypePullRequests
 | 
			
		||||
	}
 | 
			
		||||
	if org != nil {
 | 
			
		||||
	if owner != nil && owner.IsOrganization() {
 | 
			
		||||
		if team != nil {
 | 
			
		||||
			cond = cond.And(teamUnitsRepoCond(repoIDstr, userID, org.ID, team.ID, unitType)) // special team member repos
 | 
			
		||||
			cond = cond.And(teamUnitsRepoCond(repoIDstr, userID, owner.ID, team.ID, unitType)) // special team member repos
 | 
			
		||||
		} else {
 | 
			
		||||
			cond = cond.And(
 | 
			
		||||
				builder.Or(
 | 
			
		||||
					repo_model.UserOrgUnitRepoCond(repoIDstr, userID, org.ID, unitType), // team member repos
 | 
			
		||||
					repo_model.UserOrgPublicUnitRepoCond(userID, org.ID),                // user org public non-member repos, TODO: check repo has issues
 | 
			
		||||
					repo_model.UserOrgUnitRepoCond(repoIDstr, userID, owner.ID, unitType), // team member repos
 | 
			
		||||
					repo_model.UserOrgPublicUnitRepoCond(userID, owner.ID),                // user org public non-member repos, TODO: check repo has issues
 | 
			
		||||
				),
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -349,6 +349,17 @@ func GetLabelIDsInRepoByNames(ctx context.Context, repoID int64, labelNames []st
 | 
			
		||||
		Find(&labelIDs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given org.
 | 
			
		||||
func GetLabelIDsInOrgByNames(ctx context.Context, orgID int64, labelNames []string) ([]int64, error) {
 | 
			
		||||
	labelIDs := make([]int64, 0, len(labelNames))
 | 
			
		||||
	return labelIDs, db.GetEngine(ctx).Table("label").
 | 
			
		||||
		Where("org_id = ?", orgID).
 | 
			
		||||
		In("name", labelNames).
 | 
			
		||||
		Asc("name").
 | 
			
		||||
		Cols("id").
 | 
			
		||||
		Find(&labelIDs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BuildLabelNamesIssueIDsCondition returns a builder where get issue ids match label names
 | 
			
		||||
func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder {
 | 
			
		||||
	return builder.Select("issue_label.issue_id").
 | 
			
		||||
 
 | 
			
		||||
@@ -301,7 +301,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
 | 
			
		||||
	reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -320,7 +320,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
 | 
			
		||||
 | 
			
		||||
// LoadRequestedReviewersTeams loads the requested reviewers teams.
 | 
			
		||||
func (pr *PullRequest) LoadRequestedReviewersTeams(ctx context.Context) error {
 | 
			
		||||
	reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
 | 
			
		||||
	reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -639,6 +639,10 @@ func InsertReviews(ctx context.Context, reviews []*Review) error {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := UpdateIssueNumComments(ctx, review.IssueID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return committer.Commit()
 | 
			
		||||
@@ -659,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.
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@ package issues
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"sort"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	organization_model "code.gitea.io/gitea/models/organization"
 | 
			
		||||
@@ -153,43 +155,60 @@ func CountReviews(ctx context.Context, opts FindReviewOptions) (int64, error) {
 | 
			
		||||
	return db.GetEngine(ctx).Where(opts.toCond()).Count(&Review{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetReviewersFromOriginalAuthorsByIssueID gets the latest review of each original authors for a pull request
 | 
			
		||||
func GetReviewersFromOriginalAuthorsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) {
 | 
			
		||||
	reviews := make([]*Review, 0, 10)
 | 
			
		||||
 | 
			
		||||
	// Get latest review of each reviewer, sorted in order they were made
 | 
			
		||||
	if err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC",
 | 
			
		||||
		issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
 | 
			
		||||
		Find(&reviews); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return reviews, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetReviewsByIssueID gets the latest review of each reviewer for a pull request
 | 
			
		||||
func GetReviewsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) {
 | 
			
		||||
// The first returned parameter is the latest review of each individual reviewer or team
 | 
			
		||||
// The second returned parameter is the latest review of each original author which is migrated from other systems
 | 
			
		||||
// The reviews are sorted by updated time
 | 
			
		||||
func GetReviewsByIssueID(ctx context.Context, issueID int64) (latestReviews, migratedOriginalReviews ReviewList, err error) {
 | 
			
		||||
	reviews := make([]*Review, 0, 10)
 | 
			
		||||
 | 
			
		||||
	sess := db.GetEngine(ctx)
 | 
			
		||||
 | 
			
		||||
	// Get latest review of each reviewer, sorted in order they were made
 | 
			
		||||
	if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND dismissed = ? AND original_author_id = 0 GROUP BY issue_id, reviewer_id) ORDER BY review.updated_unix ASC",
 | 
			
		||||
		issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, false).
 | 
			
		||||
		Find(&reviews); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	// Get all reviews for the issue id
 | 
			
		||||
	if err := db.GetEngine(ctx).Where("issue_id=?", issueID).OrderBy("updated_unix ASC").Find(&reviews); err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// filter them in memory to get the latest review of each reviewer
 | 
			
		||||
	// Since the reviews should not be too many for one issue, less than 100 commonly, it's acceptable to do this in memory
 | 
			
		||||
	// And since there are too less indexes in review table, it will be very slow to filter in the database
 | 
			
		||||
	reviewersMap := make(map[int64][]*Review)         // key is reviewer id
 | 
			
		||||
	originalReviewersMap := make(map[int64][]*Review) // key is original author id
 | 
			
		||||
	reviewTeamsMap := make(map[int64][]*Review)       // key is reviewer team id
 | 
			
		||||
	countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest}
 | 
			
		||||
	for _, review := range reviews {
 | 
			
		||||
		if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed {
 | 
			
		||||
			if review.OriginalAuthorID != 0 {
 | 
			
		||||
				originalReviewersMap[review.OriginalAuthorID] = append(originalReviewersMap[review.OriginalAuthorID], review)
 | 
			
		||||
			} else {
 | 
			
		||||
				reviewersMap[review.ReviewerID] = append(reviewersMap[review.ReviewerID], review)
 | 
			
		||||
			}
 | 
			
		||||
		} else if review.ReviewerTeamID != 0 && review.OriginalAuthorID == 0 {
 | 
			
		||||
			reviewTeamsMap[review.ReviewerTeamID] = append(reviewTeamsMap[review.ReviewerTeamID], review)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	individualReviews := make([]*Review, 0, 10)
 | 
			
		||||
	for _, reviews := range reviewersMap {
 | 
			
		||||
		individualReviews = append(individualReviews, reviews[len(reviews)-1])
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(individualReviews, func(i, j int) bool {
 | 
			
		||||
		return individualReviews[i].UpdatedUnix < individualReviews[j].UpdatedUnix
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	originalReviews := make([]*Review, 0, 10)
 | 
			
		||||
	for _, reviews := range originalReviewersMap {
 | 
			
		||||
		originalReviews = append(originalReviews, reviews[len(reviews)-1])
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(originalReviews, func(i, j int) bool {
 | 
			
		||||
		return originalReviews[i].UpdatedUnix < originalReviews[j].UpdatedUnix
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	teamReviewRequests := make([]*Review, 0, 5)
 | 
			
		||||
	if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id <> 0 AND original_author_id = 0 GROUP BY issue_id, reviewer_team_id) ORDER BY review.updated_unix ASC",
 | 
			
		||||
		issueID).
 | 
			
		||||
		Find(&teamReviewRequests); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	for _, reviews := range reviewTeamsMap {
 | 
			
		||||
		teamReviewRequests = append(teamReviewRequests, reviews[len(reviews)-1])
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(teamReviewRequests, func(i, j int) bool {
 | 
			
		||||
		return teamReviewRequests[i].UpdatedUnix < teamReviewRequests[j].UpdatedUnix
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if len(teamReviewRequests) > 0 {
 | 
			
		||||
		reviews = append(reviews, teamReviewRequests...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return reviews, nil
 | 
			
		||||
	return append(individualReviews, teamReviewRequests...), originalReviews, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -162,8 +162,9 @@ func TestGetReviewersByIssueID(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	allReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID)
 | 
			
		||||
	allReviews, migratedReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Empty(t, migratedReviews)
 | 
			
		||||
	for _, review := range allReviews {
 | 
			
		||||
		assert.NoError(t, review.LoadReviewer(db.DefaultContext))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ func (s Stopwatch) Seconds() int64 {
 | 
			
		||||
 | 
			
		||||
// Duration returns a human-readable duration string based on local server time
 | 
			
		||||
func (s Stopwatch) Duration() string {
 | 
			
		||||
	return util.SecToTime(s.Seconds())
 | 
			
		||||
	return util.SecToHours(s.Seconds())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
 | 
			
		||||
@@ -201,7 +201,7 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
 | 
			
		||||
		Doer:    user,
 | 
			
		||||
		Issue:   issue,
 | 
			
		||||
		Repo:    issue.Repo,
 | 
			
		||||
		Content: util.SecToTime(timediff),
 | 
			
		||||
		Content: util.SecToHours(timediff),
 | 
			
		||||
		Type:    CommentTypeStopTracking,
 | 
			
		||||
		TimeID:  tt.ID,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -248,6 +248,18 @@ func GetPackageByID(ctx context.Context, packageID int64) (*Package, error) {
 | 
			
		||||
	return p, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdatePackageNameByID updates the package's name, it is only for internal usage, for example: rename some legacy packages
 | 
			
		||||
func UpdatePackageNameByID(ctx context.Context, ownerID int64, packageType Type, packageID int64, name string) error {
 | 
			
		||||
	var cond builder.Cond = builder.Eq{
 | 
			
		||||
		"package.id":          packageID,
 | 
			
		||||
		"package.owner_id":    ownerID,
 | 
			
		||||
		"package.type":        packageType,
 | 
			
		||||
		"package.is_internal": false,
 | 
			
		||||
	}
 | 
			
		||||
	_, err := db.GetEngine(ctx).Where(cond).Update(&Package{Name: name, LowerName: strings.ToLower(name)})
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPackageByName gets a package by name
 | 
			
		||||
func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) {
 | 
			
		||||
	var cond builder.Cond = builder.Eq{
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,8 @@ type Column struct {
 | 
			
		||||
	ProjectID int64 `xorm:"INDEX NOT NULL"`
 | 
			
		||||
	CreatorID int64 `xorm:"NOT NULL"`
 | 
			
		||||
 | 
			
		||||
	NumIssues int64 `xorm:"-"`
 | 
			
		||||
 | 
			
		||||
	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 | 
			
		||||
	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
			
		||||
}
 | 
			
		||||
@@ -57,20 +59,6 @@ func (Column) TableName() string {
 | 
			
		||||
	return "project_board" // TODO: the legacy table name should be project_column
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NumIssues return counter of all issues assigned to the column
 | 
			
		||||
func (c *Column) NumIssues(ctx context.Context) int {
 | 
			
		||||
	total, err := db.GetEngine(ctx).Table("project_issue").
 | 
			
		||||
		Where("project_id=?", c.ProjectID).
 | 
			
		||||
		And("project_board_id=?", c.ID).
 | 
			
		||||
		GroupBy("issue_id").
 | 
			
		||||
		Cols("issue_id").
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return int(total)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Column) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
 | 
			
		||||
	issues := make([]*ProjectIssue, 0, 5)
 | 
			
		||||
	if err := db.GetEngine(ctx).Where("project_id=?", c.ProjectID).
 | 
			
		||||
@@ -192,7 +180,7 @@ func deleteColumnByID(ctx context.Context, columnID int64) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defaultColumn, err := project.GetDefaultColumn(ctx)
 | 
			
		||||
	defaultColumn, err := project.MustDefaultColumn(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -257,8 +245,8 @@ func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) {
 | 
			
		||||
	return columns, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDefaultColumn return default column and ensure only one exists
 | 
			
		||||
func (p *Project) GetDefaultColumn(ctx context.Context) (*Column, error) {
 | 
			
		||||
// getDefaultColumn return default column and ensure only one exists
 | 
			
		||||
func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) {
 | 
			
		||||
	var column Column
 | 
			
		||||
	has, err := db.GetEngine(ctx).
 | 
			
		||||
		Where("project_id=? AND `default` = ?", p.ID, true).
 | 
			
		||||
@@ -270,6 +258,33 @@ func (p *Project) GetDefaultColumn(ctx context.Context) (*Column, error) {
 | 
			
		||||
	if has {
 | 
			
		||||
		return &column, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil, ErrProjectColumnNotExist{ColumnID: 0}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MustDefaultColumn returns the default column for a project.
 | 
			
		||||
// If one exists, it is returned
 | 
			
		||||
// If none exists, the first column will be elevated to the default column of this project
 | 
			
		||||
func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) {
 | 
			
		||||
	c, err := p.getDefaultColumn(ctx)
 | 
			
		||||
	if err != nil && !IsErrProjectColumnNotExist(err) {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if c != nil {
 | 
			
		||||
		return c, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var column Column
 | 
			
		||||
	has, err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if has {
 | 
			
		||||
		column.Default = true
 | 
			
		||||
		if _, err := db.GetEngine(ctx).ID(column.ID).Cols("`default`").Update(&column); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return &column, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create a default column if none is found
 | 
			
		||||
	column = Column{
 | 
			
		||||
 
 | 
			
		||||
@@ -20,19 +20,19 @@ func TestGetDefaultColumn(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// check if default column was added
 | 
			
		||||
	column, err := projectWithoutDefault.GetDefaultColumn(db.DefaultContext)
 | 
			
		||||
	column, err := projectWithoutDefault.MustDefaultColumn(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(5), column.ProjectID)
 | 
			
		||||
	assert.Equal(t, "Uncategorized", column.Title)
 | 
			
		||||
	assert.Equal(t, "Done", column.Title)
 | 
			
		||||
 | 
			
		||||
	projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// check if multiple defaults were removed
 | 
			
		||||
	column, err = projectWithMultipleDefaults.GetDefaultColumn(db.DefaultContext)
 | 
			
		||||
	column, err = projectWithMultipleDefaults.MustDefaultColumn(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(6), column.ProjectID)
 | 
			
		||||
	assert.Equal(t, int64(9), column.ID)
 | 
			
		||||
	assert.Equal(t, int64(9), column.ID) // there are 2 default columns in the test data, use the latest one
 | 
			
		||||
 | 
			
		||||
	// set 8 as default column
 | 
			
		||||
	assert.NoError(t, SetDefaultColumn(db.DefaultContext, column.ProjectID, 8))
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -34,48 +33,6 @@ func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NumIssues return counter of all issues assigned to a project
 | 
			
		||||
func (p *Project) NumIssues(ctx context.Context) int {
 | 
			
		||||
	c, err := db.GetEngine(ctx).Table("project_issue").
 | 
			
		||||
		Where("project_id=?", p.ID).
 | 
			
		||||
		GroupBy("issue_id").
 | 
			
		||||
		Cols("issue_id").
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("NumIssues: %v", err)
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return int(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NumClosedIssues return counter of closed issues assigned to a project
 | 
			
		||||
func (p *Project) NumClosedIssues(ctx context.Context) int {
 | 
			
		||||
	c, err := db.GetEngine(ctx).Table("project_issue").
 | 
			
		||||
		Join("INNER", "issue", "project_issue.issue_id=issue.id").
 | 
			
		||||
		Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
 | 
			
		||||
		Cols("issue_id").
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("NumClosedIssues: %v", err)
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return int(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NumOpenIssues return counter of open issues assigned to a project
 | 
			
		||||
func (p *Project) NumOpenIssues(ctx context.Context) int {
 | 
			
		||||
	c, err := db.GetEngine(ctx).Table("project_issue").
 | 
			
		||||
		Join("INNER", "issue", "project_issue.issue_id=issue.id").
 | 
			
		||||
		Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).
 | 
			
		||||
		Cols("issue_id").
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("NumOpenIssues: %v", err)
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return int(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Column) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Column) error {
 | 
			
		||||
	if c.ProjectID != newColumn.ProjectID {
 | 
			
		||||
		return fmt.Errorf("columns have to be in the same project")
 | 
			
		||||
 
 | 
			
		||||
@@ -97,6 +97,9 @@ type Project struct {
 | 
			
		||||
	Type         Type
 | 
			
		||||
 | 
			
		||||
	RenderedContent template.HTML `xorm:"-"`
 | 
			
		||||
	NumOpenIssues   int64         `xorm:"-"`
 | 
			
		||||
	NumClosedIssues int64         `xorm:"-"`
 | 
			
		||||
	NumIssues       int64         `xorm:"-"`
 | 
			
		||||
 | 
			
		||||
	CreatedUnix    timeutil.TimeStamp `xorm:"INDEX created"`
 | 
			
		||||
	UpdatedUnix    timeutil.TimeStamp `xorm:"INDEX updated"`
 | 
			
		||||
@@ -126,6 +129,14 @@ func (p *Project) LoadRepo(ctx context.Context) (err error) {
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ProjectLinkForOrg(org *user_model.User, projectID int64) string { //nolint
 | 
			
		||||
	return fmt.Sprintf("%s/-/projects/%d", org.HomeLink(), projectID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ProjectLinkForRepo(repo *repo_model.Repository, projectID int64) string { //nolint
 | 
			
		||||
	return fmt.Sprintf("%s/projects/%d", repo.Link(), projectID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Link returns the project's relative URL.
 | 
			
		||||
func (p *Project) Link(ctx context.Context) string {
 | 
			
		||||
	if p.OwnerID > 0 {
 | 
			
		||||
@@ -134,7 +145,7 @@ func (p *Project) Link(ctx context.Context) string {
 | 
			
		||||
			log.Error("LoadOwner: %v", err)
 | 
			
		||||
			return ""
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Sprintf("%s/-/projects/%d", p.Owner.HomeLink(), p.ID)
 | 
			
		||||
		return ProjectLinkForOrg(p.Owner, p.ID)
 | 
			
		||||
	}
 | 
			
		||||
	if p.RepoID > 0 {
 | 
			
		||||
		err := p.LoadRepo(ctx)
 | 
			
		||||
@@ -142,7 +153,7 @@ func (p *Project) Link(ctx context.Context) string {
 | 
			
		||||
			log.Error("LoadRepo: %v", err)
 | 
			
		||||
			return ""
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Sprintf("%s/projects/%d", p.Repo.Link(), p.ID)
 | 
			
		||||
		return ProjectLinkForRepo(p.Repo, p.ID)
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,8 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/unit"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Init initialize model
 | 
			
		||||
@@ -27,7 +29,7 @@ func Init(ctx context.Context) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type repoChecker struct {
 | 
			
		||||
	querySQL   func(ctx context.Context) ([]map[string][]byte, error)
 | 
			
		||||
	querySQL   func(ctx context.Context) ([]int64, error)
 | 
			
		||||
	correctSQL func(ctx context.Context, id int64) error
 | 
			
		||||
	desc       string
 | 
			
		||||
}
 | 
			
		||||
@@ -38,8 +40,7 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
 | 
			
		||||
		log.Error("Select %s: %v", checker.desc, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, result := range results {
 | 
			
		||||
		id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
 | 
			
		||||
	for _, id := range results {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id)
 | 
			
		||||
@@ -54,21 +55,23 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func StatsCorrectSQL(ctx context.Context, sql string, id int64) error {
 | 
			
		||||
	_, err := db.GetEngine(ctx).Exec(sql, id, id)
 | 
			
		||||
func StatsCorrectSQL(ctx context.Context, sql any, ids ...any) error {
 | 
			
		||||
	args := []any{sql}
 | 
			
		||||
	args = append(args, ids...)
 | 
			
		||||
	_, err := db.GetEngine(ctx).Exec(args...)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func repoStatsCorrectNumWatches(ctx context.Context, id int64) error {
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id)
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func repoStatsCorrectNumStars(ctx context.Context, id int64) error {
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id)
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func labelStatsCorrectNumIssues(ctx context.Context, id int64) error {
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id)
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func labelStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
 | 
			
		||||
@@ -105,11 +108,11 @@ func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func userStatsCorrectNumRepos(ctx context.Context, id int64) error {
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id)
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
 | 
			
		||||
	return StatsCorrectSQL(ctx, "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", id)
 | 
			
		||||
	return StatsCorrectSQL(ctx, issues_model.UpdateIssueNumCommentsBuilder(id))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
 | 
			
		||||
@@ -128,9 +131,12 @@ func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
 | 
			
		||||
	return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func statsQuery(args ...any) func(context.Context) ([]map[string][]byte, error) {
 | 
			
		||||
	return func(ctx context.Context) ([]map[string][]byte, error) {
 | 
			
		||||
		return db.GetEngine(ctx).Query(args...)
 | 
			
		||||
// statsQuery returns a function that queries the database for a list of IDs
 | 
			
		||||
// sql could be a string or a *builder.Builder
 | 
			
		||||
func statsQuery(sql any, args ...any) func(context.Context) ([]int64, error) {
 | 
			
		||||
	return func(ctx context.Context) ([]int64, error) {
 | 
			
		||||
		var ids []int64
 | 
			
		||||
		return ids, db.GetEngine(ctx).SQL(sql, args...).Find(&ids)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -201,7 +207,16 @@ func CheckRepoStats(ctx context.Context) error {
 | 
			
		||||
		},
 | 
			
		||||
		// Issue.NumComments
 | 
			
		||||
		{
 | 
			
		||||
			statsQuery("SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)"),
 | 
			
		||||
			statsQuery(builder.Select("`issue`.id").From("`issue`").Where(
 | 
			
		||||
				builder.Neq{
 | 
			
		||||
					"`issue`.num_comments": builder.Select("COUNT(*)").From("`comment`").Where(
 | 
			
		||||
						builder.Expr("issue_id = `issue`.id").And(
 | 
			
		||||
							builder.In("type", issues_model.ConversationCountedCommentType()),
 | 
			
		||||
						),
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
			),
 | 
			
		||||
			),
 | 
			
		||||
			repoStatsCorrectIssueNumComments,
 | 
			
		||||
			"issue count 'num_comments'",
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,7 @@ func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string,
 | 
			
		||||
		for _, o := range oldLicenses {
 | 
			
		||||
			// Update already existing license
 | 
			
		||||
			if o.License == license {
 | 
			
		||||
				o.CommitID = commitID
 | 
			
		||||
				if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -276,6 +276,8 @@ func (repo *Repository) IsBroken() bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarkAsBrokenEmpty marks the repo as broken and empty
 | 
			
		||||
// FIXME: the status "broken" and "is_empty" were abused,
 | 
			
		||||
// The code always set them together, no way to distinguish whether a repo is really "empty" or "broken"
 | 
			
		||||
func (repo *Repository) MarkAsBrokenEmpty() {
 | 
			
		||||
	repo.Status = RepositoryBroken
 | 
			
		||||
	repo.IsEmpty = true
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,12 @@ func UpdateRepositoryCols(ctx context.Context, repo *Repository, cols ...string)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateRepositoryColsNoAutoTime updates repository's columns and but applies time change automatically
 | 
			
		||||
func UpdateRepositoryColsNoAutoTime(ctx context.Context, repo *Repository, cols ...string) error {
 | 
			
		||||
	_, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).NoAutoTime().Update(repo)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrReachLimitOfRepo represents a "ReachLimitOfRepo" kind of error.
 | 
			
		||||
type ErrReachLimitOfRepo struct {
 | 
			
		||||
	Limit int
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
@@ -22,3 +23,16 @@ func TestDoctorUserStarNum(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DoctorUserStarNum(db.DefaultContext))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_repoStatsCorrectIssueNumComments(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
 | 
			
		||||
	assert.NotNil(t, issue2)
 | 
			
		||||
	assert.EqualValues(t, 0, issue2.NumComments) // the fixture data is wrong, but we don't fix it here
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, repoStatsCorrectIssueNumComments(db.DefaultContext, 2))
 | 
			
		||||
	// reload the issue
 | 
			
		||||
	issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
 | 
			
		||||
	assert.EqualValues(t, 1, issue2.NumComments)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,19 +6,21 @@ package unittest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/modules/auth/password/hash"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-testfixtures/testfixtures/v3"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
	"xorm.io/xorm/schemas"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var fixturesLoader *testfixtures.Loader
 | 
			
		||||
type FixturesLoader interface {
 | 
			
		||||
	Load() error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var fixturesLoader FixturesLoader
 | 
			
		||||
 | 
			
		||||
// GetXORMEngine gets the XORM engine
 | 
			
		||||
func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) {
 | 
			
		||||
@@ -31,38 +33,7 @@ func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) {
 | 
			
		||||
// InitFixtures initialize test fixtures for a test database
 | 
			
		||||
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
 | 
			
		||||
	e := GetXORMEngine(engine...)
 | 
			
		||||
	var fixtureOptionFiles func(*testfixtures.Loader) error
 | 
			
		||||
	if opts.Dir != "" {
 | 
			
		||||
		fixtureOptionFiles = testfixtures.Directory(opts.Dir)
 | 
			
		||||
	} else {
 | 
			
		||||
		fixtureOptionFiles = testfixtures.Files(opts.Files...)
 | 
			
		||||
	}
 | 
			
		||||
	dialect := "unknown"
 | 
			
		||||
	switch e.Dialect().URI().DBType {
 | 
			
		||||
	case schemas.POSTGRES:
 | 
			
		||||
		dialect = "postgres"
 | 
			
		||||
	case schemas.MYSQL:
 | 
			
		||||
		dialect = "mysql"
 | 
			
		||||
	case schemas.MSSQL:
 | 
			
		||||
		dialect = "mssql"
 | 
			
		||||
	case schemas.SQLITE:
 | 
			
		||||
		dialect = "sqlite3"
 | 
			
		||||
	default:
 | 
			
		||||
		fmt.Println("Unsupported RDBMS for integration tests")
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	loaderOptions := []func(loader *testfixtures.Loader) error{
 | 
			
		||||
		testfixtures.Database(e.DB().DB),
 | 
			
		||||
		testfixtures.Dialect(dialect),
 | 
			
		||||
		testfixtures.DangerousSkipTestDatabaseCheck(),
 | 
			
		||||
		fixtureOptionFiles,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if e.Dialect().URI().DBType == schemas.POSTGRES {
 | 
			
		||||
		loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fixturesLoader, err = testfixtures.New(loaderOptions...)
 | 
			
		||||
	fixturesLoader, err = NewFixturesLoader(e, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										201
									
								
								models/unittest/fixtures_loader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								models/unittest/fixtures_loader.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package unittest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"gopkg.in/yaml.v3"
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
	"xorm.io/xorm/schemas"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type fixtureItem struct {
 | 
			
		||||
	tableName       string
 | 
			
		||||
	tableNameQuoted string
 | 
			
		||||
	sqlInserts      []string
 | 
			
		||||
	sqlInsertArgs   [][]any
 | 
			
		||||
 | 
			
		||||
	mssqlHasIdentityColumn bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type fixturesLoaderInternal struct {
 | 
			
		||||
	db               *sql.DB
 | 
			
		||||
	dbType           schemas.DBType
 | 
			
		||||
	files            []string
 | 
			
		||||
	fixtures         map[string]*fixtureItem
 | 
			
		||||
	quoteObject      func(string) string
 | 
			
		||||
	paramPlaceholder func(idx int) string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fixturesLoaderInternal) mssqlTableHasIdentityColumn(db *sql.DB, tableName string) (bool, error) {
 | 
			
		||||
	row := db.QueryRow(`SELECT COUNT(*) FROM sys.identity_columns WHERE OBJECT_ID = OBJECT_ID(?)`, tableName)
 | 
			
		||||
	var count int
 | 
			
		||||
	if err := row.Scan(&count); err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return count > 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fixturesLoaderInternal) preprocessFixtureRow(row []map[string]any) (err error) {
 | 
			
		||||
	for _, m := range row {
 | 
			
		||||
		for k, v := range m {
 | 
			
		||||
			if s, ok := v.(string); ok {
 | 
			
		||||
				if strings.HasPrefix(s, "0x") {
 | 
			
		||||
					if m[k], err = hex.DecodeString(s[2:]); err != nil {
 | 
			
		||||
						return err
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fixturesLoaderInternal) prepareFixtureItem(file string) (_ *fixtureItem, err error) {
 | 
			
		||||
	fixture := &fixtureItem{}
 | 
			
		||||
	fixture.tableName, _, _ = strings.Cut(filepath.Base(file), ".")
 | 
			
		||||
	fixture.tableNameQuoted = f.quoteObject(fixture.tableName)
 | 
			
		||||
 | 
			
		||||
	if f.dbType == schemas.MSSQL {
 | 
			
		||||
		fixture.mssqlHasIdentityColumn, err = f.mssqlTableHasIdentityColumn(f.db, fixture.tableName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data, err := os.ReadFile(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to read file %q: %w", file, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var rows []map[string]any
 | 
			
		||||
	if err = yaml.Unmarshal(data, &rows); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to unmarshal yaml data from %q: %w", file, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err = f.preprocessFixtureRow(rows); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to preprocess fixture rows from %q: %w", file, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var sqlBuf []byte
 | 
			
		||||
	var sqlArguments []any
 | 
			
		||||
	for _, row := range rows {
 | 
			
		||||
		sqlBuf = append(sqlBuf, fmt.Sprintf("INSERT INTO %s (", fixture.tableNameQuoted)...)
 | 
			
		||||
		for k, v := range row {
 | 
			
		||||
			sqlBuf = append(sqlBuf, f.quoteObject(k)...)
 | 
			
		||||
			sqlBuf = append(sqlBuf, ","...)
 | 
			
		||||
			sqlArguments = append(sqlArguments, v)
 | 
			
		||||
		}
 | 
			
		||||
		sqlBuf = sqlBuf[:len(sqlBuf)-1]
 | 
			
		||||
		sqlBuf = append(sqlBuf, ") VALUES ("...)
 | 
			
		||||
		paramIdx := 1
 | 
			
		||||
		for range row {
 | 
			
		||||
			sqlBuf = append(sqlBuf, f.paramPlaceholder(paramIdx)...)
 | 
			
		||||
			sqlBuf = append(sqlBuf, ',')
 | 
			
		||||
			paramIdx++
 | 
			
		||||
		}
 | 
			
		||||
		sqlBuf[len(sqlBuf)-1] = ')'
 | 
			
		||||
		fixture.sqlInserts = append(fixture.sqlInserts, string(sqlBuf))
 | 
			
		||||
		fixture.sqlInsertArgs = append(fixture.sqlInsertArgs, slices.Clone(sqlArguments))
 | 
			
		||||
		sqlBuf = sqlBuf[:0]
 | 
			
		||||
		sqlArguments = sqlArguments[:0]
 | 
			
		||||
	}
 | 
			
		||||
	return fixture, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fixturesLoaderInternal) loadFixtures(tx *sql.Tx, file string) (err error) {
 | 
			
		||||
	fixture := f.fixtures[file]
 | 
			
		||||
	if fixture == nil {
 | 
			
		||||
		if fixture, err = f.prepareFixtureItem(file); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		f.fixtures[file] = fixture
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = tx.Exec(fmt.Sprintf("DELETE FROM %s", fixture.tableNameQuoted)) // sqlite3 doesn't support truncate
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fixture.mssqlHasIdentityColumn {
 | 
			
		||||
		_, err = tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s ON", fixture.tableNameQuoted))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		defer func() { _, err = tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s OFF", fixture.tableNameQuoted)) }()
 | 
			
		||||
	}
 | 
			
		||||
	for i := range fixture.sqlInserts {
 | 
			
		||||
		_, err = tx.Exec(fixture.sqlInserts[i], fixture.sqlInsertArgs[i]...)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fixturesLoaderInternal) Load() error {
 | 
			
		||||
	tx, err := f.db.Begin()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() { _ = tx.Rollback() }()
 | 
			
		||||
 | 
			
		||||
	for _, file := range f.files {
 | 
			
		||||
		if err := f.loadFixtures(tx, file); err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to load fixtures from %s: %w", file, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return tx.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FixturesFileFullPaths(dir string, files []string) ([]string, error) {
 | 
			
		||||
	if files != nil && len(files) == 0 {
 | 
			
		||||
		return nil, nil // load nothing
 | 
			
		||||
	}
 | 
			
		||||
	files = slices.Clone(files)
 | 
			
		||||
	if len(files) == 0 {
 | 
			
		||||
		entries, err := os.ReadDir(dir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, e := range entries {
 | 
			
		||||
			files = append(files, e.Name())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for i, file := range files {
 | 
			
		||||
		if !filepath.IsAbs(file) {
 | 
			
		||||
			files[i] = filepath.Join(dir, file)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return files, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFixturesLoader(x *xorm.Engine, opts FixturesOptions) (FixturesLoader, error) {
 | 
			
		||||
	files, err := FixturesFileFullPaths(opts.Dir, opts.Files)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to get fixtures files: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	f := &fixturesLoaderInternal{db: x.DB().DB, dbType: x.Dialect().URI().DBType, files: files, fixtures: map[string]*fixtureItem{}}
 | 
			
		||||
	switch f.dbType {
 | 
			
		||||
	case schemas.SQLITE:
 | 
			
		||||
		f.quoteObject = func(s string) string { return fmt.Sprintf(`"%s"`, s) }
 | 
			
		||||
		f.paramPlaceholder = func(idx int) string { return "?" }
 | 
			
		||||
	case schemas.POSTGRES:
 | 
			
		||||
		f.quoteObject = func(s string) string { return fmt.Sprintf(`"%s"`, s) }
 | 
			
		||||
		f.paramPlaceholder = func(idx int) string { return fmt.Sprintf(`$%d`, idx) }
 | 
			
		||||
	case schemas.MYSQL:
 | 
			
		||||
		f.quoteObject = func(s string) string { return fmt.Sprintf("`%s`", s) }
 | 
			
		||||
		f.paramPlaceholder = func(idx int) string { return "?" }
 | 
			
		||||
	case schemas.MSSQL:
 | 
			
		||||
		f.quoteObject = func(s string) string { return fmt.Sprintf("[%s]", s) }
 | 
			
		||||
		f.paramPlaceholder = func(idx int) string { return "?" }
 | 
			
		||||
	}
 | 
			
		||||
	return f, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -38,27 +38,32 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
 | 
			
		||||
 | 
			
		||||
	u.Avatar = avatars.HashEmail(seed)
 | 
			
		||||
 | 
			
		||||
	// Don't share the images so that we can delete them easily
 | 
			
		||||
	if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
 | 
			
		||||
		if err := png.Encode(w, img); err != nil {
 | 
			
		||||
			log.Error("Encode: %v", err)
 | 
			
		||||
	_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// If unable to Stat the avatar file (usually it means non-existing), then try to save a new one
 | 
			
		||||
		// Don't share the images so that we can delete them easily
 | 
			
		||||
		if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
 | 
			
		||||
			if err := png.Encode(w, img); err != nil {
 | 
			
		||||
				log.Error("Encode: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err)
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Info("New random avatar created: %d", u.ID)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
 | 
			
		||||
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
 | 
			
		||||
	if u.IsGhost() {
 | 
			
		||||
	// ghost user was deleted, Gitea actions is a bot user, 0 means the user should be a virtual user
 | 
			
		||||
	// which comes from git configure information
 | 
			
		||||
	if u.IsGhost() || u.IsGiteaActions() || u.ID <= 0 {
 | 
			
		||||
		return avatars.DefaultAvatarLink()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,19 @@
 | 
			
		||||
package user
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/storage"
 | 
			
		||||
	"code.gitea.io/gitea/modules/test"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestUserAvatarLink(t *testing.T) {
 | 
			
		||||
@@ -26,3 +32,37 @@ func TestUserAvatarLink(t *testing.T) {
 | 
			
		||||
	link = u.AvatarLink(db.DefaultContext)
 | 
			
		||||
	assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUserAvatarGenerate(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	var err error
 | 
			
		||||
	tmpDir := t.TempDir()
 | 
			
		||||
	storage.Avatars, err = storage.NewLocalStorage(context.Background(), &setting.Storage{Path: tmpDir})
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	u := unittest.AssertExistsAndLoadBean(t, &User{ID: 2})
 | 
			
		||||
 | 
			
		||||
	// there was no avatar, generate a new one
 | 
			
		||||
	assert.Empty(t, u.Avatar)
 | 
			
		||||
	err = GenerateRandomAvatar(db.DefaultContext, u)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.NotEmpty(t, u.Avatar)
 | 
			
		||||
 | 
			
		||||
	// make sure the generated one exists
 | 
			
		||||
	oldAvatarPath := u.CustomAvatarRelativePath()
 | 
			
		||||
	_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	// and try to change its content
 | 
			
		||||
	_, err = storage.Avatars.Save(u.CustomAvatarRelativePath(), strings.NewReader("abcd"), 4)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// try to generate again
 | 
			
		||||
	err = GenerateRandomAvatar(db.DefaultContext, u)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, oldAvatarPath, u.CustomAvatarRelativePath())
 | 
			
		||||
	f, err := storage.Avatars.Open(u.CustomAvatarRelativePath())
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
	content, _ := io.ReadAll(f)
 | 
			
		||||
	assert.Equal(t, "abcd", string(content))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -357,8 +357,8 @@ func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddres
 | 
			
		||||
	if user := GetVerifyUser(ctx, code); user != nil {
 | 
			
		||||
		// time limit code
 | 
			
		||||
		prefix := code[:base.TimeLimitCodeLength]
 | 
			
		||||
		data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
 | 
			
		||||
 | 
			
		||||
		opts := &TimeLimitCodeOptions{Purpose: TimeLimitCodeActivateEmail, NewEmail: email}
 | 
			
		||||
		data := makeTimeLimitCodeHashData(opts, user)
 | 
			
		||||
		if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
 | 
			
		||||
			emailAddress := &EmailAddress{UID: user.ID, Email: email}
 | 
			
		||||
			if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
 | 
			
		||||
@@ -486,10 +486,10 @@ func ActivateUserEmail(ctx context.Context, userID int64, email string, activate
 | 
			
		||||
 | 
			
		||||
	// Activate/deactivate a user's primary email address and account
 | 
			
		||||
	if addr.IsPrimary {
 | 
			
		||||
		user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID, "email": email})
 | 
			
		||||
		user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		} else if !exist {
 | 
			
		||||
		} else if !exist || !strings.EqualFold(user.Email, email) {
 | 
			
		||||
			return fmt.Errorf("no user with ID: %d and Email: %s", userID, email)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -181,7 +181,8 @@ func (u *User) BeforeUpdate() {
 | 
			
		||||
		u.MaxRepoCreation = -1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Organization does not need email
 | 
			
		||||
	// FIXME: this email doesn't need to be in lowercase, because the emails are mainly managed by the email table with lower_email field
 | 
			
		||||
	// This trick could be removed in new releases to display the user inputed email as-is.
 | 
			
		||||
	u.Email = strings.ToLower(u.Email)
 | 
			
		||||
	if !u.IsOrganization() {
 | 
			
		||||
		if len(u.AvatarEmail) == 0 {
 | 
			
		||||
@@ -310,17 +311,6 @@ func (u *User) OrganisationLink() string {
 | 
			
		||||
	return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
 | 
			
		||||
func (u *User) GenerateEmailActivateCode(email string) string {
 | 
			
		||||
	code := base.CreateTimeLimitCode(
 | 
			
		||||
		fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
 | 
			
		||||
		setting.Service.ActiveCodeLives, time.Now(), nil)
 | 
			
		||||
 | 
			
		||||
	// Add tail hex username
 | 
			
		||||
	code += hex.EncodeToString([]byte(u.LowerName))
 | 
			
		||||
	return code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUserFollowers returns range of user's followers.
 | 
			
		||||
func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
 | 
			
		||||
	sess := db.GetEngine(ctx).
 | 
			
		||||
@@ -848,12 +838,38 @@ func GetVerifyUser(ctx context.Context, code string) (user *User) {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyUserActiveCode verifies active code when active account
 | 
			
		||||
func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
 | 
			
		||||
type TimeLimitCodePurpose string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TimeLimitCodeActivateAccount TimeLimitCodePurpose = "activate_account"
 | 
			
		||||
	TimeLimitCodeActivateEmail   TimeLimitCodePurpose = "activate_email"
 | 
			
		||||
	TimeLimitCodeResetPassword   TimeLimitCodePurpose = "reset_password"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TimeLimitCodeOptions struct {
 | 
			
		||||
	Purpose  TimeLimitCodePurpose
 | 
			
		||||
	NewEmail string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func makeTimeLimitCodeHashData(opts *TimeLimitCodeOptions, u *User) string {
 | 
			
		||||
	return fmt.Sprintf("%s|%d|%s|%s|%s|%s", opts.Purpose, u.ID, strings.ToLower(util.IfZero(opts.NewEmail, u.Email)), u.LowerName, u.Passwd, u.Rands)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenerateUserTimeLimitCode generates a time-limit code based on user information and given e-mail.
 | 
			
		||||
// TODO: need to use cache or db to store it to make sure a code can only be consumed once
 | 
			
		||||
func GenerateUserTimeLimitCode(opts *TimeLimitCodeOptions, u *User) string {
 | 
			
		||||
	data := makeTimeLimitCodeHashData(opts, u)
 | 
			
		||||
	code := base.CreateTimeLimitCode(data, setting.Service.ActiveCodeLives, time.Now(), nil)
 | 
			
		||||
	code += hex.EncodeToString([]byte(u.LowerName)) // Add tail hex username
 | 
			
		||||
	return code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyUserTimeLimitCode verifies the time-limit code
 | 
			
		||||
func VerifyUserTimeLimitCode(ctx context.Context, opts *TimeLimitCodeOptions, code string) (user *User) {
 | 
			
		||||
	if user = GetVerifyUser(ctx, code); user != nil {
 | 
			
		||||
		// time limit code
 | 
			
		||||
		prefix := code[:base.TimeLimitCodeLength]
 | 
			
		||||
		data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands)
 | 
			
		||||
		data := makeTimeLimitCodeHashData(opts, user)
 | 
			
		||||
		if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
 | 
			
		||||
			return user
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,10 @@ func NewGhostUser() *User {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsGhostUserName(name string) bool {
 | 
			
		||||
	return strings.EqualFold(name, GhostUserName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsGhost check if user is fake user for a deleted account
 | 
			
		||||
func (u *User) IsGhost() bool {
 | 
			
		||||
	if u == nil {
 | 
			
		||||
@@ -48,6 +52,10 @@ const (
 | 
			
		||||
	ActionsEmail    = "teabot@gitea.io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func IsGiteaActionsUserName(name string) bool {
 | 
			
		||||
	return strings.EqualFold(name, ActionsUserName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewActionsUser creates and returns a fake user for running the actions.
 | 
			
		||||
func NewActionsUser() *User {
 | 
			
		||||
	return &User{
 | 
			
		||||
@@ -65,6 +73,16 @@ func NewActionsUser() *User {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *User) IsActions() bool {
 | 
			
		||||
func (u *User) IsGiteaActions() bool {
 | 
			
		||||
	return u != nil && u.ID == ActionsUserID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetSystemUserByName(name string) *User {
 | 
			
		||||
	if IsGhostUserName(name) {
 | 
			
		||||
		return NewGhostUser()
 | 
			
		||||
	}
 | 
			
		||||
	if IsGiteaActionsUserName(name) {
 | 
			
		||||
		return NewActionsUser()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								models/user/user_system_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								models/user/user_system_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package user
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSystemUser(t *testing.T) {
 | 
			
		||||
	u, err := GetPossibleUserByID(db.DefaultContext, -1)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "Ghost", u.Name)
 | 
			
		||||
	assert.Equal(t, "ghost", u.LowerName)
 | 
			
		||||
	assert.True(t, u.IsGhost())
 | 
			
		||||
	assert.True(t, IsGhostUserName("gHost"))
 | 
			
		||||
 | 
			
		||||
	u, err = GetPossibleUserByID(db.DefaultContext, -2)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "gitea-actions", u.Name)
 | 
			
		||||
	assert.Equal(t, "gitea-actions", u.LowerName)
 | 
			
		||||
	assert.True(t, u.IsGiteaActions())
 | 
			
		||||
	assert.True(t, IsGiteaActionsUserName("Gitea-actionS"))
 | 
			
		||||
 | 
			
		||||
	_, err = GetPossibleUserByID(db.DefaultContext, -3)
 | 
			
		||||
	require.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
@@ -299,6 +299,11 @@ func (w *Webhook) HasPackageEvent() bool {
 | 
			
		||||
		(w.ChooseEvents && w.HookEvents.Package)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Webhook) HasStatusEvent() bool {
 | 
			
		||||
	return w.SendEverything ||
 | 
			
		||||
		(w.ChooseEvents && w.HookEvents.Status)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
 | 
			
		||||
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
 | 
			
		||||
	return w.SendEverything ||
 | 
			
		||||
@@ -337,6 +342,7 @@ func (w *Webhook) EventCheckers() []struct {
 | 
			
		||||
		{w.HasReleaseEvent, webhook_module.HookEventRelease},
 | 
			
		||||
		{w.HasPackageEvent, webhook_module.HookEventPackage},
 | 
			
		||||
		{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
 | 
			
		||||
		{w.HasStatusEvent, webhook_module.HookEventStatus},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ func TestWebhook_EventsArray(t *testing.T) {
 | 
			
		||||
		"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
 | 
			
		||||
		"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
 | 
			
		||||
		"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
 | 
			
		||||
		"package", "pull_request_review_request",
 | 
			
		||||
		"package", "pull_request_review_request", "status",
 | 
			
		||||
	},
 | 
			
		||||
		(&Webhook{
 | 
			
		||||
			HookEvent: &webhook_module.HookEvent{SendEverything: true},
 | 
			
		||||
 
 | 
			
		||||
@@ -463,7 +463,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
 | 
			
		||||
				matchTimes++
 | 
			
		||||
			}
 | 
			
		||||
		case "paths":
 | 
			
		||||
			filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref)
 | 
			
		||||
			filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
 | 
			
		||||
			} else {
 | 
			
		||||
@@ -476,7 +476,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case "paths-ignore":
 | 
			
		||||
			filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref)
 | 
			
		||||
			filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
 | 
			
		||||
			} else {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) {
 | 
			
		||||
	result, err := Auth("gitea", "user1", "false-pwd")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.EqualError(t, err, "Authentication failure")
 | 
			
		||||
	assert.Len(t, result)
 | 
			
		||||
	assert.Empty(t, result)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								modules/cache/cache.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								modules/cache/cache.go
									
									
									
									
										vendored
									
									
								
							@@ -37,10 +37,15 @@ func Init() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	testCacheKey       = "DefaultCache.TestKey"
 | 
			
		||||
	SlowCacheThreshold = 100 * time.Microsecond
 | 
			
		||||
	testCacheKey = "DefaultCache.TestKey"
 | 
			
		||||
	// SlowCacheThreshold marks cache tests as slow
 | 
			
		||||
	// set to 30ms per discussion: https://github.com/go-gitea/gitea/issues/33190
 | 
			
		||||
	// TODO: Replace with metrics histogram
 | 
			
		||||
	SlowCacheThreshold = 30 * time.Millisecond
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Test performs delete, put and get operations on a predefined key
 | 
			
		||||
// returns
 | 
			
		||||
func Test() (time.Duration, error) {
 | 
			
		||||
	if defaultCache == nil {
 | 
			
		||||
		return 0, fmt.Errorf("default cache not initialized")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								modules/cache/cache_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								modules/cache/cache_test.go
									
									
									
									
										vendored
									
									
								
							@@ -43,7 +43,8 @@ func TestTest(t *testing.T) {
 | 
			
		||||
	elapsed, err := Test()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	// mem cache should take from 300ns up to 1ms on modern hardware ...
 | 
			
		||||
	assert.Less(t, elapsed, time.Millisecond)
 | 
			
		||||
	assert.Positive(t, elapsed)
 | 
			
		||||
	assert.Less(t, elapsed, SlowCacheThreshold)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetCache(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -253,7 +253,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte {
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseTreeLine reads an entry from a tree in a cat-file --batch stream
 | 
			
		||||
// ParseCatFileTreeLine reads an entry from a tree in a cat-file --batch stream
 | 
			
		||||
// This carefully avoids allocations - except where fnameBuf is too small.
 | 
			
		||||
// It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations
 | 
			
		||||
//
 | 
			
		||||
@@ -261,7 +261,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte {
 | 
			
		||||
// <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <binary HASH>
 | 
			
		||||
//
 | 
			
		||||
// We don't attempt to convert the raw HASH to save a lot of time
 | 
			
		||||
func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) {
 | 
			
		||||
func ParseCatFileTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) {
 | 
			
		||||
	var readBytes []byte
 | 
			
		||||
 | 
			
		||||
	// Read the Mode & fname
 | 
			
		||||
@@ -271,7 +271,7 @@ func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBu
 | 
			
		||||
	}
 | 
			
		||||
	idx := bytes.IndexByte(readBytes, ' ')
 | 
			
		||||
	if idx < 0 {
 | 
			
		||||
		log.Debug("missing space in readBytes ParseTreeLine: %s", readBytes)
 | 
			
		||||
		log.Debug("missing space in readBytes ParseCatFileTreeLine: %s", readBytes)
 | 
			
		||||
		return mode, fname, sha, n, &ErrNotExist{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ type Command struct {
 | 
			
		||||
	desc             string
 | 
			
		||||
	globalArgsLength int
 | 
			
		||||
	brokenArgs       []string
 | 
			
		||||
	cmd              *exec.Cmd // for debug purpose only
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Command) String() string {
 | 
			
		||||
@@ -314,6 +315,7 @@ func (c *Command) Run(opts *RunOpts) error {
 | 
			
		||||
	startTime := time.Now()
 | 
			
		||||
 | 
			
		||||
	cmd := exec.CommandContext(ctx, c.prog, c.args...)
 | 
			
		||||
	c.cmd = cmd // for debug purpose only
 | 
			
		||||
	if opts.Env == nil {
 | 
			
		||||
		cmd.Env = os.Environ()
 | 
			
		||||
	} else {
 | 
			
		||||
@@ -348,9 +350,10 @@ func (c *Command) Run(opts *RunOpts) error {
 | 
			
		||||
	// We need to check if the context is canceled by the program on Windows.
 | 
			
		||||
	// This is because Windows does not have signal checking when terminating the process.
 | 
			
		||||
	// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
 | 
			
		||||
	// `err.Error()` returns "exit status 1" when using the `git check-attr` command after the context is canceled.
 | 
			
		||||
	if runtime.GOOS == "windows" &&
 | 
			
		||||
		err != nil &&
 | 
			
		||||
		err.Error() == "" &&
 | 
			
		||||
		(err.Error() == "" || err.Error() == "exit status 1") &&
 | 
			
		||||
		cmd.ProcessState.ExitCode() == 1 &&
 | 
			
		||||
		ctx.Err() == context.Canceled {
 | 
			
		||||
		return ctx.Err()
 | 
			
		||||
 
 | 
			
		||||
@@ -360,5 +360,5 @@ func Test_GetCommitBranchStart(t *testing.T) {
 | 
			
		||||
	startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String())
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NotEmpty(t, startCommitID)
 | 
			
		||||
	assert.EqualValues(t, "9c9aef8dd84e02bc7ec12641deb4c930a7c30185", startCommitID)
 | 
			
		||||
	assert.EqualValues(t, "95bb4d39648ee7e325106df01a621c530863a653", startCommitID)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								modules/git/parse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								modules/git/parse.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/optional"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var sepSpace = []byte{' '}
 | 
			
		||||
 | 
			
		||||
type LsTreeEntry struct {
 | 
			
		||||
	ID        ObjectID
 | 
			
		||||
	EntryMode EntryMode
 | 
			
		||||
	Name      string
 | 
			
		||||
	Size      optional.Option[int64]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseLsTreeLine(line []byte) (*LsTreeEntry, error) {
 | 
			
		||||
	// expect line to be of the form:
 | 
			
		||||
	// <mode> <type> <sha> <space-padded-size>\t<filename>
 | 
			
		||||
	// <mode> <type> <sha>\t<filename>
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	posTab := bytes.IndexByte(line, '\t')
 | 
			
		||||
	if posTab == -1 {
 | 
			
		||||
		return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entry := new(LsTreeEntry)
 | 
			
		||||
 | 
			
		||||
	entryAttrs := line[:posTab]
 | 
			
		||||
	entryName := line[posTab+1:]
 | 
			
		||||
 | 
			
		||||
	entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
 | 
			
		||||
	_ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type
 | 
			
		||||
	entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
 | 
			
		||||
	if len(entryAttrs) > 0 {
 | 
			
		||||
		entrySize := entryAttrs // the last field is the space-padded-size
 | 
			
		||||
		size, _ := strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64)
 | 
			
		||||
		entry.Size = optional.Some(size)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch string(entryMode) {
 | 
			
		||||
	case "100644":
 | 
			
		||||
		entry.EntryMode = EntryModeBlob
 | 
			
		||||
	case "100755":
 | 
			
		||||
		entry.EntryMode = EntryModeExec
 | 
			
		||||
	case "120000":
 | 
			
		||||
		entry.EntryMode = EntryModeSymlink
 | 
			
		||||
	case "160000":
 | 
			
		||||
		entry.EntryMode = EntryModeCommit
 | 
			
		||||
	case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
 | 
			
		||||
		entry.EntryMode = EntryModeTree
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, fmt.Errorf("unknown type: %v", string(entryMode))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entry.ID, err = NewIDFromString(string(entryObjectID))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(entryName) > 0 && entryName[0] == '"' {
 | 
			
		||||
		entry.Name, err = strconv.Unquote(string(entryName))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		entry.Name = string(entryName)
 | 
			
		||||
	}
 | 
			
		||||
	return entry, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -10,8 +10,6 @@ import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
)
 | 
			
		||||
@@ -21,71 +19,30 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
 | 
			
		||||
	return parseTreeEntries(data, nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var sepSpace = []byte{' '}
 | 
			
		||||
 | 
			
		||||
// parseTreeEntries FIXME this function's design is not right, it should make the caller read all data into memory
 | 
			
		||||
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
 | 
			
		||||
	for pos := 0; pos < len(data); {
 | 
			
		||||
		// expect line to be of the form:
 | 
			
		||||
		// <mode> <type> <sha> <space-padded-size>\t<filename>
 | 
			
		||||
		// <mode> <type> <sha>\t<filename>
 | 
			
		||||
		posEnd := bytes.IndexByte(data[pos:], '\n')
 | 
			
		||||
		if posEnd == -1 {
 | 
			
		||||
			posEnd = len(data)
 | 
			
		||||
		} else {
 | 
			
		||||
			posEnd += pos
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		line := data[pos:posEnd]
 | 
			
		||||
		posTab := bytes.IndexByte(line, '\t')
 | 
			
		||||
		if posTab == -1 {
 | 
			
		||||
			return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		entry := new(TreeEntry)
 | 
			
		||||
		entry.ptree = ptree
 | 
			
		||||
 | 
			
		||||
		entryAttrs := line[:posTab]
 | 
			
		||||
		entryName := line[posTab+1:]
 | 
			
		||||
 | 
			
		||||
		entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
 | 
			
		||||
		_ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type
 | 
			
		||||
		entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
 | 
			
		||||
		if len(entryAttrs) > 0 {
 | 
			
		||||
			entrySize := entryAttrs // the last field is the space-padded-size
 | 
			
		||||
			entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64)
 | 
			
		||||
			entry.sized = true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch string(entryMode) {
 | 
			
		||||
		case "100644":
 | 
			
		||||
			entry.entryMode = EntryModeBlob
 | 
			
		||||
		case "100755":
 | 
			
		||||
			entry.entryMode = EntryModeExec
 | 
			
		||||
		case "120000":
 | 
			
		||||
			entry.entryMode = EntryModeSymlink
 | 
			
		||||
		case "160000":
 | 
			
		||||
			entry.entryMode = EntryModeCommit
 | 
			
		||||
		case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
 | 
			
		||||
			entry.entryMode = EntryModeTree
 | 
			
		||||
		default:
 | 
			
		||||
			return nil, fmt.Errorf("unknown type: %v", string(entryMode))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		entry.ID, err = NewIDFromString(string(entryObjectID))
 | 
			
		||||
		lsTreeLine, err := parseLsTreeLine(line)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err)
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(entryName) > 0 && entryName[0] == '"' {
 | 
			
		||||
			entry.name, err = strconv.Unquote(string(entryName))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			entry.name = string(entryName)
 | 
			
		||||
		entry := &TreeEntry{
 | 
			
		||||
			ptree:     ptree,
 | 
			
		||||
			ID:        lsTreeLine.ID,
 | 
			
		||||
			entryMode: lsTreeLine.EntryMode,
 | 
			
		||||
			name:      lsTreeLine.Name,
 | 
			
		||||
			size:      lsTreeLine.Size.Value(),
 | 
			
		||||
			sized:     lsTreeLine.Size.Has(),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pos = posEnd + 1
 | 
			
		||||
		entries = append(entries, entry)
 | 
			
		||||
	}
 | 
			
		||||
@@ -100,7 +57,7 @@ func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd *bufio.
 | 
			
		||||
 | 
			
		||||
loop:
 | 
			
		||||
	for sz > 0 {
 | 
			
		||||
		mode, fname, sha, count, err := ParseTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf)
 | 
			
		||||
		mode, fname, sha, count, err := ParseCatFileTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if err == io.EOF {
 | 
			
		||||
				break loop
 | 
			
		||||
 
 | 
			
		||||
@@ -114,7 +114,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
 | 
			
		||||
			case "tree":
 | 
			
		||||
				var n int64
 | 
			
		||||
				for n < size {
 | 
			
		||||
					mode, fname, binObjectID, count, err := git.ParseTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf)
 | 
			
		||||
					mode, fname, binObjectID, count, err := git.ParseCatFileTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return nil, err
 | 
			
		||||
					}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,8 @@ func TestRefName(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	// Test pull names
 | 
			
		||||
	assert.Equal(t, "1", RefName("refs/pull/1/head").PullName())
 | 
			
		||||
	assert.True(t, RefName("refs/pull/1/head").IsPull())
 | 
			
		||||
	assert.True(t, RefName("refs/pull/1/merge").IsPull())
 | 
			
		||||
	assert.Equal(t, "my/pull", RefName("refs/pull/my/pull/head").PullName())
 | 
			
		||||
 | 
			
		||||
	// Test for branch names
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	giturl "code.gitea.io/gitea/modules/git/url"
 | 
			
		||||
)
 | 
			
		||||
@@ -37,3 +38,12 @@ func GetRemoteURL(ctx context.Context, repoPath, remoteName string) (*giturl.Git
 | 
			
		||||
	}
 | 
			
		||||
	return giturl.Parse(addr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsRemoteNotExistError checks the prefix of the error message to see whether a remote does not exist.
 | 
			
		||||
func IsRemoteNotExistError(err error) bool {
 | 
			
		||||
	// see: https://github.com/go-gitea/gitea/issues/32889#issuecomment-2571848216
 | 
			
		||||
	// Should not add space in the end, sometimes git will add a `:`
 | 
			
		||||
	prefix1 := "exit status 128 - fatal: No such remote" // git < 2.30
 | 
			
		||||
	prefix2 := "exit status 2 - error: No such remote"   // git >= 2.30
 | 
			
		||||
	return strings.HasPrefix(err.Error(), prefix1) || strings.HasPrefix(err.Error(), prefix2)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,8 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
)
 | 
			
		||||
@@ -102,7 +104,7 @@ type CheckAttributeReader struct {
 | 
			
		||||
 | 
			
		||||
	stdinReader io.ReadCloser
 | 
			
		||||
	stdinWriter *os.File
 | 
			
		||||
	stdOut      attributeWriter
 | 
			
		||||
	stdOut      *nulSeparatedAttributeWriter
 | 
			
		||||
	cmd         *Command
 | 
			
		||||
	env         []string
 | 
			
		||||
	ctx         context.Context
 | 
			
		||||
@@ -152,7 +154,6 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run run cmd
 | 
			
		||||
func (c *CheckAttributeReader) Run() error {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = c.stdinReader.Close()
 | 
			
		||||
@@ -176,7 +177,7 @@ func (c *CheckAttributeReader) Run() error {
 | 
			
		||||
func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err != nil && err != c.ctx.Err() {
 | 
			
		||||
			log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
 | 
			
		||||
			log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.Repo.Path), err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
@@ -191,9 +192,31 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reportTimeout := func() error {
 | 
			
		||||
		stdOutClosed := false
 | 
			
		||||
		select {
 | 
			
		||||
		case <-c.stdOut.closed:
 | 
			
		||||
			stdOutClosed = true
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
		debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.Repo.Path))
 | 
			
		||||
		debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
 | 
			
		||||
		if c.cmd.cmd != nil {
 | 
			
		||||
			debugMsg += fmt.Sprintf(", process state: %q", c.cmd.cmd.ProcessState.String())
 | 
			
		||||
		}
 | 
			
		||||
		_ = c.Close()
 | 
			
		||||
		return fmt.Errorf("CheckPath timeout: %s", debugMsg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rs = make(map[string]string)
 | 
			
		||||
	for range c.Attributes {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-time.After(5 * time.Second):
 | 
			
		||||
			// There is a strange "hang" problem in gitdiff.GetDiff -> CheckPath
 | 
			
		||||
			// So add a timeout here to mitigate the problem, and output more logs for debug purpose
 | 
			
		||||
			// In real world, if CheckPath runs long than seconds, it blocks the end user's operation,
 | 
			
		||||
			// and at the moment the CheckPath result is not so important, so we can just ignore it.
 | 
			
		||||
			return nil, reportTimeout()
 | 
			
		||||
		case attr, ok := <-c.stdOut.ReadAttribute():
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return nil, c.ctx.Err()
 | 
			
		||||
@@ -206,18 +229,12 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close close pip after use
 | 
			
		||||
func (c *CheckAttributeReader) Close() error {
 | 
			
		||||
	c.cancel()
 | 
			
		||||
	err := c.stdinWriter.Close()
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type attributeWriter interface {
 | 
			
		||||
	io.WriteCloser
 | 
			
		||||
	ReadAttribute() <-chan attributeTriple
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type attributeTriple struct {
 | 
			
		||||
	Filename  string
 | 
			
		||||
	Attribute string
 | 
			
		||||
@@ -263,7 +280,7 @@ func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	wr.tmp = append(wr.tmp, p...)
 | 
			
		||||
	return len(p), nil
 | 
			
		||||
	return l, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
 | 
			
		||||
@@ -281,7 +298,7 @@ func (wr *nulSeparatedAttributeWriter) Close() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Create a check attribute reader for the current repository and provided commit ID
 | 
			
		||||
// CheckAttributeReader creates a check attribute reader for the current repository and provided commit ID
 | 
			
		||||
func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) {
 | 
			
		||||
	indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -303,21 +320,21 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
 | 
			
		||||
	}
 | 
			
		||||
	ctx, cancel := context.WithCancel(repo.Ctx)
 | 
			
		||||
	if err := checker.Init(ctx); err != nil {
 | 
			
		||||
		log.Error("Unable to open checker for %s. Error: %v", commitID, err)
 | 
			
		||||
		log.Error("Unable to open attribute checker for commit %s, error: %v", commitID, err)
 | 
			
		||||
	} else {
 | 
			
		||||
		go func() {
 | 
			
		||||
			err := checker.Run()
 | 
			
		||||
			if err != nil && err != ctx.Err() {
 | 
			
		||||
				log.Error("Unable to open checker for %s. Error: %v", commitID, err)
 | 
			
		||||
			if err != nil && !IsErrCanceledOrKilled(err) {
 | 
			
		||||
				log.Error("Attribute checker for commit %s exits with error: %v", commitID, err)
 | 
			
		||||
			}
 | 
			
		||||
			cancel()
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
	deferable := func() {
 | 
			
		||||
	deferrable := func() {
 | 
			
		||||
		_ = checker.Close()
 | 
			
		||||
		cancel()
 | 
			
		||||
		deleteTemporaryFile()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return checker, deferable
 | 
			
		||||
	return checker, deferrable
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,16 @@
 | 
			
		||||
package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	mathRand "math/rand/v2"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
 | 
			
		||||
@@ -95,3 +101,57 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
 | 
			
		||||
		Value:     "unspecified",
 | 
			
		||||
	}, attr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAttributeReader(t *testing.T) {
 | 
			
		||||
	t.Skip() // for debug purpose only, do not run in CI
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	timeout := 1 * time.Second
 | 
			
		||||
	repoPath := filepath.Join(testReposDir, "language_stats_repo")
 | 
			
		||||
	commitRef := "HEAD"
 | 
			
		||||
 | 
			
		||||
	oneRound := func(t *testing.T, roundIdx int) {
 | 
			
		||||
		ctx, cancel := context.WithTimeout(ctx, timeout)
 | 
			
		||||
		_ = cancel
 | 
			
		||||
		gitRepo, err := OpenRepository(ctx, repoPath)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		defer gitRepo.Close()
 | 
			
		||||
 | 
			
		||||
		commit, err := gitRepo.GetCommit(commitRef)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		files, err := gitRepo.LsFiles()
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		randomFiles := slices.Clone(files)
 | 
			
		||||
		randomFiles = append(randomFiles, "any-file-1", "any-file-2")
 | 
			
		||||
 | 
			
		||||
		t.Logf("Round %v with %d files", roundIdx, len(randomFiles))
 | 
			
		||||
 | 
			
		||||
		attrReader, deferrable := gitRepo.CheckAttributeReader(commit.ID.String())
 | 
			
		||||
		defer deferrable()
 | 
			
		||||
 | 
			
		||||
		wg := sync.WaitGroup{}
 | 
			
		||||
		wg.Add(1)
 | 
			
		||||
 | 
			
		||||
		go func() {
 | 
			
		||||
			for {
 | 
			
		||||
				file := randomFiles[mathRand.IntN(len(randomFiles))]
 | 
			
		||||
				_, err := attrReader.CheckPath(file)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					for i := 0; i < 10; i++ {
 | 
			
		||||
						_, _ = attrReader.CheckPath(file)
 | 
			
		||||
					}
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			wg.Done()
 | 
			
		||||
		}()
 | 
			
		||||
		wg.Wait()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < 100; i++ {
 | 
			
		||||
		oneRound(t, i)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -216,8 +216,6 @@ type CommitsByFileAndRangeOptions struct {
 | 
			
		||||
 | 
			
		||||
// CommitsByFileAndRange return the commits according revision file and the page
 | 
			
		||||
func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) {
 | 
			
		||||
	skip := (opts.Page - 1) * setting.Git.CommitsRangeSize
 | 
			
		||||
 | 
			
		||||
	stdoutReader, stdoutWriter := io.Pipe()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = stdoutReader.Close()
 | 
			
		||||
@@ -226,8 +224,8 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
 | 
			
		||||
	go func() {
 | 
			
		||||
		stderr := strings.Builder{}
 | 
			
		||||
		gitCmd := NewCommand(repo.Ctx, "rev-list").
 | 
			
		||||
			AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize*opts.Page).
 | 
			
		||||
			AddOptionFormat("--skip=%d", skip)
 | 
			
		||||
			AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
 | 
			
		||||
			AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
 | 
			
		||||
		gitCmd.AddDynamicArguments(opts.Revision)
 | 
			
		||||
 | 
			
		||||
		if opts.Not != "" {
 | 
			
		||||
@@ -521,6 +519,7 @@ func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCommitBranchStart returns the commit where the branch diverged
 | 
			
		||||
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
 | 
			
		||||
	cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
 | 
			
		||||
	cmd.AddDynamicArguments(endCommitID)
 | 
			
		||||
@@ -535,7 +534,8 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
 | 
			
		||||
 | 
			
		||||
	parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'})
 | 
			
		||||
 | 
			
		||||
	var startCommitID string
 | 
			
		||||
	// check the commits one by one until we find a commit contained by another branch
 | 
			
		||||
	// and we think this commit is the divergence point
 | 
			
		||||
	for _, commitID := range parts {
 | 
			
		||||
		branches, err := repo.getBranches(env, string(commitID), 2)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -543,11 +543,9 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
 | 
			
		||||
		}
 | 
			
		||||
		for _, b := range branches {
 | 
			
		||||
			if b != branch {
 | 
			
		||||
				return startCommitID, nil
 | 
			
		||||
				return string(commitID), nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		startCommitID = string(commitID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", nil
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user