git.go 8.41 KB
Newer Older
Mustafa Gezen's avatar
Mustafa Gezen committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Copyright (c) 2021 The Srpmproc Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

21
22
23
24
package internal

import (
	"fmt"
25
	"git.rockylinux.org/release-engineering/public/srpmproc/internal/data"
26
27
28
29
	"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"
30
	"github.com/go-git/go-git/v5/plumbing/object"
31
32
33
34
35
36
37
	"github.com/go-git/go-git/v5/storage/memory"
	"io/ioutil"
	"log"
	"net/http"
	"path/filepath"
	"sort"
	"strings"
38
	"time"
39
40
)

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
type remoteTarget struct {
	remote string
	when   time.Time
}

type remoteTargetSlice []remoteTarget

func (p remoteTargetSlice) Len() int {
	return len(p)
}

func (p remoteTargetSlice) Less(i, j int) bool {
	return p[i].when.Before(p[j].when)
}

func (p remoteTargetSlice) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

60
61
type GitMode struct{}

62
func (g *GitMode) RetrieveSource(pd *data.ProcessData) *data.ModeData {
63
64
	repo, err := git.Init(memory.NewStorage(), memfs.New())
	if err != nil {
65
		log.Fatalf("could not init git Repo: %v", err)
66
67
68
69
	}

	w, err := repo.Worktree()
	if err != nil {
70
		log.Fatalf("could not get Worktree: %v", err)
71
72
73
74
75
76
77
78
79
80
81
82
	}

	refspec := config.RefSpec("+refs/heads/*:refs/remotes/*")
	remote, err := repo.CreateRemote(&config.RemoteConfig{
		Name:  "upstream",
		URLs:  []string{pd.RpmLocation},
		Fetch: []config.RefSpec{refspec},
	})
	if err != nil {
		log.Fatalf("could not create remote: %v", err)
	}

83
	err = remote.Fetch(&git.FetchOptions{
84
85
86
		RefSpecs: []config.RefSpec{refspec},
		Tags:     git.AllTags,
		Force:    true,
87
	})
88
	if err != nil {
89
		log.Fatalf("could not fetch upstream: %v", err)
90
91
	}

92
	var branches remoteTargetSlice
93

94
95
	latestTags := map[string]*remoteTarget{}

96
97
	tagAdd := func(tag *object.Tag) error {
		if strings.HasPrefix(tag.Name, fmt.Sprintf("imports/%s%d", pd.ImportBranchPrefix, pd.Version)) {
98
99
100
101
102
103
104
105
106
107
108
109
110
111
			refSpec := fmt.Sprintf("refs/tags/%s", tag.Name)
			if tagImportRegex.MatchString(refSpec) {
				match := tagImportRegex.FindStringSubmatch(refSpec)

				exists := latestTags[match[2]]
				if exists != nil && exists.when.After(tag.Tagger.When) {
					return nil
				}

				latestTags[match[2]] = &remoteTarget{
					remote: refSpec,
					when:   tag.Tagger.When,
				}
			}
Mustafa Gezen's avatar
Mustafa Gezen committed
112
		}
113
		return nil
114
115
116
117
118
119
120
121
	}

	tagIter, err := repo.TagObjects()
	if err != nil {
		log.Fatalf("could not get tag objects: %v", err)
	}
	_ = tagIter.ForEach(tagAdd)

122
	if len(latestTags) == 0 {
123
124
125
126
127
128
129
130
131
132
133
134
		list, err := remote.List(&git.ListOptions{})
		if err != nil {
			log.Fatalf("could not list upstream: %v", err)
		}

		for _, ref := range list {
			if ref.Hash().IsZero() {
				continue
			}

			commit, err := repo.CommitObject(ref.Hash())
			if err != nil {
Mustafa Gezen's avatar
Mustafa Gezen committed
135
136
				log.Printf("could not get commit object for ref %s: %v", ref.Name().String(), err)
				continue
137
138
139
140
141
142
143
144
			}
			_ = tagAdd(&object.Tag{
				Name:   strings.TrimPrefix(string(ref.Name()), "refs/tags/"),
				Tagger: commit.Committer,
			})
		}
	}

145
146
147
148
149
	for _, branch := range latestTags {
		log.Printf("tag: %s", strings.TrimPrefix(branch.remote, "refs/tags/"))
		branches = append(branches, *branch)
	}

150
151
152
153
154
	sort.Sort(branches)

	var sortedBranches []string
	for _, branch := range branches {
		sortedBranches = append(sortedBranches, branch.remote)
Mustafa Gezen's avatar
Mustafa Gezen committed
155
	}
156

157
158
159
160
161
162
	return &data.ModeData{
		Repo:       repo,
		Worktree:   w,
		RpmFile:    createPackageFile(filepath.Base(pd.RpmLocation)),
		FileWrites: nil,
		Branches:   sortedBranches,
163
164
165
	}
}

166
167
func (g *GitMode) WriteSource(pd *data.ProcessData, md *data.ModeData) {
	remote, err := md.Repo.Remote("upstream")
168
169
170
171
	if err != nil {
		log.Fatalf("could not get upstream remote: %v", err)
	}

Mustafa Gezen's avatar
Mustafa Gezen committed
172
173
174
	var refspec config.RefSpec
	var branchName string

175
176
177
	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/")
Mustafa Gezen's avatar
Mustafa Gezen committed
178
	} else {
179
		match := tagImportRegex.FindStringSubmatch(md.TagBranch)
Mustafa Gezen's avatar
Mustafa Gezen committed
180
		branchName = match[2]
181
		refspec = config.RefSpec(fmt.Sprintf("+refs/heads/%s:%s", branchName, md.TagBranch))
Mustafa Gezen's avatar
Mustafa Gezen committed
182
	}
