Skip to content

Commit 0a136c4

Browse files
committed
add support for native git client
1 parent 1a57009 commit 0a136c4

4 files changed

Lines changed: 123 additions & 27 deletions

File tree

README.md

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,21 +104,24 @@ state any changes (for legal details please read LICENSE file).
104104

105105
Usage:
106106
layout [OPTIONS] new [new-OPTIONS] [source] [destination]
107-
107+
108108
Help Options:
109-
-h, --help Show this help message
109+
-h, --help Show this help message
110110

111111
[new command options]
112-
--version= Override binary version to bypass manifest restriction [$LAYOUT_VERSION]
113-
-c, --config= Path to configuration file, use show config command to locate default location [$LAYOUT_CONFIG]
114-
-u, --ui=[nice|simple] UI mode (default: nice) [$LAYOUT_UI]
115-
-d, --debug Enable debug mode [$LAYOUT_DEBUG]
116-
-a, --ask-once Do not retry on wrong user input, good for automation [$LAYOUT_ASK_ONCE]
117-
-D, --disable-cleanup Disable removing created dirs in case of failure [$LAYOUT_DISABLE_CLEANUP]
118-
119-
[new command arguments]
120-
source: URL, abbreviation or path to layout
121-
destination: Destination directory, will be created
112+
--version= Override binary version to bypass manifest restriction [$LAYOUT_VERSION]
113+
-c, --config= Path to configuration file, use show config command to locate default location [$LAYOUT_CONFIG]
114+
-u, --ui=[nice|simple] UI mode (default: nice) [$LAYOUT_UI]
115+
-d, --debug Enable debug mode [$LAYOUT_DEBUG]
116+
-a, --ask-once Do not retry on wrong user input, good for automation [$LAYOUT_ASK_ONCE]
117+
-D, --disable-cleanup Disable removing created dirs in case of failure [$LAYOUT_DISABLE_CLEANUP]
118+
-g, --git=[auto|native|embedded] Git client (default: auto) [$LAYOUT_GIT]
119+
120+
* `-g,--git` (v1.2.0+) specifies git client which should be used:
121+
* `native` use native Git binary (must be 2.13+)
122+
* `embedded` use Golang native git client (safe mode)
123+
* `auto` (default) in case git installed (`git` binary accessible) and git version is 2.13 or higher `native` will
124+
be used, otherwise `embedded`
122125

123126
### Architecture
124127

