added support for username and pat prompting to push when credentials arent found by git

This commit is contained in:
2025-06-29 17:22:43 -06:00
parent 18f591e79f
commit a039053e9f
7 changed files with 428 additions and 169 deletions

View File

@@ -20,6 +20,7 @@
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -1110,7 +1111,54 @@ int lsh_gg(char **args) {
} else if (strcmp(args[1], "p") == 0) {
system("git pull");
} else if (strcmp(args[1], "ps") == 0) {
system("git push");
// Try push without credentials first - force git to fail without prompting
int result = system("GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 git push </dev/null 2>/dev/null");
// If push failed, try with credentials
if (result != 0) {
char username[256] = "";
char token[256] = "";
printf("Push failed. Authentication required.\n");
printf("GitHub Username: ");
fflush(stdout);
if (fgets(username, sizeof(username), stdin)) {
// Remove newline
username[strcspn(username, "\n")] = 0;
printf("Personal Access Token: ");
fflush(stdout);
// Hide token input
system("stty -echo");
if (fgets(token, sizeof(token), stdin)) {
token[strcspn(token, "\n")] = 0;
system("stty echo");
printf("\n");
printf("Attempting authenticated push...\n");
result = execute_git_with_auth("git push", username, token);
// Clear credentials from memory for security
memset(username, 0, sizeof(username));
memset(token, 0, sizeof(token));
if (result == 0) {
printf("Push successful!\n");
} else {
printf("Push failed. Please check your credentials and try again.\n");
}
} else {
system("stty echo");
printf("\nAuthentication cancelled.\n");
}
} else {
printf("Authentication cancelled.\n");
}
} else {
printf("Push successful!\n");
}
} else if (strcmp(args[1], "a") == 0) {
system("git add .");
} else if (strcmp(args[1], "l") == 0) {
@@ -1129,6 +1177,17 @@ int lsh_gg(char **args) {
} else {
printf("Please specify a branch to checkout\n");
}
} else if (strcmp(args[1], "debug") == 0) {
printf("=== Git Debug Log ===\n");
if (access("/tmp/git_debug.log", F_OK) == 0) {
system("cat /tmp/git_debug.log");
} else {
printf("No debug log found. Try pushing to generate debug info.\n");
}
printf("\n=== End Debug Log ===\n");
} else if (strcmp(args[1], "debug-clear") == 0) {
system("rm -f /tmp/git_debug.log");
printf("Debug log cleared.\n");
} else {
printf("Unknown git command shorthand: %s\n", args[1]);
}

Binary file not shown.

Binary file not shown.

View File

