diff --git a/builder/history.go b/builder/history.go index 43f1cf8..cac061a 100644 --- a/builder/history.go +++ b/builder/history.go @@ -17,18 +17,24 @@ package builder import ( + "bytes" "encoding/xml" "errors" + "fmt" "io" "os" + "os/exec" "path/filepath" "regexp" "sort" + "strings" "time" + "github.com/go-git/go-billy/v5/helper/chroot" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/storage/filesystem" ) const ( @@ -148,45 +154,28 @@ func GetFileContents(repo *git.Repository, hash plumbing.Hash, path string) ([]b // // The repository path will be taken as the directory name of the pkgfile that // is given to this function. -func NewPackageHistory(pkgfile string) (*PackageHistory, error) { - // Get the root of the package repo - packageDir := filepath.Dir(pkgfile) +func NewPackageHistory(repo *git.Repository, pkgfile string) (*PackageHistory, error) { + repoDir := abs(repoRootDir(repo)) + pkgDir := abs(filepath.Dir(pkgfile)) - repo, err := git.PlainOpenWithOptions(packageDir, &git.PlainOpenOptions{ - DetectDotGit: true, - }) + refs, err := gitLog(pkgDir) if err != nil { return nil, err } - var hashes []string - updates := make(map[string]*PackageUpdate) - cIter, err := repo.Log(&git.LogOptions{ - FileName: &packageDir, - }) - if err != nil { - return nil, err - } - - err = cIter.ForEach(func(commit *object.Commit) error { - hash := commit.ID().String() - hashes = append(hashes, hash) - updates[hash] = NewPackageUpdate(commit, hash) - return nil - }) + for _, ref := range refs { + commit, err := repo.CommitObject(plumbing.NewHash(ref)) + if err != nil { + return nil, fmt.Errorf("unable to resolve commit %q: %w", ref, err) + } - if err != nil { - return nil, err + updates[ref] = NewPackageUpdate(commit, ref) } - // Sort the tags by -refname - sort.Sort(sort.Reverse(sort.StringSlice(hashes))) - - ret := &PackageHistory{pkgfile: pkgfile} - ret.scanUpdates(repo, updates, hashes) - updates = nil + ret := &PackageHistory{pkgfile: rel(repoDir, pkgfile)} + ret.scanUpdates(repo, updates) if len(ret.Updates) < 1 { return nil, errors.New("no usable git history found") @@ -196,6 +185,54 @@ func NewPackageHistory(pkgfile string) (*PackageHistory, error) { return ret, nil } +func repoRootDir(repo *git.Repository) string { + storer, ok := repo.Storer.(*filesystem.Storage) + if !ok { + return "" + } + + ch, ok := storer.Filesystem().(*chroot.ChrootHelper) + if !ok { + return "" + } + + return filepath.Dir(ch.Root()) +} + +func execGit(args ...string) (string, error) { + var buf bytes.Buffer + + cmd := exec.Command("git", args...) + cmd.Stdout = &buf + + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("error running Git: %w", err) + } + + return buf.String(), nil +} + +func gitLog(path string) ([]string, error) { + out, err := execGit("-C", path, "log", "--pretty=format:%H", path) + if err != nil { + return nil, fmt.Errorf("unable to get Git history: %w", err) + } + + return strings.Split(out, "\n"), nil +} + +func rel(base, target string) string { + s, _ := filepath.Rel(abs(base), abs(target)) + + return s +} + +func abs(path string) string { + s, _ := filepath.Abs(path) + + return s +} + // SortUpdatesByRelease is a simple wrapper to allowing sorting history. type SortUpdatesByRelease []*PackageUpdate @@ -208,24 +245,20 @@ func (a SortUpdatesByRelease) Swap(i, j int) { } func (a SortUpdatesByRelease) Less(i, j int) bool { + if a[i].Package.Release == a[j].Package.Release { + return a[i].Time.Before(a[j].Time) + } + return a[i].Package.Release < a[j].Package.Release } // scanUpdates will go back through the collected, "ok" tags, and analyze // them to be more useful. -func (p *PackageHistory) scanUpdates(repo *git.Repository, updates map[string]*PackageUpdate, hashes []string) { - // basename of file - fname := filepath.Base(p.pkgfile) - - updateSet := make([]*PackageUpdate, 0, len(hashes)) - - // Iterate the commit set in order - for _, tagID := range hashes { - update := updates[tagID] - if update == nil { - continue - } +func (p *PackageHistory) scanUpdates(repo *git.Repository, updates map[string]*PackageUpdate) { + fname := p.pkgfile + updateSet := make(map[int]*PackageUpdate, len(updates)) + for _, update := range updates { b, err := GetFileContents(repo, update.Commit, fname) if err != nil { continue @@ -237,16 +270,26 @@ func (p *PackageHistory) scanUpdates(repo *git.Repository, updates map[string]*P continue } + if u, ok := updateSet[pkg.Release]; ok && u.Time.Before(update.Time) { + continue + } + update.Package = pkg - updateSet = append(updateSet, update) + updateSet[pkg.Release] = update + } + + updateList := make(SortUpdatesByRelease, 0, len(updates)) + + for _, update := range updateSet { + updateList = append(updateList, update) } - sort.Sort(sort.Reverse(SortUpdatesByRelease(updateSet))) + sort.Sort(sort.Reverse(updateList)) - if len(updateSet) >= MaxChangelogEntries { - p.Updates = updateSet[:MaxChangelogEntries] + if len(updateList) >= MaxChangelogEntries { + p.Updates = updateList[:MaxChangelogEntries] } else { - p.Updates = updateSet + p.Updates = updateList } } diff --git a/builder/manager.go b/builder/manager.go index 4fc9663..5a89e4a 100644 --- a/builder/manager.go +++ b/builder/manager.go @@ -18,6 +18,7 @@ package builder import ( "errors" + "fmt" "os" "os/signal" "path/filepath" @@ -28,6 +29,7 @@ import ( log "github.com/DataDrake/waterlog" "github.com/getsolus/libosdev/disk" + "github.com/go-git/go-git/v5" ) var ( @@ -183,10 +185,14 @@ func (m *Manager) SetPackage(pkg *Package) error { // Obtain package history for git builds if pkg.Type == PackageTypeYpkg { - if PathExists(filepath.Join("../../..", ".git")) { - log.Goodln("yay, found the root dir!") + repo, err := git.PlainOpenWithOptions(filepath.Dir(pkg.Path), + &git.PlainOpenOptions{DetectDotGit: true}) + if err != nil && !errors.Is(err, git.ErrRepositoryNotExists) { + return fmt.Errorf("cannot open Git repository: %w", err) + } - if history, err := NewPackageHistory(pkg.Path); err == nil { + if err == nil { + if history, err := NewPackageHistory(repo, pkg.Path); err == nil { log.Debugln("Obtained package history") m.history = history