Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/ja-jp/manage/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@

```shell
asdf plugin add <name> <git-url>
# asdf plugin add elm https://github.com/vic/asdf-elm
# asdf plugin add odo https://github.com/asdf-community/asdf-odo
```

また、`<git-ref>`引数で任意のGit参照(ブランチ、タグ、またはコミット)を指定することもできます:

```shell
asdf plugin add <name> [<git-url>] [<git-ref>]
# asdf plugin add odo main
# asdf plugin add odo v3.1.1
# asdf plugin add odo https://github.com/asdf-community/asdf-odo 3ca6ab4
```

または下記のコマンドで、プラグインリポジトリのショートネームを指定して追加します:
Expand Down
11 changes: 10 additions & 1 deletion docs/ko-kr/manage/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ Git URL로 플러그인 추가하기:

```shell
asdf plugin add <name> <git-url>
# asdf plugin add elm https://github.com/vic/asdf-elm
# asdf plugin add odo https://github.com/asdf-community/asdf-odo
```

`<git-ref>` 인수를 사용하여 임의의 Git 참조 (브랜치, 태그, 또는 커밋)를 지정할 수도 있습니다:

```shell
asdf plugin add <name> [<git-url>] [<git-ref>]
# asdf plugin add odo main
# asdf plugin add odo v3.1.1
# asdf plugin add odo https://github.com/asdf-community/asdf-odo 3ca6ab4
```

또는 플러그인 리포지토리에 short-name을 통해 추가하기:
Expand Down
11 changes: 10 additions & 1 deletion docs/manage/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ Add plugins via their Git URL:

```shell
asdf plugin add <name> <git-url>
# asdf plugin add elm https://github.com/vic/asdf-elm
# asdf plugin add odo https://github.com/asdf-community/asdf-odo
```

You can also specify any Git reference (branch, tag, or commit) with the `<git ref>` argument:

```shell
asdf plugin add <name> [<git-url>] [<git-ref>]
# asdf plugin add odo main
# asdf plugin add odo v3.1.1
# asdf plugin add odo https://github.com/asdf-community/asdf-odo 3ca6ab4
```

or via the short-name association in the plugins repository:
Expand Down
11 changes: 10 additions & 1 deletion docs/pt-br/manage/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ Adicione os plugins via sua Url Git:

```shell
asdf plugin add <name> <git-url>
# asdf plugin add elm https://github.com/vic/asdf-elm
# asdf plugin add odo https://github.com/asdf-community/asdf-odo
```

Você também pode especificar qualquer referência Git (branch, tag, ou commit) com o argumento `<git-ref>`:

```shell
asdf plugin add <name> [<git-url>] [<git-ref>]
# asdf plugin add odo main
# asdf plugin add odo v3.1.1
# asdf plugin add odo https://github.com/asdf-community/asdf-odo 3ca6ab4
```

ou pelo nome abreviado dentro do repositório de plugins:
Expand Down
13 changes: 11 additions & 2 deletions docs/zh-hans/manage/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@

```shell
asdf plugin add <name> <git-url>
# asdf plugin add elm https://github.com/vic/asdf-elm
# asdf plugin add odo https://github.com/asdf-community/asdf-odo
```

您也可以使用 `<git-ref>` 参数来指定任何 Git 引用(分支、标签或提交):

```shell
asdf plugin add <name> [<git-url>] [<git-ref>]
# asdf plugin add odo main
# asdf plugin add odo v3.1.1
# asdf plugin add odo https://github.com/asdf-community/asdf-odo 3ca6ab4
```

或者通过插件存储库中的缩写添加插件:
Expand Down Expand Up @@ -77,7 +86,7 @@ asdf plugin remove <name>
缩写存储库将同步到你的本地计算机并定期刷新。这个周期由以下方法确定:

- 命令触发的同步事件:
- `asdf plugin add <name>`
- `asdf plugin add <name>`
- `asdf plugin list all`
- 如果配置选项 `disable_plugin_short_name_repository` 设置为 `yes`,那么同步操作将提前终止。请查看 [asdf 配置文档](/zh-hans/manage/configuration.md) 了解更多。
- 如果在过去的 `X` 分钟内没有同步,则进行同步。
Expand Down
51 changes: 47 additions & 4 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/asdf-vm/asdf/internal/exec"
"github.com/asdf-vm/asdf/internal/execenv"
"github.com/asdf-vm/asdf/internal/execute"
"github.com/asdf-vm/asdf/internal/git"
"github.com/asdf-vm/asdf/internal/help"
"github.com/asdf-vm/asdf/internal/hook"
"github.com/asdf-vm/asdf/internal/info"
Expand Down Expand Up @@ -192,7 +193,29 @@ func Execute(version string) {
return err
}

