Skip to content

Commit 975bd7c

Browse files
committed
Change choose codespace prompt
- Repo + branch is favored - Codespace name is included to disambiguate between two codespaces - Move Codespace model out of out API into its own package - Update call sites and group behavior under codespace.Codespace
1 parent 93cea6d commit 975bd7c

File tree

12 files changed

+236
-174
lines changed

12 files changed

+236
-174
lines changed

internal/codespaces/api/api.go

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"strings"
3939
"time"
4040

41+
"github.com/cli/cli/v2/internal/codespaces/codespace"
4142
"github.com/opentracing/opentracing-go"
4243
)
4344

@@ -140,46 +141,7 @@ func (a *API) GetRepository(ctx context.Context, nwo string) (*Repository, error
140141
return &response, nil
141142
}
142143

143-
type Codespace struct {
144-
Name string `json:"name"`
145-
GUID string `json:"guid"`
146-
CreatedAt string `json:"created_at"`
147-
LastUsedAt string `json:"last_used_at"`
148-
Branch string `json:"branch"`
149-
RepositoryName string `json:"repository_name"`
150-
RepositoryNWO string `json:"repository_nwo"`
151-
OwnerLogin string `json:"owner_login"`
152-
Environment CodespaceEnvironment `json:"environment"`
153-
}
154-
155-
type CodespaceEnvironment struct {
156-
State string `json:"state"`
157-
Connection CodespaceEnvironmentConnection `json:"connection"`
158-
GitStatus CodespaceEnvironmentGitStatus `json:"gitStatus"`
159-
}
160-
161-
type CodespaceEnvironmentGitStatus struct {
162-
Ahead int `json:"ahead"`
163-
Behind int `json:"behind"`
164-
Branch string `json:"branch"`
165-
Commit string `json:"commit"`
166-
HasUnpushedChanges bool `json:"hasUnpushedChanges"`
167-
HasUncommitedChanges bool `json:"hasUncommitedChanges"`
168-
}
169-
170-
const (
171-
CodespaceEnvironmentStateAvailable = "Available"
172-
)
173-
174-
type CodespaceEnvironmentConnection struct {
175-
SessionID string `json:"sessionId"`
176-
SessionToken string `json:"sessionToken"`
177-
RelayEndpoint string `json:"relayEndpoint"`
178-
RelaySAS string `json:"relaySas"`
179-
HostPublicKeys []string `json:"hostPublicKeys"`
180-
}
181-
182-
func (a *API) ListCodespaces(ctx context.Context) ([]*Codespace, error) {
144+
func (a *API) ListCodespaces(ctx context.Context) ([]*codespace.Codespace, error) {
183145
req, err := http.NewRequest(
184146
http.MethodGet, a.githubAPI+"/user/codespaces", nil,
185147
)
@@ -204,7 +166,7 @@ func (a *API) ListCodespaces(ctx context.Context) ([]*Codespace, error) {
204166
}
205167

206168
var response struct {
207-
Codespaces []*Codespace `json:"codespaces"`
169+
Codespaces []*codespace.Codespace `json:"codespaces"`
208170
}
209171
if err := json.Unmarshal(b, &response); err != nil {
210172
return nil, fmt.Errorf("error unmarshaling response: %w", err)
@@ -267,10 +229,10 @@ func (a *API) GetCodespaceToken(ctx context.Context, ownerLogin, codespaceName s
267229
return response.RepositoryToken, nil
268230
}
269231

270-
func (a *API) GetCodespace(ctx context.Context, token, owner, codespace string) (*Codespace, error) {
232+
func (a *API) GetCodespace(ctx context.Context, token, owner, codespaceName string) (*codespace.Codespace, error) {
271233
req, err := http.NewRequest(
272234
http.MethodGet,
273-
a.githubAPI+"/vscs_internal/user/"+owner+"/codespaces/"+codespace,
235+
a.githubAPI+"/vscs_internal/user/"+owner+"/codespaces/"+codespaceName,
274236
nil,
275237
)
276238
if err != nil {
@@ -294,15 +256,15 @@ func (a *API) GetCodespace(ctx context.Context, token, owner, codespace string)
294256
return nil, jsonErrorResponse(b)
295257
}
296258

297-
var response Codespace
259+
var response codespace.Codespace
298260
if err := json.Unmarshal(b, &response); err != nil {
299261
return nil, fmt.Errorf("error unmarshaling response: %w", err)
300262
}
301263

302264
return &response, nil
303265
}
304266

305-
func (a *API) StartCodespace(ctx context.Context, token string, codespace *Codespace) error {
267+
func (a *API) StartCodespace(ctx context.Context, token string, codespace *codespace.Codespace) error {
306268
req, err := http.NewRequest(
307269
http.MethodPost,
308270
a.githubAPI+"/vscs_internal/proxy/environments/"+codespace.GUID+"/start",
@@ -429,7 +391,7 @@ type CreateCodespaceParams struct {
429391

430392
// CreateCodespace creates a codespace with the given parameters and returns a non-nil error if it
431393
// fails to create.
432-
func (a *API) CreateCodespace(ctx context.Context, params *CreateCodespaceParams) (*Codespace, error) {
394+
func (a *API) CreateCodespace(ctx context.Context, params *CreateCodespaceParams) (*codespace.Codespace, error) {
433395
codespace, err := a.startCreate(ctx, params.RepositoryID, params.Machine, params.Branch, params.Location)
434396
if err != errProvisioningInProgress {
435397
return codespace, err
@@ -482,7 +444,7 @@ var errProvisioningInProgress = errors.New("provisioning in progress")
482444
// It may return success or an error, or errProvisioningInProgress indicating that the operation
483445
// did not complete before the GitHub API's time limit for RPCs (10s), in which case the caller
484446
// must poll the server to learn the outcome.
485-
func (a *API) startCreate(ctx context.Context, repoID int, machine, branch, location string) (*Codespace, error) {
447+
func (a *API) startCreate(ctx context.Context, repoID int, machine, branch, location string) (*codespace.Codespace, error) {
486448
requestBody, err := json.Marshal(startCreateRequest{repoID, branch, location, machine})
487449
if err != nil {
488450
return nil, fmt.Errorf("error marshaling request: %w", err)
@@ -512,7 +474,7 @@ func (a *API) startCreate(ctx context.Context, repoID int, machine, branch, loca
512474
return nil, errProvisioningInProgress // RPC finished before result of creation known
513475
}
514476

515-
var response Codespace
477+
var response codespace.Codespace
516478
if err := json.Unmarshal(b, &response); err != nil {
517479
return nil, fmt.Errorf("error unmarshaling response: %w", err)
518480
}
@@ -554,7 +516,7 @@ type getCodespaceRepositoryContentsResponse struct {
554516
Content string `json:"content"`
555517
}
556518

557-
func (a *API) GetCodespaceRepositoryContents(ctx context.Context, codespace *Codespace, path string) ([]byte, error) {
519+
func (a *API) GetCodespaceRepositoryContents(ctx context.Context, codespace *codespace.Codespace, path string) ([]byte, error) {
558520
req, err := http.NewRequest(http.MethodGet, a.githubAPI+"/repos/"+codespace.RepositoryNWO+"/contents/"+path, nil)
559521
if err != nil {
560522
return nil, fmt.Errorf("error creating request: %w", err)

internal/codespaces/api/api_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import (
77
"net/http"
88
"net/http/httptest"
99
"testing"
10+
11+
"github.com/cli/cli/v2/internal/codespaces/codespace"
1012
)
1113

1214
func TestListCodespaces(t *testing.T) {
13-
codespaces := []*Codespace{
15+
codespaces := []*codespace.Codespace{
1416
{
1517
Name: "testcodespace",
1618
CreatedAt: "2021-08-09T10:10:24+02:00",
@@ -19,7 +21,7 @@ func TestListCodespaces(t *testing.T) {
1921
}
2022
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2123
response := struct {
22-
Codespaces []*Codespace `json:"codespaces"`
24+
Codespaces []*codespace.Codespace `json:"codespaces"`
2325
}{
2426
Codespaces: codespaces,
2527
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package codespace
2+
3+
import "fmt"
4+
5+
type Codespace struct {
6+
Name string `json:"name"`
7+
CreatedAt string `json:"created_at"`
8+
LastUsedAt string `json:"last_used_at"`
9+
GUID string `json:"guid"`
10+
Branch string `json:"branch"`
11+
RepositoryName string `json:"repository_name"`
12+
RepositoryNWO string `json:"repository_nwo"`
13+
OwnerLogin string `json:"owner_login"`
14+
Environment Environment `json:"environment"`
15+
}
16+
17+
// DisplayName returns the repository nwo and branch.
18+
// If includeName is true, the name of the codespace is included.
19+
// If includeGitStatus is true, the branch will include a star if
20+
// the codespace has unsaved changes.
21+
func (c *Codespace) DisplayName(includeName, includeGitStatus bool) string {
22+
branch := c.Branch
23+
if includeGitStatus {
24+
branch = c.BranchWithGitStatus()
25+
}
26+
27+
if includeName {
28+
return fmt.Sprintf(
29+
"%s: %s [%s]", c.RepositoryNWO, branch, c.Name,
30+
)
31+
}
32+
return c.RepositoryNWO + ": " + branch
33+
}
34+
35+
// BranchWithGitStatus returns the branch with a star
36+
// if the branch is currently being worked on.
37+
func (c *Codespace) BranchWithGitStatus() string {
38+
if c.HasUnsavedChanges() {
39+
return c.Branch + "*"
40+
}
41+
42+
return c.Branch
43+
}
44+
45+
// HasUnsavedChanges returns whether the environment has
46+
// unsaved changes.
47+
func (c *Codespace) HasUnsavedChanges() bool {
48+
return c.Environment.GitStatus.HasUncommitedChanges || c.Environment.GitStatus.HasUnpushedChanges
49+
}
50+
51+
type Environment struct {
52+
State string `json:"state"`
53+
Connection EnvironmentConnection `json:"connection"`
54+
GitStatus EnvironmentGitStatus `json:"gitStatus"`
55+
}
56+
57+
type EnvironmentGitStatus struct {
58+
Ahead int `json:"ahead"`
59+
Behind int `json:"behind"`
60+
Branch string `json:"branch"`
61+
Commit string `json:"commit"`
62+
HasUnpushedChanges bool `json:"hasUnpushedChanges"`
63+
HasUncommitedChanges bool `json:"hasUncommitedChanges"`
64+
}
65+
66+
const (
67+
EnvironmentStateAvailable = "Available"
68+
)
69+
70+
type EnvironmentConnection struct {
71+
SessionID string `json:"sessionId"`
72+
SessionToken string `json:"sessionToken"`
73+
RelayEndpoint string `json:"relayEndpoint"`
74+
RelaySAS string `json:"relaySas"`
75+
HostPublicKeys []string `json:"hostPublicKeys"`
76+
}

internal/codespaces/codespaces.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"fmt"
77
"time"
88

9-
"github.com/cli/cli/v2/internal/codespaces/api"
9+
"github.com/cli/cli/v2/internal/codespaces/codespace"
1010
"github.com/cli/cli/v2/pkg/liveshare"
1111
)
1212

@@ -15,33 +15,33 @@ type logger interface {
1515
Println(v ...interface{}) (int, error)
1616
}
1717

18-
func connectionReady(codespace *api.Codespace) bool {
19-
return codespace.Environment.Connection.SessionID != "" &&
20-
codespace.Environment.Connection.SessionToken != "" &&
21-
codespace.Environment.Connection.RelayEndpoint != "" &&
22-
codespace.Environment.Connection.RelaySAS != "" &&
23-
codespace.Environment.State == api.CodespaceEnvironmentStateAvailable
18+
func connectionReady(cs *codespace.Codespace) bool {
19+
return cs.Environment.Connection.SessionID != "" &&
20+
cs.Environment.Connection.SessionToken != "" &&
21+
cs.Environment.Connection.RelayEndpoint != "" &&
22+
cs.Environment.Connection.RelaySAS != "" &&
23+
cs.Environment.State == codespace.EnvironmentStateAvailable
2424
}
2525

2626
type apiClient interface {
27-
GetCodespace(ctx context.Context, token, user, name string) (*api.Codespace, error)
27+
GetCodespace(ctx context.Context, token, user, name string) (*codespace.Codespace, error)
2828
GetCodespaceToken(ctx context.Context, user, codespace string) (string, error)
29-
StartCodespace(ctx context.Context, token string, codespace *api.Codespace) error
29+
StartCodespace(ctx context.Context, token string, codespace *codespace.Codespace) error
3030
}
3131

3232
// ConnectToLiveshare waits for a Codespace to become running,
3333
// and connects to it using a Live Share session.
34-
func ConnectToLiveshare(ctx context.Context, log logger, apiClient apiClient, userLogin, token string, codespace *api.Codespace) (*liveshare.Session, error) {
34+
func ConnectToLiveshare(ctx context.Context, log logger, apiClient apiClient, userLogin, token string, cs *codespace.Codespace) (*liveshare.Session, error) {
3535
var startedCodespace bool
36-
if codespace.Environment.State != api.CodespaceEnvironmentStateAvailable {
36+
if cs.Environment.State != codespace.EnvironmentStateAvailable {
3737
startedCodespace = true
3838
log.Print("Starting your codespace...")
39-
if err := apiClient.StartCodespace(ctx, token, codespace); err != nil {
39+
if err := apiClient.StartCodespace(ctx, token, cs); err != nil {
4040
return nil, fmt.Errorf("error starting codespace: %w", err)
4141
}
4242
}
4343

44-
for retries := 0; !connectionReady(codespace); retries++ {
44+
for retries := 0; !connectionReady(cs); retries++ {
4545
if retries > 1 {
4646
if retries%2 == 0 {
4747
log.Print(".")
@@ -55,7 +55,7 @@ func ConnectToLiveshare(ctx context.Context, log logger, apiClient apiClient, us
5555
}
5656

5757
var err error
58-
codespace, err = apiClient.GetCodespace(ctx, token, userLogin, codespace.Name)
58+
cs, err = apiClient.GetCodespace(ctx, token, userLogin, cs.Name)
5959
if err != nil {
6060
return nil, fmt.Errorf("error getting codespace: %w", err)
6161
}
@@ -68,10 +68,10 @@ func ConnectToLiveshare(ctx context.Context, log logger, apiClient apiClient, us
6868
log.Println("Connecting to your codespace...")
6969

7070
return liveshare.Connect(ctx, liveshare.Options{
71-
SessionID: codespace.Environment.Connection.SessionID,
72-
SessionToken: codespace.Environment.Connection.SessionToken,
73-
RelaySAS: codespace.Environment.Connection.RelaySAS,
74-
RelayEndpoint: codespace.Environment.Connection.RelayEndpoint,
75-
HostPublicKeys: codespace.Environment.Connection.HostPublicKeys,
71+
SessionID: cs.Environment.Connection.SessionID,
72+
SessionToken: cs.Environment.Connection.SessionToken,
73+
RelaySAS: cs.Environment.Connection.RelaySAS,
74+
RelayEndpoint: cs.Environment.Connection.RelayEndpoint,
75+
HostPublicKeys: cs.Environment.Connection.HostPublicKeys,
7676
})
7777
}

internal/codespaces/states.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
"github.com/cli/cli/v2/internal/codespaces/api"
13+
"github.com/cli/cli/v2/internal/codespaces/codespace"
1314
"github.com/cli/cli/v2/pkg/liveshare"
1415
)
1516

@@ -36,13 +37,13 @@ type PostCreateState struct {
3637
// PollPostCreateStates watches for state changes in a codespace,
3738
// and calls the supplied poller for each batch of state changes.
3839
// It runs until it encounters an error, including cancellation of the context.
39-
func PollPostCreateStates(ctx context.Context, log logger, apiClient apiClient, user *api.User, codespace *api.Codespace, poller func([]PostCreateState)) (err error) {
40-
token, err := apiClient.GetCodespaceToken(ctx, user.Login, codespace.Name)
40+
func PollPostCreateStates(ctx context.Context, log logger, apiClient apiClient, user *api.User, c *codespace.Codespace, poller func([]PostCreateState)) (err error) {
41+
token, err := apiClient.GetCodespaceToken(ctx, user.Login, c.Name)
4142
if err != nil {
4243
return fmt.Errorf("getting codespace token: %w", err)
4344
}
4445

45-
session, err := ConnectToLiveshare(ctx, log, apiClient, user.Login, token, codespace)
46+
session, err := ConnectToLiveshare(ctx, log, apiClient, user.Login, token, c)
4647
if err != nil {
4748
return fmt.Errorf("connect to Live Share: %w", err)
4849
}
@@ -83,7 +84,7 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient apiClient,
8384
return fmt.Errorf("connection failed: %w", err)
8485

8586
case <-t.C:
86-
states, err := getPostCreateOutput(ctx, localPort, codespace, sshUser)
87+
states, err := getPostCreateOutput(ctx, localPort, sshUser)
8788
if err != nil {
8889
return fmt.Errorf("get post create output: %w", err)
8990
}
@@ -93,7 +94,7 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient apiClient,
9394
}
9495
}
9596

96-
func getPostCreateOutput(ctx context.Context, tunnelPort int, codespace *api.Codespace, user string) ([]PostCreateState, error) {
97+
func getPostCreateOutput(ctx context.Context, tunnelPort int, user string) ([]PostCreateState, error) {
9798
cmd, err := NewRemoteCommand(
9899
ctx, tunnelPort, fmt.Sprintf("%s@localhost", user),
99100
"cat /workspaces/.codespaces/shared/postCreateOutput.json",

0 commit comments

Comments
 (0)