forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
at master 2.5 kB view raw
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}