Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 39 additions & 15 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,13 @@ func Test_diffWorktrees(t *testing.T) {
Remote: "user@host.xz:path/to/repo1.git",
Root: "/root", Interval: 10 * time.Second, GitGC: "always",
Worktrees: []mirror.WorktreeConfig{
{Link: "link", Ref: "master", Pathspec: ""},
{Link: "link2", Ref: "other-branch", Pathspec: "path"},
{Link: "link", Ref: "master", Pathspecs: nil},
{Link: "link2", Ref: "other-branch", Pathspecs: []string{"path1", "path2/**/*.yaml", "*.c"}},
},
},
wantNewWTCs: []mirror.WorktreeConfig{
{Link: "link", Ref: "master"},
{Link: "link2", Ref: "other-branch", Pathspec: "path"},
{Link: "link2", Ref: "other-branch", Pathspecs: []string{"path1", "path2/**/*.yaml", "*.c"}},
},
wantRemovedWTs: nil,
},
Expand All @@ -124,44 +124,68 @@ func Test_diffWorktrees(t *testing.T) {
Remote: "user@host.xz:path/to/repo1.git",
Root: "/root", Interval: 10 * time.Second, GitGC: "always",
Worktrees: []mirror.WorktreeConfig{
{Link: "link", Ref: "master", Pathspec: ""},
{Link: "link2", Ref: "other-branch", Pathspec: "path"},
{Link: "link", Ref: "master", Pathspecs: nil},
{Link: "link2", Ref: "other-branch", Pathspecs: []string{"path1", "path2/**/*.yaml", "*.c"}},
{Link: "link3", Ref: "other-branch", Pathspecs: []string{"path"}},
},
},
newRepoConf: &mirror.RepositoryConfig{
Remote: "user@host.xz:path/to/repo1.git",
Root: "/root", Interval: 10 * time.Second, GitGC: "always",
Worktrees: []mirror.WorktreeConfig{
{Link: "link", Ref: "master", Pathspec: "new-path"},
{Link: "link2", Ref: "new-branch", Pathspec: "path"},
{Link: "link", Ref: "master", Pathspecs: []string{"new-path"}},
{Link: "link2", Ref: "new-branch", Pathspecs: []string{"path1", "path2/**/*.yaml", "*.c"}},
{Link: "link3", Ref: "other-branch", Pathspecs: []string{"path", "new-path"}},
},
},
wantNewWTCs: []mirror.WorktreeConfig{
{Link: "link", Ref: "master", Pathspec: "new-path"},
{Link: "link2", Ref: "new-branch", Pathspec: "path"},
{Link: "link", Ref: "master", Pathspecs: []string{"new-path"}},
{Link: "link2", Ref: "new-branch", Pathspecs: []string{"path1", "path2/**/*.yaml", "*.c"}},
{Link: "link3", Ref: "other-branch", Pathspecs: []string{"path", "new-path"}},
},
wantRemovedWTs: []string{"link", "link2"},
wantRemovedWTs: []string{"link", "link2", "link3"},
},
{
name: "rearrange-path",
initialRepoConf: &mirror.RepositoryConfig{
Remote: "user@host.xz:path/to/repo1.git",
Root: "/root", Interval: 10 * time.Second, GitGC: "always",
Worktrees: []mirror.WorktreeConfig{
{Link: "link", Ref: "master", Pathspecs: []string{"a", "b/**/c"}},
{Link: "link2", Ref: "other-branch", Pathspecs: []string{"path1", "path2/**/*.yaml", "*.c"}},
},
},
newRepoConf: &mirror.RepositoryConfig{
Remote: "user@host.xz:path/to/repo1.git",
Root: "/root", Interval: 10 * time.Second, GitGC: "always",
Worktrees: []mirror.WorktreeConfig{
{Link: "link", Ref: "master", Pathspecs: []string{"b/**/c", "a"}},
{Link: "link2", Ref: "other-branch", Pathspecs: []string{"path1", "*.c", "path2/**/*.yaml"}},
},
},
wantNewWTCs: nil,
wantRemovedWTs: nil,
},
{
name: "add_new_link",
initialRepoConf: &mirror.RepositoryConfig{
Remote: "user@host.xz:path/to/repo1.git",
Root: "/root", Interval: 10 * time.Second, GitGC: "always",
Worktrees: []mirror.WorktreeConfig{
{Link: "link", Ref: "master", Pathspec: ""},
{Link: "link2", Ref: "other-branch", Pathspec: "path"},
{Link: "link", Ref: "master", Pathspecs: nil},
{Link: "link2", Ref: "other-branch", Pathspecs: []string{"path1", "path2/**/*.yaml", "*.c"}},
},
},
newRepoConf: &mirror.RepositoryConfig{
Remote: "user@host.xz:path/to/repo1.git",
Root: "/root", Interval: 10 * time.Second, GitGC: "always",
Worktrees: []mirror.WorktreeConfig{
{Link: "link", Ref: "master", Pathspec: ""},
{Link: "link3", Ref: "other-branch", Pathspec: "path"},
{Link: "link", Ref: "master", Pathspecs: nil},
{Link: "link3", Ref: "other-branch", Pathspecs: []string{"path1", "path2/**/*.yaml", "*.c"}},
},
},
wantNewWTCs: []mirror.WorktreeConfig{
{Link: "link3", Ref: "other-branch", Pathspec: "path"},
{Link: "link3", Ref: "other-branch", Pathspecs: []string{"path1", "path2/**/*.yaml", "*.c"}},
},
wantRemovedWTs: []string{"link2"},
},
Expand Down
4 changes: 2 additions & 2 deletions pkg/mirror/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ type WorktreeConfig struct {
// are supported. default is HEAD
Ref string `yaml:"ref"`

// Pathspec of the dirs to checkout if required
Pathspec string `yaml:"pathspec"`
// Pathspecs of the dirs to checkout if required
Pathspecs []string `yaml:"pathspecs"`
}

