forks.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. package core
  2. import (
  3. "log"
  4. "reflect"
  5. "sort"
  6. "gopkg.in/src-d/go-git.v4/plumbing/object"
  7. "gopkg.in/src-d/go-git.v4/plumbing"
  8. "gopkg.in/src-d/hercules.v4/internal/toposort"
  9. )
  10. // OneShotMergeProcessor provides the convenience method to consume merges only once.
  11. type OneShotMergeProcessor struct {
  12. merges map[plumbing.Hash]bool
  13. }
  14. // Initialize resets OneShotMergeProcessor.
  15. func (proc *OneShotMergeProcessor) Initialize() {
  16. proc.merges = map[plumbing.Hash]bool{}
  17. }
  18. // ShouldConsumeCommit returns true on regular commits. It also returns true upon
  19. // the first occurrence of a particular merge commit.
  20. func (proc *OneShotMergeProcessor) ShouldConsumeCommit(deps map[string]interface{}) bool {
  21. commit := deps[DependencyCommit].(*object.Commit)
  22. if commit.NumParents() <= 1 {
  23. return true
  24. }
  25. if !proc.merges[commit.Hash] {
  26. proc.merges[commit.Hash] = true
  27. return true
  28. }
  29. return false
  30. }
  31. // IsMergeCommit indicates whether the commit is a merge or not.
  32. func IsMergeCommit(deps map[string]interface{}) bool {
  33. return deps[DependencyCommit].(*object.Commit).NumParents() > 1
  34. }
  35. // NoopMerger provides an empty Merge() method suitable for PipelineItem.
  36. type NoopMerger struct {
  37. }
  38. // Merge does nothing.
  39. func (merger *NoopMerger) Merge(branches []PipelineItem) {
  40. // no-op
  41. }
  42. // ForkSamePipelineItem clones items by referencing the same origin.
  43. func ForkSamePipelineItem(origin PipelineItem, n int) []PipelineItem {
  44. clones := make([]PipelineItem, n)
  45. for i := 0; i < n; i++ {
  46. clones[i] = origin
  47. }
  48. return clones
  49. }
  50. // ForkCopyPipelineItem clones items by copying them by value from the origin.
  51. func ForkCopyPipelineItem(origin PipelineItem, n int) []PipelineItem {
  52. originValue := reflect.Indirect(reflect.ValueOf(origin))
  53. originType := originValue.Type()
  54. clones := make([]PipelineItem, n)
  55. for i := 0; i < n; i++ {
  56. cloneValue := reflect.New(originType).Elem()
  57. cloneValue.Set(originValue)
  58. clones[i] = cloneValue.Addr().Interface().(PipelineItem)
  59. }
  60. return clones
  61. }
  62. const (
  63. // runActionCommit corresponds to a regular commit
  64. runActionCommit = 0
  65. // runActionFork splits a branch into several parts
  66. runActionFork = iota
  67. // runActionMerge merges several branches together
  68. runActionMerge = iota
  69. // runActionEmerge starts a root branch
  70. runActionEmerge = iota
  71. // runActionDelete removes the branch as it is no longer needed
  72. runActionDelete = iota
  73. )
  74. type runAction struct {
  75. Action int
  76. Commit *object.Commit
  77. Items []int
  78. }
  79. type orderer = func(reverse, direction bool) []string
  80. func cloneItems(origin []PipelineItem, n int) [][]PipelineItem {
  81. clones := make([][]PipelineItem, n)
  82. for j := 0; j < n; j++ {
  83. clones[j] = make([]PipelineItem, len(origin))
  84. }
  85. for i, item := range origin {
  86. itemClones := item.Fork(n)
  87. for j := 0; j < n; j++ {
  88. clones[j][i] = itemClones[j]
  89. }
  90. }
  91. return clones
  92. }
  93. func mergeItems(branches [][]PipelineItem) {
  94. buffer := make([]PipelineItem, len(branches) - 1)
  95. for i, item := range branches[0] {
  96. for j := 0; j < len(branches)-1; j++ {
  97. buffer[j] = branches[j+1][i]
  98. }
  99. item.Merge(buffer)
  100. }
  101. }
  102. // getMasterBranch returns the branch with the smallest index.
  103. func getMasterBranch(branches map[int][]PipelineItem) []PipelineItem {
  104. minKey := 1 << 31
  105. var minVal []PipelineItem
  106. for key, val := range branches {
  107. if key < minKey {
  108. minKey = key
  109. minVal = val
  110. }
  111. }
  112. return minVal
  113. }
  114. // prepareRunPlan schedules the actions for Pipeline.Run().
  115. func prepareRunPlan(commits []*object.Commit) []runAction {
  116. hashes, dag := buildDag(commits)
  117. leaveRootComponent(hashes, dag)
  118. mergedDag, mergedSeq := mergeDag(hashes, dag)
  119. orderNodes := bindOrderNodes(mergedDag)
  120. collapseFastForwards(orderNodes, hashes, mergedDag, dag, mergedSeq)
  121. /*fmt.Printf("digraph Hercules {\n")
  122. for i, c := range orderNodes(false, false) {
  123. commit := hashes[c]
  124. fmt.Printf(" \"%s\"[label=\"[%d] %s\"]\n", commit.Hash.String(), i, commit.Hash.String()[:6])
  125. for _, child := range mergedDag[commit.Hash] {
  126. fmt.Printf(" \"%s\" -> \"%s\"\n", commit.Hash.String(), child.Hash.String())
  127. }
  128. }
  129. fmt.Printf("}\n")*/
  130. plan := generatePlan(orderNodes, hashes, mergedDag, dag, mergedSeq)
  131. plan = optimizePlan(plan)
  132. /*for _, p := range plan {
  133. firstItem := p.Items[0]
  134. switch p.Action {
  135. case runActionCommit:
  136. fmt.Fprintln(os.Stderr, "C", firstItem, p.Commit.Hash.String())
  137. case runActionFork:
  138. fmt.Fprintln(os.Stderr, "F", p.Items)
  139. case runActionMerge:
  140. fmt.Fprintln(os.Stderr, "M", p.Items)
  141. }
  142. }*/
  143. return plan
  144. }
  145. // buildDag generates the raw commit DAG and the commit hash map.
  146. func buildDag(commits []*object.Commit) (
  147. map[string]*object.Commit, map[plumbing.Hash][]*object.Commit) {
  148. hashes := map[string]*object.Commit{}
  149. for _, commit := range commits {
  150. hashes[commit.Hash.String()] = commit
  151. }
  152. dag := map[plumbing.Hash][]*object.Commit{}
  153. for _, commit := range commits {
  154. if _, exists := dag[commit.Hash]; !exists {
  155. dag[commit.Hash] = make([]*object.Commit, 0, 1)
  156. }
  157. for _, parent := range commit.ParentHashes {
  158. if _, exists := hashes[parent.String()]; !exists {
  159. continue
  160. }
  161. children := dag[parent]
  162. if children == nil {
  163. children = make([]*object.Commit, 0, 1)
  164. }
  165. dag[parent] = append(children, commit)
  166. }
  167. }
  168. return hashes, dag
  169. }
  170. // leaveRootComponent runs connected components analysis and throws away everything
  171. // but the part which grows from the root.
  172. func leaveRootComponent(
  173. hashes map[string]*object.Commit,
  174. dag map[plumbing.Hash][]*object.Commit) {
  175. visited := map[plumbing.Hash]bool{}
  176. var sets [][]plumbing.Hash
  177. for key := range dag {
  178. if visited[key] {
  179. continue
  180. }
  181. var set []plumbing.Hash
  182. for queue := []plumbing.Hash{key}; len(queue) > 0; {
  183. head := queue[len(queue)-1]
  184. queue = queue[:len(queue)-1]
  185. if visited[head] {
  186. continue
  187. }
  188. set = append(set, head)
  189. visited[head] = true
  190. for _, c := range dag[head] {
  191. if !visited[c.Hash] {
  192. queue = append(queue, c.Hash)
  193. }
  194. }
  195. if commit, exists := hashes[head.String()]; exists {
  196. for _, p := range commit.ParentHashes {
  197. if !visited[p] {
  198. if _, exists := hashes[p.String()]; exists {
  199. queue = append(queue, p)
  200. }
  201. }
  202. }
  203. }
  204. }
  205. sets = append(sets, set)
  206. }
  207. if len(sets) > 1 {
  208. maxlen := 0
  209. maxind := -1
  210. for i, set := range sets {
  211. if len(set) > maxlen {
  212. maxlen = len(set)
  213. maxind = i
  214. }
  215. }
  216. for i, set := range sets {
  217. if i == maxind {
  218. continue
  219. }
  220. for _, h := range set {
  221. log.Printf("warning: dropped %s from the analysis - disjoint", h.String())
  222. delete(dag, h)
  223. delete(hashes, h.String())
  224. }
  225. }
  226. }
  227. }
  228. // bindOrderNodes returns curried "orderNodes" function.
  229. func bindOrderNodes(mergedDag map[plumbing.Hash][]*object.Commit) orderer {
  230. return func(reverse, direction bool) []string {
  231. graph := toposort.NewGraph()
  232. keys := make([]plumbing.Hash, 0, len(mergedDag))
  233. for key := range mergedDag {
  234. keys = append(keys, key)
  235. }
  236. sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() })
  237. for _, key := range keys {
  238. graph.AddNode(key.String())
  239. }
  240. for _, key := range keys {
  241. children := mergedDag[key]
  242. sort.Slice(children, func(i, j int) bool {
  243. return children[i].Hash.String() < children[j].Hash.String()
  244. })
  245. for _, c := range children {
  246. if !direction {
  247. graph.AddEdge(key.String(), c.Hash.String())
  248. } else {
  249. graph.AddEdge(c.Hash.String(), key.String())
  250. }
  251. }
  252. }
  253. order, ok := graph.Toposort()
  254. if !ok {
  255. // should never happen
  256. panic("Could not topologically sort the DAG of commits")
  257. }
  258. if reverse != direction {
  259. // one day this must appear in the standard library...
  260. for i, j := 0, len(order)-1; i < len(order)/2; i, j = i+1, j-1 {
  261. order[i], order[j] = order[j], order[i]
  262. }
  263. }
  264. return order
  265. }
  266. }
  267. // inverts `dag`
  268. func buildParents(dag map[plumbing.Hash][]*object.Commit) map[plumbing.Hash][]plumbing.Hash {
  269. parents := map[plumbing.Hash][]plumbing.Hash{}
  270. for key, vals := range dag {
  271. for _, val := range vals {
  272. parents[val.Hash] = append(parents[val.Hash], key)
  273. }
  274. }
  275. return parents
  276. }
  277. // mergeDag turns sequences of consecutive commits into single nodes.
  278. func mergeDag(
  279. hashes map[string]*object.Commit,
  280. dag map[plumbing.Hash][]*object.Commit) (
  281. mergedDag, mergedSeq map[plumbing.Hash][]*object.Commit) {
  282. parents := buildParents(dag)
  283. mergedDag = map[plumbing.Hash][]*object.Commit{}
  284. mergedSeq = map[plumbing.Hash][]*object.Commit{}
  285. visited := map[plumbing.Hash]bool{}
  286. for head := range dag {
  287. if visited[head] {
  288. continue
  289. }
  290. c := head
  291. for true {
  292. next := parents[c]
  293. if len(next) != 1 || len(dag[next[0]]) != 1 {
  294. break
  295. }
  296. c = next[0]
  297. }
  298. head = c
  299. var seq []*object.Commit
  300. for true {
  301. visited[c] = true
  302. seq = append(seq, hashes[c.String()])
  303. if len(dag[c]) != 1 {
  304. break
  305. }
  306. c = dag[c][0].Hash
  307. if len(parents[c]) != 1 {
  308. break
  309. }
  310. }
  311. mergedSeq[head] = seq
  312. mergedDag[head] = dag[seq[len(seq)-1].Hash]
  313. }
  314. return
  315. }
  316. // collapseFastForwards removes the fast forward merges.
  317. func collapseFastForwards(
  318. orderNodes orderer, hashes map[string]*object.Commit,
  319. mergedDag, dag, mergedSeq map[plumbing.Hash][]*object.Commit) {
  320. parents := buildParents(mergedDag)
  321. processed := map[plumbing.Hash]bool{}
  322. for _, strkey := range orderNodes(false, true) {
  323. key := hashes[strkey].Hash
  324. processed[key] = true
  325. repeat:
  326. vals, exists := mergedDag[key]
  327. if !exists {
  328. continue
  329. }
  330. if len(vals) < 2 {
  331. continue
  332. }
  333. toRemove := map[plumbing.Hash]bool{}
  334. for _, child := range vals {
  335. var queue []plumbing.Hash
  336. visited := map[plumbing.Hash]bool{child.Hash: true}
  337. childParents := parents[child.Hash]
  338. childNumOtherParents := 0
  339. for _, parent := range childParents {
  340. if parent != key {
  341. visited[parent] = true
  342. childNumOtherParents++
  343. queue = append(queue, parent)
  344. }
  345. }
  346. var immediateParent plumbing.Hash
  347. if childNumOtherParents == 1 {
  348. immediateParent = queue[0]
  349. }
  350. for len(queue) > 0 {
  351. head := queue[len(queue)-1]
  352. queue = queue[:len(queue)-1]
  353. if processed[head] {
  354. if head == key {
  355. toRemove[child.Hash] = true
  356. if childNumOtherParents == 1 && len(mergedDag[immediateParent]) == 1 {
  357. mergedSeq[immediateParent] = append(
  358. mergedSeq[immediateParent], mergedSeq[child.Hash]...)
  359. delete(mergedSeq, child.Hash)
  360. mergedDag[immediateParent] = mergedDag[child.Hash]
  361. delete(mergedDag, child.Hash)
  362. parents[child.Hash] = parents[immediateParent]
  363. for _, vals := range parents {
  364. for i, v := range vals {
  365. if v == child.Hash {
  366. vals[i] = immediateParent
  367. break
  368. }
  369. }
  370. }
  371. }
  372. }
  373. break
  374. }
  375. for _, parent := range parents[head] {
  376. if !visited[parent] {
  377. visited[head] = true
  378. queue = append(queue, parent)
  379. }
  380. }
  381. }
  382. }
  383. if len(toRemove) == 0 {
  384. continue
  385. }
  386. var newVals []*object.Commit
  387. for _, child := range vals {
  388. if !toRemove[child.Hash] {
  389. newVals = append(newVals, child)
  390. }
  391. }
  392. merged := false
  393. if len(newVals) == 1 {
  394. onlyChild := newVals[0].Hash
  395. if len(parents[onlyChild]) == 1 {
  396. merged = true
  397. mergedSeq[key] = append(mergedSeq[key], mergedSeq[onlyChild]...)
  398. delete(mergedSeq, onlyChild)
  399. mergedDag[key] = mergedDag[onlyChild]
  400. delete(mergedDag, onlyChild)
  401. parents[onlyChild] = parents[key]
  402. for _, vals := range parents {
  403. for i, v := range vals {
  404. if v == onlyChild {
  405. vals[i] = key
  406. break
  407. }
  408. }
  409. }
  410. }
  411. }
  412. if !merged {
  413. mergedDag[key] = newVals
  414. }
  415. newVals = []*object.Commit{}
  416. node := mergedSeq[key][len(mergedSeq[key])-1].Hash
  417. for _, child := range dag[node] {
  418. if !toRemove[child.Hash] {
  419. newVals = append(newVals, child)
  420. }
  421. }
  422. dag[node] = newVals
  423. if merged {
  424. goto repeat
  425. }
  426. }
  427. }
  428. // generatePlan creates the list of actions from the commit DAG.
  429. func generatePlan(
  430. orderNodes orderer, hashes map[string]*object.Commit,
  431. mergedDag, dag, mergedSeq map[plumbing.Hash][]*object.Commit) []runAction {
  432. parents := buildParents(dag)
  433. var plan []runAction
  434. branches := map[plumbing.Hash]int{}
  435. branchers := map[plumbing.Hash]map[plumbing.Hash]int{}
  436. counter := 0
  437. for _, name := range orderNodes(false, true) {
  438. commit := hashes[name]
  439. if len(parents[commit.Hash]) == 0 {
  440. branches[commit.Hash] = counter
  441. plan = append(plan, runAction{
  442. Action: runActionEmerge,
  443. Commit: commit,
  444. Items: []int{counter},
  445. })
  446. counter++
  447. }
  448. var branch int
  449. {
  450. var exists bool
  451. branch, exists = branches[commit.Hash]
  452. if !exists {
  453. branch = -1
  454. }
  455. }
  456. branchExists := func() bool { return branch >= 0 }
  457. appendCommit := func(c *object.Commit, branch int) {
  458. plan = append(plan, runAction{
  459. Action: runActionCommit,
  460. Commit: c,
  461. Items: []int{branch},
  462. })
  463. }
  464. appendMergeIfNeeded := func() {
  465. if len(parents[commit.Hash]) < 2 {
  466. return
  467. }
  468. // merge after the merge commit (the first in the sequence)
  469. var items []int
  470. minBranch := 1 << 31
  471. for _, parent := range parents[commit.Hash] {
  472. parentBranch := -1
  473. if parents, exists := branchers[commit.Hash]; exists {
  474. if inheritedBranch, exists := parents[parent]; exists {
  475. parentBranch = inheritedBranch
  476. }
  477. }
  478. if parentBranch == -1 {
  479. parentBranch = branches[parent]
  480. if parentBranch == -1 {
  481. log.Panicf("parent %s > %s does not have a branch assigned",
  482. parent.String(), commit.Hash.String())
  483. }
  484. }
  485. if len(dag[parent]) == 1 && minBranch > parentBranch {
  486. minBranch = parentBranch
  487. }
  488. items = append(items, parentBranch)
  489. if parentBranch != branch {
  490. appendCommit(commit, parentBranch)
  491. }
  492. }
  493. // there should be no duplicates in items
  494. if minBranch < 1 << 31 {
  495. branch = minBranch
  496. branches[commit.Hash] = minBranch
  497. } else if !branchExists() {
  498. log.Panicf("!branchExists(%s)", commit.Hash.String())
  499. }
  500. plan = append(plan, runAction{
  501. Action: runActionMerge,
  502. Commit: nil,
  503. Items: items,
  504. })
  505. }
  506. var head plumbing.Hash
  507. if subseq, exists := mergedSeq[commit.Hash]; exists {
  508. for subseqIndex, offspring := range subseq {
  509. if branchExists() {
  510. appendCommit(offspring, branch)
  511. }
  512. if subseqIndex == 0 {
  513. appendMergeIfNeeded()
  514. }
  515. }
  516. head = subseq[len(subseq)-1].Hash
  517. branches[head] = branch
  518. } else {
  519. head = commit.Hash
  520. }
  521. if len(mergedDag[commit.Hash]) > 1 {
  522. children := []int{branch}
  523. for i, child := range mergedDag[commit.Hash] {
  524. if i == 0 {
  525. branches[child.Hash] = branch
  526. continue
  527. }
  528. if _, exists := branches[child.Hash]; !exists {
  529. branches[child.Hash] = counter
  530. }
  531. parents := branchers[child.Hash]
  532. if parents == nil {
  533. parents = map[plumbing.Hash]int{}
  534. branchers[child.Hash] = parents
  535. }
  536. parents[head] = counter
  537. children = append(children, counter)
  538. counter++
  539. }
  540. plan = append(plan, runAction{
  541. Action: runActionFork,
  542. Commit: nil,
  543. Items: children,
  544. })
  545. }
  546. }
  547. return plan
  548. }
  549. // optimizePlan removes "dead" nodes and inserts `runActionDelete` disposal steps.
  550. //
  551. // | *
  552. // * /
  553. // |\/
  554. // |/
  555. // *
  556. //
  557. func optimizePlan(plan []runAction) []runAction {
  558. // lives maps branch index to the number of commits in that branch
  559. lives := map[int]int{}
  560. // lastMentioned maps branch index to the index inside `plan` when that branch was last used
  561. lastMentioned := map[int]int{}
  562. for i, p := range plan {
  563. firstItem := p.Items[0]
  564. switch p.Action {
  565. case runActionCommit:
  566. lives[firstItem]++
  567. lastMentioned[firstItem] = i
  568. if firstItem == -1 {
  569. log.Panicf("commit %s does not have an assigned branch",
  570. p.Commit.Hash.String())
  571. }
  572. case runActionFork:
  573. lastMentioned[firstItem] = i
  574. case runActionMerge:
  575. for _, item := range p.Items {
  576. lastMentioned[item] = i
  577. }
  578. case runActionEmerge:
  579. lives[firstItem]++
  580. lastMentioned[firstItem] = i
  581. }
  582. }
  583. branchesToDelete := map[int]bool{}
  584. for key, life := range lives {
  585. if life == 1 {
  586. branchesToDelete[key] = true
  587. delete(lastMentioned, key)
  588. }
  589. }
  590. var optimizedPlan []runAction
  591. lastMentionedArr := make([][2]int, 0, len(lastMentioned) + 1)
  592. for key, val := range lastMentioned {
  593. if val != len(plan) - 1 {
  594. lastMentionedArr = append(lastMentionedArr, [2]int{val, key})
  595. }
  596. }
  597. if len(lastMentionedArr) == 0 && len(branchesToDelete) == 0 {
  598. // early return - we have nothing to optimize
  599. return plan
  600. }
  601. sort.Slice(lastMentionedArr, func(i, j int) bool {
  602. return lastMentionedArr[i][0] < lastMentionedArr[j][0]
  603. })
  604. lastMentionedArr = append(lastMentionedArr, [2]int{len(plan)-1, -1})
  605. prevpi := -1
  606. for _, pair := range lastMentionedArr {
  607. for pi := prevpi + 1; pi <= pair[0]; pi++ {
  608. p := plan[pi]
  609. switch p.Action {
  610. case runActionCommit:
  611. if !branchesToDelete[p.Items[0]] {
  612. optimizedPlan = append(optimizedPlan, p)
  613. }
  614. case runActionFork:
  615. var newBranches []int
  616. for _, b := range p.Items {
  617. if !branchesToDelete[b] {
  618. newBranches = append(newBranches, b)
  619. }
  620. }
  621. if len(newBranches) > 1 {
  622. optimizedPlan = append(optimizedPlan, runAction{
  623. Action: runActionFork,
  624. Commit: p.Commit,
  625. Items: newBranches,
  626. })
  627. }
  628. case runActionMerge:
  629. var newBranches []int
  630. for _, b := range p.Items {
  631. if !branchesToDelete[b] {
  632. newBranches = append(newBranches, b)
  633. }
  634. }
  635. if len(newBranches) > 1 {
  636. optimizedPlan = append(optimizedPlan, runAction{
  637. Action: runActionMerge,
  638. Commit: p.Commit,
  639. Items: newBranches,
  640. })
  641. }
  642. case runActionEmerge:
  643. optimizedPlan = append(optimizedPlan, p)
  644. }
  645. }
  646. if pair[1] >= 0 {
  647. prevpi = pair[0]
  648. optimizedPlan = append(optimizedPlan, runAction{
  649. Action: runActionDelete,
  650. Commit: nil,
  651. Items: []int{pair[1]},
  652. })
  653. }
  654. }
  655. // single commit can be detected as redundant
  656. if len(optimizedPlan) > 0 {
  657. return optimizedPlan
  658. }
  659. return plan
  660. }