Skip to content

Commit f65a919

Browse files
committed
[jb-remote] gp cli support
1 parent 587fc4e commit f65a919

File tree

18 files changed

+1060
-6
lines changed

18 files changed

+1060
-6
lines changed

components/gitpod-cli/cmd/credential-helper.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func (g *gitCommandInfo) Ok() bool {
174174
return g.RepoUrl != "" && g.GitCommand != ""
175175
}
176176

177-
var gitCommandRegExp = regexp.MustCompile(`git(\s+(?:[\w\-]+\s+)*)(push|clone|fetch|pull|diff|ls-remote)`)
177+
var gitCommandRegExp = regexp.MustCompile(`git(?:\s+(?:\S+\s+)*)(push|clone|fetch|pull|diff|ls-remote)(?:\s+(?:\S+\s+)*)?`)
178178
var repoUrlRegExp = regexp.MustCompile(`remote-https?\s([^\s]+)\s+(https?:[^\s]+)\s`)
179179

180180
// This method needs to be called multiple times to fill all the required info
@@ -185,8 +185,8 @@ var repoUrlRegExp = regexp.MustCompile(`remote-https?\s([^\s]+)\s+(https?:[^\s]+
185185
// `/usr/lib/git-core/git push`
186186
func (g *gitCommandInfo) parseGitCommandAndRemote(cmdLineString string) {
187187
matchCommand := gitCommandRegExp.FindStringSubmatch(cmdLineString)
188-
if len(matchCommand) == 3 {
189-
g.GitCommand = matchCommand[2]
188+
if len(matchCommand) == 2 {
189+
g.GitCommand = matchCommand[1]
190190
}
191191

192192
matchRepo := repoUrlRegExp.FindStringSubmatch(cmdLineString)

components/gitpod-cli/cmd/credential-helper_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ func Test_parse_git_command_and_remote(t *testing.T) {
6060
},
6161
Expected: gitCommandInfo{RepoUrl: "https://github.com/jeanp413/test-private-package.git", GitCommand: "clone"},
6262
},
63+
{
64+
Name: "JB push command",
65+
Commands: []string{
66+
"/usr/lib/git-core/git remote-https origin https://github.com/gitpod-io/spring-petclinic.git ",
67+
"/bin/git -c core.quotepath=false -c log.showSignature=false push --progress --porcelain origin refs/heads/master:master ",
68+
},
69+
Expected: gitCommandInfo{RepoUrl: "https://github.com/gitpod-io/spring-petclinic.git", GitCommand: "push"},
70+
},
6371
}
6472
for _, tt := range tests {
6573
t.Run(tt.Name, func(t *testing.T) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
# Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
# Licensed under the GNU Affero General Public License (AGPL).
4+
# See License-AGPL.txt in the project root for license information.
5+
6+
TEST_BACKEND_DIR=/workspace/ide-backend
7+
if [ ! -d "$TEST_BACKEND_DIR" ]; then
8+
mkdir -p $TEST_BACKEND_DIR
9+
cp -r /ide-desktop/backend/* $TEST_BACKEND_DIR
10+
fi
11+
12+
TEST_PLUGINS_DIR="$TEST_BACKEND_DIR/plugins"
13+
TEST_PLUGIN_DIR="$TEST_PLUGINS_DIR/gitpod-remote"
14+
rm -rf $TEST_PLUGIN_DIR
15+
16+
GITPOD_PLUGIN_DIR=/workspace/gitpod/components/ide/jetbrains/backend-plugin
17+
$GITPOD_PLUGIN_DIR/gradlew buildPlugin
18+
19+
# TODO(ak) actually should be gradle task to make use of output
20+
GITPOD_PLUGIN_DIST="$GITPOD_PLUGIN_DIR/build/distributions/gitpod-remote-0.0.1.zip"
21+
unzip $GITPOD_PLUGIN_DIST -d $TEST_PLUGINS_DIR
22+
23+
TEST_REPO=https://github.com/gitpod-io/spring-petclinic
24+
TEST_DIR=/workspace/spring-petclinic
25+
if [ ! -d "$TEST_DIR" ]; then
26+
git clone $TEST_REPO $TEST_DIR
27+
fi
28+
29+
export JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:0"
30+
31+
$TEST_BACKEND_DIR/bin/remote-dev-server.sh run $TEST_DIR
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package io.gitpod.jetbrains.remote
6+
7+
import com.intellij.codeWithMe.ClientId
8+
import com.intellij.ide.BrowserUtil
9+
import com.intellij.ide.CommandLineProcessor
10+
import com.intellij.openapi.client.ClientSession
11+
import com.intellij.openapi.client.ClientSessionsManager
12+
import com.intellij.openapi.diagnostic.thisLogger
13+
import com.intellij.openapi.project.Project
14+
import com.intellij.openapi.util.io.FileUtilRt
15+
import io.netty.channel.ChannelHandlerContext
16+
import io.netty.handler.codec.http.FullHttpRequest
17+
import io.netty.handler.codec.http.QueryStringDecoder
18+
import org.jetbrains.ide.RestService
19+
import java.nio.file.InvalidPathException
20+
import java.nio.file.Path
21+
22+
23+
class GitpodCLIService : RestService() {
24+
25+
override fun getServiceName() = SERVICE_NAME
26+
27+
override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? {
28+
val operation = getStringParameter("op", urlDecoder)
29+
if (operation == "open") {
30+
val fileStr = getStringParameter("file", urlDecoder)
31+
if (fileStr.isNullOrBlank()) {
32+
return "file is missing"
33+
}
34+
val file = parseFilePath(fileStr) ?: return "invalid file"
35+
val shouldWait = getBooleanParameter("wait", urlDecoder)
36+
return withClient(request, context) {
37+
CommandLineProcessor.doOpenFileOrProject(file, shouldWait).future.get()
38+
}
39+
}
40+
if (operation == "preview") {
41+
val url = getStringParameter("url", urlDecoder)
42+
if (url.isNullOrBlank()) {
43+
return "url is missing"
44+
}
45+
return withClient(request, context) { project ->
46+
BrowserUtil.browse(url, project)
47+
}
48+
}
49+
return "invalid operation"
50+
}
51+
52+
private fun withClient(request: FullHttpRequest, context: ChannelHandlerContext, action: (project: Project?) -> Unit): String? {
53+
val project = getLastFocusedOrOpenedProject()
54+
var session: ClientSession? = null
55+
if (project != null) {
56+
session = ClientSessionsManager.getProjectSessions(project, false).first()
57+
}
58+
if (session == null) {
59+
session = ClientSessionsManager.getAppSessions(false).first()
60+
}
61+
if (session == null) {
62+
return "no client"
63+
}
64+
ClientId.withClientId(session.clientId) {
65+
action(project)
66+
}
67+
sendOk(request, context)
68+
return null
69+
}
70+
71+
private fun parseFilePath(path: String): Path? {
72+
return try {
73+
var file: Path = Path.of(FileUtilRt.toSystemDependentName(path)) // handle paths like '/file/foo\qwe'
74+
if (!file.isAbsolute) {
75+
file = file.toAbsolutePath()
76+
}
77+
file.normalize()
78+
} catch (e: InvalidPathException) {
79+
thisLogger().warn("gitpod cli: failed to parse file path:", e)
80+
null
81+
}
82+
}
83+
84+
companion object {
85+
const val SERVICE_NAME = "gitpod/cli"
86+
}
87+
}

components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class GitpodManager : Disposable {
6565
}
6666
val notification = notificationGroup.createNotification(request.message, type)
6767
for (action in request.actionsList) {
68-
notification.addAction(NotificationAction.createSimple(action) {
68+
notification.addAction(NotificationAction.createSimpleExpiring(action) {
6969
futureNotifications.respond(RespondRequest.newBuilder()
7070
.setRequestId(n.requestId)
7171
.setResponse(NotifyResponse.newBuilder().setAction(action).build())

components/ide/jetbrains/backend-plugin/src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
<extensions defaultExtensionNs="com.intellij">
2020
<applicationService serviceImplementation="io.gitpod.jetbrains.remote.services.HeartbeatService" preload="true"/>
2121
<applicationService serviceImplementation="io.gitpod.jetbrains.remote.GitpodManager" preload="true"/>
22-
<notificationGroup id="Gitpod Notifications" displayType="STICKY_BALLOON" />
22+
<notificationGroup id="Gitpod Notifications" displayType="BALLOON" isLogByDefault="false" />
23+
<httpRequestHandler implementation="io.gitpod.jetbrains.remote.GitpodCLIService"/>
2324
</extensions>
2425

2526
</idea-plugin>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
packages:
2+
- name: app
3+
type: go
4+
srcs:
5+
- "**/*.go"
6+
- "go.mod"
7+
- "go.sum"
8+
env:
9+
- CGO_ENABLED=0
10+
- GOOS=linux
11+
config:
12+
packaging: app
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package cmd
6+
7+
import (
8+
"fmt"
9+
"log"
10+
"net/http"
11+
"net/url"
12+
"path/filepath"
13+
14+
"github.com/spf13/cobra"
15+
)
16+
17+
var wait bool
18+
19+
var openCmd = &cobra.Command{
20+
Use: "open",
21+
Args: cobra.ExactArgs(1),
22+
Run: func(cmd *cobra.Command, args []string) {
23+
file, err := filepath.Abs(args[0])
24+
if err != nil {
25+
log.Fatal(err)
26+
}
27+
28+
url, err := url.Parse("http://localhost:63342/api/gitpod/cli")
29+
if err != nil {
30+
log.Fatal(err)
31+
}
32+
query := url.Query()
33+
query.Add("op", "open")
34+
query.Add("file", file)
35+
query.Add("wait", fmt.Sprintf("%t", wait))
36+
url.RawQuery = query.Encode()
37+
38+
resp, err := http.Get(url.String())
39+
if err != nil {
40+
log.Fatal(err)
41+
}
42+
if resp.StatusCode != http.StatusOK {
43+
log.Fatal(resp.Status)
44+
}
45+
},
46+
}
47+
48+
func init() {
49+
rootCmd.AddCommand(openCmd)
50+
openCmd.Flags().BoolVar(&wait, "wait", false, "")
51+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package cmd
6+
7+
import (
8+
"log"
9+
"net/http"
10+
"net/url"
11+
12+
"github.com/spf13/cobra"
13+
)
14+
15+
var previewCmd = &cobra.Command{
16+
Use: "preview",
17+
Run: func(cmd *cobra.Command, args []string) {
18+
url, err := url.Parse("http://localhost:63342/api/gitpod/cli")
19+
if err != nil {
20+
log.Fatal(err)
21+
}
22+
query := url.Query()
23+
query.Add("op", "preview")
24+
query.Add("url", args[0])
25+
url.RawQuery = query.Encode()
26+
27+
resp, err := http.Get(url.String())
28+
if err != nil {
29+
log.Fatal(err)
30+
}
31+
if resp.StatusCode != http.StatusOK {
32+
log.Fatal(resp.Status)
33+
}
34+
},
35+
}
36+
37+
func init() {
38+
rootCmd.AddCommand(previewCmd)
39+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package cmd
6+
7+
import (
8+
"os"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var rootCmd = &cobra.Command{
14+
Use: "idea-cli",
15+
}
16+
17+
func Execute() {
18+
err := rootCmd.Execute()
19+
if err != nil {
20+
os.Exit(1)
21+
}
22+
}
23+
24+
func init() {}

components/ide/jetbrains/cli/go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module github.com/gitpod-io/gitpod/jetbrains/cli
2+
3+
go 1.17
4+
5+
require (
6+
github.com/inconshreveable/mousetrap v1.0.0 // indirect
7+
github.com/spf13/cobra v1.3.0 // indirect
8+
github.com/spf13/pflag v1.0.5 // indirect
9+
)

0 commit comments

Comments
 (0)