Copy copies files between the local and remote file systems. The mechanics are similar to 'ssh' but using 'scp'.
(ctx context.Context, args []string, opts cpOptions)
| 758 | // Copy copies files between the local and remote file systems. |
| 759 | // The mechanics are similar to 'ssh' but using 'scp'. |
| 760 | func (a *App) Copy(ctx context.Context, args []string, opts cpOptions) error { |
| 761 | if len(args) < 2 { |
| 762 | return fmt.Errorf("cp requires source and destination arguments") |
| 763 | } |
| 764 | if opts.recursive { |
| 765 | opts.scpArgs = append(opts.scpArgs, "-r") |
| 766 | } |
| 767 | |
| 768 | hasRemote := false |
| 769 | for _, arg := range args { |
| 770 | if rest := strings.TrimPrefix(arg, "remote:"); rest != arg { |
| 771 | hasRemote = true |
| 772 | // scp treats each filename argument as a shell expression, |
| 773 | // subjecting it to expansion of environment variables, braces, |
| 774 | // tilde, backticks, globs and so on. Because these present a |
| 775 | // security risk (see https://lwn.net/Articles/835962/), we |
| 776 | // disable them by shell-escaping the argument unless the user |
| 777 | // provided the -e flag. |
| 778 | if !opts.expand { |
| 779 | arg = `remote:'` + strings.Replace(rest, `'`, `'\''`, -1) + `'` |
| 780 | } |
| 781 | |
| 782 | } else if !filepath.IsAbs(arg) { |
| 783 | // scp treats a colon in the first path segment as a host identifier. |
| 784 | // Escape it by prepending "./". |
| 785 | // TODO(adonovan): test on Windows, including with a c:\\foo path. |
| 786 | const sep = string(os.PathSeparator) |
| 787 | first := strings.Split(filepath.ToSlash(arg), sep)[0] |
| 788 | if strings.Contains(first, ":") { |
| 789 | arg = "." + sep + arg |
| 790 | } |
| 791 | } |
| 792 | opts.scpArgs = append(opts.scpArgs, arg) |
| 793 | } |
| 794 | if !hasRemote { |
| 795 | return cmdutil.FlagErrorf("at least one argument must have a 'remote:' prefix") |
| 796 | } |
| 797 | return a.SSH(ctx, nil, opts.sshOptions) |
| 798 | } |