Fix verifyCommits error when push a new branch (#26664)

> ### Description
> If a new branch is pushed, and the repository has a rule that would
require signed commits for the new branch, the commit is rejected with a
500 error regardless of whether it's signed.
> 
> When pushing a new branch, the "old" commit is the empty ID
(0000000000000000000000000000000000000000). verifyCommits has no
provision for this and passes an invalid commit range to git rev-list.
Prior to 1.19 this wasn't an issue because only pre-existing individual
branches could be protected.
> 
> I was able to reproduce with
[try.gitea.io/CraigTest/test](https://try.gitea.io/CraigTest/test),
which is set up with a blanket rule to require commits on all branches.


Fix #25565
Very thanks to @Craig-Holmquist-NTI for reporting the bug and suggesting
an valid solution!

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
CaiCandong
2023-08-30 10:27:53 +08:00
committed by GitHub
parent 508de3a58d
commit 815d267c80
43 changed files with 270 additions and 20 deletions

View File

@ -28,23 +28,31 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []
_ = stdoutWriter.Close()
}()
var command *git.Command
if oldCommitID == git.EmptySHA {
// When creating a new branch, the oldCommitID is empty, by using "newCommitID --not --all":
// List commits that are reachable by following the newCommitID, exclude "all" existing heads/tags commits
// So, it only lists the new commits received, doesn't list the commits already present in the receiving repository
command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(newCommitID).AddArguments("--not", "--all")
} else {
command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(oldCommitID + "..." + newCommitID)
}
// This is safe as force pushes are already forbidden
err = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(oldCommitID + "..." + newCommitID).
Run(&git.RunOpts{
Env: env,
Dir: repo.Path,
Stdout: stdoutWriter,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env)
if err != nil {
log.Error("%v", err)
cancel()
}
_ = stdoutReader.Close()
return err
},
})
err = command.Run(&git.RunOpts{
Env: env,
Dir: repo.Path,
Stdout: stdoutWriter,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env)
if err != nil {
log.Error("%v", err)
cancel()
}
_ = stdoutReader.Close()
return err
},
})
if err != nil && !isErrUnverifiedCommit(err) {
log.Error("Unable to check commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err)
}

View File

@ -0,0 +1,43 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package private
import (
"context"
"testing"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"github.com/stretchr/testify/assert"
)
var testReposDir = "tests/repos/"
func TestVerifyCommits(t *testing.T) {
unittest.PrepareTestEnv(t)
gitRepo, err := git.OpenRepository(context.Background(), testReposDir+"repo1_hook_verification")
defer gitRepo.Close()
assert.NoError(t, err)
testCases := []struct {
base, head string
verified bool
}{
{"72920278f2f999e3005801e5d5b8ab8139d3641c", "d766f2917716d45be24bfa968b8409544941be32", true},
{git.EmptySHA, "93eac826f6188f34646cea81bf426aa5ba7d3bfe", true}, // New branch with verified commit
{"9779d17a04f1e2640583d35703c62460b2d86e0a", "72920278f2f999e3005801e5d5b8ab8139d3641c", false},
{git.EmptySHA, "9ce3f779ae33f31fce17fac3c512047b75d7498b", false}, // New branch with unverified commit
}
for _, tc := range testCases {
err = verifyCommits(tc.base, tc.head, gitRepo, nil)
if tc.verified {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package private
import (
"path/filepath"
"testing"
"code.gitea.io/gitea/models/unittest"
)
func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: filepath.Join("..", ".."),
})
}

View File

@ -0,0 +1 @@
ref: refs/heads/main

View File

@ -0,0 +1,6 @@
[core]
repositoryformatversion = 0
filemode = false
bare = true
symlinks = false
ignorecase = true

View File

@ -0,0 +1 @@
d766f2917716d45be24bfa968b8409544941be32 refs/heads/main

View File

@ -0,0 +1 @@
0000000000000000000000000000000000000000 d766f2917716d45be24bfa968b8409544941be32 Gitea <gitea@fake.local> 1693148474 +0800 push

View File

@ -0,0 +1 @@
0000000000000000000000000000000000000000 d766f2917716d45be24bfa968b8409544941be32 Gitea <gitea@fake.local> 1693148474 +0800 push

View File

@ -0,0 +1,2 @@
x<01><>K
1]<5D><14> <0B><><EFBFBD>A<EFBFBD>S<EFBFBD><53>$<1D>"32 oo<6F><06><>W<EFBFBD><57><EFBFBD><EFBFBD>{<7B>!<21><>`<60><>JC%<25>.<2E> $<07>r]sѱe$<24>m<EFBFBD><6D>M<EFBFBD><4D>)<29><><EFBFBD>(O`<60>btl<>E[:;4<12><>H<>1_<31><5F><EFBFBD>ray<61><79><EFBFBD>l<EFBFBD><6C><EFBFBD><EFBFBD>~<7E><>E<EFBFBD>L@c<><02>Xv<58>M<EFBFBD><4D>":<3A>MۃG<DB83>_}<7D>?<3F>