183
	log.Printf("checking out upstream refspec %s", refspec)
184
185
186
187
188
189
	err = remote.Fetch(&git.FetchOptions{
		RemoteName: "upstream",
		RefSpecs:   []config.RefSpec{refspec},
		Tags:       git.AllTags,
		Force:      true,
	})
190
	if err != nil && err != git.NoErrAlreadyUpToDate {
191
192
193
		log.Fatalf("could not fetch upstream: %v", err)
	}

194
195
	err = md.Worktree.Checkout(&git.CheckoutOptions{
		Branch: plumbing.ReferenceName(md.TagBranch),
196
197
198
199
200
201
		Force:  true,
	})
	if err != nil {
		log.Fatalf("could not checkout source from git: %v", err)
	}

202
	_, err = md.Worktree.Add(".")
203
	if err != nil {
204
		log.Fatalf("could not add Worktree: %v", err)
205
206
	}

207
	metadataFile, err := md.Worktree.Filesystem.Open(fmt.Sprintf(".%s.metadata", md.RpmFile.Name()))
208
	if err != nil {
Mustafa Gezen's avatar
Mustafa Gezen committed
209
210
		log.Printf("warn: could not open metadata file, so skipping: %v", err)
		return
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
	}

	fileBytes, err := ioutil.ReadAll(metadataFile)
	if err != nil {
		log.Fatalf("could not read metadata file: %v", err)
	}

	client := &http.Client{
		Transport: &http.Transport{
			DisableCompression: false,
		},
	}
	fileContent := strings.Split(string(fileBytes), "\n")
	for _, line := range fileContent {
		if strings.TrimSpace(line) == "" {
			continue
		}

229
230
231
		lineInfo := strings.SplitN(line, " ", 2)
		hash := strings.TrimSpace(lineInfo[0])
		path := strings.TrimSpace(lineInfo[1])
232

233
234
		var body []byte

235
236
		if md.BlobCache[hash] != nil {
			body = md.BlobCache[hash]
237
238
239
			log.Printf("retrieving %s from cache", hash)
		} else {
			fromBlobStorage := pd.BlobStorage.Read(hash)
240
			if fromBlobStorage != nil && !pd.NoStorageDownload {
241
242
243
				body = fromBlobStorage
				log.Printf("downloading %s from blob storage", hash)
			} else {
244
				url := fmt.Sprintf("https://git.centos.org/sources/%s/%s/%s", md.RpmFile.Name(), branchName, hash)
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
				log.Printf("downloading %s", url)

				req, err := http.NewRequest("GET", url, nil)
				if err != nil {
					log.Fatalf("could not create new http request: %v", err)
				}
				req.Header.Set("Accept-Encoding", "*")

				resp, err := client.Do(req)
				if err != nil {
					log.Fatalf("could not download dist-git file: %v", err)
				}

				body, err = ioutil.ReadAll(resp.Body)
				if err != nil {
					log.Fatalf("could not read the whole dist-git file: %v", err)
				}
				err = resp.Body.Close()
				if err != nil {
					log.Fatalf("could not close body handle: %v", err)
				}
			}

268
			md.BlobCache[hash] = body
269
270
		}

271
		f, err := md.Worktree.Filesystem.Create(path)
272
273
274
275
		if err != nil {
			log.Fatalf("could not open file pointer: %v", err)
		}

Mustafa Gezen's avatar
Mustafa Gezen committed
276
		hasher := CompareHash(body, hash)
277
278
279
280
		if hasher == nil {
			log.Fatal("checksum in metadata does not match dist-git file")
		}

281
282
283
		md.SourcesToIgnore = append(md.SourcesToIgnore, &data.IgnoredSource{
			Name:         path,
			HashFunction: hasher,
284
285
286
287
288
289
290
291
292
293
		})

		_, err = f.Write(body)
		if err != nil {
			log.Fatalf("could not copy dist-git file to in-tree: %v", err)
		}
		_ = f.Close()
	}
}

294
295
296
func (g *GitMode) PostProcess(md *data.ModeData) {
	for _, source := range md.SourcesToIgnore {
		_, err := md.Worktree.Filesystem.Stat(source.Name)
297
		if err == nil {
298
			err := md.Worktree.Filesystem.Remove(source.Name)
299
300
301
			if err != nil {
				log.Fatalf("could not remove dist-git file: %v", err)
			}
302
303
304
		}
	}

305
	_, err := md.Worktree.Add(".")
306
307
308
309
310
	if err != nil {
		log.Fatalf("could not add git sources: %v", err)
	}
}

311
312
313
func (g *GitMode) ImportName(_ *data.ProcessData, md *data.ModeData) string {
	if tagImportRegex.MatchString(md.TagBranch) {
		match := tagImportRegex.FindStringSubmatch(md.TagBranch)
Mustafa Gezen's avatar
Mustafa Gezen committed
314
315
316
		return match[3]
	}

317
	return strings.TrimPrefix(md.TagBranch, "refs/heads/")
318
}