summaryrefslogtreecommitdiffstats
path: root/models/migrations/v1_14/v156.go
blob: 2cf4954a15f58bb5af52b230b5d0dab1fc85e900 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_14 //nolint

import (
	"fmt"
	"path/filepath"
	"strings"

	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"

	"xorm.io/xorm"
)

// Copy paste from models/repo.go because we cannot import models package
func repoPath(userName, repoName string) string {
	return filepath.Join(userPath(userName), strings.ToLower(repoName)+".git")
}

func userPath(userName string) string {
	return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
}

func FixPublisherIDforTagReleases(x *xorm.Engine) error {
	type Release struct {
		ID          int64
		RepoID      int64
		Sha1        string
		TagName     string
		PublisherID int64
	}

	type Repository struct {
		ID        int64
		OwnerID   int64
		OwnerName string
		Name      string
	}

	type User struct {
		ID    int64
		Name  string
		Email string
	}

	const batchSize = 100
	sess := x.NewSession()
	defer sess.Close()

	var (
		repo    *Repository
		gitRepo *git.Repository
		user    *User
	)
	defer func() {
		if gitRepo != nil {
			gitRepo.Close()
		}
	}()
	for start := 0; ; start += batchSize {
		releases := make([]*Release, 0, batchSize)

		if err := sess.Begin(); err != nil {
			return err
		}

		if err := sess.Limit(batchSize, start).
			Where("publisher_id = 0 OR publisher_id is null").
			Asc("repo_id", "id").Where("is_tag=?", true).
			Find(&releases); err != nil {
			return err
		}

		if len(releases) == 0 {
			break
		}

		for _, release := range releases {
			if repo == nil || repo.ID != release.RepoID {
				if gitRepo != nil {
					gitRepo.Close()
					gitRepo = nil
				}
				repo = new(Repository)
				has, err := sess.ID(release.RepoID).Get(repo)
				if err != nil {
					log.Error("Error whilst loading repository[%d] for release[%d] with tag name %s. Error: %v", release.RepoID, release.ID, release.TagName, err)
					return err
				} else if !has {
					log.Warn("Release[%d] is orphaned and refers to non-existing repository %d", release.ID, release.RepoID)
					log.Warn("This release should be deleted")
					continue
				}

				if repo.OwnerName == "" {
					// v120.go migration may not have been run correctly - we'll just replicate it here
					// because this appears to be a common-ish problem.
					if _, err := sess.Exec("UPDATE repository SET owner_name = (SELECT name FROM `user` WHERE `user`.id = repository.owner_id)"); err != nil {
						log.Error("Error whilst updating repository[%d] owner name", repo.ID)
						return err
					}

					if _, err := sess.ID(release.RepoID).Get(repo); err != nil {
						log.Error("Error whilst loading repository[%d] for release[%d] with tag name %s. Error: %v", release.RepoID, release.ID, release.TagName, err)
						return err
					}
				}
				gitRepo, err = git.OpenRepository(git.DefaultContext, repoPath(repo.OwnerName, repo.Name))
				if err != nil {
					log.Error("Error whilst opening git repo for [%d]%s/%s. Error: %v", repo.ID, repo.OwnerName, repo.Name, err)
					return err
				}
			}

			commit, err := gitRepo.GetTagCommit(release.TagName)
			if err != nil {
				if git.IsErrNotExist(err) {
					log.Warn("Unable to find commit %s for Tag: %s in [%d]%s/%s. Cannot update publisher ID.", err.(git.ErrNotExist).ID, release.TagName, repo.ID, repo.OwnerName, repo.Name)
					continue
				}
				log.Error("Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v", release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
				return fmt.Errorf("GetTagCommit: %w", err)
			}

			if commit.Author.Email == "" {
				log.Warn("Tag: %s in Repo[%d]%s/%s does not have a tagger.", release.TagName, repo.ID, repo.OwnerName, repo.Name)
				commit, err = gitRepo.GetCommit(commit.ID.String())
				if err != nil {
					if git.IsErrNotExist(err) {
						log.Warn("Unable to find commit %s for Tag: %s in [%d]%s/%s. Cannot update publisher ID.", err.(git.ErrNotExist).ID, release.TagName, repo.ID, repo.OwnerName, repo.Name)
						continue
					}
					log.Error("Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v", release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
					return fmt.Errorf("GetCommit: %w", err)
				}
			}

			if commit.Author.Email == "" {
				log.Warn("Tag: %s in Repo[%d]%s/%s does not have a Tagger and its underlying commit does not have an Author either!", release.TagName, repo.ID, repo.OwnerName, repo.Name)
				continue
			}

			if user == nil || !strings.EqualFold(user.Email, commit.Author.Email) {
				user = new(User)
				_, err = sess.Where("email=?", commit.Author.Email).Get(user)
				if err != nil {
					log.Error("Error whilst getting commit author by email: %s for Tag: %s in [%d]%s/%s. Error: %v", commit.Author.Email, release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
					return err
				}

				user.Email = commit.Author.Email
			}

			if user.ID <= 0 {
				continue
			}

			release.PublisherID = user.ID
			if _, err := sess.ID(release.ID).Cols("publisher_id").Update(release); err != nil {
				log.Error("Error whilst updating publisher[%d] for release[%d] with tag name %s. Error: %v", release.PublisherID, release.ID, release.TagName, err)
				return err
			}
		}
		if gitRepo != nil {
			gitRepo.Close()
		}

		if err := sess.Commit(); err != nil {
			return err
		}
	}

	return nil
}