@@ -16,6 +16,11 @@ |
| 16 | #include "error.h" | 16 | #include "error.h" |
| 17 | #include "utils.h" | 17 | #include "utils.h" |
| 18 | #include "git_ops.h" | 18 | #include "git_ops.h" |
| | 19 | +#include "ssh_manager.h" |
| | 20 | + |
| | 21 | +/* Long-only options (no short form). Values above 0xff avoid colliding with |
| | 22 | + * ASCII short options handled by getopt_long. */ |
| | 23 | +#define OPT_SSH_AGENT_INFO 0x100 |
| 19 | | 24 | |
| 20 | static void print_usage(const char *prog_name) { | 25 | static void print_usage(const char *prog_name) { |
| 21 | printf("Usage: %s [OPTIONS] [COMMAND] [ARGS]\n", prog_name); | 26 | printf("Usage: %s [OPTIONS] [COMMAND] [ARGS]\n", prog_name); |
@@ -28,6 +33,7 @@ static void print_usage(const char *prog_name) { |
| 28 | printf(" status Show current account status\n"); | 33 | printf(" status Show current account status\n"); |
| 29 | printf(" doctor, health Run comprehensive health check\n"); | 34 | printf(" doctor, health Run comprehensive health check\n"); |
| 30 | printf(" config Show configuration file information\n"); | 35 | printf(" config Show configuration file information\n"); |
| | 36 | + printf(" init <shell> Emit shell integration (fish|bash|zsh|sh)\n"); |
| 31 | printf(" <account> Switch to specified account\n"); | 37 | printf(" <account> Switch to specified account\n"); |
| 32 | printf("\nOptions:\n"); | 38 | printf("\nOptions:\n"); |
| 33 | printf(" --global, -g Use global git scope\n"); | 39 | printf(" --global, -g Use global git scope\n"); |
@@ -67,6 +73,8 @@ static int handle_status_command(gitswitch_ctx_t *ctx); |
| 67 | static int handle_switch_command(gitswitch_ctx_t *ctx, const char *identifier); | 73 | static int handle_switch_command(gitswitch_ctx_t *ctx, const char *identifier); |
| 68 | static int handle_doctor_command(gitswitch_ctx_t *ctx); | 74 | static int handle_doctor_command(gitswitch_ctx_t *ctx); |
| 69 | static int handle_config_command(gitswitch_ctx_t *ctx); | 75 | static int handle_config_command(gitswitch_ctx_t *ctx); |
| | 76 | +static int handle_init_command(const char *shell); |
| | 77 | +static const char *detect_shell_from_env(void); |
| 70 | | 78 | |
| 71 | int main(int argc, char *argv[]) { | 79 | int main(int argc, char *argv[]) { |
| 72 | gitswitch_ctx_t ctx; | 80 | gitswitch_ctx_t ctx; |
@@ -88,6 +96,9 @@ int main(int argc, char *argv[]) { |
| 88 | {"dry-run", no_argument, 0, 'n'}, | 96 | {"dry-run", no_argument, 0, 'n'}, |
| 89 | {"global", no_argument, 0, 'g'}, | 97 | {"global", no_argument, 0, 'g'}, |
| 90 | {"local", no_argument, 0, 'l'}, | 98 | {"local", no_argument, 0, 'l'}, |
| | 99 | + /* Compat alias for the Python gitswitch era. Dispatches to `init` |
| | 100 | + * with shell auto-detected from $SHELL so stale rc lines keep working. */ |
| | 101 | + {"ssh-agent-info", no_argument, 0, OPT_SSH_AGENT_INFO}, |
| 91 | {0, 0, 0, 0} | 102 | {0, 0, 0, 0} |
| 92 | }; | 103 | }; |
| 93 | | 104 | |
@@ -129,6 +140,11 @@ int main(int argc, char *argv[]) { |
| 129 | case 'l': | 140 | case 'l': |
| 130 | /* Local scope - will be handled by command handlers */ | 141 | /* Local scope - will be handled by command handlers */ |
| 131 | break; | 142 | break; |
| | 143 | + case OPT_SSH_AGENT_INFO: { |
| | 144 | + int rc = handle_init_command(detect_shell_from_env()); |
| | 145 | + error_cleanup(); |
| | 146 | + return rc; |
| | 147 | + } |
| 132 | default: | 148 | default: |
| 133 | print_usage(argv[0]); | 149 | print_usage(argv[0]); |
| 134 | error_cleanup(); | 150 | error_cleanup(); |
@@ -211,6 +227,8 @@ int main(int argc, char *argv[]) { |
| 211 | exit_code = handle_doctor_command(&ctx); | 227 | exit_code = handle_doctor_command(&ctx); |
| 212 | } else if (strcmp(command, "config") == 0) { | 228 | } else if (strcmp(command, "config") == 0) { |
| 213 | exit_code = handle_config_command(&ctx); | 229 | exit_code = handle_config_command(&ctx); |
| | 230 | + } else if (strcmp(command, "init") == 0) { |
| | 231 | + exit_code = handle_init_command(arg1 ? arg1 : detect_shell_from_env()); |
| 214 | } else { | 232 | } else { |
| 215 | /* Assume it's an account identifier for switching */ | 233 | /* Assume it's an account identifier for switching */ |
| 216 | exit_code = handle_switch_command(&ctx, command); | 234 | exit_code = handle_switch_command(&ctx, command); |
@@ -229,7 +247,8 @@ int main(int argc, char *argv[]) { |
| 229 | strcmp(command, "status") != 0 && | 247 | strcmp(command, "status") != 0 && |
| 230 | strcmp(command, "doctor") != 0 && | 248 | strcmp(command, "doctor") != 0 && |
| 231 | strcmp(command, "health") != 0 && | 249 | strcmp(command, "health") != 0 && |
| 232 | - strcmp(command, "config") != 0) { | 250 | + strcmp(command, "config") != 0 && |
| | 251 | + strcmp(command, "init") != 0) { |
| 233 | /* Assume it's a switch command - may have modified default scope */ | 252 | /* Assume it's a switch command - may have modified default scope */ |
| 234 | should_save = true; | 253 | should_save = true; |
| 235 | } | 254 | } |
@@ -391,3 +410,59 @@ static int handle_config_command(gitswitch_ctx_t *ctx) { |
| 391 | return EXIT_SUCCESS; | 410 | return EXIT_SUCCESS; |
| 392 | } | 411 | } |
| 393 | | 412 | |
| | 413 | +/* Return the basename of $SHELL, or NULL if it can't be determined. The |
| | 414 | + * pointer aliases into the environment string — callers must not free it. */ |
| | 415 | +static const char *detect_shell_from_env(void) { |
| | 416 | + const char *shell = getenv("SHELL"); |
| | 417 | + if (!shell || !*shell) { |
| | 418 | + return NULL; |
| | 419 | + } |
| | 420 | + const char *slash = strrchr(shell, '/'); |
| | 421 | + return slash ? slash + 1 : shell; |
| | 422 | +} |
| | 423 | + |
| | 424 | +/* Emit shell-integration snippet for `shell` on stdout. The snippet sets |
| | 425 | + * SSH_AUTH_SOCK to the stable gitswitch symlink, guarded by a socket test so |
| | 426 | + * sourcing before the first switch (or after /tmp is wiped) is silent. */ |
| | 427 | +static int handle_init_command(const char *shell) { |
| | 428 | + char sock_path[MAX_PATH_LEN]; |
| | 429 | + if (ssh_manager_get_auth_sock_path(sock_path, sizeof(sock_path)) != 0) { |
| | 430 | + fprintf(stderr, "gitswitch: failed to compute SSH_AUTH_SOCK path: %s\n", |
| | 431 | + get_last_error()->message); |
| | 432 | + return EXIT_FAILURE; |
| | 433 | + } |
| | 434 | + |
| | 435 | + if (!shell || !*shell) { |
| | 436 | + fprintf(stderr, |
| | 437 | + "gitswitch: could not detect shell; pass one explicitly:\n" |
| | 438 | + " gitswitch init fish | source\n" |
| | 439 | + " eval \"$(gitswitch init bash)\"\n" |
| | 440 | + " eval \"$(gitswitch init zsh)\"\n"); |
| | 441 | + return EXIT_FAILURE; |
| | 442 | + } |
| | 443 | + |
| | 444 | + if (strcmp(shell, "fish") == 0) { |
| | 445 | + printf("# gitswitch shell integration (fish)\n"); |
| | 446 | + printf("set -l __gitswitch_auth_sock %s\n", sock_path); |
| | 447 | + printf("if test -S $__gitswitch_auth_sock\n"); |
| | 448 | + printf(" set -gx SSH_AUTH_SOCK $__gitswitch_auth_sock\n"); |
| | 449 | + printf("end\n"); |
| | 450 | + printf("set -e __gitswitch_auth_sock\n"); |
| | 451 | + return EXIT_SUCCESS; |
| | 452 | + } |
| | 453 | + |
| | 454 | + if (strcmp(shell, "bash") == 0 || strcmp(shell, "zsh") == 0 || |
| | 455 | + strcmp(shell, "sh") == 0 || strcmp(shell, "dash") == 0 || |
| | 456 | + strcmp(shell, "ksh") == 0) { |
| | 457 | + printf("# gitswitch shell integration (%s)\n", shell); |
| | 458 | + printf("__gitswitch_auth_sock=%s\n", sock_path); |
| | 459 | + printf("[ -S \"$__gitswitch_auth_sock\" ] && export SSH_AUTH_SOCK=\"$__gitswitch_auth_sock\"\n"); |
| | 460 | + printf("unset __gitswitch_auth_sock\n"); |
| | 461 | + return EXIT_SUCCESS; |
| | 462 | + } |
| | 463 | + |
| | 464 | + fprintf(stderr, |
| | 465 | + "gitswitch: unsupported shell '%s' (supported: fish, bash, zsh, sh, dash, ksh)\n", |
| | 466 | + shell); |
| | 467 | + return EXIT_FAILURE; |
| | 468 | +} |