Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

migrate.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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. "path/filepath"
  12. "strings"
  13. "code.gitea.io/gitea/models"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/matchlist"
  16. "code.gitea.io/gitea/modules/migrations/base"
  17. "code.gitea.io/gitea/modules/setting"
  18. "code.gitea.io/gitea/modules/util"
  19. )
  20. // MigrateOptions is equal to base.MigrateOptions
  21. type MigrateOptions = base.MigrateOptions
  22. var (
  23. factories []base.DownloaderFactory
  24. allowList *matchlist.Matchlist
  25. blockList *matchlist.Matchlist
  26. )
  27. // RegisterDownloaderFactory registers a downloader factory
  28. func RegisterDownloaderFactory(factory base.DownloaderFactory) {
  29. factories = append(factories, factory)
  30. }
  31. // IsMigrateURLAllowed checks if an URL is allowed to be migrated from
  32. func IsMigrateURLAllowed(remoteURL string, doer *models.User) error {
  33. // Remote address can be HTTP/HTTPS/Git URL or local path.
  34. u, err := url.Parse(strings.ToLower(remoteURL))
  35. if err != nil {
  36. return &models.ErrInvalidCloneAddr{IsURLError: true}
  37. }
  38. if u.Scheme == "file" || u.Scheme == "" {
  39. if !doer.CanImportLocal() {
  40. return &models.ErrInvalidCloneAddr{Host: "<LOCAL_FILESYSTEM>", IsPermissionDenied: true, LocalPath: true}
  41. }
  42. isAbs := filepath.IsAbs(u.Host + u.Path)
  43. if !isAbs {
  44. return &models.ErrInvalidCloneAddr{Host: "<LOCAL_FILESYSTEM>", IsInvalidPath: true, LocalPath: true}
  45. }
  46. isDir, err := util.IsDir(u.Host + u.Path)
  47. if err != nil {
  48. log.Error("Unable to check if %s is a directory: %v", u.Host+u.Path, err)
  49. return err
  50. }
  51. if !isDir {
  52. return &models.ErrInvalidCloneAddr{Host: "<LOCAL_FILESYSTEM>", IsInvalidPath: true, LocalPath: true}
  53. }
  54. return nil
  55. }
  56. if u.Scheme == "git" && u.Port() != "" && (strings.Contains(remoteURL, "%0d") || strings.Contains(remoteURL, "%0a")) {
  57. return &models.ErrInvalidCloneAddr{Host: u.Host, IsURLError: true}
  58. }
  59. if u.Opaque != "" || u.Scheme != "" && u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "git" {
  60. return &models.ErrInvalidCloneAddr{Host: u.Host, IsProtocolInvalid: true, IsPermissionDenied: true, IsURLError: true}
  61. }
  62. if len(setting.Migrations.AllowedDomains) > 0 {
  63. if !allowList.Match(u.Host) {
  64. return &models.ErrInvalidCloneAddr{Host: u.Host, IsPermissionDenied: true}
  65. }
  66. } else {
  67. if blockList.Match(u.Host) {
  68. return &models.ErrInvalidCloneAddr{Host: u.Host, IsPermissionDenied: true}
  69. }
  70. }
  71. if !setting.Migrations.AllowLocalNetworks {
  72. addrList, err := net.LookupIP(strings.Split(u.Host, ":")[0])
  73. if err != nil {
  74. return &models.ErrInvalidCloneAddr{Host: u.Host, NotResolvedIP: true}
  75. }
  76. for _, addr := range addrList {
  77. if isIPPrivate(addr) || !addr.IsGlobalUnicast() {
  78. return &models.ErrInvalidCloneAddr{Host: u.Host, PrivateNet: addr.String(), IsPermissionDenied: true}
  79. }
  80. }
  81. }
  82. return nil
  83. }
  84. // MigrateRepository migrate repository according MigrateOptions
  85. func MigrateRepository(ctx context.Context, doer *models.User, ownerName string, opts base.MigrateOptions) (*models.Repository, error) {
  86. err := IsMigrateURLAllowed(opts.CloneAddr, doer)
  87. if err != nil {
  88. return nil, err
  89. }
  90. downloader, err := newDownloader(ctx, ownerName, opts)
  91. if err != nil {
  92. return nil, err
  93. }
  94. var uploader = NewGiteaLocalUploader(ctx, doer, ownerName, opts.RepoName)
  95. uploader.gitServiceType = opts.GitServiceType
  96. if err := migrateRepository(downloader, uploader, opts); err != nil {
  97. if err1 := uploader.Rollback(); err1 != nil {
  98. log.Error("rollback failed: %v", err1)
  99. }
  100. if err2 := models.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.OriginalURL, err)); err2 != nil {
  101. log.Error("create respotiry notice failed: ", err2)
  102. }
  103. return nil, err
  104. }
  105. return uploader.repo, nil
  106. }
  107. func newDownloader(ctx context.Context, ownerName string, opts base.MigrateOptions) (base.Downloader, error) {
  108. var (
  109. downloader base.Downloader
  110. err error
  111. )
  112. for _, factory := range factories {
  113. if factory.GitServiceType() == opts.GitServiceType {
  114. downloader, err = factory.New(ctx, opts)
  115. if err != nil {
  116. return nil, err
  117. }
  118. break
  119. }
  120. }
  121. if downloader == nil {
  122. opts.Wiki = true
  123. opts.Milestones = false
  124. opts.Labels = false
  125. opts.Releases = false
  126. opts.Comments = false
  127. opts.Issues = false
  128. opts.PullRequests = false
  129. downloader = NewPlainGitDownloader(ownerName, opts.RepoName, opts.CloneAddr)
  130. log.Trace("Will migrate from git: %s", opts.OriginalURL)
  131. }
  132. if setting.Migrations.MaxAttempts > 1 {
  133. downloader = base.NewRetryDownloader(ctx, downloader, setting.Migrations.MaxAttempts, setting.Migrations.RetryBackoff)
  134. }
  135. return downloader, nil
  136. }
  137. // migrateRepository will download information and then upload it to Uploader, this is a simple
  138. // process for small repository. For a big repository, save all the data to disk
  139. // before upload is better
  140. func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions) error {
  141. repo, err := downloader.GetRepoInfo()
  142. if err != nil {
  143. if !base.IsErrNotSupported(err) {
  144. return err
  145. }
  146. log.Info("migrating repo infos is not supported, ignored")
  147. }
  148. repo.IsPrivate = opts.Private
  149. repo.IsMirror = opts.Mirror
  150. if opts.Description != "" {
  151. repo.Description = opts.Description
  152. }
  153. if repo.CloneURL, err = downloader.FormatCloneURL(opts, repo.CloneURL); err != nil {
  154. return err
  155. }
  156. log.Trace("migrating git data")
  157. if err = uploader.CreateRepo(repo, opts); err != nil {
  158. return err
  159. }
  160. defer uploader.Close()
  161. log.Trace("migrating topics")
  162. topics, err := downloader.GetTopics()
  163. if err != nil {
  164. if !base.IsErrNotSupported(err) {
  165. return err
  166. }
  167. log.Warn("migrating topics is not supported, ignored")
  168. }
  169. if len(topics) != 0 {
  170. if err = uploader.CreateTopics(topics...); err != nil {
  171. return err
  172. }
  173. }
  174. if opts.Milestones {
  175. log.Trace("migrating milestones")
  176. milestones, err := downloader.GetMilestones()
  177. if err != nil {
  178. if !base.IsErrNotSupported(err) {
  179. return err
  180. }
  181. log.Warn("migrating milestones is not supported, ignored")
  182. }
  183. msBatchSize := uploader.MaxBatchInsertSize("milestone")
  184. for len(milestones) > 0 {
  185. if len(milestones) < msBatchSize {
  186. msBatchSize = len(milestones)
  187. }
  188. if err := uploader.CreateMilestones(milestones...); err != nil {
  189. return err
  190. }
  191. milestones = milestones[msBatchSize:]
  192. }
  193. }
  194. if opts.Labels {
  195. log.Trace("migrating labels")
  196. labels, err := downloader.GetLabels()
  197. if err != nil {
  198. if !base.IsErrNotSupported(err) {
  199. return err
  200. }
  201. log.Warn("migrating labels is not supported, ignored")
  202. }
  203. lbBatchSize := uploader.MaxBatchInsertSize("label")
  204. for len(labels) > 0 {
  205. if len(labels) < lbBatchSize {
  206. lbBatchSize = len(labels)
  207. }
  208. if err := uploader.CreateLabels(labels...); err != nil {
  209. return err
  210. }
  211. labels = labels[lbBatchSize:]
  212. }
  213. }
  214. if opts.Releases {
  215. log.Trace("migrating releases")
  216. releases, err := downloader.GetReleases()
  217. if err != nil {
  218. if !base.IsErrNotSupported(err) {
  219. return err
  220. }
  221. log.Warn("migrating releases is not supported, ignored")
  222. }
  223. relBatchSize := uploader.MaxBatchInsertSize("release")
  224. for len(releases) > 0 {
  225. if len(releases) < relBatchSize {
  226. relBatchSize = len(releases)
  227. }
  228. if err = uploader.CreateReleases(releases[:relBatchSize]...); err != nil {
  229. return err
  230. }
  231. releases = releases[relBatchSize:]
  232. }
  233. // Once all releases (if any) are inserted, sync any remaining non-release tags
  234. if err = uploader.SyncTags(); err != nil {
  235. return err
  236. }
  237. }
  238. var (
  239. commentBatchSize = uploader.MaxBatchInsertSize("comment")
  240. reviewBatchSize = uploader.MaxBatchInsertSize("review")
  241. )
  242. if opts.Issues {
  243. log.Trace("migrating issues and comments")
  244. var issueBatchSize = uploader.MaxBatchInsertSize("issue")
  245. for i := 1; ; i++ {
  246. issues, isEnd, err := downloader.GetIssues(i, issueBatchSize)
  247. if err != nil {
  248. if !base.IsErrNotSupported(err) {
  249. return err
  250. }
  251. log.Warn("migrating issues is not supported, ignored")
  252. break
  253. }
  254. if err := uploader.CreateIssues(issues...); err != nil {
  255. return err
  256. }
  257. if opts.Comments {
  258. var allComments = make([]*base.Comment, 0, commentBatchSize)
  259. for _, issue := range issues {
  260. log.Trace("migrating issue %d's comments", issue.Number)
  261. comments, err := downloader.GetComments(issue.Number)
  262. if err != nil {
  263. if !base.IsErrNotSupported(err) {
  264. return err
  265. }
  266. log.Warn("migrating comments is not supported, ignored")
  267. }
  268. allComments = append(allComments, comments...)
  269. if len(allComments) >= commentBatchSize {
  270. if err = uploader.CreateComments(allComments[:commentBatchSize]...); err != nil {
  271. return err
  272. }
  273. allComments = allComments[commentBatchSize:]
  274. }
  275. }
  276. if len(allComments) > 0 {
  277. if err = uploader.CreateComments(allComments...); err != nil {
  278. return err
  279. }
  280. }
  281. }
  282. if isEnd {
  283. break
  284. }
  285. }
  286. }
  287. if opts.PullRequests {
  288. log.Trace("migrating pull requests and comments")
  289. var prBatchSize = uploader.MaxBatchInsertSize("pullrequest")
  290. for i := 1; ; i++ {
  291. prs, isEnd, err := downloader.GetPullRequests(i, prBatchSize)
  292. if err != nil {
  293. if !base.IsErrNotSupported(err) {
  294. return err
  295. }
  296. log.Warn("migrating pull requests is not supported, ignored")
  297. break
  298. }
  299. if err := uploader.CreatePullRequests(prs...); err != nil {
  300. return err
  301. }
  302. if opts.Comments {
  303. // plain comments
  304. var allComments = make([]*base.Comment, 0, commentBatchSize)
  305. for _, pr := range prs {
  306. log.Trace("migrating pull request %d's comments", pr.Number)
  307. comments, err := downloader.GetComments(pr.Number)
  308. if err != nil {
  309. if !base.IsErrNotSupported(err) {
  310. return err
  311. }
  312. log.Warn("migrating comments is not supported, ignored")
  313. }
  314. allComments = append(allComments, comments...)
  315. if len(allComments) >= commentBatchSize {
  316. if err = uploader.CreateComments(allComments[:commentBatchSize]...); err != nil {
  317. return err
  318. }
  319. allComments = allComments[commentBatchSize:]
  320. }
  321. }
  322. if len(allComments) > 0 {
  323. if err = uploader.CreateComments(allComments...); err != nil {
  324. return err
  325. }
  326. }
  327. // migrate reviews
  328. var allReviews = make([]*base.Review, 0, reviewBatchSize)
  329. for _, pr := range prs {
  330. number := pr.Number
  331. // on gitlab migrations pull number change
  332. if pr.OriginalNumber > 0 {
  333. number = pr.OriginalNumber
  334. }
  335. reviews, err := downloader.GetReviews(number)
  336. if err != nil {
  337. if !base.IsErrNotSupported(err) {
  338. return err
  339. }
  340. log.Warn("migrating reviews is not supported, ignored")
  341. break
  342. }
  343. if pr.OriginalNumber > 0 {
  344. for i := range reviews {
  345. reviews[i].IssueIndex = pr.Number
  346. }
  347. }
  348. allReviews = append(allReviews, reviews...)
  349. if len(allReviews) >= reviewBatchSize {
  350. if err = uploader.CreateReviews(allReviews[:reviewBatchSize]...); err != nil {
  351. return err
  352. }
  353. allReviews = allReviews[reviewBatchSize:]
  354. }
  355. }
  356. if len(allReviews) > 0 {
  357. if err = uploader.CreateReviews(allReviews...); err != nil {
  358. return err
  359. }
  360. }
  361. }
  362. if isEnd {
  363. break
  364. }
  365. }
  366. }
  367. return uploader.Finish()
  368. }
  369. // Init migrations service
  370. func Init() error {
  371. var err error
  372. allowList, err = matchlist.NewMatchlist(setting.Migrations.AllowedDomains...)
  373. if err != nil {
  374. return fmt.Errorf("init migration allowList domains failed: %v", err)
  375. }
  376. blockList, err = matchlist.NewMatchlist(setting.Migrations.BlockedDomains...)
  377. if err != nil {
  378. return fmt.Errorf("init migration blockList domains failed: %v", err)
  379. }
  380. return nil
  381. }
  382. // isIPPrivate reports whether ip is a private address, according to
  383. // RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
  384. // from https://github.com/golang/go/pull/42793
  385. // TODO remove if https://github.com/golang/go/issues/29146 got resolved
  386. func isIPPrivate(ip net.IP) bool {
  387. if ip4 := ip.To4(); ip4 != nil {
  388. return ip4[0] == 10 ||
  389. (ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
  390. (ip4[0] == 192 && ip4[1] == 168)
  391. }
  392. return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
  393. }