forks.go 20 KB

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