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.

374 lines
9.8 KiB

  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. "fmt"
  7. "strings"
  8. "time"
  9. "github.com/Unknwon/com"
  10. "github.com/go-xorm/xorm"
  11. "github.com/gogits/gogs/modules/log"
  12. "github.com/gogits/gogs/modules/setting"
  13. )
  14. const _MIN_DB_VER = 0
  15. type Migration interface {
  16. Description() string
  17. Migrate(*xorm.Engine) error
  18. }
  19. type migration struct {
  20. description string
  21. migrate func(*xorm.Engine) error
  22. }
  23. func NewMigration(desc string, fn func(*xorm.Engine) error) Migration {
  24. return &migration{desc, fn}
  25. }
  26. func (m *migration) Description() string {
  27. return m.description
  28. }
  29. func (m *migration) Migrate(x *xorm.Engine) error {
  30. return m.migrate(x)
  31. }
  32. // The version table. Should have only one row with id==1
  33. type Version struct {
  34. Id int64
  35. Version int64
  36. }
  37. // This is a sequence of migrations. Add new migrations to the bottom of the list.
  38. // If you want to "retire" a migration, remove it from the top of the list and
  39. // update _MIN_VER_DB accordingly
  40. var migrations = []Migration{
  41. NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1
  42. NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2
  43. NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3
  44. NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4
  45. }
  46. // Migrate database to current version
  47. func Migrate(x *xorm.Engine) error {
  48. if err := x.Sync(new(Version)); err != nil {
  49. return fmt.Errorf("sync: %v", err)
  50. }
  51. currentVersion := &Version{Id: 1}
  52. has, err := x.Get(currentVersion)
  53. if err != nil {
  54. return fmt.Errorf("get: %v", err)
  55. } else if !has {
  56. // If the user table does not exist it is a fresh installation and we
  57. // can skip all migrations.
  58. needsMigration, err := x.IsTableExist("user")
  59. if err != nil {
  60. return err
  61. }
  62. if needsMigration {
  63. isEmpty, err := x.IsTableEmpty("user")
  64. if err != nil {
  65. return err
  66. }
  67. // If the user table is empty it is a fresh installation and we can
  68. // skip all migrations.
  69. needsMigration = !isEmpty
  70. }
  71. if !needsMigration {
  72. currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
  73. }
  74. if _, err = x.InsertOne(currentVersion); err != nil {
  75. return fmt.Errorf("insert: %v", err)
  76. }
  77. }
  78. v := currentVersion.Version
  79. for i, m := range migrations[v-_MIN_DB_VER:] {
  80. log.Info("Migration: %s", m.Description())
  81. if err = m.Migrate(x); err != nil {
  82. return fmt.Errorf("do migrate: %v", err)
  83. }
  84. currentVersion.Version = v + int64(i) + 1
  85. if _, err = x.Id(1).Update(currentVersion); err != nil {
  86. return err
  87. }
  88. }
  89. return nil
  90. }
  91. func sessionRelease(sess *xorm.Session) {
  92. if !sess.IsCommitedOrRollbacked {
  93. sess.Rollback()
  94. }
  95. sess.Close()
  96. }
  97. func accessToCollaboration(x *xorm.Engine) (err error) {
  98. type Collaboration struct {
  99. ID int64 `xorm:"pk autoincr"`
  100. RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
  101. UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
  102. Created time.Time
  103. }
  104. if err = x.Sync(new(Collaboration)); err != nil {
  105. return fmt.Errorf("sync: %v", err)
  106. }
  107. 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")
  108. if err != nil {
  109. return err
  110. }
  111. sess := x.NewSession()
  112. defer sessionRelease(sess)
  113. if err = sess.Begin(); err != nil {
  114. return err
  115. }
  116. offset := strings.Split(time.Now().String(), " ")[2]
  117. for _, result := range results {
  118. mode := com.StrTo(result["mode"]).MustInt64()
  119. // Collaborators must have write access.
  120. if mode < 2 {
  121. continue
  122. }
  123. userID := com.StrTo(result["uid"]).MustInt64()
  124. repoRefName := string(result["repo"])
  125. var created time.Time
  126. switch {
  127. case setting.UseSQLite3:
  128. created, _ = time.Parse(time.RFC3339, string(result["created"]))
  129. case setting.UseMySQL:
  130. created, _ = time.Parse("2006-01-02 15:04:05-0700", string(result["created"])+offset)
  131. case setting.UsePostgreSQL:
  132. created, _ = time.Parse("2006-01-02T15:04:05Z-0700", string(result["created"])+offset)
  133. }
  134. // find owner of repository
  135. parts := strings.SplitN(repoRefName, "/", 2)
  136. ownerName := parts[0]
  137. repoName := parts[1]
  138. 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)
  139. if err != nil {
  140. return err
  141. }
  142. if len(results) < 1 {
  143. continue
  144. }
  145. ownerID := com.StrTo(results[0]["uid"]).MustInt64()
  146. if ownerID == userID {
  147. continue
  148. }
  149. // test if user is member of owning organization
  150. isMember := false
  151. for _, member := range results {
  152. memberID := com.StrTo(member["memberid"]).MustInt64()
  153. // We can skip all cases that a user is member of the owning organization
  154. if memberID == userID {
  155. isMember = true
  156. }
  157. }
  158. if isMember {
  159. continue
  160. }
  161. results, err = sess.Query("SELECT id FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName)
  162. if err != nil {
  163. return err
  164. } else if len(results) < 1 {
  165. continue
  166. }
  167. collaboration := &Collaboration{
  168. UserID: userID,
  169. RepoID: com.StrTo(results[0]["id"]).MustInt64(),
  170. }
  171. has, err := sess.Get(collaboration)
  172. if err != nil {
  173. return err
  174. } else if has {
  175. continue
  176. }
  177. collaboration.Created = created
  178. if _, err = sess.InsertOne(collaboration); err != nil {
  179. return err
  180. }
  181. }
  182. return sess.Commit()
  183. }
  184. func ownerTeamUpdate(x *xorm.Engine) (err error) {
  185. if _, err := x.Exec("UPDATE `team` SET authorize=4 WHERE lower_name=?", "owners"); err != nil {
  186. return fmt.Errorf("update owner team table: %v", err)
  187. }
  188. return nil
  189. }
  190. func accessRefactor(x *xorm.Engine) (err error) {
  191. type (
  192. AccessMode int
  193. Access struct {
  194. ID int64 `xorm:"pk autoincr"`
  195. UserID int64 `xorm:"UNIQUE(s)"`
  196. RepoID int64 `xorm:"UNIQUE(s)"`
  197. Mode AccessMode
  198. }
  199. UserRepo struct {
  200. UserID int64
  201. RepoID int64
  202. }
  203. )
  204. // We consiously don't start a session yet as we make only reads for now, no writes
  205. accessMap := make(map[UserRepo]AccessMode, 50)
  206. 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")
  207. if err != nil {
  208. return fmt.Errorf("select repositories: %v", err)
  209. }
  210. for _, repo := range results {
  211. repoID := com.StrTo(repo["repo_id"]).MustInt64()
  212. isPrivate := com.StrTo(repo["is_private"]).MustInt() > 0
  213. ownerID := com.StrTo(repo["owner_id"]).MustInt64()
  214. ownerIsOrganization := com.StrTo(repo["owner_type"]).MustInt() > 0
  215. results, err := x.Query("SELECT `user_id` FROM `collaboration` WHERE repo_id=?", repoID)
  216. if err != nil {
  217. return fmt.Errorf("select collaborators: %v", err)
  218. }
  219. for _, user := range results {
  220. userID := com.StrTo(user["user_id"]).MustInt64()
  221. accessMap[UserRepo{userID, repoID}] = 2 // WRITE ACCESS
  222. }
  223. if !ownerIsOrganization {
  224. continue
  225. }
  226. // The minimum level to add a new access record,
  227. // because public repository has implicit open access.
  228. minAccessLevel := AccessMode(0)
  229. if !isPrivate {
  230. minAccessLevel = 1
  231. }
  232. repoString := "$" + string(repo["repo_id"]) + "|"
  233. results, err = x.Query("SELECT `id`,`authorize`,`repo_ids` FROM `team` WHERE org_id=? AND authorize>? ORDER BY `authorize` ASC", ownerID, int(minAccessLevel))
  234. if err != nil {
  235. return fmt.Errorf("select teams from org: %v", err)
  236. }
  237. for _, team := range results {
  238. if !strings.Contains(string(team["repo_ids"]), repoString) {
  239. continue
  240. }
  241. teamID := com.StrTo(team["id"]).MustInt64()
  242. mode := AccessMode(com.StrTo(team["authorize"]).MustInt())
  243. results, err := x.Query("SELECT `uid` FROM `team_user` WHERE team_id=?", teamID)
  244. if err != nil {
  245. return fmt.Errorf("select users from team: %v", err)
  246. }
  247. for _, user := range results {
  248. userID := com.StrTo(user["uid"]).MustInt64()
  249. accessMap[UserRepo{userID, repoID}] = mode
  250. }
  251. }
  252. }
  253. // Drop table can't be in a session (at least not in sqlite)
  254. if _, err = x.Exec("DROP TABLE `access`"); err != nil {
  255. return fmt.Errorf("drop access table: %v", err)
  256. }
  257. // Now we start writing so we make a session
  258. sess := x.NewSession()
  259. defer sessionRelease(sess)
  260. if err = sess.Begin(); err != nil {
  261. return err
  262. }
  263. if err = sess.Sync2(new(Access)); err != nil {
  264. return fmt.Errorf("sync: %v", err)
  265. }
  266. accesses := make([]*Access, 0, len(accessMap))
  267. for ur, mode := range accessMap {
  268. accesses = append(accesses, &Access{UserID: ur.UserID, RepoID: ur.RepoID, Mode: mode})
  269. }
  270. if _, err = sess.Insert(accesses); err != nil {
  271. return fmt.Errorf("insert accesses: %v", err)
  272. }
  273. return sess.Commit()
  274. }
  275. func teamToTeamRepo(x *xorm.Engine) error {
  276. type TeamRepo struct {
  277. ID int64 `xorm:"pk autoincr"`
  278. OrgID int64 `xorm:"INDEX"`
  279. TeamID int64 `xorm:"UNIQUE(s)"`
  280. RepoID int64 `xorm:"UNIQUE(s)"`
  281. }
  282. teamRepos := make([]*TeamRepo, 0, 50)
  283. results, err := x.Query("SELECT `id`,`org_id`,`repo_ids` FROM `team`")
  284. if err != nil {
  285. return fmt.Errorf("select teams: %v", err)
  286. }
  287. for _, team := range results {
  288. orgID := com.StrTo(team["org_id"]).MustInt64()
  289. teamID := com.StrTo(team["id"]).MustInt64()
  290. // #1032: legacy code can have duplicated IDs for same repository.
  291. mark := make(map[int64]bool)
  292. for _, idStr := range strings.Split(string(team["repo_ids"]), "|") {
  293. repoID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
  294. if repoID == 0 || mark[repoID] {
  295. continue
  296. }
  297. mark[repoID] = true
  298. teamRepos = append(teamRepos, &TeamRepo{
  299. OrgID: orgID,
  300. TeamID: teamID,
  301. RepoID: repoID,
  302. })
  303. }
  304. }
  305. sess := x.NewSession()
  306. defer sessionRelease(sess)
  307. if err = sess.Begin(); err != nil {
  308. return err
  309. }
  310. if err = sess.Sync2(new(TeamRepo)); err != nil {
  311. return fmt.Errorf("sync: %v", err)
  312. } else if _, err = sess.Insert(teamRepos); err != nil {
  313. return fmt.Errorf("insert team-repos: %v", err)
  314. }
  315. return sess.Commit()
  316. }