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.

704 lines
18 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package migrations
  5. import (
  6. "bytes"
  7. "encoding/json"
  8. "fmt"
  9. "io/ioutil"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "strings"
  14. "time"
  15. "github.com/Unknwon/com"
  16. "github.com/go-xorm/xorm"
  17. "gopkg.in/ini.v1"
  18. "github.com/gogits/gogs/modules/log"
  19. "github.com/gogits/gogs/modules/setting"
  20. gouuid "github.com/gogits/gogs/modules/uuid"
  21. )
  22. const _MIN_DB_VER = 0
  23. type Migration interface {
  24. Description() string
  25. Migrate(*xorm.Engine) error
  26. }
  27. type migration struct {
  28. description string
  29. migrate func(*xorm.Engine) error
  30. }
  31. func NewMigration(desc string, fn func(*xorm.Engine) error) Migration {
  32. return &migration{desc, fn}
  33. }
  34. func (m *migration) Description() string {
  35. return m.description
  36. }
  37. func (m *migration) Migrate(x *xorm.Engine) error {
  38. return m.migrate(x)
  39. }
  40. // The version table. Should have only one row with id==1
  41. type Version struct {
  42. Id int64
  43. Version int64
  44. }
  45. // This is a sequence of migrations. Add new migrations to the bottom of the list.
  46. // If you want to "retire" a migration, remove it from the top of the list and
  47. // update _MIN_VER_DB accordingly
  48. var migrations = []Migration{
  49. NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1:v0.5.13
  50. NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2:v0.5.13
  51. NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3:v0.5.13
  52. NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4:v0.5.13
  53. NewMigration("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
  54. NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
  55. NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
  56. NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
  57. NewMigration("rename pull request fields", renamePullRequestFields), // V8 -> V9:v0.6.16
  58. NewMigration("clean up migrate repo info", cleanUpMigrateRepoInfo), // V9 -> V10:v0.6.20
  59. }
  60. // Migrate database to current version
  61. func Migrate(x *xorm.Engine) error {
  62. if err := x.Sync(new(Version)); err != nil {
  63. return fmt.Errorf("sync: %v", err)
  64. }
  65. currentVersion := &Version{Id: 1}
  66. has, err := x.Get(currentVersion)
  67. if err != nil {
  68. return fmt.Errorf("get: %v", err)
  69. } else if !has {
  70. // If the user table does not exist it is a fresh installation and we
  71. // can skip all migrations.
  72. needsMigration, err := x.IsTableExist("user")
  73. if err != nil {
  74. return err
  75. }
  76. if needsMigration {
  77. isEmpty, err := x.IsTableEmpty("user")
  78. if err != nil {
  79. return err
  80. }
  81. // If the user table is empty it is a fresh installation and we can
  82. // skip all migrations.
  83. needsMigration = !isEmpty
  84. }
  85. if !needsMigration {
  86. currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
  87. }
  88. if _, err = x.InsertOne(currentVersion); err != nil {
  89. return fmt.Errorf("insert: %v", err)
  90. }
  91. }
  92. v := currentVersion.Version
  93. if int(v-_MIN_DB_VER) > len(migrations) {
  94. // User downgraded Gogs.
  95. currentVersion.Version = int64(len(migrations) + _MIN_DB_VER)
  96. _, err = x.Id(1).Update(currentVersion)
  97. return err
  98. }
  99. for i, m := range migrations[v-_MIN_DB_VER:] {
  100. log.Info("Migration: %s", m.Description())
  101. if err = m.Migrate(x); err != nil {
  102. return fmt.Errorf("do migrate: %v", err)
  103. }
  104. currentVersion.Version = v + int64(i) + 1
  105. if _, err = x.Id(1).Update(currentVersion); err != nil {
  106. return err
  107. }
  108. }
  109. return nil
  110. }
  111. func sessionRelease(sess *xorm.Session) {
  112. if !sess.IsCommitedOrRollbacked {
  113. sess.Rollback()
  114. }
  115. sess.Close()
  116. }
  117. func accessToCollaboration(x *xorm.Engine) (err error) {
  118. type Collaboration struct {
  119. ID int64 `xorm:"pk autoincr"`
  120. RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
  121. UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
  122. Created time.Time
  123. }
  124. if err = x.Sync(new(Collaboration)); err != nil {
  125. return fmt.Errorf("sync: %v", err)
  126. }
  127. results, err := x.Query("SELECT u.id AS `uid`, a.repo_name AS `repo`, a.mode AS `mode`, a.created as `created` FROM `access` a JOIN `user` u ON a.user_name=u.lower_name")
  128. if err != nil {
  129. if strings.Contains(err.Error(), "no such column") {
  130. return nil
  131. }
  132. return err
  133. }
  134. sess := x.NewSession()
  135. defer sessionRelease(sess)
  136. if err = sess.Begin(); err != nil {
  137. return err
  138. }
  139. offset := strings.Split(time.Now().String(), " ")[2]
  140. for _, result := range results {
  141. mode := com.StrTo(result["mode"]).MustInt64()
  142. // Collaborators must have write access.
  143. if mode < 2 {
  144. continue
  145. }
  146. userID := com.StrTo(result["uid"]).MustInt64()
  147. repoRefName := string(result["repo"])
  148. var created time.Time
  149. switch {
  150. case setting.UseSQLite3:
  151. created, _ = time.Parse(time.RFC3339, string(result["created"]))
  152. case setting.UseMySQL:
  153. created, _ = time.Parse("2006-01-02 15:04:05-0700", string(result["created"])+offset)
  154. case setting.UsePostgreSQL:
  155. created, _ = time.Parse("2006-01-02T15:04:05Z-0700", string(result["created"])+offset)
  156. }
  157. // find owner of repository
  158. parts := strings.SplitN(repoRefName, "/", 2)
  159. ownerName := parts[0]
  160. repoName := parts[1]
  161. results, err := sess.Query("SELECT u.id as `uid`, ou.uid as `memberid` FROM `user` u LEFT JOIN org_user ou ON ou.org_id=u.id WHERE u.lower_name=?", ownerName)
  162. if err != nil {
  163. return err
  164. }
  165. if len(results) < 1 {
  166. continue
  167. }
  168. ownerID := com.StrTo(results[0]["uid"]).MustInt64()
  169. if ownerID == userID {
  170. continue
  171. }
  172. // test if user is member of owning organization
  173. isMember := false
  174. for _, member := range results {
  175. memberID := com.StrTo(member["memberid"]).MustInt64()
  176. // We can skip all cases that a user is member of the owning organization
  177. if memberID == userID {
  178. isMember = true
  179. }
  180. }
  181. if isMember {
  182. continue
  183. }
  184. results, err = sess.Query("SELECT id FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName)
  185. if err != nil {
  186. return err
  187. } else if len(results) < 1 {
  188. continue
  189. }
  190. collaboration := &Collaboration{
  191. UserID: userID,
  192. RepoID: com.StrTo(results[0]["id"]).MustInt64(),
  193. }
  194. has, err := sess.Get(collaboration)
  195. if err != nil {
  196. return err
  197. } else if has {
  198. continue
  199. }
  200. collaboration.Created = created
  201. if _, err = sess.InsertOne(collaboration); err != nil {
  202. return err
  203. }
  204. }
  205. return sess.Commit()
  206. }
  207. func ownerTeamUpdate(x *xorm.Engine) (err error) {
  208. if _, err := x.Exec("UPDATE `team` SET authorize=4 WHERE lower_name=?", "owners"); err != nil {
  209. return fmt.Errorf("update owner team table: %v", err)
  210. }
  211. return nil
  212. }
  213. func accessRefactor(x *xorm.Engine) (err error) {
  214. type (
  215. AccessMode int
  216. Access struct {
  217. ID int64 `xorm:"pk autoincr"`
  218. UserID int64 `xorm:"UNIQUE(s)"`
  219. RepoID int64 `xorm:"UNIQUE(s)"`
  220. Mode AccessMode
  221. }
  222. UserRepo struct {
  223. UserID int64
  224. RepoID int64
  225. }
  226. )
  227. // We consiously don't start a session yet as we make only reads for now, no writes
  228. accessMap := make(map[UserRepo]AccessMode, 50)
  229. results, err := x.Query("SELECT r.id AS `repo_id`, r.is_private AS `is_private`, r.owner_id AS `owner_id`, u.type AS `owner_type` FROM `repository` r LEFT JOIN `user` u ON r.owner_id=u.id")
  230. if err != nil {
  231. return fmt.Errorf("select repositories: %v", err)
  232. }
  233. for _, repo := range results {
  234. repoID := com.StrTo(repo["repo_id"]).MustInt64()
  235. isPrivate := com.StrTo(repo["is_private"]).MustInt() > 0
  236. ownerID := com.StrTo(repo["owner_id"]).MustInt64()
  237. ownerIsOrganization := com.StrTo(repo["owner_type"]).MustInt() > 0
  238. results, err := x.Query("SELECT `user_id` FROM `collaboration` WHERE repo_id=?", repoID)
  239. if err != nil {
  240. return fmt.Errorf("select collaborators: %v", err)
  241. }
  242. for _, user := range results {
  243. userID := com.StrTo(user["user_id"]).MustInt64()
  244. accessMap[UserRepo{userID, repoID}] = 2 // WRITE ACCESS
  245. }
  246. if !ownerIsOrganization {
  247. continue
  248. }
  249. // The minimum level to add a new access record,
  250. // because public repository has implicit open access.
  251. minAccessLevel := AccessMode(0)
  252. if !isPrivate {
  253. minAccessLevel = 1
  254. }
  255. repoString := "$" + string(repo["repo_id"]) + "|"
  256. results, err = x.Query("SELECT `id`,`authorize`,`repo_ids` FROM `team` WHERE org_id=? AND authorize>? ORDER BY `authorize` ASC", ownerID, int(minAccessLevel))
  257. if err != nil {
  258. if strings.Contains(err.Error(), "no such column") {
  259. return nil
  260. }
  261. return fmt.Errorf("select teams from org: %v", err)
  262. }
  263. for _, team := range results {
  264. if !strings.Contains(string(team["repo_ids"]), repoString) {
  265. continue
  266. }
  267. teamID := com.StrTo(team["id"]).MustInt64()
  268. mode := AccessMode(com.StrTo(team["authorize"]).MustInt())
  269. results, err := x.Query("SELECT `uid` FROM `team_user` WHERE team_id=?", teamID)
  270. if err != nil {
  271. return fmt.Errorf("select users from team: %v", err)
  272. }
  273. for _, user := range results {
  274. userID := com.StrTo(user["uid"]).MustInt64()
  275. accessMap[UserRepo{userID, repoID}] = mode
  276. }
  277. }
  278. }
  279. // Drop table can't be in a session (at least not in sqlite)
  280. if _, err = x.Exec("DROP TABLE `access`"); err != nil {
  281. return fmt.Errorf("drop access table: %v", err)
  282. }
  283. // Now we start writing so we make a session
  284. sess := x.NewSession()
  285. defer sessionRelease(sess)
  286. if err = sess.Begin(); err != nil {
  287. return err
  288. }
  289. if err = sess.Sync2(new(Access)); err != nil {
  290. return fmt.Errorf("sync: %v", err)
  291. }
  292. accesses := make([]*Access, 0, len(accessMap))
  293. for ur, mode := range accessMap {
  294. accesses = append(accesses, &Access{UserID: ur.UserID, RepoID: ur.RepoID, Mode: mode})
  295. }
  296. if _, err = sess.Insert(accesses); err != nil {
  297. return fmt.Errorf("insert accesses: %v", err)
  298. }
  299. return sess.Commit()
  300. }
  301. func teamToTeamRepo(x *xorm.Engine) error {
  302. type TeamRepo struct {
  303. ID int64 `xorm:"pk autoincr"`
  304. OrgID int64 `xorm:"INDEX"`
  305. TeamID int64 `xorm:"UNIQUE(s)"`
  306. RepoID int64 `xorm:"UNIQUE(s)"`
  307. }
  308. teamRepos := make([]*TeamRepo, 0, 50)
  309. results, err := x.Query("SELECT `id`,`org_id`,`repo_ids` FROM `team`")
  310. if err != nil {
  311. if strings.Contains(err.Error(), "no such column") {
  312. return nil
  313. }
  314. return fmt.Errorf("select teams: %v", err)
  315. }
  316. for _, team := range results {
  317. orgID := com.StrTo(team["org_id"]).MustInt64()
  318. teamID := com.StrTo(team["id"]).MustInt64()
  319. // #1032: legacy code can have duplicated IDs for same repository.
  320. mark := make(map[int64]bool)
  321. for _, idStr := range strings.Split(string(team["repo_ids"]), "|") {
  322. repoID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
  323. if repoID == 0 || mark[repoID] {
  324. continue
  325. }
  326. mark[repoID] = true
  327. teamRepos = append(teamRepos, &TeamRepo{
  328. OrgID: orgID,
  329. TeamID: teamID,
  330. RepoID: repoID,
  331. })
  332. }
  333. }
  334. sess := x.NewSession()
  335. defer sessionRelease(sess)
  336. if err = sess.Begin(); err != nil {
  337. return err
  338. }
  339. if err = sess.Sync2(new(TeamRepo)); err != nil {
  340. return fmt.Errorf("sync2: %v", err)
  341. } else if _, err = sess.Insert(teamRepos); err != nil {
  342. return fmt.Errorf("insert team-repos: %v", err)
  343. }
  344. return sess.Commit()
  345. }
  346. func fixLocaleFileLoadPanic(_ *xorm.Engine) error {
  347. cfg, err := ini.Load(setting.CustomConf)
  348. if err != nil {
  349. return fmt.Errorf("load custom config: %v", err)
  350. }
  351. cfg.DeleteSection("i18n")
  352. if err = cfg.SaveTo(setting.CustomConf); err != nil {
  353. return fmt.Errorf("save custom config: %v", err)
  354. }
  355. setting.Langs = strings.Split(strings.Replace(strings.Join(setting.Langs, ","), "fr-CA", "fr-FR", 1), ",")
  356. return nil
  357. }
  358. func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
  359. type PushCommit struct {
  360. Sha1 string
  361. Message string
  362. AuthorEmail string
  363. AuthorName string
  364. }
  365. type PushCommits struct {
  366. Len int
  367. Commits []*PushCommit
  368. CompareUrl string
  369. }
  370. type Action struct {
  371. ID int64 `xorm:"pk autoincr"`
  372. Content string `xorm:"TEXT"`
  373. }
  374. results, err := x.Query("SELECT `id`,`content` FROM `action` WHERE `op_type`=?", 5)
  375. if err != nil {
  376. return fmt.Errorf("select commit actions: %v", err)
  377. }
  378. sess := x.NewSession()
  379. defer sessionRelease(sess)
  380. if err = sess.Begin(); err != nil {
  381. return err
  382. }
  383. var pushCommits *PushCommits
  384. for _, action := range results {
  385. actID := com.StrTo(string(action["id"])).MustInt64()
  386. if actID == 0 {
  387. continue
  388. }
  389. pushCommits = new(PushCommits)
  390. if err = json.Unmarshal(action["content"], pushCommits); err != nil {
  391. return fmt.Errorf("unmarshal action content[%d]: %v", actID, err)
  392. }
  393. infos := strings.Split(pushCommits.CompareUrl, "/")
  394. if len(infos) <= 4 {
  395. continue
  396. }
  397. pushCommits.CompareUrl = strings.Join(infos[len(infos)-4:], "/")
  398. p, err := json.Marshal(pushCommits)
  399. if err != nil {
  400. return fmt.Errorf("marshal action content[%d]: %v", actID, err)
  401. }
  402. if _, err = sess.Id(actID).Update(&Action{
  403. Content: string(p),
  404. }); err != nil {
  405. return fmt.Errorf("update action[%d]: %v", actID, err)
  406. }
  407. }
  408. return sess.Commit()
  409. }
  410. func issueToIssueLabel(x *xorm.Engine) error {
  411. type IssueLabel struct {
  412. ID int64 `xorm:"pk autoincr"`
  413. IssueID int64 `xorm:"UNIQUE(s)"`
  414. LabelID int64 `xorm:"UNIQUE(s)"`
  415. }
  416. issueLabels := make([]*IssueLabel, 0, 50)
  417. results, err := x.Query("SELECT `id`,`label_ids` FROM `issue`")
  418. if err != nil {
  419. if strings.Contains(err.Error(), "no such column") {
  420. return nil
  421. }
  422. return fmt.Errorf("select issues: %v", err)
  423. }
  424. for _, issue := range results {
  425. issueID := com.StrTo(issue["id"]).MustInt64()
  426. // Just in case legacy code can have duplicated IDs for same label.
  427. mark := make(map[int64]bool)
  428. for _, idStr := range strings.Split(string(issue["label_ids"]), "|") {
  429. labelID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
  430. if labelID == 0 || mark[labelID] {
  431. continue
  432. }
  433. mark[labelID] = true
  434. issueLabels = append(issueLabels, &IssueLabel{
  435. IssueID: issueID,
  436. LabelID: labelID,
  437. })
  438. }
  439. }
  440. sess := x.NewSession()
  441. defer sessionRelease(sess)
  442. if err = sess.Begin(); err != nil {
  443. return err
  444. }
  445. if err = sess.Sync2(new(IssueLabel)); err != nil {
  446. return fmt.Errorf("sync2: %v", err)
  447. } else if _, err = sess.Insert(issueLabels); err != nil {
  448. return fmt.Errorf("insert issue-labels: %v", err)
  449. }
  450. return sess.Commit()
  451. }
  452. func attachmentRefactor(x *xorm.Engine) error {
  453. type Attachment struct {
  454. ID int64 `xorm:"pk autoincr"`
  455. UUID string `xorm:"uuid INDEX"`
  456. // For rename purpose.
  457. Path string `xorm:"-"`
  458. NewPath string `xorm:"-"`
  459. }
  460. results, err := x.Query("SELECT * FROM `attachment`")
  461. if err != nil {
  462. return fmt.Errorf("select attachments: %v", err)
  463. }
  464. attachments := make([]*Attachment, 0, len(results))
  465. for _, attach := range results {
  466. if !com.IsExist(string(attach["path"])) {
  467. // If the attachment is already missing, there is no point to update it.
  468. continue
  469. }
  470. attachments = append(attachments, &Attachment{
  471. ID: com.StrTo(attach["id"]).MustInt64(),
  472. UUID: gouuid.NewV4().String(),
  473. Path: string(attach["path"]),
  474. })
  475. }
  476. sess := x.NewSession()
  477. defer sessionRelease(sess)
  478. if err = sess.Begin(); err != nil {
  479. return err
  480. }
  481. if err = sess.Sync2(new(Attachment)); err != nil {
  482. return fmt.Errorf("Sync2: %v", err)
  483. }
  484. // Note: Roll back for rename can be a dead loop,
  485. // so produces a backup file.
  486. var buf bytes.Buffer
  487. buf.WriteString("# old path -> new path\n")
  488. // Update database first because this is where error happens the most often.
  489. for _, attach := range attachments {
  490. if _, err = sess.Id(attach.ID).Update(attach); err != nil {
  491. return err
  492. }
  493. attach.NewPath = path.Join(setting.AttachmentPath, attach.UUID[0:1], attach.UUID[1:2], attach.UUID)
  494. buf.WriteString(attach.Path)
  495. buf.WriteString("\t")
  496. buf.WriteString(attach.NewPath)
  497. buf.WriteString("\n")
  498. }
  499. // Then rename attachments.
  500. isSucceed := true
  501. defer func() {
  502. if isSucceed {
  503. return
  504. }
  505. dumpPath := path.Join(setting.LogRootPath, "attachment_path.dump")
  506. ioutil.WriteFile(dumpPath, buf.Bytes(), 0666)
  507. fmt.Println("Fail to rename some attachments, old and new paths are saved into:", dumpPath)
  508. }()
  509. for _, attach := range attachments {
  510. if err = os.MkdirAll(path.Dir(attach.NewPath), os.ModePerm); err != nil {
  511. isSucceed = false
  512. return err
  513. }
  514. if err = os.Rename(attach.Path, attach.NewPath); err != nil {
  515. isSucceed = false
  516. return err
  517. }
  518. }
  519. return sess.Commit()
  520. }
  521. func renamePullRequestFields(x *xorm.Engine) (err error) {
  522. type PullRequest struct {
  523. ID int64 `xorm:"pk autoincr"`
  524. PullID int64 `xorm:"INDEX"`
  525. PullIndex int64
  526. HeadBarcnh string
  527. IssueID int64 `xorm:"INDEX"`
  528. Index int64
  529. HeadBranch string
  530. }
  531. if err = x.Sync(new(PullRequest)); err != nil {
  532. return fmt.Errorf("sync: %v", err)
  533. }
  534. results, err := x.Query("SELECT `id`,`pull_id`,`pull_index`,`head_barcnh` FROM `pull_request`")
  535. if err != nil {
  536. if strings.Contains(err.Error(), "no such column") {
  537. return nil
  538. }
  539. return fmt.Errorf("select pull requests: %v", err)
  540. }
  541. sess := x.NewSession()
  542. defer sessionRelease(sess)
  543. if err = sess.Begin(); err != nil {
  544. return err
  545. }
  546. var pull *PullRequest
  547. for _, pr := range results {
  548. pull = &PullRequest{
  549. ID: com.StrTo(pr["id"]).MustInt64(),
  550. IssueID: com.StrTo(pr["pull_id"]).MustInt64(),
  551. Index: com.StrTo(pr["pull_index"]).MustInt64(),
  552. HeadBranch: string(pr["head_barcnh"]),
  553. }
  554. if _, err = sess.Id(pull.ID).Update(pull); err != nil {
  555. return err
  556. }
  557. }
  558. return sess.Commit()
  559. }
  560. func cleanUpMigrateRepoInfo(x *xorm.Engine) (err error) {
  561. type (
  562. User struct {
  563. ID int64 `xorm:"pk autoincr"`
  564. LowerName string
  565. }
  566. Repository struct {
  567. ID int64 `xorm:"pk autoincr"`
  568. OwnerID int64
  569. LowerName string
  570. }
  571. )
  572. repos := make([]*Repository, 0, 25)
  573. if err = x.Where("is_mirror=?", false).Find(&repos); err != nil {
  574. return fmt.Errorf("select all non-mirror repositories: %v", err)
  575. }
  576. var user *User
  577. for _, repo := range repos {
  578. user = &User{ID: repo.OwnerID}
  579. has, err := x.Get(user)
  580. if err != nil {
  581. return fmt.Errorf("get owner of repository[%d - %d]: %v", repo.ID, repo.OwnerID, err)
  582. } else if !has {
  583. continue
  584. }
  585. configPath := filepath.Join(setting.RepoRootPath, user.LowerName, repo.LowerName+".git/config")
  586. // In case repository file is somehow missing.
  587. if !com.IsFile(configPath) {
  588. continue
  589. }
  590. cfg, err := ini.Load(configPath)
  591. if err != nil {
  592. return fmt.Errorf("open config file: %v", err)
  593. }
  594. cfg.DeleteSection("remote \"origin\"")
  595. if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
  596. return fmt.Errorf("save config file: %v", err)
  597. }
  598. }
  599. return nil
  600. }