feat: add --worktree flag for bare clone with worktree

Closes #428

Signed-off-by: David Jeong <drumrobot43@gmail.com>
This commit is contained in:
David Jeong 2025-12-15 01:44:57 +09:00
parent 4f4abb143d
commit d243f00208
4 changed files with 68 additions and 4 deletions

View file

@ -26,6 +26,7 @@ func doGet(c *cli.Context) error {
parallel = c.Bool("parallel")
silent = c.Bool("silent")
)
worktreePath := c.String("worktree")
g := &getter{
update: c.Bool("update"),
shallow: c.Bool("shallow"),
@ -34,8 +35,9 @@ func doGet(c *cli.Context) error {
silent: silent,
branch: c.String("branch"),
recursive: !c.Bool("no-recursive"),
bare: c.Bool("bare"),
bare: c.Bool("bare") || worktreePath != "", // --worktree implies --bare
partial: c.String("partial"),
worktree: worktreePath,
}
if parallel {
// force silent in parallel import

View file

@ -39,6 +39,7 @@ var commandGet = &cli.Command{
Usage: "Specify `branch` name. This flag implies --single-branch on Git"},
&cli.BoolFlag{Name: "parallel", Aliases: []string{"P"}, Usage: "Import parallelly"},
&cli.BoolFlag{Name: "bare", Usage: "Do a bare clone"},
&cli.StringFlag{Name: "worktree", Usage: "Create worktree at specified `path` (implies --bare)"},
&cli.StringFlag{
Name: "partial",
Usage: "Do a partial clone. Can specify either \"blobless\" or \"treeless\"",

View file

@ -25,7 +25,7 @@ type getInfo struct {
type getter struct {
update, shallow, silent, ssh, recursive, bare bool
vcs, branch, partial string
vcs, branch, partial, worktree string
}
func (g *getter) get(argURL string) (getInfo, error) {
@ -114,6 +114,7 @@ func (g *getter) getRemoteRepository(remote RemoteRepository, branch string) (ge
recursive: g.recursive,
bare: g.bare,
partial: g.partial,
worktree: g.worktree,
})
}
return info, nil

64
vcs.go
View file

@ -44,7 +44,7 @@ type vcsGetOption struct {
url *url.URL
dir string
recursive, shallow, silent, bare bool
branch, partial string
branch, partial, worktree string
}
// GitBackend is the VCSBackend of git
@ -77,7 +77,15 @@ var GitBackend = &VCSBackend{
}
args = append(args, vg.url.String(), vg.dir)
return run(vg.silent)("git", args...)
if err := run(vg.silent)("git", args...); err != nil {
return err
}
// Create worktree if specified
if vg.worktree != "" && vg.bare {
return createWorktreeFromBare(vg)
}
return nil
},
Update: func(vg *vcsGetOption) error {
if _, err := os.Stat(filepath.Join(vg.dir, ".git/svn")); err == nil {
@ -398,6 +406,58 @@ var BazaarBackend = &VCSBackend{
Contents: []string{".bzr"},
}
// createWorktreeFromBare creates a worktree from a bare repository
func createWorktreeFromBare(vg *vcsGetOption) error {
// Configure fetch refspec for bare repo
if err := runInDir(true)(vg.dir, "git", "config", "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"); err != nil {
return err
}
if err := runInDir(vg.silent)(vg.dir, "git", "fetch", "origin"); err != nil {
return err
}
branch := vg.branch
if branch == "" {
branch = detectDefaultBranch(vg.dir)
}
if branch == "" {
return fmt.Errorf("could not determine default branch for worktree")
}
localBranchExists := runInDir(true)(vg.dir, "git", "show-ref", "--verify", "--quiet", "refs/heads/"+branch) == nil
if localBranchExists {
return runInDir(vg.silent)(vg.dir, "git", "worktree", "add", vg.worktree, branch)
}
return runInDir(vg.silent)(vg.dir, "git", "worktree", "add", "-b", branch, "--track", vg.worktree, "origin/"+branch)
}
// detectDefaultBranch detects the default branch from a bare repository
func detectDefaultBranch(dir string) string {
// Try symbolic-ref first
if out, err := exec.Command("git", "-C", dir, "symbolic-ref", "refs/remotes/origin/HEAD").Output(); err == nil {
parts := strings.Split(strings.TrimSpace(string(out)), "/")
if len(parts) > 0 {
return parts[len(parts)-1]
}
}
// Check common default branches
for _, b := range []string{"main", "master"} {
if runInDir(true)(dir, "git", "show-ref", "--verify", "--quiet", "refs/remotes/origin/"+b) == nil {
return b
}
}
// Use first remote branch
if out, err := exec.Command("git", "-C", dir, "branch", "-r").Output(); err == nil {
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "origin/") && !strings.Contains(line, "->") {
return strings.TrimPrefix(line, "origin/")
}
}
}
return ""
}
var vcsRegistry = map[string]*VCSBackend{
"git": GitBackend,
"github": GitBackend,