1
1
package cli
2
2
3
3
import (
4
+ "bytes"
4
5
"errors"
5
6
"fmt"
6
7
"io/fs"
@@ -18,6 +19,8 @@ import (
18
19
19
20
func (r * RootCmd ) dotfiles () * clibase.Cmd {
20
21
var symlinkDir string
22
+ var gitbranch string
23
+
21
24
cmd := & clibase.Cmd {
22
25
Use : "dotfiles <git_repo_url>" ,
23
26
Middleware : clibase .RequireNArgs (1 ),
@@ -102,6 +105,9 @@ func (r *RootCmd) dotfiles() *clibase.Cmd {
102
105
}
103
106
gitCmdDir = cfgDir
104
107
subcommands = []string {"clone" , inv .Args [0 ], dotfilesRepoDir }
108
+ if gitbranch != "" {
109
+ subcommands = append (subcommands , "--branch" , gitbranch )
110
+ }
105
111
promptText = fmt .Sprintf ("Cloning %s into directory %s.\n \n Continue?" , gitRepo , dotfilesDir )
106
112
}
107
113
@@ -140,6 +146,23 @@ func (r *RootCmd) dotfiles() *clibase.Cmd {
140
146
_ , _ = fmt .Fprintln (inv .Stdout , cliui .DefaultStyles .Error .Render ("Failed to update repo, continuing..." ))
141
147
}
142
148
149
+ if dotfilesExists && gitbranch != "" {
150
+ // If the repo exists and the git-branch is specified, we need to check out the branch. We do this after
151
+ // git pull to make sure the branch was pulled down locally. If we do this before the pull, we could be
152
+ // trying to checkout a branch that does not yet exist locally and get a git error.
153
+ _ , _ = fmt .Fprintf (inv .Stdout , "Dotfiles git branch %q specified\n " , gitbranch )
154
+ err := ensureCorrectGitBranch (inv , ensureCorrectGitBranchParams {
155
+ repoDir : dotfilesDir ,
156
+ gitSSHCommand : gitsshCmd ,
157
+ gitBranch : gitbranch ,
158
+ })
159
+ if err != nil {
160
+ // Do not block on this error, just log it and continue
161
+ _ , _ = fmt .Fprintln (inv .Stdout ,
162
+ cliui .DefaultStyles .Error .Render (fmt .Sprintf ("Failed to use branch %q (%s), continuing..." , err .Error (), gitbranch )))
163
+ }
164
+ }
165
+
143
166
// save git repo url so we can detect changes next time
144
167
err = cfg .DotfilesURL ().Write (gitRepo )
145
168
if err != nil {
@@ -246,11 +269,59 @@ func (r *RootCmd) dotfiles() *clibase.Cmd {
246
269
Description : "Specifies the directory for the dotfiles symlink destinations. If empty, will use $HOME." ,
247
270
Value : clibase .StringOf (& symlinkDir ),
248
271
},
272
+ {
273
+ Flag : "branch" ,
274
+ FlagShorthand : "b" ,
275
+ Description : "Specifies which branch to clone. " +
276
+ "If empty, will default to cloning the default branch or using the existing branch in the cloned repo on disk." ,
277
+ Value : clibase .StringOf (& gitbranch ),
278
+ },
249
279
cliui .SkipPromptOption (),
250
280
}
251
281
return cmd
252
282
}
253
283
284
+ type ensureCorrectGitBranchParams struct {
285
+ repoDir string
286
+ gitSSHCommand string
287
+ gitBranch string
288
+ }
289
+
290
+ func ensureCorrectGitBranch (baseInv * clibase.Invocation , params ensureCorrectGitBranchParams ) error {
291
+ dotfileCmd := func (cmd string , args ... string ) * exec.Cmd {
292
+ c := exec .CommandContext (baseInv .Context (), cmd , args ... )
293
+ c .Dir = params .repoDir
294
+ c .Env = append (baseInv .Environ .ToOS (), fmt .Sprintf (`GIT_SSH_COMMAND=%s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no` , params .gitSSHCommand ))
295
+ c .Stdout = baseInv .Stdout
296
+ c .Stderr = baseInv .Stderr
297
+ return c
298
+ }
299
+ c := dotfileCmd ("git" , "branch" , "--show-current" )
300
+ // Save the output
301
+ var out bytes.Buffer
302
+ c .Stdout = & out
303
+ err := c .Run ()
304
+ if err != nil {
305
+ return xerrors .Errorf ("getting current git branch: %w" , err )
306
+ }
307
+
308
+ if strings .TrimSpace (out .String ()) != params .gitBranch {
309
+ // Checkout and pull the branch
310
+ c := dotfileCmd ("git" , "checkout" , params .gitBranch )
311
+ err := c .Run ()
312
+ if err != nil {
313
+ return xerrors .Errorf ("checkout git branch %q: %w" , params .gitBranch , err )
314
+ }
315
+
316
+ c = dotfileCmd ("git" , "pull" , "--ff-only" )
317
+ err = c .Run ()
318
+ if err != nil {
319
+ return xerrors .Errorf ("pull git branch %q: %w" , params .gitBranch , err )
320
+ }
321
+ }
322
+ return nil
323
+ }
324
+
254
325
// dirExists checks if the path exists and is a directory.
255
326
func dirExists (name string ) (bool , error ) {
256
327
fi , err := os .Stat (name )
0 commit comments