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 "github.com/go-git/go-git/v6/plumbing"
11 "github.com/go-git/go-git/v6/plumbing/object"
12 "github.com/go-git/go-git/v6/plumbing/protocol/packp"
13 "github.com/go-git/go-git/v6/plumbing/storer"
14 "gitroot.dev/server/user"
15)
16
17var ErrCantWrite = errors.New("user can't write")
18
19type MergeRes struct {
20 Cmd *packp.Command
21 Commits []plumbing.Hash
22}
23
24func (repo *GitRootRepositoryWrite) Merge(fromBranch string, toBranch string, commiter *user.Commiter, pusher user.SimpleUser) (*MergeRes, error) {
25 if ok, _, err := repo.repo.CanWrite(commiter.Signer.PublicKey(), fromBranch); err != nil || !ok {
26 if !ok {
27 return nil, repo.errorHandler.With("commiter", commiter.Email).With("ssh", commiter.Signer.PublicKey()).Wrapf(ErrCantWrite, "commiter can't write")
28 }
29 return nil, repo.errorHandler.Wrapf(err, "commiter can write err")
30 }
31
32 if ok, _, err := repo.repo.CanWrite(pusher.Ssh, fromBranch); err != nil || !ok {
33 if !ok {
34 return nil, repo.errorHandler.With("sshUser", pusher.Ssh).Wrapf(ErrCantWrite, "sshUser can't write")
35 }
36 return nil, repo.errorHandler.Wrapf(err, "sshUser can write err")
37 }
38
39 fromRef, err := repo.repoWrite.Storer.Reference(plumbing.NewBranchReferenceName(fromBranch))
40 if err != nil {
41 return nil, repo.errorHandler.Wrapf(err, "fromRef error")
42 }
43
44 toRef, err := repo.repoWrite.Storer.Reference(plumbing.NewBranchReferenceName(toBranch))
45 if err != nil {
46 return nil, repo.errorHandler.Wrapf(err, "toRef error")
47 }
48
49 if ok, err := isFastForward(repo.repoWrite.Storer, fromRef.Hash(), toRef.Hash()); !ok {
50 return nil, repo.errorHandler.Errorf("fastForward not possible")
51 } else if err != nil {
52 return nil, repo.errorHandler.Wrapf(err, "sshUser can write err")
53 }
54
55 repo.repo.logger.Debug("merge isFastForward")
56
57 toRefNew, err := repo.DeleteBranchInFilesAndCommit(toBranch)
58 if err != nil {
59 return nil, repo.errorHandler.Wrapf(err, "can't delete branch")
60 }
61
62 unlock := repo.lock.Write()
63
64 if err := repo.repoWrite.Storer.SetReference(plumbing.NewHashReference(plumbing.NewBranchReferenceName(fromBranch), toRefNew)); err != nil {
65 return nil, repo.errorHandler.Wrapf(err, "SetReference err")
66 }
67
68 unlock()
69
70 commits := []plumbing.Hash{}
71 repo.WalkCommit(fromRef.Hash(), toRefNew, func(c *object.Commit) error {
72 commits = append(commits, c.Hash)
73 return nil
74 })
75
76 return &MergeRes{
77 Cmd: &packp.Command{
78 Name: plumbing.NewBranchReferenceName(fromBranch),
79 Old: fromRef.Hash(),
80 New: toRefNew,
81 },
82 Commits: commits,
83 }, nil
84}
85
86func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash) (bool, error) {
87 found := false
88
89 c, err := object.GetCommit(s, new)
90 if err != nil {
91 return false, err
92 }
93
94 iter := object.NewCommitPreorderIter(c, nil, nil)
95 err = iter.ForEach(func(c *object.Commit) error {
96 if c.Hash != old {
97 return nil
98 }
99
100 found = true
101 return storer.ErrStop
102 })
103 return found, err
104}