// Auth represents authentication config of the repository
Expand Down
26 changes: 14 additions & 12 deletions pkg/mirror/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,16 @@ func (r *Repository) AddWorktreeLink(wtc WorktreeConfig) error {
}

wt := &WorkTreeLink{
link: wtc.Link,
linkAbs: linkAbs,
ref: wtc.Ref,
pathspec: wtc.Pathspec,
log: r.log.With("worktree", wtc.Link),
link: wtc.Link,
linkAbs: linkAbs,
ref: wtc.Ref,
pathspecs: wtc.Pathspecs,
log: r.log.With("worktree", wtc.Link),
}

// pathspecs must be sorted for for worktree equality checks
slices.Sort(wt.pathspecs)

r.workTreeLinks[wtc.Link] = wt
return nil
}
Expand Down Expand Up @@ -363,10 +366,8 @@ func (r *Repository) cloneByRef(ctx context.Context, dst, ref, pathspec string,

args := []string{"reset", "--hard", ref}
// git reset --hard <ref>
if out, err := runGitCommand(ctx, r.log, nil, dst, args...); err != nil {
if _, err := runGitCommand(ctx, r.log, nil, dst, args...); err != nil {
return "", err
} else {
fmt.Println(out)
}

// get the hash of the repos HEAD
Expand Down Expand Up @@ -708,7 +709,7 @@ func (r *Repository) hash(ctx context.Context, ref, path string) (string, error)
// it will remove worktree if tracking ref is removed from the remote
func (r *Repository) ensureWorktreeLink(ctx context.Context, wl *WorkTreeLink) error {
// get remote hash from mirrored repo for the worktree link
remoteHash, err := r.hash(ctx, wl.ref, wl.pathspec)
remoteHash, err := r.hash(ctx, wl.ref, "")
if err != nil {
return fmt.Errorf("unable to get hash for worktree:%s err:%w", wl.link, err)
}
Expand Down Expand Up @@ -798,10 +799,11 @@ func (r *Repository) createWorktree(ctx context.Context, wl *WorkTreeLink, hash

// only checkout required path if specified
args := []string{"checkout", hash}
if wl.pathspec != "" {
args = append(args, "--", wl.pathspec)
if len(wl.pathspecs) > 0 {
args = append(args, "--")
args = append(args, wl.pathspecs...)
}
// git checkout <hash> -- <pathspec>
// git checkout <hash> -- <pathspec...>
if _, err := runGitCommand(ctx, wl.log, nil, wtPath, args...); err != nil {
return "", err
}
Expand Down
20 changes: 10 additions & 10 deletions pkg/mirror/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,12 @@ func TestRepo_AddWorktreeLink(t *testing.T) {
args args
wantErr bool
}{
{"all-valid", args{wtc: WorktreeConfig{"link", "master", ""}}, false},
{"all-valid-with-path", args{wtc: WorktreeConfig{"link2", "other-branch", "path"}}, false},
{"duplicate-link", args{wtc: WorktreeConfig{"link", "master", ""}}, true},
{"no-link", args{wtc: WorktreeConfig{"", "master", ""}}, true},
{"no-ref", args{wtc: WorktreeConfig{"link3", "", ""}}, false},
{"absLink", args{wtc: WorktreeConfig{"/tmp/link", "tag", ""}}, false},
{"all-valid", args{wtc: WorktreeConfig{"link", "master", []string{}}}, false},
{"all-valid-with-paths", args{wtc: WorktreeConfig{"link2", "other-branch", []string{"path1", "path2/**/*.yaml", "*.c"}}}, false},
{"duplicate-link", args{wtc: WorktreeConfig{"link", "master", []string{}}}, true},
{"no-link", args{wtc: WorktreeConfig{"", "master", []string{}}}, true},
{"no-ref", args{wtc: WorktreeConfig{"link3", "", []string{}}}, false},
{"absLink", args{wtc: WorktreeConfig{"/tmp/link", "tag", []string{}}}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -155,10 +155,10 @@ func TestRepo_AddWorktreeLink(t *testing.T) {
}
// compare all worktree links
want := map[string]*WorkTreeLink{
"link": {link: "link", linkAbs: "/tmp/root/link", ref: "master"},
"link2": {link: "link2", linkAbs: "/tmp/root/link2", ref: "other-branch", pathspec: "path"},
"link3": {link: "link3", linkAbs: "/tmp/root/link3", ref: "HEAD"},
"/tmp/link": {link: "/tmp/link", linkAbs: "/tmp/link", ref: "tag"},
"link": {link: "link", linkAbs: "/tmp/root/link", ref: "master", pathspecs: []string{}},
"link2": {link: "link2", linkAbs: "/tmp/root/link2", ref: "other-branch", pathspecs: []string{"*.c", "path1", "path2/**/*.yaml"}},
"link3": {link: "link3", linkAbs: "/tmp/root/link3", ref: "HEAD", pathspecs: []string{}},
"/tmp/link": {link: "/tmp/link", linkAbs: "/tmp/link", ref: "tag", pathspecs: []string{}},
}
if diff := cmp.Diff(want, r.workTreeLinks, cmpopts.IgnoreFields(WorkTreeLink{}, "log"), cmp.AllowUnexported(WorkTreeLink{})); diff != "" {
t.Errorf("Repo.AddWorktreeLink() worktreelinks mismatch (-want +got):\n%s", diff)
Expand Down
18 changes: 11 additions & 7 deletions pkg/mirror/worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@ import (
"fmt"
"log/slog"
"path/filepath"
"slices"
"strings"
)

type WorkTreeLink struct {
link string // link name as its specified in config, might not be unique only use it for logging
linkAbs string // the path at which to create a symlink to the worktree dir
ref string // the ref of the worktree
pathspec string // pathspec of the dirs to checkout
log *slog.Logger
link string // link name as its specified in config, might not be unique only use it for logging
linkAbs string // the path at which to create a symlink to the worktree dir
ref string // the ref of the worktree
pathspecs []string // pathspecs of the paths to checkout
log *slog.Logger
}

func (wt *WorkTreeLink) Equals(wtc WorktreeConfig) bool {
sortedConfigPaths := slices.Clone(wtc.Pathspecs)
slices.Sort(sortedConfigPaths)

return wt.link == wtc.Link &&
wt.pathspec == wtc.Pathspec &&
wt.ref == wtc.Ref
wt.ref == wtc.Ref &&
slices.Compare(wt.pathspecs, sortedConfigPaths) == 0
}

// worktreeDirName will generate worktree name for specific worktree link
Expand Down
11 changes: 6 additions & 5 deletions pkg/mirror/z_e2e_race_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package mirror

import (
"context"
"log"
"os"
"path/filepath"
"sync"
Expand All @@ -31,7 +30,7 @@ func Test_mirror_detect_race(t *testing.T) {

repo := mustCreateRepoAndMirror(t, upstream, root, link1, ref1)
// add worktree for HEAD
if err := repo.AddWorktreeLink(WorktreeConfig{link2, ref2, ""}); err != nil {
if err := repo.AddWorktreeLink(WorktreeConfig{link2, ref2, []string{}}); err != nil {
t.Fatalf("unable to add worktree error: %v", err)
}
// mirror again for 2nd worktree
Expand All @@ -51,7 +50,7 @@ func Test_mirror_detect_race(t *testing.T) {
t.Log("TEST-2: forward HEAD")
fileSHA2 := mustCommit(t, upstream, "file", testName+"-2")

t.Run("test-1", func(t *testing.T) {
t.Run("clone-test", func(t *testing.T) {
wg := &sync.WaitGroup{}
// all following assertions will always be true
// this test is about testing deadlocks and detecting race conditions
Expand All @@ -60,7 +59,8 @@ func Test_mirror_detect_race(t *testing.T) {
go func() {
defer wg.Done()
if err := repo.Mirror(ctx); err != nil {
log.Fatalf("unable to mirror error: %v", err)
t.Error("unable to mirror", "err", err)
os.Exit(1)
}

assertLinkedFile(t, root, link1, "file", testName+"-2")
Expand All @@ -81,7 +81,8 @@ func Test_mirror_detect_race(t *testing.T) {
go func() {
defer wg.Done()
if err := repo.Mirror(ctx); err != nil {
log.Fatalf("unable to mirror error: %v", err)
t.Error("unable to mirror error", "err", err)
os.Exit(1)
}
}()

Expand Down
Loading
Loading