github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/client/cmd_git_lfs_config.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os/exec"
     9  	"runtime"
    10  	"strings"
    11  
    12  	"github.com/keybase/cli"
    13  	"github.com/keybase/client/go/libcmdline"
    14  	"github.com/keybase/client/go/libkb"
    15  )
    16  
    17  const (
    18  	gitURLPrefix = "keybase://"
    19  )
    20  
    21  type CmdGitLFSConfig struct {
    22  	libkb.Contextified
    23  	path string
    24  	repo string
    25  }
    26  
    27  func newCmdGitLFSConfig(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
    28  	return cli.Command{
    29  		Name:        "lfs-config",
    30  		Usage:       "Configures a keybase git checkout to use LFS",
    31  		Description: "Git LFS (Large File Storage) is a git extension that keeps pointers to\n   large files within your git repository, but the files themselves are stored\n   externally.  KBFS supports being the external storage for LFS, and running\n   this command in a checkout will configure it to use KBFS for LFS.\n   To install Git LFS, see https://git-lfs.github.com.",
    32  		Action: func(c *cli.Context) {
    33  			cmd := NewCmdGitLFSConfigRunner(g)
    34  			cl.ChooseCommand(cmd, "lfs-config", c)
    35  		},
    36  		Flags: []cli.Flag{
    37  			cli.StringFlag{
    38  				Name:  "path",
    39  				Usage: "Location of local git checkout (default: current working dir)",
    40  			},
    41  			cli.StringFlag{
    42  				Name:  "repo",
    43  				Usage: "Keybase repo URL (default: first keybase remote URL in checkout)",
    44  			},
    45  		},
    46  	}
    47  }
    48  
    49  func NewCmdGitLFSConfigRunner(g *libkb.GlobalContext) *CmdGitLFSConfig {
    50  	return &CmdGitLFSConfig{Contextified: libkb.NewContextified(g)}
    51  }
    52  
    53  func (c *CmdGitLFSConfig) ParseArgv(ctx *cli.Context) error {
    54  	c.path = ctx.String("path")
    55  	c.repo = ctx.String("repo")
    56  	if c.repo != "" && !strings.HasPrefix(c.repo, gitURLPrefix) {
    57  		return fmt.Errorf("%s is not a valid Keybase repo", c.repo)
    58  	}
    59  	return nil
    60  }
    61  
    62  func (c *CmdGitLFSConfig) gitExec(command ...string) (string, error) {
    63  	path := []string{}
    64  	if c.path != "" {
    65  		path = []string{"-C", c.path}
    66  	}
    67  	cmd := exec.Command("git",
    68  		append(path, command...)...)
    69  	output, err := cmd.CombinedOutput()
    70  	if err != nil {
    71  		return "", err
    72  	}
    73  	return string(output), nil
    74  }
    75  
    76  func (c *CmdGitLFSConfig) getRepo() (string, error) {
    77  	if c.repo != "" {
    78  		return c.repo, nil
    79  	}
    80  
    81  	// Use the first keybase:// link by default.
    82  	output, err := c.gitExec("remote", "-v")
    83  	if err != nil {
    84  		return "", err
    85  	}
    86  	reader := bytes.NewBufferString(output)
    87  	for {
    88  		line, err := reader.ReadString('\n')
    89  		if err == io.EOF {
    90  			return "", errors.New("No keybase remote found")
    91  		} else if err != nil {
    92  			return "", err
    93  		}
    94  
    95  		s := strings.Fields(line)
    96  		if len(s) < 2 {
    97  			continue
    98  		}
    99  		if strings.HasPrefix(s[1], gitURLPrefix) {
   100  			return s[1], nil
   101  		}
   102  	}
   103  }
   104  
   105  func (c *CmdGitLFSConfig) Run() error {
   106  	dui := c.G().UI.GetDumbOutputUI()
   107  
   108  	// Find the repo URL.
   109  	repo, err := c.getRepo()
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	// Add the necessary lfs config.
   115  	_, err = c.gitExec(
   116  		"config", "--add", "lfs.standalonetransferagent", "keybase-lfs")
   117  	if err != nil {
   118  		return err
   119  	}
   120  	_, err = c.gitExec(
   121  		"config", "--add", "lfs.customtransfer.keybase-lfs.path",
   122  		"git-remote-keybase")
   123  	if err != nil {
   124  		return err
   125  	}
   126  	// Note that the "origin" here as a remote name doesn't really
   127  	// matter, since git-remote-keybase doesn't use it for anything
   128  	// when in LFS mode.  It's only there because it's expected in the
   129  	// argument list.
   130  	quoteArgs := ""
   131  	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
   132  		// Windows and macOS require quotes around the args, but linux
   133  		// does not.
   134  		quoteArgs = "\""
   135  	}
   136  	_, err = c.gitExec(
   137  		"config", "--add", "lfs.customtransfer.keybase-lfs.args",
   138  		fmt.Sprintf("%slfs origin %s%s", quoteArgs, repo, quoteArgs))
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	repoString := "This repo"
   144  	if c.path != "" {
   145  		repoString = "The repo at " + c.path
   146  	}
   147  	dui.Printf("Success! %s is now configured to use the following Keybase\nrepository for LFS:\n", repoString)
   148  	dui.Printf("\t%s\n\n", repo)
   149  	dui.Printf("Assuming you have installed Git LFS (see https://git-lfs.github.com) you can now\nconfigure git to store certain files directly in Keybase.  For example:\n")
   150  	dui.Printf("\tgit lfs install\n")
   151  	dui.Printf("\tgit lfs track \"*.zip\"\n")
   152  	dui.Printf("\tgit add .gitattributes\n\n")
   153  	dui.Printf("Note that new checkouts of this repository will see a \"missing protocol\" error\nuntil you have configured it with this `keybase git lfs-config` command.  After\ndoing so, you can sync the LFS files with this command:\n")
   154  	dui.Printf("\tgit checkout -f HEAD\n")
   155  	return nil
   156  }
   157  
   158  func (c *CmdGitLFSConfig) GetUsage() libkb.Usage {
   159  	return libkb.Usage{
   160  		Config:    true,
   161  		API:       true,
   162  		KbKeyring: true,
   163  	}
   164  }