forks.go 19 KB

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