messy code ahead. implement new directives (spec_change) and restructure code to prevent import cycles

This commit is contained in:
Mustafa Gezen 2021-02-24 08:27:51 +01:00
parent 2782a66848
commit 01380b9b2d
20 changed files with 1046 additions and 399 deletions

View File

@ -3,10 +3,17 @@ package main
import (
"fmt"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/blob"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/blob/file"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/blob/gcs"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/blob/s3"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"io/ioutil"
"log"
"os"
"os/user"
"path/filepath"
"strings"
@ -28,8 +35,12 @@ var (
rpmPrefix string
importBranchPrefix string
branchPrefix string
singleTag string
noDupMode bool
moduleMode bool
tmpFsMode bool
noStorageDownload bool
noStorageUpload bool
)
var root = &cobra.Command{
@ -45,13 +56,15 @@ func mn(_ *cobra.Command, _ []string) {
log.Fatalf("unsupported upstream version %d", version)
}
var importer internal.ImportMode
var importer data.ImportMode
var blobStorage blob.Storage
if strings.HasPrefix(storageAddr, "gs://") {
blobStorage = gcs.New(strings.Replace(storageAddr, "gs://", "", 1))
} else if strings.HasPrefix(storageAddr, "s3://") {
blobStorage = s3.New(strings.Replace(storageAddr, "s3://", "", 1))
} else if strings.HasPrefix(storageAddr, "file://") {
blobStorage = file.New(strings.Replace(storageAddr, "file://", "", 1))
} else {
log.Fatalf("invalid blob storage")
}
@ -83,7 +96,22 @@ func mn(_ *cobra.Command, _ []string) {
log.Fatalf("could not get git authenticator: %v", err)
}
internal.ProcessRPM(&internal.ProcessData{
fsCreator := func() billy.Filesystem {
return memfs.New()
}
if tmpFsMode {
tmpDir, err := ioutil.TempDir(os.TempDir(), "srpmproc-*")
if err != nil {
log.Fatalf("could not create temp dir: %v", err)
}
log.Printf("using temp dir: %s", tmpDir)
fsCreator = func() billy.Filesystem {
return osfs.New(tmpDir)
}
}
internal.ProcessRPM(&data.ProcessData{
Importer: importer,
RpmLocation: sourceRpmLocation,
UpstreamPrefix: upstreamPrefix,
@ -96,9 +124,14 @@ func mn(_ *cobra.Command, _ []string) {
ModulePrefix: modulePrefix,
ImportBranchPrefix: importBranchPrefix,
BranchPrefix: branchPrefix,
SingleTag: singleTag,
Authenticator: authenticator,
NoDupMode: noDupMode,
ModuleMode: moduleMode,
TmpFsMode: tmpFsMode,
NoStorageDownload: noStorageDownload,
NoStorageUpload: noStorageUpload,
FsCreator: fsCreator,
})
}
@ -120,8 +153,12 @@ func main() {
root.Flags().StringVar(&rpmPrefix, "rpm-prefix", "https://git.centos.org/rpms", "Where to retrieve SRPM content. Only used when source-rpm is not a local file")
root.Flags().StringVar(&importBranchPrefix, "import-branch-prefix", "c", "Import branch prefix")
root.Flags().StringVar(&branchPrefix, "branch-prefix", "r", "Branch prefix (replaces import-branch-prefix)")
root.Flags().StringVar(&singleTag, "single-tag", "", "If set, only this tag is imported")
root.Flags().BoolVar(&noDupMode, "no-dup-mode", false, "If enabled, skips already imported tags")
root.Flags().BoolVar(&moduleMode, "module-mode", false, "If enabled, imports a module instead of a package")
root.Flags().BoolVar(&tmpFsMode, "tmpfs-mode", false, "If enabled, packages are imported and patched but not pushed")
root.Flags().BoolVar(&noStorageDownload, "no-storage-download", false, "If enabled, blobs are always downloaded from upstream")
root.Flags().BoolVar(&noStorageUpload, "no-storage-upload", false, "If enabled, blobs are not uploaded to blob storage")
if err := root.Execute(); err != nil {
log.Fatal(err)

View File

@ -0,0 +1,54 @@
package file
import (
"io/ioutil"
"log"
"os"
"path/filepath"
)
type File struct {
path string
}
func New(path string) *File {
return &File{
path: path,
}
}
func (f *File) Write(path string, content []byte) {
w, err := os.OpenFile(filepath.Join(f.path, path), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0644)
if err != nil {
log.Fatalf("could not open file: %v", err)
}
_, err = w.Write(content)
if err != nil {
log.Fatalf("could not write file to file: %v", err)
}
// Close, just like writing a file.
if err := w.Close(); err != nil {
log.Fatalf("could not close file writer to source: %v", err)
}
}
func (f *File) Read(path string) []byte {
r, err := os.OpenFile(filepath.Join(f.path, path), os.O_RDONLY, 0644)
if err != nil {
return nil
}
body, err := ioutil.ReadAll(r)
if err != nil {
return nil
}
return body
}
func (f *File) Exists(path string) bool {
_, err := os.Stat(filepath.Join(f.path, path))
return !os.IsNotExist(err)
}

32
internal/data/import.go Normal file
View File

@ -0,0 +1,32 @@
package data
import (
"github.com/cavaliercoder/go-rpm"
"github.com/go-git/go-git/v5"
"hash"
)
type ImportMode interface {
RetrieveSource(pd *ProcessData) *ModeData
WriteSource(pd *ProcessData, md *ModeData)
PostProcess(md *ModeData)
ImportName(pd *ProcessData, md *ModeData) string
}
type ModeData struct {
Repo *git.Repository
Worktree *git.Worktree
RpmFile *rpm.PackageFile
FileWrites map[string][]byte
TagBranch string
PushBranch string
Branches []string
SourcesToIgnore []*IgnoredSource
BlobCache map[string][]byte
}
type IgnoredSource struct {
Name string
HashFunction hash.Hash
Expired bool
}

31
internal/data/process.go Normal file
View File

@ -0,0 +1,31 @@
package data
import (
"git.rockylinux.org/release-engineering/public/srpmproc/internal/blob"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
)
type ProcessData struct {
RpmLocation string
UpstreamPrefix string
SshKeyLocation string
SshUser string
Version int
GitCommitterName string
GitCommitterEmail string
Mode int
ModulePrefix string
ImportBranchPrefix string
BranchPrefix string
SingleTag string
Authenticator *ssh.PublicKeys
Importer ImportMode
BlobStorage blob.Storage
NoDupMode bool
ModuleMode bool
TmpFsMode bool
NoStorageDownload bool
NoStorageUpload bool
FsCreator func() billy.Filesystem
}

View File

@ -3,6 +3,7 @@ package directives
import (
"errors"
"fmt"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
srpmprocpb "git.rockylinux.org/release-engineering/public/srpmproc/pb"
"github.com/go-git/go-git/v5"
"io/ioutil"
@ -10,7 +11,7 @@ import (
"path/filepath"
)
func add(cfg *srpmprocpb.Cfg, patchTree *git.Worktree, pushTree *git.Worktree) error {
func add(cfg *srpmprocpb.Cfg, _ *data.ProcessData, _ *data.ModeData, patchTree *git.Worktree, pushTree *git.Worktree) error {
for _, add := range cfg.Add {
filePath := checkAddPrefix(filepath.Base(add.File))

View File

@ -3,11 +3,12 @@ package directives
import (
"errors"
"fmt"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
srpmprocpb "git.rockylinux.org/release-engineering/public/srpmproc/pb"
"github.com/go-git/go-git/v5"
)
func del(cfg *srpmprocpb.Cfg, _ *git.Worktree, pushTree *git.Worktree) error {
func del(cfg *srpmprocpb.Cfg, _ *data.ProcessData, _ *data.ModeData, _ *git.Worktree, pushTree *git.Worktree) error {
for _, del := range cfg.Delete {
filePath := del.File
_, err := pushTree.Filesystem.Stat(filePath)

View File

@ -2,6 +2,7 @@ package directives
import (
"encoding/json"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
srpmprocpb "git.rockylinux.org/release-engineering/public/srpmproc/pb"
"github.com/go-git/go-git/v5"
"log"
@ -19,27 +20,23 @@ func checkAddPrefix(file string) string {
return filepath.Join("SOURCES", file)
}
func Apply(cfg *srpmprocpb.Cfg, patchTree *git.Worktree, pushTree *git.Worktree) {
func Apply(cfg *srpmprocpb.Cfg, pd *data.ProcessData, md *data.ModeData, patchTree *git.Worktree, pushTree *git.Worktree) {
var errs []string
err := replace(cfg, patchTree, pushTree)
if err != nil {
errs = append(errs, err.Error())
directives := []func(*srpmprocpb.Cfg, *data.ProcessData, *data.ModeData, *git.Worktree, *git.Worktree) error{
replace,
del,
add,
patch,
lookaside,
specChange,
}
err = del(cfg, patchTree, pushTree)
if err != nil {
errs = append(errs, err.Error())
}
err = add(cfg, patchTree, pushTree)
if err != nil {
errs = append(errs, err.Error())
}
err = patch(cfg, patchTree, pushTree)
if err != nil {
errs = append(errs, err.Error())
for _, directive := range directives {
err := directive(cfg, pd, md, patchTree, pushTree)
if err != nil {
errs = append(errs, err.Error())
}
}
if len(errs) > 0 {

View File

@ -0,0 +1,11 @@
package directives
import (
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
srpmprocpb "git.rockylinux.org/release-engineering/public/srpmproc/pb"
"github.com/go-git/go-git/v5"
)
func lookaside(cfg *srpmprocpb.Cfg, _ *data.ProcessData, _ *data.ModeData, _ *git.Worktree, pushTree *git.Worktree) error {
return nil
}

View File

@ -4,13 +4,14 @@ import (
"bytes"
"errors"
"fmt"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
srpmprocpb "git.rockylinux.org/release-engineering/public/srpmproc/pb"
"github.com/bluekeyes/go-gitdiff/gitdiff"
"github.com/go-git/go-git/v5"
"log"
)
func patch(cfg *srpmprocpb.Cfg, patchTree *git.Worktree, pushTree *git.Worktree) error {
func patch(cfg *srpmprocpb.Cfg, _ *data.ProcessData, _ *data.ModeData, patchTree *git.Worktree, pushTree *git.Worktree) error {
for _, patch := range cfg.Patch {
patchFile, err := patchTree.Filesystem.Open(patch.File)
if err != nil {

View File

@ -3,13 +3,14 @@ package directives
import (
"errors"
"fmt"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
srpmprocpb "git.rockylinux.org/release-engineering/public/srpmproc/pb"
"github.com/go-git/go-git/v5"
"io/ioutil"
"os"
)
func replace(cfg *srpmprocpb.Cfg, patchTree *git.Worktree, pushTree *git.Worktree) error {
func replace(cfg *srpmprocpb.Cfg, _ *data.ProcessData, _ *data.ModeData, patchTree *git.Worktree, pushTree *git.Worktree) error {
for _, replace := range cfg.Replace {
filePath := checkAddPrefix(replace.File)
stat, err := pushTree.Filesystem.Stat(filePath)

View File

@ -0,0 +1,327 @@
package directives
import (
"errors"
"fmt"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
srpmprocpb "git.rockylinux.org/release-engineering/public/srpmproc/pb"
"github.com/go-git/go-git/v5"
"io/ioutil"
"math"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
type sourcePatchOperationInLoopRequest struct {
cfg *srpmprocpb.Cfg
field string
value *string
longestField int
lastNum *int
in *bool
expectedField string
}
type sourcePatchOperationAfterLoopRequest struct {
cfg *srpmprocpb.Cfg
inLoopNum int
lastNum *int
longestField int
newLines *[]string
in *bool
expectedField string
operation srpmprocpb.SpecChange_FileOperation_Type
}
func sourcePatchOperationInLoop(req *sourcePatchOperationInLoopRequest) error {
if strings.HasPrefix(req.field, req.expectedField) {
for _, file := range req.cfg.SpecChange.File {
if file.Type != srpmprocpb.SpecChange_FileOperation_Source {
continue
}
switch file.Mode.(type) {
case *srpmprocpb.SpecChange_FileOperation_Delete:
if file.Name == *req.value {
*req.value = ""
}
break
}
}
sourceNum, err := strconv.Atoi(strings.Split(req.field, req.expectedField)[1])
if err != nil {
return errors.New(fmt.Sprintf("INVALID_%s_NUM", strings.ToUpper(req.expectedField)))
}
*req.lastNum = sourceNum
}
return nil
}
func sourcePatchOperationAfterLoop(req *sourcePatchOperationAfterLoopRequest) (bool, error) {
if req.inLoopNum == *req.lastNum && *req.in {
for _, file := range req.cfg.SpecChange.File {
if file.Type != req.operation {
continue
}
switch file.Mode.(type) {
case *srpmprocpb.SpecChange_FileOperation_Add:
field := fmt.Sprintf("%s%d", req.expectedField, *req.lastNum+1)
spaces := calculateSpaces(req.longestField, len(field))
*req.newLines = append(*req.newLines, fmt.Sprintf("%s:%s%s", field, spaces, file.Name))
*req.lastNum++
break
}
}
*req.in = false
return true, nil
}
return false, nil
}
func calculateSpaces(longestField int, fieldLength int) string {
return strings.Repeat(" ", longestField+8-fieldLength)
}
func searchAndReplaceLine(line string, sar []*srpmprocpb.SpecChange_SearchAndReplaceOperation) string {
for _, searchAndReplace := range sar {
switch searchAndReplace.Identifier.(type) {
case *srpmprocpb.SpecChange_SearchAndReplaceOperation_Any:
line = strings.Replace(line, searchAndReplace.Find, searchAndReplace.Replace, int(searchAndReplace.N))
break
case *srpmprocpb.SpecChange_SearchAndReplaceOperation_StartsWith:
if strings.HasPrefix(line, searchAndReplace.Find) {
line = strings.Replace(line, searchAndReplace.Find, searchAndReplace.Replace, int(searchAndReplace.N))
}
break
case *srpmprocpb.SpecChange_SearchAndReplaceOperation_EndsWith:
if strings.HasSuffix(line, searchAndReplace.Find) {
line = strings.Replace(line, searchAndReplace.Find, searchAndReplace.Replace, int(searchAndReplace.N))
}
break
}
}
return line
}
func specChange(cfg *srpmprocpb.Cfg, pd *data.ProcessData, md *data.ModeData, _ *git.Worktree, pushTree *git.Worktree) error {
// no spec change operations present
// skip parsing spec
if cfg.SpecChange == nil {
return nil
}
specFiles, err := pushTree.Filesystem.ReadDir("SPECS")
if err != nil {
return errors.New("COULD_NOT_READ_SPECS_DIR")
}
if len(specFiles) != 1 {
return errors.New("ONLY_ONE_SPEC_FILE_IS_SUPPORTED")
}
filePath := filepath.Join("SPECS", specFiles[0].Name())
stat, err := pushTree.Filesystem.Stat(filePath)
if err != nil {
return errors.New("COULD_NOT_STAT_SPEC_FILE")
}
specFile, err := pushTree.Filesystem.OpenFile(filePath, os.O_RDONLY, 0644)
if err != nil {
return errors.New("COULD_NOT_READ_SPEC_FILE")
}
specBts, err := ioutil.ReadAll(specFile)
if err != nil {
return errors.New("COULD_NOT_READ_ALL_BYTES")
}
specStr := string(specBts)
lines := strings.Split(specStr, "\n")
var newLines []string
lastSourceNum := 0
lastPatchNum := 0
inSources := false
inPatches := false
inChangelog := false
lastSource := ""
lastPatch := ""
version := ""
importNameSplit := strings.SplitN(pd.Importer.ImportName(pd, md), "-", 2)
if len(importNameSplit) == 2 {
versionSplit := strings.SplitN(importNameSplit[1], ".el", 2)
if len(versionSplit) == 2 {
version = versionSplit[0]
}
}
fieldValueRegex := regexp.MustCompile("^[A-Z].+:")
longestField := 0
for _, line := range lines {
if fieldValueRegex.MatchString(line) {
fieldValue := strings.SplitN(line, ":", 2)
field := strings.TrimSpace(fieldValue[0])
longestField = int(math.Max(float64(len(field)), float64(longestField)))
if strings.HasPrefix(field, "Source") {
lastSource = field
}
if strings.HasPrefix(field, "Patch") {
lastPatch = field
}
}
}
for _, line := range lines {
inLoopSourceNum := lastSourceNum
inLoopPatchNum := lastPatchNum
prefixLine := strings.TrimSpace(line)
if fieldValueRegex.MatchString(line) {
line = searchAndReplaceLine(line, cfg.SpecChange.SearchAndReplace)
fieldValue := strings.SplitN(line, ":", 2)
field := strings.TrimSpace(fieldValue[0])
value := strings.TrimSpace(fieldValue[1])
if field == lastSource {
inSources = true
} else if field == lastPatch {
inPatches = true
}
if field == "Version" && version == "" {
version = value
}
for _, searchAndReplace := range cfg.SpecChange.SearchAndReplace {
switch identifier := searchAndReplace.Identifier.(type) {
case *srpmprocpb.SpecChange_SearchAndReplaceOperation_Field:
if field == identifier.Field {
value = strings.Replace(value, searchAndReplace.Find, searchAndReplace.Replace, int(searchAndReplace.N))
}
break
}
}
spaces := calculateSpaces(longestField, len(field))
err := sourcePatchOperationInLoop(&sourcePatchOperationInLoopRequest{
cfg: cfg,
field: field,
value: &value,
lastNum: &lastSourceNum,
longestField: longestField,
expectedField: "Source",
in: &inSources,
})
if err != nil {
return err
}
err = sourcePatchOperationInLoop(&sourcePatchOperationInLoopRequest{
cfg: cfg,
field: field,
value: &value,
longestField: longestField,
lastNum: &lastPatchNum,
in: &inPatches,
expectedField: "Patch",
})
if err != nil {
return err
}
if value != "" {
newLines = append(newLines, fmt.Sprintf("%s:%s%s", field, spaces, value))
}
} else {
executed, err := sourcePatchOperationAfterLoop(&sourcePatchOperationAfterLoopRequest{
cfg: cfg,
inLoopNum: inLoopSourceNum,
lastNum: &lastSourceNum,
longestField: longestField,
newLines: &newLines,
expectedField: "Source",
in: &inSources,
operation: srpmprocpb.SpecChange_FileOperation_Source,
})
if err != nil {
return err
}
if executed && !strings.Contains(specStr, "Patch") {
newLines = append(newLines, "")
inPatches = true
}
executed, err = sourcePatchOperationAfterLoop(&sourcePatchOperationAfterLoopRequest{
cfg: cfg,
inLoopNum: inLoopPatchNum,
lastNum: &lastPatchNum,
longestField: longestField,
newLines: &newLines,
expectedField: "Patch",
in: &inPatches,
operation: srpmprocpb.SpecChange_FileOperation_Patch,
})
if err != nil {
return err
}
if executed && !strings.Contains(specStr, "%changelog") {
newLines = append(newLines, "")
newLines = append(newLines, "%changelog")
inChangelog = true
}
if inChangelog {
now := time.Now().Format("Mon Jan 02 2006")
for _, changelog := range cfg.SpecChange.Changelog {
newLines = append(newLines, fmt.Sprintf("* %s %s <%s> - %s", now, changelog.AuthorName, changelog.AuthorEmail, version))
for _, msg := range changelog.Message {
newLines = append(newLines, fmt.Sprintf("- %s", msg))
}
newLines = append(newLines, "")
}
inChangelog = false
} else {
line = searchAndReplaceLine(line, cfg.SpecChange.SearchAndReplace)
}
if strings.HasPrefix(prefixLine, "%changelog") {
inChangelog = true
}
newLines = append(newLines, line)
}
}
err = pushTree.Filesystem.Remove(filePath)
if err != nil {
return errors.New(fmt.Sprintf("COULD_NOT_REMOVE_OLD_SPEC_FILE:%s", filePath))
}
f, err := pushTree.Filesystem.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, stat.Mode())
if err != nil {
return errors.New(fmt.Sprintf("COULD_NOT_OPEN_REPLACEMENT_SPEC_FILE:%s", filePath))
}
_, err = f.Write([]byte(strings.Join(newLines, "\n")))
if err != nil {
return errors.New("COULD_NOT_WRITE_NEW_SPEC_FILE")
}
return nil
}

View File

@ -2,6 +2,7 @@ package internal
import (
"fmt"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
@ -38,15 +39,15 @@ func (p remoteTargetSlice) Swap(i, j int) {
type GitMode struct{}
func (g *GitMode) RetrieveSource(pd *ProcessData) *modeData {
func (g *GitMode) RetrieveSource(pd *data.ProcessData) *data.ModeData {
repo, err := git.Init(memory.NewStorage(), memfs.New())
if err != nil {
log.Fatalf("could not init git repo: %v", err)
log.Fatalf("could not init git Repo: %v", err)
}
w, err := repo.Worktree()
if err != nil {
log.Fatalf("could not get worktree: %v", err)
log.Fatalf("could not get Worktree: %v", err)
}
refspec := config.RefSpec("+refs/heads/*:refs/remotes/*")
@ -116,17 +117,17 @@ func (g *GitMode) RetrieveSource(pd *ProcessData) *modeData {
sortedBranches = append(sortedBranches, branch.remote)
}
return &modeData{
repo: repo,
worktree: w,
rpmFile: createPackageFile(filepath.Base(pd.RpmLocation)),
fileWrites: nil,
branches: sortedBranches,
return &data.ModeData{
Repo: repo,
Worktree: w,
RpmFile: createPackageFile(filepath.Base(pd.RpmLocation)),
FileWrites: nil,
Branches: sortedBranches,
}
}
func (g *GitMode) WriteSource(pd *ProcessData, md *modeData) {
remote, err := md.repo.Remote("upstream")
func (g *GitMode) WriteSource(pd *data.ProcessData, md *data.ModeData) {
remote, err := md.Repo.Remote("upstream")
if err != nil {
log.Fatalf("could not get upstream remote: %v", err)
}
@ -134,13 +135,13 @@ func (g *GitMode) WriteSource(pd *ProcessData, md *modeData) {
var refspec config.RefSpec
var branchName string
if strings.HasPrefix(md.tagBranch, "refs/heads") {
refspec = config.RefSpec(fmt.Sprintf("+%s:%s", md.tagBranch, md.tagBranch))
branchName = strings.TrimPrefix(md.tagBranch, "refs/heads/")
if strings.HasPrefix(md.TagBranch, "refs/heads") {
refspec = config.RefSpec(fmt.Sprintf("+%s:%s", md.TagBranch, md.TagBranch))
branchName = strings.TrimPrefix(md.TagBranch, "refs/heads/")
} else {
match := tagImportRegex.FindStringSubmatch(md.tagBranch)
match := tagImportRegex.FindStringSubmatch(md.TagBranch)
branchName = match[2]
refspec = config.RefSpec(fmt.Sprintf("+refs/heads/%s:%s", branchName, md.tagBranch))
refspec = config.RefSpec(fmt.Sprintf("+refs/heads/%s:%s", branchName, md.TagBranch))
}
log.Printf("checking out upstream refspec %s", refspec)
err = remote.Fetch(&git.FetchOptions{
@ -153,20 +154,20 @@ func (g *GitMode) WriteSource(pd *ProcessData, md *modeData) {
log.Fatalf("could not fetch upstream: %v", err)
}
err = md.worktree.Checkout(&git.CheckoutOptions{
Branch: plumbing.ReferenceName(md.tagBranch),
err = md.Worktree.Checkout(&git.CheckoutOptions{
Branch: plumbing.ReferenceName(md.TagBranch),
Force: true,
})
if err != nil {
log.Fatalf("could not checkout source from git: %v", err)
}
_, err = md.worktree.Add(".")
_, err = md.Worktree.Add(".")
if err != nil {
log.Fatalf("could not add worktree: %v", err)
log.Fatalf("could not add Worktree: %v", err)
}
metadataFile, err := md.worktree.Filesystem.Open(fmt.Sprintf(".%s.metadata", md.rpmFile.Name()))
metadataFile, err := md.Worktree.Filesystem.Open(fmt.Sprintf(".%s.metadata", md.RpmFile.Name()))
if err != nil {
log.Printf("warn: could not open metadata file, so skipping: %v", err)
return
@ -194,16 +195,16 @@ func (g *GitMode) WriteSource(pd *ProcessData, md *modeData) {
var body []byte
if md.blobCache[hash] != nil {
body = md.blobCache[hash]
if md.BlobCache[hash] != nil {
body = md.BlobCache[hash]
log.Printf("retrieving %s from cache", hash)
} else {
fromBlobStorage := pd.BlobStorage.Read(hash)
if fromBlobStorage != nil {
if fromBlobStorage != nil && !pd.NoStorageDownload {
body = fromBlobStorage
log.Printf("downloading %s from blob storage", hash)
} else {
url := fmt.Sprintf("https://git.centos.org/sources/%s/%s/%s", md.rpmFile.Name(), branchName, hash)
url := fmt.Sprintf("https://git.centos.org/sources/%s/%s/%s", md.RpmFile.Name(), branchName, hash)
log.Printf("downloading %s", url)
req, err := http.NewRequest("GET", url, nil)
@ -227,10 +228,10 @@ func (g *GitMode) WriteSource(pd *ProcessData, md *modeData) {
}
}
md.blobCache[hash] = body
md.BlobCache[hash] = body
}
f, err := md.worktree.Filesystem.Create(path)
f, err := md.Worktree.Filesystem.Create(path)
if err != nil {
log.Fatalf("could not open file pointer: %v", err)
}
@ -240,9 +241,9 @@ func (g *GitMode) WriteSource(pd *ProcessData, md *modeData) {
log.Fatal("checksum in metadata does not match dist-git file")
}
md.sourcesToIgnore = append(md.sourcesToIgnore, &ignoredSource{
name: path,
hashFunction: hasher,
md.SourcesToIgnore = append(md.SourcesToIgnore, &data.IgnoredSource{
Name: path,
HashFunction: hasher,
})
_, err = f.Write(body)
@ -253,28 +254,28 @@ func (g *GitMode) WriteSource(pd *ProcessData, md *modeData) {
}
}
func (g *GitMode) PostProcess(md *modeData) {
for _, source := range md.sourcesToIgnore {
_, err := md.worktree.Filesystem.Stat(source.name)
func (g *GitMode) PostProcess(md *data.ModeData) {
for _, source := range md.SourcesToIgnore {
_, err := md.Worktree.Filesystem.Stat(source.Name)
if err == nil {
err := md.worktree.Filesystem.Remove(source.name)
err := md.Worktree.Filesystem.Remove(source.Name)
if err != nil {
log.Fatalf("could not remove dist-git file: %v", err)
}
}
}
_, err := md.worktree.Add(".")
_, err := md.Worktree.Add(".")
if err != nil {
log.Fatalf("could not add git sources: %v", err)
}
}
func (g *GitMode) ImportName(_ *ProcessData, md *modeData) string {
if tagImportRegex.MatchString(md.tagBranch) {
match := tagImportRegex.FindStringSubmatch(md.tagBranch)
func (g *GitMode) ImportName(_ *data.ProcessData, md *data.ModeData) string {
if tagImportRegex.MatchString(md.TagBranch) {
match := tagImportRegex.FindStringSubmatch(md.TagBranch)
return match[3]
}
return strings.TrimPrefix(md.tagBranch, "refs/heads/")
return strings.TrimPrefix(md.TagBranch, "refs/heads/")
}

View File

@ -6,7 +6,7 @@ import (
// TODO: ugly hack, should create an interface
// since GitMode does not parse an RPM file, we just mimick
// the headers of an actual file to reuse rpmFile.Name()
// the headers of an actual file to reuse RpmFile.Name()
func createPackageFile(name string) *rpm.PackageFile {
return &rpm.PackageFile{
Lead: rpm.Lead{},

View File

@ -1,8 +1 @@
package internal
type ImportMode interface {
RetrieveSource(pd *ProcessData) *modeData
WriteSource(pd *ProcessData, md *modeData)
PostProcess(md *modeData)
ImportName(pd *ProcessData, md *modeData) string
}

View File

@ -1,12 +1,11 @@
package internal
import (
"bytes"
"fmt"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/directives"
"git.rockylinux.org/release-engineering/public/srpmproc/modulemd"
srpmprocpb "git.rockylinux.org/release-engineering/public/srpmproc/pb"
"github.com/bluekeyes/go-gitdiff/gitdiff"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
@ -19,107 +18,7 @@ import (
"strings"
)
func srpmPatches(patchTree *git.Worktree, pushTree *git.Worktree) {
// check SRPM patches
_, err := patchTree.Filesystem.Stat("ROCKY/SRPM")
if err == nil {
// iterate through patches
infos, err := patchTree.Filesystem.ReadDir("ROCKY/SRPM")
if err != nil {
log.Fatalf("could not walk patches: %v", err)
}
for _, info := range infos {
// can only process .patch files
if !strings.HasSuffix(info.Name(), ".patch") {
continue
}
log.Printf("applying %s", info.Name())
filePath := filepath.Join("ROCKY/SRPM", info.Name())
patch, err := patchTree.Filesystem.Open(filePath)
if err != nil {
log.Fatalf("could not open patch file %s: %v", info.Name(), err)
}
files, _, err := gitdiff.Parse(patch)
if err != nil {
log.Fatalf("could not parse patch file: %v", err)
}
for _, patchedFile := range files {
srcPath := patchedFile.NewName
if !strings.HasPrefix(srcPath, "SPECS") {
srcPath = filepath.Join("SOURCES", patchedFile.NewName)
}
var output bytes.Buffer
if !patchedFile.IsDelete && !patchedFile.IsNew {
patchSubjectFile, err := pushTree.Filesystem.Open(srcPath)
if err != nil {
log.Fatalf("could not open patch subject: %v", err)
}
err = gitdiff.NewApplier(patchSubjectFile).ApplyFile(&output, patchedFile)
if err != nil {
log.Fatalf("could not apply patch: %v", err)
}
}
oldName := filepath.Join("SOURCES", patchedFile.OldName)
_ = pushTree.Filesystem.Remove(oldName)
_ = pushTree.Filesystem.Remove(srcPath)
if patchedFile.IsNew {
newFile, err := pushTree.Filesystem.Create(srcPath)
if err != nil {
log.Fatalf("could not create new file: %v", err)
}
err = gitdiff.NewApplier(newFile).ApplyFile(&output, patchedFile)
if err != nil {
log.Fatalf("could not apply patch: %v", err)
}
_, err = newFile.Write(output.Bytes())
if err != nil {
log.Fatalf("could not write post-patch file: %v", err)
}
_, err = pushTree.Add(srcPath)
if err != nil {
log.Fatalf("could not add file %s to git: %v", srcPath, err)
}
log.Printf("git add %s", srcPath)
} else if !patchedFile.IsDelete {
newFile, err := pushTree.Filesystem.Create(srcPath)
if err != nil {
log.Fatalf("could not create post-patch file: %v", err)
}
_, err = newFile.Write(output.Bytes())
if err != nil {
log.Fatalf("could not write post-patch file: %v", err)
}
_, err = pushTree.Add(srcPath)
if err != nil {
log.Fatalf("could not add file %s to git: %v", srcPath, err)
}
log.Printf("git add %s", srcPath)
} else {
_, err = pushTree.Remove(oldName)
if err != nil {
log.Fatalf("could not remove file %s to git: %v", oldName, err)
}
log.Printf("git rm %s", oldName)
}
}
_, err = pushTree.Add(filePath)
if err != nil {
log.Fatalf("could not add file %s to git: %v", filePath, err)
}
log.Printf("git add %s", filePath)
}
}
}
func cfgPatches(patchTree *git.Worktree, pushTree *git.Worktree) {
func cfgPatches(pd *data.ProcessData, md *data.ModeData, patchTree *git.Worktree, pushTree *git.Worktree) {
// check CFG patches
_, err := patchTree.Filesystem.Stat("ROCKY/CFG")
if err == nil {
@ -152,32 +51,31 @@ func cfgPatches(patchTree *git.Worktree, pushTree *git.Worktree) {
log.Fatalf("could not unmarshal cfg file: %v", err)
}
directives.Apply(&cfg, patchTree, pushTree)
directives.Apply(&cfg, pd, md, patchTree, pushTree)
}
}
}
func applyPatches(patchTree *git.Worktree, pushTree *git.Worktree) {
func applyPatches(pd *data.ProcessData, md *data.ModeData, patchTree *git.Worktree, pushTree *git.Worktree) {
// check if patches exist
_, err := patchTree.Filesystem.Stat("ROCKY")
if err == nil {
srpmPatches(patchTree, pushTree)
cfgPatches(patchTree, pushTree)
cfgPatches(pd, md, patchTree, pushTree)
}
}
func executePatchesRpm(pd *ProcessData, md *modeData) {
func executePatchesRpm(pd *data.ProcessData, md *data.ModeData) {
// fetch patch repository
repo, err := git.Init(memory.NewStorage(), memfs.New())
if err != nil {
log.Fatalf("could not create new dist repo: %v", err)
log.Fatalf("could not create new dist Repo: %v", err)
}
w, err := repo.Worktree()
if err != nil {
log.Fatalf("could not get dist worktree: %v", err)
log.Fatalf("could not get dist Worktree: %v", err)
}
remoteUrl := fmt.Sprintf("%s/patch/%s.git", pd.UpstreamPrefix, md.rpmFile.Name())
remoteUrl := fmt.Sprintf("%s/patch/%s.git", pd.UpstreamPrefix, md.RpmFile.Name())
refspec := config.RefSpec(fmt.Sprintf("+refs/heads/*:refs/remotes/origin/*"))
_, err = repo.CreateRemote(&config.RemoteConfig{
@ -195,12 +93,12 @@ func executePatchesRpm(pd *ProcessData, md *modeData) {
Auth: pd.Authenticator,
})
refName := plumbing.NewBranchReferenceName(md.pushBranch)
refName := plumbing.NewBranchReferenceName(md.PushBranch)
log.Printf("set reference to ref: %s", refName)
if err != nil {
// no patches active
log.Println("info: patch repo not found")
log.Println("info: patch Repo not found")
return
} else {
err = w.Checkout(&git.CheckoutOptions{
@ -209,28 +107,28 @@ func executePatchesRpm(pd *ProcessData, md *modeData) {
})
// common patches found, apply them
if err == nil {
applyPatches(w, md.worktree)
applyPatches(pd, md, w, md.Worktree)
} else {
log.Println("info: no common patches found")
}
err = w.Checkout(&git.CheckoutOptions{
Branch: plumbing.NewRemoteReferenceName("origin", md.pushBranch),
Branch: plumbing.NewRemoteReferenceName("origin", md.PushBranch),
Force: true,
})
// branch specific patches found, apply them
if err == nil {
applyPatches(w, md.worktree)
applyPatches(pd, md, w, md.Worktree)
} else {
log.Println("info: no branch specific patches found")
}
}
}
func getTipStream(pd *ProcessData, module string, pushBranch string) string {
func getTipStream(pd *data.ProcessData, module string, pushBranch string) string {
repo, err := git.Init(memory.NewStorage(), memfs.New())
if err != nil {
log.Fatalf("could not init git repo: %v", err)
log.Fatalf("could not init git Repo: %v", err)
}
remoteUrl := fmt.Sprintf("%s/rpms/%s.git", pd.UpstreamPrefix, module)
@ -267,15 +165,15 @@ func getTipStream(pd *ProcessData, module string, pushBranch string) string {
return tipHash
}
func patchModuleYaml(pd *ProcessData, md *modeData) {
func patchModuleYaml(pd *data.ProcessData, md *data.ModeData) {
// special case for platform.yaml
_, err := md.worktree.Filesystem.Open("platform.yaml")
_, err := md.Worktree.Filesystem.Open("platform.yaml")
if err == nil {
return
}
mdTxtPath := "SOURCES/modulemd.src.txt"
f, err := md.worktree.Filesystem.Open(mdTxtPath)
f, err := md.Worktree.Filesystem.Open(mdTxtPath)
if err != nil {
log.Fatalf("could not open modulemd file: %v", err)
}
@ -300,16 +198,16 @@ func patchModuleYaml(pd *ProcessData, md *modeData) {
if strings.HasPrefix(rpm.Ref, "stream-rhel-") {
repString := fmt.Sprintf("%s%ss-", pd.BranchPrefix, string(split[4][0]))
newString := fmt.Sprintf("%s%s-", pd.BranchPrefix, string(split[4][0]))
pushBranch = strings.Replace(md.pushBranch, repString, newString, 1)
pushBranch = strings.Replace(md.PushBranch, repString, newString, 1)
} else if strings.HasPrefix(rpm.Ref, "stream-") && len(split) == 2 {
pushBranch = md.pushBranch
pushBranch = md.PushBranch
} else if strings.HasPrefix(rpm.Ref, "stream-") && len(split) == 3 {
// example: ant
pushBranch = fmt.Sprintf("%s%d-stream-%s", pd.BranchPrefix, pd.Version, split[2])
} else if strings.HasPrefix(rpm.Ref, "stream-") {
pushBranch = fmt.Sprintf("%s%s-stream-%s", pd.BranchPrefix, string(split[3][0]), split[1])
} else if strings.HasPrefix(rpm.Ref, "rhel-") {
pushBranch = md.pushBranch
pushBranch = md.PushBranch
} else {
log.Fatal("could not recognize modulemd ref")
}
@ -317,7 +215,7 @@ func patchModuleYaml(pd *ProcessData, md *modeData) {
rpm.Ref = pushBranch
tipHash = getTipStream(pd, name, pushBranch)
err = module.Marshal(md.worktree.Filesystem, mdTxtPath)
err = module.Marshal(md.Worktree.Filesystem, mdTxtPath)
if err != nil {
log.Fatalf("could not marshal modulemd: %v", err)
}
@ -325,13 +223,13 @@ func patchModuleYaml(pd *ProcessData, md *modeData) {
rpm.Ref = tipHash
}
rootModule := fmt.Sprintf("%s.yaml", md.rpmFile.Name())
err = module.Marshal(md.worktree.Filesystem, rootModule)
rootModule := fmt.Sprintf("%s.yaml", md.RpmFile.Name())
err = module.Marshal(md.Worktree.Filesystem, rootModule)
if err != nil {
log.Fatalf("could not marshal root modulemd: %v", err)
}
_, err = md.worktree.Add(rootModule)
_, err = md.Worktree.Add(rootModule)
if err != nil {
log.Fatalf("could not add root modulemd: %v", err)
}

View File

@ -4,16 +4,13 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/blob"
"github.com/cavaliercoder/go-rpm"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/go-git/go-git/v5/storage/memory"
"hash"
"io/ioutil"
"log"
"os"
@ -25,54 +22,17 @@ import (
var tagImportRegex *regexp.Regexp
type ProcessData struct {
RpmLocation string
UpstreamPrefix string
SshKeyLocation string
SshUser string
Version int
GitCommitterName string
GitCommitterEmail string
Mode int
ModulePrefix string
ImportBranchPrefix string
BranchPrefix string
Authenticator *ssh.PublicKeys
Importer ImportMode
BlobStorage blob.Storage
NoDupMode bool
ModuleMode bool
}
type ignoredSource struct {
name string
hashFunction hash.Hash
expired bool
}
type modeData struct {
repo *git.Repository
worktree *git.Worktree
rpmFile *rpm.PackageFile
fileWrites map[string][]byte
tagBranch string
pushBranch string
branches []string
sourcesToIgnore []*ignoredSource
blobCache map[string][]byte
}
// ProcessRPM checks the RPM specs and discards any remote files
// This functions also sorts files into directories
// .spec files goes into -> SPECS
// metadata files goes to root
// source files goes into -> SOURCES
// all files that are remote goes into .gitignore
// all ignored files' hash goes into .{name}.metadata
func ProcessRPM(pd *ProcessData) {
// all ignored files' hash goes into .{Name}.metadata
func ProcessRPM(pd *data.ProcessData) {
tagImportRegex = regexp.MustCompile(fmt.Sprintf("refs/tags/(imports/(%s.|%s.-.+)/(.*))", pd.ImportBranchPrefix, pd.ImportBranchPrefix))
md := pd.Importer.RetrieveSource(pd)
md.blobCache = map[string][]byte{}
md.BlobCache = map[string][]byte{}
remotePrefix := "rpms"
if pd.ModuleMode {
@ -89,9 +49,9 @@ func ProcessRPM(pd *ProcessData) {
if pd.NoDupMode {
repo, err := git.Init(memory.NewStorage(), memfs.New())
if err != nil {
log.Fatalf("could not init git repo: %v", err)
log.Fatalf("could not init git Repo: %v", err)
}
remoteUrl := fmt.Sprintf("%s/%s/%s.git", pd.UpstreamPrefix, remotePrefix, md.rpmFile.Name())
remoteUrl := fmt.Sprintf("%s/%s/%s.git", pd.UpstreamPrefix, remotePrefix, md.RpmFile.Name())
refspec := config.RefSpec("+refs/heads/*:refs/remotes/origin/*")
remote, err := repo.CreateRemote(&config.RemoteConfig{
@ -118,38 +78,42 @@ func ProcessRPM(pd *ProcessData) {
}
}
sourceRepo := *md.repo
sourceWorktree := *md.worktree
sourceRepo := *md.Repo
sourceWorktree := *md.Worktree
for _, branch := range md.branches {
md.repo = &sourceRepo
md.worktree = &sourceWorktree
md.tagBranch = branch
for _, source := range md.sourcesToIgnore {
source.expired = true
if pd.SingleTag != "" {
md.Branches = []string{fmt.Sprintf("refs/tags/%s", pd.SingleTag)}
}
for _, branch := range md.Branches {
md.Repo = &sourceRepo
md.Worktree = &sourceWorktree
md.TagBranch = branch
for _, source := range md.SourcesToIgnore {
source.Expired = true
}
if strings.Contains(md.tagBranch, "-beta") {
if strings.Contains(md.TagBranch, "-beta") {
continue
}
rpmFile := md.rpmFile
// create new repo for final dist
repo, err := git.Init(memory.NewStorage(), memfs.New())
rpmFile := md.RpmFile
// create new Repo for final dist
repo, err := git.Init(memory.NewStorage(), pd.FsCreator())
if err != nil {
log.Fatalf("could not create new dist repo: %v", err)
log.Fatalf("could not create new dist Repo: %v", err)
}
w, err := repo.Worktree()
if err != nil {
log.Fatalf("could not get dist worktree: %v", err)
log.Fatalf("could not get dist Worktree: %v", err)
}
var matchString string
if !tagImportRegex.MatchString(md.tagBranch) {
if !tagImportRegex.MatchString(md.TagBranch) {
if pd.ModuleMode {
prefix := fmt.Sprintf("refs/heads/%s%d", pd.ImportBranchPrefix, pd.Version)
if strings.HasPrefix(md.tagBranch, prefix) {
replace := strings.Replace(md.tagBranch, "refs/heads/", "", 1)
if strings.HasPrefix(md.TagBranch, prefix) {
replace := strings.Replace(md.TagBranch, "refs/heads/", "", 1)
matchString = fmt.Sprintf("refs/tags/imports/%s/%s", replace, filepath.Base(pd.RpmLocation))
log.Printf("using match string: %s", matchString)
}
@ -158,11 +122,11 @@ func ProcessRPM(pd *ProcessData) {
continue
}
} else {
matchString = md.tagBranch
matchString = md.TagBranch
}
match := tagImportRegex.FindStringSubmatch(matchString)
md.pushBranch = pd.BranchPrefix + strings.TrimPrefix(match[2], pd.ImportBranchPrefix)
md.PushBranch = pd.BranchPrefix + strings.TrimPrefix(match[2], pd.ImportBranchPrefix)
newTag := "imports/" + pd.BranchPrefix + strings.TrimPrefix(match[1], "imports/"+pd.ImportBranchPrefix)
shouldContinue := true
@ -179,7 +143,7 @@ func ProcessRPM(pd *ProcessData) {
// create a new remote
remoteUrl := fmt.Sprintf("%s/%s/%s.git", pd.UpstreamPrefix, remotePrefix, rpmFile.Name())
log.Printf("using remote: %s", remoteUrl)
refspec := config.RefSpec(fmt.Sprintf("+refs/heads/%s:refs/remotes/origin/%s", md.pushBranch, md.pushBranch))
refspec := config.RefSpec(fmt.Sprintf("+refs/heads/%s:refs/remotes/origin/%s", md.PushBranch, md.PushBranch))
log.Printf("using refspec: %s", refspec)
_, err = repo.CreateRemote(&config.RemoteConfig{
@ -197,7 +161,7 @@ func ProcessRPM(pd *ProcessData) {
Auth: pd.Authenticator,
})
refName := plumbing.NewBranchReferenceName(md.pushBranch)
refName := plumbing.NewBranchReferenceName(md.PushBranch)
log.Printf("set reference to ref: %s", refName)
if err != nil {
@ -207,7 +171,7 @@ func ProcessRPM(pd *ProcessData) {
}
} else {
err = w.Checkout(&git.CheckoutOptions{
Branch: plumbing.NewRemoteReferenceName("origin", md.pushBranch),
Branch: plumbing.NewRemoteReferenceName("origin", md.PushBranch),
Force: true,
})
if err != nil {
@ -217,9 +181,9 @@ func ProcessRPM(pd *ProcessData) {
pd.Importer.WriteSource(pd, md)
copyFromFs(md.worktree.Filesystem, w.Filesystem, ".")
md.repo = repo
md.worktree = w
copyFromFs(md.Worktree.Filesystem, w.Filesystem, ".")
md.Repo = repo
md.Worktree = w
if pd.ModuleMode {
patchModuleYaml(pd, md)
@ -227,17 +191,17 @@ func ProcessRPM(pd *ProcessData) {
executePatchesRpm(pd, md)
}
// get ignored files hash and add to .{name}.metadata
// get ignored files hash and add to .{Name}.metadata
metadataFile := fmt.Sprintf(".%s.metadata", rpmFile.Name())
metadata, err := w.Filesystem.Create(metadataFile)
if err != nil {
log.Fatalf("could not create metadata file: %v", err)
}
for _, source := range md.sourcesToIgnore {
sourcePath := source.name
for _, source := range md.SourcesToIgnore {
sourcePath := source.Name
_, err := w.Filesystem.Stat(sourcePath)
if source.expired || err != nil {
if source.Expired || err != nil {
continue
}
@ -250,12 +214,12 @@ func ProcessRPM(pd *ProcessData) {
log.Fatalf("could not read the whole of ignored source file: %v", err)
}
source.hashFunction.Reset()
_, err = source.hashFunction.Write(sourceFileBts)
source.HashFunction.Reset()
_, err = source.HashFunction.Write(sourceFileBts)
if err != nil {
log.Fatalf("could not write bytes to hash function: %v", err)
}
checksum := hex.EncodeToString(source.hashFunction.Sum(nil))
checksum := hex.EncodeToString(source.HashFunction.Sum(nil))
checksumLine := fmt.Sprintf("%s %s\n", checksum, sourcePath)
_, err = metadata.Write([]byte(checksumLine))
if err != nil {
@ -266,7 +230,7 @@ func ProcessRPM(pd *ProcessData) {
if strContains(alreadyUploadedBlobs, path) {
continue
}
if !pd.BlobStorage.Exists(path) {
if !pd.BlobStorage.Exists(path) && !pd.NoStorageUpload {
pd.BlobStorage.Write(path, sourceFileBts)
log.Printf("wrote %s to blob storage", path)
}
@ -317,11 +281,11 @@ func ProcessRPM(pd *ProcessData) {
} else {
log.Printf("tip %s", head.String())
hashes = append(hashes, head.Hash())
refOrigin := "refs/heads/" + md.pushBranch
refOrigin := "refs/heads/" + md.PushBranch
pushRefspecs = append(pushRefspecs, config.RefSpec(fmt.Sprintf("HEAD:%s", refOrigin)))
}
// we are now finished with the tree and are going to push it to the src repo
// we are now finished with the tree and are going to push it to the src Repo
// create import commit
commit, err := w.Commit("import "+pd.Importer.ImportName(pd, md), &git.CommitOptions{
Author: &object.Signature{
@ -348,7 +312,7 @@ func ProcessRPM(pd *ProcessData) {
Email: pd.GitCommitterEmail,
When: time.Now(),
},
Message: "import " + md.tagBranch + " from " + pd.RpmLocation,
Message: "import " + md.TagBranch + " from " + pd.RpmLocation,
SignKey: nil,
})
if err != nil {
@ -368,7 +332,7 @@ func ProcessRPM(pd *ProcessData) {
}
hashString := obj.Hash.String()
latestHashForBranch[md.pushBranch] = hashString
latestHashForBranch[md.PushBranch] = hashString
}
err := json.NewEncoder(os.Stdout).Encode(latestHashForBranch)

View File

@ -4,6 +4,7 @@ import (
"bytes"
"crypto/sha256"
"fmt"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
"github.com/cavaliercoder/go-cpio"
"github.com/cavaliercoder/go-rpm"
"github.com/go-git/go-billy/v5/memfs"
@ -20,7 +21,7 @@ import (
type SrpmMode struct{}
func (s *SrpmMode) RetrieveSource(pd *ProcessData) *modeData {
func (s *SrpmMode) RetrieveSource(pd *data.ProcessData) *data.ModeData {
cmd := exec.Command("rpm2cpio", pd.RpmLocation)
cpioBytes, err := cmd.Output()
if err != nil {
@ -30,7 +31,7 @@ func (s *SrpmMode) RetrieveSource(pd *ProcessData) *modeData {
// create in memory git repository
repo, err := git.Init(memory.NewStorage(), memfs.New())
if err != nil {
log.Fatalf("could not init git repo: %v", err)
log.Fatalf("could not init git Repo: %v", err)
}
// read the rpm in cpio format
@ -56,7 +57,7 @@ func (s *SrpmMode) RetrieveSource(pd *ProcessData) *modeData {
w, err := repo.Worktree()
if err != nil {
log.Fatalf("could not get worktree: %v", err)
log.Fatalf("could not get Worktree: %v", err)
}
// create structure
@ -78,29 +79,29 @@ func (s *SrpmMode) RetrieveSource(pd *ProcessData) *modeData {
log.Fatalf("could not read package, invalid?: %v", err)
}
var sourcesToIgnore []*ignoredSource
var sourcesToIgnore []*data.IgnoredSource
for _, source := range rpmFile.Source() {
if strings.Contains(source, ".tar") {
sourcesToIgnore = append(sourcesToIgnore, &ignoredSource{
name: source,
hashFunction: sha256.New(),
sourcesToIgnore = append(sourcesToIgnore, &data.IgnoredSource{
Name: source,
HashFunction: sha256.New(),
})
}
}
branch := fmt.Sprintf("%s%d", pd.BranchPrefix, pd.Version)
return &modeData{
repo: repo,
worktree: w,
rpmFile: rpmFile,
fileWrites: fileWrites,
branches: []string{branch},
sourcesToIgnore: sourcesToIgnore,
return &data.ModeData{
Repo: repo,
Worktree: w,
RpmFile: rpmFile,
FileWrites: fileWrites,
Branches: []string{branch},
SourcesToIgnore: sourcesToIgnore,
}
}
func (s *SrpmMode) WriteSource(_ *ProcessData, md *modeData) {
for fileName, contents := range md.fileWrites {
func (s *SrpmMode) WriteSource(_ *data.ProcessData, md *data.ModeData) {
for fileName, contents := range md.FileWrites {
var newPath string
if filepath.Ext(fileName) == ".spec" {
newPath = filepath.Join("SPECS", fileName)
@ -109,7 +110,7 @@ func (s *SrpmMode) WriteSource(_ *ProcessData, md *modeData) {
}
mode := os.FileMode(0666)
for _, file := range md.rpmFile.Files() {
for _, file := range md.RpmFile.Files() {
if file.Name() == fileName {
mode = file.Mode()
}
@ -117,7 +118,7 @@ func (s *SrpmMode) WriteSource(_ *ProcessData, md *modeData) {
// add the file to the virtual filesystem
// we will move it to correct destination later
f, err := md.worktree.Filesystem.OpenFile(newPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
f, err := md.Worktree.Filesystem.OpenFile(newPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
log.Fatalf("could not create file %s: %v", fileName, err)
}
@ -130,22 +131,22 @@ func (s *SrpmMode) WriteSource(_ *ProcessData, md *modeData) {
_ = f.Close()
// don't add ignored file to git
if ignoredContains(md.sourcesToIgnore, fileName) {
if ignoredContains(md.SourcesToIgnore, fileName) {
continue
}
_, err = md.worktree.Add(newPath)
_, err = md.Worktree.Add(newPath)
if err != nil {
log.Fatalf("could not add source file: %v", err)
}
}
// add sources to ignore (remote sources)
gitIgnore, err := md.worktree.Filesystem.Create(".gitignore")
gitIgnore, err := md.Worktree.Filesystem.Create(".gitignore")
if err != nil {
log.Fatalf("could not create .gitignore: %v", err)
}
for _, ignore := range md.sourcesToIgnore {
for _, ignore := range md.SourcesToIgnore {
line := fmt.Sprintf("SOURCES/%s\n", ignore)
_, err := gitIgnore.Write([]byte(line))
if err != nil {
@ -158,8 +159,8 @@ func (s *SrpmMode) WriteSource(_ *ProcessData, md *modeData) {
}
}
func (s *SrpmMode) PostProcess(_ *modeData) {}
func (s *SrpmMode) PostProcess(_ *data.ModeData) {}
func (s *SrpmMode) ImportName(pd *ProcessData, _ *modeData) string {
func (s *SrpmMode) ImportName(pd *data.ProcessData, _ *data.ModeData) string {
return filepath.Base(pd.RpmLocation)
}

View File

@ -6,6 +6,7 @@ import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
"github.com/go-git/go-billy/v5"
"hash"
"io"
@ -47,9 +48,9 @@ func copyFromFs(from billy.Filesystem, to billy.Filesystem, path string) {
}
}
func ignoredContains(a []*ignoredSource, b string) bool {
func ignoredContains(a []*data.IgnoredSource, b string) bool {
for _, val := range a {
if val.name == b {
if val.Name == b {
return true
}
}

View File

@ -220,6 +220,9 @@ func (x *Delete) GetFile() string {
}
// Add directive adds a file from the patch repository to the rpm repository.
// The file is added in the `SOURCES` directory
// Won't add to spec automatically.
// Use the `SpecChange` directive for that
type Add struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -343,9 +346,9 @@ type SpecChange struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Operation:
// *SpecChange_FileOperation_
Operation isSpecChange_Operation `protobuf_oneof:"operation"`
File []*SpecChange_FileOperation `protobuf:"bytes,1,rep,name=file,proto3" json:"file,omitempty"`
Changelog []*SpecChange_ChangelogOperation `protobuf:"bytes,2,rep,name=changelog,proto3" json:"changelog,omitempty"`
SearchAndReplace []*SpecChange_SearchAndReplaceOperation `protobuf:"bytes,3,rep,name=search_and_replace,json=searchAndReplace,proto3" json:"search_and_replace,omitempty"`
}
func (x *SpecChange) Reset() {
@ -380,30 +383,27 @@ func (*SpecChange) Descriptor() ([]byte, []int) {
return file_cfg_proto_rawDescGZIP(), []int{4}
}
func (m *SpecChange) GetOperation() isSpecChange_Operation {
if m != nil {
return m.Operation
func (x *SpecChange) GetFile() []*SpecChange_FileOperation {
if x != nil {
return x.File
}
return nil
}
func (x *SpecChange) GetFileOperation() *SpecChange_FileOperation {
if x, ok := x.GetOperation().(*SpecChange_FileOperation_); ok {
return x.FileOperation
func (x *SpecChange) GetChangelog() []*SpecChange_ChangelogOperation {
if x != nil {
return x.Changelog
}
return nil
}
type isSpecChange_Operation interface {
isSpecChange_Operation()
func (x *SpecChange) GetSearchAndReplace() []*SpecChange_SearchAndReplaceOperation {
if x != nil {
return x.SearchAndReplace
}
return nil
}
type SpecChange_FileOperation_ struct {
FileOperation *SpecChange_FileOperation `protobuf:"bytes,1,opt,name=file_operation,json=fileOperation,proto3,oneof"`
}
func (*SpecChange_FileOperation_) isSpecChange_Operation() {}
type Patch struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -468,12 +468,12 @@ type Cfg struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Replace []*Replace `protobuf:"bytes,1,rep,name=replace,proto3" json:"replace,omitempty"`
Delete []*Delete `protobuf:"bytes,2,rep,name=delete,proto3" json:"delete,omitempty"`
Add []*Add `protobuf:"bytes,3,rep,name=add,proto3" json:"add,omitempty"`
Lookaside []*Lookaside `protobuf:"bytes,4,rep,name=lookaside,proto3" json:"lookaside,omitempty"`
SpecChange []*SpecChange `protobuf:"bytes,5,rep,name=spec_change,json=specChange,proto3" json:"spec_change,omitempty"`
Patch []*Patch `protobuf:"bytes,6,rep,name=patch,proto3" json:"patch,omitempty"`
Replace []*Replace `protobuf:"bytes,1,rep,name=replace,proto3" json:"replace,omitempty"`
Delete []*Delete `protobuf:"bytes,2,rep,name=delete,proto3" json:"delete,omitempty"`
Add []*Add `protobuf:"bytes,3,rep,name=add,proto3" json:"add,omitempty"`
Lookaside []*Lookaside `protobuf:"bytes,4,rep,name=lookaside,proto3" json:"lookaside,omitempty"`
SpecChange *SpecChange `protobuf:"bytes,5,opt,name=spec_change,json=specChange,proto3" json:"spec_change,omitempty"`
Patch []*Patch `protobuf:"bytes,6,rep,name=patch,proto3" json:"patch,omitempty"`
}
func (x *Cfg) Reset() {
@ -536,7 +536,7 @@ func (x *Cfg) GetLookaside() []*Lookaside {
return nil
}
func (x *Cfg) GetSpecChange() []*SpecChange {
func (x *Cfg) GetSpecChange() *SpecChange {
if x != nil {
return x.SpecChange
}
@ -558,7 +558,7 @@ type SpecChange_FileOperation struct {
unknownFields protoimpl.UnknownFields
// File name
File string `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// File type
Type SpecChange_FileOperation_Type `protobuf:"varint,2,opt,name=type,proto3,enum=srpmproc.SpecChange_FileOperation_Type" json:"type,omitempty"`
// Types that are assignable to Mode:
@ -599,9 +599,9 @@ func (*SpecChange_FileOperation) Descriptor() ([]byte, []int) {
return file_cfg_proto_rawDescGZIP(), []int{4, 0}
}
func (x *SpecChange_FileOperation) GetFile() string {
func (x *SpecChange_FileOperation) GetName() string {
if x != nil {
return x.File
return x.Name
}
return ""
}
@ -654,6 +654,214 @@ func (*SpecChange_FileOperation_Add) isSpecChange_FileOperation_Mode() {}
func (*SpecChange_FileOperation_Delete) isSpecChange_FileOperation_Mode() {}
// ChangelogOperation adds a new changelog entry
type SpecChange_ChangelogOperation struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AuthorName string `protobuf:"bytes,1,opt,name=author_name,json=authorName,proto3" json:"author_name,omitempty"`
AuthorEmail string `protobuf:"bytes,2,opt,name=author_email,json=authorEmail,proto3" json:"author_email,omitempty"`
Message []string `protobuf:"bytes,3,rep,name=message,proto3" json:"message,omitempty"`
}
func (x *SpecChange_ChangelogOperation) Reset() {
*x = SpecChange_ChangelogOperation{}
if protoimpl.UnsafeEnabled {
mi := &file_cfg_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SpecChange_ChangelogOperation) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SpecChange_ChangelogOperation) ProtoMessage() {}
func (x *SpecChange_ChangelogOperation) ProtoReflect() protoreflect.Message {
mi := &file_cfg_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SpecChange_ChangelogOperation.ProtoReflect.Descriptor instead.
func (*SpecChange_ChangelogOperation) Descriptor() ([]byte, []int) {
return file_cfg_proto_rawDescGZIP(), []int{4, 1}
}
func (x *SpecChange_ChangelogOperation) GetAuthorName() string {
if x != nil {
return x.AuthorName
}
return ""
}
func (x *SpecChange_ChangelogOperation) GetAuthorEmail() string {
if x != nil {
return x.AuthorEmail
}
return ""
}
func (x *SpecChange_ChangelogOperation) GetMessage() []string {
if x != nil {
return x.Message
}
return nil
}
// SearchAndReplaceOperation replaces substring with value
// in a specified field
type SpecChange_SearchAndReplaceOperation struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Identifier:
// *SpecChange_SearchAndReplaceOperation_Field
// *SpecChange_SearchAndReplaceOperation_Any
// *SpecChange_SearchAndReplaceOperation_StartsWith
// *SpecChange_SearchAndReplaceOperation_EndsWith
Identifier isSpecChange_SearchAndReplaceOperation_Identifier `protobuf_oneof:"identifier"`
Find string `protobuf:"bytes,5,opt,name=find,proto3" json:"find,omitempty"`
Replace string `protobuf:"bytes,6,opt,name=replace,proto3" json:"replace,omitempty"`
// How many occurences to replace.
// Set to -1 for all
N int32 `protobuf:"zigzag32,7,opt,name=n,proto3" json:"n,omitempty"`
}
func (x *SpecChange_SearchAndReplaceOperation) Reset() {
*x = SpecChange_SearchAndReplaceOperation{}
if protoimpl.UnsafeEnabled {
mi := &file_cfg_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SpecChange_SearchAndReplaceOperation) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SpecChange_SearchAndReplaceOperation) ProtoMessage() {}
func (x *SpecChange_SearchAndReplaceOperation) ProtoReflect() protoreflect.Message {
mi := &file_cfg_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SpecChange_SearchAndReplaceOperation.ProtoReflect.Descriptor instead.
func (*SpecChange_SearchAndReplaceOperation) Descriptor() ([]byte, []int) {
return file_cfg_proto_rawDescGZIP(), []int{4, 2}
}
func (m *SpecChange_SearchAndReplaceOperation) GetIdentifier() isSpecChange_SearchAndReplaceOperation_Identifier {
if m != nil {
return m.Identifier
}
return nil
}
func (x *SpecChange_SearchAndReplaceOperation) GetField() string {
if x, ok := x.GetIdentifier().(*SpecChange_SearchAndReplaceOperation_Field); ok {
return x.Field
}
return ""
}
func (x *SpecChange_SearchAndReplaceOperation) GetAny() bool {
if x, ok := x.GetIdentifier().(*SpecChange_SearchAndReplaceOperation_Any); ok {
return x.Any
}
return false
}
func (x *SpecChange_SearchAndReplaceOperation) GetStartsWith() bool {
if x, ok := x.GetIdentifier().(*SpecChange_SearchAndReplaceOperation_StartsWith); ok {
return x.StartsWith
}
return false
}
func (x *SpecChange_SearchAndReplaceOperation) GetEndsWith() bool {
if x, ok := x.GetIdentifier().(*SpecChange_SearchAndReplaceOperation_EndsWith); ok {
return x.EndsWith
}
return false
}
func (x *SpecChange_SearchAndReplaceOperation) GetFind() string {
if x != nil {
return x.Find
}
return ""
}
func (x *SpecChange_SearchAndReplaceOperation) GetReplace() string {
if x != nil {
return x.Replace
}
return ""
}
func (x *SpecChange_SearchAndReplaceOperation) GetN() int32 {
if x != nil {
return x.N
}
return 0
}
type isSpecChange_SearchAndReplaceOperation_Identifier interface {
isSpecChange_SearchAndReplaceOperation_Identifier()
}
type SpecChange_SearchAndReplaceOperation_Field struct {
// replace occurrences in field value
Field string `protobuf:"bytes,1,opt,name=field,proto3,oneof"`
}
type SpecChange_SearchAndReplaceOperation_Any struct {
// replace occurrences in any line
Any bool `protobuf:"varint,2,opt,name=any,proto3,oneof"`
}
type SpecChange_SearchAndReplaceOperation_StartsWith struct {
// replace occurrences that starts with find
StartsWith bool `protobuf:"varint,3,opt,name=starts_with,json=startsWith,proto3,oneof"`
}
type SpecChange_SearchAndReplaceOperation_EndsWith struct {
// replace occurrences that ends with find
EndsWith bool `protobuf:"varint,4,opt,name=ends_with,json=endsWith,proto3,oneof"`
}
func (*SpecChange_SearchAndReplaceOperation_Field) isSpecChange_SearchAndReplaceOperation_Identifier() {
}
func (*SpecChange_SearchAndReplaceOperation_Any) isSpecChange_SearchAndReplaceOperation_Identifier() {
}
func (*SpecChange_SearchAndReplaceOperation_StartsWith) isSpecChange_SearchAndReplaceOperation_Identifier() {
}
func (*SpecChange_SearchAndReplaceOperation_EndsWith) isSpecChange_SearchAndReplaceOperation_Identifier() {
}
var File_cfg_proto protoreflect.FileDescriptor
var file_cfg_proto_rawDesc = []byte{
@ -673,15 +881,24 @@ var file_cfg_proto_rawDesc = []byte{
0x22, 0x33, 0x0a, 0x09, 0x4c, 0x6f, 0x6f, 0x6b, 0x61, 0x73, 0x69, 0x64, 0x65, 0x12, 0x14, 0x0a,
0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69,
0x6c, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
0x52, 0x03, 0x74, 0x61, 0x72, 0x22, 0xab, 0x02, 0x0a, 0x0a, 0x53, 0x70, 0x65, 0x63, 0x43, 0x68,
0x61, 0x6e, 0x67, 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6f, 0x70, 0x65,
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73,
0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x63, 0x43, 0x68, 0x61, 0x6e,
0x67, 0x65, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x48, 0x00, 0x52, 0x0d, 0x66, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x1a, 0xc2, 0x01, 0x0a, 0x0d, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
0x52, 0x03, 0x74, 0x61, 0x72, 0x22, 0xf8, 0x05, 0x0a, 0x0a, 0x53, 0x70, 0x65, 0x63, 0x43, 0x68,
0x61, 0x6e, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x53, 0x70,
0x65, 0x63, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x65,
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x09,
0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x27, 0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x63, 0x43,
0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x4f,
0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65,
0x6c, 0x6f, 0x67, 0x12, 0x5c, 0x0a, 0x12, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x61, 0x6e,
0x64, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x2e, 0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x63, 0x43,
0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x6e, 0x64, 0x52,
0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x10, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x6e, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63,
0x65, 0x1a, 0xc2, 0x01, 0x0a, 0x0d, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63,
0x2e, 0x53, 0x70, 0x65, 0x63, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x46, 0x69, 0x6c, 0x65,
0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04,
@ -691,31 +908,53 @@ var file_cfg_proto_rawDesc = []byte{
0x74, 0x65, 0x22, 0x2a, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e,
0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, 0x42, 0x06,
0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x22, 0x33, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04,
0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65,
0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x22, 0x8e, 0x02, 0x0a, 0x03, 0x43, 0x66, 0x67,
0x12, 0x2b, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x11, 0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x52, 0x65, 0x70,
0x6c, 0x61, 0x63, 0x65, 0x52, 0x07, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x28, 0x0a,
0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e,
0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52,
0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x03, 0x61, 0x64, 0x64, 0x18, 0x03,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e,
0x41, 0x64, 0x64, 0x52, 0x03, 0x61, 0x64, 0x64, 0x12, 0x31, 0x0a, 0x09, 0x6c, 0x6f, 0x6f, 0x6b,
0x61, 0x73, 0x69, 0x64, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x72,
0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x61, 0x73, 0x69, 0x64, 0x65,
0x52, 0x09, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x73, 0x69, 0x64, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73,
0x70, 0x65, 0x63, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x14, 0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x63,
0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0a, 0x73, 0x70, 0x65, 0x63, 0x43, 0x68, 0x61, 0x6e,
0x67, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x06, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x0f, 0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x50, 0x61, 0x74,
0x63, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x73, 0x74, 0x67, 0x2f, 0x73, 0x72, 0x70,
0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2f, 0x70, 0x62, 0x3b, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f,
0x63, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x1a, 0x72, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65,
0x6c, 0x6f, 0x67, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b,
0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a,
0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c,
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28,
0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0xd3, 0x01, 0x0a, 0x19, 0x53,
0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x6e, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4f,
0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64,
0x12, 0x12, 0x0a, 0x03, 0x61, 0x6e, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52,
0x03, 0x61, 0x6e, 0x79, 0x12, 0x21, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x5f, 0x77,
0x69, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x61,
0x72, 0x74, 0x73, 0x57, 0x69, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x73, 0x5f,
0x77, 0x69, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x65, 0x6e,
0x64, 0x73, 0x57, 0x69, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6e, 0x64, 0x18, 0x05,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65,
0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x70,
0x6c, 0x61, 0x63, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x11, 0x52,
0x01, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72,
0x22, 0x33, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73,
0x74, 0x72, 0x69, 0x63, 0x74, 0x22, 0x8e, 0x02, 0x0a, 0x03, 0x43, 0x66, 0x67, 0x12, 0x2b, 0x0a,
0x07, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11,
0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63,
0x65, 0x52, 0x07, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x06, 0x64, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x73, 0x72, 0x70,
0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x06, 0x64, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x03, 0x61, 0x64, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x0d, 0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x41, 0x64, 0x64,
0x52, 0x03, 0x61, 0x64, 0x64, 0x12, 0x31, 0x0a, 0x09, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x73, 0x69,
0x64, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70,
0x72, 0x6f, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x61, 0x73, 0x69, 0x64, 0x65, 0x52, 0x09, 0x6c,
0x6f, 0x6f, 0x6b, 0x61, 0x73, 0x69, 0x64, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x63,
0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e,
0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x63, 0x43, 0x68, 0x61,
0x6e, 0x67, 0x65, 0x52, 0x0a, 0x73, 0x70, 0x65, 0x63, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12,
0x25, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f,
0x2e, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52,
0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x2e, 0x72, 0x6f,
0x63, 0x6b, 0x79, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x72, 0x65, 0x6c,
0x65, 0x61, 0x73, 0x65, 0x2d, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67,
0x2f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63,
0x2f, 0x70, 0x62, 0x3b, 0x73, 0x72, 0x70, 0x6d, 0x70, 0x72, 0x6f, 0x63, 0x70, 0x62, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -731,32 +970,36 @@ func file_cfg_proto_rawDescGZIP() []byte {
}
var file_cfg_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_cfg_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_cfg_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_cfg_proto_goTypes = []interface{}{
(SpecChange_FileOperation_Type)(0), // 0: srpmproc.SpecChange.FileOperation.Type
(*Replace)(nil), // 1: srpmproc.Replace
(*Delete)(nil), // 2: srpmproc.Delete
(*Add)(nil), // 3: srpmproc.Add
(*Lookaside)(nil), // 4: srpmproc.Lookaside
(*SpecChange)(nil), // 5: srpmproc.SpecChange
(*Patch)(nil), // 6: srpmproc.Patch
(*Cfg)(nil), // 7: srpmproc.Cfg
(*SpecChange_FileOperation)(nil), // 8: srpmproc.SpecChange.FileOperation
(SpecChange_FileOperation_Type)(0), // 0: srpmproc.SpecChange.FileOperation.Type
(*Replace)(nil), // 1: srpmproc.Replace
(*Delete)(nil), // 2: srpmproc.Delete
(*Add)(nil), // 3: srpmproc.Add
(*Lookaside)(nil), // 4: srpmproc.Lookaside
(*SpecChange)(nil), // 5: srpmproc.SpecChange
(*Patch)(nil), // 6: srpmproc.Patch
(*Cfg)(nil), // 7: srpmproc.Cfg
(*SpecChange_FileOperation)(nil), // 8: srpmproc.SpecChange.FileOperation
(*SpecChange_ChangelogOperation)(nil), // 9: srpmproc.SpecChange.ChangelogOperation
(*SpecChange_SearchAndReplaceOperation)(nil), // 10: srpmproc.SpecChange.SearchAndReplaceOperation
}
var file_cfg_proto_depIdxs = []int32{
8, // 0: srpmproc.SpecChange.file_operation:type_name -> srpmproc.SpecChange.FileOperation
1, // 1: srpmproc.Cfg.replace:type_name -> srpmproc.Replace
2, // 2: srpmproc.Cfg.delete:type_name -> srpmproc.Delete
3, // 3: srpmproc.Cfg.add:type_name -> srpmproc.Add
4, // 4: srpmproc.Cfg.lookaside:type_name -> srpmproc.Lookaside
5, // 5: srpmproc.Cfg.spec_change:type_name -> srpmproc.SpecChange
6, // 6: srpmproc.Cfg.patch:type_name -> srpmproc.Patch
0, // 7: srpmproc.SpecChange.FileOperation.type:type_name -> srpmproc.SpecChange.FileOperation.Type
8, // [8:8] is the sub-list for method output_type
8, // [8:8] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
8, // 0: srpmproc.SpecChange.file:type_name -> srpmproc.SpecChange.FileOperation
9, // 1: srpmproc.SpecChange.changelog:type_name -> srpmproc.SpecChange.ChangelogOperation
10, // 2: srpmproc.SpecChange.search_and_replace:type_name -> srpmproc.SpecChange.SearchAndReplaceOperation
1, // 3: srpmproc.Cfg.replace:type_name -> srpmproc.Replace
2, // 4: srpmproc.Cfg.delete:type_name -> srpmproc.Delete
3, // 5: srpmproc.Cfg.add:type_name -> srpmproc.Add
4, // 6: srpmproc.Cfg.lookaside:type_name -> srpmproc.Lookaside
5, // 7: srpmproc.Cfg.spec_change:type_name -> srpmproc.SpecChange
6, // 8: srpmproc.Cfg.patch:type_name -> srpmproc.Patch
0, // 9: srpmproc.SpecChange.FileOperation.type:type_name -> srpmproc.SpecChange.FileOperation.Type
10, // [10:10] is the sub-list for method output_type
10, // [10:10] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
}
func init() { file_cfg_proto_init() }
@ -861,25 +1104,52 @@ func file_cfg_proto_init() {
return nil
}
}
file_cfg_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SpecChange_ChangelogOperation); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cfg_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SpecChange_SearchAndReplaceOperation); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_cfg_proto_msgTypes[0].OneofWrappers = []interface{}{
(*Replace_WithFile)(nil),
(*Replace_WithInline)(nil),
}
file_cfg_proto_msgTypes[4].OneofWrappers = []interface{}{
(*SpecChange_FileOperation_)(nil),
}
file_cfg_proto_msgTypes[7].OneofWrappers = []interface{}{
(*SpecChange_FileOperation_Add)(nil),
(*SpecChange_FileOperation_Delete)(nil),
}
file_cfg_proto_msgTypes[9].OneofWrappers = []interface{}{
(*SpecChange_SearchAndReplaceOperation_Field)(nil),
(*SpecChange_SearchAndReplaceOperation_Any)(nil),
(*SpecChange_SearchAndReplaceOperation_StartsWith)(nil),
(*SpecChange_SearchAndReplaceOperation_EndsWith)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_cfg_proto_rawDesc,
NumEnums: 1,
NumMessages: 8,
NumMessages: 10,
NumExtensions: 0,
NumServices: 0,
},

View File

@ -62,7 +62,7 @@ message SpecChange {
Patch = 2;
}
// File name
string file = 1;
string name = 1;
// File type
Type type = 2;
@ -75,9 +75,35 @@ message SpecChange {
bool delete = 4;
}
}
oneof operation {
FileOperation file_operation = 1;
// ChangelogOperation adds a new changelog entry
message ChangelogOperation {
string author_name = 1;
string author_email = 2;
repeated string message = 3;
}
// SearchAndReplaceOperation replaces substring with value
// in a specified field
message SearchAndReplaceOperation {
oneof identifier {
// replace occurrences in field value
string field = 1;
// replace occurrences in any line
bool any = 2;
// replace occurrences that starts with find
bool starts_with = 3;
// replace occurrences that ends with find
bool ends_with = 4;
}
string find = 5;
string replace = 6;
// How many occurences to replace.
// Set to -1 for all
sint32 n = 7;
}
repeated FileOperation file = 1;
repeated ChangelogOperation changelog = 2;
repeated SearchAndReplaceOperation search_and_replace = 3;
}
message Patch {
@ -95,6 +121,6 @@ message Cfg {
repeated Delete delete = 2;
repeated Add add = 3;
repeated Lookaside lookaside = 4;
repeated SpecChange spec_change = 5;
SpecChange spec_change = 5;
repeated Patch patch = 6;
}