Fix modified files list in webhooks when there is a space (#16288)
* Fix modified files list in webhooks when there is a space There is an unfortunate bug with GetCommitFileStatus where files with spaces are misparsed and split at the space. There is a second bug because modern gits detect renames meaning that this function no longer works correctly. There is a third bug in that merge commits don't have their modified files detected correctly. Fix #15865 Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
parent
836884429a
commit
62c278e4ab
2 changed files with 152 additions and 18 deletions
|
@ -15,6 +15,8 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commit represents a git commit.
|
// Commit represents a git commit.
|
||||||
|
@ -432,33 +434,59 @@ func NewCommitFileStatus() *CommitFileStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseCommitFileStatus(fileStatus *CommitFileStatus, stdout io.Reader) {
|
||||||
|
rd := bufio.NewReader(stdout)
|
||||||
|
peek, err := rd.Peek(1)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if peek[0] == '\n' || peek[0] == '\x00' {
|
||||||
|
_, _ = rd.Discard(1)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
modifier, err := rd.ReadSlice('\x00')
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, err := rd.ReadString('\x00')
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file = file[:len(file)-1]
|
||||||
|
switch modifier[0] {
|
||||||
|
case 'A':
|
||||||
|
fileStatus.Added = append(fileStatus.Added, file)
|
||||||
|
case 'D':
|
||||||
|
fileStatus.Removed = append(fileStatus.Removed, file)
|
||||||
|
case 'M':
|
||||||
|
fileStatus.Modified = append(fileStatus.Modified, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetCommitFileStatus returns file status of commit in given repository.
|
// GetCommitFileStatus returns file status of commit in given repository.
|
||||||
func GetCommitFileStatus(repoPath, commitID string) (*CommitFileStatus, error) {
|
func GetCommitFileStatus(repoPath, commitID string) (*CommitFileStatus, error) {
|
||||||
stdout, w := io.Pipe()
|
stdout, w := io.Pipe()
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
fileStatus := NewCommitFileStatus()
|
fileStatus := NewCommitFileStatus()
|
||||||
go func() {
|
go func() {
|
||||||
scanner := bufio.NewScanner(stdout)
|
parseCommitFileStatus(fileStatus, stdout)
|
||||||
for scanner.Scan() {
|
close(done)
|
||||||
fields := strings.Fields(scanner.Text())
|
|
||||||
if len(fields) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch fields[0][0] {
|
|
||||||
case 'A':
|
|
||||||
fileStatus.Added = append(fileStatus.Added, fields[1])
|
|
||||||
case 'D':
|
|
||||||
fileStatus.Removed = append(fileStatus.Removed, fields[1])
|
|
||||||
case 'M':
|
|
||||||
fileStatus.Modified = append(fileStatus.Modified, fields[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
stderr := new(bytes.Buffer)
|
stderr := new(bytes.Buffer)
|
||||||
err := NewCommand("show", "--name-status", "--pretty=format:''", commitID).RunInDirPipeline(repoPath, w, stderr)
|
args := []string{"log", "--name-status", "-c", "--pretty=format:", "--parents", "--no-renames", "-z", "-1", commitID}
|
||||||
|
|
||||||
|
err := NewCommand(args...).RunInDirPipeline(repoPath, w, stderr)
|
||||||
w.Close() // Close writer to exit parsing goroutine
|
w.Close() // Close writer to exit parsing goroutine
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ConcatenateError(err, stderr.String())
|
return nil, ConcatenateError(err, stderr.String())
|
||||||
|
|
|
@ -130,3 +130,109 @@ func TestHasPreviousCommit(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, selfNot)
|
assert.False(t, selfNot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseCommitFileStatus(t *testing.T) {
|
||||||
|
type testcase struct {
|
||||||
|
output string
|
||||||
|
added []string
|
||||||
|
removed []string
|
||||||
|
modified []string
|
||||||
|
}
|
||||||
|
|
||||||
|
kases := []testcase{
|
||||||
|
{
|
||||||
|
// Merge commit
|
||||||
|
output: "MM\x00options/locale/locale_en-US.ini\x00",
|
||||||
|
modified: []string{
|
||||||
|
"options/locale/locale_en-US.ini",
|
||||||
|
},
|
||||||
|
added: []string{},
|
||||||
|
removed: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Spaces commit
|
||||||
|
output: "D\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
|
||||||
|
removed: []string{
|
||||||
|
"b",
|
||||||
|
"b b/b",
|
||||||
|
},
|
||||||
|
modified: []string{},
|
||||||
|
added: []string{
|
||||||
|
"b b/b b/b b/b",
|
||||||
|
"b b/b b/b b/b b/b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// larger commit
|
||||||
|
output: "M\x00go.mod\x00M\x00go.sum\x00M\x00modules/ssh/ssh.go\x00M\x00vendor/github.com/gliderlabs/ssh/circle.yml\x00M\x00vendor/github.com/gliderlabs/ssh/context.go\x00A\x00vendor/github.com/gliderlabs/ssh/go.mod\x00A\x00vendor/github.com/gliderlabs/ssh/go.sum\x00M\x00vendor/github.com/gliderlabs/ssh/server.go\x00M\x00vendor/github.com/gliderlabs/ssh/session.go\x00M\x00vendor/github.com/gliderlabs/ssh/ssh.go\x00M\x00vendor/golang.org/x/sys/unix/mkerrors.sh\x00M\x00vendor/golang.org/x/sys/unix/syscall_darwin.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_linux.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go\x00M\x00vendor/modules.txt\x00",
|
||||||
|
modified: []string{
|
||||||
|
"go.mod",
|
||||||
|
"go.sum",
|
||||||
|
"modules/ssh/ssh.go",
|
||||||
|
"vendor/github.com/gliderlabs/ssh/circle.yml",
|
||||||
|
"vendor/github.com/gliderlabs/ssh/context.go",
|
||||||
|
"vendor/github.com/gliderlabs/ssh/server.go",
|
||||||
|
"vendor/github.com/gliderlabs/ssh/session.go",
|
||||||
|
"vendor/github.com/gliderlabs/ssh/ssh.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/mkerrors.sh",
|
||||||
|
"vendor/golang.org/x/sys/unix/syscall_darwin.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/zerrors_linux.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go",
|
||||||
|
"vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go",
|
||||||
|
"vendor/modules.txt",
|
||||||
|
},
|
||||||
|
added: []string{
|
||||||
|
"vendor/github.com/gliderlabs/ssh/go.mod",
|
||||||
|
"vendor/github.com/gliderlabs/ssh/go.sum",
|
||||||
|
},
|
||||||
|
removed: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// git 1.7.2 adds an unnecessary \x00 on merge commit
|
||||||
|
output: "\x00MM\x00options/locale/locale_en-US.ini\x00",
|
||||||
|
modified: []string{
|
||||||
|
"options/locale/locale_en-US.ini",
|
||||||
|
},
|
||||||
|
added: []string{},
|
||||||
|
removed: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// git 1.7.2 adds an unnecessary \n on normal commit
|
||||||
|
output: "\nD\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
|
||||||
|
removed: []string{
|
||||||
|
"b",
|
||||||
|
"b b/b",
|
||||||
|
},
|
||||||
|
modified: []string{},
|
||||||
|
added: []string{
|
||||||
|
"b b/b b/b b/b",
|
||||||
|
"b b/b b/b b/b b/b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kase := range kases {
|
||||||
|
fileStatus := NewCommitFileStatus()
|
||||||
|
parseCommitFileStatus(fileStatus, strings.NewReader(kase.output))
|
||||||
|
|
||||||
|
assert.Equal(t, kase.added, fileStatus.Added)
|
||||||
|
assert.Equal(t, kase.removed, fileStatus.Removed)
|
||||||
|
assert.Equal(t, kase.modified, fileStatus.Modified)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue