Skip to content

Commit

Permalink
Add --ssh option to init command to guess SSH repo URLs
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed May 24, 2021
1 parent 891e461 commit fba2ec9
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 45 deletions.
66 changes: 44 additions & 22 deletions cmd/initcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,43 +29,53 @@ type initCmdConfig struct {
oneShot bool
purge bool
purgeBinary bool
ssh bool
}

var dotfilesRepoGuesses = []struct {
rx *regexp.Regexp
format string
rx *regexp.Regexp
httpsGuessFunc func([]string) string
sshGuessFunc func([]string) string
}{
{
rx: regexp.MustCompile(`\A[-0-9A-Za-z]+\z`),
format: "https://github.com/%s/dotfiles.git",
rx: regexp.MustCompile(`\A([-0-9A-Za-z]+)\z`),
httpsGuessFunc: func(m []string) string { return "https://github.com/" + m[1] + "/dotfiles.git" },
sshGuessFunc: func(m []string) string { return "git@github.com:" + m[1] + "/dotfiles.git" },
},
{
rx: regexp.MustCompile(`\A[-0-9A-Za-z]+/[-0-9A-Za-z]+\.git\z`),
format: "https://github.com/%s",
rx: regexp.MustCompile(`\A([-0-9A-Za-z]+/[-0-9A-Za-z]+\.git)\z`),
httpsGuessFunc: func(m []string) string { return "https://github.com/" + m[1] },
sshGuessFunc: func(m []string) string { return "git@github.com:" + m[1] },
},
{
rx: regexp.MustCompile(`\A[-0-9A-Za-z]+/[-0-9A-Za-z]+\z`),
format: "https://github.com/%s.git",
rx: regexp.MustCompile(`\A([-0-9A-Za-z]+/[-0-9A-Za-z]+)\z`),
httpsGuessFunc: func(m []string) string { return "https://github.com/" + m[1] + ".git" },
sshGuessFunc: func(m []string) string { return "git@github.com:" + m[1] + ".git" },
},
{
rx: regexp.MustCompile(`\A[-.0-9A-Za-z]+/[-0-9A-Za-z]+\z`),
format: "https://%s/dotfiles.git",
rx: regexp.MustCompile(`\A([-.0-9A-Za-z]+)/([-0-9A-Za-z]+)\z`),
httpsGuessFunc: func(m []string) string { return "https://" + m[1] + "/" + m[2] + "/dotfiles.git" },
sshGuessFunc: func(m []string) string { return "git@" + m[1] + ":" + m[2] + "/dotfiles.git" },
},
{
rx: regexp.MustCompile(`\A[-.0-9A-Za-z]+/[-0-9A-Za-z]+/[-0-9A-Za-z]+\z`),
format: "https://%s.git",
rx: regexp.MustCompile(`\A([-.0-9A-Za-z]+)/([-0-9A-Za-z]+/[-0-9A-Za-z]+)\z`),
httpsGuessFunc: func(m []string) string { return "https://" + m[1] + "/" + m[2] + ".git" },
sshGuessFunc: func(m []string) string { return "git@" + m[1] + ":" + m[2] + ".git" },
},
{
rx: regexp.MustCompile(`\A[-.0-9A-Za-z]+/[-0-9A-Za-z]+/[-0-9A-Za-z]+\.git\z`),
format: "https://%s",
rx: regexp.MustCompile(`\A([-.0-9A-Za-z]+)/([-0-9A-Za-z]+/[-0-9A-Za-z]+\.git)\z`),
httpsGuessFunc: func(m []string) string { return "https://" + m[1] + "/" + m[2] },
sshGuessFunc: func(m []string) string { return "git@" + m[1] + ":" + m[2] },
},
{
rx: regexp.MustCompile(`\Asr\.ht/~[-0-9A-Za-z]+\z`),
format: "https://git.%s/dotfiles",
rx: regexp.MustCompile(`\Asr\.ht/(~[-0-9A-Za-z]+)\z`),
httpsGuessFunc: func(m []string) string { return "https://git.sr.ht/" + m[1] + "/dotfiles" },
sshGuessFunc: func(m []string) string { return "git@git.sr.ht:" + m[1] + "/dotfiles" },
},
{
rx: regexp.MustCompile(`\Asr\.ht/~[-0-9A-Za-z]+/[-0-9A-Za-z]+\z`),
format: "https://git.%s",
rx: regexp.MustCompile(`\Asr\.ht/(~[-0-9A-Za-z]+/[-0-9A-Za-z]+)\z`),
httpsGuessFunc: func(m []string) string { return "https://git.sr.ht/" + m[1] },
sshGuessFunc: func(m []string) string { return "git@git.sr.ht:" + m[1] },
},
}