return pluginAddCommand(cmd, conf, logger, args.Get(0), args.Get(1))
// Handle both formats:
// asdf plugin add <name> <git-ref>
// asdf plugin add <name> <git-url> <git-ref>
var pluginName, pluginRepo, gitRef string
switch args.Len() {
case 1:
pluginName = args.Get(0)
case 2:
pluginName = args.Get(0)
// Could be either <git-url> or <git-ref>
arg1 := args.Get(1)
if git.IsURL(arg1) {
pluginRepo = arg1
} else {
gitRef = arg1
}
case 3:
pluginName = args.Get(0)
pluginRepo = args.Get(1)
gitRef = args.Get(2)
}

return pluginAddCommand(cmd, conf, logger, pluginName, pluginRepo, gitRef)
},
},
{
Expand Down Expand Up @@ -710,15 +733,15 @@ func anyInstalled(conf config.Config, toolVersions []toolversions.ToolVersions)
return false
}

func pluginAddCommand(_ *cli.Command, conf config.Config, logger *log.Logger, pluginName, pluginRepo string) error {
func pluginAddCommand(_ *cli.Command, conf config.Config, logger *log.Logger, pluginName, pluginRepo, gitRef string) error {
if pluginName == "" {
// Invalid arguments
// Maybe one day switch this to show the generated help
// cli.ShowSubcommandHelp(cCtx)
return cli.Exit("usage: asdf plugin add <name> [<git-url>]", 1)
return cli.Exit("usage: asdf plugin add <name> [<git-url>] [<git-ref>]", 1)
}

err := plugins.Add(conf, pluginName, pluginRepo, "")
err := plugins.Add(conf, pluginName, pluginRepo, gitRef)
if err != nil {
logger.Printf("%s", err)

Expand Down Expand Up @@ -1032,6 +1055,21 @@ func pluginTestCommand(l *log.Logger, args []string, toolVersion, ref string) {
// a CLI argument
if toolVersion == "" {
toolVersion = allVersions[0]
} else if toolVersion == "latest" {
// Resolve "latest" to an actual version
latestVersion, err := versions.Latest(plugin, "")
if err != nil {
failTest(l, fmt.Sprintf("Unable to resolve latest version: %s", err))
}
toolVersion = latestVersion
} else if strings.HasPrefix(toolVersion, "latest:") {
// Handle "latest:prefix" syntax
prefix := strings.TrimPrefix(toolVersion, "latest:")
latestVersion, err := versions.Latest(plugin, prefix)
if err != nil {
failTest(l, fmt.Sprintf("Unable to resolve latest version with prefix '%s': %s", prefix, err))
}
toolVersion = latestVersion
}

err = versions.InstallOneVersion(conf, plugin, toolVersion, false, os.Stdout, os.Stderr)
Expand Down Expand Up @@ -1477,6 +1515,11 @@ func whereCommand(logger *log.Logger, tool, versionStr string) error {
return err
}

if tool == "" {
logger.Printf("No plugin given")
return errors.New("No plugin given")
}

currentDir, err := os.Getwd()
if err != nil {
logger.Printf("unable to get current directory: %s", err)
Expand Down
128 changes: 113 additions & 15 deletions internal/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io/fs"
"os"
"regexp"
"strings"

"github.com/asdf-vm/asdf/internal/execute"
Expand All @@ -15,6 +16,76 @@ import (
// DefaultRemoteName for Git repositories in asdf
const DefaultRemoteName = "origin"

// isSHA returns true if the ref looks like a SHA commit hash
func isSHA(ref string) bool {
// Match 7-40 hex characters (common range for git SHAs)
matched, _ := regexp.MatchString("^[0-9a-f]{7,40}$", ref)
return matched
}

// IsURL determines if a string is a Git URL based on Git's supported protocols
// Reference: https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols
func IsURL(s string) bool {
// Special case: "." and ".." are directory references, not git repository URLs
if s == "." || s == ".." {
return false
}

// HTTP/HTTPS protocols - must have proper :// format
if strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") {
return strings.Contains(s, "://") // Double check for proper format
}

// Git protocol
if strings.HasPrefix(s, "git://") {
return true
}

// SSH protocol variants
if strings.HasPrefix(s, "ssh://") {
return true
}

// SSH shorthand (user@host:path) - must not contain spaces
if strings.Contains(s, "@") && strings.Contains(s, ":") && !strings.Contains(s, " ") {
parts := strings.Split(s, "@")
if len(parts) == 2 {
hostPath := parts[1]
// Must have : after the host part and before path
return strings.Contains(hostPath, ":") && len(strings.Split(hostPath, ":")) >= 2
}
}

// File protocol
if strings.HasPrefix(s, "file://") {
return true
}

// Absolute paths are always URLs (Git branch names cannot start with /)
if strings.HasPrefix(s, "/") {
return true
}

// Explicit relative paths are always URLs (Git branch names cannot start with ./ or ../)
if strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../") {
return true
}

// Windows-style paths are always URLs (Git branch names cannot contain \)
if len(s) >= 3 && s[1] == ':' && (s[2] == '\\' || s[2] == '/') {
return true
}

// For anything else, check if it exists as a file or directory
// If it exists, it's more likely a file path (URL), otherwise it's likely a git ref
if _, err := os.Stat(s); err == nil {
return true
}

// If it doesn't exist, it's more likely a git ref (branch, tag, commit)
return false
}

// Repoer is an interface for operations that can be applied to asdf plugins.
// Right now we only support Git, but in the future we might have other
// mechanisms to install and upgrade plugins. asdf doesn't require a plugin
Expand All @@ -41,15 +112,33 @@ func NewRepo(directory string) Repo {

// Clone installs a plugin via Git
func (r Repo) Clone(pluginURL, ref string) error {
cmdStr := []string{"git", "clone", pluginURL, r.Directory}
var cmdStr []string

if ref != "" {
cmdStr = []string{"git", "clone", pluginURL, r.Directory, "--branch", ref}
}
if ref != "" && isSHA(ref) {
// For SHA commits, we need to clone first and then checkout the specific commit
cmdStr = []string{"git", "clone", pluginURL, r.Directory}
_, stderr, err := exec(cmdStr)
if err != nil {
return fmt.Errorf("unable to clone plugin: %s", stdErrToErrMsg(stderr))
}

_, stderr, err := exec(cmdStr)
if err != nil {
return fmt.Errorf("unable to clone plugin: %s", stdErrToErrMsg(stderr))
// Now checkout the specific commit
checkoutCmd := []string{"git", "-C", r.Directory, "-c", "advice.detachedHead=false", "checkout", ref}
_, stderr, err = exec(checkoutCmd)
if err != nil {
return fmt.Errorf("unable to checkout commit %s: %s", ref, stdErrToErrMsg(stderr))
}
} else {
// For branches and tags, use --branch flag
cmdStr = []string{"git", "clone", pluginURL, r.Directory}
if ref != "" {
cmdStr = []string{"git", "clone", pluginURL, r.Directory, "--branch", ref}
}

_, stderr, err := exec(cmdStr)
if err != nil {
return fmt.Errorf("unable to clone plugin: %s", stdErrToErrMsg(stderr))
}
}

return nil
Expand Down Expand Up @@ -113,16 +202,25 @@ func (r Repo) Update(ref string) (string, string, string, error) {

commonOpts := []string{"git", "-C", r.Directory}

refSpec := fmt.Sprintf("%s:%s", shortRef, shortRef)
cmdStr := append(commonOpts, []string{"fetch", "--prune", "--update-head-ok", remoteName, refSpec}...)

_, stderr, err := exec(cmdStr)
if err != nil {
return "", "", "", errors.New(stdErrToErrMsg(stderr))
if isSHA(shortRef) {
// For SHA commits, fetch all refs and then checkout the specific commit
fetchCmd := append(commonOpts, []string{"fetch", "--prune", "--update-head-ok", remoteName}...)
_, stderr, err := exec(fetchCmd)
if err != nil {
return "", "", "", errors.New(stdErrToErrMsg(stderr))
}
} else {
// For branches and tags, use refspec to create local tracking branch
refSpec := fmt.Sprintf("%s:%s", shortRef, shortRef)
fetchCmd := append(commonOpts, []string{"fetch", "--prune", "--update-head-ok", remoteName, refSpec}...)
_, stderr, err := exec(fetchCmd)
if err != nil {
return "", "", "", errors.New(stdErrToErrMsg(stderr))
}
}

cmdStr = append(commonOpts, []string{"-c", "advice.detachedHead=false", "checkout", "--force", shortRef}...)
_, stderr, err = exec(cmdStr)
cmdStr := append(commonOpts, []string{"-c", "advice.detachedHead=false", "checkout", "--force", shortRef}...)
_, stderr, err := exec(cmdStr)
if err != nil {
return "", "", "", errors.New(stdErrToErrMsg(stderr))
}
Expand Down
Loading
Loading