OverviewFilesCommitsIssuesDocsReleasesPeers

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

136 lines · javascript ref:
/**
 * pgh pull - Pull from 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 pullCommand (client, sub, args, flags) {
  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 pull args
  const gitArgs = ['pull']
  if (flags.rebase) gitArgs.push('--rebase')
  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 pullSubmodules(cwd, refs, flags, results)
  }

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

async function pullSubmodules (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(`\nPulling submodule: ${sub.name}`)

    // First ensure submodule is initialized
    try {
      await gitSpawn(['submodule', 'update', '--init', '--', sub.path], cwd)
    } catch {
      // May already be initialized
    }

    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 = ['pull']
    if (flags.rebase) subArgs.push('--rebase')
    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 pullHelp () {
  log(`Usage: pgh pull [refs...] [flags]

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

Arguments:
  [refs...]                Branch to pull (default: current branch)

Flags:
  --rebase                 Rebase instead of merge
  --recurse-submodules     Pull @pear-marked submodules too
  --json                   Output as JSON

Examples:
  pgh pull                          Pull current branch
  pgh pull main                     Pull main branch
  pgh pull --rebase                 Pull with rebase
  pgh pull --recurse-submodules     Pull main repo + submodules`)
}