forked from adamm/autogits
Compare commits
1 Commits
generate-b
...
build-dire
Author | SHA256 | Date | |
---|---|---|---|
|
a6a07f5cd5
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
mock
|
||||||
node_modules
|
node_modules
|
||||||
*.obscpio
|
*.obscpio
|
||||||
autogits-tmp.tar.zst
|
autogits-tmp.tar.zst
|
||||||
|
3
_service
3
_service
@@ -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>
|
||||||
|
|
||||||
|
165
autogits.spec
165
autogits.spec
@@ -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
15
bots-common/Makefile
Normal 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
@@ -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
130
bots-common/config.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@@ -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
|
|
@@ -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
|
|
||||||
}
|
|
@@ -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
Reference in New Issue
Block a user