1 Commits

Author SHA256 Message Date
Jan Zerebecki
a6a07f5cd5 Build in obs directly from this repo 2025-04-01 17:23:40 +02:00
1183 changed files with 5202 additions and 21597 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
mock
node_modules node_modules
*.obscpio *.obscpio
autogits-tmp.tar.zst autogits-tmp.tar.zst

View File

@@ -9,7 +9,8 @@
<service name="go_modules" mode="manual"> <service name="go_modules" mode="manual">
<param name="basename">./</param> <param name="basename">./</param>
<param name="compression">zst</param> <param name="compression">zst</param>
<param name="vendorname">vendor</param> <param name="subdir">gitea-events-rabbitmq-publisher</param>
<param name="vendorname">vendor-gitea-events-rabbitmq-publisher</param>
</service> </service>
</services> </services>

View File

@@ -22,8 +22,7 @@ Release: 0
Summary: GitWorkflow utilities Summary: GitWorkflow utilities
License: GPL-2.0-or-later License: GPL-2.0-or-later
URL: https://src.opensuse.org/adamm/autogits URL: https://src.opensuse.org/adamm/autogits
Source1: vendor.tar.zst Source1: vendor-gitea-events-rabbitmq-publisher.tar.zst
BuildRequires: git
BuildRequires: golang-packaging BuildRequires: golang-packaging
BuildRequires: systemd-rpm-macros BuildRequires: systemd-rpm-macros
BuildRequires: zstd BuildRequires: zstd
@@ -33,23 +32,6 @@ BuildRequires: zstd
Git Workflow tooling and utilities enabling automated handing of OBS projects Git Workflow tooling and utilities enabling automated handing of OBS projects
as git repositories as git repositories
%package -n devel-importer
Summary: Imports devel projects from obs to git
%description -n devel-importer
Command-line tool to import devel projects from obs to git
%package -n doc
Summary: Common documentation files
%description -n doc
Common documentation files
%package -n gitea-events-rabbitmq-publisher %package -n gitea-events-rabbitmq-publisher
Summary: Publishes Gitea webhook data via RabbitMQ Summary: Publishes Gitea webhook data via RabbitMQ
@@ -58,119 +40,19 @@ Listens on an HTTP socket and publishes Gitea events on a RabbitMQ instance
with a topic with a topic
<scope>.src.$organization.$webhook_type.[$webhook_action_type] <scope>.src.$organization.$webhook_type.[$webhook_action_type]
%package -n gitea-status-proxy
Summary: gitea-status-proxy
%description -n gitea-status-proxy
%package -n group-review
Summary: Reviews of groups defined in ProjectGit
%description -n group-review
Is used to handle reviews associated with groups defined in the
ProjectGit.
%package -n obs-forward-bot
Summary: obs-forward-bot
%description -n obs-forward-bot
%package -n obs-staging-bot
Summary: Build a PR against a ProjectGit, if review is requested
%description -n obs-staging-bot
Build a PR against a ProjectGit, if review is requested.
%package -n obs-status-service
Summary: Reports build status of OBS service as an easily to produce SVG
%description -n obs-status-service
Reports build status of OBS service as an easily to produce SVG
%package -n workflow-direct
Summary: Keep ProjectGit in sync for a devel project
%description -n workflow-direct
Keep ProjectGit in sync with packages in the organization of a devel project
%package -n workflow-pr
Summary: Keeps ProjectGit PR in-sync with a PackageGit PR
%description -n workflow-pr
Keeps ProjectGit PR in-sync with a PackageGit PR
%prep %prep
cp -r /home/abuild/rpmbuild/SOURCES/* ./ cp -r /home/abuild/rpmbuild/SOURCES/* ./
tar x --zstd -f %{SOURCE1} cd gitea-events-rabbitmq-publisher && tar x --zstd -f %{SOURCE1}
%build %build
go build \
-C devel-importer \
-mod=vendor \
-buildmode=pie
go build \ go build \
-C gitea-events-rabbitmq-publisher \ -C gitea-events-rabbitmq-publisher \
-mod=vendor \ -mod=vendor \
-buildmode=pie -buildmode=pie
go build \
-C gitea_status_proxy \
-mod=vendor \
-buildmode=pie
go build \
-C group-review \
-mod=vendor \
-buildmode=pie
go build \
-C obs-forward-bot \
-mod=vendor \
-buildmode=pie
go build \
-C obs-staging-bot \
-mod=vendor \
-buildmode=pie
go build \
-C obs-status-service \
-mod=vendor \
-buildmode=pie
go build \
-C workflow-direct \
-mod=vendor \
-buildmode=pie
go build \
-C workflow-pr \
-mod=vendor \
-buildmode=pie
%check
# TODO currently needs the source git history, maybe rewrite to create a git repo in the test?
#go test -C common
go test -C group-review
go test -C obs-staging-bot
go test -C obs-status-service
go test -C workflow-direct
# TODO build fails
go test -C workflow-pr
%install %install
install -D -m0755 devel-importer/devel-importer %{buildroot}%{_bindir}/devel-importer
install -D -m0755 gitea-events-rabbitmq-publisher/gitea-events-rabbitmq-publisher %{buildroot}%{_bindir}/gitea-events-rabbitmq-publisher install -D -m0755 gitea-events-rabbitmq-publisher/gitea-events-rabbitmq-publisher %{buildroot}%{_bindir}/gitea-events-rabbitmq-publisher
install -D -m0644 systemd/gitea-events-rabbitmq-publisher.service %{buildroot}%{_unitdir}/gitea-events-rabbitmq-publisher.service install -D -m0644 systemd/gitea-events-rabbitmq-publisher.service %{buildroot}%{_unitdir}/gitea-events-rabbitmq-publisher.service
install -D -m0755 gitea_status_proxy/gitea_status_proxy %{buildroot}%{_bindir}/gitea_status_proxy
install -D -m0755 group-review/group-review %{buildroot}%{_bindir}/group-review
install -D -m0755 obs-forward-bot/obs-forward-bot %{buildroot}%{_bindir}/obs-forward-bot
install -D -m0755 obs-staging-bot/obs-staging-bot %{buildroot}%{_bindir}/obs-staging-bot
install -D -m0755 obs-status-service/obs-status-service %{buildroot}%{_bindir}/obs-status-service
install -D -m0755 workflow-direct/workflow-direct %{buildroot}%{_bindir}/workflow-direct
install -D -m0755 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr
%pre -n gitea-events-rabbitmq-publisher %pre -n gitea-events-rabbitmq-publisher
%service_add_pre gitea-events-rabbitmq-publisher.service %service_add_pre gitea-events-rabbitmq-publisher.service
@@ -184,52 +66,11 @@ install -D -m0755 workflow-pr/workflow-pr
%postun -n gitea-events-rabbitmq-publisher %postun -n gitea-events-rabbitmq-publisher
%service_del_postun gitea-events-rabbitmq-publisher.service %service_del_postun gitea-events-rabbitmq-publisher.service
%files -n devel-importer
%license COPYING
%doc devel-importer/README.md
%{_bindir}/devel-importer
%files -n doc
%license COPYING
%doc doc/README.md
%doc doc/workflows.md
%files -n gitea-events-rabbitmq-publisher %files -n gitea-events-rabbitmq-publisher
%license COPYING %license COPYING
%doc gitea-events-rabbitmq-publisher/README.md %doc gitea-events-rabbitmq-publisher/README.md
%{_bindir}/gitea-events-rabbitmq-publisher %{_bindir}/gitea-events-rabbitmq-publisher
%{_unitdir}/gitea-events-rabbitmq-publisher.service %{_unitdir}/gitea-events-rabbitmq-publisher.service
%files -n gitea-status-proxy %changelog
%license COPYING
%{_bindir}/gitea_status_proxy
%files -n group-review
%license COPYING
%doc group-review/README.md
%{_bindir}/group-review
%files -n obs-forward-bot
%license COPYING
%{_bindir}/obs-forward-bot
%files -n obs-staging-bot
%license COPYING
%doc obs-staging-bot/README.md
%{_bindir}/obs-staging-bot
%files -n obs-status-service
%license COPYING
%doc obs-status-service/README.md
%{_bindir}/obs-status-service
%files -n workflow-direct
%license COPYING
%doc workflow-direct/README.md
%{_bindir}/workflow-direct
%files -n workflow-pr
%license COPYING
%doc workflow-pr/README.md
%{_bindir}/workflow-pr

15
bots-common/Makefile Normal file
View File

@@ -0,0 +1,15 @@
all: build
api.json:
curl -o api.json https://src.opensuse.org/swagger.v1.json
gitea-generated/client/gitea_api_client.go:: api.json
[ -d gitea-generated ] || mkdir gitea-generated
podman run --rm -v $$(pwd):/api ghcr.io/go-swagger/go-swagger generate client -f /api/api.json -t /api/gitea-generated
api: gitea-generated/client/gitea_api_client.go mock_gitea_utils.go
go generate
build: api
go build

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@ type BasicPR struct {
Num int64 Num int64
} }
var validOrgAndRepoRx *regexp.Regexp = regexp.MustCompile("^[A-Za-z0-9_\\.-]+$") var validOrgAndRepoRx *regexp.Regexp = regexp.MustCompile("^[A-Za-z0-9_-]+$")
func parsePrLine(line string) (BasicPR, error) { func parsePrLine(line string) (BasicPR, error) {
var ret BasicPR var ret BasicPR

130
bots-common/config.go Normal file
View File

@@ -0,0 +1,130 @@
package common
/*
* This file is part of Autogits.
*
* Copyright © 2024 SUSE LLC
*
* Autogits is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 2 of the License, or (at your option) any later
* version.
*
* Autogits is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Foobar. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"strings"
)
type ConfigFile struct {
GitProjectName []string
}
type AutogitConfig struct {
Workflows []string // [pr, direct, test]
Organization string
GitProjectName string // Organization/GitProjectName.git is PrjGit
Branch string // branch name of PkgGit that aligns with PrjGit submodules
Reviewers []string // only used by `pr` workflow
}
type AutogitConfigs []*AutogitConfig
func ReadConfig(reader io.Reader) (*ConfigFile, error) {
data, err := io.ReadAll(reader)
if err != nil {
return nil, fmt.Errorf("Error reading config data: %w", err)
}
config := ConfigFile{}
if err := json.Unmarshal(data, &config.GitProjectName); err != nil {
return nil, fmt.Errorf("Error parsing Git Project paths: %w", err)
}
return &config, nil
}
func ReadConfigFile(filename string) (*ConfigFile, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("Cannot open config file for reading. err: %w", err)
}
defer file.Close()
return ReadConfig(file)
}
func ReadWorkflowConfig(gitea Gitea, git_project string) (*AutogitConfig, error) {
hash := strings.Split(git_project, "#")
branch := ""
if len(hash) > 1 {
branch = hash[1]
}
a := strings.Split(hash[0], "/")
prjGitRepo := DefaultGitPrj
switch len(a) {
case 1:
case 2:
prjGitRepo = a[1]
default:
return nil, fmt.Errorf("Missing org/repo in projectgit: %s", git_project)
}
data, _, err := gitea.GetRepositoryFileContent(a[0], prjGitRepo, branch, "workflow.config")
if err != nil {
return nil, fmt.Errorf("Error fetching 'workflow.config': %w", err)
}
var config AutogitConfig
if err := json.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("Error parsing config file: %w", err)
}
config.GitProjectName = a[0] + "/" + prjGitRepo
if len(branch) > 0 {
config.GitProjectName = config.GitProjectName + "#" + branch
}
if len(config.Organization) < 1 {
config.Organization = a[0]
}
log.Println(config)
return &config, nil
}
func ResolveWorkflowConfigs(gitea Gitea, config *ConfigFile) (AutogitConfigs, error) {
configs := make([]*AutogitConfig, 0, len(config.GitProjectName))
for _, git_project := range config.GitProjectName {
c, err := ReadWorkflowConfig(gitea, git_project)
if err != nil {
// can't sync, so ignore for now
log.Println(err)
} else {
configs = append(configs, c)
}
}
return configs, nil
}
func (configs AutogitConfigs) GetPrjGitConfig(org, repo, branch string) *AutogitConfig {
for _, c := range configs {
if c.Organization == org && c.Branch == branch {
return c
}
}
return nil
}

View File

@@ -19,11 +19,9 @@ package common
*/ */
const ( const (
GiteaTokenEnv = "GITEA_TOKEN" GiteaTokenEnv = "GITEA_TOKEN"
ObsUserEnv = "OBS_USER" ObsUserEnv = "OBS_USER"
ObsPasswordEnv = "OBS_PASSWORD" ObsPasswordEnv = "OBS_PASSWORD"
ObsSshkeyEnv = "OBS_SSHKEY"
ObsSshkeyFileEnv = "OBS_SSHKEYFILE"
DefaultGitPrj = "_ObsPrj" DefaultGitPrj = "_ObsPrj"
PrjLinksFile = "links.json" PrjLinksFile = "links.json"
@@ -33,6 +31,3 @@ const (
TopicApp = "src" TopicApp = "src"
) )
// when set, pushing to remote does not happen, and other remote side-effects should also not happen
var IsDryRun bool

View File

@@ -24,11 +24,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"slices"
"strings" "strings"
"sync" "sync"
) )
@@ -44,20 +43,12 @@ type GitStatusLister interface {
GitStatus(cwd string) ([]GitStatusData, error) GitStatus(cwd string) ([]GitStatusData, error)
} }
type GitDiffLister interface {
GitDiff(cwd, base, head string) (string, error)
}
type Git interface { type Git interface {
// error if git, but wrong remote
GitClone(repo, branch, remoteUrl string) (string, error) // clone, or check if path is already checked out remote and force pulls, error otherwise. Return remotename, errror
GitParseCommits(cwd string, commitIDs []string) (parsedCommits []GitCommit, err error) GitParseCommits(cwd string, commitIDs []string) (parsedCommits []GitCommit, err error)
GitCatFile(cwd, commitId, filename string) (data []byte, err error) GitCatFile(cwd, commitId, filename string) (data []byte, err error)
GetPath() string GetPath() string
GitBranchHead(gitDir, branchName string) (string, error) GitBranchHead(gitDir, branchName string) (string, error)
GitRemoteHead(gitDir, remoteName, branchName string) (string, error)
io.Closer io.Closer
GitSubmoduleLister GitSubmoduleLister
@@ -67,16 +58,14 @@ type Git interface {
GitExecOrPanic(cwd string, params ...string) GitExecOrPanic(cwd string, params ...string)
GitExec(cwd string, params ...string) error GitExec(cwd string, params ...string) error
GitExecWithOutput(cwd string, params ...string) (string, error) GitExecWithOutput(cwd string, params ...string) (string, error)
GitDiffLister
} }
type GitHandlerImpl struct { type GitHandlerImpl struct {
DebugLogger bool
GitPath string GitPath string
GitCommiter string GitCommiter string
GitEmail string GitEmail string
lock *sync.Mutex
} }
func (s *GitHandlerImpl) GetPath() string { func (s *GitHandlerImpl) GetPath() string {
@@ -84,88 +73,34 @@ func (s *GitHandlerImpl) GetPath() string {
} }
type GitHandlerGenerator interface { type GitHandlerGenerator interface {
CreateGitHandler(org string) (Git, error) CreateGitHandler(git_author, email, prjName string) (Git, error)
ReadExistingPath(org string) (Git, error) ReadExistingPath(git_author, email, gitPath string) (Git, error)
ReleaseLock(path string)
} }
type gitHandlerGeneratorImpl struct { type GitHandlerGeneratorImpl struct{}
path string
git_author string
email string
lock_lock sync.Mutex func (s *GitHandlerGeneratorImpl) CreateGitHandler(git_author, email, prj_name string) (Git, error) {
lock map[string]*sync.Mutex // per org gitPath, err := os.MkdirTemp("", prj_name)
if err != nil {
return nil, fmt.Errorf("Cannot create temp dir: %w", err)
}
if err = os.Chmod(gitPath, 0700); err != nil {
return nil, fmt.Errorf("Cannot fix permissions of temp dir: %w", err)
}
return s.ReadExistingPath(git_author, email, gitPath)
} }
func AllocateGitWorkTree(basePath, gitAuthor, email string) (*gitHandlerGeneratorImpl, error) { func (*GitHandlerGeneratorImpl) ReadExistingPath(git_author, email, gitPath string) (Git, error) {
if fi, err := os.Stat(basePath); err != nil || !fi.IsDir() {
return nil, fmt.Errorf("Git basepath not a valid directory: %s %w", basePath, err)
}
if fi, err := os.Stat(basePath); err != nil {
if os.IsNotExist(err) {
if err = os.MkdirAll(basePath, 0o700); err != nil {
return nil, fmt.Errorf("Cannot create git directory structure: %s: %w", basePath, err)
}
} else {
return nil, fmt.Errorf("Error checking git directory strcture: %s: %w", basePath, err)
}
} else if !fi.IsDir() {
return nil, fmt.Errorf("Invalid git directory structure: %s != directory", basePath)
}
return &gitHandlerGeneratorImpl{
path: basePath,
git_author: gitAuthor,
email: email,
lock: make(map[string]*sync.Mutex),
}, nil
}
func (s *gitHandlerGeneratorImpl) CreateGitHandler(org string) (Git, error) {
path := path.Join(s.path, org)
if fs, err := os.Stat(path); (err != nil && !os.IsNotExist(err)) || (err == nil && !fs.IsDir()) {
return nil, err
} else if err != nil && os.IsNotExist(err) {
if err := os.MkdirAll(path, 0o777); err != nil && !os.IsExist(err) {
return nil, err
}
}
return s.ReadExistingPath(org)
}
func (s *gitHandlerGeneratorImpl) ReadExistingPath(org string) (Git, error) {
LogDebug("Locking git org:", org)
s.lock_lock.Lock()
defer s.lock_lock.Unlock()
if _, ok := s.lock[org]; !ok {
s.lock[org] = &sync.Mutex{}
}
s.lock[org].Lock()
git := &GitHandlerImpl{ git := &GitHandlerImpl{
GitCommiter: s.git_author, GitCommiter: git_author,
GitEmail: s.email, GitPath: gitPath,
GitPath: path.Join(s.path, org),
lock: s.lock[org],
} }
return git, nil return git, nil
} }
func (s *gitHandlerGeneratorImpl) ReleaseLock(org string) {
m, ok := s.lock[org]
if ok {
LogDebug("Unlocking git org:", org)
m.Unlock()
}
}
//func (h *GitHandler) ProcessBranchList() []string { //func (h *GitHandler) ProcessBranchList() []string {
// if h.HasError() { // if h.HasError() {
// return make([]string, 0) // return make([]string, 0)
@@ -204,101 +139,20 @@ func (refs *GitReferences) addReference(id, branch string) {
refs.refs = append(refs.refs, GitReference{Branch: branch, Id: id}) refs.refs = append(refs.refs, GitReference{Branch: branch, Id: id})
} }
func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error) {
LogDebug("Cloning", remoteUrl, " repo:", repo, " branch:", branch)
remoteUrlComp, err := ParseGitRemoteUrl(remoteUrl)
if err != nil {
return "", fmt.Errorf("Cannot parse remote URL: %w", err)
}
remoteBranch := "HEAD"
if len(branch) == 0 && remoteUrlComp != nil {
branch = remoteUrlComp.Commit
remoteBranch = branch
} else if len(branch) > 0 {
remoteBranch = branch
}
remoteName := remoteUrlComp.RemoteName()
if remoteUrlComp != nil {
LogDebug("Clone", *remoteUrlComp, " -> ", remoteName)
} else {
LogDebug("Clone", "[default] -> ", remoteName)
}
remoteRef := remoteName + "/" + remoteBranch
if fi, err := os.Stat(path.Join(e.GitPath, repo)); os.IsNotExist(err) {
if err = e.GitExec("", "clone", "--origin", remoteName, remoteUrl, repo); err != nil {
return remoteName, err
}
} else if err != nil || !fi.IsDir() {
return remoteName, fmt.Errorf("Clone location not a directory or Stat error: %w", err)
} else {
if u, err := e.GitExecWithOutput(repo, "remote", "get-url", remoteName); err != nil {
e.GitExecOrPanic(repo, "remote", "add", remoteName, remoteUrl)
} else if clonedRemote := strings.TrimSpace(u); clonedRemote != remoteUrl {
e.GitExecOrPanic(repo, "remote", "set-url", remoteName, remoteUrl)
}
// check if we have submodule to deinit
if list, _ := e.GitSubmoduleList(repo, "HEAD"); len(list) > 0 {
e.GitExecOrPanic(repo, "submodule", "deinit", "--all", "--force")
}
e.GitExecOrPanic(repo, "fetch", "--prune", remoteName, remoteBranch)
}
refsBytes, err := os.ReadFile(path.Join(e.GitPath, repo, ".git/refs/remotes", remoteName, "HEAD"))
if err != nil {
LogError("Cannot read HEAD of remote", remoteName)
return remoteName, fmt.Errorf("Cannot read HEAD of remote %s", remoteName)
}
refs := string(refsBytes)
if refs[0:5] != "ref: " {
LogError("Unexpected format of remote HEAD ref:", refs)
return remoteName, fmt.Errorf("Unexpected format of remote HEAD ref: %s", refs)
}
if len(branch) == 0 || branch == "HEAD" {
remoteRef = strings.TrimSpace(refs[5:])
branch = remoteRef[strings.LastIndex(remoteRef, "/")+1:]
LogDebug("remoteRef", remoteRef)
LogDebug("branch", branch)
}
args := []string{"fetch", "--prune", remoteName, branch}
if strings.TrimSpace(e.GitExecWithOutputOrPanic(repo, "rev-parse", "--is-shallow-repository")) == "true" {
args = slices.Insert(args, 1, "--unshallow")
}
e.GitExecOrPanic(repo, args...)
return remoteName, e.GitExec(repo, "checkout", "--track", "-B", branch, remoteRef)
}
func (e *GitHandlerImpl) GitBranchHead(gitDir, branchName string) (string, error) { func (e *GitHandlerImpl) GitBranchHead(gitDir, branchName string) (string, error) {
id, err := e.GitExecWithOutput(gitDir, "show-ref", "--branch", "--hash", branchName) id, err := e.GitExecWithOutput(gitDir, "rev-list", "-1", branchName)
if err != nil { if err != nil {
return "", fmt.Errorf("Can't find default branch: %s", branchName) return "", fmt.Errorf("Can't find default remote branch: %s", branchName)
}
id = strings.TrimSpace(SplitLines(id)[0])
if len(id) < 10 {
return "", fmt.Errorf("Can't find branch: %s", branchName)
}
return id, nil
}
func (e *GitHandlerImpl) GitRemoteHead(gitDir, remote, branchName string) (string, error) {
id, err := e.GitExecWithOutput(gitDir, "show-ref", "--hash", "--verify", "refs/remotes/"+remote+"/"+branchName)
if err != nil {
return "", fmt.Errorf("Can't find default branch: %s", branchName)
} }
return strings.TrimSpace(id), nil return strings.TrimSpace(id), nil
} }
func (e *GitHandlerImpl) Close() error { func (e *GitHandlerImpl) Close() error {
LogDebug("Unlocking git lock") if err := os.RemoveAll(e.GitPath); err != nil {
e.lock.Unlock() return err
}
e.GitPath = ""
return nil return nil
} }
@@ -321,16 +175,14 @@ func (h writeFunc) Close() error {
func (e *GitHandlerImpl) GitExecWithOutputOrPanic(cwd string, params ...string) string { func (e *GitHandlerImpl) GitExecWithOutputOrPanic(cwd string, params ...string) string {
out, err := e.GitExecWithOutput(cwd, params...) out, err := e.GitExecWithOutput(cwd, params...)
if err != nil { if err != nil {
LogError("git command failed:", params, "@", cwd, "err:", err) log.Panicln("git command failed:", params, "@", cwd, "err:", err)
panic(err)
} }
return out return out
} }
func (e *GitHandlerImpl) GitExecOrPanic(cwd string, params ...string) { func (e *GitHandlerImpl) GitExecOrPanic(cwd string, params ...string) {
if err := e.GitExec(cwd, params...); err != nil { if err := e.GitExec(cwd, params...); err != nil {
LogError("git command failed:", params, "@", cwd, "err:", err) log.Panicln("git command failed:", params, "@", cwd, "err:", err)
panic(err)
} }
} }
@@ -350,7 +202,6 @@ func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string
"GIT_COMMITTER_NAME=" + e.GitCommiter, "GIT_COMMITTER_NAME=" + e.GitCommiter,
"EMAIL=not@exist@src.opensuse.org", "EMAIL=not@exist@src.opensuse.org",
"GIT_LFS_SKIP_SMUDGE=1", "GIT_LFS_SKIP_SMUDGE=1",
"GIT_LFS_SKIP_PUSH=1",
"GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes", "GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes",
} }
if len(ExtraGitParams) > 0 { if len(ExtraGitParams) > 0 {
@@ -359,11 +210,17 @@ func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string
cmd.Dir = filepath.Join(e.GitPath, cwd) cmd.Dir = filepath.Join(e.GitPath, cwd)
cmd.Stdin = nil cmd.Stdin = nil
LogDebug("git execute @", cwd, ":", cmd.Args) if e.DebugLogger {
log.Printf("git execute: %#v\n", cmd.Args)
}
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
LogDebug(string(out)) if e.DebugLogger {
log.Println(string(out))
}
if err != nil { if err != nil {
LogError("git", cmd.Args, " error:", err) if e.DebugLogger {
log.Printf(" *** error: %v\n", err)
}
return "", fmt.Errorf("error executing: git %#v \n%s\n err: %w", cmd.Args, out, err) return "", fmt.Errorf("error executing: git %#v \n%s\n err: %w", cmd.Args, out, err)
} }
@@ -499,29 +356,21 @@ func parseGitMsg(data <-chan byte) (GitMsg, error) {
}, nil }, nil
} }
func parseGitCommitHdr(oldHdr [2]string, data <-chan byte) ([2]string, int, error) { func parseGitCommitHdr(data <-chan byte) ([2]string, error) {
hdr := make([]byte, 0, 60) hdr := make([]byte, 0, 60)
val := make([]byte, 0, 1000) val := make([]byte, 0, 1000)
c := <-data c := <-data
size := 1
if c != '\n' { // end of header marker if c != '\n' { // end of header marker
for ; c != ' '; c = <-data { for ; c != ' '; c = <-data {
hdr = append(hdr, c) hdr = append(hdr, c)
size++
}
if size == 1 { // continuation header here
hdr = []byte(oldHdr[0])
val = append([]byte(oldHdr[1]), '\n')
} }
for c := <-data; c != '\n'; c = <-data { for c := <-data; c != '\n'; c = <-data {
val = append(val, c) val = append(val, c)
size++
} }
size++
} }
return [2]string{string(hdr), string(val)}, size, nil return [2]string{string(hdr), string(val)}, nil
} }
func parseGitCommitMsg(data <-chan byte, l int) (string, error) { func parseGitCommitMsg(data <-chan byte, l int) (string, error) {
@@ -531,6 +380,7 @@ func parseGitCommitMsg(data <-chan byte, l int) (string, error) {
msg = append(msg, c) msg = append(msg, c)
l-- l--
} }
// l--
if l != 0 { if l != 0 {
return "", fmt.Errorf("Unexpected data in the git commit msg: l=%d", l) return "", fmt.Errorf("Unexpected data in the git commit msg: l=%d", l)
@@ -550,14 +400,12 @@ func parseGitCommit(data <-chan byte) (GitCommit, error) {
var c GitCommit var c GitCommit
l := hdr.size l := hdr.size
for { for {
var hdr [2]string hdr, err := parseGitCommitHdr(data)
hdr, size, err := parseGitCommitHdr(hdr, data)
if err != nil { if err != nil {
return GitCommit{}, nil return GitCommit{}, nil
} }
l -= size
if size == 1 { if len(hdr[0])+len(hdr[1]) == 0 { // hdr end marker
break break
} }
@@ -565,7 +413,10 @@ func parseGitCommit(data <-chan byte) (GitCommit, error) {
case "tree": case "tree":
c.Tree = hdr[1] c.Tree = hdr[1]
} }
l -= len(hdr[0]) + len(hdr[1]) + 2
} }
l--
c.Msg, err = parseGitCommitMsg(data, l) c.Msg, err = parseGitCommitMsg(data, l)
return c, err return c, err
@@ -602,6 +453,7 @@ func parseTreeEntry(data <-chan byte, hashLen int) (GitTreeEntry, error) {
} }
func parseGitTree(data <-chan byte) (GitTree, error) { func parseGitTree(data <-chan byte) (GitTree, error) {
hdr, err := parseGitMsg(data) hdr, err := parseGitMsg(data)
if err != nil { if err != nil {
return GitTree{}, err return GitTree{}, err
@@ -654,7 +506,7 @@ func (e *GitHandlerImpl) GitParseCommits(cwd string, commitIDs []string) (parsed
var done sync.Mutex var done sync.Mutex
done.Lock() done.Lock()
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)} data_in, data_out := ChanIO{make(chan byte, 256)}, ChanIO{make(chan byte, 70)}
parsedCommits = make([]GitCommit, 0, len(commitIDs)) parsedCommits = make([]GitCommit, 0, len(commitIDs))
go func() { go func() {
@@ -684,16 +536,15 @@ func (e *GitHandlerImpl) GitParseCommits(cwd string, commitIDs []string) (parsed
cmd.Stdout = &data_in cmd.Stdout = &data_in
cmd.Stdin = &data_out cmd.Stdin = &data_out
cmd.Stderr = writeFunc(func(data []byte) (int, error) { cmd.Stderr = writeFunc(func(data []byte) (int, error) {
LogError(string(data)) if e.DebugLogger {
log.Println(string(data))
}
return len(data), nil return len(data), nil
}) })
LogDebug("command run:", cmd.Args) if e.DebugLogger {
if e := cmd.Run(); e != nil { log.Printf("command run: %v\n", cmd.Args)
LogError(e)
close(data_in.ch)
close(data_out.ch)
return nil, e
} }
err = cmd.Run()
done.Lock() done.Lock()
return return
@@ -704,7 +555,7 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
var done sync.Mutex var done sync.Mutex
done.Lock() done.Lock()
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)} data_in, data_out := ChanIO{make(chan byte, 256)}, ChanIO{make(chan byte, 70)}
go func() { go func() {
defer done.Unlock() defer done.Unlock()
@@ -712,27 +563,24 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
data_out.Write([]byte(commitId)) data_out.Write([]byte(commitId))
data_out.ch <- '\x00' data_out.ch <- '\x00'
c, err := parseGitCommit(data_in.ch)
var c GitCommit
c, err = parseGitCommit(data_in.ch)
if err != nil { if err != nil {
LogError("Error parsing git commit:", err) log.Printf("Error parsing git commit: %v\n", err)
return return
} }
data_out.Write([]byte(c.Tree)) data_out.Write([]byte(c.Tree))
data_out.ch <- '\x00' data_out.ch <- '\x00'
tree, err := parseGitTree(data_in.ch)
var tree GitTree
tree, err = parseGitTree(data_in.ch)
if err != nil { if err != nil {
LogError("Error parsing git tree:", err) if e.DebugLogger {
log.Printf("Error parsing git tree: %v\n", err)
}
return return
} }
for _, te := range tree.items { for _, te := range tree.items {
if te.isBlob() && te.name == filename { if te.isBlob() && te.name == filename {
LogInfo("blob", te.hash)
data_out.Write([]byte(te.hash)) data_out.Write([]byte(te.hash))
data_out.ch <- '\x00' data_out.ch <- '\x00'
data, err = parseGitBlob(data_in.ch) data, err = parseGitBlob(data_in.ch)
@@ -740,7 +588,7 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
} }
} }
LogError("file not found:", filename) err = fmt.Errorf("file not found: '%s'", filename)
}() }()
cmd := exec.Command("/usr/bin/git", "cat-file", "--batch", "-Z") cmd := exec.Command("/usr/bin/git", "cat-file", "--batch", "-Z")
@@ -753,29 +601,28 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
cmd.Stdout = &data_in cmd.Stdout = &data_in
cmd.Stdin = &data_out cmd.Stdin = &data_out
cmd.Stderr = writeFunc(func(data []byte) (int, error) { cmd.Stderr = writeFunc(func(data []byte) (int, error) {
LogError(string(data)) if e.DebugLogger {
log.Println(string(data))
}
return len(data), nil return len(data), nil
}) })
LogDebug("command run:", cmd.Args) if e.DebugLogger {
if e := cmd.Run(); e != nil { log.Printf("command run: %v\n", cmd.Args)
LogError(e)
close(data_in.ch)
close(data_out.ch)
return nil, e
} }
err = cmd.Run()
done.Lock() done.Lock()
return return
} }
// return (filename) -> (hash) map for all submodules // return (filename) -> (hash) map for all submodules
// TODO: recursive? map different orgs, not just assume '.' for path
func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleList map[string]string, err error) { func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleList map[string]string, err error) {
var done sync.Mutex var done sync.Mutex
submoduleList = make(map[string]string) submoduleList = make(map[string]string)
done.Lock() done.Lock()
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)} data_in, data_out := ChanIO{make(chan byte, 256)}, ChanIO{make(chan byte, 70)}
LogDebug("Getting submodules for:", commitId)
go func() { go func() {
defer done.Unlock() defer done.Unlock()
@@ -789,32 +636,19 @@ func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleLi
err = fmt.Errorf("Error parsing git commit. Err: %w", err) err = fmt.Errorf("Error parsing git commit. Err: %w", err)
return return
} }
data_out.Write([]byte(c.Tree))
data_out.ch <- '\x00'
var tree GitTree
tree, err = parseGitTree(data_in.ch)
trees := make(map[string]string) if err != nil {
trees[""] = c.Tree err = fmt.Errorf("Error parsing git tree: %w", err)
return
}
for len(trees) > 0 { for _, te := range tree.items {
for p, tree := range trees { if te.isSubmodule() {
delete(trees, p) submoduleList[te.name] = te.hash
data_out.Write([]byte(tree))
data_out.ch <- '\x00'
var tree GitTree
tree, err = parseGitTree(data_in.ch)
if err != nil {
err = fmt.Errorf("Error parsing git tree: %w", err)
return
}
for _, te := range tree.items {
if te.isTree() {
trees[p+te.name+"/"] = te.hash
} else if te.isSubmodule() {
submoduleList[p+te.name] = te.hash
}
}
} }
} }
}() }()
@@ -829,32 +663,34 @@ func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleLi
cmd.Stdout = &data_in cmd.Stdout = &data_in
cmd.Stdin = &data_out cmd.Stdin = &data_out
cmd.Stderr = writeFunc(func(data []byte) (int, error) { cmd.Stderr = writeFunc(func(data []byte) (int, error) {
LogError(string(data)) if e.DebugLogger {
log.Println(string(data))
}
return len(data), nil return len(data), nil
}) })
LogDebug("command run:", cmd.Args) if e.DebugLogger {
if e := cmd.Run(); e != nil { log.Printf("command run: %v\n", cmd.Args)
LogError(e)
close(data_in.ch)
close(data_out.ch)
return submoduleList, e
} }
err = cmd.Run()
done.Lock() done.Lock()
return submoduleList, err return submoduleList, err
} }
func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string) (subCommitId string, valid bool) { func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string) (subCommitId string, valid bool) {
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)} data_in, data_out := ChanIO{make(chan byte, 256)}, ChanIO{make(chan byte, 70)}
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
LogDebug("getting commit id", commitId, "from git at", cwd, "with packageName:", packageName)
if e.DebugLogger {
log.Printf("getting commit id '%s' from git at '%s' with packageName: %s\n", commitId, cwd, packageName)
}
go func() { go func() {
defer func() { defer func() {
if recover() != nil { if recover() != nil {
subCommitId = "" subCommitId = "wrong"
commitId = "ok" commitId = "ok"
valid = false valid = false
} }
@@ -867,16 +703,14 @@ func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string)
data_out.ch <- '\x00' data_out.ch <- '\x00'
c, err := parseGitCommit(data_in.ch) c, err := parseGitCommit(data_in.ch)
if err != nil { if err != nil {
LogError("Error parsing git commit:", err) log.Panicf("Error parsing git commit: %v\n", err)
panic(err)
} }
data_out.Write([]byte(c.Tree)) data_out.Write([]byte(c.Tree))
data_out.ch <- '\x00' data_out.ch <- '\x00'
tree, err := parseGitTree(data_in.ch) tree, err := parseGitTree(data_in.ch)
if err != nil { if err != nil {
LogError("Error parsing git tree:", err) log.Panicf("Error parsing git tree: %v\n", err)
panic(err)
} }
for _, te := range tree.items { for _, te := range tree.items {
@@ -897,19 +731,18 @@ func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string)
cmd.Stdout = &data_in cmd.Stdout = &data_in
cmd.Stdin = &data_out cmd.Stdin = &data_out
cmd.Stderr = writeFunc(func(data []byte) (int, error) { cmd.Stderr = writeFunc(func(data []byte) (int, error) {
LogError(string(data)) log.Println(string(data))
return len(data), nil return len(data), nil
}) })
LogDebug("command run:", cmd.Args) if e.DebugLogger {
if e := cmd.Run(); e != nil { log.Printf("command run: %v\n", cmd.Args)
LogError(e) }
close(data_in.ch) if err := cmd.Run(); err != nil {
close(data_out.ch) log.Printf("Error running command %v, err: %v", cmd.Args, err)
return subCommitId, false
} }
wg.Wait() wg.Wait()
return subCommitId, len(subCommitId) > 0 return subCommitId, len(subCommitId) == len(commitId)
} }
const ( const (
@@ -924,16 +757,6 @@ type GitStatusData struct {
Path string Path string
Status int Status int
States [3]string States [3]string
/*
<sub> A 4 character field describing the submodule state.
"N..." when the entry is not a submodule.
"S<c><m><u>" when the entry is a submodule.
<c> is "C" if the commit changed; otherwise ".".
<m> is "M" if it has tracked changes; otherwise ".".
<u> is "U" if there are untracked changes; otherwise ".".
*/
SubmoduleChanges string
} }
func parseGitStatusHexString(data io.ByteReader) (string, error) { func parseGitStatusHexString(data io.ByteReader) (string, error) {
@@ -956,20 +779,6 @@ func parseGitStatusHexString(data io.ByteReader) (string, error) {
} }
} }
func parseGitStatusString(data io.ByteReader) (string, error) { func parseGitStatusString(data io.ByteReader) (string, error) {
str := make([]byte, 0, 100)
for {
c, err := data.ReadByte()
if err != nil {
return "", errors.New("Unexpected EOF. Expected NUL string term")
}
if c == 0 || c == ' ' {
return string(str), nil
}
str = append(str, c)
}
}
func parseGitStatusStringWithSpace(data io.ByteReader) (string, error) {
str := make([]byte, 0, 100) str := make([]byte, 0, 100)
for { for {
c, err := data.ReadByte() c, err := data.ReadByte()
@@ -1010,7 +819,7 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
return nil, err return nil, err
} }
ret.Status = GitStatus_Modified ret.Status = GitStatus_Modified
ret.Path, err = parseGitStatusStringWithSpace(data) ret.Path, err = parseGitStatusString(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1020,11 +829,11 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
return nil, err return nil, err
} }
ret.Status = GitStatus_Renamed ret.Status = GitStatus_Renamed
ret.Path, err = parseGitStatusStringWithSpace(data) ret.Path, err = parseGitStatusString(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ret.States[0], err = parseGitStatusStringWithSpace(data) ret.States[0], err = parseGitStatusString(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1034,7 +843,7 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
return nil, err return nil, err
} }
ret.Status = GitStatus_Untracked ret.Status = GitStatus_Untracked
ret.Path, err = parseGitStatusStringWithSpace(data) ret.Path, err = parseGitStatusString(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1044,22 +853,15 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
return nil, err return nil, err
} }
ret.Status = GitStatus_Ignored ret.Status = GitStatus_Ignored
ret.Path, err = parseGitStatusStringWithSpace(data) ret.Path, err = parseGitStatusString(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
case 'u': case 'u':
var err error var err error
if err = skipGitStatusEntry(data, 2); err != nil { if err = skipGitStatusEntry(data, 7); err != nil {
return nil, err return nil, err
} }
if ret.SubmoduleChanges, err = parseGitStatusString(data); err != nil {
return nil, err
}
if err = skipGitStatusEntry(data, 4); err != nil {
return nil, err
}
if ret.States[0], err = parseGitStatusHexString(data); err != nil { if ret.States[0], err = parseGitStatusHexString(data); err != nil {
return nil, err return nil, err
} }
@@ -1070,7 +872,7 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
return nil, err return nil, err
} }
ret.Status = GitStatus_Unmerged ret.Status = GitStatus_Unmerged
ret.Path, err = parseGitStatusStringWithSpace(data) ret.Path, err = parseGitStatusString(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1096,7 +898,9 @@ func parseGitStatusData(data io.ByteReader) ([]GitStatusData, error) {
} }
func (e *GitHandlerImpl) GitStatus(cwd string) (ret []GitStatusData, err error) { func (e *GitHandlerImpl) GitStatus(cwd string) (ret []GitStatusData, err error) {
LogDebug("getting git-status()") if e.DebugLogger {
log.Println("getting git-status()")
}
cmd := exec.Command("/usr/bin/git", "status", "--porcelain=2", "-z") cmd := exec.Command("/usr/bin/git", "status", "--porcelain=2", "-z")
cmd.Env = []string{ cmd.Env = []string{
@@ -1106,37 +910,16 @@ func (e *GitHandlerImpl) GitStatus(cwd string) (ret []GitStatusData, err error)
} }
cmd.Dir = filepath.Join(e.GitPath, cwd) cmd.Dir = filepath.Join(e.GitPath, cwd)
cmd.Stderr = writeFunc(func(data []byte) (int, error) { cmd.Stderr = writeFunc(func(data []byte) (int, error) {
LogError(string(data)) log.Println(string(data))
return len(data), nil return len(data), nil
}) })
LogDebug("command run:", cmd.Args) if e.DebugLogger {
log.Printf("command run: %v\n", cmd.Args)
}
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {
LogError("Error running command", cmd.Args, err) log.Printf("Error running command %v, err: %v", cmd.Args, err)
} }
return parseGitStatusData(bufio.NewReader(bytes.NewReader(out))) return parseGitStatusData(bufio.NewReader(bytes.NewReader(out)))
} }
func (e *GitHandlerImpl) GitDiff(cwd, base, head string) (string, error) {
LogDebug("getting diff from", base, "..", head)
cmd := exec.Command("/usr/bin/git", "diff", base+".."+head)
cmd.Env = []string{
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
"GIT_LFS_SKIP_SMUDGE=1",
"GIT_CONFIG_GLOBAL=/dev/null",
}
cmd.Dir = filepath.Join(e.GitPath, cwd)
cmd.Stderr = writeFunc(func(data []byte) (int, error) {
LogError(string(data))
return len(data), nil
})
LogDebug("command run:", cmd.Args)
out, err := cmd.Output()
if err != nil {
LogError("Error running command", cmd.Args, err)
}
return string(out), nil
}

