diff --git a/git_integration.c b/git_integration.c index 1a36c40..4c4b6e8 100644 --- a/git_integration.c +++ b/git_integration.c @@ -248,3 +248,57 @@ int get_repo_url(char *url, size_t url_size) { return 0; } + +/** + * Check if the current branch has diverged from its remote tracking branch + */ +int check_branch_divergence(int *commits_ahead, int *commits_behind) { + if (!commits_ahead || !commits_behind) { + return 0; + } + + *commits_ahead = 0; + *commits_behind = 0; + + // Check if we have a remote tracking branch + FILE *fp = popen("git rev-parse --abbrev-ref @{u} 2>/dev/null", "r"); + if (!fp) { + return 0; + } + + char remote_branch[256]; + if (fgets(remote_branch, sizeof(remote_branch), fp) == NULL) { + pclose(fp); + return 0; // No remote tracking branch + } + pclose(fp); + + // Remove newline + char *newline = strchr(remote_branch, '\n'); + if (newline) { + *newline = '\0'; + } + + // Get commits ahead (local commits not in remote) + fp = popen("git rev-list --count @{u}..HEAD 2>/dev/null", "r"); + if (fp) { + char count_str[32]; + if (fgets(count_str, sizeof(count_str), fp) != NULL) { + *commits_ahead = atoi(count_str); + } + pclose(fp); + } + + // Get commits behind (remote commits not in local) + fp = popen("git rev-list --count HEAD..@{u} 2>/dev/null", "r"); + if (fp) { + char count_str[32]; + if (fgets(count_str, sizeof(count_str), fp) != NULL) { + *commits_behind = atoi(count_str); + } + pclose(fp); + } + + // Branch has diverged if we have commits both ahead and behind + return (*commits_ahead > 0 && *commits_behind > 0) ? 1 : 0; +} diff --git a/git_integration.h b/git_integration.h index e48a058..0e784b7 100644 --- a/git_integration.h +++ b/git_integration.h @@ -47,4 +47,13 @@ int get_git_repo_name(char *repo_name, size_t buffer_size); */ char *get_git_status(void); +/** + * Check if the current branch has diverged from its remote tracking branch + * + * @param commits_ahead Pointer to store number of commits ahead of remote + * @param commits_behind Pointer to store number of commits behind remote + * @return 1 if diverged (both ahead and behind > 0), 0 otherwise + */ +int check_branch_divergence(int *commits_ahead, int *commits_behind); + #endif // GIT_INTEGRATION_H diff --git a/git_integration.o b/git_integration.o index df4ac64..6d09b9b 100644 Binary files a/git_integration.o and b/git_integration.o differ diff --git a/ncurses_diff_viewer.c b/ncurses_diff_viewer.c index b5210bf..55252f0 100644 --- a/ncurses_diff_viewer.c +++ b/ncurses_diff_viewer.c @@ -4,6 +4,7 @@ */ #include "ncurses_diff_viewer.h" +#include "git_integration.h" #include #include #include @@ -488,6 +489,73 @@ void mark_all_files(NCursesDiffViewer *viewer) { } } +/** + * Show confirmation dialog for diverged branch + * Returns 1 for force push, 0 for cancel + */ +int show_diverged_branch_dialog(int commits_ahead, int commits_behind) { + // Save current screen + WINDOW *saved_screen = dupwin(stdscr); + + // Calculate window dimensions + int dialog_width = 60; + int dialog_height = 8; + int start_x = COLS / 2 - dialog_width / 2; + int start_y = LINES / 2 - dialog_height / 2; + + // Create dialog window + WINDOW *dialog_win = newwin(dialog_height, dialog_width, start_y, start_x); + if (!dialog_win) { + if (saved_screen) + delwin(saved_screen); + return 0; + } + + // Draw dialog + wattron(dialog_win, COLOR_PAIR(3)); // Red for warning + box(dialog_win, 0, 0); + + // Title + mvwprintw(dialog_win, 1, 2, "Branch has diverged!"); + + // Message + mvwprintw(dialog_win, 3, 2, "Local: %d commit(s) ahead", commits_ahead); + mvwprintw(dialog_win, 4, 2, "Remote: %d commit(s) ahead", commits_behind); + + // Instructions + mvwprintw(dialog_win, 6, 2, "Force push anyway? (y/N):"); + wattroff(dialog_win, COLOR_PAIR(3)); + + wrefresh(dialog_win); + + // Get user input + int ch; + int result = 0; + + while ((ch = wgetch(dialog_win)) != ERR) { + if (ch == 'y' || ch == 'Y') { + result = 1; + break; + } else if (ch == 'n' || ch == 'N' || ch == 27 || ch == 'q') { // ESC or q or n + result = 0; + break; + } else if (ch == '\n' || ch == '\r') { + result = 0; // Default to no on Enter + break; + } + } + + // Cleanup + delwin(dialog_win); + if (saved_screen) { + touchwin(saved_screen); + wrefresh(saved_screen); + delwin(saved_screen); + } + + return result; +} + /** * Get commit title and message input from user */ @@ -1024,6 +1092,19 @@ int push_commit(NCursesDiffViewer *viewer, int commit_index) { if (!viewer || commit_index < 0 || commit_index >= viewer->commit_count) return 0; + // Check for branch divergence first + int commits_ahead = 0; + int commits_behind = 0; + int is_diverged = check_branch_divergence(&commits_ahead, &commits_behind); + + // If diverged, show confirmation dialog + if (is_diverged) { + if (!show_diverged_branch_dialog(commits_ahead, commits_behind)) { + // User cancelled + return 0; + } + } + // Start pushing animation immediately viewer->sync_status = SYNC_STATUS_PUSHING_APPEARING; viewer->animation_frame = 0; @@ -1033,7 +1114,14 @@ int push_commit(NCursesDiffViewer *viewer, int commit_index) { render_status_bar(viewer); // Do the actual push work - int result = system("git push origin 2>/dev/null >/dev/null"); + int result; + if (is_diverged) { + // Use force push with lease for safety + result = system("git push --force-with-lease origin 2>/dev/null >/dev/null"); + } else { + // Normal push + result = system("git push origin 2>/dev/null >/dev/null"); + } if (result == 0) { // Refresh commit history to get proper push status diff --git a/ncurses_diff_viewer.o b/ncurses_diff_viewer.o index 107113b..38c0fad 100644 Binary files a/ncurses_diff_viewer.o and b/ncurses_diff_viewer.o differ diff --git a/shell b/shell index 13e828c..e4d1f3d 100755 Binary files a/shell and b/shell differ