| ... | ... | @@ -65,6 +65,23 @@ const RawBitbucketRepositorySchema = Schema.Struct({ |
| 65 | 65 | ), |
| 66 | 66 | }); |
| 67 | 67 | |
| 68 | const RawBitbucketBranchingModelSchema = Schema.Struct({ |
| 69 | development: Schema.optional( |
| 70 | Schema.Struct({ |
| 71 | branch: Schema.optional( |
| 72 | Schema.NullOr( |
| 73 | Schema.Struct({ |
| 74 | name: Schema.optional(TrimmedNonEmptyString), |
| 75 | }), |
| 76 | ), |
| 77 | ), |
| 78 | is_valid: Schema.optional(Schema.Boolean), |
| 79 | name: Schema.optional(Schema.NullOr(Schema.String)), |
| 80 | use_mainbranch: Schema.optional(Schema.Boolean), |
| 81 | }), |
| 82 | ), |
| 83 | }); |
| 84 | |
| 68 | 85 | const BitbucketUserSchema = Schema.Struct({ |
| 69 | 86 | username: Schema.optional(TrimmedNonEmptyString), |
| 70 | 87 | display_name: Schema.optional(TrimmedNonEmptyString), |
| ... | ... | @@ -216,7 +233,7 @@ function parseBitbucketRemoteUrl(remoteUrl: string): BitbucketRepositoryLocator |
| 216 | 233 | } |
| 217 | 234 | |
| 218 | 235 | function normalizeRepositoryCloneUrls( |
| 219 | | raw: Schema.Schema.Type<typeof RawBitbucketRepositorySchema>, |
| 236 | raw: typeof RawBitbucketRepositorySchema.Type, |
| 220 | 237 | ): SourceControlRepositoryCloneUrls { |
| 221 | 238 | const httpClone = |
| 222 | 239 | raw.links.clone?.find((entry) => entry.name.toLowerCase() === "https")?.href ?? |
| ... | ... | @@ -230,6 +247,24 @@ function normalizeRepositoryCloneUrls( |
| 230 | 247 | }; |
| 231 | 248 | } |
| 232 | 249 | |
| 250 | function defaultChangeRequestTargetBranch(input: { |
| 251 | readonly repository: typeof RawBitbucketRepositorySchema.Type; |
| 252 | readonly branchingModel: typeof RawBitbucketBranchingModelSchema.Type | null; |
| 253 | }): string | null { |
| 254 | const repositoryMainBranch = input.repository.mainbranch?.name ?? null; |
| 255 | const development = input.branchingModel?.development; |
| 256 | if (!development || development.use_mainbranch === true || development.is_valid === false) { |
| 257 | return repositoryMainBranch; |
| 258 | } |
| 259 | |
| 260 | const developmentBranch = development.branch?.name?.trim() ?? development.name?.trim() ?? ""; |
| 261 | if (developmentBranch.length === 0 || developmentBranch === "null") { |
| 262 | return repositoryMainBranch; |
| 263 | } |
| 264 | |
| 265 | return developmentBranch; |
| 266 | } |
| 267 | |
| 233 | 268 | function shouldPreferSshRemote(originRemoteUrl: string | null): boolean { |
| 234 | 269 | const trimmed = originRemoteUrl?.trim() ?? ""; |
| 235 | 270 | return trimmed.startsWith("git@") || trimmed.startsWith("ssh://"); |
| ... | ... | @@ -430,23 +465,32 @@ export const make = Effect.fn("makeBitbucketApi")(function* () { |
| 430 | 465 | }); |
| 431 | 466 | }); |
| 432 | 467 | |
| 468 | const getRepositoryFromLocator = (repository: BitbucketRepositoryLocator) => |
| 469 | executeJson( |
| 470 | "getRepository", |
| 471 | HttpClientRequest.get( |
| 472 | apiUrl( |
| 473 | `/repositories/${encodeURIComponent(repository.workspace)}/${encodeURIComponent(repository.repoSlug)}`, |
| 474 | ), |
| 475 | ), |
| 476 | RawBitbucketRepositorySchema, |
| 477 | ); |
| 478 | |
| 433 | 479 | const getRepository = (input: { |
| 434 | 480 | readonly cwd: string; |
| 435 | 481 | readonly context?: SourceControlProvider.SourceControlProviderContext; |
| 436 | 482 | readonly repository?: string; |
| 437 | | }) => |
| 438 | | resolveRepository(input).pipe( |
| 439 | | Effect.flatMap((repository) => |
| 440 | | executeJson( |
| 441 | | "getRepository", |
| 442 | | HttpClientRequest.get( |
| 443 | | apiUrl( |
| 444 | | `/repositories/${encodeURIComponent(repository.workspace)}/${encodeURIComponent(repository.repoSlug)}`, |
| 445 | | ), |
| 446 | | ), |
| 447 | | RawBitbucketRepositorySchema, |
| 483 | }) => resolveRepository(input).pipe(Effect.flatMap(getRepositoryFromLocator)); |
| 484 | |
| 485 | const getBranchingModelFromLocator = (repository: BitbucketRepositoryLocator) => |
| 486 | executeJson( |
| 487 | "getBranchingModel", |
| 488 | HttpClientRequest.get( |
| 489 | apiUrl( |
| 490 | `/repositories/${encodeURIComponent(repository.workspace)}/${encodeURIComponent(repository.repoSlug)}/branching-model`, |
| 448 | 491 | ), |
| 449 | 492 | ), |
| 493 | RawBitbucketBranchingModelSchema, |
| 450 | 494 | ); |
| 451 | 495 | |
| 452 | 496 | const getRawPullRequestFromRepository = ( |
| ... | ... | @@ -628,7 +672,22 @@ export const make = Effect.fn("makeBitbucketApi")(function* () { |
| 628 | 672 | ); |
| 629 | 673 | }), |
| 630 | 674 | getDefaultBranch: (input) => |
| 631 | | getRepository(input).pipe(Effect.map((repository) => repository.mainbranch?.name ?? null)), |
| 675 | resolveRepository(input).pipe( |
| 676 | Effect.flatMap((locator) => |
| 677 | Effect.all( |
| 678 | { |
| 679 | repository: getRepositoryFromLocator(locator), |
| 680 | branchingModel: getBranchingModelFromLocator(locator).pipe( |
| 681 | Effect.catch(() => |
| 682 | Effect.succeed<typeof RawBitbucketBranchingModelSchema.Type | null>(null), |
| 683 | ), |
| 684 | ), |
| 685 | }, |
| 686 | { concurrency: "unbounded" }, |
| 687 | ), |
| 688 | ), |
| 689 | Effect.map(defaultChangeRequestTargetBranch), |
| 690 | ), |
| 632 | 691 | // Bitbucket Cloud pull requests are Git-backed and Bitbucket does not provide |
| 633 | 692 | // an official checkout CLI. This provider-local path uses GitVcsDriver as a |
| 634 | 693 | // narrow escape hatch to materialize Bitbucket PR refs. Do not generalize this |