Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 103 additions & 33 deletions go/cmd/gitter/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,50 +509,120 @@ func (r *Repository) expandByCherrypick(commits []int) []int {
return cherrypicked
}

// Affected returns a list of commits that are affected by the given introduced, fixed and last_affected events.
// It also returns two slices of hex hashes for newly identified cherry-picked introduced and fixed commits.
// A commit is affected when: from at least one introduced that is an ancestor of the commit, there is no path between them that passes through a fix.
// A fix can either be a fixed commit, or the children of a lastAffected commit.
func (r *Repository) Affected(ctx context.Context, se *SeparatedEvents, cherrypickIntro, cherrypickFixed bool) ([]*Commit, []string, []string) {
logger.InfoContext(ctx, "Starting affected commit walking")
start := time.Now()
// findAncestorRoots returns the subset of r.rootCommits that are ancestors of any of the input commits.
// It performs a BFS from the input fix commits to find reachable roots.
func (r *Repository) findAncestorRoots(from []int) []int {
visited := make([]bool, len(r.commits))
queue := make([]int, 0, len(from))
foundRoots := make([]int, 0, len(r.rootCommits))

introduced := r.parseHashes(ctx, se.Introduced)
for _, idx := range from {
if !visited[idx] {
visited[idx] = true
queue = append(queue, idx)
}
}

for len(queue) > 0 {
if len(foundRoots) == len(r.rootCommits) {
// All roots are found, we can terminate early
break
}

curr := queue[0]
queue = queue[1:]

if len(r.commits[curr].Parents) == 0 {
foundRoots = append(foundRoots, curr)
}

for _, pIdx := range r.commits[curr].Parents {
if !visited[pIdx] {
visited[pIdx] = true
queue = append(queue, pIdx)
}
}
}

return foundRoots
}

// resolveEvents parses and expands SeparatedEvents into lists of introduced and fixed commits
// In case of intro=0, it will not include root commits that are not ancestors of any fixed commits
func (r *Repository) resolveEvents(ctx context.Context, se *SeparatedEvents, cherrypickIntro, cherrypickFixed bool) ([]int, []int, []string, []string) {
// Parsing and expanding fixed events first because we need them to find relevant roots for intro=0
fixed := r.parseHashes(ctx, se.Fixed)
lastAffected := r.parseHashes(ctx, se.LastAffected)

var newIntroHashes []string
var newFixedHashes []string

// Expands the introduced and fixed commits to include cherrypick equivalents
// Hash strings of commits cherrypicked from fixed commits (excluding the original commits)
var cherrypickedFixedHashes []string
// lastAffected should not be expanded because it does not imply a "fix" commit that can be cherrypicked to other branches
if cherrypickIntro {
newIntro := r.expandByCherrypick(introduced)
newIntroHashes = r.hexHashes(newIntro)
introduced = append(introduced, newIntro...)
}
if cherrypickFixed {
newFixed := r.expandByCherrypick(fixed)
newFixedHashes = r.hexHashes(newFixed)
cherrypickedFixedHashes = r.hexHashes(newFixed)
fixed = append(fixed, newFixed...)
}

// Fixed commits and children of last affected are both in this set
// Fixed commits and children of last affected are both in this list
// For graph traversal sake they are both considered the fix
fixedMap := make([]bool, len(r.commits))
allFixes := make([]int, 0, len(se.Fixed)+len(se.LastAffected))
allFixes = append(allFixes, fixed...)
for _, idx := range lastAffected {
if idx < len(r.commitGraph) {
allFixes = append(allFixes, r.commitGraph[idx]...)
}
}

for _, idx := range fixed {
fixedMap[idx] = true
hasIntroZero := false
filteredIntro := make([]string, 0, len(se.Introduced))
for _, s := range se.Introduced {
if s == "0" {
hasIntroZero = true
} else {
filteredIntro = append(filteredIntro, s)
}
}

for _, idx := range lastAffected {
if idx < len(r.commitGraph) {
for _, childIdx := range r.commitGraph[idx] {
fixedMap[childIdx] = true
}
introduced := r.parseHashes(ctx, filteredIntro)

if hasIntroZero {
if len(allFixes) > 0 {
// If there are fixes, introduced=0 should only include root commits that are ancestors of the fixes
introduced = append(introduced, r.findAncestorRoots(allFixes)...)
} else {
// If there are no fixes, then introduced=0 means all root commits
introduced = append(introduced, r.rootCommits...)
}
}

// Hash strings of commits cherrypicked from introduced commits (excluding the original commits)
var cherrypickedIntroHashes []string
if cherrypickIntro {
newIntro := r.expandByCherrypick(introduced)
cherrypickedIntroHashes = r.hexHashes(newIntro)
introduced = append(introduced, newIntro...)
}

return introduced, allFixes, cherrypickedIntroHashes, cherrypickedFixedHashes
}

// Affected returns a list of commits that are affected by the given introduced, fixed and last_affected events.
// It also returns two slices of hex hashes for newly identified cherry-picked introduced and fixed commits.
// A commit is affected when: from at least one introduced that is an ancestor of the commit, there is no path between them that passes through a fix.
// A fix can either be a fixed commit, or the children of a lastAffected commit.
func (r *Repository) Affected(ctx context.Context, se *SeparatedEvents, cherrypickIntro, cherrypickFixed bool) ([]*Commit, []string, []string) {
logger.InfoContext(ctx, "Starting affected commit walking")
start := time.Now()

introduced, allFixes, cherrypickedIntroHashes, cherrypickedFixedHashes := r.resolveEvents(ctx, se, cherrypickIntro, cherrypickFixed)

logger.DebugContext(ctx, "Resolved affected commit events to walk", slog.Any("introduced", introduced), slog.Any("allFixes", allFixes))

fixedMap := make([]bool, len(r.commits))
for _, idx := range allFixes {
fixedMap[idx] = true
}

// The graph traversal
// affectedMap deduplicates the affected commits from the graph walk from each introduced commit
affectedMap := make([]bool, len(r.commits))
Expand Down Expand Up @@ -643,7 +713,7 @@ func (r *Repository) Affected(ctx context.Context, se *SeparatedEvents, cherrypi

logger.InfoContext(ctx, "Affected commit walking completed", slog.Duration("duration", time.Since(start)))

return affectedCommits, newIntroHashes, newFixedHashes
return affectedCommits, cherrypickedIntroHashes, cherrypickedFixedHashes
}

// Limit walks and returns the commits that are strictly between introduced (inclusive) and limit (exclusive).
Expand All @@ -652,17 +722,17 @@ func (r *Repository) Limit(ctx context.Context, se *SeparatedEvents, cherrypickI
introduced := r.parseHashes(ctx, se.Introduced)
limit := r.parseHashes(ctx, se.Limit)

var newIntroHashes []string
var newLimitHashes []string
var cherrypickedIntroHashes []string
var cherrypickedLimitHashes []string

if cherrypickIntro {
newIntro := r.expandByCherrypick(introduced)
newIntroHashes = r.hexHashes(newIntro)
cherrypickedIntroHashes = r.hexHashes(newIntro)
introduced = append(introduced, newIntro...)
}
if cherrypickLimit {
newLimit := r.expandByCherrypick(limit)
newLimitHashes = r.hexHashes(newLimit)
cherrypickedLimitHashes = r.hexHashes(newLimit)
limit = append(limit, newLimit...)
}

Expand Down Expand Up @@ -709,5 +779,5 @@ func (r *Repository) Limit(ctx context.Context, se *SeparatedEvents, cherrypickI
}
}

return affectedCommits, newIntroHashes, newLimitHashes
return affectedCommits, cherrypickedIntroHashes, cherrypickedLimitHashes
}
Loading
Loading