Skip to content

Commit 20e598b

Browse files
authored
[push] Add devbox global push (#1064)
## Summary Stacked on #1061 This allows users to push global config (and any other files) to a repo. Pushing applies the current global profile on top of the latest commit in the repo (clone, commit, and push). It does not require force because it always applies on the latest commit. ## How was it tested? ```bash devbox global pull [email protected]:mikeland86/global.git devbox global push [email protected]:mikeland86/global.git devbox global add curl devbox global push [email protected]:mikeland86/global.git ```
1 parent 7925856 commit 20e598b

File tree

11 files changed

+225
-28
lines changed

11 files changed

+225
-28
lines changed

devbox.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type Devbox interface {
3535
PrintGlobalList() error
3636
PrintEnvrcContent(w io.Writer) error
3737
Pull(ctx context.Context, overwrite bool, path string) error
38+
Push(url string) error
3839
// Remove removes Nix packages from the config so that it no longer exists in
3940
// the devbox environment.
4041
Remove(ctx context.Context, pkgs ...string) error

internal/boxcli/global.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func globalCmd() *cobra.Command {
2727
addCommandAndHideConfigFlag(globalCmd, installCmd())
2828
addCommandAndHideConfigFlag(globalCmd, pathCmd())
2929
addCommandAndHideConfigFlag(globalCmd, pullCmd())
30+
addCommandAndHideConfigFlag(globalCmd, pushCmd())
3031
addCommandAndHideConfigFlag(globalCmd, removeCmd())
3132
addCommandAndHideConfigFlag(globalCmd, runCmd())
3233
addCommandAndHideConfigFlag(globalCmd, servicesCmd())

internal/boxcli/push.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved.
2+
// Use of this source code is governed by the license in the LICENSE file.
3+
4+
package boxcli
5+
6+
import (
7+
"github.com/pkg/errors"
8+
"github.com/spf13/cobra"
9+
"go.jetpack.io/devbox"
10+
)
11+
12+
type pushCmdFlags struct {
13+
config configFlags
14+
}
15+
16+
func pushCmd() *cobra.Command {
17+
flags := pushCmdFlags{}
18+
cmd := &cobra.Command{
19+
Use: "push <git-repo>",
20+
Short: "Push a [global] config to a git repo",
21+
PreRunE: ensureNixInstalled,
22+
Args: cobra.ExactArgs(1),
23+
RunE: func(cmd *cobra.Command, args []string) error {
24+
return pushCmdFunc(cmd, args[0], flags)
25+
},
26+
}
27+
28+
flags.config.register(cmd)
29+
30+
return cmd
31+
}
32+
33+
func pushCmdFunc(cmd *cobra.Command, url string, flags pushCmdFlags) error {
34+
box, err := devbox.Open(flags.config.path, cmd.ErrOrStderr())
35+
if err != nil {
36+
return errors.WithStack(err)
37+
}
38+
return box.Push(url)
39+
}

internal/cmdutil/exec.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved.
2+
// Use of this source code is governed by the license in the LICENSE file.
3+
4+
package cmdutil
5+
6+
import (
7+
"bytes"
8+
"io"
9+
"os"
10+
"os/exec"
11+
)
12+
13+
func CommandTTY(name string, arg ...string) *exec.Cmd {
14+
cmd := exec.Command(name, arg...)
15+
cmd.Stdin = os.Stdin
16+
cmd.Stdout = os.Stdout
17+
cmd.Stderr = os.Stderr
18+
return cmd
19+
}
20+
21+
// CommandTTYWithBuffer returns a command with stdin, stdout, and stderr
22+
// and a buffer that contains stdout and stderr combined.
23+
func CommandTTYWithBuffer(
24+
name string,
25+
arg ...string,
26+
) (*exec.Cmd, *bytes.Buffer) {
27+
cmd := exec.Command(name, arg...)
28+
cmd.Stdin = os.Stdin
29+
30+
errBuf := bytes.NewBuffer(nil)
31+
outBuf := bytes.NewBuffer(nil)
32+
cmd.Stderr = io.MultiWriter(os.Stderr, errBuf)
33+
cmd.Stdout = io.MultiWriter(os.Stdout, outBuf)
34+
outBuf.Write(errBuf.Bytes())
35+
return cmd, outBuf
36+
}

internal/fileutil/dir.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved.
2+
// Use of this source code is governed by the license in the LICENSE file.
3+
4+
package fileutil
5+
6+
import (
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/pkg/errors"
11+
"go.jetpack.io/devbox/internal/cmdutil"
12+
)
13+
14+
func CopyAll(src, dst string) error {
15+
entries, err := os.ReadDir(src)
16+
if err != nil {
17+
return errors.WithStack(err)
18+
}
19+
for _, entry := range entries {
20+
cmd := cmdutil.CommandTTY("cp", "-rf", filepath.Join(src, entry.Name()), dst)
21+
if err := cmd.Run(); err != nil {
22+
return errors.WithStack(err)
23+
}
24+
}
25+
return nil
26+
}
27+
28+
func ClearDir(dir string) error {
29+
if err := os.RemoveAll(dir); err != nil {
30+
return errors.WithStack(err)
31+
}
32+
return errors.WithStack(os.MkdirAll(dir, 0755))
33+
}

internal/impl/global.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,19 @@
44
package impl
55

66
import (
7-
"context"
87
"fmt"
98
"io/fs"
109
"os"
1110
"path/filepath"
1211

1312
"github.com/pkg/errors"
1413

15-
"go.jetpack.io/devbox/internal/pullbox"
1614
"go.jetpack.io/devbox/internal/xdg"
1715
)
1816

1917
// In the future we will support multiple global profiles
2018
const currentGlobalProfile = "default"
2119

22-
func (d *Devbox) Pull(
23-
ctx context.Context,
24-
force bool,
25-
path string,
26-
) error {
27-
fmt.Fprintf(d.writer, "Pulling global config from %s\n", path)
28-
return pullbox.New(d, path, force).Pull()
29-
}
30-
3120
func (d *Devbox) PrintGlobalList() error {
3221
for _, p := range d.cfg.Packages {
3322
fmt.Fprintf(d.writer, "* %s\n", p)

internal/impl/pushpull.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package impl
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"go.jetpack.io/devbox/internal/pullbox"
8+
)
9+
10+
func (d *Devbox) Pull(
11+
ctx context.Context,
12+
force bool,
13+
path string,
14+
) error {
15+
fmt.Fprintf(d.writer, "Pulling global config from %s\n", path)
16+
return pullbox.New(d, path, force).Pull()
17+
}
18+
19+
func (d *Devbox) Push(url string) error {
20+
fmt.Fprintf(d.writer, "Pushing global config\n")
21+
return pullbox.New(d, url, false).Push()
22+
}

internal/pullbox/git/git.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ package git
55

66
import (
77
"os"
8-
"os/exec"
9-
"path/filepath"
108
"strings"
119

1210
"github.com/pkg/errors"
11+
"go.jetpack.io/devbox/internal/cmdutil"
1312
)
1413

1514
func CloneToTmp(repo string) (string, error) {
@@ -20,21 +19,17 @@ func CloneToTmp(repo string) (string, error) {
2019
if err := clone(repo, tmpDir); err != nil {
2120
return "", errors.WithStack(err)
2221
}
23-
if err := os.RemoveAll(filepath.Join(tmpDir, ".git")); err != nil {
24-
return "", errors.WithStack(err)
25-
}
2622
return tmpDir, nil
2723
}
2824

2925
func IsRepoURL(url string) bool {
3026
// For now only support ssh
31-
return strings.HasPrefix(url, "git@")
27+
return strings.HasPrefix(url, "git@") ||
28+
(strings.HasPrefix(url, "https://") && strings.HasSuffix(url, ".git"))
3229
}
3330

3431
func clone(repo, dir string) error {
35-
cmd := exec.Command("git", "clone", repo, dir)
36-
cmd.Stderr = os.Stderr
37-
cmd.Stdout = os.Stdout
32+
cmd := cmdutil.CommandTTY("git", "clone", repo, dir)
3833
cmd.Dir = dir
3934
err := cmd.Run()
4035
return errors.WithStack(err)

internal/pullbox/git/push.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package git
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
8+
"github.com/pkg/errors"
9+
"go.jetpack.io/devbox/internal/cmdutil"
10+
"go.jetpack.io/devbox/internal/fileutil"
11+
)
12+
13+
const nothingToCommitErrorText = "nothing to commit"
14+
15+
func Push(dir, url string) error {
16+
tmpDir, err := CloneToTmp(url)
17+
if err != nil {
18+
return err
19+
}
20+
if err := removeNonGitFiles(tmpDir); err != nil {
21+
return err
22+
}
23+
24+
if err := fileutil.CopyAll(dir, tmpDir); err != nil {
25+
return err
26+
}
27+
28+
if err := createCommit(tmpDir); err != nil {
29+
return err
30+
}
31+
32+
return push(tmpDir)
33+
}
34+
35+
func createCommit(dir string) error {
36+
cmd := cmdutil.CommandTTY("git", "add", ".")
37+
cmd.Dir = dir
38+
if err := cmd.Run(); err != nil {
39+
return errors.WithStack(err)
40+
}
41+
cmd, buf := cmdutil.CommandTTYWithBuffer(
42+
"git", "commit", "-m", "devbox commit")
43+
cmd.Dir = dir
44+
err := cmd.Run()
45+
if strings.Contains(buf.String(), nothingToCommitErrorText) {
46+
return nil
47+
}
48+
return errors.WithStack(err)
49+
}
50+
51+
func push(dir string) error {
52+
cmd := cmdutil.CommandTTY("git", "push")
53+
cmd.Dir = dir
54+
err := cmd.Run()
55+
return errors.WithStack(err)
56+
}
57+
58+
func removeNonGitFiles(dir string) error {
59+
entries, err := os.ReadDir(dir)
60+
if err != nil {
61+
return err
62+
}
63+
for _, entry := range entries {
64+
if entry.Name() == ".git" {
65+
continue
66+
}
67+
if err := os.RemoveAll(filepath.Join(dir, entry.Name())); err != nil {
68+
return err
69+
}
70+
}
71+
return nil
72+
}

internal/pullbox/pullbox.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
package pullbox
55

66
import (
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/pkg/errors"
711
"go.jetpack.io/devbox/internal/boxcli/usererr"
812
"go.jetpack.io/devbox/internal/pullbox/git"
913
)
@@ -28,6 +32,10 @@ func (p *pullbox) Pull() error {
2832
if err != nil {
2933
return err
3034
}
35+
// Remove the .git directory, we don't want to keep state
36+
if err := os.RemoveAll(filepath.Join(tmpDir, ".git")); err != nil {
37+
return errors.WithStack(err)
38+
}
3139
return p.copy(p.overwrite, tmpDir, p.ProjectDir())
3240
}
3341

@@ -52,3 +60,7 @@ func (p *pullbox) Pull() error {
5260

5361
return usererr.New("Could not determine how to pull %s", p.url)
5462
}
63+
64+
func (p *pullbox) Push() error {
65+
return git.Push(p.ProjectDir(), p.url)
66+
}

0 commit comments

Comments
 (0)