View File

@@ -29,70 +29,6 @@ import (
"testing" "testing"
) )
func TestGitClone(t *testing.T) {
tests := []struct {
name string
repo string
branch string
remoteName string
remoteUrl string
}{
{
name: "Basic clone",
repo: "pkgAclone",
branch: "main",
remoteName: "pkgA_main",
remoteUrl: "/pkgA",
},
{
name: "Remote branch is non-existent",
repo: "pkgAclone",
branch: "main_not_here",
remoteName: "pkgA_main",
remoteUrl: "/pkgA",
},
}
return
execPath, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
d := t.TempDir()
os.Chdir(d)
defer os.Chdir(execPath)
cmd := exec.Command(path.Join(execPath, "test_clone_setup.sh"))
if _, err := cmd.Output(); err != nil {
t.Fatal(err)
}
gh, err := AllocateGitWorkTree(d, "Test", "test@example.com")
if err != nil {
t.Fatal(err)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
g, err := gh.CreateGitHandler("org")
if err != nil {
t.Fatal(err)
}
if _, err := g.GitClone(test.repo, test.branch, "file://"+d+test.remoteUrl); err != nil {
t.Fatal(err)
}
id, err := g.GitBranchHead(test.repo, test.branch)
if err != nil {
t.Fatal(err)
}
t.Fatal(id)
})
}
}
func TestGitMsgParsing(t *testing.T) { func TestGitMsgParsing(t *testing.T) {
t.Run("tree message with size 56", func(t *testing.T) { t.Run("tree message with size 56", func(t *testing.T) {
const hdr = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f98 tree 56\x00" const hdr = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f98 tree 56\x00"
@@ -200,7 +136,7 @@ committer Adam Majer <amajer@suse.com> 1720709149 +0200
}) })
t.Run("parse multiline headers", func(t *testing.T) { t.Run("parse multiline headers", func(t *testing.T) {
const commitData = "cae5831ab48470ff060a5aaa12eb6e5a7acaf91e commit 1492\000" + const commitData = "cae5831ab48470ff060a5aaa12eb6e5a7acaf91e commit 1491\x00" +
`tree 1f9c8fe8099615d6d3921528402ac53f09213b02 `tree 1f9c8fe8099615d6d3921528402ac53f09213b02
parent e08a654fae0ecc91678819e0b62a2e014bad3339 parent e08a654fae0ecc91678819e0b62a2e014bad3339
author Yagiz Nizipli <yagiz@nizipli.com> 1720967314 -0400 author Yagiz Nizipli <yagiz@nizipli.com> 1720967314 -0400
@@ -232,7 +168,7 @@ Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com> Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
Reviewed-By: Richard Lau <rlau@redhat.com> Reviewed-By: Richard Lau <rlau@redhat.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>` + "\000" Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>` + "\x00"
ch := make(chan byte, 5000) ch := make(chan byte, 5000)
for _, b := range []byte(commitData) { for _, b := range []byte(commitData) {
@@ -253,51 +189,6 @@ Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>` + "\000"
} }
}) })
t.Run("parse multiline headers", func(t *testing.T) {
const commitData = "c07c52c57a10fb355956df3caad2986613838f149274fbe312ad76560764829d commit 1150\000" + `tree 3e06b280ea056141ed5e8af9794a41ae5281930c45321803eab53a240cb60044
parent 19362a2cecb1fd25a89e03611d08ac68dcb1732f9dc0a68a40926356787fa4ca
author Adrian Schröter <adrian@suse.de> 1746600403 +0200
committer Adrian Schröter <adrian@suse.de> 1746600403 +0200
gpgsig-sha256 -----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEE1QF1zm/pNbvyhgLFkY2MlUwI22cFAmgbAd0ACgkQkY2MlUwI
22dxtA//eUCzIqxVdaEnOrFeTyxKig/mCOjaAyctmwr0vXUyElRtjXe4TzVG3QtR
uDfhIrKYLZ2tU/0TewTW/4XopWxLuqEzVQLrjuYl7K5P3GoYk52W1yGT0szzm7/i
87j4UdRL9YGU/gYO7nSzstcfTP6AcmYzVUoOnwYR0K2vyOVjO4niL3mFXxLkIgIt
jd82xcE4JpQz9Yjyq2nDdz4A55kLAwsqY+dOct4oC6bZmj1/JeoGQfPvUsvsQgcI
syCHVh0GBxjvSv50V/VPzxQTFMal/TdtvAD4kmP/9RDi/5THzus8Peam8pV0gEIC
Q15ZcuLwIsC9i7ifUDYgzLgBBRdpSI0qji4Y6clWULPVjsyghgyfQw1trBSySpC8
O1XfajUM+rXyrBLP6kzY+zl/zyzRdJ8JhljmC+SmNuyyEB77Hkn83k0f+aBhhqC2
4b3fIsKtwJZ1w6gr6SSz1BottiT9ShQzRaL8iRoF/2l5MkHPR+QFg2J7EIBqCbCQ
hFUjdvWAXQBWkkTQlJmLmJBXDOLQg3o6xCbnZM0gPFjZWE7e3Mpky7H0+xPnoeg9
ukuvkexXQ6yrdiekA7HRLc76Te/I0m7KDOOWZ3rbJV6uH/3ps4FbLQTZO12AtZ6J
n8hYdYfw9yjCxiKUjnEtXtDRe8DJpqv+hO0Wj4MI5gIA2JE2lzY=
=Keg5
-----END PGP SIGNATURE-----
dummy change, don't merge
` + "\000"
ch := make(chan byte)
go func() {
for _, b := range []byte(commitData) {
ch <- b
}
}()
commit, err := parseGitCommit(ch)
if err != nil {
t.Error(err)
}
if commit.Tree != "3e06b280ea056141ed5e8af9794a41ae5281930c45321803eab53a240cb60044" {
t.Errorf("Invalid commit object: %#v", commit)
}
if commit.Msg != "dummy change, don't merge\n" {
t.Errorf("Invalid commit msg: '%s'", commit.Msg)
}
})
t.Run("parse tree object", func(t *testing.T) { t.Run("parse tree object", func(t *testing.T) {
const treeData = "\x31\x61\x30\x35\x64\x62\x37\x33\x36\x39\x33\x37\x34\x33\x30\x65\x31\x38\x64\x66\x34\x33\x61\x32\x37\x61\x39\x38\x30\x30\x31\x30\x31\x32\x65\x31\x65\x64\x32\x30\x34\x38\x32\x39\x38\x36\x37\x31\x32\x38\x66\x32\x63\x65\x38\x34\x30\x36\x62\x35\x63\x66\x63\x39\x20\x74\x72\x65\x65\x20\x32\x30\x35\x00\x34\x30\x30\x30\x30\x20\x62\x6f\x74\x73\x2d\x63\x6f\x6d\x6d\x6f\x6e\x00\x93\x17\xaa\x47\xf6\xea\x37\xe8\xbc\xe2\x80\x77\x57\x90\xf4\xa8\x01\xd7\xe3\x70\x2f\x84\xfb\xe1\xb0\x0e\x4a\x2c\x1c\x75\x2c\x2b\x34\x30\x30\x30\x30\x20\x6f\x62\x73\x2d\x73\x74\x61\x67\x69\x6e\x67\x2d\x62\x6f\x74\x00\x79\x77\x8b\x28\x7d\x37\x10\x59\xb9\x71\x28\x36\xed\x20\x31\x5f\xfb\xe1\xed\xb5\xba\x4f\x5e\xbb\x65\x65\x68\x23\x77\x32\x58\xfe\x34\x30\x30\x30\x30\x20\x70\x72\x2d\x72\x65\x76\x69\x65\x77\x00\x36\x0d\x45\xcb\x76\xb8\x93\xb3\x21\xba\xfa\xd5\x00\x9d\xfc\x59\xab\x88\xc1\x3c\x81\xcb\x48\x5a\xe0\x29\x29\x0f\xe3\x6b\x3c\x5e\x34\x30\x30\x30\x30\x20\x70\x72\x6a\x67\x69\x74\x2d\x75\x70\x64\x61\x74\x65\x72\x00\xb4\x0b\x1c\xf5\xfb\xec\x9a\xb2\x9f\x48\x3e\x21\x18\x0d\x51\xb7\x98\x6e\x21\x99\x74\x84\x67\x71\x41\x24\x42\xfc\xc9\x04\x12\x99\x00" const treeData = "\x31\x61\x30\x35\x64\x62\x37\x33\x36\x39\x33\x37\x34\x33\x30\x65\x31\x38\x64\x66\x34\x33\x61\x32\x37\x61\x39\x38\x30\x30\x31\x30\x31\x32\x65\x31\x65\x64\x32\x30\x34\x38\x32\x39\x38\x36\x37\x31\x32\x38\x66\x32\x63\x65\x38\x34\x30\x36\x62\x35\x63\x66\x63\x39\x20\x74\x72\x65\x65\x20\x32\x30\x35\x00\x34\x30\x30\x30\x30\x20\x62\x6f\x74\x73\x2d\x63\x6f\x6d\x6d\x6f\x6e\x00\x93\x17\xaa\x47\xf6\xea\x37\xe8\xbc\xe2\x80\x77\x57\x90\xf4\xa8\x01\xd7\xe3\x70\x2f\x84\xfb\xe1\xb0\x0e\x4a\x2c\x1c\x75\x2c\x2b\x34\x30\x30\x30\x30\x20\x6f\x62\x73\x2d\x73\x74\x61\x67\x69\x6e\x67\x2d\x62\x6f\x74\x00\x79\x77\x8b\x28\x7d\x37\x10\x59\xb9\x71\x28\x36\xed\x20\x31\x5f\xfb\xe1\xed\xb5\xba\x4f\x5e\xbb\x65\x65\x68\x23\x77\x32\x58\xfe\x34\x30\x30\x30\x30\x20\x70\x72\x2d\x72\x65\x76\x69\x65\x77\x00\x36\x0d\x45\xcb\x76\xb8\x93\xb3\x21\xba\xfa\xd5\x00\x9d\xfc\x59\xab\x88\xc1\x3c\x81\xcb\x48\x5a\xe0\x29\x29\x0f\xe3\x6b\x3c\x5e\x34\x30\x30\x30\x30\x20\x70\x72\x6a\x67\x69\x74\x2d\x75\x70\x64\x61\x74\x65\x72\x00\xb4\x0b\x1c\xf5\xfb\xec\x9a\xb2\x9f\x48\x3e\x21\x18\x0d\x51\xb7\x98\x6e\x21\x99\x74\x84\x67\x71\x41\x24\x42\xfc\xc9\x04\x12\x99\x00"
@@ -352,36 +243,9 @@ dummy change, don't merge
t.Error("expected submodule not found") t.Error("expected submodule not found")
} }
}) })
t.Run("parse nested trees with subtrees", func(t *testing.T) {
const data = "873a323b262ebb3bd77b2592b2e11bdd08dbc721cbf4ac9f97637e58e1fffce7 tree 1083\x00100644\x20\x2Egitattributes\x00\xD8v\xA95\x87\xC1\xA9\xFCPn\xDD\xD4\x13\x9B\x8E\xD2\xCFs\xBD\x11q\x8A\xAE\x8A\x7Cg\xE2C\x14J\x01\xB0100644\x20\x2Egitignore\x00\xC3\xCD\x8En\x887\x3AJ\xA0P\xEEL\xD4\xF5\xD2v\x9C\xA6v\xC5D\x60\x40\x95\xD1\x0B\xA4\xB8\x86\xD4rE100644\x20COPYING\x00\x12\x2A\x28\xC8\xB9\x5D\x9B\x8A\x23\x1F\xE96\x07\x3F\xA9D\x90\xFD\xCE\x2Bi\x2D\x031\x5C\xCC\xC4fx\x00\xC22100644\x20README\x2Emd\x00\x92D\xF7\xFF\x0E0\x5C\xF2\xAC\x0DA\x06\x92\x0B\xD6z\x3CGh\x00y\x7EW1\xB9a\x8Ch\x215Fa100644\x20_service\x00\xC51\xF2\x12\xF3\x24\x9C\xD9\x9F\x0A\x93Mp\x12\xC1\xF7i\x05\x95\xC5Z\x06\x95i\x3Az\xC3\xF59\x7E\xF8\x1B100644\x20autogits\x2Echanges\x00\xF7\x8D\xBF\x0A\xCB\x5D\xB7y\x8C\xA9\x9C\xEB\x92\xAFd\x2C\x98\x23\x0C\x13\x13\xED\xDE\x5D\xBALD6\x3BR\x5B\xCA100644\x20autogits\x2Espec\x00\xD2\xBC\x20v\xD3\xE5F\xCA\xEE\xEA\x18\xC84\x0D\xA7\xCA\xD8O\xF2\x0A\xAB\x40\x2A\xFAL\x3B\xB4\xE6\x11\xE7o\xD140000\x20common\x00\xE2\xC9dg\xD0\x5D\xD1\xF1\x8ARW\xF0\x96\xD6\x29\x2F\x8F\xD9\xC7\x82\x1A\xB7\xAAw\xB0\xCE\xA8\xFE\xC8\xD7D\xF2100755\x20dev_test_helper\x2Esh\x00\xECY\xDD\xB3rz\x9Fh\xD4\x2E\x85\x02\x13\xF8\xFE\xB57\x8B\x1B6\x8E\x09dC\x1E\xE0\x90\x09\x08\xED\xBD_40000\x20devel\x2Dimporter\x00v\x98\x9B\x92\xD8\x24lu\xFC\xB2d\xC9\xCENb\xEE\x0F\x21\x8B\x92\x88\xDBs\xF8\x2E\xA8\xC8W\x1C\x20\xCF\xD440000\x20doc\x00\x8Akyq\xD0\xCF\xB8\x2F\x80Y\x2F\x11\xF0\x14\xA9\xFE\x96\x14\xE0W\x2C\xCF\xB9\x86\x7E\xFDi\xD7\x1F\x08Q\xFB40000\x20gitea\x2Devents\x2Drabbitmq\x2Dpublisher\x00\x5Cb\x3Fh\xA2\x06\x06\x0Cd\x09\xA5\xD9\xF7\x23\x5C\xF85\xF5\xB8\xBE\x7F\xD4O\x25t\xEF\xCC\xAB\x18\x7C\x0C\xF3100644\x20go\x2Emod\x00j\x85\x0B\x03\xC8\x9F\x9F\x0F\xC8\xE0\x8C\xF7\x3D\xC19\xF7\x12gk\xD6\x18JN\x24\xC0\x1C\xBE\x97oY\x02\x8D100644\x20go\x2Esum\x00h\x88\x2E\x27\xED\xD39\x8D\x12\x0F\x7D\x97\xA2\x5DE\xB9\x82o\x0Cu\xF4l\xA17s\x28\x2BQT\xE6\x12\x9040000\x20group\x2Dreview\x00\x7E\x7B\xB42\x0F\x3B\xC9o\x2C\xE79\x1DR\xE2\xE4i\xAE\xF6u\x90\x09\xD8\xC9c\xE7\xF7\xC7\x92\xFB\xD7\xDD140000\x20obs\x2Dstaging\x2Dbot\x00\x12\xE8\xAF\x09\xD4\x5D\x13\x8D\xC9\x0AvPDc\xB6\x7C\xAC4\xD9\xC5\xD4_\x98i\xBE2\xA7\x25aj\xE2k40000\x20obs\x2Dstatus\x2Dservice\x00MATY\xA3\xFA\xED\x05\xBE\xEB\x2B\x07\x9CN\xA9\xF3SB\x22MlV\xA4\x5D\xDA\x0B\x0F\x23\xA1\xA8z\xD740000\x20systemd\x00\x2D\xE2\x03\x7E\xBD\xEB6\x8F\xC5\x0E\x12\xD4\xBD\x97P\xDD\xA2\x92\xCE6n\x08Q\xCA\xE4\x15\x97\x8F\x26V\x3DW100644\x20vendor\x2Etar\x2Ezst\x00\xD9\x2Es\x03I\x91\x22\x24\xC86q\x91\x95\xEF\xA3\xC9\x3C\x06D\x90w\xAD\xCB\xAE\xEEu2i\xCE\x05\x09u40000\x20workflow\x2Ddirect\x00\x94\xDB\xDFc\xB5A\xD5\x16\xB3\xC3ng\x94J\xE7\x101jYF\x15Q\xE97\xCFg\x14\x12\x28\x3A\xFC\xDB40000\x20workflow\x2Dpr\x00\xC1\xD8Z9\x18\x60\xA2\xE2\xEF\xB0\xFC\xD7\x2Ah\xF07\x0D\xEC\x8A7\x7E\x1A\xAAn\x13\x9C\xEC\x05s\xE8\xBDf\x00"
ch := make(chan byte, 2000)
for _, b := range []byte(data) {
ch <- b
}
tree, err := parseGitTree(ch)
if err != nil {
t.Error(err)
}
found := false
for _, item := range tree.items {
t.Log(item)
if item.name == "workflow-pr" && item.hash == "c1d85a391860a2e2efb0fcd72a68f0370dec8a377e1aaa6e139cec0573e8bd66" && item.isTree() {
found = true
break
}
}
if !found {
t.Error("expected submodule not found")
}
})
} }
func TestCommitTreeParsing(t *testing.T) { func TestCommitTreeParsingOfHead(t *testing.T) {
gitDir := t.TempDir() gitDir := t.TempDir()
testDir, _ := os.Getwd() testDir, _ := os.Getwd()
var commitId string var commitId string
@@ -396,58 +260,11 @@ func TestCommitTreeParsing(t *testing.T) {
t.Fatal(err.Error()) t.Fatal(err.Error())
} }
gh, err := AllocateGitWorkTree(gitDir, "", "")
if err != nil {
t.Fatal(err)
}
t.Run("GitCatFile commit", func(t *testing.T) {
h, _ := gh.ReadExistingPath(".")
defer h.Close()
file, err := h.GitCatFile("", commitId, "help")
if err != nil {
t.Error("failed", err)
}
if string(file) != "help\n" {
t.Error("expected 'help\\n' but got", string(file))
}
})
t.Run("GitCatFile commit", func(t *testing.T) {
h, _ := gh.ReadExistingPath(".")
defer h.Close()
file, err := h.GitCatFile("", "HEAD", "help")
if err != nil {
t.Error("failed", err)
}
if string(file) != "help\n" {
t.Error("expected 'help\\n' but got", string(file))
}
})
t.Run("GitCatFile bad commit", func(t *testing.T) {
h, _ := gh.ReadExistingPath(".")
defer h.Close()
file, err := h.GitCatFile("", "518b468f391bf01d5d76d497d7cbecfa8b46d185714cf8745800ae18afb21afd", "help")
if err == nil {
t.Error("expected error, but not nothing")
}
if string(file) != "" {
t.Error("expected 'help\\n' but got", file)
}
})
t.Run("reads HEAD and parses the tree", func(t *testing.T) { t.Run("reads HEAD and parses the tree", func(t *testing.T) {
const nodejs21 = "c678c57007d496a98bec668ae38f2c26a695f94af78012f15d044ccf066ccb41" const nodejs21 = "c678c57007d496a98bec668ae38f2c26a695f94af78012f15d044ccf066ccb41"
h, _ := gh.ReadExistingPath(".") h := GitHandlerImpl{
defer h.Close() GitPath: gitDir,
}
id, ok := h.GitSubmoduleCommitId("", "nodejs21", commitId) id, ok := h.GitSubmoduleCommitId("", "nodejs21", commitId)
if !ok { if !ok {
t.Error("failed parse") t.Error("failed parse")
@@ -458,9 +275,9 @@ func TestCommitTreeParsing(t *testing.T) {
}) })
t.Run("reads README.md", func(t *testing.T) { t.Run("reads README.md", func(t *testing.T) {
h, _ := gh.ReadExistingPath(".") h := GitHandlerImpl{
defer h.Close() GitPath: gitDir,
}
data, err := h.GitCatFile("", commitId, "README.md") data, err := h.GitCatFile("", commitId, "README.md")
if err != nil { if err != nil {
t.Errorf("failed parse: %v", err) t.Errorf("failed parse: %v", err)
@@ -471,8 +288,9 @@ func TestCommitTreeParsing(t *testing.T) {
}) })
t.Run("read HEAD", func(t *testing.T) { t.Run("read HEAD", func(t *testing.T) {
h, _ := gh.ReadExistingPath(".") h := GitHandlerImpl{
defer h.Close() GitPath: gitDir,
}
data, err := h.GitSubmoduleList("", "HEAD") data, err := h.GitSubmoduleList("", "HEAD")
if err != nil { if err != nil {
@@ -555,8 +373,6 @@ func TestGitStatusParse(t *testing.T) {
Path: ".gitmodules", Path: ".gitmodules",
Status: GitStatus_Unmerged, Status: GitStatus_Unmerged,
States: [3]string{"587ec403f01113f2629da538f6e14b84781f70ac59c41aeedd978ea8b1253a76", "d23eb05d9ca92883ab9f4d28f3ec90c05f667f3a5c8c8e291bd65e03bac9ae3c", "087b1d5f22dbf0aa4a879fff27fff03568b334c90daa5f2653f4a7961e24ea33"}, States: [3]string{"587ec403f01113f2629da538f6e14b84781f70ac59c41aeedd978ea8b1253a76", "d23eb05d9ca92883ab9f4d28f3ec90c05f667f3a5c8c8e291bd65e03bac9ae3c", "087b1d5f22dbf0aa4a879fff27fff03568b334c90daa5f2653f4a7961e24ea33"},
SubmoduleChanges: "N...",
}, },
}, },
}, },
@@ -564,13 +380,14 @@ func TestGitStatusParse(t *testing.T) {
name: "Renamed file", name: "Renamed file",
data: []byte("1 M. N... 100644 100644 100644 d23eb05d9ca92883ab9f4d28f3ec90c05f667f3a5c8c8e291bd65e03bac9ae3c 896cd09f36d39e782d66ae32dd5614d4f4d83fc689f132aab2dfc019a9f5b6f3 .gitmodules\x002 R. S... 160000 160000 160000 3befe051a34612530acfa84c736d2454278453ec0f78ec028f25d2980f8c3559 3befe051a34612530acfa84c736d2454278453ec0f78ec028f25d2980f8c3559 R100 pkgQ\x00pkgC\x00"), data: []byte("1 M. N... 100644 100644 100644 d23eb05d9ca92883ab9f4d28f3ec90c05f667f3a5c8c8e291bd65e03bac9ae3c 896cd09f36d39e782d66ae32dd5614d4f4d83fc689f132aab2dfc019a9f5b6f3 .gitmodules\x002 R. S... 160000 160000 160000 3befe051a34612530acfa84c736d2454278453ec0f78ec028f25d2980f8c3559 3befe051a34612530acfa84c736d2454278453ec0f78ec028f25d2980f8c3559 R100 pkgQ\x00pkgC\x00"),
res: []GitStatusData{ res: []GitStatusData{
{ {
Path: "pkgQ", Path: "pkgQ",
Status: GitStatus_Renamed, Status: GitStatus_Renamed,
States: [3]string{"pkgC"}, States: [3]string{"pkgC"},
}, },
{ {
Path: ".gitmodules", Path: ".gitmodules",
Status: GitStatus_Modified, Status: GitStatus_Modified,
}, },
}, },

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