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