You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

migrate.go 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Copyright 2018 Jonas Franz. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package migrations
  6. import (
  7. "context"
  8. "fmt"
  9. "net"
  10. "net/url"
  11. "strings"
  12. "code.gitea.io/gitea/models"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/matchlist"
  15. "code.gitea.io/gitea/modules/migrations/base"
  16. "code.gitea.io/gitea/modules/setting"
  17. )
  18. // MigrateOptions is equal to base.MigrateOptions
  19. type MigrateOptions = base.MigrateOptions
  20. var (
  21. factories []base.DownloaderFactory
  22. allowList *matchlist.Matchlist
  23. blockList *matchlist.Matchlist
  24. )
  25. // RegisterDownloaderFactory registers a downloader factory
  26. func RegisterDownloaderFactory(factory base.DownloaderFactory) {
  27. factories = append(factories, factory)
  28. }
  29. func isMigrateURLAllowed(remoteURL string) error {
  30. u, err := url.Parse(strings.ToLower(remoteURL))
  31. if err != nil {
  32. return err
  33. }
  34. if strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https") {
  35. if len(setting.Migrations.AllowedDomains) > 0 {
  36. if !allowList.Match(u.Host) {
  37. return &models.ErrMigrationNotAllowed{Host: u.Host}
  38. }
  39. } else {
  40. if blockList.Match(u.Host) {
  41. return &models.ErrMigrationNotAllowed{Host: u.Host}
  42. }
  43. }
  44. }
  45. if !setting.Migrations.AllowLocalNetworks {
  46. addrList, err := net.LookupIP(strings.Split(u.Host, ":")[0])
  47. if err != nil {
  48. return &models.ErrMigrationNotAllowed{Host: u.Host, NotResolvedIP: true}
  49. }
  50. for _, addr := range addrList {
  51. if isIPPrivate(addr) || !addr.IsGlobalUnicast() {
  52. return &models.ErrMigrationNotAllowed{Host: u.Host, PrivateNet: addr.String()}
  53. }
  54. }
  55. }
  56. return nil
  57. }
  58. // MigrateRepository migrate repository according MigrateOptions
  59. func MigrateRepository(ctx context.Context, doer *models.User, ownerName string, opts base.MigrateOptions) (*models.Repository, error) {
  60. err := isMigrateURLAllowed(opts.CloneAddr)
  61. if err != nil {
  62. return nil, err
  63. }
  64. var (
  65. downloader base.Downloader
  66. uploader = NewGiteaLocalUploader(ctx, doer, ownerName, opts.RepoName)
  67. )
  68. for _, factory := range factories {
  69. if factory.GitServiceType() == opts.GitServiceType {
  70. downloader, err = factory.New(ctx, opts)
  71. if err != nil {
  72. return nil, err
  73. }
  74. break
  75. }
  76. }
  77. if downloader == nil {
  78. opts.Wiki = true
  79. opts.Milestones = false
  80. opts.Labels = false
  81. opts.Releases = false
  82. opts.Comments = false
  83. opts.Issues = false
  84. opts.PullRequests = false
  85. downloader = NewPlainGitDownloader(ownerName, opts.RepoName, opts.CloneAddr)
  86. log.Trace("Will migrate from git: %s", opts.OriginalURL)
  87. }
  88. uploader.gitServiceType = opts.GitServiceType
  89. if setting.Migrations.MaxAttempts > 1 {
  90. downloader = base.NewRetryDownloader(ctx, downloader, setting.Migrations.MaxAttempts, setting.Migrations.RetryBackoff)
  91. }
  92. if err := migrateRepository(downloader, uploader, opts); err != nil {
  93. if err1 := uploader.Rollback(); err1 != nil {
  94. log.Error("rollback failed: %v", err1)
  95. }
  96. if err2 := models.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.OriginalURL, err)); err2 != nil {
  97. log.Error("create repository notice failed: ", err2)
  98. }
  99. return nil, err
  100. }
  101. return uploader.repo, nil
  102. }
  103. // migrateRepository will download information and then upload it to Uploader, this is a simple
  104. // process for small repository. For a big repository, save all the data to disk
  105. // before upload is better
  106. func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions) error {
  107. repo, err := downloader.GetRepoInfo()
  108. if err != nil {
  109. return err
  110. }
  111. repo.IsPrivate = opts.Private
  112. repo.IsMirror = opts.Mirror
  113. if opts.Description != "" {
  114. repo.Description = opts.Description
  115. }
  116. log.Trace("migrating git data")
  117. if err := uploader.CreateRepo(repo, opts); err != nil {
  118. return err
  119. }
  120. defer uploader.Close()
  121. log.Trace("migrating topics")
  122. topics, err := downloader.GetTopics()
  123. if err != nil {
  124. return err
  125. }
  126. if len(topics) > 0 {
  127. if err := uploader.CreateTopics(topics...); err != nil {
  128. return err
  129. }
  130. }
  131. if opts.Milestones {
  132. log.Trace("migrating milestones")
  133. milestones, err := downloader.GetMilestones()
  134. if err != nil {
  135. return err
  136. }
  137. msBatchSize := uploader.MaxBatchInsertSize("milestone")
  138. for len(milestones) > 0 {
  139. if len(milestones) < msBatchSize {
  140. msBatchSize = len(milestones)
  141. }
  142. if err := uploader.CreateMilestones(milestones...); err != nil {
  143. return err
  144. }
  145. milestones = milestones[msBatchSize:]
  146. }
  147. }
  148. if opts.Labels {
  149. log.Trace("migrating labels")
  150. labels, err := downloader.GetLabels()
  151. if err != nil {
  152. return err
  153. }
  154. lbBatchSize := uploader.MaxBatchInsertSize("label")
  155. for len(labels) > 0 {
  156. if len(labels) < lbBatchSize {
  157. lbBatchSize = len(labels)
  158. }
  159. if err := uploader.CreateLabels(labels...); err != nil {
  160. return err
  161. }
  162. labels = labels[lbBatchSize:]
  163. }
  164. }
  165. if opts.Releases {
  166. log.Trace("migrating releases")
  167. releases, err := downloader.GetReleases()
  168. if err != nil {
  169. return err
  170. }
  171. relBatchSize := uploader.MaxBatchInsertSize("release")
  172. for len(releases) > 0 {
  173. if len(releases) < relBatchSize {
  174. relBatchSize = len(releases)
  175. }
  176. if err := uploader.CreateReleases(downloader, releases[:relBatchSize]...); err != nil {
  177. return err
  178. }
  179. releases = releases[relBatchSize:]
  180. }
  181. // Once all releases (if any) are inserted, sync any remaining non-release tags
  182. if err := uploader.SyncTags(); err != nil {
  183. return err
  184. }
  185. }
  186. var (
  187. commentBatchSize = uploader.MaxBatchInsertSize("comment")
  188. reviewBatchSize = uploader.MaxBatchInsertSize("review")
  189. )
  190. if opts.Issues {
  191. log.Trace("migrating issues and comments")
  192. var issueBatchSize = uploader.MaxBatchInsertSize("issue")
  193. for i := 1; ; i++ {
  194. issues, isEnd, err := downloader.GetIssues(i, issueBatchSize)
  195. if err != nil {
  196. return err
  197. }
  198. if err := uploader.CreateIssues(issues...); err != nil {
  199. return err
  200. }
  201. if !opts.Comments {
  202. continue
  203. }
  204. var allComments = make([]*base.Comment, 0, commentBatchSize)
  205. for _, issue := range issues {
  206. comments, err := downloader.GetComments(issue.Number)
  207. if err != nil {
  208. return err
  209. }
  210. allComments = append(allComments, comments...)
  211. if len(allComments) >= commentBatchSize {
  212. if err := uploader.CreateComments(allComments[:commentBatchSize]...); err != nil {
  213. return err
  214. }
  215. allComments = allComments[commentBatchSize:]
  216. }
  217. }
  218. if len(allComments) > 0 {
  219. if err := uploader.CreateComments(allComments...); err != nil {
  220. return err
  221. }
  222. }
  223. if isEnd {
  224. break
  225. }
  226. }
  227. }
  228. if opts.PullRequests {
  229. log.Trace("migrating pull requests and comments")
  230. var prBatchSize = uploader.MaxBatchInsertSize("pullrequest")
  231. for i := 1; ; i++ {
  232. prs, isEnd, err := downloader.GetPullRequests(i, prBatchSize)
  233. if err != nil {
  234. return err
  235. }
  236. if err := uploader.CreatePullRequests(prs...); err != nil {
  237. return err
  238. }
  239. if !opts.Comments {
  240. continue
  241. }
  242. // plain comments
  243. var allComments = make([]*base.Comment, 0, commentBatchSize)
  244. for _, pr := range prs {
  245. comments, err := downloader.GetComments(pr.Number)
  246. if err != nil {
  247. return err
  248. }
  249. allComments = append(allComments, comments...)
  250. if len(allComments) >= commentBatchSize {
  251. if err := uploader.CreateComments(allComments[:commentBatchSize]...); err != nil {
  252. return err
  253. }
  254. allComments = allComments[commentBatchSize:]
  255. }
  256. }
  257. if len(allComments) > 0 {
  258. if err := uploader.CreateComments(allComments...); err != nil {
  259. return err
  260. }
  261. }
  262. // migrate reviews
  263. var allReviews = make([]*base.Review, 0, reviewBatchSize)
  264. for _, pr := range prs {
  265. number := pr.Number
  266. // on gitlab migrations pull number change
  267. if pr.OriginalNumber > 0 {
  268. number = pr.OriginalNumber
  269. }
  270. reviews, err := downloader.GetReviews(number)
  271. if pr.OriginalNumber > 0 {
  272. for i := range reviews {
  273. reviews[i].IssueIndex = pr.Number
  274. }
  275. }
  276. if err != nil {
  277. return err
  278. }
  279. allReviews = append(allReviews, reviews...)
  280. if len(allReviews) >= reviewBatchSize {
  281. if err := uploader.CreateReviews(allReviews[:reviewBatchSize]...); err != nil {
  282. return err
  283. }
  284. allReviews = allReviews[reviewBatchSize:]
  285. }
  286. }
  287. if len(allReviews) > 0 {
  288. if err := uploader.CreateReviews(allReviews...); err != nil {
  289. return err
  290. }
  291. }
  292. if isEnd {
  293. break
  294. }
  295. }
  296. }
  297. return nil
  298. }
  299. // Init migrations service
  300. func Init() error {
  301. var err error
  302. allowList, err = matchlist.NewMatchlist(setting.Migrations.AllowedDomains...)
  303. if err != nil {
  304. return fmt.Errorf("init migration allowList domains failed: %v", err)
  305. }
  306. blockList, err = matchlist.NewMatchlist(setting.Migrations.BlockedDomains...)
  307. if err != nil {
  308. return fmt.Errorf("init migration blockList domains failed: %v", err)
  309. }
  310. return nil
  311. }
  312. // isIPPrivate reports whether ip is a private address, according to
  313. // RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
  314. // from https://github.com/golang/go/pull/42793
  315. // TODO remove if https://github.com/golang/go/issues/29146 got resolved
  316. func isIPPrivate(ip net.IP) bool {
  317. if ip4 := ip.To4(); ip4 != nil {
  318. return ip4[0] == 10 ||
  319. (ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
  320. (ip4[0] == 192 && ip4[1] == 168)
  321. }
  322. return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
  323. }