xref: /aosp_15_r20/tools/treble/hacksaw/workspace/workspace.go (revision 105f628577ac4ba0e277a494fbb614ed8c12a994)
1*105f6285SAndroid Build Coastguard Worker// Copyright 2020 Google LLC
2*105f6285SAndroid Build Coastguard Worker//
3*105f6285SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*105f6285SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*105f6285SAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*105f6285SAndroid Build Coastguard Worker//
7*105f6285SAndroid Build Coastguard Worker//     https://www.apache.org/licenses/LICENSE-2.0
8*105f6285SAndroid Build Coastguard Worker//
9*105f6285SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*105f6285SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*105f6285SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*105f6285SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*105f6285SAndroid Build Coastguard Worker// limitations under the License.
14*105f6285SAndroid Build Coastguard Worker
15*105f6285SAndroid Build Coastguard Worker// Package workspace let's you manage workspaces
16*105f6285SAndroid Build Coastguard Workerpackage workspace
17*105f6285SAndroid Build Coastguard Worker
18*105f6285SAndroid Build Coastguard Workerimport (
19*105f6285SAndroid Build Coastguard Worker	"fmt"
20*105f6285SAndroid Build Coastguard Worker	"io/ioutil"
21*105f6285SAndroid Build Coastguard Worker	"os"
22*105f6285SAndroid Build Coastguard Worker	"os/exec"
23*105f6285SAndroid Build Coastguard Worker	"path/filepath"
24*105f6285SAndroid Build Coastguard Worker	"strings"
25*105f6285SAndroid Build Coastguard Worker
26*105f6285SAndroid Build Coastguard Worker	"android.googlesource.com/platform/tools/treble.git/hacksaw/bind"
27*105f6285SAndroid Build Coastguard Worker	"android.googlesource.com/platform/tools/treble.git/hacksaw/codebase"
28*105f6285SAndroid Build Coastguard Worker	"android.googlesource.com/platform/tools/treble.git/hacksaw/config"
29*105f6285SAndroid Build Coastguard Worker	"android.googlesource.com/platform/tools/treble.git/hacksaw/git"
30*105f6285SAndroid Build Coastguard Worker)
31*105f6285SAndroid Build Coastguard Worker
32*105f6285SAndroid Build Coastguard Workertype Workspace struct {
33*105f6285SAndroid Build Coastguard Worker	composer Composer
34*105f6285SAndroid Build Coastguard Worker	topDir   string
35*105f6285SAndroid Build Coastguard Worker}
36*105f6285SAndroid Build Coastguard Worker
37*105f6285SAndroid Build Coastguard Workerfunc New(bm bind.PathBinder, topDir string) Workspace {
38*105f6285SAndroid Build Coastguard Worker	return Workspace{NewComposer(bm), topDir}
39*105f6285SAndroid Build Coastguard Worker}
40*105f6285SAndroid Build Coastguard Worker
41*105f6285SAndroid Build Coastguard Worker// Create workspace
42*105f6285SAndroid Build Coastguard Workerfunc (w Workspace) Create(workspaceName string, codebaseName string) (string, error) {
43*105f6285SAndroid Build Coastguard Worker	cfg := config.GetConfig()
44*105f6285SAndroid Build Coastguard Worker	_, ok := cfg.Codebases[codebaseName]
45*105f6285SAndroid Build Coastguard Worker	if !ok {
46*105f6285SAndroid Build Coastguard Worker		return "", fmt.Errorf("Codebase %s does not exist", codebaseName)
47*105f6285SAndroid Build Coastguard Worker	}
48*105f6285SAndroid Build Coastguard Worker	if _, ok := cfg.Workspaces[workspaceName]; ok {
49*105f6285SAndroid Build Coastguard Worker		return "", fmt.Errorf("Workspace %s already exists", workspaceName)
50*105f6285SAndroid Build Coastguard Worker	}
51*105f6285SAndroid Build Coastguard Worker	cfg.Workspaces[workspaceName] = codebaseName
52*105f6285SAndroid Build Coastguard Worker	workspaceDir, err := w.GetDir(workspaceName)
53*105f6285SAndroid Build Coastguard Worker	if err != nil {
54*105f6285SAndroid Build Coastguard Worker		return "", err
55*105f6285SAndroid Build Coastguard Worker	}
56*105f6285SAndroid Build Coastguard Worker	if err = os.MkdirAll(workspaceDir, os.ModePerm); err != nil {
57*105f6285SAndroid Build Coastguard Worker		return "", err
58*105f6285SAndroid Build Coastguard Worker	}
59*105f6285SAndroid Build Coastguard Worker	codebaseDir, err := codebase.GetDir(codebaseName)
60*105f6285SAndroid Build Coastguard Worker	if err != nil {
61*105f6285SAndroid Build Coastguard Worker		return "", err
62*105f6285SAndroid Build Coastguard Worker	}
63*105f6285SAndroid Build Coastguard Worker	//TODO: match the order of parameters with Create
64*105f6285SAndroid Build Coastguard Worker	if _, err = w.composer.Compose(codebaseDir, workspaceDir); err != nil {
65*105f6285SAndroid Build Coastguard Worker		return "", err
66*105f6285SAndroid Build Coastguard Worker	}
67*105f6285SAndroid Build Coastguard Worker	return workspaceDir, nil
68*105f6285SAndroid Build Coastguard Worker}
69*105f6285SAndroid Build Coastguard Worker
70*105f6285SAndroid Build Coastguard Worker// Recreate workspace
71*105f6285SAndroid Build Coastguard Workerfunc (w Workspace) Recreate(workspaceName string) (string, error) {
72*105f6285SAndroid Build Coastguard Worker	cfg := config.GetConfig()
73*105f6285SAndroid Build Coastguard Worker	codebaseName, ok := cfg.Workspaces[workspaceName]
74*105f6285SAndroid Build Coastguard Worker	if !ok {
75*105f6285SAndroid Build Coastguard Worker		return "", fmt.Errorf("Workspace %s does not exist", workspaceName)
76*105f6285SAndroid Build Coastguard Worker	}
77*105f6285SAndroid Build Coastguard Worker	workspaceDir, err := w.GetDir(workspaceName)
78*105f6285SAndroid Build Coastguard Worker	if err != nil {
79*105f6285SAndroid Build Coastguard Worker		return "", err
80*105f6285SAndroid Build Coastguard Worker	}
81*105f6285SAndroid Build Coastguard Worker	codebaseDir, err := codebase.GetDir(codebaseName)
82*105f6285SAndroid Build Coastguard Worker	if err != nil {
83*105f6285SAndroid Build Coastguard Worker		return "", err
84*105f6285SAndroid Build Coastguard Worker	}
85*105f6285SAndroid Build Coastguard Worker	if _, err = w.composer.Compose(codebaseDir, workspaceDir); err != nil {
86*105f6285SAndroid Build Coastguard Worker		return "", err
87*105f6285SAndroid Build Coastguard Worker	}
88*105f6285SAndroid Build Coastguard Worker	return workspaceDir, nil
89*105f6285SAndroid Build Coastguard Worker}
90*105f6285SAndroid Build Coastguard Worker
91*105f6285SAndroid Build Coastguard Worker// GetDir retrieves the directory of a specific workspace
92*105f6285SAndroid Build Coastguard Workerfunc (w Workspace) GetDir(workspaceName string) (string, error) {
93*105f6285SAndroid Build Coastguard Worker	cfg := config.GetConfig()
94*105f6285SAndroid Build Coastguard Worker	_, ok := cfg.Workspaces[workspaceName]
95*105f6285SAndroid Build Coastguard Worker	if !ok {
96*105f6285SAndroid Build Coastguard Worker		return "", fmt.Errorf("Workspace %s not found", workspaceName)
97*105f6285SAndroid Build Coastguard Worker	}
98*105f6285SAndroid Build Coastguard Worker	dir := filepath.Join(w.topDir, workspaceName)
99*105f6285SAndroid Build Coastguard Worker	return dir, nil
100*105f6285SAndroid Build Coastguard Worker}
101*105f6285SAndroid Build Coastguard Worker
102*105f6285SAndroid Build Coastguard Worker// GetCodebase retrieves the codebase that a workspace belongs to
103*105f6285SAndroid Build Coastguard Workerfunc (w Workspace) GetCodebase(workspaceName string) (string, error) {
104*105f6285SAndroid Build Coastguard Worker	cfg := config.GetConfig()
105*105f6285SAndroid Build Coastguard Worker	codebase, ok := cfg.Workspaces[workspaceName]
106*105f6285SAndroid Build Coastguard Worker	if !ok {
107*105f6285SAndroid Build Coastguard Worker		return "", fmt.Errorf("Workspace %s not found", workspaceName)
108*105f6285SAndroid Build Coastguard Worker	}
109*105f6285SAndroid Build Coastguard Worker	return codebase, nil
110*105f6285SAndroid Build Coastguard Worker}
111*105f6285SAndroid Build Coastguard Worker
112*105f6285SAndroid Build Coastguard Worker//SetTopDir sets the directory that contains all workspaces
113*105f6285SAndroid Build Coastguard Workerfunc (w *Workspace) SetTopDir(dir string) {
114*105f6285SAndroid Build Coastguard Worker	w.topDir = dir
115*105f6285SAndroid Build Coastguard Worker}
116*105f6285SAndroid Build Coastguard Worker
117*105f6285SAndroid Build Coastguard Workerfunc (w Workspace) List() map[string]string {
118*105f6285SAndroid Build Coastguard Worker	cfg := config.GetConfig()
119*105f6285SAndroid Build Coastguard Worker	list := make(map[string]string)
120*105f6285SAndroid Build Coastguard Worker	for name, codebaseName := range cfg.Workspaces {
121*105f6285SAndroid Build Coastguard Worker		list[name] = codebaseName
122*105f6285SAndroid Build Coastguard Worker	}
123*105f6285SAndroid Build Coastguard Worker	return list
124*105f6285SAndroid Build Coastguard Worker}
125*105f6285SAndroid Build Coastguard Worker
126*105f6285SAndroid Build Coastguard Workerfunc (w Workspace) DetachGitWorktrees(workspaceName string, unbindList []string) error {
127*105f6285SAndroid Build Coastguard Worker	workspaceDir, err := w.GetDir(workspaceName)
128*105f6285SAndroid Build Coastguard Worker	if err != nil {
129*105f6285SAndroid Build Coastguard Worker		return err
130*105f6285SAndroid Build Coastguard Worker	}
131*105f6285SAndroid Build Coastguard Worker	workspaceDir, err = filepath.Abs(workspaceDir)
132*105f6285SAndroid Build Coastguard Worker	if err != nil {
133*105f6285SAndroid Build Coastguard Worker		return err
134*105f6285SAndroid Build Coastguard Worker	}
135*105f6285SAndroid Build Coastguard Worker	//resolve all symlinks so it can be
136*105f6285SAndroid Build Coastguard Worker	//matched to mount paths
137*105f6285SAndroid Build Coastguard Worker	workspaceDir, err = filepath.EvalSymlinks(workspaceDir)
138*105f6285SAndroid Build Coastguard Worker	if err != nil {
139*105f6285SAndroid Build Coastguard Worker		return err
140*105f6285SAndroid Build Coastguard Worker	}
141*105f6285SAndroid Build Coastguard Worker	codebaseName, err := w.GetCodebase(workspaceName)
142*105f6285SAndroid Build Coastguard Worker	if err != nil {
143*105f6285SAndroid Build Coastguard Worker		return err
144*105f6285SAndroid Build Coastguard Worker	}
145*105f6285SAndroid Build Coastguard Worker	codebaseDir, err := codebase.GetDir(codebaseName)
146*105f6285SAndroid Build Coastguard Worker	if err != nil {
147*105f6285SAndroid Build Coastguard Worker		return err
148*105f6285SAndroid Build Coastguard Worker	}
149*105f6285SAndroid Build Coastguard Worker	lister := git.NewRepoLister()
150*105f6285SAndroid Build Coastguard Worker	gitProjects, err := lister.List(codebaseDir)
151*105f6285SAndroid Build Coastguard Worker	if err != nil {
152*105f6285SAndroid Build Coastguard Worker		return err
153*105f6285SAndroid Build Coastguard Worker	}
154*105f6285SAndroid Build Coastguard Worker	gitWorktrees := make(map[string]bool)
155*105f6285SAndroid Build Coastguard Worker	for _, project := range gitProjects {
156*105f6285SAndroid Build Coastguard Worker		gitWorktrees[project] = true
157*105f6285SAndroid Build Coastguard Worker	}
158*105f6285SAndroid Build Coastguard Worker	//projects that were unbound were definitely
159*105f6285SAndroid Build Coastguard Worker	//never git worktrees
160*105f6285SAndroid Build Coastguard Worker	for _, unbindPath := range unbindList {
161*105f6285SAndroid Build Coastguard Worker		project, err := filepath.Rel(workspaceDir, unbindPath)
162*105f6285SAndroid Build Coastguard Worker		if err != nil {
163*105f6285SAndroid Build Coastguard Worker			return err
164*105f6285SAndroid Build Coastguard Worker		}
165*105f6285SAndroid Build Coastguard Worker		if _, ok := gitWorktrees[project]; ok {
166*105f6285SAndroid Build Coastguard Worker			gitWorktrees[project] = false
167*105f6285SAndroid Build Coastguard Worker		}
168*105f6285SAndroid Build Coastguard Worker	}
169*105f6285SAndroid Build Coastguard Worker	for project, isWorktree := range gitWorktrees {
170*105f6285SAndroid Build Coastguard Worker		if !isWorktree {
171*105f6285SAndroid Build Coastguard Worker			continue
172*105f6285SAndroid Build Coastguard Worker		}
173*105f6285SAndroid Build Coastguard Worker		codebaseProject := filepath.Join(codebaseDir, project)
174*105f6285SAndroid Build Coastguard Worker		workspaceProject := filepath.Join(workspaceDir, project)
175*105f6285SAndroid Build Coastguard Worker		_, err = os.Stat(workspaceProject)
176*105f6285SAndroid Build Coastguard Worker		if err == nil {
177*105f6285SAndroid Build Coastguard Worker			//proceed to detach
178*105f6285SAndroid Build Coastguard Worker		} else if os.IsNotExist(err) {
179*105f6285SAndroid Build Coastguard Worker			//just skip if it doesn't exist
180*105f6285SAndroid Build Coastguard Worker			continue
181*105f6285SAndroid Build Coastguard Worker		} else {
182*105f6285SAndroid Build Coastguard Worker			return err
183*105f6285SAndroid Build Coastguard Worker		}
184*105f6285SAndroid Build Coastguard Worker		contents, err := ioutil.ReadDir(workspaceProject)
185*105f6285SAndroid Build Coastguard Worker		if err != nil {
186*105f6285SAndroid Build Coastguard Worker			return err
187*105f6285SAndroid Build Coastguard Worker		}
188*105f6285SAndroid Build Coastguard Worker		if len(contents) == 0 {
189*105f6285SAndroid Build Coastguard Worker			//empty directory, not even a .git
190*105f6285SAndroid Build Coastguard Worker			//not a wortree
191*105f6285SAndroid Build Coastguard Worker			continue
192*105f6285SAndroid Build Coastguard Worker		}
193*105f6285SAndroid Build Coastguard Worker		fmt.Print(".")
194*105f6285SAndroid Build Coastguard Worker		cmd := exec.Command("git",
195*105f6285SAndroid Build Coastguard Worker			"-C", codebaseProject,
196*105f6285SAndroid Build Coastguard Worker			"worktree", "remove", "--force", workspaceProject)
197*105f6285SAndroid Build Coastguard Worker		output, err := cmd.CombinedOutput()
198*105f6285SAndroid Build Coastguard Worker		if err != nil {
199*105f6285SAndroid Build Coastguard Worker			return fmt.Errorf("Command\n%s\nfailed with the following:\n%s\n%s",
200*105f6285SAndroid Build Coastguard Worker				cmd.String(), err.Error(), output)
201*105f6285SAndroid Build Coastguard Worker		}
202*105f6285SAndroid Build Coastguard Worker		cmd = exec.Command("git",
203*105f6285SAndroid Build Coastguard Worker			"-C", codebaseProject,
204*105f6285SAndroid Build Coastguard Worker			"branch", "--delete", "--force", workspaceName)
205*105f6285SAndroid Build Coastguard Worker		output, err = cmd.CombinedOutput()
206*105f6285SAndroid Build Coastguard Worker		if err != nil {
207*105f6285SAndroid Build Coastguard Worker			return fmt.Errorf("Command\n%s\nfailed with the following:\n%s\n%s",
208*105f6285SAndroid Build Coastguard Worker				cmd.String(), err.Error(), output)
209*105f6285SAndroid Build Coastguard Worker		}
210*105f6285SAndroid Build Coastguard Worker	}
211*105f6285SAndroid Build Coastguard Worker	return nil
212*105f6285SAndroid Build Coastguard Worker}
213*105f6285SAndroid Build Coastguard Worker
214*105f6285SAndroid Build Coastguard Workerfunc (w Workspace) Remove(remove string) (*config.Config, error) {
215*105f6285SAndroid Build Coastguard Worker	cfg := config.GetConfig()
216*105f6285SAndroid Build Coastguard Worker	_, ok := cfg.Workspaces[remove]
217*105f6285SAndroid Build Coastguard Worker	if !ok {
218*105f6285SAndroid Build Coastguard Worker		return cfg, fmt.Errorf("Workspace %s not found", remove)
219*105f6285SAndroid Build Coastguard Worker	}
220*105f6285SAndroid Build Coastguard Worker	workspaceDir, err := w.GetDir(remove)
221*105f6285SAndroid Build Coastguard Worker	if err != nil {
222*105f6285SAndroid Build Coastguard Worker		return cfg, err
223*105f6285SAndroid Build Coastguard Worker	}
224*105f6285SAndroid Build Coastguard Worker	unbindList, err := w.composer.Dismantle(workspaceDir)
225*105f6285SAndroid Build Coastguard Worker	if err != nil {
226*105f6285SAndroid Build Coastguard Worker		return cfg, err
227*105f6285SAndroid Build Coastguard Worker	}
228*105f6285SAndroid Build Coastguard Worker	fmt.Print("Detaching worktrees")
229*105f6285SAndroid Build Coastguard Worker	if err = w.DetachGitWorktrees(remove, unbindList); err != nil {
230*105f6285SAndroid Build Coastguard Worker		return cfg, err
231*105f6285SAndroid Build Coastguard Worker	}
232*105f6285SAndroid Build Coastguard Worker	fmt.Print("\n")
233*105f6285SAndroid Build Coastguard Worker	fmt.Println("Removing files")
234*105f6285SAndroid Build Coastguard Worker	if err = os.RemoveAll(workspaceDir); err != nil {
235*105f6285SAndroid Build Coastguard Worker		return cfg, err
236*105f6285SAndroid Build Coastguard Worker	}
237*105f6285SAndroid Build Coastguard Worker	delete(cfg.Workspaces, remove)
238*105f6285SAndroid Build Coastguard Worker	return cfg, err
239*105f6285SAndroid Build Coastguard Worker}
240*105f6285SAndroid Build Coastguard Worker
241*105f6285SAndroid Build Coastguard Workerfunc (w Workspace) Edit(editPath string) (string, string, error) {
242*105f6285SAndroid Build Coastguard Worker	editPath, err := filepath.Abs(editPath)
243*105f6285SAndroid Build Coastguard Worker	if err != nil {
244*105f6285SAndroid Build Coastguard Worker		return "", "", err
245*105f6285SAndroid Build Coastguard Worker	}
246*105f6285SAndroid Build Coastguard Worker	editPath, err = filepath.EvalSymlinks(editPath)
247*105f6285SAndroid Build Coastguard Worker	if err != nil {
248*105f6285SAndroid Build Coastguard Worker		return "", "", err
249*105f6285SAndroid Build Coastguard Worker	}
250*105f6285SAndroid Build Coastguard Worker	relProjectPath, err := w.getReadOnlyProjectFromPath(editPath)
251*105f6285SAndroid Build Coastguard Worker	if err != nil {
252*105f6285SAndroid Build Coastguard Worker		return "", "", err
253*105f6285SAndroid Build Coastguard Worker	}
254*105f6285SAndroid Build Coastguard Worker	workspaceName, err := w.getWorkspaceFromPath(editPath)
255*105f6285SAndroid Build Coastguard Worker	if err != nil {
256*105f6285SAndroid Build Coastguard Worker		return "", "", err
257*105f6285SAndroid Build Coastguard Worker	}
258*105f6285SAndroid Build Coastguard Worker	workspaceDir, err := w.GetDir(workspaceName)
259*105f6285SAndroid Build Coastguard Worker	if err != nil {
260*105f6285SAndroid Build Coastguard Worker		return "", "", err
261*105f6285SAndroid Build Coastguard Worker	}
262*105f6285SAndroid Build Coastguard Worker	codebaseName, err := w.GetCodebase(workspaceName)
263*105f6285SAndroid Build Coastguard Worker	if err != nil {
264*105f6285SAndroid Build Coastguard Worker		return "", "", err
265*105f6285SAndroid Build Coastguard Worker	}
266*105f6285SAndroid Build Coastguard Worker	codebaseDir, err := codebase.GetDir(codebaseName)
267*105f6285SAndroid Build Coastguard Worker	if err != nil {
268*105f6285SAndroid Build Coastguard Worker		return "", "", err
269*105f6285SAndroid Build Coastguard Worker	}
270*105f6285SAndroid Build Coastguard Worker	wsProjectPath := filepath.Join(workspaceDir, relProjectPath)
271*105f6285SAndroid Build Coastguard Worker	if err = w.composer.Unbind(wsProjectPath); err != nil {
272*105f6285SAndroid Build Coastguard Worker		return "", "", err
273*105f6285SAndroid Build Coastguard Worker	}
274*105f6285SAndroid Build Coastguard Worker	//TODO: support editing nested projects
275*105f6285SAndroid Build Coastguard Worker	//the command above unbinds nested child projects but
276*105f6285SAndroid Build Coastguard Worker	//we don't rebind them after checking out an editable project branch
277*105f6285SAndroid Build Coastguard Worker	cbProjectPath := filepath.Join(codebaseDir, relProjectPath)
278*105f6285SAndroid Build Coastguard Worker	branchName := workspaceName
279*105f6285SAndroid Build Coastguard Worker	cmd := exec.Command("git",
280*105f6285SAndroid Build Coastguard Worker		"-C", cbProjectPath,
281*105f6285SAndroid Build Coastguard Worker		"worktree", "add",
282*105f6285SAndroid Build Coastguard Worker		"-b", branchName,
283*105f6285SAndroid Build Coastguard Worker		wsProjectPath)
284*105f6285SAndroid Build Coastguard Worker	output, err := cmd.CombinedOutput()
285*105f6285SAndroid Build Coastguard Worker	if err != nil {
286*105f6285SAndroid Build Coastguard Worker		return "", "", fmt.Errorf("Command\n%s\nfailed with the following:\n%s\n%s",
287*105f6285SAndroid Build Coastguard Worker			cmd.String(), err.Error(), output)
288*105f6285SAndroid Build Coastguard Worker	}
289*105f6285SAndroid Build Coastguard Worker	return branchName, wsProjectPath, err
290*105f6285SAndroid Build Coastguard Worker}
291*105f6285SAndroid Build Coastguard Worker
292*105f6285SAndroid Build Coastguard Workerfunc (w Workspace) getReadOnlyProjectFromPath(inPath string) (string, error) {
293*105f6285SAndroid Build Coastguard Worker	worspaceName, err := w.getWorkspaceFromPath(inPath)
294*105f6285SAndroid Build Coastguard Worker	if err != nil {
295*105f6285SAndroid Build Coastguard Worker		return "", err
296*105f6285SAndroid Build Coastguard Worker	}
297*105f6285SAndroid Build Coastguard Worker	workspacePath, err := w.GetDir(worspaceName)
298*105f6285SAndroid Build Coastguard Worker	if err != nil {
299*105f6285SAndroid Build Coastguard Worker		return "", err
300*105f6285SAndroid Build Coastguard Worker	}
301*105f6285SAndroid Build Coastguard Worker	bindList, err := w.composer.List(workspacePath)
302*105f6285SAndroid Build Coastguard Worker	if err != nil {
303*105f6285SAndroid Build Coastguard Worker		return "", err
304*105f6285SAndroid Build Coastguard Worker	}
305*105f6285SAndroid Build Coastguard Worker	for _, bindPath := range bindList {
306*105f6285SAndroid Build Coastguard Worker		if !strings.HasPrefix(inPath+"/", bindPath+"/") {
307*105f6285SAndroid Build Coastguard Worker			continue
308*105f6285SAndroid Build Coastguard Worker		}
309*105f6285SAndroid Build Coastguard Worker		relProjectPath, err := filepath.Rel(workspacePath, bindPath)
310*105f6285SAndroid Build Coastguard Worker		if err != nil {
311*105f6285SAndroid Build Coastguard Worker			return "", err
312*105f6285SAndroid Build Coastguard Worker		}
313*105f6285SAndroid Build Coastguard Worker		return relProjectPath, nil
314*105f6285SAndroid Build Coastguard Worker	}
315*105f6285SAndroid Build Coastguard Worker	return "", fmt.Errorf("Path %s is already editable", inPath)
316*105f6285SAndroid Build Coastguard Worker}
317*105f6285SAndroid Build Coastguard Worker
318*105f6285SAndroid Build Coastguard Workerfunc (w Workspace) getWorkspaceFromPath(inPath string) (string, error) {
319*105f6285SAndroid Build Coastguard Worker	for workspaceName, _ := range w.List() {
320*105f6285SAndroid Build Coastguard Worker		dir, err := w.GetDir(workspaceName)
321*105f6285SAndroid Build Coastguard Worker		if err != nil {
322*105f6285SAndroid Build Coastguard Worker			return "", err
323*105f6285SAndroid Build Coastguard Worker		}
324*105f6285SAndroid Build Coastguard Worker		if strings.HasPrefix(inPath+"/", dir+"/") {
325*105f6285SAndroid Build Coastguard Worker			return workspaceName, nil
326*105f6285SAndroid Build Coastguard Worker		}
327*105f6285SAndroid Build Coastguard Worker	}
328*105f6285SAndroid Build Coastguard Worker	return "", fmt.Errorf("Path %s is not contained in a workspace", inPath)
329*105f6285SAndroid Build Coastguard Worker}
330