Skip to content
Merged
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
124 changes: 67 additions & 57 deletions pkg/workflow/action_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,81 +488,91 @@ func (c *ActionCache) GetCachePath() string {
// for each repo+SHA combination. For example, if both "actions/cache@v4" and "actions/cache@v4.3.0"
// point to the same SHA and version, only "actions/cache@v4.3.0" is kept.
func (c *ActionCache) deduplicateEntries() {
// Group entries by repo+SHA
type entryKey struct {
repo string
sha string
}
groups := make(map[entryKey][]string)

for key, entry := range c.Entries {
ek := entryKey{repo: entry.Repo, sha: entry.SHA}
groups[ek] = append(groups[ek], key)
}

// For each group with multiple entries, keep only the most precise one
groups := c.groupEntriesByRepoAndSHA()
var toDelete []string
var deduplicationDetails []string // Track details for user-friendly message

var deduplicationDetails []string
for ek, keys := range groups {
if len(keys) <= 1 {
continue
}

// Truncate SHA for logging (handle short SHAs in tests)
shortSHA := ek.sha
if len(ek.sha) > 8 {
shortSHA = ek.sha[:8]
actionCacheLog.Printf("Found %d cache entries for %s with SHA %s", len(keys), ek.repo, truncateSHAForLog(ek.sha))
keyInfos := buildDedupKeyInfos(keys)
if len(keyInfos) <= 1 {
continue
}
actionCacheLog.Printf("Found %d cache entries for %s with SHA %s", len(keys), ek.repo, shortSHA)

// Find the most precise version reference
// Extract the version reference from each key (format: "repo@versionRef")
type keyInfo struct {
key string
versionRef string
keepVersion, removedKeys, removedVersions := collectDedupRemovals(keyInfos)
for _, removedKey := range removedKeys {
toDelete = append(toDelete, removedKey)
actionCacheLog.Printf("Deduplicating: keeping %s, removing %s", keyInfos[0].key, removedKey)
}
keyInfos := make([]keyInfo, len(keys))
for i, key := range keys {
parts := strings.SplitN(key, "@", 2)
versionRef := ""
if len(parts) == 2 {
versionRef = parts[1]
}
keyInfos[i] = keyInfo{key: key, versionRef: versionRef}
deduplicationDetails = append(deduplicationDetails, fmt.Sprintf("%s: kept %s, removed %s", ek.repo, keepVersion, strings.Join(removedVersions, ", ")))
}
c.deleteDedupEntries(toDelete)
if len(toDelete) > 0 {
actionCacheLog.Printf("Deduplicated %d entries, %d entries remaining", len(toDelete), len(c.Entries))
for _, detail := range deduplicationDetails {
actionCacheLog.Printf("Deduplication detail: %s", detail)
}
}
}

// Sort by version precision (most precise first)
sort.Slice(keyInfos, func(i, j int) bool {
return isMorePreciseVersion(keyInfos[i].versionRef, keyInfos[j].versionRef)
})

// Keep the most precise version, mark others for deletion
keepVersion := keyInfos[0].versionRef
var removedVersions []string
for i := 1; i < len(keyInfos); i++ {
toDelete = append(toDelete, keyInfos[i].key)
removedVersions = append(removedVersions, keyInfos[i].versionRef)
actionCacheLog.Printf("Deduplicating: keeping %s, removing %s", keyInfos[0].key, keyInfos[i].key)
type deduplicationKey struct {
repo string
sha string
}

type cacheKeyInfo struct {
key string
versionRef string
}

func (c *ActionCache) groupEntriesByRepoAndSHA() map[deduplicationKey][]string {
groups := make(map[deduplicationKey][]string)
for key, entry := range c.Entries {
ek := deduplicationKey{repo: entry.Repo, sha: entry.SHA}
groups[ek] = append(groups[ek], key)
}
return groups
}

func buildDedupKeyInfos(keys []string) []cacheKeyInfo {
keyInfos := make([]cacheKeyInfo, len(keys))
for i, key := range keys {
parts := strings.SplitN(key, "@", 2)
versionRef := ""
if len(parts) == 2 {
versionRef = parts[1]
}
keyInfos[i] = cacheKeyInfo{key: key, versionRef: versionRef}
}
sort.Slice(keyInfos, func(i, j int) bool {
return isMorePreciseVersion(keyInfos[i].versionRef, keyInfos[j].versionRef)
})
return keyInfos
}

// Build user-friendly message
detail := fmt.Sprintf("%s: kept %s, removed %s", ek.repo, keepVersion, strings.Join(removedVersions, ", "))
deduplicationDetails = append(deduplicationDetails, detail)
func collectDedupRemovals(keyInfos []cacheKeyInfo) (string, []string, []string) {
keepVersion := keyInfos[0].versionRef
removedKeys := make([]string, 0, len(keyInfos)-1)
removedVersions := make([]string, 0, len(keyInfos)-1)
for i := 1; i < len(keyInfos); i++ {
removedKeys = append(removedKeys, keyInfos[i].key)
removedVersions = append(removedVersions, keyInfos[i].versionRef)
}
return keepVersion, removedKeys, removedVersions
}

// Delete the less precise entries
func (c *ActionCache) deleteDedupEntries(toDelete []string) {
for _, key := range toDelete {
delete(c.Entries, key)
}
}

if len(toDelete) > 0 {
actionCacheLog.Printf("Deduplicated %d entries, %d entries remaining", len(toDelete), len(c.Entries))
// Log detailed deduplication info at verbose level
for _, detail := range deduplicationDetails {
actionCacheLog.Printf("Deduplication detail: %s", detail)
}
func truncateSHAForLog(sha string) string {
if len(sha) > 8 {
return sha[:8]
}
return sha
}

// PruneStaleGHAWEntries removes entries from the cache for the gh-aw-actions
Expand Down
Loading
Loading