forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package git
2
3import (
4 "errors"
5 "fmt"
6 "log/slog"
7 "net/url"
8 "os/exec"
9 "path/filepath"
10
11 "github.com/go-git/go-git/v5"
12 "github.com/go-git/go-git/v5/config"
13 knotconfig "tangled.org/core/knotserver/config"
14)
15
16func Fork(repoPath, source string, cfg *knotconfig.Config) error {
17 u, err := url.Parse(source)
18 if err != nil {
19 return fmt.Errorf("failed to parse source URL: %w", err)
20 }
21
22 if o := optimizeClone(u, cfg); o != nil {
23 u = o
24 }
25
26 cloneCmd := exec.Command("git", "clone", "--bare", u.String(), repoPath)
27 if err := cloneCmd.Run(); err != nil {
28 return fmt.Errorf("failed to bare clone repository: %w", err)
29 }
30
31 configureCmd := exec.Command("git", "-C", repoPath, "config", "receive.hideRefs", "refs/hidden")
32 if err := configureCmd.Run(); err != nil {
33 return fmt.Errorf("failed to configure hidden refs: %w", err)
34 }
35
36 return nil
37}
38
39func optimizeClone(u *url.URL, cfg *knotconfig.Config) *url.URL {
40 // only optimize if it's the same host
41 if u.Host != cfg.Server.Hostname {
42 return nil
43 }
44
45 local := filepath.Join(cfg.Repo.ScanPath, u.Path)
46
47 // sanity check: is there a git repo there?
48 if _, err := PlainOpen(local); err != nil {
49 return nil
50 }
51
52 // create optimized file:// URL
53 optimized := &url.URL{
54 Scheme: "file",
55 Path: local,
56 }
57
58 slog.Debug("performing local clone", "url", optimized.String())
59 return optimized
60}
61
62func (g *GitRepo) Sync() error {
63 branch := g.h.String()
64
65 fetchOpts := &git.FetchOptions{
66 RefSpecs: []config.RefSpec{
67 config.RefSpec("+" + branch + ":" + branch), // +refs/heads/master:refs/heads/master
68 },
69 }
70
71 err := g.r.Fetch(fetchOpts)
72 if errors.Is(git.NoErrAlreadyUpToDate, err) {
73 return nil
74 } else if err != nil {
75 return fmt.Errorf("failed to fetch origin branch: %s: %w", branch, err)
76 }
77 return nil
78}
79
80// TrackHiddenRemoteRef tracks a hidden remote in the repository. For example,
81// if the feature branch on the fork (forkRef) is feature-1, and the remoteRef,
82// i.e. the branch we want to merge into, is main, this will result in a refspec:
83//
84// +refs/heads/main:refs/hidden/feature-1/main
85func (g *GitRepo) TrackHiddenRemoteRef(forkRef, remoteRef string) error {
86 fetchOpts := &git.FetchOptions{
87 RefSpecs: []config.RefSpec{
88 config.RefSpec(fmt.Sprintf("+refs/heads/%s:refs/hidden/%s/%s", remoteRef, forkRef, remoteRef)),
89 },
90 RemoteName: "origin",
91 }
92
93 err := g.r.Fetch(fetchOpts)
94 if errors.Is(git.NoErrAlreadyUpToDate, err) {
95 return nil
96 } else if err != nil {
97 return fmt.Errorf("failed to fetch hidden remote: %s: %w", forkRef, err)
98 }
99 return nil
100}