Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: start workspace in a subpath #898

Merged
merged 9 commits into from
Feb 15, 2024
Merged
2 changes: 1 addition & 1 deletion cmd/agent/workspace/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ func CloneRepository(ctx context.Context, local bool, workspaceDir string, sourc
}

// run git command
gitInfo := git.NewGitInfo(source.GitRepository, source.GitBranch, source.GitCommit, source.GitPRReference)
gitInfo := git.NewGitInfo(source.GitRepository, source.GitBranch, source.GitCommit, source.GitPRReference, source.GitSubPath)
err := git.CloneRepository(ctx, gitInfo, workspaceDir, helper, false, writer, log)
if err != nil {
return errors.Wrap(err, "clone repository")
Expand Down
4 changes: 2 additions & 2 deletions cmd/helper/get_workspace_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ func findDevcontainerFiles(ctx context.Context, rawSource, tmpDirPath string, ma
}

// git repo
gitRepository, gitPRReference, gitBranch, gitCommit := git.NormalizeRepository(rawSource)
gitRepository, gitPRReference, gitBranch, gitCommit, gitSubDir := git.NormalizeRepository(rawSource)
if strings.HasSuffix(rawSource, ".git") || git.PingRepository(gitRepository) {
log.Debug("Git repository detected")
result.IsGitRepository = true

gitInfo := git.NewGitInfo(gitRepository, gitBranch, gitCommit, gitPRReference)
gitInfo := git.NewGitInfo(gitRepository, gitBranch, gitCommit, gitPRReference, gitSubDir)
log.Debugf("Cloning git repository into %s", tmpDirPath)
err := git.CloneRepository(ctx, gitInfo, tmpDirPath, "", true, log.Writer(logrus.DebugLevel, false), log)
if err != nil {
Expand Down
8 changes: 6 additions & 2 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"

Expand Down Expand Up @@ -175,12 +176,16 @@ func (cmd *UpCmd) Run(
workdir = result.MergedConfig.WorkspaceFolder
}

if client.WorkspaceConfig().Source.GitSubPath != "" {
result.SubstitutionContext.ContainerWorkspaceFolder = filepath.Join(result.SubstitutionContext.ContainerWorkspaceFolder, client.WorkspaceConfig().Source.GitSubPath)
workdir = result.SubstitutionContext.ContainerWorkspaceFolder
}

// configure container ssh
if cmd.ConfigureSSH {
err = configureSSH(devPodConfig, client, cmd.SSHConfigPath, user, workdir,
cmd.GPGAgentForwarding ||
devPodConfig.ContextOption(config.ContextOptionGPGAgentForwarding) == "true")

if err != nil {
return err
}
Expand Down Expand Up @@ -868,7 +873,6 @@ func performGpgForwarding(
"--log-output=raw",
"--command", "sleep infinity",
).Run()

if err != nil {
log.Error("failure in forwarding gpg-agent")
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/devcontainer/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,16 @@ func (r *runner) prepare(
} else {
var err error

localWorkspaceFolder := r.LocalWorkspaceFolder
// if a subpath is specified, let's move to it

if r.WorkspaceConfig.Workspace.Source.GitSubPath != "" {
localWorkspaceFolder = filepath.Join(r.LocalWorkspaceFolder, r.WorkspaceConfig.Workspace.Source.GitSubPath)
}

// parse the devcontainer json
rawParsedConfig, err = config.ParseDevContainerJSON(
r.LocalWorkspaceFolder,
localWorkspaceFolder,
r.WorkspaceConfig.Workspace.DevContainerPath,
)

Expand Down
23 changes: 17 additions & 6 deletions pkg/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import (
const (
CommitDelimiter string = "@sha256:"
PullRequestReference string = "pull/([0-9]+)/head"
SubPathDelimiter string = "@subpath:"
)

var (
branchRegEx = regexp.MustCompile(`^([^@]*(?:git@)?[^@/]+/[^@/]+/?[^@/]+)@([a-zA-Z0-9\./\-\_]+)$`)
commitRegEx = regexp.MustCompile(`^([^@]*(?:git@)?[^@/]+/[^@]+)` + regexp.QuoteMeta(CommitDelimiter) + `([a-zA-Z0-9]+)$`)
prReferenceRegEx = regexp.MustCompile(`^([^@]*(?:git@)?[^@/]+/[^@]+)@(` + PullRequestReference + `)$`)
subPathRegEx = regexp.MustCompile(`^([^@]*(?:git@)?[^@/]+/[^@]+)` + regexp.QuoteMeta(SubPathDelimiter) + `([a-zA-Z0-9\./\-\_]+)$`)
)

func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
Expand All @@ -33,7 +35,7 @@ func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
return cmd
}

func NormalizeRepository(str string) (string, string, string, string) {
func NormalizeRepository(str string) (string, string, string, string, string) {
if !strings.HasPrefix(str, "ssh://") && !strings.HasPrefix(str, "git@") && !strings.HasPrefix(str, "http://") && !strings.HasPrefix(str, "https://") {
str = "https://" + str
}
Expand All @@ -44,7 +46,14 @@ func NormalizeRepository(str string) (string, string, string, string) {
str = match[1]
prReference = match[2]

return str, prReference, "", ""
return str, prReference, "", "", ""
}

// resolve subpath
subpath := ""
if match := subPathRegEx.FindStringSubmatch(str); match != nil {
str = match[1]
subpath = match[2]
}

// resolve branch
Expand All @@ -61,7 +70,7 @@ func NormalizeRepository(str string) (string, string, string, string) {
commit = match[2]
}

return str, prReference, branch, commit
return str, prReference, branch, commit, subpath
}

func PingRepository(str string) bool {
Expand All @@ -86,20 +95,22 @@ type GitInfo struct {
Branch string
Commit string
PR string
SubPath string
}

func NewGitInfo(repository, branch, commit, pr string) *GitInfo {
func NewGitInfo(repository, branch, commit, pr, subpath string) *GitInfo {
return &GitInfo{
Repository: repository,
Branch: branch,
Commit: commit,
PR: pr,
SubPath: subpath,
}
}

func NormalizeRepositoryGitInfo(str string) *GitInfo {
repository, pr, branch, commit := NormalizeRepository(str)
return NewGitInfo(repository, branch, commit, pr)
repository, pr, branch, commit, subpath := NormalizeRepository(str)
return NewGitInfo(repository, branch, commit, pr, subpath)
}

func CloneRepository(ctx context.Context, gitInfo *GitInfo, targetDir string, helper string, bare bool, writer io.Writer, log log.Logger) error {
Expand Down
27 changes: 26 additions & 1 deletion pkg/git/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type testCaseNormalizeRepository struct {
expectedRepo string
expectedBranch string
expectedCommit string
expectedSubpath string
}

type testCaseGetBranchNameForPR struct {
Expand All @@ -28,113 +29,137 @@ func TestNormalizeRepository(t *testing.T) {
expectedPRReference: "",
expectedBranch: "",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "ssh://git@github.com/loft-sh/devpod.git",
expectedRepo: "ssh://git@github.com/loft-sh/devpod.git",
expectedPRReference: "",
expectedBranch: "",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "git@github.com/loft-sh/devpod-without-branch.git",
expectedRepo: "git@github.com/loft-sh/devpod-without-branch.git",
expectedPRReference: "",
expectedBranch: "",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "https://github.com/loft-sh/devpod.git",
expectedRepo: "https://github.com/loft-sh/devpod.git",
expectedPRReference: "",
expectedBranch: "",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "github.com/loft-sh/devpod.git",
expectedRepo: "https://github.com/loft-sh/devpod.git",
expectedPRReference: "",
expectedBranch: "",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "github.com/loft-sh/devpod.git@test-branch",
expectedRepo: "https://github.com/loft-sh/devpod.git",
expectedPRReference: "",
expectedBranch: "test-branch",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "git@github.com/loft-sh/devpod-with-branch.git@test-branch",
expectedRepo: "git@github.com/loft-sh/devpod-with-branch.git",
expectedPRReference: "",
expectedBranch: "test-branch",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "git@github.com/loft-sh/devpod-with-branch.git@test_branch",
expectedRepo: "git@github.com/loft-sh/devpod-with-branch.git",
expectedPRReference: "",
expectedBranch: "test_branch",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "ssh://git@github.com/loft-sh/devpod.git@test_branch",
expectedRepo: "ssh://git@github.com/loft-sh/devpod.git",
expectedPRReference: "",
expectedBranch: "test_branch",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "github.com/loft-sh/devpod-without-protocol-with-slash.git@user/branch",
expectedRepo: "https://github.com/loft-sh/devpod-without-protocol-with-slash.git",
expectedPRReference: "",
expectedBranch: "user/branch",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "git@github.com/loft-sh/devpod-with-slash.git@user/branch",
expectedRepo: "git@github.com/loft-sh/devpod-with-slash.git",
expectedPRReference: "",
expectedBranch: "user/branch",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "github.com/loft-sh/devpod.git@sha256:905ffb0",
expectedRepo: "https://github.com/loft-sh/devpod.git",
expectedPRReference: "",
expectedBranch: "",
expectedCommit: "905ffb0",
expectedSubpath: "",
},
{
in: "git@github.com:loft-sh/devpod.git@sha256:905ffb0",
expectedRepo: "git@github.com:loft-sh/devpod.git",
expectedPRReference: "",
expectedBranch: "",
expectedCommit: "905ffb0",
expectedSubpath: "",
},
{
in: "github.com/loft-sh/devpod.git@pull/996/head",
expectedRepo: "https://github.com/loft-sh/devpod.git",
expectedPRReference: "pull/996/head",
expectedBranch: "",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "git@github.com:loft-sh/devpod.git@pull/996/head",
expectedRepo: "git@github.com:loft-sh/devpod.git",
expectedPRReference: "pull/996/head",
expectedBranch: "",
expectedCommit: "",
expectedSubpath: "",
},
{
in: "github.com/loft-sh/devpod-without-protocol-with-slash.git@subpath:/test/path",
expectedRepo: "https://github.com/loft-sh/devpod-without-protocol-with-slash.git",
expectedPRReference: "",
expectedBranch: "",
expectedCommit: "",
expectedSubpath: "/test/path",
},
}

for _, testCase := range testCases {
outRepo, outPRReference, outBranch, outCommit := NormalizeRepository(testCase.in)
outRepo, outPRReference, outBranch, outCommit, outSubpath := NormalizeRepository(testCase.in)
assert.Check(t, cmp.Equal(testCase.expectedRepo, outRepo))
assert.Check(t, cmp.Equal(testCase.expectedPRReference, outPRReference))
assert.Check(t, cmp.Equal(testCase.expectedBranch, outBranch))
assert.Check(t, cmp.Equal(testCase.expectedCommit, outCommit))
assert.Check(t, cmp.Equal(testCase.expectedSubpath, outSubpath))
}
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/provider/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ type WorkspaceSource struct {
// GitPRReference is the pull request reference to checkout
GitPRReference string `json:"gitPRReference,omitempty"`

// GitSubPath is the subpath in the repo to use
GitSubPath string `json:"gitSubDir,omitempty"`

// LocalFolder is the local folder to use
LocalFolder string `json:"localFolder,omitempty"`

Expand Down Expand Up @@ -216,12 +219,13 @@ func (w WorkspaceSource) String() string {

func ParseWorkspaceSource(source string) *WorkspaceSource {
if strings.HasPrefix(source, WorkspaceSourceGit) {
gitRepo, gitPRReference, gitBranch, gitCommit := git.NormalizeRepository(strings.TrimPrefix(source, WorkspaceSourceGit))
gitRepo, gitPRReference, gitBranch, gitCommit, gitSubdir := git.NormalizeRepository(strings.TrimPrefix(source, WorkspaceSourceGit))
return &WorkspaceSource{
GitRepository: gitRepo,
GitPRReference: gitPRReference,
GitBranch: gitBranch,
GitCommit: gitCommit,
GitSubPath: gitSubdir,
}
} else if strings.HasPrefix(source, WorkspaceSourceLocal) {
return &WorkspaceSource{
Expand Down
9 changes: 6 additions & 3 deletions pkg/workspace/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,14 +450,15 @@ func resolve(
}

// is git?
gitRepository, gitPRReference, gitBranch, gitCommit := git.NormalizeRepository(name)
gitRepository, gitPRReference, gitBranch, gitCommit, gitSubdir := git.NormalizeRepository(name)
if strings.HasSuffix(name, ".git") || git.PingRepository(gitRepository) {
workspace.Picture = getProjectImage(name)
workspace.Source = provider2.WorkspaceSource{
GitRepository: gitRepository,
GitPRReference: gitPRReference,
GitBranch: gitBranch,
GitCommit: gitCommit,
GitSubPath: gitSubdir,
}
return workspace, nil
}
Expand Down Expand Up @@ -524,8 +525,10 @@ func getProjectImage(link string) string {
return ""
}

var workspaceIDRegEx1 = regexp.MustCompile(`[^\w\-]`)
var workspaceIDRegEx2 = regexp.MustCompile(`[^0-9a-z\-]+`)
var (
workspaceIDRegEx1 = regexp.MustCompile(`[^\w\-]`)
workspaceIDRegEx2 = regexp.MustCompile(`[^0-9a-z\-]+`)
)

func ToID(str string) string {
str = strings.ToLower(filepath.ToSlash(str))
Expand Down
Loading