diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index 753f871296..d7fbda5c2c 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -109,8 +109,9 @@ async function resolveEffectiveSyncOptions( workspace: Workspace, localConfig: SyncOptions, promotion?: string, + branchOverride?: string, ): Promise { - return await getEffectiveSettings(localConfig, promotion); + return await getEffectiveSettings(localConfig, promotion, false, false, branchOverride); } type DynFSElement = { @@ -1858,7 +1859,7 @@ async function buildTracker(changes: Change[]) { export async function pull( opts: GlobalOptions & - SyncOptions & { repository?: string; promotion?: string }, + SyncOptions & { repository?: string; promotion?: string; branch?: string }, ) { const originalCliOpts = { ...opts }; opts = await mergeConfigWithConfigFile(opts); @@ -1886,10 +1887,11 @@ export async function pull( workspace, opts, opts.promotion, + opts.branch, ); // Extract specific items configuration before merging overwrites gitBranches - const specificItems = getSpecificItemsForCurrentBranch(opts); + const specificItems = getSpecificItemsForCurrentBranch(opts, opts.branch); // Merge CLI flags with resolved settings (CLI flags take precedence only for explicit overrides) opts = mergeCliWithEffectiveOptions(originalCliOpts, effectiveOpts); @@ -2334,7 +2336,7 @@ function removeSuffix(str: string, suffix: string) { } export async function push( - opts: GlobalOptions & SyncOptions & { repository?: string }, + opts: GlobalOptions & SyncOptions & { repository?: string; branch?: string }, ) { // Save original CLI options before merging with config file const originalCliOpts = { ...opts }; @@ -2361,10 +2363,11 @@ export async function push( workspace, opts, opts.promotion, + opts.branch, ); // Extract specific items configuration BEFORE merging overwrites gitBranches - const specificItems = getSpecificItemsForCurrentBranch(opts); + const specificItems = getSpecificItemsForCurrentBranch(opts, opts.branch); // Merge CLI flags with resolved settings (CLI flags take precedence only for explicit overrides) opts = mergeCliWithEffectiveOptions(originalCliOpts, effectiveOpts); @@ -3118,6 +3121,10 @@ const command = new Command() "--promotion ", "Use promotionOverrides from the specified branch instead of regular overrides", ) + .option( + "--branch ", + "Override the current git branch (works even outside a git repository)", + ) // deno-lint-ignore no-explicit-any .action(pull as any) .command("push") @@ -3171,6 +3178,10 @@ const command = new Command() "--repository ", "Specify repository path (e.g., u/user/repo) when multiple repositories exist", ) + .option( + "--branch ", + "Override the current git branch (works even outside a git repository)", + ) // deno-lint-ignore no-explicit-any .action(push as any); diff --git a/cli/src/core/conf.ts b/cli/src/core/conf.ts index d2edc8569c..f5b95f90ce 100644 --- a/cli/src/core/conf.ts +++ b/cli/src/core/conf.ts @@ -487,18 +487,26 @@ export async function getEffectiveSettings( config: SyncOptions, promotion?: string, skipBranchValidation?: boolean, - suppressLogs?: boolean + suppressLogs?: boolean, + branchOverride?: string ): Promise { // Start with top-level settings from config const { gitBranches, ...topLevelSettings } = config; const effective = { ...topLevelSettings }; - if (isGitRepository()) { + // Determine the branch to use: branchOverride takes precedence, then git detection + let currentBranch: string | null = null; + let originalBranchIfForked: string | null = null; + + if (branchOverride) { + currentBranch = branchOverride; + if (!suppressLogs) { + log.info(`Using branch override: ${branchOverride}`); + } + } else if (isGitRepository()) { const branch = getCurrentGitBranch(); + originalBranchIfForked = getOriginalBranchForWorkspaceForks(branch); - const originalBranchIfForked = getOriginalBranchForWorkspaceForks(branch); - - let currentBranch: string | null; if (originalBranchIfForked) { log.info( `Using overrides from original branch \`${originalBranchIfForked}\`` @@ -507,53 +515,53 @@ export async function getEffectiveSettings( } else { currentBranch = branch; } + } else { + log.debug("Not in a Git repository and no branch override provided, using top-level settings"); + } - // If promotion is specified, use that branch's promotionOverrides or overrides - if (promotion && gitBranches && gitBranches[promotion]) { - const targetBranch = gitBranches[promotion]; + // If promotion is specified, use that branch's promotionOverrides or overrides + if (promotion && gitBranches && gitBranches[promotion]) { + const targetBranch = gitBranches[promotion]; - // First try promotionOverrides, then fall back to overrides - if (targetBranch.promotionOverrides) { - Object.assign(effective, targetBranch.promotionOverrides); - if (!suppressLogs) { - log.info(`Applied promotion settings from branch: ${promotion}`); - } - } else if (targetBranch.overrides) { - Object.assign(effective, targetBranch.overrides); - if (!suppressLogs) { - log.info( - `Applied settings from branch: ${promotion} (no promotionOverrides found)` - ); - } - } else { - log.debug( - `No promotion or regular overrides found for branch '${promotion}', using top-level settings` - ); - } - } - // Otherwise use current branch overrides (existing behavior) - else if ( - currentBranch && - gitBranches && - gitBranches[currentBranch] && - gitBranches[currentBranch].overrides - ) { - Object.assign(effective, gitBranches[currentBranch].overrides); + // First try promotionOverrides, then fall back to overrides + if (targetBranch.promotionOverrides) { + Object.assign(effective, targetBranch.promotionOverrides); + if (!suppressLogs) { + log.info(`Applied promotion settings from branch: ${promotion}`); + } + } else if (targetBranch.overrides) { + Object.assign(effective, targetBranch.overrides); if (!suppressLogs) { - const extraLog = originalBranchIfForked - ? ` (because it is the origin of the workspace fork branch \`${branch}\`)` - : ""; log.info( - `Applied settings for Git branch: ${currentBranch}${extraLog}` + `Applied settings from branch: ${promotion} (no promotionOverrides found)` ); } - } else if (currentBranch) { + } else { log.debug( - `No branch-specific overrides found for '${currentBranch}', using top-level settings` + `No promotion or regular overrides found for branch '${promotion}', using top-level settings` ); } - } else { - log.debug("Not in a Git repository, using top-level settings"); + } + // Otherwise use current branch overrides (existing behavior) + else if ( + currentBranch && + gitBranches && + gitBranches[currentBranch] && + gitBranches[currentBranch].overrides + ) { + Object.assign(effective, gitBranches[currentBranch].overrides); + if (!suppressLogs) { + const extraLog = originalBranchIfForked + ? ` (because it is the origin of the workspace fork branch \`${currentBranch}\`)` + : ""; + log.info( + `Applied settings for Git branch: ${currentBranch}${extraLog}` + ); + } + } else if (currentBranch) { + log.debug( + `No branch-specific overrides found for '${currentBranch}', using top-level settings` + ); } return effective; diff --git a/cli/src/core/specific_items.ts b/cli/src/core/specific_items.ts index 2e9b5052f0..2f6c0f38f9 100644 --- a/cli/src/core/specific_items.ts +++ b/cli/src/core/specific_items.ts @@ -60,12 +60,19 @@ function buildYamlTypePattern(): string { * Get the specific items configuration for the current git branch * Merges commonSpecificItems with branch-specific specificItems */ -export function getSpecificItemsForCurrentBranch(config: SyncOptions): SpecificItemsConfig | undefined { - if (!isGitRepository() || !config.gitBranches) { +export function getSpecificItemsForCurrentBranch(config: SyncOptions, branchOverride?: string): SpecificItemsConfig | undefined { + if (!config.gitBranches) { return undefined; } - const currentBranch = getCurrentGitBranch(); + // Use branch override if provided, otherwise detect from git + let currentBranch: string | null = null; + if (branchOverride) { + currentBranch = branchOverride; + } else if (isGitRepository()) { + currentBranch = getCurrentGitBranch(); + } + if (!currentBranch) { return undefined; }