@@ -148,67 +148,68 @@ function authorityHost(authority: string): string | null {
148148 return hostname . toLowerCase ( )
149149}
150150
151- /** `https://<host>/<path>` for a repo on github.com, gitlab.com or
152- * bitbucket.org, whatever URL form `canonical` is in; otherwise `canonical`
153- * unchanged. `canonical` is a `canonicalRepoRemote` value: the service's
154- * `_unify_public_host`. */
155- export function unifyPublicHost ( canonical : string ) : string {
151+ /** The host and path of a repo on github.com, gitlab.com or bitbucket.org,
152+ * whatever URL form `canonical` is in, or undefined where the service's rule
153+ * leaves the value as it is. `canonical` is a `canonicalRepoRemote` value. */
154+ function publicHostRepo ( canonical : string ) : { host : string ; path : string } | undefined {
156155 let [ scheme , sep , rest ] = partition ( canonical , "://" )
157156 let host : string
158157 let path : string
159158 if ( sep ) {
160159 scheme = scheme . toLowerCase ( )
161- if ( ! REMOTE_SCHEMES . has ( scheme ) ) return canonical
160+ if ( ! REMOTE_SCHEMES . has ( scheme ) ) return undefined
162161 let authority : string
163162 ; [ authority , , path ] = partition ( rest , "/" )
164163 const parsed = authorityHost ( authority )
165- if ( parsed === null ) return canonical
164+ if ( parsed === null ) return undefined
166165 host = parsed
167166 } else {
168167 // scp-like `host:path` with no user. Git reads a value as scp-like only
169168 // when no `/` precedes the first `:`; anything else is a local path.
170169 const [ authority , colon , after ] = partition ( canonical , ":" )
171- if ( ! colon || authority . includes ( "/" ) ) return canonical
170+ if ( ! colon || authority . includes ( "/" ) ) return undefined
172171 scheme = "ssh"
173172 host = rpartition ( authority , "@" ) [ 2 ] . toLowerCase ( )
174173 path = after
175174 }
176175 if ( SSH_SCHEMES . has ( scheme ) ) host = SSH_HOST_ALIASES [ host ] ?? host
177176 else if ( WEB_SCHEMES . has ( scheme ) ) host = WEB_HOST_ALIASES [ host ] ?? host
178- if ( ! CASE_INSENSITIVE_HOSTS . has ( host ) || ! isAscii ( path ) ) return canonical
177+ if ( ! CASE_INSENSITIVE_HOSTS . has ( host ) || ! isAscii ( path ) ) return undefined
179178 path = stripChar ( path , "/" , "both" ) . toLowerCase ( )
180- const unified = `https://${ host } /${ path } `
181- if ( ! path || new TextEncoder ( ) . encode ( unified ) . length > IDENTIFIER_MAX_LENGTH ) return canonical
182- return unified
179+ if ( ! path || new TextEncoder ( ) . encode ( `https://${ host } /${ path } ` ) . length > IDENTIFIER_MAX_LENGTH ) return undefined
180+ return { host, path }
183181}
184182
185- /** The value two remotes of one repo share: the service's unified value of the
186- * remote's canonical form, applied until it stops changing.
183+ /** `https://<host>/<path>` for a repo on github.com, gitlab.com or
184+ * bitbucket.org, whatever URL form `canonical` is in; otherwise `canonical`
185+ * unchanged: the service's `_unify_public_host`. */
186+ export function unifyPublicHost ( canonical : string ) : string {
187+ const repo = publicHostRepo ( canonical )
188+ return repo ? `https://${ repo . host } /${ repo . path } ` : canonical
189+ }
190+
191+ /** The key every form of a repo on github.com, gitlab.com or bitbucket.org
192+ * shares, or undefined for any other remote, which compares exactly.
187193 *
188- * Inputs come in three shapes: a remote as git prints it, a value the service
189- * stored (canonical, or unified), and a raw remote the client cached before
190- * the service answered. One pass maps all of them to the service's value,
191- * except where a pass is not idempotent: canonicalization takes one trailing
192- * `.git` off a stored value that still ends in one. Repeating reaches the same
193- * value from every shape. The cost is that a repo whose name ends in `.git`
194- * compares equal to the one without it; the service already stores several
195- * forms of such a repo under the shorter name. */
196- export function repoRemoteKey ( value : string ) : string {
197- // Only the first pass can lengthen the value (`git@` to `ssh://git@`, `http`
198- // to `https`). Every later pass that changes it removes at least one
199- // character, so the value settles within this bound.
200- const bound = value . length + 16
201- let key = value
202- for ( let i = 0 ; i < bound ; i ++ ) {
203- const next = unifyPublicHost ( canonicalRepoRemote ( key ) )
204- if ( next === key ) break
205- key = next
206- }
207- return key
194+ * A cached value is either the service's or, when the service gave none (a
195+ * workspace pinned from the IDE), the remote as git printed it. Read as git
196+ * prints it, a remote ending in `.git` names the repo without it; as the
197+ * service stores it, the repo with it. These hosts don't let the two exist
198+ * side by side (GitHub and GitLab refuse a name ending in `.git`, and Bitbucket
199+ * resolves `x.git` to `x`), so the key drops every trailing `.git`. Other hosts
200+ * may, so their remotes get no key. */
201+ export function publicRepoKey ( value : string ) : string | undefined {
202+ const repo = publicHostRepo ( canonicalRepoRemote ( value ) )
203+ if ( ! repo ) return undefined
204+ const path = repo . path . replace ( / (?: \. g i t | \/ ) + $ / , "" ) || repo . path
205+ return `${ repo . host } /${ path } `
208206}
209207
208+ /** Do two cached remotes name the same repo? */
210209export function sameRepoRemote ( a : string , b : string ) : boolean {
211- return a === b || repoRemoteKey ( a ) === repoRemoteKey ( b )
210+ if ( a === b ) return true
211+ const key = publicRepoKey ( a )
212+ return key !== undefined && key === publicRepoKey ( b )
212213}
213214
214215/** A project's identity as the binding system records it: its git remote when
@@ -224,7 +225,7 @@ export function projectIdentity(remote: unknown, projectPath: unknown): ProjectI
224225}
225226
226227/** Do two identities name the same project? Remotes compare by
227- * `repoRemoteKey `; paths compare exactly, since a path is never a remote and
228+ * `sameRepoRemote `; paths compare exactly, since a path is never a remote and
228229 * must not be rewritten (`/srv/x.git`, `C:\x.git`); a remote never equals a
229230 * path. A missing identity matches nothing. */
230231export function sameProjectIdentity ( a : ProjectIdentity | undefined , b : ProjectIdentity | undefined ) : boolean {
@@ -237,5 +238,8 @@ export function sameProjectIdentity(a: ProjectIdentity | undefined, b: ProjectId
237238 * service or the local cache, and `current` is this project's, with the remote
238239 * as git prints it (`[email protected] :acme/x.git`). */ 239240export function sameIdentifier ( kind : "remote" | "path" , stored : string , current : string ) : boolean {
240- return kind === "remote" ? sameRepoRemote ( stored , current ) : stored === current
241+ if ( kind === "path" ) return stored === current
242+ // The service found the binding by the canonical form of `current`, and off
243+ // the public hosts that form is the value it stores.
244+ return sameRepoRemote ( stored , current ) || canonicalRepoRemote ( current ) === stored
241245}
0 commit comments