// SPDX-FileCopyrightText: 2025 Romain Maneschi // // SPDX-License-Identifier: EUPL-1.2 package repository import ( "errors" git "github.com/go-git/go-git/v6" "github.com/go-git/go-git/v6/plumbing" "github.com/go-git/go-git/v6/plumbing/object" "github.com/go-git/go-git/v6/plumbing/storer" "github.com/samber/oops" "gitroot.dev/server/logger" ) func (repo *GitRootRepository) WalkCommit(from plumbing.Hash, to plumbing.Hash, callback func(*object.Commit) error) error { repoConfiguration, err := repo.Configuration() if err != nil { return repo.errorHandler.Wrapf(err, "configuration fail") } unlock := repo.manager.repoLocks.Read(repo.name) defer unlock() return walkCommit(repo.repo, repo.manager, repoConfiguration, from, to, callback) } func (repo *GitRootRepositoryWrite) WalkCommit(from plumbing.Hash, to plumbing.Hash, callback func(*object.Commit) error) error { repoConfiguration, err := repo.repo.Configuration() if err != nil { return repo.errorHandler.Wrapf(err, "configuration fail") } unlock := repo.repo.manager.repoLocks.Read(repo.repo.name) defer unlock() return walkCommit(repo.repoWrite, repo.repo.manager, repoConfiguration, from, to, callback) } func walkCommit(repo *git.Repository, manager *Manager, repoConf RepoConfiguration, from plumbing.Hash, to plumbing.Hash, callback func(*object.Commit) error) error { if to == plumbing.ZeroHash { // branch deletion return nil } if from == plumbing.ZeroHash { // branch creation com, err := repo.CommitObject(to) if err != nil { return oops.With("hash", to.String()).Wrapf(err, "can't get CommitObject") } com2, err := foundAncestor(repo, manager.logger, repoConf, com) if err != nil { return oops.With("hash", to.String()).Wrapf(err, "can't get FoundAncestor") } from = com2.Hash } // cIter, err := repo.Log(&git.LogOptions{From: to}) // if err != nil { // return err // } // display := fmt.Sprintf("----- [%s] -> [%s]\n", from.String(), to.String()) // cIter.ForEach(func(commit *object.Commit) error { // if commit.Hash == from { // return storer.ErrStop // } // display = fmt.Sprintf("%s [%s] %s : %s\n", display, commit.Hash.String(), commit.Message, commit.Author.Email) // return nil // }) // display = display + "-----\n" //os.Stdout.Write([]byte(display)) // TODO make a special logger? // cIter.Close() cIter, err := repo.Log(&git.LogOptions{From: to}) if err != nil { return oops.With("hash", to.String()).Wrapf(err, "can't Log") } defer cIter.Close() return cIter.ForEach(func(commit *object.Commit) error { if commit.Hash == from { return storer.ErrStop } return callback(commit) }) } func foundAncestor(repo *git.Repository, log *logger.Logger, repoConf RepoConfiguration, fromCommit *object.Commit) (*object.Commit, error) { var goodBranch *object.Commit var mainBranch *object.Commit bIter, err := repo.Branches() if err != nil { return nil, errors.New("branches fail") } err = bIter.ForEach(func(ref *plumbing.Reference) error { log.Info("Branch", logger.NewLoggerPair("name", ref.Name().String()), logger.NewLoggerPair("hash", ref.Hash().String()), logger.NewLoggerPair("symbolic", ref.Type() == plumbing.SymbolicReference)) branchCommit, err := repo.CommitObject(ref.Hash()) if err != nil { log.Error("CommitObject fail", err, logger.NewLoggerPair("hash", ref.Hash().String())) return nil } if ref.Name() == repoConf.DefaultBranch { mainBranch = branchCommit } if ref.Hash() == fromCommit.Hash { // eliminate current branch return nil } if ok, err := branchCommit.IsAncestor(fromCommit); ok { log.Info("Ref found", logger.NewLoggerPair("hash", ref.Hash().String())) if goodBranch == nil { goodBranch = branchCommit return nil } if ok, err := goodBranch.IsAncestor(branchCommit); ok { goodBranch = branchCommit } else if err != nil { log.Error("Ancestor between branches fail", err, logger.NewLoggerPair("goodBranch", goodBranch.Hash.String()), logger.NewLoggerPair("branchCommit", branchCommit.Hash.String())) } } else if err != nil { log.Error("IsAncestor fail", err, logger.NewLoggerPair("hash", ref.Hash().String())) return nil } else { log.Info("Not ancestor", logger.NewLoggerPair("hash", ref.Hash().String())) } return nil }) if goodBranch != nil && err == nil { log.Info("Selected Ref", logger.NewLoggerPair("for", fromCommit.Hash.String()), logger.NewLoggerPair("hash", goodBranch.Hash.String())) return goodBranch, nil } else if goodBranch == nil { log.Info("Branch not found try with mergeBase", logger.NewLoggerPair("for", fromCommit.Hash.String())) if mainBranch != nil { try, err := mainBranch.MergeBase(fromCommit) if err != nil { return nil, oops.Wrapf(err, "can't mergeBase") } else if len(try) > 0 { log.Info("Selected Ref", logger.NewLoggerPair("for", fromCommit.Hash.String()), logger.NewLoggerPair("hash", try[0].Hash.String())) return try[0], nil } } return nil, oops.With("for", fromCommit.Hash.String()).Errorf("Ancestor not found") } return nil, err }