1package commands
2
3import (
4	"fmt"
5	"os"
6	"path"
7	"path/filepath"
8	"strings"
9
10	"github.com/git-lfs/git-lfs/v3/subprocess"
11
12	"github.com/git-lfs/git-lfs/v3/git"
13	"github.com/git-lfs/git-lfs/v3/tools"
14	"github.com/spf13/cobra"
15)
16
17var (
18	cloneFlags git.CloneFlags
19
20	cloneSkipRepoInstall bool
21)
22
23func cloneCommand(cmd *cobra.Command, args []string) {
24	requireGitVersion()
25
26	if git.IsGitVersionAtLeast("2.15.0") {
27		msg := []string{
28			"WARNING: 'git lfs clone' is deprecated and will not be updated",
29			"          with new flags from 'git clone'",
30			"",
31			"'git clone' has been updated in upstream Git to have comparable",
32			"speeds to 'git lfs clone'.",
33		}
34
35		fmt.Fprintln(os.Stderr, strings.Join(msg, "\n"))
36	}
37
38	// We pass all args to git clone
39	err := git.CloneWithoutFilters(cloneFlags, args)
40	if err != nil {
41		Exit("Error(s) during clone:\n%v", err)
42	}
43
44	// now execute pull (need to be inside dir)
45	cwd, err := tools.Getwd()
46	if err != nil {
47		Exit("Unable to derive current working dir: %v", err)
48	}
49
50	// Either the last argument was a relative or local dir, or we have to
51	// derive it from the clone URL
52	clonedir, err := filepath.Abs(args[len(args)-1])
53	if err != nil || !tools.DirExists(clonedir) {
54		// Derive from clone URL instead
55		base := path.Base(args[len(args)-1])
56		if strings.HasSuffix(base, ".git") {
57			base = base[:len(base)-4]
58		}
59		clonedir, _ = filepath.Abs(base)
60		if !tools.DirExists(clonedir) {
61			Exit("Unable to find clone dir at %q", clonedir)
62		}
63	}
64
65	err = os.Chdir(clonedir)
66	if err != nil {
67		Exit("Unable to change directory to clone dir %q: %v", clonedir, err)
68	}
69
70	// Make sure we pop back to dir we started in at the end
71	defer os.Chdir(cwd)
72
73	setupRepository()
74
75	// Support --origin option to clone
76	if len(cloneFlags.Origin) > 0 {
77		cfg.SetRemote(cloneFlags.Origin)
78	}
79
80	if ref, err := git.CurrentRef(); err == nil {
81		includeArg, excludeArg := getIncludeExcludeArgs(cmd)
82		filter := buildFilepathFilter(cfg, includeArg, excludeArg, true)
83		if cloneFlags.NoCheckout || cloneFlags.Bare {
84			// If --no-checkout or --bare then we shouldn't check out, just fetch instead
85			fetchRef(ref.Name, filter)
86		} else {
87			pull(filter)
88			err := postCloneSubmodules(args)
89			if err != nil {
90				Exit("Error performing 'git lfs pull' for submodules: %v", err)
91			}
92		}
93	}
94
95	if !cloneSkipRepoInstall {
96		// If --skip-repo wasn't given, install repo-level hooks while
97		// we're still in the checkout directory.
98
99		if err := installHooks(false); err != nil {
100			ExitWithError(err)
101		}
102	}
103}
104
105func postCloneSubmodules(args []string) error {
106	// In git 2.9+ the filter option will have been passed through to submodules
107	// So we need to lfs pull inside each
108	if !git.IsGitVersionAtLeast("2.9.0") {
109		// In earlier versions submodules would have used smudge filter
110		return nil
111	}
112	// Also we only do this if --recursive or --recurse-submodules was provided
113	if !cloneFlags.Recursive && !cloneFlags.RecurseSubmodules {
114		return nil
115	}
116
117	// Use `git submodule foreach --recursive` to cascade into nested submodules
118	// Also good to call a new instance of git-lfs rather than do things
119	// inside this instance, since that way we get a clean env in that subrepo
120	cmd := subprocess.ExecCommand("git", "submodule", "foreach", "--recursive",
121		"git lfs pull")
122	cmd.Stderr = os.Stderr
123	cmd.Stdin = os.Stdin
124	cmd.Stdout = os.Stdout
125	return cmd.Run()
126}
127
128func init() {
129	RegisterCommand("clone", cloneCommand, func(cmd *cobra.Command) {
130		cmd.PreRun = nil
131
132		// Mirror all git clone flags
133		cmd.Flags().StringVarP(&cloneFlags.TemplateDirectory, "template", "", "", "See 'git clone --help'")
134		cmd.Flags().BoolVarP(&cloneFlags.Local, "local", "l", false, "See 'git clone --help'")
135		cmd.Flags().BoolVarP(&cloneFlags.Shared, "shared", "s", false, "See 'git clone --help'")
136		cmd.Flags().BoolVarP(&cloneFlags.NoHardlinks, "no-hardlinks", "", false, "See 'git clone --help'")
137		cmd.Flags().BoolVarP(&cloneFlags.Quiet, "quiet", "q", false, "See 'git clone --help'")
138		cmd.Flags().BoolVarP(&cloneFlags.NoCheckout, "no-checkout", "n", false, "See 'git clone --help'")
139		cmd.Flags().BoolVarP(&cloneFlags.Progress, "progress", "", false, "See 'git clone --help'")
140		cmd.Flags().BoolVarP(&cloneFlags.Bare, "bare", "", false, "See 'git clone --help'")
141		cmd.Flags().BoolVarP(&cloneFlags.Mirror, "mirror", "", false, "See 'git clone --help'")
142		cmd.Flags().StringVarP(&cloneFlags.Origin, "origin", "o", "", "See 'git clone --help'")
143		cmd.Flags().StringVarP(&cloneFlags.Branch, "branch", "b", "", "See 'git clone --help'")
144		cmd.Flags().StringVarP(&cloneFlags.Upload, "upload-pack", "u", "", "See 'git clone --help'")
145		cmd.Flags().StringVarP(&cloneFlags.Reference, "reference", "", "", "See 'git clone --help'")
146		cmd.Flags().StringVarP(&cloneFlags.ReferenceIfAble, "reference-if-able", "", "", "See 'git clone --help'")
147		cmd.Flags().BoolVarP(&cloneFlags.Dissociate, "dissociate", "", false, "See 'git clone --help'")
148		cmd.Flags().StringVarP(&cloneFlags.SeparateGit, "separate-git-dir", "", "", "See 'git clone --help'")
149		cmd.Flags().StringVarP(&cloneFlags.Depth, "depth", "", "", "See 'git clone --help'")
150		cmd.Flags().BoolVarP(&cloneFlags.Recursive, "recursive", "", false, "See 'git clone --help'")
151		cmd.Flags().BoolVarP(&cloneFlags.RecurseSubmodules, "recurse-submodules", "", false, "See 'git clone --help'")
152		cmd.Flags().StringVarP(&cloneFlags.Config, "config", "c", "", "See 'git clone --help'")
153		cmd.Flags().BoolVarP(&cloneFlags.SingleBranch, "single-branch", "", false, "See 'git clone --help'")
154		cmd.Flags().BoolVarP(&cloneFlags.NoSingleBranch, "no-single-branch", "", false, "See 'git clone --help'")
155		cmd.Flags().BoolVarP(&cloneFlags.Verbose, "verbose", "v", false, "See 'git clone --help'")
156		cmd.Flags().BoolVarP(&cloneFlags.Ipv4, "ipv4", "", false, "See 'git clone --help'")
157		cmd.Flags().BoolVarP(&cloneFlags.Ipv6, "ipv6", "", false, "See 'git clone --help'")
158		cmd.Flags().StringVarP(&cloneFlags.ShallowSince, "shallow-since", "", "", "See 'git clone --help'")
159		cmd.Flags().StringVarP(&cloneFlags.ShallowExclude, "shallow-exclude", "", "", "See 'git clone --help'")
160		cmd.Flags().BoolVarP(&cloneFlags.ShallowSubmodules, "shallow-submodules", "", false, "See 'git clone --help'")
161		cmd.Flags().BoolVarP(&cloneFlags.NoShallowSubmodules, "no-shallow-submodules", "", false, "See 'git clone --help'")
162		cmd.Flags().Int64VarP(&cloneFlags.Jobs, "jobs", "j", -1, "See 'git clone --help'")
163
164		cmd.Flags().StringVarP(&includeArg, "include", "I", "", "Include a list of paths")
165		cmd.Flags().StringVarP(&excludeArg, "exclude", "X", "", "Exclude a list of paths")
166
167		cmd.Flags().BoolVar(&cloneSkipRepoInstall, "skip-repo", false, "Skip LFS repo setup")
168	})
169}
170