@@ -142,16 +142,6 @@ enum Command {
142142 #[ command( subcommand) ]
143143 action : CacheAction ,
144144 } ,
145- /// Import branch stack from existing GitHub PRs.
146- /// Reconstructs the git-stack tree by walking the PR base chain.
147- Import {
148- /// Branch to import (defaults to current branch)
149- #[ arg( long, short) ]
150- branch : Option < String > ,
151- /// Import all open PRs for the repo, not just the current branch's chain
152- #[ arg( long, short) ]
153- all : bool ,
154- } ,
155145 /// Sync local git-stack state with GitHub PRs.
156146 /// Default: weak push then weak pull (bidirectional sync).
157147 Sync {
@@ -396,10 +386,6 @@ fn inner_main() -> Result<()> {
396386 Some ( Command :: Cache { action } ) => {
397387 handle_cache_command ( & git_repo, & mut state, & repo, action)
398388 }
399- Some ( Command :: Import { branch, all } ) => {
400- let branch = branch. unwrap_or ( current_branch) ;
401- handle_import_command ( & git_repo, & mut state, & repo, & branch, all)
402- }
403389 Some ( Command :: Sync {
404390 push,
405391 pull,
@@ -1233,293 +1219,6 @@ fn git_push(git_repo: &GitRepo, branch: &str) -> Result<()> {
12331219 Ok ( ( ) )
12341220}
12351221
1236- // ============== GitHub Import Command ==============
1237-
1238- fn handle_import_command (
1239- git_repo : & GitRepo ,
1240- state : & mut State ,
1241- repo : & str ,
1242- branch : & str ,
1243- import_all : bool ,
1244- ) -> Result < ( ) > {
1245- use github:: {
1246- GitHubClient ,
1247- get_repo_identifier,
1248- has_github_token,
1249- setup_github_token_interactive,
1250- } ;
1251-
1252- let repo_id = get_repo_identifier ( git_repo) ?;
1253-
1254- // Ensure we have auth configured
1255- if !has_github_token ( & repo_id. host ) {
1256- println ! ( "{}" , "GitHub authentication required." . yellow( ) ) ;
1257- setup_github_token_interactive ( ) ?;
1258- }
1259-
1260- let client = GitHubClient :: from_env ( & repo_id) ?;
1261- let trunk = crate :: git:: git_trunk ( git_repo) . ok_or_else ( || anyhow ! ( "No remote configured" ) ) ?;
1262-
1263- // Ensure trunk exists in tree
1264- state. ensure_trunk ( git_repo, repo) ;
1265-
1266- if import_all {
1267- // Import all open PRs
1268- import_all_prs ( git_repo, & client, & repo_id, state, repo, & trunk. main_branch ) ?;
1269- } else {
1270- // Import just this branch's chain
1271- import_branch_chain (
1272- git_repo,
1273- & client,
1274- & repo_id,
1275- state,
1276- repo,
1277- branch,
1278- & trunk. main_branch ,
1279- ) ?;
1280- }
1281-
1282- state. save_state ( ) ?;
1283- println ! ( "\n {}" , "Import complete!" . green( ) . bold( ) ) ;
1284-
1285- // Show the tree
1286- println ! ( ) ;
1287- let Some ( tree) = state. get_tree ( repo) else {
1288- return Ok ( ( ) ) ;
1289- } ;
1290- let pr_cache = fetch_pr_cache ( git_repo) ;
1291- let display_authors = github:: load_display_authors ( ) ;
1292- let renderable = render:: compute_renderable_tree (
1293- git_repo,
1294- tree,
1295- branch,
1296- false ,
1297- pr_cache. as_ref ( ) ,
1298- & display_authors,
1299- ) ;
1300- render:: render_cli ( & renderable, false ) ;
1301-
1302- Ok ( ( ) )
1303- }
1304-
1305- /// Import a single branch's PR chain back to trunk
1306- fn import_branch_chain (
1307- git_repo : & GitRepo ,
1308- client : & github:: GitHubClient ,
1309- repo_id : & github:: RepoIdentifier ,
1310- state : & mut State ,
1311- repo : & str ,
1312- branch : & str ,
1313- trunk : & str ,
1314- ) -> Result < ( ) > {
1315- // Find PR for this branch
1316- let pr = client. find_pr_for_branch ( repo_id, branch) ?. ok_or_else ( || {
1317- anyhow ! (
1318- "No open PR found for branch '{}'. Nothing to import." ,
1319- branch
1320- )
1321- } ) ?;
1322-
1323- println ! (
1324- "Found PR #{} for '{}' (base: '{}')" ,
1325- pr. number. to_string( ) . green( ) ,
1326- branch. yellow( ) ,
1327- pr. base. ref_name. cyan( )
1328- ) ;
1329-
1330- // Build the chain from this branch up to trunk
1331- let mut chain: Vec < ( String , Option < github:: PullRequest > ) > =
1332- vec ! [ ( branch. to_string( ) , Some ( pr. clone( ) ) ) ] ;
1333- let mut current_base = pr. base . ref_name . clone ( ) ;
1334-
1335- while current_base != trunk {
1336- // Check if we already have this branch in our chain (cycle detection)
1337- if chain. iter ( ) . any ( |( b, _) | b == & current_base) {
1338- println ! (
1339- "{} Detected cycle at '{}', stopping chain walk" ,
1340- "Warning:" . yellow( ) . bold( ) ,
1341- current_base
1342- ) ;
1343- break ;
1344- }
1345-
1346- // Find PR for the base branch
1347- match client. find_pr_for_branch ( repo_id, & current_base) ? {
1348- Some ( base_pr) => {
1349- println ! (
1350- "Found PR #{} for '{}' (base: '{}')" ,
1351- base_pr. number. to_string( ) . green( ) ,
1352- current_base. yellow( ) ,
1353- base_pr. base. ref_name. cyan( )
1354- ) ;
1355- let next_base = base_pr. base . ref_name . clone ( ) ;
1356- chain. push ( ( current_base. clone ( ) , Some ( base_pr) ) ) ;
1357- current_base = next_base;
1358- }
1359- None => {
1360- // No PR for this base - it might be an intermediate branch or already merged
1361- // Check if it's the trunk
1362- if current_base == trunk {
1363- break ;
1364- }
1365- println ! (
1366- "{} No PR found for '{}', assuming it's an intermediate branch" ,
1367- "Note:" . cyan( ) . bold( ) ,
1368- current_base. yellow( )
1369- ) ;
1370- chain. push ( ( current_base. clone ( ) , None ) ) ;
1371- // We can't walk further without a PR, assume parent is trunk
1372- break ;
1373- }
1374- }
1375- }
1376-
1377- // Reverse to process from trunk-side down
1378- chain. reverse ( ) ;
1379-
1380- // Mount each branch in order
1381- let mut parent = trunk. to_string ( ) ;
1382- for ( branch_name, pr_opt) in chain {
1383- // Check if branch exists locally
1384- if !git_repo. branch_exists ( & branch_name) {
1385- println ! (
1386- "{} Branch '{}' doesn't exist locally, skipping" ,
1387- "Note:" . cyan( ) . bold( ) ,
1388- branch_name. yellow( )
1389- ) ;
1390- continue ;
1391- }
1392-
1393- // Mount this branch under the parent
1394- if !state. branch_exists_in_tree ( repo, & branch_name) {
1395- println ! (
1396- "Mounting '{}' on '{}'" ,
1397- branch_name. yellow( ) ,
1398- parent. green( )
1399- ) ;
1400- state. mount ( git_repo, repo, & branch_name, Some ( parent. clone ( ) ) ) ?;
1401- } else {
1402- println ! ( "'{}' already in tree" , branch_name. yellow( ) ) ;
1403- }
1404-
1405- // Store PR number if we have one
1406- if let Some ( pr) = pr_opt
1407- && let Some ( tree_branch) =
1408- find_branch_by_name_mut ( state. get_tree_mut ( repo) . unwrap ( ) , & branch_name)
1409- && tree_branch. pr_number . is_none ( )
1410- {
1411- tree_branch. pr_number = Some ( pr. number ) ;
1412- }
1413-
1414- parent = branch_name;
1415- }
1416-
1417- Ok ( ( ) )
1418- }
1419-
1420- /// Import all open PRs for the repo
1421- fn import_all_prs (
1422- git_repo : & GitRepo ,
1423- client : & github:: GitHubClient ,
1424- repo_id : & github:: RepoIdentifier ,
1425- state : & mut State ,
1426- repo : & str ,
1427- trunk : & str ,
1428- ) -> Result < ( ) > {
1429- let all_prs = client. list_open_prs ( repo_id, None ) ?. prs ;
1430-
1431- if all_prs. is_empty ( ) {
1432- println ! ( "No open PRs found for this repository." ) ;
1433- return Ok ( ( ) ) ;
1434- }
1435-
1436- println ! ( "Found {} open PR(s)" , all_prs. len( ) . to_string( ) . green( ) ) ;
1437-
1438- // Build a map of branch -> (parent, pr_number)
1439- let mut branch_parents: std:: collections:: HashMap < String , ( String , u64 ) > =
1440- std:: collections:: HashMap :: new ( ) ;
1441-
1442- for ( branch_name, pr) in & all_prs {
1443- branch_parents. insert ( branch_name. clone ( ) , ( pr. base . ref_name . clone ( ) , pr. number ) ) ;
1444- }
1445-
1446- // Find branches whose parent is trunk or another PR branch (roots of stacks)
1447- // Then build each stack
1448- let mut imported = std:: collections:: HashSet :: new ( ) ;
1449-
1450- // Process in order: first branches based on trunk, then their children, etc.
1451- let mut to_process: Vec < String > = branch_parents
1452- . iter ( )
1453- . filter ( |( _, ( parent, _) ) | parent == trunk)
1454- . map ( |( branch, _) | branch. clone ( ) )
1455- . collect ( ) ;
1456-
1457- while !to_process. is_empty ( ) {
1458- let branch_name = to_process. remove ( 0 ) ;
1459-
1460- if imported. contains ( & branch_name) {
1461- continue ;
1462- }
1463-
1464- // Get parent and PR number
1465- let ( parent, pr_number) = match branch_parents. get ( & branch_name) {
1466- Some ( ( p, n) ) => ( p. clone ( ) , * n) ,
1467- None => continue ,
1468- } ;
1469-
1470- // Determine actual parent (could be trunk or another imported branch)
1471- let actual_parent = if parent == trunk || imported. contains ( & parent) {
1472- parent. clone ( )
1473- } else {
1474- // Parent not yet imported - skip for now, will process later
1475- to_process. push ( branch_name) ;
1476- continue ;
1477- } ;
1478-
1479- // Check if branch exists locally
1480- if !git_repo. branch_exists ( & branch_name) {
1481- println ! (
1482- "{} Branch '{}' doesn't exist locally (PR #{}), skipping" ,
1483- "Note:" . cyan( ) . bold( ) ,
1484- branch_name. yellow( ) ,
1485- pr_number
1486- ) ;
1487- imported. insert ( branch_name. clone ( ) ) ;
1488- continue ;
1489- }
1490-
1491- // Mount this branch
1492- if !state. branch_exists_in_tree ( repo, & branch_name) {
1493- println ! (
1494- "Mounting '{}' on '{}' (PR #{})" ,
1495- branch_name. yellow( ) ,
1496- actual_parent. green( ) ,
1497- pr_number
1498- ) ;
1499- state. mount ( git_repo, repo, & branch_name, Some ( actual_parent. clone ( ) ) ) ?;
1500- }
1501-
1502- // Store PR number
1503- if let Some ( tree_branch) =
1504- find_branch_by_name_mut ( state. get_tree_mut ( repo) . unwrap ( ) , & branch_name)
1505- && tree_branch. pr_number . is_none ( )
1506- {
1507- tree_branch. pr_number = Some ( pr_number) ;
1508- }
1509-
1510- imported. insert ( branch_name. clone ( ) ) ;
1511-
1512- // Add children of this branch to process list
1513- for ( child, ( child_parent, _) ) in & branch_parents {
1514- if child_parent == & branch_name && !imported. contains ( child) {
1515- to_process. push ( child. clone ( ) ) ;
1516- }
1517- }
1518- }
1519-
1520- Ok ( ( ) )
1521- }
1522-
15231222// ============== GitHub PR Commands ==============
15241223
15251224fn handle_pr_command (
0 commit comments