-
Notifications
You must be signed in to change notification settings - Fork 191
Expand file tree
/
Copy pathinfo.go
More file actions
176 lines (150 loc) · 5.66 KB
/
Copy pathinfo.go
File metadata and controls
176 lines (150 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package git
import (
"context"
"errors"
"io/fs"
"net/http"
"path"
"strings"
"github.com/databricks/cli/libs/auth"
"github.com/databricks/cli/libs/dbr"
"github.com/databricks/cli/libs/folders"
"github.com/databricks/cli/libs/log"
"github.com/databricks/cli/libs/vfs"
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/apierr"
"github.com/databricks/databricks-sdk-go/client"
)
type RepositoryInfo struct {
// Various metadata about the repo. Each could be "" if it could not be read. No error is returned for such case.
OriginURL string
LatestCommit string
CurrentBranch string
// Absolute path to determined worktree root or "" if worktree root could not be determined.
WorktreeRoot string
}
type gitInfo struct {
Branch string `json:"branch"`
HeadCommitID string `json:"head_commit_id"`
Path string `json:"path"`
URL string `json:"url"`
// ID of the git folder object. Some workspace git folders return only id+path
// from get-status (omitting branch/commit/url), so the id lets us recover the
// rest via the Repos API. See the fallback in fetchRepositoryInfoAPI.
ID int64 `json:"id"`
}
type response struct {
GitInfo *gitInfo `json:"git_info,omitempty"`
}
// Fetch repository information either by quering .git or by fetching it from API (for dabs-in-workspace case).
// - In case we could not find git repository (including when the path does not exist), all string fields of RepositoryInfo will be "" and err will be nil.
// - If there were any errors when trying to determine git root (e.g. API call returned an error or there were permission issues
// reading the file system), all strings fields of RepositoryInfo will be "" and err will be non-nil.
// - If we could determine git worktree root but there were errors when reading metadata (origin, branch, commit), those errors
// will be logged as warnings, RepositoryInfo is guaranteed to have non-empty WorktreeRoot and other fields on best effort basis.
// - In successful case, all fields are set to proper git repository metadata.
func FetchRepositoryInfo(ctx context.Context, path string, w *databricks.WorkspaceClient) (RepositoryInfo, error) {
var info RepositoryInfo
var err error
if strings.HasPrefix(path, "/Workspace/") && dbr.RunsOnRuntime(ctx) {
info, err = fetchRepositoryInfoAPI(ctx, path, w)
} else {
info, err = fetchRepositoryInfoDotGit(ctx, path)
}
// A path that does not exist just means there is no repository there, which
// is not an error. Both backends report this as fs.ErrNotExist (the API
// backend translates a workspace 404 to it), so it is normalized to a nil
// error in a single place rather than special-cased by every caller.
if errors.Is(err, fs.ErrNotExist) {
return info, nil
}
return info, err
}
func fetchRepositoryInfoAPI(ctx context.Context, path string, w *databricks.WorkspaceClient) (RepositoryInfo, error) {
result := RepositoryInfo{}
apiClient, err := client.New(w.Config)
if err != nil {
return result, err
}
var response response
const apiEndpoint = "/api/2.0/workspace/get-status"
err = apiClient.Do(
ctx,
http.MethodGet,
apiEndpoint,
auth.WorkspaceIDHeaders(w.Config),
nil,
map[string]string{
"path": path,
"return_git_info": "true",
},
&response,
)
if err != nil {
// The workspace API returns 404 when the path is not a workspace object
// (for example, an ephemeral directory that is not part of a Repo).
// Normalize it to fs.ErrNotExist, the same signal fetchRepositoryInfoDotGit
// produces for a missing local path, so FetchRepositoryInfo can treat
// "no path" as "no repository" uniformly.
if apierr.IsMissing(err) {
return result, fs.ErrNotExist
}
return result, err
}
// Check if GitInfo is present and extract relevant fields
gi := response.GitInfo
if gi == nil {
log.Infof(ctx, "Failed to load git info from %s", apiEndpoint)
return result, nil
}
result.OriginURL = gi.URL
result.LatestCommit = gi.HeadCommitID
result.CurrentBranch = gi.Branch
result.WorktreeRoot = ensureWorkspacePrefix(gi.Path)
// Some workspace git folders return only id+path from get-status and omit the
// origin URL. When that happens, fetch the full provenance from the Repos API
// by id. Classic repos return the URL inline and skip this extra call.
if gi.ID != 0 && result.OriginURL == "" {
repo, err := w.Repos.GetByRepoId(ctx, gi.ID)
if err != nil {
// Best effort: WorktreeRoot is already set, so degrade to partial info
// rather than failing the deploy (see FetchRepositoryInfo's contract).
log.Warnf(ctx, "failed to load git info from Repos API for id %d: %v", gi.ID, err)
return result, nil
}
result.OriginURL = repo.Url
result.LatestCommit = repo.HeadCommitId
result.CurrentBranch = repo.Branch
}
return result, nil
}
func ensureWorkspacePrefix(p string) string {
if !strings.HasPrefix(p, "/Workspace/") {
return path.Join("/Workspace", p)
}
return p
}
func fetchRepositoryInfoDotGit(ctx context.Context, path string) (RepositoryInfo, error) {
result := RepositoryInfo{}
rootDir, err := folders.FindDirWithLeaf(path, GitDirectoryName)
if rootDir == "" {
return result, err
}
result.WorktreeRoot = rootDir
repo, err := NewRepository(ctx, vfs.MustNew(rootDir))
if err != nil {
log.Warnf(ctx, "failed to read .git: %s", err)
// return early since operations below won't work
return result, nil
}
result.OriginURL = repo.OriginUrl()
result.CurrentBranch, err = repo.CurrentBranch()
if err != nil {
log.Warnf(ctx, "failed to load current branch: %s", err)
}
result.LatestCommit, err = repo.LatestCommit()
if err != nil {
log.Warnf(ctx, "failed to load latest commit: %s", err)
}
return result, nil
}