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.

455 lines
12 KiB

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. "github.com/Unknwon/com"
  15. "github.com/go-xorm/xorm"
  16. "gopkg.in/ini.v1"
  17. "github.com/gogits/gogs/modules/base"
  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 = 4
  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("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
  50. NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
  51. NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
  52. NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
  53. NewMigration("rename pull request fields", renamePullRequestFields), // V8 -> V9:v0.6.16
  54. NewMigration("clean up migrate repo info", cleanUpMigrateRepoInfo), // V9 -> V10:v0.6.20
  55. NewMigration("generate rands and salt for organizations", generateOrgRandsAndSalt), // V10 -> V11:v0.8.5
  56. }
  57. // Migrate database to current version
  58. func Migrate(x *xorm.Engine) error {
  59. if err := x.Sync(new(Version)); err != nil {
  60. return fmt.Errorf("sync: %v", err)
  61. }
  62. currentVersion := &Version{Id: 1}
  63. has, err := x.Get(currentVersion)
  64. if err != nil {
  65. return fmt.Errorf("get: %v", err)
  66. } else if !has {
  67. // If the version record does not exist we think
  68. // it is a fresh installation and we can skip all migrations.
  69. currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
  70. if _, err = x.InsertOne(currentVersion); err != nil {
  71. return fmt.Errorf("insert: %v", err)
  72. }
  73. }
  74. v := currentVersion.Version
  75. if _MIN_DB_VER > v {
  76. log.Fatal(4, `Gogs no longer supports auto-migration from your previously installed version.
  77. Please try to upgrade to a lower version (>= v0.6.0) first, then upgrade to current version.`)
  78. return nil
  79. }
  80. if int(v-_MIN_DB_VER) > len(migrations) {
  81. // User downgraded Gogs.
  82. currentVersion.Version = int64(len(migrations) + _MIN_DB_VER)
  83. _, err = x.Id(1).Update(currentVersion)
  84. return err
  85. }
  86. for i, m := range migrations[v-_MIN_DB_VER:] {
  87. log.Info("Migration: %s", m.Description())
  88. if err = m.Migrate(x); err != nil {
  89. return fmt.Errorf("do migrate: %v", err)
  90. }
  91. currentVersion.Version = v + int64(i) + 1
  92. if _, err = x.Id(1).Update(currentVersion); err != nil {
  93. return err
  94. }
  95. }
  96. return nil
  97. }
  98. func sessionRelease(sess *xorm.Session) {
  99. if !sess.IsCommitedOrRollbacked {
  100. sess.Rollback()
  101. }
  102. sess.Close()
  103. }
  104. func fixLocaleFileLoadPanic(_ *xorm.Engine) error {
  105. cfg, err := ini.Load(setting.CustomConf)
  106. if err != nil {
  107. return fmt.Errorf("load custom config: %v", err)
  108. }
  109. cfg.DeleteSection("i18n")
  110. if err = cfg.SaveTo(setting.CustomConf); err != nil {
  111. return fmt.Errorf("save custom config: %v", err)
  112. }
  113. setting.Langs = strings.Split(strings.Replace(strings.Join(setting.Langs, ","), "fr-CA", "fr-FR", 1), ",")
  114. return nil
  115. }
  116. func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
  117. type PushCommit struct {
  118. Sha1 string
  119. Message string
  120. AuthorEmail string
  121. AuthorName string
  122. }
  123. type PushCommits struct {
  124. Len int
  125. Commits []*PushCommit
  126. CompareUrl string
  127. }
  128. type Action struct {
  129. ID int64 `xorm:"pk autoincr"`
  130. Content string `xorm:"TEXT"`
  131. }
  132. results, err := x.Query("SELECT `id`,`content` FROM `action` WHERE `op_type`=?", 5)
  133. if err != nil {
  134. return fmt.Errorf("select commit actions: %v", err)
  135. }
  136. sess := x.NewSession()
  137. defer sessionRelease(sess)
  138. if err = sess.Begin(); err != nil {
  139. return err
  140. }
  141. var pushCommits *PushCommits
  142. for _, action := range results {
  143. actID := com.StrTo(string(action["id"])).MustInt64()
  144. if actID == 0 {
  145. continue
  146. }
  147. pushCommits = new(PushCommits)
  148. if err = json.Unmarshal(action["content"], pushCommits); err != nil {
  149. return fmt.Errorf("unmarshal action content[%d]: %v", actID, err)
  150. }
  151. infos := strings.Split(pushCommits.CompareUrl, "/")
  152. if len(infos) <= 4 {
  153. continue
  154. }
  155. pushCommits.CompareUrl = strings.Join(infos[len(infos)-4:], "/")
  156. p, err := json.Marshal(pushCommits)
  157. if err != nil {
  158. return fmt.Errorf("marshal action content[%d]: %v", actID, err)
  159. }
  160. if _, err = sess.Id(actID).Update(&Action{
  161. Content: string(p),
  162. }); err != nil {
  163. return fmt.Errorf("update action[%d]: %v", actID, err)
  164. }
  165. }
  166. return sess.Commit()
  167. }
  168. func issueToIssueLabel(x *xorm.Engine) error {
  169. type IssueLabel struct {
  170. ID int64 `xorm:"pk autoincr"`
  171. IssueID int64 `xorm:"UNIQUE(s)"`
  172. LabelID int64 `xorm:"UNIQUE(s)"`
  173. }
  174. issueLabels := make([]*IssueLabel, 0, 50)
  175. results, err := x.Query("SELECT `id`,`label_ids` FROM `issue`")
  176. if err != nil {
  177. if strings.Contains(err.Error(), "no such column") ||
  178. strings.Contains(err.Error(), "Unknown column") {
  179. return nil
  180. }
  181. return fmt.Errorf("select issues: %v", err)
  182. }
  183. for _, issue := range results {
  184. issueID := com.StrTo(issue["id"]).MustInt64()
  185. // Just in case legacy code can have duplicated IDs for same label.
  186. mark := make(map[int64]bool)
  187. for _, idStr := range strings.Split(string(issue["label_ids"]), "|") {
  188. labelID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
  189. if labelID == 0 || mark[labelID] {
  190. continue
  191. }
  192. mark[labelID] = true
  193. issueLabels = append(issueLabels, &IssueLabel{
  194. IssueID: issueID,
  195. LabelID: labelID,
  196. })
  197. }
  198. }
  199. sess := x.NewSession()
  200. defer sessionRelease(sess)
  201. if err = sess.Begin(); err != nil {
  202. return err
  203. }
  204. if err = sess.Sync2(new(IssueLabel)); err != nil {
  205. return fmt.Errorf("sync2: %v", err)
  206. } else if _, err = sess.Insert(issueLabels); err != nil {
  207. return fmt.Errorf("insert issue-labels: %v", err)
  208. }
  209. return sess.Commit()
  210. }
  211. func attachmentRefactor(x *xorm.Engine) error {
  212. type Attachment struct {
  213. ID int64 `xorm:"pk autoincr"`
  214. UUID string `xorm:"uuid INDEX"`
  215. // For rename purpose.
  216. Path string `xorm:"-"`
  217. NewPath string `xorm:"-"`
  218. }
  219. results, err := x.Query("SELECT * FROM `attachment`")
  220. if err != nil {
  221. return fmt.Errorf("select attachments: %v", err)
  222. }
  223. attachments := make([]*Attachment, 0, len(results))
  224. for _, attach := range results {
  225. if !com.IsExist(string(attach["path"])) {
  226. // If the attachment is already missing, there is no point to update it.
  227. continue
  228. }
  229. attachments = append(attachments, &Attachment{
  230. ID: com.StrTo(attach["id"]).MustInt64(),
  231. UUID: gouuid.NewV4().String(),
  232. Path: string(attach["path"]),
  233. })
  234. }
  235. sess := x.NewSession()
  236. defer sessionRelease(sess)
  237. if err = sess.Begin(); err != nil {
  238. return err
  239. }
  240. if err = sess.Sync2(new(Attachment)); err != nil {
  241. return fmt.Errorf("Sync2: %v", err)
  242. }
  243. // Note: Roll back for rename can be a dead loop,
  244. // so produces a backup file.
  245. var buf bytes.Buffer
  246. buf.WriteString("# old path -> new path\n")
  247. // Update database first because this is where error happens the most often.
  248. for _, attach := range attachments {
  249. if _, err = sess.Id(attach.ID).Update(attach); err != nil {
  250. return err
  251. }
  252. attach.NewPath = path.Join(setting.AttachmentPath, attach.UUID[0:1], attach.UUID[1:2], attach.UUID)
  253. buf.WriteString(attach.Path)
  254. buf.WriteString("\t")
  255. buf.WriteString(attach.NewPath)
  256. buf.WriteString("\n")
  257. }
  258. // Then rename attachments.
  259. isSucceed := true
  260. defer func() {
  261. if isSucceed {
  262. return
  263. }
  264. dumpPath := path.Join(setting.LogRootPath, "attachment_path.dump")
  265. ioutil.WriteFile(dumpPath, buf.Bytes(), 0666)
  266. fmt.Println("Fail to rename some attachments, old and new paths are saved into:", dumpPath)
  267. }()
  268. for _, attach := range attachments {
  269. if err = os.MkdirAll(path.Dir(attach.NewPath), os.ModePerm); err != nil {
  270. isSucceed = false
  271. return err
  272. }
  273. if err = os.Rename(attach.Path, attach.NewPath); err != nil {
  274. isSucceed = false
  275. return err
  276. }
  277. }
  278. return sess.Commit()
  279. }
  280. func renamePullRequestFields(x *xorm.Engine) (err error) {
  281. type PullRequest struct {
  282. ID int64 `xorm:"pk autoincr"`
  283. PullID int64 `xorm:"INDEX"`
  284. PullIndex int64
  285. HeadBarcnh string
  286. IssueID int64 `xorm:"INDEX"`
  287. Index int64
  288. HeadBranch string
  289. }
  290. if err = x.Sync(new(PullRequest)); err != nil {
  291. return fmt.Errorf("sync: %v", err)
  292. }
  293. results, err := x.Query("SELECT `id`,`pull_id`,`pull_index`,`head_barcnh` FROM `pull_request`")
  294. if err != nil {
  295. if strings.Contains(err.Error(), "no such column") {
  296. return nil
  297. }
  298. return fmt.Errorf("select pull requests: %v", err)
  299. }
  300. sess := x.NewSession()
  301. defer sessionRelease(sess)
  302. if err = sess.Begin(); err != nil {
  303. return err
  304. }
  305. var pull *PullRequest
  306. for _, pr := range results {
  307. pull = &PullRequest{
  308. ID: com.StrTo(pr["id"]).MustInt64(),
  309. IssueID: com.StrTo(pr["pull_id"]).MustInt64(),
  310. Index: com.StrTo(pr["pull_index"]).MustInt64(),
  311. HeadBranch: string(pr["head_barcnh"]),
  312. }
  313. if pull.Index == 0 {
  314. continue
  315. }
  316. if _, err = sess.Id(pull.ID).Update(pull); err != nil {
  317. return err
  318. }
  319. }
  320. return sess.Commit()
  321. }
  322. func cleanUpMigrateRepoInfo(x *xorm.Engine) (err error) {
  323. type (
  324. User struct {
  325. ID int64 `xorm:"pk autoincr"`
  326. LowerName string
  327. }
  328. Repository struct {
  329. ID int64 `xorm:"pk autoincr"`
  330. OwnerID int64
  331. LowerName string
  332. }
  333. )
  334. repos := make([]*Repository, 0, 25)
  335. if err = x.Where("is_mirror=?", false).Find(&repos); err != nil {
  336. return fmt.Errorf("select all non-mirror repositories: %v", err)
  337. }
  338. var user *User
  339. for _, repo := range repos {
  340. user = &User{ID: repo.OwnerID}
  341. has, err := x.Get(user)
  342. if err != nil {
  343. return fmt.Errorf("get owner of repository[%d - %d]: %v", repo.ID, repo.OwnerID, err)
  344. } else if !has {
  345. continue
  346. }
  347. configPath := filepath.Join(setting.RepoRootPath, user.LowerName, repo.LowerName+".git/config")
  348. // In case repository file is somehow missing.
  349. if !com.IsFile(configPath) {
  350. continue
  351. }
  352. cfg, err := ini.Load(configPath)
  353. if err != nil {
  354. return fmt.Errorf("open config file: %v", err)
  355. }
  356. cfg.DeleteSection("remote \"origin\"")
  357. if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
  358. return fmt.Errorf("save config file: %v", err)
  359. }
  360. }
  361. return nil
  362. }
  363. func generateOrgRandsAndSalt(x *xorm.Engine) (err error) {
  364. type User struct {
  365. ID int64 `xorm:"pk autoincr"`
  366. Rands string `xorm:"VARCHAR(10)"`
  367. Salt string `xorm:"VARCHAR(10)"`
  368. }
  369. orgs := make([]*User, 0, 10)
  370. if err = x.Where("type=1").And("rands=''").Find(&orgs); err != nil {
  371. return fmt.Errorf("select all organizations: %v", err)
  372. }
  373. sess := x.NewSession()
  374. defer sessionRelease(sess)
  375. if err = sess.Begin(); err != nil {
  376. return err
  377. }
  378. for _, org := range orgs {
  379. org.Rands = base.GetRandomString(10)
  380. org.Salt = base.GetRandomString(10)
  381. if _, err = sess.Id(org.ID).Update(org); err != nil {
  382. return err
  383. }
  384. }
  385. return sess.Commit()
  386. }