@@ -589,7 +592,7 @@ See [roadmap](#roadmap) for planning related features.
589592
- global before/after hooks
590593
- globally disable hooks
591594
- compute variables by script
592-
- allow users use native `git` binary
595+
- multiple templates in one repo
593596
- Delivery
594597
- apt repository
595598
- Arch AUR

cmd/layout/commands/new.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ import (
2121
"fmt"
2222
"os"
2323
"os/signal"
24+
"reflect"
25+
"runtime"
2426

2527
"github.com/reddec/layout/internal"
28+
"github.com/reddec/layout/internal/gitclient"
2629
"github.com/reddec/layout/internal/ui"
2730
"github.com/reddec/layout/internal/ui/nice"
2831
"github.com/reddec/layout/internal/ui/simple"
@@ -35,6 +38,7 @@ type NewCommand struct {
3538
Debug bool `short:"d" long:"debug" env:"DEBUG" description:"Enable debug mode"`
3639
AskOnce bool `short:"a" long:"ask-once" env:"ASK_ONCE" description:"Do not retry on wrong user input, good for automation"`
3740
DisableCleanup bool `short:"D" long:"disable-cleanup" env:"DISABLE_CLEANUP" description:"Disable removing created dirs in case of failure"`
41+
Git string `short:"g" long:"git" env:"GIT" description:"Git client" default:"auto" choice:"auto" choice:"native" choice:"embedded"`
3842
Args struct {
3943
URL string `positional-arg-name:"source" required:"yes" description:"URL, abbreviation or path to layout"`
4044
Dest string `positional-arg-name:"destination" required:"yes" description:"Destination directory, will be created"`
@@ -72,6 +76,11 @@ func (cmd NewCommand) Execute([]string) error {
7276
if _, err := os.Stat(cmd.Args.Dest); os.IsNotExist(err) {
7377
weCreatedDestination = true
7478
}
79+
80+
gitClient := cmd.gitClient(ctx)
81+
if cmd.Debug {
82+
fmt.Println("Git:", runtime.FuncForPC(reflect.ValueOf(gitClient).Pointer()).Name())
83+
}
7584
err = internal.Deploy(ctx, internal.Config{
7685
Source: cmd.Args.URL,
7786
Target: cmd.Args.Dest,
@@ -81,6 +90,7 @@ func (cmd NewCommand) Execute([]string) error {
8190
Debug: cmd.Debug,
8291
Version: cmd.Version,
8392
AskOnce: cmd.AskOnce,
93+
Git: gitClient,
8494
})
8595

8696
if err != nil && weCreatedDestination && !cmd.DisableCleanup {
@@ -89,3 +99,16 @@ func (cmd NewCommand) Execute([]string) error {
8999

90100
return err
91101
}
102+
103+
func (cmd NewCommand) gitClient(ctx context.Context) gitclient.Client {
104+
switch cmd.Git {
105+
case "auto":
106+
return gitclient.Auto(ctx)
107+
case "native":
108+
return gitclient.Native
109+
case "embedded":
110+
fallthrough
111+
default:
112+
return gitclient.Embedded
113+
}
114+
}

internal/deploy.go

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@ import (
2525
"path/filepath"
2626
"strings"
2727

28+
"github.com/reddec/layout/internal/gitclient"
2829
"github.com/reddec/layout/internal/ui"
2930
"github.com/reddec/layout/internal/ui/simple"
30-
31-
"github.com/go-git/go-git/v5"
3231
)
3332

3433
const (
@@ -45,21 +44,25 @@ type Config struct {
4544
Debug bool // enable debug messages and tracing
4645
Version string // current version, used to filter manifests by constraints
4746
AskOnce bool // do not try to ask for user input after wrong value and interrupt deployment
47+
Git gitclient.Client // Git client, default is gitclient.Auto
4848
}
4949

50-
func (cfg Config) withDefaults() Config {
50+
func (cfg Config) withDefaults(ctx context.Context) Config {
5151
if cfg.Default == "" {
5252
cfg.Default = defaultRepoTemplate
5353
}
5454
if cfg.Display == nil {
5555
cfg.Display = simple.Default()
5656
}
57+
if cfg.Git == nil {
58+
cfg.Git = gitclient.Auto(ctx)
59+
}
5760
return cfg
5861
}
5962

6063
// Deploy layout, which means clone repo, ask for question, and template content.
6164
func Deploy(ctx context.Context, config Config) error {
62-
config = config.withDefaults()
65+
config = config.withDefaults(ctx)
6366

6467
var projectDir string
6568

@@ -85,7 +88,7 @@ func Deploy(ctx context.Context, config Config) error {
8588
url = strings.ReplaceAll(repoTemplate, "{0}", repo)
8689
fallthrough
8790
default: // finally all we need is to pull remote repository by URL
88-
tmpDir, err := cloneFromGit(ctx, url)
91+
tmpDir, err := cloneFromGit(ctx, config.Git, url)
8992
if err != nil {
9093
return fmt.Errorf("copy project from git %s: %w", url, err)
9194
}
@@ -112,20 +115,14 @@ func Deploy(ctx context.Context, config Config) error {
112115
return nil
113116
}
114117

115-
// clones from git repository from default branch with minimal depth (1).
116-
// Reports progress to STDERR. Supports submodules.
118+
// clones from git repository into temporary directory.
117119
// Returned directory should be removed by caller.
118-
func cloneFromGit(ctx context.Context, url string) (projectDir string, err error) {
120+
func cloneFromGit(ctx context.Context, client gitclient.Client, url string) (projectDir string, err error) {
119121
tmpDir, err := os.MkdirTemp("", "layout-*")
120122
if err != nil {
121123
return "", fmt.Errorf("create temp dir: %w", err)
122124
}
123-
_, err = git.PlainCloneContext(ctx, tmpDir, false, &git.CloneOptions{
124-
URL: url,
125-
Depth: 1,
126-
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
127-
Progress: os.Stderr,
128-
})
125+
err = client(ctx, url, tmpDir)
129126
if err != nil {
130127
_ = os.RemoveAll(tmpDir)
131128
return "", fmt.Errorf("clone repo: %w", err)

internal/gitclient/client.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
Copyright 2022 Aleksandr Baryshnikov
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package gitclient
18+
19+
import (
20+
"context"
21+
"os"
22+
"os/exec"
23+
"strings"
24+
25+
"github.com/Masterminds/semver"
26+
"github.com/go-git/go-git/v5"
27+
)
28+
29+
// Client for GIT.
30+
type Client func(ctx context.Context, repo string, directory string) error
31+
32+
// Embedded go-native git client which clones from git repository from default branch with minimal depth (1).
33+
// Reports progress to STDERR. Supports submodules.
34+
func Embedded(ctx context.Context, repo string, directory string) error {
35+
_, err := git.PlainCloneContext(ctx, directory, false, &git.CloneOptions{
36+
URL: repo,
37+
Depth: 1,
38+
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
39+
Progress: os.Stderr,
40+
})
41+
return err
42+
}
43+
44+
// Native git client (uses git binary) which clones from git repository from default branch with minimal depth (1).
45+
// Send both outputs to STDERR. Supports submodules.
46+
func Native(ctx context.Context, repo string, directory string) error {
47+
cmd := exec.CommandContext(ctx, "git", "clone", "--depth", "1", "--recurse-submodules", repo, directory)
48+
cmd.Stdout = os.Stderr
49+
cmd.Stderr = os.Stderr
50+
return cmd.Run()
51+
}
52+
53+
// Auto select git client. In case Git binary exists and version is at least 2.13+ than use native, otherwise - embedded.
54+
func Auto(ctx context.Context) Client {
55+
minVersion := semver.MustParse("2.13")
56+
version, err := getGitVersion(ctx)
57+
if err != nil || version.LessThan(minVersion) {
58+
return Embedded
59+
}
60+
return Native
61+
}
62+
63+
func getGitVersion(ctx context.Context) (*semver.Version, error) {
64+
cmd := exec.CommandContext(ctx, "git", "--version")
65+
cmd.Stderr = os.Stderr
66+
output, err := cmd.Output()
67+
if err != nil {
68+
return nil, err
69+
}
70+
// git version x.y.z
71+
parts := strings.Split(strings.TrimSpace(string(output)), " ")
72+
return semver.NewVersion(parts[len(parts)-1])
73+
}

0 commit comments

Comments
 (0)