Skip to content

Commit ce21f01

Browse files
authored
Merge pull request #61 from stuartleeks/sl/handle-dc-in-repo-subfolder
Handle .devcontainer in subfolder of git repo
2 parents 847fbf2 + 8adf3d1 commit ce21f01

File tree

1 file changed

+52
-15
lines changed

1 file changed

+52
-15
lines changed

internal/pkg/devcontainers/dockerutils.go

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -107,34 +107,71 @@ type DockerMount struct {
107107
Destination string `json:"Destination"`
108108
}
109109

110-
// GetSourceMountFolderFromDevContainer inspects the specified container and returns the DockerMount for the source mount
111-
func GetSourceMountFolderFromDevContainer(containerIDOrName string) (DockerMount, error) {
110+
// SourceInfo holds properties about the source mounted in a dev container
111+
type SourceInfo struct {
112+
DevcontainerFolder string
113+
DockerMount DockerMount
114+
}
115+
116+
// getMountFolderFromFolder walks up the path hierarchy checking for a git repo. If found that is returned, if not the original folder is used
117+
func getMountFolderFromFolder(folder string) (string, error) {
118+
119+
for currentFolder := folder; currentFolder != ""; currentFolder = path.Dir(currentFolder) {
120+
info, err := os.Stat(path.Join(currentFolder, ".git"))
121+
if os.IsNotExist(err) {
122+
continue // walk up the hierarchy
123+
}
124+
if err != nil {
125+
// other error - e.g. permissions issue
126+
// give up the search
127+
break
128+
}
129+
if info.IsDir() {
130+
// We've found a .git folder - use it
131+
return currentFolder, nil
132+
}
133+
134+
}
135+
// didn't find anything - use original folder
136+
return folder, nil
137+
}
138+
139+
// GetSourceInfoFromDevContainer inspects the specified container and returns the SourceInfo
140+
func GetSourceInfoFromDevContainer(containerIDOrName string) (SourceInfo, error) {
112141
localPath, err := GetLocalFolderFromDevContainer(containerIDOrName)
113142
if err != nil {
114-
return DockerMount{}, err
143+
return SourceInfo{}, err
115144
}
116145

117146
if strings.HasPrefix(localPath, "\\\\wsl$") && wsl.IsWsl() {
118147
localPath, err = wsl.ConvertWindowsPathToWslPath(localPath)
119148
if err != nil {
120-
return DockerMount{}, fmt.Errorf("error converting path: %s", err)
149+
return SourceInfo{}, fmt.Errorf("error converting path: %s", err)
121150
}
122151
}
123152

124-
cmd := exec.Command("docker", "inspect", containerIDOrName, "--format", fmt.Sprintf("{{ range .Mounts }}{{if eq .Source \"%s\"}}{{json .}}{{end}}{{end}}", localPath))
153+
mountFolder, err := getMountFolderFromFolder(localPath)
154+
if err != nil {
155+
return SourceInfo{}, fmt.Errorf("search for mount folder failed: %s", err)
156+
}
157+
158+
cmd := exec.Command("docker", "inspect", containerIDOrName, "--format", fmt.Sprintf("{{ range .Mounts }}{{if eq .Source \"%s\"}}{{json .}}{{end}}{{end}}", mountFolder))
125159

126160
output, err := cmd.Output()
127161
if err != nil {
128-
return DockerMount{}, fmt.Errorf("Failed to read docker stdout: %v", err)
162+
return SourceInfo{}, fmt.Errorf("Failed to read docker stdout: %v", err)
129163
}
130164

131165
var mount DockerMount
132166
err = json.Unmarshal(output, &mount)
133167
if err != nil {
134-
return DockerMount{}, err
168+
return SourceInfo{}, fmt.Errorf("failed to get parse JSON getting mount folder for container %q (path=%q): %s", containerIDOrName, mountFolder, err)
135169
}
136170

137-
return mount, nil
171+
return SourceInfo{
172+
DevcontainerFolder: localPath,
173+
DockerMount: mount,
174+
}, nil
138175
}
139176

140177
type byLocalPathLength []DevcontainerInfo
@@ -178,11 +215,11 @@ func ExecInDevContainer(containerID string, workDir string, args []string) error
178215

179216
statusWriter := &terminal.UpdatingStatusWriter{}
180217

181-
sourceMount, err := GetSourceMountFolderFromDevContainer(containerID)
218+
sourceInfo, err := GetSourceInfoFromDevContainer(containerID)
182219
if err != nil {
183-
return err
220+
return fmt.Errorf("failed to get source mount: %s", err)
184221
}
185-
localPath := sourceMount.Source
222+
localPath := sourceInfo.DevcontainerFolder
186223

187224
statusWriter.Printf("Getting user name")
188225
devcontainerJSONPath := path.Join(localPath, ".devcontainer/devcontainer.json")
@@ -230,9 +267,9 @@ func ExecInDevContainer(containerID string, workDir string, args []string) error
230267
fmt.Println("Continuing without setting VSCODE_IPC_HOOK_CLI...")
231268
}
232269

233-
mountPath := sourceMount.Destination
270+
mountPath := sourceInfo.DockerMount.Destination
234271
if workDir == "" {
235-
workDir = mountPath
272+
workDir = sourceInfo.DevcontainerFolder
236273
} else if !filepath.IsAbs(workDir) {
237274

238275
// Convert to absolute (local) path
@@ -251,8 +288,8 @@ func ExecInDevContainer(containerID string, workDir string, args []string) error
251288
}
252289
if !containerPathExists {
253290
// path not found - try converting from local path
254-
// ? Should we check here that the workDir has localPath as a prefix?
255-
devContainerRelativePath, err := filepath.Rel(localPath, workDir)
291+
// ? Should we check here that the workDir has mountPath as a prefix?
292+
devContainerRelativePath, err := filepath.Rel(sourceInfo.DockerMount.Source, workDir)
256293
if err != nil {
257294
return fmt.Errorf("error getting path relative to mount dir: %s", err)
258295
}

0 commit comments

Comments
 (0)