@@ -1812,89 +1812,136 @@ int amend_commit(NCursesDiffViewer *viewer) {
return 0;
}
int get_github_credentials(char *username, int username_len, char *token,
int token_len) {
if (!username || !token)
// Helper function to check if a character is safe for PAT/username input
static int is_safe_input_char(int ch) {
// Allow most printable ASCII but exclude problematic characters
if (ch < 32 || ch > 126) return 0; // Non-printable
if (ch == '?' || ch == 127) return 0; // Question mark and DEL
if (ch == '\x1b') return 0; // ESC
return 1; // Allow everything else
}
int get_single_input(const char *title, const char *prompt, char *input, int input_len, int is_password) {
if (!input)
return 0;
int dialog_width = 60;
int dialog_height = 10;
int start_x = COLS / 2 - dialog_width / 2;
int start_y = LINES / 2 - dialog_height / 2;
// Debug logging
FILE *debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "\n=== Starting input for: %s ===\n", prompt);
fclose(debug_file);
}
WINDOW *cred_win = newwin(dialog_height, dialog_width, start_y, start_x);
if (!cred_win)
// Make dialog much wider for PAT input - 80% of screen width, minimum 80 characters
int dialog_width = (COLS * 8) / 10;
if (dialog_width < 80) dialog_width = 80;
if (dialog_width > 120) dialog_width = 120;
int dialog_height = 4; // Keep it simple and compact
int start_x = (COLS - dialog_width) / 2;
int start_y = (LINES - dialog_height) / 2;
// Ensure we're properly set up for ncurses
if (!stdscr) {
return 0;
}
WINDOW *input_win = newwin(dialog_height, dialog_width, start_y, start_x);
if (!input_win)
return 0;
char temp_username[256] = "";
char temp_token[256] = "";
int current_field = 0;
int username_pos = 0;
int token_pos = 0;
char temp_input[512] = ""; // Larger buffer for long PATs
int input_pos = 0;
// Enable keypad for special keys and disable timeout for paste
keypad(input_win, TRUE);
nodelay(input_win, FALSE);
notimeout(input_win, TRUE);
curs_set(1);
while (1) {
werase(cred_win);
box(cred_win, 0, 0);
werase(input_win);
box(input_win, 0, 0);
mvwprintw(cred_win, 1, 2, "Github Authentication Required");
if (current_field == 0) {
wattron(cred_win, COLOR_PAIR(4));
mvwprintw(cred_win, 3, 2, "Username: %s", temp_username);
wattroff(cred_win, COLOR_PAIR(4));
// Title with helpful instructions for PAT
if (is_password) {
mvwprintw(input_win, 1, 2, "%s (Ctrl+V to paste):", prompt);
} else {
mvwprintw(cred_win, 3, 2, "Username: %s", temp_username);
mvwprintw(input_win, 1, 2, "%s:", prompt);
}
if (current_field == 1) {
wattron(cred_win, COLOR_PAIR(4));
mvwprintw(cred_win, 4, 2, "Token: ");
// Clear the input area
for (int x = 2; x < dialog_width - 2; x++) {
mvwaddch(input_win, 2, x, ' ');
}
for (int i = 0; i < token_pos; i++) {
waddch(cred_win, '*');
// Input area - much simpler approach
int max_display = dialog_width - 6; // Leave room for borders and padding
if (is_password) {
// For passwords, show simple asterisks without complex scrolling
mvwprintw(input_win, 2, 2, "");
int display_count = (input_pos > max_display) ? max_display : input_pos;
for (int i = 0; i < display_count; i++) {
waddch(input_win, '*');
}
wattroff(cred_win, COLOR_PAIR(4));
} else {
mvwprintw(cred_win, 4, 2, "Token: ");
for (int i = 0; i < token_pos; i++) {
waddch(cred_win, '*');
// Show length if PAT is long
if (input_pos > max_display) {
wprintw(input_win, " (%d total)", input_pos);
}
}
mvwprintw(cred_win, 6, 2, "Get token at: github.com/settings/tokens");
mvwprintw(cred_win, 7, 2,
"Tab: switch fields | Enter: confirm | Esc: cancel");
if (current_field == 0) {
wmove(cred_win, 3, 12 + username_pos);
} else {
wmove(cred_win, 4, 12 + token_pos);
// Regular text input
mvwprintw(input_win, 2, 2, "%s", temp_input);
}
wrefresh(cred_win);
// Simple cursor positioning
int cursor_x = 2 + ((input_pos > max_display) ? max_display : input_pos);
if (cursor_x >= dialog_width - 1) cursor_x = dialog_width - 2;
wmove(input_win, 2, cursor_x);
wrefresh(input_win);
int ch = getch();
int ch = wgetch(input_win);
// Debug log key presses
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Key pressed: %d (0x%x) '%c'\n", ch, ch, (ch >= 32 && ch <= 126) ? ch : '?');
fclose(debug_file);
}
switch (ch) {
case 27:
delwin(cred_win);
case 27: // Escape
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "User cancelled with Escape\n");
fclose(debug_file);
}
delwin(input_win);
curs_set(0);
return 0;
case '\t':
current_field = (current_field + 1) % 2;
break;
case '\n':
case '\r':
if (strlen(temp_username) > 0 && strlen(temp_token) > 0) {
strncpy(username, temp_username, username_len - 1);
username[username_len - 1] = '\0';
strncpy(token, temp_token, token_len - 1);
token[token_len - 1] = '\0';
delwin(cred_win);
case KEY_ENTER:
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Enter key detected (key: %d), input length: %d\n", ch, input_pos);
fclose(debug_file);
}
if (input_pos > 0) {
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
if (is_password) {
fprintf(debug_file, "Input completed, length: %d characters\n", input_pos);
} else {
fprintf(debug_file, "Input completed: '%s'\n", temp_input);
}
fclose(debug_file);
}
strncpy(input, temp_input, input_len - 1);
input[input_len - 1] = '\0';
delwin(input_win);
curs_set(0);
return 1;
}
@@ -1903,44 +1950,172 @@ int get_github_credentials(char *username, int username_len, char *token,
case KEY_BACKSPACE:
case 127:
case 8:
if (current_field == 0 && username_pos > 0) {
username_pos--;
temp_username[username_pos] = '\0';
} else if (current_field == 1 && token_pos > 0) {
token_pos--;
temp_token[token_pos] = '\0';
if (input_pos > 0) {
input_pos--;
temp_input[input_pos] = '\0';
}
break;
// Handle Ctrl+V paste (ASCII 22) and other paste-like sequences
case 22:
case 200: // Bracketed paste start
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Paste detected (key: %d), starting paste mode...\n", ch);
fclose(debug_file);
}
// Clear current input and start fresh for paste
input_pos = 0;
temp_input[0] = '\0';
// Set non-blocking and read all available characters
nodelay(input_win, TRUE);
usleep(50000); // Longer delay to let entire paste buffer fill
int paste_count = 0;
char paste_buffer[512] = {0};
int paste_pos = 0;
// Read all available characters
while (paste_pos < 511) {
int paste_ch = wgetch(input_win);
if (paste_ch == ERR) break; // No more characters
if (paste_ch == 201) break; // Bracketed paste end
// Only collect printable ASCII characters (exclude backspaces!)
if (paste_ch >= 32 && paste_ch <= 126) {
paste_buffer[paste_pos] = paste_ch;
paste_pos++;
paste_count++;
}
// Skip backspaces (263), control chars, etc.
}
nodelay(input_win, FALSE); // Back to blocking mode
paste_buffer[paste_pos] = '\0';
// Clean up the pasted content - remove spaces and special chars
for (int i = 0; i < paste_pos && input_pos < 511; i++) {
char c = paste_buffer[i];
// Only keep alphanumeric and safe special characters for PAT
if ((c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
c == '_' || c == '-') {
temp_input[input_pos] = c;
input_pos++;
}
}
temp_input[input_pos] = '\0';
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Paste cleanup: raw chars=%d, final chars=%d\n", paste_count, input_pos);
if (is_password) {
fprintf(debug_file, "Final PAT length: %d\n", input_pos);
} else {
fprintf(debug_file, "Final input: '%s'\n", temp_input);
}
fclose(debug_file);
}
break;
default:
if (ch >= 32 && ch <= 126) {
if (current_field == 0 && username_pos < 256) {
temp_username[username_pos] = ch;
username_pos++;
temp_username[username_pos] = '\0';
} else if (current_field == 1 && token_pos < 256) {
temp_token[token_pos] = ch;
token_pos++;
temp_token[token_pos] = '\0';
// Accept basic ASCII characters only (no spaces for passwords)
if (ch >= 32 && ch <= 126 && input_pos < 511) {
if (is_password && ch == ' ') {
// Skip spaces in password input
break;
}
temp_input[input_pos] = ch;
input_pos++;
temp_input[input_pos] = '\0';
}
break;
}
}
}
int get_github_credentials(char *username, int username_len, char *token,
int token_len) {
if (!username || !token)
return 0;
FILE *debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "\n=== Getting GitHub credentials ===\n");
fclose(debug_file);
}
// Get username first
if (!get_single_input("GitHub Authentication", "Username", username, username_len, 0)) {
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Username input cancelled\n");
fclose(debug_file);
}
return 0;
}
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Username entered: '%s'\n", username);
fclose(debug_file);
}
// Get token second
if (!get_single_input("GitHub Authentication", "PAT", token, token_len, 1)) {
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "PAT input cancelled\n");
fclose(debug_file);
}
return 0;
}
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "PAT entered, length: %zu characters\n", strlen(token));
fclose(debug_file);
}
return 1;
}
int execute_git_with_auth(const char *base_cmd, const char *username,
const char *token) {
if (!base_cmd || !username || !token)
return 1;
FILE *debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "\n=== Executing git with auth (SAFE VERSION) ===\n");
fprintf(debug_file, "Base command: %s\n", base_cmd);
fprintf(debug_file, "Username: %s\n", username);
fprintf(debug_file, "Token length: %zu\n", strlen(token));
fclose(debug_file);
}
// Get remote URL to determine auth format
char remote_url[1024] = "";
FILE *fp = popen("git config --get remote.origin.url 2>/dev/null", "r");
if (!fp)
if (!fp) {
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "ERROR: Could not get remote URL\n");
fclose(debug_file);
}
return 1;
}
if (fgets(remote_url, sizeof(remote_url), fp) == NULL) {
pclose(fp);
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "ERROR: No remote URL found\n");
fclose(debug_file);
}
return 1;
}
pclose(fp);
@@ -1949,27 +2124,66 @@ int execute_git_with_auth(const char *base_cmd, const char *username,
if (newline)
*newline = '\0';
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Remote URL: %s\n", remote_url);
fclose(debug_file);
}
// Create authenticated command using environment variables - SAFE APPROACH
char auth_cmd[4096];
char auth_url[2048];
if (strstr(remote_url, "https://github.com/")) {
char *repo_part = remote_url + strlen("https://github.com/");
snprintf(auth_url, sizeof(auth_url), "https://%s:%s@github.com/%s",
username, token, repo_part);
} else if (strstr(remote_url, "git@github.com:")) {
char *repo_part = strchr(remote_url, ':') + 1;
if (strstr(repo_part, ".git")) {
*(strstr(repo_part, ".git")) = '\0';
char repo_clean[512];
strncpy(repo_clean, repo_part, sizeof(repo_clean) - 1);
repo_clean[sizeof(repo_clean) - 1] = '\0';
if (strstr(repo_clean, ".git")) {
*(strstr(repo_clean, ".git")) = '\0';
}
snprintf(auth_url, sizeof(auth_url), "https://%s%s@github.com%s", username,
token, repo_part);
snprintf(auth_url, sizeof(auth_url), "https://%s:%s@github.com/%s",
username, token, repo_clean);
} else {
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "ERROR: Unsupported remote URL format: %s\n", remote_url);
fclose(debug_file);
}
return 1;
}
char auth_cmd[4096];
snprintf(auth_cmd, sizeof(auth_cmd), "%s %s >/dev/null >&1", base_cmd,
auth_url);
// Create a simple git push command with authenticated URL
snprintf(auth_cmd, sizeof(auth_cmd),
"git push %s", auth_url);
return system(auth_cmd);
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "SAFE: Using direct git push with auth URL\n");
fprintf(debug_file, "Executing: git push ***auth_url***\n");
fclose(debug_file);
}
// Execute with authentication - this does NOT modify the repository
int result = system(auth_cmd);
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "SAFE: No repository modification needed - git -c used\n");
fprintf(debug_file, "Git command result: %d\n", result);
if (result == 0) {
fprintf(debug_file, "SUCCESS: Git push with authentication succeeded\n");
} else {
fprintf(debug_file, "FAILED: Git push with authentication failed\n");
}
fclose(debug_file);
}
return result;
}
/**
@@ -2072,102 +2286,86 @@ int push_commit(NCursesDiffViewer *viewer, int commit_index) {
strcpy(push_cmd, "git push origin");
}
push_pid = fork();
if (push_pid == 0) {
// Child process: try normal push first
exit(system(is_diverged
? "git push --force-with-lease origin >/dev/null 2>&1"
: "git push origin >/dev/null 2>&1"));
}
// Try push without credentials first - force git to fail without prompting
// Use multiple methods to ensure git doesn't prompt for credentials
result = system(is_diverged
? "GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 SSH_ASKPASS=/bin/false git push --force-with-lease origin </dev/null >/dev/null 2>/dev/null"
: "GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 SSH_ASKPASS=/bin/false git push origin </dev/null >/dev/null 2>/dev/null");
// Parent process: wait and check if authentication is needed
if (push_pid > 0) {
int status;
int spinner_counter = 0;
while (waitpid(push_pid, &status, WNOHANG) == 0) {
// Update spinner animation
viewer->branch_animation_frame = spinner_counter;
spinner_counter = (spinner_counter + 1) % 40;
render_file_list_window(viewer);
render_file_content_window(viewer);
render_commit_list_window(viewer);
render_branch_list_window(viewer);
render_stash_list_window(viewer);
render_status_bar(viewer);
usleep(100000); // 100ms delay
// If push failed, immediately try with authentication
if (result != 0) {
FILE *debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "\n=== PUSH FAILED - Starting credential flow ===\n");
fprintf(debug_file, "Initial push result: %d\n", result);
fclose(debug_file);
}
// Check if push failed (likely due to authentication)
if (WIFEXITED(status)) {
result = WEXITSTATUS(status);
} else {
result = 1;
char username[256] = "";
char token[512] = ""; // Larger buffer for long PATs
// Save current terminal state and fully clear screen
endwin();
clear();
refresh();
// Reinitialize ncurses for clean credential dialog
initscr();
noecho();
cbreak();
keypad(stdscr, TRUE);
start_color();
// Initialize color pairs for the dialog
init_pair(1, COLOR_WHITE, COLOR_BLACK);
init_pair(2, COLOR_GREEN, COLOR_BLACK);
init_pair(3, COLOR_YELLOW, COLOR_BLACK);
init_pair(4, COLOR_CYAN, COLOR_BLACK);
init_pair(5, COLOR_RED, COLOR_BLACK);
init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
clear();
refresh();
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Ncurses reinitialized, calling credential dialog\n");
fclose(debug_file);
}
// If push failed, try with authentication
if (result != 0) {
char username[256] = "";
char token[256] = "";
// Clear screen and get credentials
clear();
refresh();
if (get_github_credentials(username, sizeof(username), token,
sizeof(token))) {
// Try authenticated push
result = execute_git_with_auth(push_cmd, username, token);
// Clear credentials from memory for security
memset(username, 0, sizeof(username));
memset(token, 0, sizeof(token));
} else {
result = 1; // User cancelled
if (get_github_credentials(username, sizeof(username), token,
sizeof(token))) {
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Credentials obtained successfully, attempting authenticated push\n");
fclose(debug_file);
}
// Force complete screen refresh after credential dialog
clear();
refresh();
}
} else {
result = 1; // Fork failed
}
// Try authenticated push
result = execute_git_with_auth(push_cmd, username, token);
// Parent process: animate the spinner while push is happening
if (push_pid > 0) {
int status;
int spinner_counter = 0;
while (waitpid(push_pid, &status, WNOHANG) == 0) {
// Update spinner animation
viewer->branch_animation_frame = spinner_counter;
spinner_counter = (spinner_counter + 1) % 40;
// Refresh ALL windows, not just branch window
render_file_list_window(viewer);
render_file_content_window(viewer);
render_commit_list_window(viewer);
render_branch_list_window(viewer);
render_stash_list_window(viewer);
render_status_bar(viewer);
// Small delay for animation timing
usleep(100000); // 100ms delay
}
// Get the exit status of the push command
if (WIFEXITED(status)) {
result = WEXITSTATUS(status);
// Clear credentials from memory for security
memset(username, 0, sizeof(username));
memset(token, 0, sizeof(token));
} else {
result = 1; // Error
debug_file = fopen("/tmp/git_debug.log", "a");
if (debug_file) {
fprintf(debug_file, "Credential dialog cancelled by user\n");
fclose(debug_file);
}
result = 1; // User cancelled
}
} else {
result = 1; // Fork failed
// Force complete screen refresh after credential dialog
clear();
refresh();
}
// Update UI after push attempt
get_ncurses_changed_files(viewer);
get_commit_history(viewer);
get_ncurses_git_branches(viewer);
if (result == 0) {
// Immediately transition to "Pushed!" animation
viewer->sync_status = SYNC_STATUS_PUSHED_APPEARING;

View File

@@ -430,6 +430,8 @@ int load_file_preview(NCursesDiffViewer *viewer, const char *filename);
*/
void update_preview_for_current_selection(NCursesDiffViewer *viewer);
int get_single_input(const char *title, const char *prompt, char *input, int input_len, int is_password);
int get_github_credentials(char *username, int username_len, char *token,
int token_len);

Binary file not shown.

BIN
shell

Binary file not shown.