Skip to content

Commit

Permalink
fix: Adds support for azure devops based branch, commit and subpath (l…
Browse files Browse the repository at this point in the history
…oft-sh#1231)

qualifiers on the repository string

background: Azure DevOps doesn't follow the normal $HOSTER/$ORG/$REPO
syntax but uses [email protected]/$ORG/$PROJECT/_git/$REPO. Prior to
this change this would throw off our repository normalization.

With this fix we can also get rid of the deprecated `--git-branch` and
`--git-commit` flags which have been added initially only to support Azure DevOps
  • Loading branch information
pascalbreuninger authored Aug 23, 2024
1 parent 6812c57 commit 1d4e463
Show file tree
Hide file tree
Showing 15 changed files with 73 additions and 146 deletions.
4 changes: 0 additions & 4 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ func NewBuildCmd(flags *flags.GlobalFlags) *cobra.Command {
cmd.DevContainerPath,
sshConfigPath,
nil,
cmd.GitBranch,
cmd.GitCommit,
cmd.UID,
false,
log.Default,
Expand Down Expand Up @@ -115,8 +113,6 @@ func NewBuildCmd(flags *flags.GlobalFlags) *cobra.Command {
buildCmd.Flags().StringVar(&cmd.Repository, "repository", "", "The repository to push to")
buildCmd.Flags().StringSliceVar(&cmd.Platform, "platform", []string{}, "Set target platform for build")
buildCmd.Flags().BoolVar(&cmd.SkipPush, "skip-push", false, "If true will not push the image to the repository, useful for testing")
buildCmd.Flags().StringVar(&cmd.GitBranch, "git-branch", "", "The git branch to use")
buildCmd.Flags().StringVar(&cmd.GitCommit, "git-commit", "", "The git commit SHA to use")
buildCmd.Flags().Var(&cmd.GitCloneStrategy, "git-clone-strategy", "The git clone strategy DevPod uses to checkout git based workspaces. Can be full (default), blobless, treeless or shallow")

// TESTING
Expand Down
4 changes: 0 additions & 4 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,6 @@ func NewUpCmd(flags *flags.GlobalFlags) *cobra.Command {
cmd.DevContainerPath,
cmd.SSHConfigPath,
source,
cmd.GitBranch,
cmd.GitCommit,
cmd.UID,
true,
logger,
Expand Down Expand Up @@ -161,8 +159,6 @@ func NewUpCmd(flags *flags.GlobalFlags) *cobra.Command {
upCmd.Flags().StringVar(&cmd.Machine, "machine", "", "The machine to use for this workspace. The machine needs to exist beforehand or the command will fail. If the workspace already exists, this option has no effect")
upCmd.Flags().StringVar(&cmd.IDE, "ide", "", "The IDE to open the workspace in. If empty will use vscode locally or in browser")
upCmd.Flags().BoolVar(&cmd.OpenIDE, "open-ide", true, "If this is false and an IDE is configured, DevPod will only install the IDE server backend, but not open it")
upCmd.Flags().StringVar(&cmd.GitBranch, "git-branch", "", "The git branch to use")
upCmd.Flags().StringVar(&cmd.GitCommit, "git-commit", "", "The git commit SHA to use")
upCmd.Flags().Var(&cmd.GitCloneStrategy, "git-clone-strategy", "The git clone strategy DevPod uses to checkout git based workspaces. Can be full (default), blobless, treeless or shallow")
upCmd.Flags().StringVar(&cmd.GitSSHSigningKey, "git-ssh-signing-key", "", "The ssh key to use when signing git commits. Used to explicitly setup DevPod's ssh signature forwarding with given key. Should be same format as value of `git config user.signingkey`")
upCmd.Flags().StringVar(&cmd.FallbackImage, "fallback-image", "", "The fallback image to use if no devcontainer configuration has been detected")
Expand Down
2 changes: 0 additions & 2 deletions desktop/src/client/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,5 @@ export const DEVPOD_FLAG_WORKSPACE_ID = "--workspace-id"
export const DEVPOD_FLAG_WORKSPACE_UID = "--workspace-uid"
export const DEVPOD_FLAG_WORKSPACE_PROJECT = "--workspace-project"
export const DEVPOD_FLAG_LOGIN = "--login"
export const DEVPOD_FLAG_GIT_BRANCH = "--git-branch"
export const DEVPOD_FLAG_GIT_COMMIT = "--git-commit"

export const DEVPOD_UI_ENV_VAR = "DEVPOD_UI"
2 changes: 1 addition & 1 deletion desktop/src/client/workspaces/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class WorkspacesClient implements TDebuggable {

public setSSHKeyPath(sshKeyPath: string): void {
WorkspaceCommands.ADDITIONAL_FLAGS =
WorkspaceCommands.ADDITIONAL_FLAGS + " --git-ssh-signing-key="+sshKeyPath
WorkspaceCommands.ADDITIONAL_FLAGS + " --git-ssh-signing-key=" + sshKeyPath
}

public async listAll(): Promise<Result<readonly TWorkspaceWithoutStatus[]>> {
Expand Down
14 changes: 0 additions & 14 deletions desktop/src/client/workspaces/workspaceCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import {
DEVPOD_FLAG_DEBUG,
DEVPOD_FLAG_DEVCONTAINER_PATH,
DEVPOD_FLAG_FORCE,
DEVPOD_FLAG_GIT_BRANCH,
DEVPOD_FLAG_GIT_COMMIT,
DEVPOD_FLAG_ID,
DEVPOD_FLAG_IDE,
DEVPOD_FLAG_JSON_LOG_OUTPUT,
Expand Down Expand Up @@ -134,16 +132,6 @@ export class WorkspaceCommands {
? toMultipleFlagArg(WorkspaceCommands.ADDITIONAL_FLAGS)
: []

const maybeGitBranch = config.sourceConfig?.gitBranch
const gitBranchFlag = exists(maybeGitBranch)
? [toFlagArg(DEVPOD_FLAG_GIT_BRANCH, maybeGitBranch)]
: []

const maybeGitCommit = config.sourceConfig?.gitCommit
const gitCommitFlag = exists(maybeGitCommit)
? [toFlagArg(DEVPOD_FLAG_GIT_COMMIT, maybeGitCommit)]
: []

return WorkspaceCommands.newCommand([
DEVPOD_COMMAND_UP,
identifier,
Expand All @@ -153,8 +141,6 @@ export class WorkspaceCommands {
...maybeProviderFlag,
...maybePrebuildRepositories,
...maybeDevcontainerPath,
...gitBranchFlag,
...gitCommitFlag,
...additionalFlags,
...maybeProviderOptionsFlag,
DEVPOD_FLAG_JSON_LOG_OUTPUT,
Expand Down
2 changes: 0 additions & 2 deletions desktop/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,6 @@ export type TWorkspaceStartConfig = Readonly<{
sourceConfig?: Readonly<{
source: string
type?: TWorkspaceSourceType
gitBranch: string | undefined
gitCommit: string | undefined
}>
}>
export const SUPPORTED_IDES = [
Expand Down
50 changes: 1 addition & 49 deletions desktop/src/views/Workspaces/CreateWorkspace/CreateWorkspace.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CollapsibleSection, ExampleCard, Form, IDEIcon, WarningMessageBox } from "@/components"
import { ExampleCard, Form, IDEIcon, WarningMessageBox } from "@/components"
import {
Box,
Button,
Expand Down Expand Up @@ -64,8 +64,6 @@ export function CreateWorkspace() {
defaultIDE,
workspaceSource,
devcontainerPath,
gitBranch,
gitCommit,
}: TCreateWorkspaceArgs) => {
const actionID = workspace.create({
id: workspaceID,
Expand All @@ -76,8 +74,6 @@ export function CreateWorkspace() {
sourceConfig: {
source: workspaceSource,
type: sourceType,
gitBranch,
gitCommit,
},
})

Expand Down Expand Up @@ -108,8 +104,6 @@ export function CreateWorkspace() {
idError,
prebuildRepositoryError,
devcontainerPathError,
gitBranchError,
gitCommitError,
} = useFormErrors(Object.values(FieldName), formState)

const providerOptions = useMemo<TSelectProviderOptions>(() => {
Expand Down Expand Up @@ -415,48 +409,6 @@ export function CreateWorkspace() {
{/* placholder box */}
<Box width={"full"} />
</HStack>

<CollapsibleSection title="Git Options (deprecated)" showIcon>
<Text color="gray.600" fontSize="sm" mb="4" mt="-2">
Force a repository to use a git branch or a commit in cases where the
workspace-source@branch syntax isn&apos;t compatible, like Azure DevOps.
</Text>

<HStack spacing="8" alignItems={"top"} width={"100%"} justifyContent={"start"}>
<FormControl isInvalid={exists(gitBranchError)}>
<FormLabel>Git Branch</FormLabel>
<Input
spellCheck={false}
placeholder="main"
type="text"
{...register(FieldName.GIT_BRANCH)}
/>
{exists(gitBranchError) ? (
<FormErrorMessage>{gitBranchError.message ?? "Error"}</FormErrorMessage>
) : (
<FormHelperText>
Optionally specify the branch name for this workspace.
</FormHelperText>
)}
</FormControl>
<FormControl isInvalid={exists(gitCommitError)}>
<FormLabel>Git Commit</FormLabel>
<Input
spellCheck={false}
placeholder="SHA256"
type="text"
{...register(FieldName.GIT_COMMIT)}
/>
{exists(gitCommitError) ? (
<FormErrorMessage>{gitCommitError.message ?? "Error"}</FormErrorMessage>
) : (
<FormHelperText>
Optionally specify the commit SHA256 for this workspace.
</FormHelperText>
)}
</FormControl>
</HStack>
</CollapsibleSection>
</VStack>

<HStack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ import { FieldName, TFormValues } from "./types"
import { TWorkspaceSourceType } from "@/types"

// WARN: Make sure these match the regexes in /pkg/git/git.go
const GIT_REPOSITORY_REGEX = new RegExp("^([^@]*(?:git@)?@?[^@/]+\\/[^@/]+(\\/?[^@/]+)){2,}")
const BRANCH_REGEX = new RegExp(
"^([^@]*(?:git@)?@?[^@/]+\\/[^@/]+\\/?[^@/]+)@([a-zA-Z0-9\\./\\-\\_]+)$"
)
const COMMIT_REGEX = new RegExp("^([^@]*(?:git@)?@?[^@/]+/[^@]+)@sha256:([a-zA-Z0-9]+)$")
const PR_REGEX = new RegExp("^([^@]*(?:git@)?@?[^@/]+\\/[^@]+)@pull\\/([0-9]+)\\/head$")
const SUBPATH_REGEX = new RegExp(
"^([^@]*(?:git@)?@?[^@/]+\\/[^@]+)@subpath:([a-zA-Z0-9\\./\\-\\_]+)$"
)
const GIT_REPOSITORY_PATTERN =
"((?:(?:https?|git|ssh)://)?(?:[^@/\\n]+@)?(?:[^:/\\n]+)(?:[:/][^/\\n]+)+(?:\\.git)?)"
const GIT_REPOSITORY_REGEX = new RegExp(GIT_REPOSITORY_PATTERN)
const BRANCH_REGEX = new RegExp(`^${GIT_REPOSITORY_PATTERN}@([a-zA-Z0-9\\./\\-\\_]+)$`)
const COMMIT_REGEX = new RegExp(`^${GIT_REPOSITORY_PATTERN}@sha256:([a-zA-Z0-9]+)$`)
const PR_REGEX = new RegExp(`^${GIT_REPOSITORY_PATTERN}@pull\\/([0-9]+)\\/head$`)
const SUBPATH_REGEX = new RegExp(`^${GIT_REPOSITORY_PATTERN}@subpath:([a-zA-Z0-9\\./\\-\\_]+)$`)

const AdvancedGitSetting = {
BRANCH: "branch",
Expand Down
6 changes: 0 additions & 6 deletions desktop/src/views/Workspaces/CreateWorkspace/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ export const FieldName = {
PROVIDER: "provider",
PREBUILD_REPOSITORY: "prebuildRepository",
DEVCONTAINER_PATH: "devcontainerPath",
GIT_BRANCH: "gitBranch",
GIT_COMMIT: "gitCommit",
} as const

export type TFormValues = {
Expand All @@ -20,8 +18,6 @@ export type TFormValues = {
[FieldName.ID]: string
[FieldName.PREBUILD_REPOSITORY]: string
[FieldName.DEVCONTAINER_PATH]?: string
[FieldName.GIT_BRANCH]?: string
[FieldName.GIT_COMMIT]?: string
}

export type TCreateWorkspaceSearchParams = ReturnType<
Expand All @@ -34,8 +30,6 @@ export type TCreateWorkspaceArgs = Readonly<{
defaultIDE: string
workspaceSource: string
devcontainerPath: string | undefined
gitBranch: string | undefined
gitCommit: string | undefined
}>
export type TSelectProviderOptions = Readonly<{
installed: readonly TNamedProvider[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,6 @@ export function useCreateWorkspaceForm(onCreateWorkspace: (args: TCreateWorkspac
const prebuildRepositories = data[FieldName.PREBUILD_REPOSITORY]
? [data[FieldName.PREBUILD_REPOSITORY]]
: []
const gitBranch = data[FieldName.GIT_BRANCH]
const gitCommit = data[FieldName.GIT_COMMIT]

onCreateWorkspace({
workspaceID,
Expand All @@ -226,8 +224,6 @@ export function useCreateWorkspaceForm(onCreateWorkspace: (args: TCreateWorkspac
defaultIDE,
workspaceSource,
devcontainerPath: maybeDevcontainerPath,
gitBranch,
gitCommit,
})
})(event),
[
Expand Down
10 changes: 5 additions & 5 deletions e2e/tests/up/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ var _ = DevPodDescribe("devpod up test suite", func() {
framework.ExpectNoError(err)
})

err = f.DevPodUp(ctx, "https://github.com/loft-sh/examples/@subpath:/devpod/jupyter-notebook-hello-world")
err = f.DevPodUp(ctx, "https://github.com/loft-sh/examples@subpath:/devpod/jupyter-notebook-hello-world")
framework.ExpectNoError(err)

id := "subpath--devpod-jupyter-notebook-hello-world"
Expand Down Expand Up @@ -467,14 +467,14 @@ var _ = DevPodDescribe("devpod up test suite", func() {
})

id := "subpath--devpod-jupyter-notebook-hello-world"
err = f.DevPodUp(ctx, "https://github.com/loft-sh/examples/@subpath:/devpod/jupyter-notebook-hello-world")
err = f.DevPodUp(ctx, "https://github.com/loft-sh/examples@subpath:/devpod/jupyter-notebook-hello-world")
framework.ExpectNoError(err)

_, err = f.DevPodSSH(ctx, id, "pwd")
framework.ExpectNoError(err)

// recreate
err = f.DevPodUpRecreate(ctx, "https://github.com/loft-sh/examples/@subpath:/devpod/jupyter-notebook-hello-world")
err = f.DevPodUpRecreate(ctx, "https://github.com/loft-sh/examples@subpath:/devpod/jupyter-notebook-hello-world")
framework.ExpectNoError(err)

_, err = f.DevPodSSH(ctx, id, "pwd")
Expand Down Expand Up @@ -503,7 +503,7 @@ var _ = DevPodDescribe("devpod up test suite", func() {
})

id := "subpath--devpod-jupyter-notebook-hello-world"
err = f.DevPodUp(ctx, "https://github.com/loft-sh/examples/@subpath:/devpod/jupyter-notebook-hello-world")
err = f.DevPodUp(ctx, "https://github.com/loft-sh/examples@subpath:/devpod/jupyter-notebook-hello-world")
framework.ExpectNoError(err)

// create files in root and in workspace, after create we expect data to still be there
Expand All @@ -513,7 +513,7 @@ var _ = DevPodDescribe("devpod up test suite", func() {
framework.ExpectNoError(err)

// reset
err = f.DevPodUpReset(ctx, "https://github.com/loft-sh/examples/@subpath:/devpod/jupyter-notebook-hello-world")
err = f.DevPodUpReset(ctx, "https://github.com/loft-sh/examples@subpath:/devpod/jupyter-notebook-hello-world")
framework.ExpectNoError(err)

// this should fail! because --reset should trigger a new git clone
Expand Down
28 changes: 15 additions & 13 deletions pkg/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,14 @@ const (

// WARN: Make sure this matches the regex in /desktop/src/views/Workspaces/CreateWorkspace/CreateWorkspaceInput.tsx!
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\./\-\_]+)$`)
// Updated regex pattern to support SSH-style Git URLs
repoBaseRegEx = `((?:(?:https?|git|ssh):\/\/)?(?:[^@\/\n]+@)?(?:[^:\/\n]+)(?:[:\/][^\/\n]+)+(?:\.git)?)`
branchRegEx = regexp.MustCompile(`^` + repoBaseRegEx + `@([a-zA-Z0-9\./\-\_]+)$`)
commitRegEx = regexp.MustCompile(`^` + repoBaseRegEx + regexp.QuoteMeta(CommitDelimiter) + `([a-zA-Z0-9]+)$`)
prReferenceRegEx = regexp.MustCompile(`^` + repoBaseRegEx + `@(` + PullRequestReference + `)$`)
subPathRegEx = regexp.MustCompile(`^` + repoBaseRegEx + regexp.QuoteMeta(SubPathDelimiter) + `([a-zA-Z0-9\./\-\_]+)$`)
)

func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
cmd := exec.CommandContext(ctx, "git", args...)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0")
cmd.Env = append(cmd.Env, "GIT_SSH_COMMAND=ssh -oBatchMode=yes -oStrictHostKeyChecking=no")
return cmd
}

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 @@ -54,7 +48,7 @@ func NormalizeRepository(str string) (string, string, string, string, string) {
subpath := ""
if match := subPathRegEx.FindStringSubmatch(str); match != nil {
str = match[1]
subpath = match[2]
subpath = strings.TrimSuffix(match[2], "/")
}

// resolve branch
Expand All @@ -74,6 +68,14 @@ func NormalizeRepository(str string) (string, string, string, string, string) {
return str, prReference, branch, commit, subpath
}

func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
cmd := exec.CommandContext(ctx, "git", args...)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0")
cmd.Env = append(cmd.Env, "GIT_SSH_COMMAND=ssh -oBatchMode=yes -oStrictHostKeyChecking=no")
return cmd
}

func PingRepository(str string) bool {
if !command.Exists("git") {
return false
Expand Down
Loading

0 comments on commit 1d4e463

Please sign in to comment.