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