summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--doc/api-reference.md9
-rw-r--r--doc/configuration.md22
-rw-r--r--internal/config/config.go20
-rw-r--r--internal/config/config_test.go27
-rw-r--r--internal/showcase/metadata.go17
-rw-r--r--internal/showcase/metadata_test.go63
-rw-r--r--internal/showcase/showcase.go102
-rw-r--r--internal/showcase/showcase_test.go125
-rw-r--r--internal/version/version.go2
10 files changed, 370 insertions, 24 deletions
diff --git a/README.md b/README.md
index aeccbee..8ad4e05 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,10 @@ Create a configuration file at `~/.config/gitsyncer/config.json` (or specify a c
"repositories": [
"repo1",
"repo2"
- ]
+ ],
+ "showcase_stats_branches": {
+ "foo.zone": "content-gemtext"
+ }
}
```
@@ -449,6 +452,8 @@ Weekly rank snapshots are written on full showcase runs (all repositories), incl
The showcase output is written to `~/git/foo.zone-content/gemtext/about/showcase.gmi.tpl` by default (currently hardcoded).
+You can override the branch used for showcase stats and cached code snippets on a per-repository basis with `showcase_stats_branches`. For example, `foo.zone` can use `content-gemtext` while the rest of the repos continue to use their current checkout branch.
+
Projects can be excluded from the showcase by creating a `.nosync` file in their repository root.
## Example Workflows
diff --git a/doc/api-reference.md b/doc/api-reference.md
index cc4f69c..c189e99 100644
--- a/doc/api-reference.md
+++ b/doc/api-reference.md
@@ -205,9 +205,10 @@ type Organization struct {
#### type Config
```go
type Config struct {
- Organizations []Organization `json:"organizations"` // List of git organizations
- Repositories []string `json:"repositories"` // Specific repos to sync
- ExcludeBranches []string `json:"exclude_branches"` // Regex patterns for branch exclusion
+ Organizations []Organization `json:"organizations"` // List of git organizations
+ Repositories []string `json:"repositories"` // Specific repos to sync
+ ExcludeBranches []string `json:"exclude_branches"` // Regex patterns for branch exclusion
+ ShowcaseStatsBranches map[string]string `json:"showcase_stats_branches"` // Per-repo branch overrides for showcase stats/code snippets
}
```
@@ -511,4 +512,4 @@ gitsyncer version 0.1.0
```
#### func GetShortVersion() string
-Returns just the version number: `0.1.0` \ No newline at end of file
+Returns just the version number: `0.1.0`
diff --git a/doc/configuration.md b/doc/configuration.md
index 25a425f..230cac6 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -37,7 +37,10 @@ GitSyncer looks for configuration files in the following order:
"exclude_branches": [
"^temp-",
"-wip$"
- ]
+ ],
+ "showcase_stats_branches": {
+ "foo.zone": "content-gemtext"
+ }
}
```
@@ -78,6 +81,18 @@ Example:
}
```
+#### showcase_stats_branches (optional)
+Map of repository names to the branch that should be used when generating showcase statistics and cached code snippets. This is useful when the primary content for a repo lives on a non-default branch.
+
+Example:
+```json
+{
+ "showcase_stats_branches": {
+ "foo.zone": "content-gemtext"
+ }
+}
+```
+
## Examples
### Minimal Configuration
@@ -123,7 +138,10 @@ Sync between GitHub and Codeberg:
"^temp-",
"-wip$",
"^old-"
- ]
+ ],
+ "showcase_stats_branches": {
+ "foo.zone": "content-gemtext"
+ }
}
```
diff --git a/internal/config/config.go b/internal/config/config.go
index 48e6d5f..4e40cdf 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -19,11 +19,12 @@ type Organization struct {
// Config holds the application configuration
type Config struct {
- Organizations []Organization `json:"organizations"`
- Repositories []string `json:"repositories,omitempty"`
- ExcludeBranches []string `json:"exclude_branches,omitempty"` // Regex patterns for branches to exclude
- WorkDir string `json:"work_dir,omitempty"` // Working directory for cloning repositories
- ExcludeFromShowcase []string `json:"exclude_from_showcase,omitempty"` // Repository names to exclude from showcase
+ Organizations []Organization `json:"organizations"`
+ Repositories []string `json:"repositories,omitempty"`
+ ExcludeBranches []string `json:"exclude_branches,omitempty"` // Regex patterns for branches to exclude
+ WorkDir string `json:"work_dir,omitempty"` // Working directory for cloning repositories
+ ExcludeFromShowcase []string `json:"exclude_from_showcase,omitempty"` // Repository names to exclude from showcase
+ ShowcaseStatsBranches map[string]string `json:"showcase_stats_branches,omitempty"` // Repository names mapped to the branch used for showcase stats/code snippets
// SkipReleases maps a repository name to a list of tag names for which
// releases should NOT be created on any platform (GitHub/Codeberg)
SkipReleases map[string][]string `json:"skip_releases,omitempty"`
@@ -102,6 +103,15 @@ func (c *Config) Validate() error {
}
}
+ for repo, branch := range c.ShowcaseStatsBranches {
+ if strings.TrimSpace(repo) == "" {
+ return fmt.Errorf("showcase_stats_branches: repository name cannot be empty")
+ }
+ if strings.TrimSpace(branch) == "" {
+ return fmt.Errorf("showcase_stats_branches[%q]: branch name cannot be empty", repo)
+ }
+ }
+
return nil
}
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
new file mode 100644
index 0000000..db70457
--- /dev/null
+++ b/internal/config/config_test.go
@@ -0,0 +1,27 @@
+package config
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestValidate_ShowcaseStatsBranchesRejectsEmptyBranch(t *testing.T) {
+ t.Parallel()
+
+ cfg := &Config{
+ Organizations: []Organization{
+ {Host: "git@github.com", Name: "test-user"},
+ },
+ ShowcaseStatsBranches: map[string]string{
+ "foo.zone": " ",
+ },
+ }
+
+ err := cfg.Validate()
+ if err == nil {
+ t.Fatal("Validate() error = nil, want branch validation error")
+ }
+ if !strings.Contains(err.Error(), "showcase_stats_branches") {
+ t.Fatalf("Validate() error = %q, want showcase_stats_branches context", err)
+ }
+}
diff --git a/internal/showcase/metadata.go b/internal/showcase/metadata.go
index 798b8fe..038dc08 100644
--- a/internal/showcase/metadata.go
+++ b/internal/showcase/metadata.go
@@ -131,9 +131,9 @@ func calculateRepoScore(linesOfCode int, avgCommitAge float64, tagCount int, has
return score
}
-// getCommitCount returns the total number of commits
+// getCommitCount returns the total number of commits reachable from the current HEAD.
func getCommitCount(repoPath string) (int, error) {
- cmd := exec.Command("git", "-C", repoPath, "rev-list", "--all", "--count")
+ cmd := exec.Command("git", "-C", repoPath, "rev-list", "--count", "HEAD")
output, err := cmd.Output()
if err != nil {
return 0, err
@@ -179,7 +179,7 @@ func countLinesOfCode(repoPath string) (int, error) {
// getFirstCommitDate returns the date of the first commit
func getFirstCommitDate(repoPath string) (string, error) {
- cmd := exec.Command("git", "-C", repoPath, "log", "--reverse", "--pretty=format:%ai", "--date=short")
+ cmd := exec.Command("git", "-C", repoPath, "log", "--reverse", "--pretty=format:%ai", "--date=short", "HEAD")
output, err := cmd.Output()
if err != nil {
return "", err
@@ -199,7 +199,7 @@ func getFirstCommitDate(repoPath string) (string, error) {
// getLastCommitDate returns the date of the last commit
func getLastCommitDate(repoPath string) (string, error) {
- cmd := exec.Command("git", "-C", repoPath, "log", "-1", "--pretty=format:%ai", "--date=short")
+ cmd := exec.Command("git", "-C", repoPath, "log", "-1", "--pretty=format:%ai", "--date=short", "HEAD")
output, err := cmd.Output()
if err != nil {
return "", err
@@ -273,7 +273,7 @@ func detectLicense(repoPath string) string {
// getAverageCommitAge calculates the average age of the last N commits in days
func getAverageCommitAge(repoPath string, commitCount int) (float64, error) {
// Get the last N commit dates
- cmd := exec.Command("git", "-C", repoPath, "log", fmt.Sprintf("-%d", commitCount), "--pretty=format:%at")
+ cmd := exec.Command("git", "-C", repoPath, "log", fmt.Sprintf("-%d", commitCount), "--pretty=format:%at", "HEAD")
output, err := cmd.Output()
if err != nil {
return 0, err
@@ -311,14 +311,15 @@ func getAverageCommitAge(repoPath string, commitCount int) (float64, error) {
return totalAge / float64(validCommits), nil
}
-// getLatestTag returns the latest version-like tag, its date, whether the repo has releases, and total tag count.
+// getLatestTag returns the latest version-like tag merged into HEAD, its date,
+// whether the repo has releases, and total merged tag count.
func getLatestTag(repoPath string) (string, string, bool, int, error) {
// First try to get tags sorted by version
- cmd := exec.Command("git", "-C", repoPath, "tag", "-l", "--sort=-version:refname")
+ cmd := exec.Command("git", "-C", repoPath, "tag", "-l", "--merged", "HEAD", "--sort=-version:refname")
output, err := cmd.Output()
if err != nil {
// Fallback to describe
- cmd = exec.Command("git", "-C", repoPath, "describe", "--tags", "--abbrev=0")
+ cmd = exec.Command("git", "-C", repoPath, "describe", "--tags", "--abbrev=0", "HEAD")
output, err = cmd.Output()
if err != nil {
// No tags at all
diff --git a/internal/showcase/metadata_test.go b/internal/showcase/metadata_test.go
index e8777ce..abc4664 100644
--- a/internal/showcase/metadata_test.go
+++ b/internal/showcase/metadata_test.go
@@ -79,6 +79,69 @@ func TestGetLatestTag_ReturnsTotalTagCount(t *testing.T) {
}
}
+func TestExtractRepoMetadata_UsesCurrentBranchState(t *testing.T) {
+ t.Parallel()
+
+ repoPath := t.TempDir()
+ runGit(t, repoPath, "init", "--initial-branch=main")
+ runGit(t, repoPath, "config", "user.name", "Test User")
+ runGit(t, repoPath, "config", "user.email", "test@example.com")
+
+ writeAndCommit := func(name, content, message string) {
+ path := filepath.Join(repoPath, name)
+ if err := os.WriteFile(path, []byte(content), 0644); err != nil {
+ t.Fatalf("write file %s: %v", name, err)
+ }
+ runGit(t, repoPath, "add", name)
+ runGit(t, repoPath, "commit", "-m", message)
+ }
+
+ writeAndCommit("main.go", "package main\n\nfunc main() {\n}\n", "main branch")
+ runGit(t, repoPath, "tag", "v1.0.0")
+
+ runGit(t, repoPath, "checkout", "-b", "content-gemtext")
+ writeAndCommit("content.go", "package main\n\nfunc render() string {\n\treturn \"gemtext\"\n}\n", "content branch")
+ runGit(t, repoPath, "tag", "v2.0.0")
+
+ runGit(t, repoPath, "checkout", "main")
+
+ mainMetadata, err := extractRepoMetadata(repoPath)
+ if err != nil {
+ t.Fatalf("extractRepoMetadata(main) error = %v", err)
+ }
+ if mainMetadata.CommitCount != 1 {
+ t.Fatalf("main branch CommitCount = %d, want %d", mainMetadata.CommitCount, 1)
+ }
+ if mainMetadata.LinesOfCode != 4 {
+ t.Fatalf("main branch LinesOfCode = %d, want %d", mainMetadata.LinesOfCode, 4)
+ }
+ if mainMetadata.LatestTag != "v1.0.0" {
+ t.Fatalf("main branch LatestTag = %q, want %q", mainMetadata.LatestTag, "v1.0.0")
+ }
+ if mainMetadata.TagCount != 1 {
+ t.Fatalf("main branch TagCount = %d, want %d", mainMetadata.TagCount, 1)
+ }
+
+ runGit(t, repoPath, "checkout", "content-gemtext")
+
+ contentMetadata, err := extractRepoMetadata(repoPath)
+ if err != nil {
+ t.Fatalf("extractRepoMetadata(content-gemtext) error = %v", err)
+ }
+ if contentMetadata.CommitCount != 2 {
+ t.Fatalf("content-gemtext CommitCount = %d, want %d", contentMetadata.CommitCount, 2)
+ }
+ if contentMetadata.LinesOfCode != 9 {
+ t.Fatalf("content-gemtext LinesOfCode = %d, want %d", contentMetadata.LinesOfCode, 9)
+ }
+ if contentMetadata.LatestTag != "v2.0.0" {
+ t.Fatalf("content-gemtext LatestTag = %q, want %q", contentMetadata.LatestTag, "v2.0.0")
+ }
+ if contentMetadata.TagCount != 2 {
+ t.Fatalf("content-gemtext TagCount = %d, want %d", contentMetadata.TagCount, 2)
+ }
+}
+
func runGit(t *testing.T, repoPath string, args ...string) string {
t.Helper()
diff --git a/internal/showcase/showcase.go b/internal/showcase/showcase.go
index 25f28f9..9cf43a6 100644
--- a/internal/showcase/showcase.go
+++ b/internal/showcase/showcase.go
@@ -174,7 +174,11 @@ func (g *Generator) GenerateShowcase(repoFilter []string, forceRegenerate bool)
// runCommandWithTimeout runs a command with a short timeout and returns trimmed stdout.
// Stderr is included in the error message for easier debugging when GITSYNCER_DEBUG=1.
func runCommandWithTimeout(name string, args ...string) (string, error) {
- ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
+ return runCommandWithCustomTimeout(8*time.Second, name, args...)
+}
+
+func runCommandWithCustomTimeout(timeout time.Duration, name string, args ...string) (string, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, name, args...)
out, err := cmd.CombinedOutput()
@@ -680,6 +684,88 @@ func (g *Generator) buildProjectLinks(repoName string) (string, string) {
return codebergURL, githubURL
}
+func (g *Generator) prepareStatsRepoPath(repoName, repoPath string) (string, func() error, error) {
+ if g.config == nil {
+ return repoPath, func() error { return nil }, nil
+ }
+
+ branch := strings.TrimSpace(g.config.ShowcaseStatsBranches[repoName])
+ if branch == "" {
+ return repoPath, func() error { return nil }, nil
+ }
+
+ resolvedRef, err := resolveShowcaseStatsRef(repoPath, branch)
+ if err != nil {
+ return "", nil, fmt.Errorf("failed to resolve showcase stats branch for %s: %w", repoName, err)
+ }
+
+ tempPrefix := strings.ReplaceAll(repoName, string(os.PathSeparator), "-")
+ tempRoot, err := os.MkdirTemp("", "gitsyncer-showcase-"+tempPrefix+"-")
+ if err != nil {
+ return "", nil, fmt.Errorf("failed to create temporary worktree root for %s: %w", repoName, err)
+ }
+
+ worktreePath := filepath.Join(tempRoot, "repo")
+ if _, err := runCommandWithCustomTimeout(45*time.Second, "git", "-C", repoPath, "worktree", "add", "--detach", worktreePath, resolvedRef); err != nil {
+ _ = os.RemoveAll(tempRoot)
+ return "", nil, fmt.Errorf("failed to create showcase stats worktree for %s on branch %q: %w", repoName, branch, err)
+ }
+
+ cleanup := func() error {
+ defer os.RemoveAll(tempRoot)
+
+ if _, err := runCommandWithCustomTimeout(45*time.Second, "git", "-C", repoPath, "worktree", "remove", "--force", worktreePath); err != nil {
+ return fmt.Errorf("failed to remove temporary worktree for %s: %w", repoName, err)
+ }
+
+ return nil
+ }
+
+ if resolvedRef == branch {
+ fmt.Printf("Using showcase stats branch %q for %s\n", branch, repoName)
+ } else {
+ fmt.Printf("Using showcase stats branch %q for %s (resolved to %s)\n", branch, repoName, resolvedRef)
+ }
+
+ return worktreePath, cleanup, nil
+}
+
+func resolveShowcaseStatsRef(repoPath, branch string) (string, error) {
+ localRef := "refs/heads/" + branch
+ if _, err := runCommandWithTimeout("git", "-C", repoPath, "show-ref", "--verify", "--quiet", localRef); err == nil {
+ return branch, nil
+ }
+
+ output, err := runCommandWithTimeout("git", "-C", repoPath, "for-each-ref", "--format=%(refname)", "refs/remotes")
+ if err != nil {
+ return "", fmt.Errorf("failed to inspect remote refs for branch %q: %w", branch, err)
+ }
+
+ var candidates []string
+ for _, line := range strings.Split(strings.TrimSpace(output), "\n") {
+ ref := strings.TrimSpace(line)
+ if ref == "" || strings.HasSuffix(ref, "/HEAD") {
+ continue
+ }
+ if strings.HasSuffix(ref, "/"+branch) {
+ candidates = append(candidates, ref)
+ }
+ }
+
+ if len(candidates) == 0 {
+ return "", fmt.Errorf("branch %q not found locally or on any remote", branch)
+ }
+
+ sort.Strings(candidates)
+ for _, ref := range candidates {
+ if strings.HasPrefix(ref, "refs/remotes/origin/") {
+ return ref, nil
+ }
+ }
+
+ return candidates[0], nil
+}
+
// generateProjectSummary generates a summary for a single project
func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool) (*ProjectSummary, error) {
repoPath := filepath.Join(g.workDir, repoName)
@@ -708,9 +794,19 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool
readmeContent, readmeFile, readmeFound := findReadmeContent(repoPath)
+ statsRepoPath, cleanupStatsRepoPath, err := g.prepareStatsRepoPath(repoName, repoPath)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err := cleanupStatsRepoPath(); err != nil {
+ fmt.Printf("Warning: %v\n", err)
+ }
+ }()
+
// Always extract metadata (not cached)
fmt.Printf("Extracting repository metadata...\n")
- metadata, err := extractRepoMetadata(repoPath)
+ metadata, err := extractRepoMetadata(statsRepoPath)
if err != nil {
fmt.Printf("Warning: Failed to extract some metadata: %v\n", err)
// Continue anyway with partial metadata
@@ -755,7 +851,7 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool
// Extract code snippet for all projects
var codeSnippet, codeLanguage string
if metadata != nil && len(metadata.Languages) > 0 {
- snippet, lang, err := extractCodeSnippet(repoPath, metadata.Languages)
+ snippet, lang, err := extractCodeSnippet(statsRepoPath, metadata.Languages)
if err != nil {
fmt.Printf("Warning: Failed to extract code snippet: %v\n", err)
} else {
diff --git a/internal/showcase/showcase_test.go b/internal/showcase/showcase_test.go
index 8bcad88..fa18799 100644
--- a/internal/showcase/showcase_test.go
+++ b/internal/showcase/showcase_test.go
@@ -2,6 +2,7 @@ package showcase
import (
"os"
+ "os/exec"
"path/filepath"
"reflect"
"strings"
@@ -229,3 +230,127 @@ func TestExtractUsefulSummary_SkipsFencedCodeBlocks(t *testing.T) {
t.Fatalf("extractUsefulSummary() = %q, want %q", got, want)
}
}
+
+func TestPrepareStatsRepoPath_UsesConfiguredBranchWithoutChangingMainCheckout(t *testing.T) {
+ t.Parallel()
+
+ repoPath := t.TempDir()
+ runGit(t, repoPath, "init", "--initial-branch=main")
+ runGit(t, repoPath, "config", "user.name", "Test User")
+ runGit(t, repoPath, "config", "user.email", "test@example.com")
+
+ mainFile := filepath.Join(repoPath, "README.md")
+ if err := os.WriteFile(mainFile, []byte("main branch"), 0644); err != nil {
+ t.Fatalf("write README.md: %v", err)
+ }
+ runGit(t, repoPath, "add", "README.md")
+ runGit(t, repoPath, "commit", "-m", "main")
+
+ runGit(t, repoPath, "checkout", "-b", "content-gemtext")
+ branchOnlyFile := filepath.Join(repoPath, "branch-only.txt")
+ if err := os.WriteFile(branchOnlyFile, []byte("content branch"), 0644); err != nil {
+ t.Fatalf("write branch-only.txt: %v", err)
+ }
+ runGit(t, repoPath, "add", "branch-only.txt")
+ runGit(t, repoPath, "commit", "-m", "content branch")
+ runGit(t, repoPath, "checkout", "main")
+
+ g := &Generator{
+ config: &config.Config{
+ ShowcaseStatsBranches: map[string]string{
+ "foo.zone": "content-gemtext",
+ },
+ },
+ }
+
+ statsRepoPath, cleanup, err := g.prepareStatsRepoPath("foo.zone", repoPath)
+ if err != nil {
+ t.Fatalf("prepareStatsRepoPath() error = %v", err)
+ }
+ defer func() {
+ if err := cleanup(); err != nil {
+ t.Fatalf("cleanup() error = %v", err)
+ }
+ }()
+
+ if statsRepoPath == repoPath {
+ t.Fatal("expected a detached worktree path for configured stats branch")
+ }
+ if _, err := os.Stat(filepath.Join(statsRepoPath, "branch-only.txt")); err != nil {
+ t.Fatalf("expected branch-only file in detached worktree: %v", err)
+ }
+ if _, err := os.Stat(filepath.Join(repoPath, "branch-only.txt")); !os.IsNotExist(err) {
+ t.Fatalf("expected branch-only file to stay absent from main checkout, stat err = %v", err)
+ }
+
+ currentBranch := strings.TrimSpace(runGit(t, repoPath, "branch", "--show-current"))
+ if currentBranch != "main" {
+ t.Fatalf("current branch = %q, want %q", currentBranch, "main")
+ }
+}
+
+func TestPrepareStatsRepoPath_UsesRemoteTrackingBranchWhenLocalBranchMissing(t *testing.T) {
+ t.Parallel()
+
+ rootDir := t.TempDir()
+ seedRepoPath := filepath.Join(rootDir, "seed")
+ runGit(t, rootDir, "init", "--initial-branch=main", seedRepoPath)
+ runGit(t, seedRepoPath, "config", "user.name", "Test User")
+ runGit(t, seedRepoPath, "config", "user.email", "test@example.com")
+
+ if err := os.WriteFile(filepath.Join(seedRepoPath, "README.md"), []byte("main branch"), 0644); err != nil {
+ t.Fatalf("write README.md: %v", err)
+ }
+ runGit(t, seedRepoPath, "add", "README.md")
+ runGit(t, seedRepoPath, "commit", "-m", "main")
+
+ runGit(t, seedRepoPath, "checkout", "-b", "content-gemtext")
+ if err := os.WriteFile(filepath.Join(seedRepoPath, "branch-only.txt"), []byte("content branch"), 0644); err != nil {
+ t.Fatalf("write branch-only.txt: %v", err)
+ }
+ runGit(t, seedRepoPath, "add", "branch-only.txt")
+ runGit(t, seedRepoPath, "commit", "-m", "content branch")
+ runGit(t, seedRepoPath, "checkout", "main")
+
+ remoteRepoPath := filepath.Join(rootDir, "remote.git")
+ cloneCmd := exec.Command("git", "clone", "--bare", seedRepoPath, remoteRepoPath)
+ if output, err := cloneCmd.CombinedOutput(); err != nil {
+ t.Fatalf("git clone --bare failed: %v\n%s", err, string(output))
+ }
+
+ cloneRepoPath := filepath.Join(rootDir, "clone")
+ workingCloneCmd := exec.Command("git", "clone", remoteRepoPath, cloneRepoPath)
+ if output, err := workingCloneCmd.CombinedOutput(); err != nil {
+ t.Fatalf("git clone failed: %v\n%s", err, string(output))
+ }
+
+ g := &Generator{
+ config: &config.Config{
+ ShowcaseStatsBranches: map[string]string{
+ "foo.zone": "content-gemtext",
+ },
+ },
+ }
+
+ statsRepoPath, cleanup, err := g.prepareStatsRepoPath("foo.zone", cloneRepoPath)
+ if err != nil {
+ t.Fatalf("prepareStatsRepoPath() error = %v", err)
+ }
+ defer func() {
+ if err := cleanup(); err != nil {
+ t.Fatalf("cleanup() error = %v", err)
+ }
+ }()
+
+ if _, err := os.Stat(filepath.Join(statsRepoPath, "branch-only.txt")); err != nil {
+ t.Fatalf("expected branch-only file in detached worktree from remote branch: %v", err)
+ }
+ if _, err := os.Stat(filepath.Join(cloneRepoPath, "branch-only.txt")); !os.IsNotExist(err) {
+ t.Fatalf("expected branch-only file to stay absent from main checkout, stat err = %v", err)
+ }
+
+ currentBranch := strings.TrimSpace(runGit(t, cloneRepoPath, "branch", "--show-current"))
+ if currentBranch != "main" {
+ t.Fatalf("current branch = %q, want %q", currentBranch, "main")
+ }
+}
diff --git a/internal/version/version.go b/internal/version/version.go
index feb050c..afd3d4e 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -7,7 +7,7 @@ import (
var (
// Version is the current version of gitsyncer
- Version = "0.15.8"
+ Version = "0.16.0"
// GitCommit is the git commit hash at build time
GitCommit = "unknown"