GitRoot
craft your forge, build your project, grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package repository
6
7import (
8 "errors"
9
10 git "github.com/go-git/go-git/v5"
11 "github.com/go-git/go-git/v5/plumbing"
12 "github.com/go-git/go-git/v5/plumbing/object"
13 "github.com/go-git/go-git/v5/plumbing/storer"
14 "github.com/samber/oops"
15 "gitroot.dev/server/logger"
16)
17
18func (repo *GitRootRepository) WalkCommit(from plumbing.Hash, to plumbing.Hash, callback func(*object.Commit) error) error {
19 repoConfiguration, err := repo.Configuration()
20 if err != nil {
21 return repo.errorHandler.Wrapf(err, "configuration fail")
22 }
23 unlock := repo.manager.repoLocks.Read(repo.name)
24 defer unlock()
25 return walkCommit(repo.repo, repo.manager, repoConfiguration, from, to, callback)
26}
27
28func (repo *GitRootRepositoryWrite) WalkCommit(from plumbing.Hash, to plumbing.Hash, callback func(*object.Commit) error) error {
29 repoConfiguration, err := repo.repo.Configuration()
30 if err != nil {
31 return repo.errorHandler.Wrapf(err, "configuration fail")
32 }
33 unlock := repo.repo.manager.repoLocks.Read(repo.repo.name)
34 defer unlock()
35 return walkCommit(repo.repoWrite, repo.repo.manager, repoConfiguration, from, to, callback)
36}
37
38func walkCommit(repo *git.Repository, manager *Manager, repoConf RepoConfiguration, from plumbing.Hash, to plumbing.Hash, callback func(*object.Commit) error) error {
39 if to == plumbing.ZeroHash { // branch deletion
40 return nil
41 }
42 if from == plumbing.ZeroHash { // branch creation
43 com, err := repo.CommitObject(to)
44 if err != nil {
45 return oops.With("hash", to.String()).Wrapf(err, "can't get CommitObject")
46 }
47 com2, err := foundAncestor(repo, manager.logger, repoConf, com)
48 if err != nil {
49 return oops.With("hash", to.String()).Wrapf(err, "can't get FoundAncestor")
50 }
51 from = com2.Hash
52 }
53 // cIter, err := repo.Log(&git.LogOptions{From: to})
54 // if err != nil {
55 // return err
56 // }
57 // display := fmt.Sprintf("----- [%s] -> [%s]\n", from.String(), to.String())
58 // cIter.ForEach(func(commit *object.Commit) error {
59 // if commit.Hash == from {
60 // return storer.ErrStop
61 // }
62 // display = fmt.Sprintf("%s [%s] %s : %s\n", display, commit.Hash.String(), commit.Message, commit.Author.Email)
63 // return nil
64 // })
65 // display = display + "-----\n"
66 //os.Stdout.Write([]byte(display)) // TODO make a special logger?
67 // cIter.Close()
68 cIter, err := repo.Log(&git.LogOptions{From: to})
69 if err != nil {
70 return err
71 }
72 defer cIter.Close()
73 return cIter.ForEach(func(commit *object.Commit) error {
74 if commit.Hash == from {
75 return storer.ErrStop
76 }
77 return callback(commit)
78 })
79}
80
81func foundAncestor(repo *git.Repository, log *logger.Logger, repoConf RepoConfiguration, fromCommit *object.Commit) (*object.Commit, error) {
82 var goodBranch *object.Commit
83 var mainBranch *object.Commit
84 bIter, err := repo.Branches()
85 if err != nil {
86 return nil, errors.New("branches fail")
87 }
88
89 err = bIter.ForEach(func(ref *plumbing.Reference) error {
90 log.Info("Branch",
91 logger.NewLoggerPair("name", ref.Name().String()),
92 logger.NewLoggerPair("hash", ref.Hash().String()),
93 logger.NewLoggerPair("symbolic", ref.Type() == plumbing.SymbolicReference))
94 branchCommit, err := repo.CommitObject(ref.Hash())
95 if err != nil {
96 log.Error("CommitObject fail", err, logger.NewLoggerPair("hash", ref.Hash().String()))
97 return nil
98 }
99 if ref.Name() == repoConf.DefaultBranch {
100 mainBranch = branchCommit
101 }
102 if ref.Hash() == fromCommit.Hash { // eliminate current branch
103 return nil
104 }
105 if ok, err := branchCommit.IsAncestor(fromCommit); ok {
106 log.Info("Ref found", logger.NewLoggerPair("hash", ref.Hash().String()))
107 if goodBranch == nil {
108 goodBranch = branchCommit
109 return nil
110 }
111 if ok, err := goodBranch.IsAncestor(branchCommit); ok {
112 goodBranch = branchCommit
113 } else if err != nil {
114 log.Error("Ancestor between branches fail", err, logger.NewLoggerPair("goodBranch", goodBranch.Hash.String()), logger.NewLoggerPair("branchCommit", branchCommit.Hash.String()))
115 }
116 } else if err != nil {
117 log.Error("IsAncestor fail", err, logger.NewLoggerPair("hash", ref.Hash().String()))
118 return nil
119 } else {
120 log.Info("Not ancestor", logger.NewLoggerPair("hash", ref.Hash().String()))
121 }
122 return nil
123 })
124 if goodBranch != nil && err == nil {
125 log.Info("Selected Ref", logger.NewLoggerPair("for", fromCommit.Hash.String()), logger.NewLoggerPair("hash", goodBranch.Hash.String()))
126 return goodBranch, nil
127 } else if goodBranch == nil {
128 log.Info("Branch not found try with mergeBase", logger.NewLoggerPair("for", fromCommit.Hash.String()))
129 if mainBranch != nil {
130 try, err := mainBranch.MergeBase(fromCommit)
131 if err != nil {
132 return nil, oops.Wrapf(err, "can't mergeBase")
133 } else if len(try) > 0 {
134 log.Info("Selected Ref", logger.NewLoggerPair("for", fromCommit.Hash.String()), logger.NewLoggerPair("hash", try[0].Hash.String()))
135 return try[0], nil
136 }
137 }
138 return nil, oops.With("for", fromCommit.Hash.String()).Errorf("Ancestor not found")
139 }
140 return nil, err
141}