Expand Down Expand Up @@ -93,6 +103,7 @@ func (c *Config) newInitCmd() *cobra.Command {
flags.BoolVar(&c.init.oneShot, "one-shot", c.init.oneShot, "one shot")
flags.BoolVarP(&c.init.purge, "purge", "p", c.init.purge, "purge config and source directories")
flags.BoolVarP(&c.init.purgeBinary, "purge-binary", "P", c.init.purgeBinary, "purge chezmoi binary")
flags.BoolVar(&c.init.ssh, "ssh", false, "use ssh instead of https for guessed dotfile repo URL")

return initCmd
}
Expand Down Expand Up @@ -129,7 +140,7 @@ func (c *Config) runInitCmd(cmd *cobra.Command, args []string) error {
return err
}
} else {
dotfilesRepoURL := guessDotfilesRepoURL(args[0])
dotfilesRepoURL := guessDotfilesRepoURL(args[0], c.init.ssh)
if useBuiltinGit {
isBare := false
if _, err := git.PlainClone(string(rawSourceDir), isBare, &git.CloneOptions{
Expand Down Expand Up @@ -319,10 +330,21 @@ func (c *Config) writeToStdout(args ...string) string {
}

// guessDotfilesRepoURL guesses the user's dotfile repo from arg.
func guessDotfilesRepoURL(arg string) string {
func guessDotfilesRepoURL(arg string, ssh bool) string {
for _, dotfileRepoGuess := range dotfilesRepoGuesses {
if dotfileRepoGuess.rx.MatchString(arg) {
return fmt.Sprintf(dotfileRepoGuess.format, arg)
switch {
case ssh:
if dotfileRepoGuess.sshGuessFunc != nil {
if m := dotfileRepoGuess.rx.FindStringSubmatch(arg); m != nil {
return dotfileRepoGuess.sshGuessFunc(m)
}
}
default:
if dotfileRepoGuess.rx.MatchString(arg) {
if m := dotfileRepoGuess.rx.FindStringSubmatch(arg); m != nil {
return dotfileRepoGuess.httpsGuessFunc(m)
}
}
}
}
return arg
Expand Down
76 changes: 64 additions & 12 deletions cmd/initcmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,70 @@ import (
)

func TestGuessDotfilesRepoURL(t *testing.T) {
for argStr, expected := range map[string]string{
"git@github.com:user/dotfiles.git": "git@github.com:user/dotfiles.git",
"gitlab.com/user": "https://gitlab.com/user/dotfiles.git",
"gitlab.com/user/dots": "https://gitlab.com/user/dots.git",
"gitlab.com/user/dots.git": "https://gitlab.com/user/dots.git",
"https://gitlab.com/user/dots.git": "https://gitlab.com/user/dots.git",
"sr.ht/~user": "https://git.sr.ht/~user/dotfiles",
"sr.ht/~user/dots": "https://git.sr.ht/~user/dots",
"user": "https://github.com/user/dotfiles.git",
"user/dots": "https://github.com/user/dots.git",
"user/dots.git": "https://github.com/user/dots.git",
for _, tc := range []struct {
arg string
expectedHTTPSURL string
expectedSSHURL string
}{
{
arg: "git@github.com:user/dotfiles.git",
},
{
arg: "gitlab.com/user",
expectedHTTPSURL: "https://gitlab.com/user/dotfiles.git",
expectedSSHURL: "git@gitlab.com:user/dotfiles.git",
},
{
arg: "gitlab.com/user/dots",
expectedHTTPSURL: "https://gitlab.com/user/dots.git",
expectedSSHURL: "git@gitlab.com:user/dots.git",
},
{
arg: "gitlab.com/user/dots.git",
expectedHTTPSURL: "https://gitlab.com/user/dots.git",
expectedSSHURL: "git@gitlab.com:user/dots.git",
},
{
arg: "https://gitlab.com/user/dots.git",
expectedHTTPSURL: "https://gitlab.com/user/dots.git",
},
{
arg: "sr.ht/~user",
expectedHTTPSURL: "https://git.sr.ht/~user/dotfiles",
expectedSSHURL: "git@git.sr.ht:~user/dotfiles",
},
{
arg: "sr.ht/~user/dots",
expectedHTTPSURL: "https://git.sr.ht/~user/dots",
expectedSSHURL: "git@git.sr.ht:~user/dots",
},
{
arg: "user",
expectedHTTPSURL: "https://github.com/user/dotfiles.git",
expectedSSHURL: "git@github.com:user/dotfiles.git",
},
{
arg: "user/dots",
expectedHTTPSURL: "https://github.com/user/dots.git",
expectedSSHURL: "git@github.com:user/dots.git",
},
{
arg: "user/dots.git",
expectedHTTPSURL: "https://github.com/user/dots.git",
expectedSSHURL: "git@github.com:user/dots.git",
},
} {
assert.Equal(t, expected, guessDotfilesRepoURL(argStr))
t.Run(tc.arg, func(t *testing.T) {
expectedHTTPSURL := tc.expectedHTTPSURL
if expectedHTTPSURL == "" {
expectedHTTPSURL = tc.arg
}
assert.Equal(t, expectedHTTPSURL, guessDotfilesRepoURL(tc.arg, false))
expectedSSHURL := tc.expectedSSHURL
if tc.expectedSSHURL == "" {
expectedSSHURL = tc.arg
}
assert.Equal(t, expectedSSHURL, guessDotfilesRepoURL(tc.arg, true))
})
}
}
2 changes: 2 additions & 0 deletions completions/chezmoi-completion.bash

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 16 additions & 11 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -883,17 +883,18 @@ given.

### `init` [*repo*]

Setup the source directory, generate the config file, and optionally update
the destination directory to match the target state. *repo* is expanded to a
full git repo URL using the following rules:

| Pattern | Repo |
| ------------------ | -------------------------------------- |
| `user` | `https://github.com/user/dotfiles.git` |
| `user/repo` | `https://github.com/user/repo.git` |
| `site/user/repo` | `https://site/user/repo.git` |
| `~sr.ht/user` | `https://git.sr.ht/~user/dotfiles` |
| `~sr.ht/user/repo` | `https://git.sr.ht/~user/repo` |
Setup the source directory, generate the config file, and optionally update the
destination directory to match the target state. *repo* is expanded to a full
git repo URL, using HTTPS by default, or SSH if the `--ssh` option is specified,
according to the following patterns:

| Pattern | HTTPS Repo | SSH repo |
| ------------------ | -------------------------------------- | ---------------------------------- |
| `user` | `https://github.com/user/dotfiles.git` | `git@github.com:user/dotfiles.git` |
| `user/repo` | `https://github.com/user/repo.git` | `git@github.com:user/repo.git` |
| `site/user/repo` | `https://site/user/repo.git` | `git@site:user/repo.git` |
| `~sr.ht/user` | `https://git.sr.ht/~user/dotfiles` | `git@git.sr.ht:~user/dotfiles.git` |
| `~sr.ht/user/repo` | `https://git.sr.ht/~user/repo` | `git@git.sr.ht:~/user/repo.git` |

First, if the source directory is not already contain a repository, then if
*repo* is given it is checked out into the source directory, otherwise a new
Expand Down Expand Up @@ -940,6 +941,10 @@ Remove the source and config directories after applying.

Attempt to remove the chezmoi binary after applying.

#### `--ssh`

Guess an SSH repo URL instead of an HTTPS repo.

#### `init` examples

chezmoi init user
Expand Down

0 comments on commit fba2ec9

Please sign in to comment.