Initial support for passing URLs to `new` and `up` (#1727)
* Initial support for passing URLs to `new` and `up`
This PR adds initial support for `pulumi new` using Git under the covers
to manage Pulumi templates, providing the same experience as before.
You can now also optionally pass a URL to a Git repository, e.g.
`pulumi new [<url>]`, including subdirectories within the repository,
and arbitrary branches, tags, or commits.
The following commands result in the same behavior from the user's
perspective:
- `pulumi new javascript`
- `pulumi new https://github.com/pulumi/templates/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/master/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/HEAD/templates/javascript`
To specify an arbitrary branch, tag, or commit:
- `pulumi new https://github.com/pulumi/templates/tree/<branch>/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/<tag>/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/<commit>/templates/javascript`
Branches and tags can include '/' separators, and `pulumi` will still
find the right subdirectory.
URLs to Gists are also supported, e.g.:
`pulumi new https://gist.github.com/justinvp/6673959ceb9d2ac5a14c6d536cb871a6`
If the specified subdirectory in the repository does not contain a
`Pulumi.yaml`, it will look for subdirectories within containing
`Pulumi.yaml` files, and prompt the user to choose a template, along the
lines of how `pulumi new` behaves when no template is specified.
The following commands result in the CLI prompting to choose a template:
- `pulumi new`
- `pulumi new https://github.com/pulumi/templates/templates`
- `pulumi new https://github.com/pulumi/templates/tree/master/templates`
- `pulumi new https://github.com/pulumi/templates/tree/HEAD/templates`
Of course, arbitrary branches, tags, or commits can be specified as well:
- `pulumi new https://github.com/pulumi/templates/tree/<branch>/templates`
- `pulumi new https://github.com/pulumi/templates/tree/<tag>/templates`
- `pulumi new https://github.com/pulumi/templates/tree/<commit>/templates`
This PR also includes initial support for passing URLs to `pulumi up`,
providing a streamlined way to deploy installable cloud applications
with Pulumi, without having to manage source code locally before doing
a deployment.
For example, `pulumi up https://github.com/justinvp/aws` can be used to
deploy a sample AWS app. The stack can be updated with different
versions, e.g.
`pulumi up https://github.com/justinvp/aws/tree/v2 -s <stack-to-update>`
Config values can optionally be passed via command line flags, e.g.
`pulumi up https://github.com/justinvp/aws -c aws:region=us-west-2 -c foo:bar=blah`
Gists can also be used, e.g.
`pulumi up https://gist.github.com/justinvp/62fde0463f243fcb49f5a7222e51bc76`
* Fix panic when hitting ^C from "choose template" prompt
* Add description to templates
When running `pulumi new` without specifying a template, include the template description along with the name in the "choose template" display.
```
$ pulumi new
Please choose a template:
aws-go A minimal AWS Go program
aws-javascript A minimal AWS JavaScript program
aws-python A minimal AWS Python program
aws-typescript A minimal AWS TypeScript program
> go A minimal Go program
hello-aws-javascript A simple AWS serverless JavaScript program
javascript A minimal JavaScript program
python A minimal Python program
typescript A minimal TypeScript program
```
* React to changes to the pulumi/templates repo.
We restructured the `pulumi/templates` repo to have all the templates in the root instead of in a `templates` subdirectory, so make the change here to no longer look for templates in `templates`.
This also fixes an issue around using `Depth: 1` that I found while testing this. When a named template is used, we attempt to clone or pull from the `pulumi/templates` repo to `~/.pulumi/templates`. Having it go in this well-known directory allows us to maintain previous behavior around allowing offline use of templates. If we use `Depth: 1` for the initial clone, it will fail when attempting to pull when there are updates to the remote repository. Unfortunately, there's no built-in `--unshallow` support in `go-git` and setting a larger `Depth` doesn't appear to help. There may be a workaround, but for now, if we're cloning the pulumi templates directory to `~/.pulumi/templates`, we won't use `Depth: 1`. For template URLs, we will continue to use `Depth: 1` as we clone those to a temp directory (which gets deleted) that we'll never try to update.
* List available templates in help text
* Address PR Feedback
* Don't show "Installing dependencies" message for `up`
* Fix secrets handling
When prompting for config, if the existing stack value is a secret, keep it a secret and mask the prompt. If the template says it should be secret, make it a secret.
* Fix ${PROJECT} and ${DESCRIPTION} handling for `up`
Templates used with `up` should already have a filled-in project name and description, but if it's a `new`-style template, that has `${PROJECT}` and/or `${DESCRIPTION}`, be helpful and just replace these with better values.
* Fix stack handling
Add a bool `setCurrent` param to `requireStack` to control whether the current stack should be saved in workspace settings. For the `up <url>` case, we don't want to save. Also, split the `up` code into two separate functions: one for the `up <url>` case and another for the normal `up` case where you have workspace in your current directory. While we may be able to combine them back into a single function, right now it's a bit cleaner being separate, even with some small amount of duplication.
* Fix panic due to nil crypter
Lazily get the crypter only if needed inside `promptForConfig`.
* Embellish comment
* Harden isPreconfiguredEmptyStack check
Fix the code to check to make sure the URL specified on the command line matches the URL stored in the `pulumi:template` config value, and that the rest of the config from the stack satisfies the config requirements of the template.
2018-08-11 03:08:16 +02:00
|
|
|
// Copyright 2016-2018, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2018-05-22 01:17:12 +02:00
|
|
|
|
|
|
|
package gitutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
Initial support for passing URLs to `new` and `up` (#1727)
* Initial support for passing URLs to `new` and `up`
This PR adds initial support for `pulumi new` using Git under the covers
to manage Pulumi templates, providing the same experience as before.
You can now also optionally pass a URL to a Git repository, e.g.
`pulumi new [<url>]`, including subdirectories within the repository,
and arbitrary branches, tags, or commits.
The following commands result in the same behavior from the user's
perspective:
- `pulumi new javascript`
- `pulumi new https://github.com/pulumi/templates/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/master/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/HEAD/templates/javascript`
To specify an arbitrary branch, tag, or commit:
- `pulumi new https://github.com/pulumi/templates/tree/<branch>/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/<tag>/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/<commit>/templates/javascript`
Branches and tags can include '/' separators, and `pulumi` will still
find the right subdirectory.
URLs to Gists are also supported, e.g.:
`pulumi new https://gist.github.com/justinvp/6673959ceb9d2ac5a14c6d536cb871a6`
If the specified subdirectory in the repository does not contain a
`Pulumi.yaml`, it will look for subdirectories within containing
`Pulumi.yaml` files, and prompt the user to choose a template, along the
lines of how `pulumi new` behaves when no template is specified.
The following commands result in the CLI prompting to choose a template:
- `pulumi new`
- `pulumi new https://github.com/pulumi/templates/templates`
- `pulumi new https://github.com/pulumi/templates/tree/master/templates`
- `pulumi new https://github.com/pulumi/templates/tree/HEAD/templates`
Of course, arbitrary branches, tags, or commits can be specified as well:
- `pulumi new https://github.com/pulumi/templates/tree/<branch>/templates`
- `pulumi new https://github.com/pulumi/templates/tree/<tag>/templates`
- `pulumi new https://github.com/pulumi/templates/tree/<commit>/templates`
This PR also includes initial support for passing URLs to `pulumi up`,
providing a streamlined way to deploy installable cloud applications
with Pulumi, without having to manage source code locally before doing
a deployment.
For example, `pulumi up https://github.com/justinvp/aws` can be used to
deploy a sample AWS app. The stack can be updated with different
versions, e.g.
`pulumi up https://github.com/justinvp/aws/tree/v2 -s <stack-to-update>`
Config values can optionally be passed via command line flags, e.g.
`pulumi up https://github.com/justinvp/aws -c aws:region=us-west-2 -c foo:bar=blah`
Gists can also be used, e.g.
`pulumi up https://gist.github.com/justinvp/62fde0463f243fcb49f5a7222e51bc76`
* Fix panic when hitting ^C from "choose template" prompt
* Add description to templates
When running `pulumi new` without specifying a template, include the template description along with the name in the "choose template" display.
```
$ pulumi new
Please choose a template:
aws-go A minimal AWS Go program
aws-javascript A minimal AWS JavaScript program
aws-python A minimal AWS Python program
aws-typescript A minimal AWS TypeScript program
> go A minimal Go program
hello-aws-javascript A simple AWS serverless JavaScript program
javascript A minimal JavaScript program
python A minimal Python program
typescript A minimal TypeScript program
```
* React to changes to the pulumi/templates repo.
We restructured the `pulumi/templates` repo to have all the templates in the root instead of in a `templates` subdirectory, so make the change here to no longer look for templates in `templates`.
This also fixes an issue around using `Depth: 1` that I found while testing this. When a named template is used, we attempt to clone or pull from the `pulumi/templates` repo to `~/.pulumi/templates`. Having it go in this well-known directory allows us to maintain previous behavior around allowing offline use of templates. If we use `Depth: 1` for the initial clone, it will fail when attempting to pull when there are updates to the remote repository. Unfortunately, there's no built-in `--unshallow` support in `go-git` and setting a larger `Depth` doesn't appear to help. There may be a workaround, but for now, if we're cloning the pulumi templates directory to `~/.pulumi/templates`, we won't use `Depth: 1`. For template URLs, we will continue to use `Depth: 1` as we clone those to a temp directory (which gets deleted) that we'll never try to update.
* List available templates in help text
* Address PR Feedback
* Don't show "Installing dependencies" message for `up`
* Fix secrets handling
When prompting for config, if the existing stack value is a secret, keep it a secret and mask the prompt. If the template says it should be secret, make it a secret.
* Fix ${PROJECT} and ${DESCRIPTION} handling for `up`
Templates used with `up` should already have a filled-in project name and description, but if it's a `new`-style template, that has `${PROJECT}` and/or `${DESCRIPTION}`, be helpful and just replace these with better values.
* Fix stack handling
Add a bool `setCurrent` param to `requireStack` to control whether the current stack should be saved in workspace settings. For the `up <url>` case, we don't want to save. Also, split the `up` code into two separate functions: one for the `up <url>` case and another for the normal `up` case where you have workspace in your current directory. While we may be able to combine them back into a single function, right now it's a bit cleaner being separate, even with some small amount of duplication.
* Fix panic due to nil crypter
Lazily get the crypter only if needed inside `promptForConfig`.
* Embellish comment
* Harden isPreconfiguredEmptyStack check
Fix the code to check to make sure the URL specified on the command line matches the URL stored in the `pulumi:template` config value, and that the rest of the config from the stack satisfies the config requirements of the template.
2018-08-11 03:08:16 +02:00
|
|
|
"net/url"
|
2018-05-22 01:17:12 +02:00
|
|
|
"path"
|
|
|
|
"path/filepath"
|
Initial support for passing URLs to `new` and `up` (#1727)
* Initial support for passing URLs to `new` and `up`
This PR adds initial support for `pulumi new` using Git under the covers
to manage Pulumi templates, providing the same experience as before.
You can now also optionally pass a URL to a Git repository, e.g.
`pulumi new [<url>]`, including subdirectories within the repository,
and arbitrary branches, tags, or commits.
The following commands result in the same behavior from the user's
perspective:
- `pulumi new javascript`
- `pulumi new https://github.com/pulumi/templates/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/master/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/HEAD/templates/javascript`
To specify an arbitrary branch, tag, or commit:
- `pulumi new https://github.com/pulumi/templates/tree/<branch>/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/<tag>/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/<commit>/templates/javascript`
Branches and tags can include '/' separators, and `pulumi` will still
find the right subdirectory.
URLs to Gists are also supported, e.g.:
`pulumi new https://gist.github.com/justinvp/6673959ceb9d2ac5a14c6d536cb871a6`
If the specified subdirectory in the repository does not contain a
`Pulumi.yaml`, it will look for subdirectories within containing
`Pulumi.yaml` files, and prompt the user to choose a template, along the
lines of how `pulumi new` behaves when no template is specified.
The following commands result in the CLI prompting to choose a template:
- `pulumi new`
- `pulumi new https://github.com/pulumi/templates/templates`
- `pulumi new https://github.com/pulumi/templates/tree/master/templates`
- `pulumi new https://github.com/pulumi/templates/tree/HEAD/templates`
Of course, arbitrary branches, tags, or commits can be specified as well:
- `pulumi new https://github.com/pulumi/templates/tree/<branch>/templates`
- `pulumi new https://github.com/pulumi/templates/tree/<tag>/templates`
- `pulumi new https://github.com/pulumi/templates/tree/<commit>/templates`
This PR also includes initial support for passing URLs to `pulumi up`,
providing a streamlined way to deploy installable cloud applications
with Pulumi, without having to manage source code locally before doing
a deployment.
For example, `pulumi up https://github.com/justinvp/aws` can be used to
deploy a sample AWS app. The stack can be updated with different
versions, e.g.
`pulumi up https://github.com/justinvp/aws/tree/v2 -s <stack-to-update>`
Config values can optionally be passed via command line flags, e.g.
`pulumi up https://github.com/justinvp/aws -c aws:region=us-west-2 -c foo:bar=blah`
Gists can also be used, e.g.
`pulumi up https://gist.github.com/justinvp/62fde0463f243fcb49f5a7222e51bc76`
* Fix panic when hitting ^C from "choose template" prompt
* Add description to templates
When running `pulumi new` without specifying a template, include the template description along with the name in the "choose template" display.
```
$ pulumi new
Please choose a template:
aws-go A minimal AWS Go program
aws-javascript A minimal AWS JavaScript program
aws-python A minimal AWS Python program
aws-typescript A minimal AWS TypeScript program
> go A minimal Go program
hello-aws-javascript A simple AWS serverless JavaScript program
javascript A minimal JavaScript program
python A minimal Python program
typescript A minimal TypeScript program
```
* React to changes to the pulumi/templates repo.
We restructured the `pulumi/templates` repo to have all the templates in the root instead of in a `templates` subdirectory, so make the change here to no longer look for templates in `templates`.
This also fixes an issue around using `Depth: 1` that I found while testing this. When a named template is used, we attempt to clone or pull from the `pulumi/templates` repo to `~/.pulumi/templates`. Having it go in this well-known directory allows us to maintain previous behavior around allowing offline use of templates. If we use `Depth: 1` for the initial clone, it will fail when attempting to pull when there are updates to the remote repository. Unfortunately, there's no built-in `--unshallow` support in `go-git` and setting a larger `Depth` doesn't appear to help. There may be a workaround, but for now, if we're cloning the pulumi templates directory to `~/.pulumi/templates`, we won't use `Depth: 1`. For template URLs, we will continue to use `Depth: 1` as we clone those to a temp directory (which gets deleted) that we'll never try to update.
* List available templates in help text
* Address PR Feedback
* Don't show "Installing dependencies" message for `up`
* Fix secrets handling
When prompting for config, if the existing stack value is a secret, keep it a secret and mask the prompt. If the template says it should be secret, make it a secret.
* Fix ${PROJECT} and ${DESCRIPTION} handling for `up`
Templates used with `up` should already have a filled-in project name and description, but if it's a `new`-style template, that has `${PROJECT}` and/or `${DESCRIPTION}`, be helpful and just replace these with better values.
* Fix stack handling
Add a bool `setCurrent` param to `requireStack` to control whether the current stack should be saved in workspace settings. For the `up <url>` case, we don't want to save. Also, split the `up` code into two separate functions: one for the `up <url>` case and another for the normal `up` case where you have workspace in your current directory. While we may be able to combine them back into a single function, right now it's a bit cleaner being separate, even with some small amount of duplication.
* Fix panic due to nil crypter
Lazily get the crypter only if needed inside `promptForConfig`.
* Embellish comment
* Harden isPreconfiguredEmptyStack check
Fix the code to check to make sure the URL specified on the command line matches the URL stored in the `pulumi:template` config value, and that the rest of the config from the stack satisfies the config requirements of the template.
2018-08-11 03:08:16 +02:00
|
|
|
"regexp"
|
|
|
|
"sort"
|
2018-05-22 01:17:12 +02:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/pulumi/pulumi/pkg/util/fsutil"
|
|
|
|
git "gopkg.in/src-d/go-git.v4"
|
Initial support for passing URLs to `new` and `up` (#1727)
* Initial support for passing URLs to `new` and `up`
This PR adds initial support for `pulumi new` using Git under the covers
to manage Pulumi templates, providing the same experience as before.
You can now also optionally pass a URL to a Git repository, e.g.
`pulumi new [<url>]`, including subdirectories within the repository,
and arbitrary branches, tags, or commits.
The following commands result in the same behavior from the user's
perspective:
- `pulumi new javascript`
- `pulumi new https://github.com/pulumi/templates/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/master/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/HEAD/templates/javascript`
To specify an arbitrary branch, tag, or commit:
- `pulumi new https://github.com/pulumi/templates/tree/<branch>/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/<tag>/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/<commit>/templates/javascript`
Branches and tags can include '/' separators, and `pulumi` will still
find the right subdirectory.
URLs to Gists are also supported, e.g.:
`pulumi new https://gist.github.com/justinvp/6673959ceb9d2ac5a14c6d536cb871a6`
If the specified subdirectory in the repository does not contain a
`Pulumi.yaml`, it will look for subdirectories within containing
`Pulumi.yaml` files, and prompt the user to choose a template, along the
lines of how `pulumi new` behaves when no template is specified.
The following commands result in the CLI prompting to choose a template:
- `pulumi new`
- `pulumi new https://github.com/pulumi/templates/templates`
- `pulumi new https://github.com/pulumi/templates/tree/master/templates`
- `pulumi new https://github.com/pulumi/templates/tree/HEAD/templates`
Of course, arbitrary branches, tags, or commits can be specified as well:
- `pulumi new https://github.com/pulumi/templates/tree/<branch>/templates`
- `pulumi new https://github.com/pulumi/templates/tree/<tag>/templates`
- `pulumi new https://github.com/pulumi/templates/tree/<commit>/templates`
This PR also includes initial support for passing URLs to `pulumi up`,
providing a streamlined way to deploy installable cloud applications
with Pulumi, without having to manage source code locally before doing
a deployment.
For example, `pulumi up https://github.com/justinvp/aws` can be used to
deploy a sample AWS app. The stack can be updated with different
versions, e.g.
`pulumi up https://github.com/justinvp/aws/tree/v2 -s <stack-to-update>`
Config values can optionally be passed via command line flags, e.g.
`pulumi up https://github.com/justinvp/aws -c aws:region=us-west-2 -c foo:bar=blah`
Gists can also be used, e.g.
`pulumi up https://gist.github.com/justinvp/62fde0463f243fcb49f5a7222e51bc76`
* Fix panic when hitting ^C from "choose template" prompt
* Add description to templates
When running `pulumi new` without specifying a template, include the template description along with the name in the "choose template" display.
```
$ pulumi new
Please choose a template:
aws-go A minimal AWS Go program
aws-javascript A minimal AWS JavaScript program
aws-python A minimal AWS Python program
aws-typescript A minimal AWS TypeScript program
> go A minimal Go program
hello-aws-javascript A simple AWS serverless JavaScript program
javascript A minimal JavaScript program
python A minimal Python program
typescript A minimal TypeScript program
```
* React to changes to the pulumi/templates repo.
We restructured the `pulumi/templates` repo to have all the templates in the root instead of in a `templates` subdirectory, so make the change here to no longer look for templates in `templates`.
This also fixes an issue around using `Depth: 1` that I found while testing this. When a named template is used, we attempt to clone or pull from the `pulumi/templates` repo to `~/.pulumi/templates`. Having it go in this well-known directory allows us to maintain previous behavior around allowing offline use of templates. If we use `Depth: 1` for the initial clone, it will fail when attempting to pull when there are updates to the remote repository. Unfortunately, there's no built-in `--unshallow` support in `go-git` and setting a larger `Depth` doesn't appear to help. There may be a workaround, but for now, if we're cloning the pulumi templates directory to `~/.pulumi/templates`, we won't use `Depth: 1`. For template URLs, we will continue to use `Depth: 1` as we clone those to a temp directory (which gets deleted) that we'll never try to update.
* List available templates in help text
* Address PR Feedback
* Don't show "Installing dependencies" message for `up`
* Fix secrets handling
When prompting for config, if the existing stack value is a secret, keep it a secret and mask the prompt. If the template says it should be secret, make it a secret.
* Fix ${PROJECT} and ${DESCRIPTION} handling for `up`
Templates used with `up` should already have a filled-in project name and description, but if it's a `new`-style template, that has `${PROJECT}` and/or `${DESCRIPTION}`, be helpful and just replace these with better values.
* Fix stack handling
Add a bool `setCurrent` param to `requireStack` to control whether the current stack should be saved in workspace settings. For the `up <url>` case, we don't want to save. Also, split the `up` code into two separate functions: one for the `up <url>` case and another for the normal `up` case where you have workspace in your current directory. While we may be able to combine them back into a single function, right now it's a bit cleaner being separate, even with some small amount of duplication.
* Fix panic due to nil crypter
Lazily get the crypter only if needed inside `promptForConfig`.
* Embellish comment
* Harden isPreconfiguredEmptyStack check
Fix the code to check to make sure the URL specified on the command line matches the URL stored in the `pulumi:template` config value, and that the rest of the config from the stack satisfies the config requirements of the template.
2018-08-11 03:08:16 +02:00
|
|
|
"gopkg.in/src-d/go-git.v4/config"
|
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
|
|
"gopkg.in/src-d/go-git.v4/storage/memory"
|
2018-05-22 01:17:12 +02:00
|
|
|
)
|
|
|
|
|
2018-10-23 23:53:52 +02:00
|
|
|
// VCSKind represents the hostname of a specific type of VCS.
|
|
|
|
// For eg., github.com, gitlab.com etc.
|
|
|
|
type VCSKind = string
|
|
|
|
|
|
|
|
// Constants related to detecting the right type of source control provider for git.
|
|
|
|
const (
|
|
|
|
defaultGitCloudRepositorySuffix = ".git"
|
|
|
|
|
|
|
|
// The host name for GitLab.
|
|
|
|
GitLabHostName VCSKind = "gitlab.com"
|
|
|
|
// The host name for GitHub.
|
|
|
|
GitHubHostName VCSKind = "github.com"
|
|
|
|
// The host name for Azure DevOps
|
|
|
|
AzureDevOpsHostName VCSKind = "dev.azure.com"
|
|
|
|
// The host name for Bitbucket
|
|
|
|
BitbucketHostName VCSKind = "bitbucket.org"
|
|
|
|
)
|
|
|
|
|
|
|
|
// The pre-compiled regex used to extract owner and repo name from an SSH git remote URL.
|
|
|
|
// Note: If you are renaming any of the group names in the regex (the ?P<group_name> part) to something else,
|
|
|
|
// be sure to update its usage elsewhere in the code as well.
|
|
|
|
// The nolint instruction prevents gometalinter from complaining about the length of the line.
|
|
|
|
var (
|
2019-05-08 18:25:59 +02:00
|
|
|
cloudSourceControlSSHRegex = regexp.MustCompile(`git@(?P<host_name>[a-zA-Z]*\.com|[a-zA-Z]*\.org):(?P<owner_and_repo>.*)`) //nolint
|
|
|
|
azureSourceControlSSHRegex = regexp.MustCompile(`git@([a-zA-Z]+\.)?(?P<host_name>([a-zA-Z]+\.)*[a-zA-Z]*\.com):(v[0-9]{1}/)?(?P<owner_and_repo>.*)`) //nolint
|
|
|
|
legacyAzureSourceControlRegex = regexp.MustCompile("(?P<owner>[a-zA-Z0-9-]*).visualstudio.com$")
|
2018-10-23 23:53:52 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// VCSInfo describes a cloud-hosted version control system.
|
|
|
|
// Cloud hosted VCS' typically have an owner (could be an organization),
|
|
|
|
// to whom the repo belongs.
|
|
|
|
type VCSInfo struct {
|
|
|
|
Owner string
|
|
|
|
Repo string
|
|
|
|
Kind VCSKind
|
|
|
|
}
|
|
|
|
|
2018-05-22 01:17:12 +02:00
|
|
|
// GetGitRepository returns the git repository by walking up from the provided directory.
|
|
|
|
// If no repository is found, will return (nil, nil).
|
|
|
|
func GetGitRepository(dir string) (*git.Repository, error) {
|
|
|
|
gitRoot, err := fsutil.WalkUp(dir, func(s string) bool { return filepath.Base(s) == ".git" }, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "searching for git repository from %v", dir)
|
|
|
|
}
|
|
|
|
if gitRoot == "" {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the git repo in the .git folder's parent, not the .git folder itself.
|
|
|
|
repo, err := git.PlainOpen(path.Join(gitRoot, ".."))
|
|
|
|
if err == git.ErrRepositoryNotExists {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "reading git repository")
|
|
|
|
}
|
|
|
|
return repo, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetGitHubProjectForOrigin returns the GitHub login, and GitHub repo name if the "origin" remote is
|
|
|
|
// a GitHub URL.
|
2018-10-23 23:53:52 +02:00
|
|
|
func GetGitHubProjectForOrigin(dir string) (*VCSInfo, error) {
|
2018-05-22 01:17:12 +02:00
|
|
|
repo, err := GetGitRepository(dir)
|
|
|
|
if repo == nil {
|
2018-10-23 23:53:52 +02:00
|
|
|
return nil, fmt.Errorf("no git repository found from %v", dir)
|
2018-05-22 01:17:12 +02:00
|
|
|
}
|
|
|
|
if err != nil {
|
2018-10-23 23:53:52 +02:00
|
|
|
return nil, err
|
2018-05-22 01:17:12 +02:00
|
|
|
}
|
2018-10-23 23:53:52 +02:00
|
|
|
remoteURL, err := GetGitRemoteURL(repo, "origin")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return TryGetVCSInfo(remoteURL)
|
2018-05-22 01:17:12 +02:00
|
|
|
}
|
|
|
|
|
2018-10-23 23:53:52 +02:00
|
|
|
// GetGitRemoteURL returns the remote URL for the given remoteName in the repo.
|
|
|
|
func GetGitRemoteURL(repo *git.Repository, remoteName string) (string, error) {
|
|
|
|
remote, err := repo.Remote(remoteName)
|
2018-05-22 01:17:12 +02:00
|
|
|
if err != nil {
|
2018-10-23 23:53:52 +02:00
|
|
|
return "", errors.Wrap(err, "could not read origin information")
|
2018-05-22 01:17:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
remoteURL := ""
|
|
|
|
if len(remote.Config().URLs) > 0 {
|
|
|
|
remoteURL = remote.Config().URLs[0]
|
|
|
|
}
|
2018-10-23 23:53:52 +02:00
|
|
|
|
|
|
|
return remoteURL, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsGitOriginURLGitHub returns true if the provided remoteURL is detected as GitHub.
|
|
|
|
func IsGitOriginURLGitHub(remoteURL string) bool {
|
|
|
|
return strings.Contains(remoteURL, GitHubHostName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TryGetVCSInfo attempts to detect whether the provided remoteURL
|
|
|
|
// is an SSH or an HTTPS remote URL. It then extracts the repo, owner name,
|
|
|
|
// and the type (kind) of VCS from it.
|
|
|
|
func TryGetVCSInfo(remoteURL string) (*VCSInfo, error) {
|
2018-05-22 01:17:12 +02:00
|
|
|
project := ""
|
2018-10-23 23:53:52 +02:00
|
|
|
vcsKind := ""
|
|
|
|
|
|
|
|
// If the remote is using git SSH, then we extract the named groups by matching
|
|
|
|
// with the pre-compiled regex pattern.
|
|
|
|
if strings.HasPrefix(remoteURL, "git@") {
|
|
|
|
// Most cloud-hosted VCS have the ssh URL of the format git@somehostname.com:owner/repo
|
|
|
|
if cloudSourceControlSSHRegex.MatchString(remoteURL) {
|
|
|
|
groups := getMatchedGroupsFromRegex(cloudSourceControlSSHRegex, remoteURL)
|
|
|
|
vcsKind = groups["host_name"]
|
|
|
|
project = groups["owner_and_repo"]
|
|
|
|
project = strings.TrimSuffix(project, defaultGitCloudRepositorySuffix)
|
|
|
|
} else if azureSourceControlSSHRegex.MatchString(remoteURL) {
|
|
|
|
// Azure's DevOps service uses a git SSH url, that is completely different
|
|
|
|
// from the rest of the services.
|
|
|
|
groups := getMatchedGroupsFromRegex(azureSourceControlSSHRegex, remoteURL)
|
|
|
|
vcsKind = groups["host_name"]
|
|
|
|
project = groups["owner_and_repo"]
|
|
|
|
project = strings.TrimSuffix(project, defaultGitCloudRepositorySuffix)
|
|
|
|
}
|
|
|
|
} else if strings.HasPrefix(remoteURL, "http") {
|
|
|
|
// This could be an HTTP(S)-based remote.
|
|
|
|
if parsedURL, err := url.Parse(remoteURL); err == nil {
|
|
|
|
vcsKind = parsedURL.Host
|
|
|
|
project = parsedURL.Path
|
|
|
|
// Replace the .git extension from the path.
|
|
|
|
project = strings.TrimSuffix(project, defaultGitCloudRepositorySuffix)
|
|
|
|
// Remove the prefix "/". TrimPrefix returns the same value if there is no prefix.
|
|
|
|
// So it is safe to use it instead of doing any sort of substring matches.
|
|
|
|
project = strings.TrimPrefix(project, "/")
|
|
|
|
}
|
|
|
|
}
|
2018-05-22 01:17:12 +02:00
|
|
|
|
2018-10-23 23:53:52 +02:00
|
|
|
if project == "" {
|
|
|
|
return nil, errors.Errorf("detecting the VCS info from the remote URL %v", remoteURL)
|
|
|
|
}
|
2018-05-22 01:17:12 +02:00
|
|
|
|
2018-10-23 23:53:52 +02:00
|
|
|
// For Azure, we will have more than 2 parts in the array.
|
|
|
|
// Ex: owner/project/repo.git
|
|
|
|
if vcsKind == AzureDevOpsHostName {
|
|
|
|
azureSplit := strings.SplitN(project, "/", 2)
|
2019-05-08 18:25:59 +02:00
|
|
|
|
|
|
|
// Azure DevOps repo links are in the format `owner/project/_git/repo`. Some remote URLs do
|
|
|
|
// not include the `_git` piece, which results in the reconstructed URL linking to the
|
|
|
|
// project dashboard. To remedy this, we will add the _git portion to the URL if its
|
|
|
|
// missing.
|
|
|
|
project = azureSplit[1]
|
|
|
|
if !strings.Contains(project, "_git") {
|
|
|
|
projectSplit := strings.SplitN(project, "/", 2)
|
|
|
|
project = projectSplit[0] + "/_git/" + projectSplit[1]
|
|
|
|
}
|
|
|
|
|
2018-10-23 23:53:52 +02:00
|
|
|
return &VCSInfo{
|
|
|
|
Owner: azureSplit[0],
|
2019-05-08 18:25:59 +02:00
|
|
|
Repo: project,
|
2018-10-23 23:53:52 +02:00
|
|
|
Kind: vcsKind,
|
|
|
|
}, nil
|
2019-05-08 18:25:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Legacy Azure URLs have the owner as part of the host name. We will convert the Git info to
|
|
|
|
// reflect the newer Azure DevOps URLs. This allows the UI to properly construct the repo URL
|
|
|
|
// and group it with other projects/stacks that have been pulled with a newer version of the Git
|
|
|
|
// URL.
|
|
|
|
if legacyAzureSourceControlRegex.MatchString(vcsKind) {
|
|
|
|
groups := getMatchedGroupsFromRegex(legacyAzureSourceControlRegex, vcsKind)
|
|
|
|
|
|
|
|
return &VCSInfo{
|
|
|
|
Owner: groups["owner"],
|
|
|
|
Repo: project,
|
|
|
|
Kind: AzureDevOpsHostName,
|
|
|
|
}, nil
|
2018-05-22 01:17:12 +02:00
|
|
|
}
|
|
|
|
|
2018-10-23 23:53:52 +02:00
|
|
|
// Since the vcsKind is not Azure, we can try to detect the other kinds of VCS.
|
2018-05-22 01:17:12 +02:00
|
|
|
split := strings.Split(project, "/")
|
|
|
|
if len(split) != 2 {
|
2018-10-23 23:53:52 +02:00
|
|
|
return nil, errors.Errorf("could not detect VCS project from url: %v", remoteURL)
|
2018-05-22 01:17:12 +02:00
|
|
|
}
|
|
|
|
|
2018-10-23 23:53:52 +02:00
|
|
|
return &VCSInfo{
|
|
|
|
Owner: split[0],
|
|
|
|
Repo: split[1],
|
|
|
|
Kind: vcsKind,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMatchedGroupsFromRegex(regex *regexp.Regexp, remoteURL string) map[string]string {
|
|
|
|
// Get all matching groups.
|
|
|
|
matches := regex.FindAllStringSubmatch(remoteURL, -1)[0]
|
|
|
|
// Get the named groups in our regex.
|
|
|
|
groupNames := regex.SubexpNames()
|
|
|
|
|
|
|
|
groups := map[string]string{}
|
|
|
|
for i, value := range matches {
|
|
|
|
groups[groupNames[i]] = value
|
|
|
|
}
|
|
|
|
|
|
|
|
return groups
|
2018-05-22 01:17:12 +02:00
|
|
|
}
|
|
|
|
|
Initial support for passing URLs to `new` and `up` (#1727)
* Initial support for passing URLs to `new` and `up`
This PR adds initial support for `pulumi new` using Git under the covers
to manage Pulumi templates, providing the same experience as before.
You can now also optionally pass a URL to a Git repository, e.g.
`pulumi new [<url>]`, including subdirectories within the repository,
and arbitrary branches, tags, or commits.
The following commands result in the same behavior from the user's
perspective:
- `pulumi new javascript`
- `pulumi new https://github.com/pulumi/templates/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/master/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/HEAD/templates/javascript`
To specify an arbitrary branch, tag, or commit:
- `pulumi new https://github.com/pulumi/templates/tree/<branch>/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/<tag>/templates/javascript`
- `pulumi new https://github.com/pulumi/templates/tree/<commit>/templates/javascript`
Branches and tags can include '/' separators, and `pulumi` will still
find the right subdirectory.
URLs to Gists are also supported, e.g.:
`pulumi new https://gist.github.com/justinvp/6673959ceb9d2ac5a14c6d536cb871a6`
If the specified subdirectory in the repository does not contain a
`Pulumi.yaml`, it will look for subdirectories within containing
`Pulumi.yaml` files, and prompt the user to choose a template, along the
lines of how `pulumi new` behaves when no template is specified.
The following commands result in the CLI prompting to choose a template:
- `pulumi new`
- `pulumi new https://github.com/pulumi/templates/templates`
- `pulumi new https://github.com/pulumi/templates/tree/master/templates`
- `pulumi new https://github.com/pulumi/templates/tree/HEAD/templates`
Of course, arbitrary branches, tags, or commits can be specified as well:
- `pulumi new https://github.com/pulumi/templates/tree/<branch>/templates`
- `pulumi new https://github.com/pulumi/templates/tree/<tag>/templates`
- `pulumi new https://github.com/pulumi/templates/tree/<commit>/templates`
This PR also includes initial support for passing URLs to `pulumi up`,
providing a streamlined way to deploy installable cloud applications
with Pulumi, without having to manage source code locally before doing
a deployment.
For example, `pulumi up https://github.com/justinvp/aws` can be used to
deploy a sample AWS app. The stack can be updated with different
versions, e.g.
`pulumi up https://github.com/justinvp/aws/tree/v2 -s <stack-to-update>`
Config values can optionally be passed via command line flags, e.g.
`pulumi up https://github.com/justinvp/aws -c aws:region=us-west-2 -c foo:bar=blah`
Gists can also be used, e.g.
`pulumi up https://gist.github.com/justinvp/62fde0463f243fcb49f5a7222e51bc76`
* Fix panic when hitting ^C from "choose template" prompt
* Add description to templates
When running `pulumi new` without specifying a template, include the template description along with the name in the "choose template" display.
```
$ pulumi new
Please choose a template:
aws-go A minimal AWS Go program
aws-javascript A minimal AWS JavaScript program
aws-python A minimal AWS Python program
aws-typescript A minimal AWS TypeScript program
> go A minimal Go program
hello-aws-javascript A simple AWS serverless JavaScript program
javascript A minimal JavaScript program
python A minimal Python program
typescript A minimal TypeScript program
```
* React to changes to the pulumi/templates repo.
We restructured the `pulumi/templates` repo to have all the templates in the root instead of in a `templates` subdirectory, so make the change here to no longer look for templates in `templates`.
This also fixes an issue around using `Depth: 1` that I found while testing this. When a named template is used, we attempt to clone or pull from the `pulumi/templates` repo to `~/.pulumi/templates`. Having it go in this well-known directory allows us to maintain previous behavior around allowing offline use of templates. If we use `Depth: 1` for the initial clone, it will fail when attempting to pull when there are updates to the remote repository. Unfortunately, there's no built-in `--unshallow` support in `go-git` and setting a larger `Depth` doesn't appear to help. There may be a workaround, but for now, if we're cloning the pulumi templates directory to `~/.pulumi/templates`, we won't use `Depth: 1`. For template URLs, we will continue to use `Depth: 1` as we clone those to a temp directory (which gets deleted) that we'll never try to update.
* List available templates in help text
* Address PR Feedback
* Don't show "Installing dependencies" message for `up`
* Fix secrets handling
When prompting for config, if the existing stack value is a secret, keep it a secret and mask the prompt. If the template says it should be secret, make it a secret.
* Fix ${PROJECT} and ${DESCRIPTION} handling for `up`
Templates used with `up` should already have a filled-in project name and description, but if it's a `new`-style template, that has `${PROJECT}` and/or `${DESCRIPTION}`, be helpful and just replace these with better values.
* Fix stack handling
Add a bool `setCurrent` param to `requireStack` to control whether the current stack should be saved in workspace settings. For the `up <url>` case, we don't want to save. Also, split the `up` code into two separate functions: one for the `up <url>` case and another for the normal `up` case where you have workspace in your current directory. While we may be able to combine them back into a single function, right now it's a bit cleaner being separate, even with some small amount of duplication.
* Fix panic due to nil crypter
Lazily get the crypter only if needed inside `promptForConfig`.
* Embellish comment
* Harden isPreconfiguredEmptyStack check
Fix the code to check to make sure the URL specified on the command line matches the URL stored in the `pulumi:template` config value, and that the rest of the config from the stack satisfies the config requirements of the template.
2018-08-11 03:08:16 +02:00
|
|
|
// GitCloneAndCheckoutCommit clones the Git repository and checkouts the specified commit.
|
|
|
|
func GitCloneAndCheckoutCommit(url string, commit plumbing.Hash, path string) error {
|
|
|
|
repo, err := git.PlainClone(path, false, &git.CloneOptions{
|
|
|
|
URL: url,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
w, err := repo.Worktree()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return w.Checkout(&git.CheckoutOptions{
|
|
|
|
Hash: commit,
|
|
|
|
Force: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// GitCloneOrPull clones or updates the specified referenceName (branch or tag) of a Git repository.
|
|
|
|
func GitCloneOrPull(url string, referenceName plumbing.ReferenceName, path string, shallow bool) error {
|
|
|
|
// For shallow clones, use a depth of 1.
|
|
|
|
depth := 0
|
|
|
|
if shallow {
|
|
|
|
depth = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to clone the repo.
|
|
|
|
_, cloneErr := git.PlainClone(path, false, &git.CloneOptions{
|
|
|
|
URL: url,
|
|
|
|
ReferenceName: referenceName,
|
|
|
|
SingleBranch: true,
|
|
|
|
Depth: depth,
|
|
|
|
Tags: git.NoTags,
|
|
|
|
})
|
|
|
|
if cloneErr != nil {
|
|
|
|
// If the repo already exists, open it and pull.
|
|
|
|
if cloneErr == git.ErrRepositoryAlreadyExists {
|
|
|
|
repo, err := git.PlainOpen(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
w, err := repo.Worktree()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = w.Pull(&git.PullOptions{
|
|
|
|
ReferenceName: referenceName,
|
|
|
|
SingleBranch: true,
|
|
|
|
Force: true,
|
|
|
|
}); err != nil && err != git.NoErrAlreadyUpToDate {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return cloneErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseGitRepoURL returns the URL to the Git repository and path from a raw URL.
|
|
|
|
// For example, an input of "https://github.com/pulumi/templates/templates/javascript" returns
|
|
|
|
// "https://github.com/pulumi/templates.git" and "templates/javascript".
|
|
|
|
func ParseGitRepoURL(rawurl string) (string, string, error) {
|
|
|
|
u, err := url.Parse(rawurl)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if u.Scheme != "https" {
|
|
|
|
return "", "", errors.New("invalid URL scheme")
|
|
|
|
}
|
|
|
|
|
|
|
|
path := strings.TrimPrefix(u.Path, "/")
|
|
|
|
|
|
|
|
// Special case Gists.
|
|
|
|
if u.Hostname() == "gist.github.com" {
|
|
|
|
// We currently accept Gist URLs in the form: https://gist.github.com/owner/id.
|
|
|
|
// We may want to consider supporting https://gist.github.com/id at some point,
|
|
|
|
// as well as arbitrary revisions, e.g. https://gist.github.com/owner/id/commit.
|
|
|
|
path = strings.TrimSuffix(path, "/")
|
|
|
|
paths := strings.Split(path, "/")
|
|
|
|
if len(paths) != 2 {
|
|
|
|
return "", "", errors.New("invalid Gist URL")
|
|
|
|
}
|
|
|
|
|
|
|
|
owner := paths[0]
|
|
|
|
if owner == "" {
|
|
|
|
return "", "", errors.New("invalid Gist URL; no owner")
|
|
|
|
}
|
|
|
|
|
|
|
|
id := paths[1]
|
|
|
|
if id == "" {
|
|
|
|
return "", "", errors.New("invalid Gist URL; no id")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.HasSuffix(id, ".git") {
|
|
|
|
id = id + ".git"
|
|
|
|
}
|
|
|
|
|
|
|
|
resultURL := u.Scheme + "://" + u.Host + "/" + id
|
|
|
|
return resultURL, "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
paths := strings.Split(path, "/")
|
|
|
|
if len(paths) < 2 {
|
|
|
|
return "", "", errors.New("invalid Git URL")
|
|
|
|
}
|
|
|
|
|
|
|
|
owner := paths[0]
|
|
|
|
if owner == "" {
|
|
|
|
return "", "", errors.New("invalid Git URL; no owner")
|
|
|
|
}
|
|
|
|
|
|
|
|
repo := paths[1]
|
|
|
|
if repo == "" {
|
|
|
|
return "", "", errors.New("invalid Git URL; no repository")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.HasSuffix(repo, ".git") {
|
|
|
|
repo = repo + ".git"
|
|
|
|
}
|
|
|
|
|
|
|
|
resultURL := u.Scheme + "://" + u.Host + "/" + owner + "/" + repo
|
|
|
|
resultPath := strings.TrimSuffix(strings.Join(paths[2:], "/"), "/")
|
|
|
|
return resultURL, resultPath, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var gitSHARegex = regexp.MustCompile(`^[0-9a-fA-F]{40}$`)
|
|
|
|
|
|
|
|
// GetGitReferenceNameOrHashAndSubDirectory returns the reference name or hash, and sub directory path.
|
|
|
|
// The sub directory path always uses "/" as the separator.
|
|
|
|
func GetGitReferenceNameOrHashAndSubDirectory(url string, urlPath string) (
|
|
|
|
plumbing.ReferenceName, plumbing.Hash, string, error) {
|
|
|
|
|
|
|
|
// If path is empty, use HEAD.
|
|
|
|
if urlPath == "" {
|
|
|
|
return plumbing.HEAD, plumbing.ZeroHash, "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trim leading/trailing separator(s).
|
|
|
|
urlPath = strings.TrimPrefix(urlPath, "/")
|
|
|
|
urlPath = strings.TrimSuffix(urlPath, "/")
|
|
|
|
|
|
|
|
paths := strings.Split(urlPath, "/")
|
|
|
|
|
|
|
|
// Ensure the path components are not "." or "..".
|
|
|
|
for _, path := range paths {
|
|
|
|
if path == "." || path == ".." {
|
|
|
|
return "", plumbing.ZeroHash, "", errors.New("invalid Git URL")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if paths[0] == "tree" {
|
|
|
|
if len(paths) >= 2 {
|
|
|
|
// If it looks like a SHA, use that.
|
|
|
|
if gitSHARegex.MatchString(paths[1]) {
|
|
|
|
return "", plumbing.NewHash(paths[1]), strings.Join(paths[2:], "/"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, try matching based on the repo's refs.
|
|
|
|
|
|
|
|
// Get the list of refs sorted by length.
|
|
|
|
refs, err := GitListBranchesAndTags(url)
|
|
|
|
if err != nil {
|
|
|
|
return "", plumbing.ZeroHash, "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to find the matching ref, checking the longest names first, so
|
|
|
|
// if there are multiple refs that would match, we pick the longest.
|
|
|
|
path := strings.Join(paths[1:], "/") + "/"
|
|
|
|
for _, ref := range refs {
|
|
|
|
shortName := ref.Short()
|
|
|
|
prefix := shortName + "/"
|
|
|
|
if strings.HasPrefix(path, prefix) {
|
|
|
|
subDir := strings.TrimPrefix(path, prefix)
|
|
|
|
return ref, plumbing.ZeroHash, strings.TrimSuffix(subDir, "/"), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there aren't any path components after "tree", it's an error.
|
|
|
|
return "", plumbing.ZeroHash, "", errors.New("invalid Git URL")
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there wasn't "tree" in the path, just use HEAD.
|
|
|
|
return plumbing.HEAD, plumbing.ZeroHash, strings.Join(paths, "/"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GitListBranchesAndTags fetches a remote Git repository's branch and tag references
|
|
|
|
// (including HEAD), sorted by the length of the short name descending.
|
|
|
|
func GitListBranchesAndTags(url string) ([]plumbing.ReferenceName, error) {
|
|
|
|
// We're only listing the references, so just use in-memory storage.
|
|
|
|
repo, err := git.Init(memory.NewStorage(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
remote, err := repo.CreateRemote(&config.RemoteConfig{
|
|
|
|
Name: "origin",
|
|
|
|
URLs: []string{url},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
refs, err := remote.List(&git.ListOptions{})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var results []plumbing.ReferenceName
|
|
|
|
for _, ref := range refs {
|
|
|
|
name := ref.Name()
|
|
|
|
if name == plumbing.HEAD || name.IsBranch() || name.IsTag() {
|
|
|
|
results = append(results, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Sort(byShortNameLengthDesc(results))
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type byShortNameLengthDesc []plumbing.ReferenceName
|
|
|
|
|
|
|
|
func (r byShortNameLengthDesc) Len() int { return len(r) }
|
|
|
|
func (r byShortNameLengthDesc) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
|
|
|
func (r byShortNameLengthDesc) Less(i, j int) bool {
|
|
|
|
return len(r[j].Short()) < len(r[i].Short())
|
|
|
|
}
|