(Potentially Breaking Change) Git-Based Hak Chunk Ordering

This commit is contained in:
2026-04-10 22:08:54 +02:00
parent e05620d285
commit 29ddf8e3c5
2 changed files with 380 additions and 6 deletions
+114 -6
View File
@@ -11,7 +11,9 @@ import (
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
"time"
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/erf"
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/gff"
@@ -47,9 +49,10 @@ type BuildManifestHAK struct {
}
type assetResource struct {
Rel string
Resource erf.Resource
Size int64
Rel string
Resource erf.Resource
Size int64
ChangedAt time.Time
}
type hakChunk struct {
@@ -447,6 +450,15 @@ func fieldStringValueForScripts(value gff.Value) (string, bool) {
func collectAssetResources(p *project.Project) ([]assetResource, error) {
var hakResources []assetResource
assetChangedAt, err := collectGitAssetChangeTimes(p)
if err != nil {
return nil, err
}
assetsRelPath, err := filepath.Rel(p.Root, p.AssetsDir())
if err != nil {
return nil, fmt.Errorf("resolve assets dir relative path: %w", err)
}
assetsRelPath = filepath.ToSlash(assetsRelPath)
for _, rel := range p.Inventory.AssetFiles {
abs := filepath.Join(p.AssetsDir(), filepath.FromSlash(rel))
@@ -454,10 +466,20 @@ func collectAssetResources(p *project.Project) ([]assetResource, error) {
if err != nil {
return nil, err
}
info, err := os.Stat(abs)
if err != nil {
return nil, fmt.Errorf("stat %s: %w", abs, err)
}
repoRel := filepath.ToSlash(filepath.Join(assetsRelPath, filepath.FromSlash(rel)))
changedAt := assetChangedAt[repoRel]
if changedAt.IsZero() {
changedAt = info.ModTime()
}
hakResources = append(hakResources, assetResource{
Rel: rel,
Resource: resource,
Size: erf.ArchiveSize([]erf.Resource{resource}),
Rel: rel,
Resource: resource,
Size: erf.ArchiveSize([]erf.Resource{resource}),
ChangedAt: changedAt,
})
}
@@ -467,6 +489,73 @@ func collectAssetResources(p *project.Project) ([]assetResource, error) {
return hakResources, nil
}
func collectGitAssetChangeTimes(p *project.Project) (map[string]time.Time, error) {
result := make(map[string]time.Time, len(p.Inventory.AssetFiles))
if len(p.Inventory.AssetFiles) == 0 {
return result, nil
}
assetsRelPath, err := filepath.Rel(p.Root, p.AssetsDir())
if err != nil {
return result, fmt.Errorf("resolve assets dir relative path: %w", err)
}
assetsRelPath = filepath.ToSlash(assetsRelPath)
output, err := gitOutput(p.Root, "log", "--format=commit:%ct", "--name-only", "--", assetsRelPath)
if err != nil {
return result, nil
}
wanted := make(map[string]struct{}, len(p.Inventory.AssetFiles))
for _, rel := range p.Inventory.AssetFiles {
wanted[filepath.ToSlash(filepath.Join(assetsRelPath, filepath.FromSlash(rel)))] = struct{}{}
}
var currentCommitTime time.Time
for _, line := range strings.Split(output, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
if strings.HasPrefix(line, "commit:") {
sec, err := strconv.ParseInt(strings.TrimPrefix(line, "commit:"), 10, 64)
if err != nil {
currentCommitTime = time.Time{}
continue
}
currentCommitTime = time.Unix(sec, 0).UTC()
continue
}
if currentCommitTime.IsZero() {
continue
}
if _, ok := wanted[line]; !ok {
continue
}
if _, exists := result[line]; exists {
continue
}
result[line] = currentCommitTime
if len(result) == len(wanted) {
break
}
}
return result, nil
}
func gitOutput(dir string, args ...string) (string, error) {
cmd := exec.Command("git", args...)
if dir != "" {
cmd.Dir = dir
}
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("git %s: %w: %s", strings.Join(args, " "), err, strings.TrimSpace(string(output)))
}
return string(output), nil
}
func sortResources(resources []erf.Resource) {
slices.SortFunc(resources, func(a, b erf.Resource) int {
return compareResourceKeys(a, b)
@@ -629,6 +718,7 @@ func planHAKChunks(p *project.Project, assets []assetResource) ([]hakChunk, erro
if len(groupAssets) == 0 {
continue
}
slices.SortFunc(groupAssets, compareAssetBuildOrder)
groupChunks, err := splitHAKGroup(cfg, groupAssets)
if err != nil {
@@ -708,6 +798,16 @@ func splitHAKGroup(cfg project.HAKConfig, assets []assetResource) ([]hakChunk, e
return chunks, nil
}
func compareAssetBuildOrder(a, b assetResource) int {
if cmp := a.ChangedAt.Compare(b.ChangedAt); cmp != 0 {
return cmp
}
if cmp := compareResourceKeys(a.Resource, b.Resource); cmp != 0 {
return cmp
}
return strings.Compare(a.Rel, b.Rel)
}
func chunkName(base string, index int, split bool) string {
if !split {
return base
@@ -868,6 +968,14 @@ func cleanupGeneratedHAKs(buildDir, moduleResRef string) error {
}
func plannedModuleHAKOrder(p *project.Project) ([]string, error) {
manifest, err := loadPreviousBuildManifest(p.BuildDir())
if err != nil {
return nil, err
}
if manifest != nil && len(manifest.ModuleHAKs) > 0 {
return append([]string(nil), manifest.ModuleHAKs...), nil
}
assetResources, err := collectAssetResources(p)
if err != nil {
return nil, err