View File

@ -0,0 +1,2 @@
x<01><>1
!ES{<7B><>AwGGa 9E<39>Qg W<10><><EFBFBD>#<23>A<EFBFBD><0F><><EFBFBD><EFBFBD>Z<18>/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>p<EFBFBD><70>(<28>(<28><><EFBFBD><EFBFBD>Bh<42>ۼ&<26><11><14><><EFBFBD>:pLY`<60><><EFBFBD>U<EFBFBD><55>-<2D>z<EFBFBD><7A><EFBFBD><1A><>\<5C><>ZM:<3A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>xJ/<2F>G}:<3A>3

View File

@ -0,0 +1,3 @@
x<01><>A
<EFBFBD>0E]<5D><14>$<24>L<EFBFBD> <0C>x<EFBFBD>L2<4C>]<5D><><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD>

View File

@ -0,0 +1,3 @@
x<01><>ˮ<EFBFBD>FE3<45>+zn%44<34>!%Qx<51><78>ۀs<>A<EFBFBD>` 8<><38><EFBFBD><EFBFBD>{<7B><>2IM<49>j<EFBFBD><6A><EFBFBD><EFBFBD><EFBFBD>d<EFBFBD><64>f<06><><EFBFBD>2<EFBFBD><32><EFBFBD>"<22>$<24>e<EFBFBD><65>
-(<28> <20><04>!<21><>J"<22>a<EFBFBD><17>@<40>Ba<42><61>Ho3<6F><33>V<EFBFBD><56><04><$<24>/)<29>$J<>JD<04>B<EFBFBD><42>H<EFBFBD><1F><><EFBFBD>{<7B> #<23> RR<52><52>O<EFBFBD><4F>nf<6E><66>F<EFBFBD><46>O<EFBFBD><4F>
<EFBFBD>q[<5B><>2<EFBFBD>̇~<7E><><EFBFBD><EFBFBD><7F><EFBFBD>zjj<6A><6A><EFBFBD><EFBFBD>L<EFBFBD><4C><EFBFBD><EFBFBD>}prm<72>Fqh<71><68> `@ث<><D8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>՘f<D598>?3<>[7<><37><EFBFBD><EFBFBD><EFBFBD><EFBFBD>) ^<5E><>u<EFBFBD>ֿ,<2C><><EFBFBD>l7<6C>z<EFBFBD><06>r|&<10>Ou4<75><34>9<EFBFBD>:<3A><>Qj<51><6A><EFBFBD><15><>1x<31><78><EFBFBD>6<EFBFBD>Q<EFBFBD><51><EFBFBD><EFBFBD>%<25><>t<><74>s<EFBFBD><73><EFBFBD>V<EFBFBD>| (<1C>V<EFBFBD><56>,aL,<2C><>G~<7E><><16><><EFBFBD><EFBFBD><EFBFBD>r<1D><><EFBFBD><EFBFBD>@<40>`<60><><EFBFBD>$[! Xˊep<><70><1E>[8 o<><6F>(<28><><EFBFBD>k<EFBFBD>Z<EFBFBD>γy<CEB3><65><D0B9><EFBFBD><05>Y<EFBFBD><59>k<EFBFBD>d<64><7F>6<EFBFBD>3<EFBFBD>;3<><06> R<>i ދdY<64>Dk91V]/C<>#<23><>&<26><>po<70>F<EFBFBD>b<EFBFBD><62><EFBFBD>}<7D><><EFBFBD><EFBFBD><EFBFBD>uW&]+m xaqd<71>I<EFBFBD>X<EFBFBD><58>3

Some files were not shown because too many files have changed in this diff Show More