OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / lib / cli / commands / push.js

134 lines · javascript ref:
/**
 * pgh push - Push to pear remote
 *
 * Auto-detects pear://, punch://, git+pear://, or named 'pear' remote.
 * Supports --recurse-submodules for @pear-marked submodules.
 */

import path from 'bare-path'
import { log, error, jsonOut } from '../output.js'
import { gitExec, gitSpawn, findPearRemote, parseGitmodules } from '../git.js'

export async function pushCommand (client, sub, args, flags) {
  // sub is actually the first positional after 'push' (e.g. branch name)
  // Collect refs from sub + args
  const refs = []
  if (sub) refs.push(sub)
  refs.push(...args)

  const cwd = await gitExec(['rev-parse', '--show-toplevel'], '.')

  const remote = await findPearRemote(cwd)
  if (!remote) {
    error('No pear remote found. Run "pgh repo link <name> <path>" to add one.')
  }

  // Build git push args
  const gitArgs = ['push']
  if (flags.force) gitArgs.push('--force')
  if (flags.u || flags['set-upstream']) gitArgs.push('--set-upstream')
  gitArgs.push(remote)
  if (refs.length > 0) {
    gitArgs.push(...refs)
  }

  const results = { remote, refs, submodules: [] }

  try {
    if (flags.json) {
      const output = await gitExec(gitArgs, cwd)
      if (output) log(output)
    } else {
      await gitSpawn(gitArgs, cwd)
    }
    results.status = 'ok'
  } catch (err) {
    if (flags.json) {
      results.status = 'error'
      results.error = err.message
      jsonOut(results)
      process.exit(1)
    }
    throw err
  }

  // Handle --recurse-submodules
  if (flags['recurse-submodules']) {
    await pushSubmodules(cwd, refs, flags, results)
  }

  if (flags.json) {
    jsonOut(results)
  }
}

async function pushSubmodules (cwd, refs, flags, results) {
  let content
  try {
    content = await gitExec(['show', 'HEAD:.gitmodules'], cwd)
  } catch {
    if (!flags.json) log('No .gitmodules found - skipping submodules')
    return
  }

  const submodules = parseGitmodules(content)
  const pearSubs = submodules.filter(s => s.pear && s.path)

  if (pearSubs.length === 0) {
    if (!flags.json) log('No @pear submodules found')
    return
  }

  for (const sub of pearSubs) {
    const subPath = path.resolve(cwd, sub.path)
    if (!flags.json) log(`\nPushing submodule: ${sub.name}`)

    const subRemote = await findPearRemote(subPath)
    if (!subRemote) {
      const msg = `No pear remote in submodule ${sub.name} - skipping`
      if (!flags.json) log(`  ${msg}`)
      results.submodules.push({ name: sub.name, status: 'skipped', reason: msg })
      continue
    }

    const subArgs = ['push']
    if (flags.force) subArgs.push('--force')
    subArgs.push(subRemote)
    if (refs.length > 0) subArgs.push(...refs)

    try {
      if (flags.json) {
        await gitExec(subArgs, subPath)
      } else {
        await gitSpawn(subArgs, subPath)
      }
      results.submodules.push({ name: sub.name, status: 'ok' })
    } catch (err) {
      if (!flags.json) console.error(`  Failed: ${err.message}`)
      results.submodules.push({ name: sub.name, status: 'error', error: err.message })
    }
  }
}

export function pushHelp () {
  log(`Usage: pgh push [refs...] [flags]

Push to the pear remote (auto-detected from pear://, punch://, git+pear://, or named 'pear').

Arguments:
  [refs...]                Branch or refspec to push (default: current branch)

Flags:
  --force                  Force push
  -u, --set-upstream       Set upstream tracking
  --recurse-submodules     Push @pear-marked submodules too
  --json                   Output as JSON

Examples:
  pgh push                          Push current branch
  pgh push main                     Push main branch
  pgh push --force main             Force push main
  pgh push -u feature-branch        Push and set upstream
  pgh push --recurse-submodules     Push main repo + submodules`)
}