@@ -1232,6 +1232,123 @@ contains |
| 1232 | 1232 | call execute_command_line('sleep 1', exitstat=status_code) |
| 1233 | 1233 | end subroutine git_switch_branch |
| 1234 | 1234 | |
| 1235 | + subroutine git_create_branch(branch_name, success) |
| 1236 | + character(len=*), intent(in) :: branch_name |
| 1237 | + logical, intent(out) :: success |
| 1238 | + character(len=1024) :: command |
| 1239 | + integer :: status |
| 1240 | + |
| 1241 | + success = .false. |
| 1242 | + |
| 1243 | + if (len_trim(branch_name) == 0) then |
| 1244 | + print '(A)', achar(27) // '[33mBranch name cannot be empty' // achar(27) // '[0m' |
| 1245 | + return |
| 1246 | + end if |
| 1247 | + |
| 1248 | + ! Create and switch to new branch |
| 1249 | + write(command, '(A,A,A)') 'git switch -c "', trim(branch_name), '" 2>&1' |
| 1250 | + print '(A)', 'Creating new branch...' |
| 1251 | + call execute_command_line(trim(command), exitstat=status) |
| 1252 | + |
| 1253 | + if (status == 0) then |
| 1254 | + print '(A)', achar(27) // '[32m✓ Created and switched to branch: ' // trim(branch_name) // achar(27) // '[0m' |
| 1255 | + success = .true. |
| 1256 | + else |
| 1257 | + print '(A)', achar(27) // '[31m✗ Failed to create branch (may already exist)' // achar(27) // '[0m' |
| 1258 | + end if |
| 1259 | + |
| 1260 | + call execute_command_line('sleep 1', exitstat=status) |
| 1261 | + end subroutine git_create_branch |
| 1262 | + |
| 1263 | + subroutine git_delete_branch(success) |
| 1264 | + use terminal_module, only: read_key |
| 1265 | + logical, intent(out) :: success |
| 1266 | + integer :: status_code |
| 1267 | + character(len=512) :: selected_branch, current_branch |
| 1268 | + character(len=1024) :: command |
| 1269 | + character(len=1) :: key |
| 1270 | + |
| 1271 | + success = .false. |
| 1272 | + |
| 1273 | + ! Get current branch name to prevent deleting it |
| 1274 | + call execute_command_line('git rev-parse --abbrev-ref HEAD > /tmp/fuss_current_branch.txt 2>&1', & |
| 1275 | + exitstat=status_code) |
| 1276 | + |
| 1277 | + if (status_code /= 0) then |
| 1278 | + print '(A)', achar(27) // '[31m✗ Could not determine current branch' // achar(27) // '[0m' |
| 1279 | + return |
| 1280 | + end if |
| 1281 | + |
| 1282 | + open(unit=99, file='/tmp/fuss_current_branch.txt', status='old', action='read', iostat=status_code) |
| 1283 | + if (status_code == 0) then |
| 1284 | + read(99, '(A)', iostat=status_code) current_branch |
| 1285 | + close(99, status='delete') |
| 1286 | + else |
| 1287 | + return |
| 1288 | + end if |
| 1289 | + |
| 1290 | + ! Restore terminal for fzf |
| 1291 | + call execute_command_line('stty sane < /dev/tty', exitstat=status_code) |
| 1292 | + |
| 1293 | + ! Use fzf to select branch to delete (exclude current branch) |
| 1294 | + write(command, '(A,A,A)') 'git branch | grep -v "^* " | sed "s/^ //" | grep -v "^', trim(current_branch), & |
| 1295 | + '$" | fzf --height=15 --prompt="Delete branch: " > /tmp/fuss_branch_delete.txt' |
| 1296 | + call execute_command_line(trim(command), exitstat=status_code) |
| 1297 | + |
| 1298 | + ! Re-enable cbreak mode |
| 1299 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status_code) |
| 1300 | + |
| 1301 | + if (status_code /= 0) then |
| 1302 | + ! User cancelled |
| 1303 | + return |
| 1304 | + end if |
| 1305 | + |
| 1306 | + ! Read selected branch |
| 1307 | + open(unit=99, file='/tmp/fuss_branch_delete.txt', status='old', action='read', iostat=status_code) |
| 1308 | + if (status_code /= 0) return |
| 1309 | + |
| 1310 | + read(99, '(A)', iostat=status_code) selected_branch |
| 1311 | + close(99, status='delete') |
| 1312 | + |
| 1313 | + if (status_code /= 0 .or. len_trim(selected_branch) == 0) return |
| 1314 | + |
| 1315 | + ! Confirm deletion |
| 1316 | + print '(A)', '' |
| 1317 | + print '(A)', 'Delete branch "' // trim(selected_branch) // '"?' |
| 1318 | + print '(A)', 'Press ''d'' for regular delete, ''D'' to force delete, any other key to cancel.' |
| 1319 | + call read_key(key) |
| 1320 | + |
| 1321 | + if (key == 'd') then |
| 1322 | + ! Regular delete (will fail if not merged) |
| 1323 | + write(command, '(A,A,A)') 'git branch -d "', trim(selected_branch), '" 2>&1' |
| 1324 | + print '(A)', 'Deleting branch...' |
| 1325 | + call execute_command_line(trim(command), exitstat=status_code) |
| 1326 | + |
| 1327 | + if (status_code == 0) then |
| 1328 | + print '(A)', achar(27) // '[32m✓ Deleted branch: ' // trim(selected_branch) // achar(27) // '[0m' |
| 1329 | + success = .true. |
| 1330 | + else |
| 1331 | + print '(A)', achar(27) // '[31m✗ Failed to delete (not fully merged? use D to force)' // achar(27) // '[0m' |
| 1332 | + end if |
| 1333 | + else if (key == 'D') then |
| 1334 | + ! Force delete |
| 1335 | + write(command, '(A,A,A)') 'git branch -D "', trim(selected_branch), '" 2>&1' |
| 1336 | + print '(A)', 'Force deleting branch...' |
| 1337 | + call execute_command_line(trim(command), exitstat=status_code) |
| 1338 | + |
| 1339 | + if (status_code == 0) then |
| 1340 | + print '(A)', achar(27) // '[32m✓ Force deleted branch: ' // trim(selected_branch) // achar(27) // '[0m' |
| 1341 | + success = .true. |
| 1342 | + else |
| 1343 | + print '(A)', achar(27) // '[31m✗ Failed to delete branch' // achar(27) // '[0m' |
| 1344 | + end if |
| 1345 | + else |
| 1346 | + print '(A)', 'Delete cancelled.' |
| 1347 | + end if |
| 1348 | + |
| 1349 | + call execute_command_line('sleep 1', exitstat=status_code) |
| 1350 | + end subroutine git_delete_branch |
| 1351 | + |
| 1235 | 1352 | subroutine git_push_tag(tag_name, success) |
| 1236 | 1353 | character(len=*), intent(in) :: tag_name |
| 1237 | 1354 | logical, intent(out) :: success |
@@ -1253,4 +1370,130 @@ contains |
| 1253 | 1370 | end if |
| 1254 | 1371 | end subroutine git_push_tag |
| 1255 | 1372 | |
| 1373 | + subroutine git_stash_push(stash_message, success) |
| 1374 | + character(len=*), intent(in) :: stash_message |
| 1375 | + logical, intent(out) :: success |
| 1376 | + character(len=2048) :: command |
| 1377 | + integer :: status |
| 1378 | + |
| 1379 | + success = .false. |
| 1380 | + |
| 1381 | + ! Build stash push command with optional message |
| 1382 | + if (len_trim(stash_message) > 0) then |
| 1383 | + write(command, '(A,A,A)') 'git stash push -m "', trim(stash_message), '" 2>&1' |
| 1384 | + else |
| 1385 | + command = 'git stash push 2>&1' |
| 1386 | + end if |
| 1387 | + |
| 1388 | + print '(A)', 'Stashing changes...' |
| 1389 | + call execute_command_line(trim(command), exitstat=status) |
| 1390 | + |
| 1391 | + if (status == 0) then |
| 1392 | + print '(A)', achar(27) // '[32m✓ Changes stashed successfully!' // achar(27) // '[0m' |
| 1393 | + success = .true. |
| 1394 | + else |
| 1395 | + print '(A)', achar(27) // '[31m✗ Stash failed (no changes to stash?)' // achar(27) // '[0m' |
| 1396 | + end if |
| 1397 | + |
| 1398 | + print '(A)', 'Press any key to continue...' |
| 1399 | + end subroutine git_stash_push |
| 1400 | + |
| 1401 | + subroutine git_stash_pop_apply(success) |
| 1402 | + use terminal_module, only: read_key |
| 1403 | + logical, intent(out) :: success |
| 1404 | + integer :: status_code |
| 1405 | + character(len=512) :: selected_stash |
| 1406 | + character(len=1024) :: command |
| 1407 | + character(len=1) :: key |
| 1408 | + |
| 1409 | + success = .false. |
| 1410 | + |
| 1411 | + ! Check if there are any stashes |
| 1412 | + call execute_command_line('git stash list > /tmp/fuss_stash_check.txt 2>&1', exitstat=status_code) |
| 1413 | + if (status_code /= 0) then |
| 1414 | + print '(A)', achar(27) // '[33mNo stashes available' // achar(27) // '[0m' |
| 1415 | + print '(A)', 'Press any key to continue...' |
| 1416 | + return |
| 1417 | + end if |
| 1418 | + |
| 1419 | + ! Check if stash list is empty |
| 1420 | + call execute_command_line('test -s /tmp/fuss_stash_check.txt', exitstat=status_code) |
| 1421 | + if (status_code /= 0) then |
| 1422 | + print '(A)', achar(27) // '[33mNo stashes available' // achar(27) // '[0m' |
| 1423 | + print '(A)', 'Press any key to continue...' |
| 1424 | + call execute_command_line('rm -f /tmp/fuss_stash_check.txt', exitstat=status_code) |
| 1425 | + return |
| 1426 | + end if |
| 1427 | + |
| 1428 | + call execute_command_line('rm -f /tmp/fuss_stash_check.txt', exitstat=status_code) |
| 1429 | + |
| 1430 | + ! Restore terminal for fzf |
| 1431 | + call execute_command_line('stty sane < /dev/tty', exitstat=status_code) |
| 1432 | + |
| 1433 | + ! Use fzf to select stash with preview |
| 1434 | + call execute_command_line('git stash list --format="%gd: %s" | ' // & |
| 1435 | + 'fzf --height=15 --prompt="Select stash: " ' // & |
| 1436 | + '--preview="git stash show -p {1}" --preview-window=right:50% ' // & |
| 1437 | + '> /tmp/fuss_stash_select.txt', & |
| 1438 | + exitstat=status_code) |
| 1439 | + |
| 1440 | + ! Re-enable cbreak mode |
| 1441 | + call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status_code) |
| 1442 | + |
| 1443 | + if (status_code /= 0) then |
| 1444 | + ! User cancelled |
| 1445 | + return |
| 1446 | + end if |
| 1447 | + |
| 1448 | + ! Read selected stash |
| 1449 | + open(unit=99, file='/tmp/fuss_stash_select.txt', status='old', action='read', iostat=status_code) |
| 1450 | + if (status_code /= 0) return |
| 1451 | + |
| 1452 | + read(99, '(A)', iostat=status_code) selected_stash |
| 1453 | + close(99, status='delete') |
| 1454 | + |
| 1455 | + if (status_code /= 0 .or. len_trim(selected_stash) == 0) return |
| 1456 | + |
| 1457 | + ! Extract stash reference (e.g., "stash@{0}") |
| 1458 | + ! Format is "stash@{N}: message", so we take everything before the first ":" |
| 1459 | + command = selected_stash(1:index(selected_stash, ':') - 1) |
| 1460 | + |
| 1461 | + ! Ask user whether to pop or apply |
| 1462 | + print '(A)', '' |
| 1463 | + print '(A)', 'Pop (apply and remove) or Apply (keep stash)?' |
| 1464 | + print '(A)', 'Press ''p'' to pop, ''a'' to apply, any other key to cancel.' |
| 1465 | + call read_key(key) |
| 1466 | + |
| 1467 | + if (key == 'p' .or. key == 'P') then |
| 1468 | + ! Pop the stash (apply and remove) |
| 1469 | + print '(A)', 'Popping stash...' |
| 1470 | + write(command, '(A,A,A)') 'git stash pop "', trim(command), '" 2>&1' |
| 1471 | + call execute_command_line(trim(command), exitstat=status_code) |
| 1472 | + |
| 1473 | + if (status_code == 0) then |
| 1474 | + print '(A)', achar(27) // '[32m✓ Stash popped successfully!' // achar(27) // '[0m' |
| 1475 | + success = .true. |
| 1476 | + else |
| 1477 | + print '(A)', achar(27) // '[31m✗ Stash pop failed (conflicts?)' // achar(27) // '[0m' |
| 1478 | + end if |
| 1479 | + else if (key == 'a' .or. key == 'A') then |
| 1480 | + ! Apply the stash (keep it) |
| 1481 | + print '(A)', 'Applying stash...' |
| 1482 | + write(command, '(A,A,A)') 'git stash apply "', trim(command), '" 2>&1' |
| 1483 | + call execute_command_line(trim(command), exitstat=status_code) |
| 1484 | + |
| 1485 | + if (status_code == 0) then |
| 1486 | + print '(A)', achar(27) // '[32m✓ Stash applied successfully!' // achar(27) // '[0m' |
| 1487 | + success = .true. |
| 1488 | + else |
| 1489 | + print '(A)', achar(27) // '[31m✗ Stash apply failed (conflicts?)' // achar(27) // '[0m' |
| 1490 | + end if |
| 1491 | + else |
| 1492 | + print '(A)', 'Stash operation cancelled.' |
| 1493 | + end if |
| 1494 | + |
| 1495 | + print '(A)', '' |
| 1496 | + print '(A)', 'Press any key to continue...' |
| 1497 | + end subroutine git_stash_pop_apply |
| 1498 | + |
| 1256 | 1499 | end module git_module |