11 files changed,
95 insertions(+),
59 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed by:
GitHub
noreply@github.com
Committed at:
2026-04-03 12:32:09 +0300
Parent:
3cf8623
jump to
M
internal/cli/cli.go
路路路 83 83 Name: "private", 84 84 Usage: "make the repository private", 85 85 }, 86 + &cli.StringFlag{ 87 + Name: "default", 88 + Usage: "set default branch", 89 + }, 86 90 }, 87 91 }, 88 92 { 路路路 102 106 }, 103 107 }, 104 108 { 105 - Name: "set-head", 106 - Usage: "switches repo's head to specified branch", 107 - Action: c.repoSetHeadAction, 109 + Name: "set-default", 110 + Usage: "switch repo's default branch", 111 + Action: c.repoDefaultAction, 108 112 Arguments: []cli.Argument{ 109 113 &cli.StringArg{Name: "name"}, 110 114 },
M
internal/cli/repo.go
路路路 59 59 } 60 60 } 61 61 62 + defaultBranch := cmd.String("default") 63 + if defaultBranch != "" { 64 + if err := repo.SetDefaultBranch(defaultBranch); err != nil { 65 + return fmt.Errorf("failed to set default branch: %w", err) 66 + } 67 + } 68 + 62 69 return nil 63 70 } 64 71 路路路 114 121 return nil 115 122 } 116 123 117 -func (c *Cli) repoSetHeadAction(ctx context.Context, cmd *cli.Command) error { 124 +func (c *Cli) repoDefaultAction(ctx context.Context, cmd *cli.Command) error { 118 125 name, err := c.getRepoNameArg(cmd) 119 126 if name == "" { 120 127 return err 路路路 126 133 } 127 134 128 135 branch := cmd.Args().Get(0) 129 - if err = repo.Checkout(branch); err != nil { 136 + if err = repo.SetDefaultBranch(branch); err != nil { 130 137 return err 131 138 } 132 139
M
internal/config/config.go
路路路 31 31 type RepoConfig struct { 32 32 Dir string `yaml:"dir"` 33 33 Readmes []string `yaml:"readmes"` 34 - Masters []string `yaml:"masters"` 35 34 } 36 35 37 36 type SSHConfig struct { 路路路 127 126 } 128 127 129 128 // repos 130 - if len(c.Repo.Masters) == 0 { 131 - c.Repo.Masters = []string{"master", "main"} 132 - } 133 - 134 129 if len(c.Repo.Readmes) == 0 { 135 130 c.Repo.Readmes = []string{ 136 131 "README.md", "readme.md",
M
internal/git/external.go
路路路 3 3 import ( 4 4 "cmp" 5 5 "context" 6 + "errors" 6 7 "fmt" 7 8 "io" 8 9 "os/exec" 路路路 33 34 cmd.Stdout = cmp.Or(opts.Stdout, io.Discard) 34 35 cmd.Stderr = cmp.Or(opts.Stderr, io.Discard) 35 36 return cmd.Run() 37 +} 38 + 39 +func (g *Repo) runGitCmd(cmd string, args ...string) ([]byte, error) { 40 + var gitArgs []string 41 + gitArgs = append(gitArgs, cmd) 42 + gitArgs = append(gitArgs, args...) 43 + gitCmd := exec.Command("git", gitArgs...) 44 + gitCmd.Dir = g.path 45 + gitCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 46 + gitCmd.Env = gitEnv 47 + 48 + out, err := gitCmd.Output() 49 + if err != nil { 50 + if err, ok := errors.AsType[*exec.ExitError](err); ok { 51 + return nil, fmt.Errorf("%w, stderr: %s", err, err.Stderr) 52 + } 53 + return nil, err 54 + } 55 + return out, nil 36 56 } 37 57 38 58 func (g *Repo) streamingGitLog(ctx context.Context, extraArgs ...string) (io.ReadCloser, error) {
M
internal/git/repo.go
路路路 95 95 return strings.TrimSuffix(name, ".git") 96 96 } 97 97 98 -func (g *Repo) Checkout(ref string) error { 99 - head := plumbing.NewSymbolicReference(plumbing.HEAD, 100 - plumbing.NewBranchReferenceName(ref)) 98 +func (g *Repo) DefaultBranch() (string, error) { 99 + out, err := g.runGitCmd("rev-parse", "--abbrev-ref", "HEAD") 100 + if err != nil { 101 + return "", fmt.Errorf("failed to get default branch: %w", err) 102 + } 103 + return strings.TrimSpace(string(out)), nil 104 +} 105 + 106 +func (g *Repo) SetDefaultBranch(branch string) error { 107 + b := plumbing.NewBranchReferenceName(branch) 108 + _, err := g.r.Reference(b, true) 109 + if err != nil { 110 + return fmt.Errorf("branch %q not found: %w", branch, err) 111 + } 112 + head := plumbing.NewSymbolicReference(plumbing.HEAD, b) 101 113 return g.r.Storer.SetReference(head) 102 114 } 103 115 路路路 222 234 func (g *Repo) IsGoMod() bool { 223 235 _, err := g.FileContent("go.mod") 224 236 return err == nil 225 -} 226 - 227 -func (g *Repo) FindMasterBranch(masters []string) (string, error) { 228 - if g.IsEmpty() { 229 - return "", ErrEmptyRepo 230 - } 231 - 232 - for _, b := range masters { 233 - if _, err := g.r.ResolveRevision(plumbing.Revision(b)); err == nil { 234 - return b, nil 235 - } 236 - } 237 - return "", fmt.Errorf("unable to find master branch") 238 237 } 239 238 240 239 func (g *Repo) Fetch(ctx context.Context) error {
M
internal/git/repo_test.go
路路路 216 216 }) 217 217 } 218 218 219 -func TestRepo_FindMasterBranch(t *testing.T) { 220 - t.Run("finds master branch", func(t *testing.T) { 219 +func TestRepo_DefaultBranch(t *testing.T) { 220 + t.Run("works", func(t *testing.T) { 221 221 r := newTestRepo(t) 222 - r.commitFile("README.md", "test", "init") 223 - branch, err := r.open().FindMasterBranch([]string{"master", "main"}) 222 + r.commitFile("readme", "test", "init") 224 223 224 + branch, err := r.open().DefaultBranch() 225 225 is.Equal(t, branch, "master") 226 226 is.Err(t, err, nil) 227 227 }) 228 228 229 - t.Run("finds first matching candidate", func(t *testing.T) { 229 + t.Run("multiple branches", func(t *testing.T) { 230 230 r := newTestRepo(t) 231 - hash := r.commitFile("file.txt", "x", "Commit") 232 - r.createBranch("main", hash) 231 + r.commitFile("readme", "test", "init") 232 + m := r.commitFile("main", "test", "init2") 233 + r.createBranch("develop", m) 233 234 234 - // master should be found first (master is created by init) 235 - branch, err := r.open().FindMasterBranch([]string{"master", "main"}) 235 + branch, err := r.open().DefaultBranch() 236 236 is.Equal(t, branch, "master") 237 237 is.Err(t, err, nil) 238 - 239 - // if main is check first, it should be found 240 - branch, err = r.open().FindMasterBranch([]string{"main", "master"}) 241 - is.Equal(t, branch, "main") 242 - is.Err(t, err, nil) 243 238 }) 239 +} 244 240 245 - t.Run("returns error when no match", func(t *testing.T) { 241 +func TestRepo_SetDefaultBranch(t *testing.T) { 242 + t.Run("works", func(t *testing.T) { 246 243 r := newTestRepo(t) 247 - r.commitFile("file.txt", "x", "Commit") 248 - _, err := r.open().FindMasterBranch([]string{"nonexistent"}) 249 - is.Err(t, err, "unable to find master") 244 + r.commitFile("readme", "test", "init") 245 + 246 + rr := r.open() 247 + 248 + branch, err := rr.DefaultBranch() 249 + is.Equal(t, branch, "master") 250 + is.Err(t, err, nil) 251 + 252 + h := r.commitFile("thing", "hello worldie", "new feature") 253 + r.createBranch("develop", h) 254 + is.Err(t, rr.SetDefaultBranch("develop"), nil) 255 + 256 + branch, err = rr.DefaultBranch() 257 + is.Equal(t, branch, "develop") 258 + is.Err(t, err, nil) 250 259 }) 251 260 252 - t.Run("returns error for empty repo", func(t *testing.T) { 261 + t.Run("sets only existent branches", func(t *testing.T) { 253 262 r := newTestRepo(t) 254 - _, err := r.open().FindMasterBranch([]string{"main"}) 255 - is.Err(t, err, ErrEmptyRepo) 263 + r.commitFile("readme", "test", "init") 264 + 265 + rr := r.open() 266 + err := rr.SetDefaultBranch("tesites") 267 + is.Err(t, err, `not found:`) 256 268 }) 257 269 }
M
internal/handlers/repo.go
路路路 88 88 return 89 89 } 90 90 91 - p.Ref, err = repo.FindMasterBranch(h.c.Repo.Masters) 91 + p.Ref, err = repo.DefaultBranch() 92 92 if err != nil { 93 93 h.write500(w, err) 94 94 return 路路路 349 349 return 350 350 } 351 351 352 - master, err := repo.FindMasterBranch(h.c.Repo.Masters) 352 + master, err := repo.DefaultBranch() 353 353 if err != nil { 354 354 h.write500(w, err) 355 355 return
M
testscript/cli-repo-set-head.txtar
→ testscript/cli-repo-set-default.txtar
路路路 20 20 stdout 'ref: refs/heads/master' 21 21 22 22 # change head 23 -mugit repo set-head heady develop 23 +mugit repo set-default heady develop 24 24 stderr 'changed repo head repo=heady.git branch=develop' # fix output 25 25 26 26 exec cat $REPOS/heady.git/HEAD 27 27 stdout 'ref: refs/heads/develop' 28 28 29 29 # go back to master 30 -mugit repo set-head heady.git master 30 +mugit repo set-default heady.git master 31 31 stderr 'changed repo head repo=heady.git branch=master' # fix output 32 32 33 33 exec cat $REPOS/heady.git/HEAD