5
5
package migrations
6
6
7
7
import (
8
+ "fmt"
9
+ "regexp"
8
10
"strings"
9
11
10
- "code.gitea.io/gitea/models"
11
12
"code.gitea.io/gitea/modules/log"
12
13
13
14
"github.com/go-xorm/xorm"
14
15
)
15
16
17
+ var topicPattern = regexp .MustCompile (`^[a-z0-9][a-z0-9-]*$` )
18
+
19
+ func validateTopic (topic string ) bool {
20
+ return len (topic ) <= 35 && topicPattern .MatchString (topic )
21
+ }
22
+
16
23
func reformatAndRemoveIncorrectTopics (x * xorm.Engine ) (err error ) {
17
24
log .Info ("This migration could take up to minutes, please be patient." )
25
+
18
26
type Topic struct {
19
- ID int64
20
- Name string `xorm:"unique"`
27
+ ID int64
28
+ Name string `xorm:"UNIQUE"`
29
+ RepoCount int
30
+ CreatedUnix int64 `xorm:"INDEX created"`
31
+ UpdatedUnix int64 `xorm:"INDEX updated"`
32
+ }
33
+
34
+ type RepoTopic struct {
35
+ RepoID int64 `xorm:"UNIQUE(s)"`
36
+ TopicID int64 `xorm:"UNIQUE(s)"`
37
+ }
38
+
39
+ type Repository struct {
40
+ ID int64 `xorm:"pk autoincr"`
41
+ Topics []string `xorm:"TEXT JSON"`
42
+ }
43
+
44
+ if err := x .Sync2 (new (Topic )); err != nil {
45
+ return fmt .Errorf ("Sync2: %v" , err )
46
+ }
47
+ if err := x .Sync2 (new (RepoTopic )); err != nil {
48
+ return fmt .Errorf ("Sync2: %v" , err )
21
49
}
22
50
23
51
sess := x .NewSession ()
24
52
defer sess .Close ()
25
53
26
54
const batchSize = 100
27
55
touchedRepo := make (map [int64 ]struct {})
28
- topics := make ([]* Topic , 0 , batchSize )
29
56
delTopicIDs := make ([]int64 , 0 , batchSize )
30
- ids := make ([]int64 , 0 , 30 )
31
57
58
+ log .Info ("Validating existed topics..." )
32
59
if err := sess .Begin (); err != nil {
33
60
return err
34
61
}
35
- log .Info ("Validating existed topics..." )
36
62
for start := 0 ; ; start += batchSize {
37
- topics = topics [: 0 ]
38
- if err := sess .Asc ("id" ).Limit (batchSize , start ).Find (& topics ); err != nil {
63
+ topics := make ([] * Topic , 0 , batchSize )
64
+ if err := x . Cols ( "id" , "name" ) .Asc ("id" ).Limit (batchSize , start ).Find (& topics ); err != nil {
39
65
return err
40
66
}
41
67
if len (topics ) == 0 {
42
68
break
43
69
}
44
70
for _ , topic := range topics {
45
- if models . ValidateTopic (topic .Name ) {
71
+ if validateTopic (topic .Name ) {
46
72
continue
47
73
}
74
+ log .Info ("Incorrect topic: id = %v, name = %q" , topic .ID , topic .Name )
75
+
48
76
topic .Name = strings .Replace (strings .TrimSpace (strings .ToLower (topic .Name )), " " , "-" , - 1 )
49
77
78
+ ids := make ([]int64 , 0 , 30 )
50
79
if err := sess .Table ("repo_topic" ).Cols ("repo_id" ).
51
80
Where ("topic_id = ?" , topic .ID ).Find (& ids ); err != nil {
52
81
return err
53
82
}
83
+ log .Info ("Touched repo ids: %v" , ids )
54
84
for _ , id := range ids {
55
85
touchedRepo [id ] = struct {}{}
56
86
}
57
87
58
- if models .ValidateTopic (topic .Name ) {
59
- log .Info ("Updating topic: id = %v, name = %v" , topic .ID , topic .Name )
60
- if _ , err := sess .Table ("topic" ).ID (topic .ID ).
61
- Update (& Topic {Name : topic .Name }); err != nil {
88
+ if validateTopic (topic .Name ) {
89
+ unifiedTopic := Topic {Name : topic .Name }
90
+ exists , err := sess .Cols ("id" , "name" ).Get (& unifiedTopic )
91
+ log .Info ("Exists topic with the name %q? %v, id = %v" , topic .Name , exists , unifiedTopic .ID )
92
+ if err != nil {
62
93
return err
63
94
}
64
- } else {
65
- delTopicIDs = append (delTopicIDs , topic .ID )
95
+ if exists {
96
+ log .Info ("Updating repo_topic rows with topic_id = %v to topic_id = %v" , topic .ID , unifiedTopic .ID )
97
+ if _ , err := sess .Where ("topic_id = ? AND repo_id NOT IN " +
98
+ "(SELECT rt1.repo_id FROM repo_topic rt1 INNER JOIN repo_topic rt2 " +
99
+ "ON rt1.repo_id = rt2.repo_id WHERE rt1.topic_id = ? AND rt2.topic_id = ?)" ,
100
+ topic .ID , topic .ID , unifiedTopic .ID ).Update (& RepoTopic {TopicID : unifiedTopic .ID }); err != nil {
101
+ return err
102
+ }
103
+ log .Info ("Updating topic `repo_count` field" )
104
+ if _ , err := sess .Exec (
105
+ "UPDATE topic SET repo_count = (SELECT COUNT(*) FROM repo_topic WHERE topic_id = ? GROUP BY topic_id) WHERE id = ?" ,
106
+ unifiedTopic .ID , unifiedTopic .ID ); err != nil {
107
+ return err
108
+ }
109
+ } else {
110
+ log .Info ("Updating topic: id = %v, name = %q" , topic .ID , topic .Name )
111
+ if _ , err := sess .Table ("topic" ).ID (topic .ID ).
112
+ Update (& Topic {Name : topic .Name }); err != nil {
113
+ return err
114
+ }
115
+ continue
116
+ }
66
117
}
118
+ delTopicIDs = append (delTopicIDs , topic .ID )
67
119
}
68
120
}
121
+ if err := sess .Commit (); err != nil {
122
+ return err
123
+ }
69
124
70
- log .Info ("Deleting incorrect topics..." )
71
- for start := 0 ; ; start += batchSize {
72
- if (start + batchSize ) < len (delTopicIDs ) {
73
- ids = delTopicIDs [start :(start + batchSize )]
74
- } else {
75
- ids = delTopicIDs [start :]
76
- }
77
-
78
- log .Info ("Deleting 'repo_topic' rows for topics with ids = %v" , ids )
79
- if _ , err := sess .In ("topic_id" , ids ).Delete (& models.RepoTopic {}); err != nil {
80
- return err
81
- }
82
-
83
- log .Info ("Deleting topics with id = %v" , ids )
84
- if _ , err := sess .In ("id" , ids ).Delete (& Topic {}); err != nil {
85
- return err
86
- }
125
+ sess .Init ()
87
126
88
- if len (ids ) < batchSize {
89
- break
90
- }
127
+ log .Info ("Deleting incorrect topics..." )
128
+ if err := sess .Begin (); err != nil {
129
+ return err
130
+ }
131
+ log .Info ("Deleting 'repo_topic' rows for topics with ids = %v" , delTopicIDs )
132
+ if _ , err := sess .In ("topic_id" , delTopicIDs ).Delete (& RepoTopic {}); err != nil {
133
+ return err
134
+ }
135
+ log .Info ("Deleting topics with id = %v" , delTopicIDs )
136
+ if _ , err := sess .In ("id" , delTopicIDs ).Delete (& Topic {}); err != nil {
137
+ return err
138
+ }
139
+ if err := sess .Commit (); err != nil {
140
+ return err
91
141
}
92
142
93
- repoTopics := make ([]* models.RepoTopic , 0 , batchSize )
94
- delRepoTopics := make ([]* models.RepoTopic , 0 , batchSize )
95
- tmpRepoTopics := make ([]* models.RepoTopic , 0 , 30 )
143
+ delRepoTopics := make ([]* RepoTopic , 0 , batchSize )
96
144
97
145
log .Info ("Checking the number of topics in the repositories..." )
98
146
for start := 0 ; ; start += batchSize {
99
- repoTopics = repoTopics [: 0 ]
100
- if err := sess .Cols ("repo_id" ).Asc ("repo_id" ).Limit (batchSize , start ).
147
+ repoTopics := make ([] * RepoTopic , 0 , batchSize )
148
+ if err := x .Cols ("repo_id" ).Asc ("repo_id" ).Limit (batchSize , start ).
101
149
GroupBy ("repo_id" ).Having ("COUNT(*) > 25" ).Find (& repoTopics ); err != nil {
102
150
return err
103
151
}
@@ -109,8 +157,8 @@ func reformatAndRemoveIncorrectTopics(x *xorm.Engine) (err error) {
109
157
for _ , repoTopic := range repoTopics {
110
158
touchedRepo [repoTopic .RepoID ] = struct {}{}
111
159
112
- tmpRepoTopics = tmpRepoTopics [: 0 ]
113
- if err := sess .Where ("repo_id = ?" , repoTopic .RepoID ).Find (& tmpRepoTopics ); err != nil {
160
+ tmpRepoTopics := make ([] * RepoTopic , 0 , 30 )
161
+ if err := x .Where ("repo_id = ?" , repoTopic .RepoID ).Find (& tmpRepoTopics ); err != nil {
114
162
return err
115
163
}
116
164
@@ -122,13 +170,18 @@ func reformatAndRemoveIncorrectTopics(x *xorm.Engine) (err error) {
122
170
}
123
171
}
124
172
173
+ sess .Init ()
174
+
125
175
log .Info ("Deleting superfluous topics for repositories (more than 25 topics)..." )
176
+ if err := sess .Begin (); err != nil {
177
+ return err
178
+ }
126
179
for _ , repoTopic := range delRepoTopics {
127
180
log .Info ("Deleting 'repo_topic' rows for 'repository' with id = %v. Topic id = %v" ,
128
181
repoTopic .RepoID , repoTopic .TopicID )
129
182
130
183
if _ , err := sess .Where ("repo_id = ? AND topic_id = ?" , repoTopic .RepoID ,
131
- repoTopic .TopicID ).Delete (& models. RepoTopic {}); err != nil {
184
+ repoTopic .TopicID ).Delete (& RepoTopic {}); err != nil {
132
185
return err
133
186
}
134
187
if _ , err := sess .Exec (
@@ -138,17 +191,17 @@ func reformatAndRemoveIncorrectTopics(x *xorm.Engine) (err error) {
138
191
}
139
192
}
140
193
141
- topicNames := make ([]string , 0 , 30 )
142
194
log .Info ("Updating repositories 'topics' fields..." )
143
195
for repoID := range touchedRepo {
196
+ topicNames := make ([]string , 0 , 30 )
144
197
if err := sess .Table ("topic" ).Cols ("name" ).
145
- Join ("INNER" , "repo_topic" , "topic.id = repo_topic.topic_id " ).
146
- Where ("repo_topic.repo_id = ?" , repoID ).Find (& topicNames ); err != nil {
198
+ Join ("INNER" , "repo_topic" , "repo_topic.topic_id = topic.id " ).
199
+ Where ("repo_topic.repo_id = ?" , repoID ).Desc ( "topic.repo_count" ). Find (& topicNames ); err != nil {
147
200
return err
148
201
}
149
202
log .Info ("Updating 'topics' field for repository with id = %v" , repoID )
150
203
if _ , err := sess .ID (repoID ).Cols ("topics" ).
151
- Update (& models. Repository {Topics : topicNames }); err != nil {
204
+ Update (& Repository {Topics : topicNames }); err != nil {
152
205
return err
153
206
}
154
207
}
0 commit comments