11-- Git utility functions for interactive operations
22local M = {}
33
4+ -- Get the parent branch (dev > develop > main > master) - always prefer origin
5+ function M .get_parent_branch ()
6+ -- Try branches in priority order - ALWAYS prefer origin/remote branches
7+ local branches = { " dev" , " develop" , " main" , " master" }
8+
9+ -- First pass: check if remote branches exist locally
10+ for _ , b in ipairs (branches ) do
11+ local exists = vim .fn .system (" git rev-parse --verify origin/" .. b .. " 2>/dev/null" )
12+ if vim .v .shell_error == 0 then
13+ return " origin/" .. b
14+ end
15+ end
16+
17+ -- If we didn't find dev/develop remotely, try fetching them specifically
18+ -- This is faster than fetching everything
19+ vim .fn .system (" git fetch origin dev:refs/remotes/origin/dev 2>/dev/null" )
20+ if vim .v .shell_error == 0 then
21+ return " origin/dev"
22+ end
23+
24+ vim .fn .system (" git fetch origin develop:refs/remotes/origin/develop 2>/dev/null" )
25+ if vim .v .shell_error == 0 then
26+ return " origin/develop"
27+ end
28+
29+ -- Fallback: try to detect default branch from origin/HEAD
30+ local default_branch = vim .fn .system (" git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/@@'" ):gsub (" %s+" , " " )
31+ if default_branch ~= " " then
32+ return default_branch
33+ end
34+
35+ -- Last resort: check local branches
36+ for _ , b in ipairs (branches ) do
37+ local exists = vim .fn .system (" git rev-parse --verify " .. b .. " 2>/dev/null" )
38+ if vim .v .shell_error == 0 then
39+ return b
40+ end
41+ end
42+
43+ return " origin/main" -- Default fallback
44+ end
45+
46+ -- Get the default branch name (backward compatibility)
47+ function M .get_default_branch_name ()
48+ return M .get_parent_branch ()
49+ end
50+
451-- Interactive diff with menu selection
552function M .open_diff_interactive ()
6- -- Check if in jj repo to provide appropriate prompt
7- local jj_root = vim .fn .system (" jj root 2>/dev/null" ):gsub (" %s+" , " " )
8- local is_jj = vim .v .shell_error == 0 and jj_root ~= " "
9-
10- local options = is_jj and {
11- { " @-..@" , " Changes from parent to current" },
12- { " @--..@" , " Changes from grandparent to current" },
13- { " main@origin..@" , " All changes from remote main" },
14- { " @-" , " Show parent change" },
15- { " @" , " Show current change" },
16- { " custom" , " Enter custom revision..." },
17- } or {
53+ local default_branch = M .get_parent_branch ()
54+
55+ -- Find merge base for more accurate feature branch diff
56+ local merge_base = vim .fn .system (" git merge-base HEAD " .. default_branch ):gsub (" %s+" , " " )
57+ local feature_branch_pattern = merge_base ~= " " and (merge_base .. " ..HEAD" ) or (default_branch .. " ..HEAD" )
58+ local feature_branch_desc = merge_base ~= " " and " Your changes (from merge base)" or (" Feature branch changes from " .. default_branch )
59+
60+ -- Use git-compatible options for both git and jj repos
61+ local options = {
62+ { " " , " Working directory changes" },
1863 { " HEAD" , " Last commit" },
1964 { " HEAD~3" , " Last 3 commits" },
20- { " main..HEAD " , " Feature branch changes " },
21- { " origin/main ..HEAD" , " Changes from remote main " },
65+ { feature_branch_pattern , feature_branch_desc },
66+ { default_branch .. " ..HEAD" , " All changes from " .. default_branch .. " tip " },
2267 { " HEAD^!" , " Only last commit (no context)" },
2368 { " @{u}..HEAD" , " Unpushed changes" },
2469 { " @{1}" , " Previous HEAD position" },
2570 { " stash" , " View stashed changes" },
2671 { " --staged" , " Only staged changes" },
72+ { " --cached" , " Staged changes (cached)" },
2773 { " custom" , " Enter custom revision..." },
2874 }
2975
@@ -44,14 +90,13 @@ function M.open_diff_interactive()
4490
4591 if pattern == " custom" then
4692 -- Fall back to manual input
47- local prompt = is_jj
48- and " Revision to diff (@, @-, @-..@, main..@): "
49- or " Revision to diff (HEAD, HEAD~3, main..HEAD, stash): "
50-
51- local input = vim .fn .input (prompt )
93+ local input = vim .fn .input (" Revision to diff (HEAD, HEAD~3, main..HEAD, stash): " )
5294 if input ~= " " then
5395 vim .cmd (" DiffviewOpen " .. input )
5496 end
97+ elseif pattern == " " then
98+ -- Working directory changes
99+ vim .cmd (" DiffviewOpen" )
55100 else
56101 vim .cmd (" DiffviewOpen " .. pattern )
57102 end
@@ -64,8 +109,12 @@ function M.file_history_interactive()
64109 { " %" , " Current file" },
65110 { " ." , " Entire repository" },
66111 { " % --follow" , " Current file (follow renames)" },
112+ { " % --range=" .. vim .fn .line (" ." ) .. " ,+1" , " Current line history" },
113+ { vim .fn .expand (" %:h" ), " Current directory" },
67114 { " src/" , " src/ directory" },
68115 { " *.lua" , " All Lua files" },
116+ { " *.py" , " All Python files" },
117+ { " *.js *.ts *.jsx *.tsx" , " All JavaScript/TypeScript files" },
69118 { " custom" , " Enter custom path..." },
70119 }
71120
98147
99148-- Review PR changes
100149function M .review_pr_changes ()
101- -- Check if in jj repo
102- local jj_root = vim .fn .system (" jj root 2>/dev/null" ):gsub (" %s+" , " " )
103- local is_jj = vim .v .shell_error == 0 and jj_root ~= " "
104-
105- if is_jj then
106- -- For jj, show diff from main branch to current change
107- local main_commit = vim .fn .system (" jj log --no-graph -r 'main' -T 'commit_id' --limit 1 2>/dev/null" ):gsub (" %s+" , " " )
150+ local parent_branch = M .get_parent_branch ()
108151
109- if vim .v .shell_error == 0 and main_commit ~= " " then
110- vim .cmd (" DiffviewOpen " .. main_commit .. " ..HEAD" )
111- else
112- -- Fallback: try main@origin
113- main_commit = vim .fn .system (" jj log --no-graph -r 'main@origin' -T 'commit_id' --limit 1 2>/dev/null" ):gsub (" %s+" , " " )
114- if vim .v .shell_error == 0 and main_commit ~= " " then
115- vim .cmd (" DiffviewOpen " .. main_commit .. " ..HEAD" )
116- else
117- -- Final fallback: show diff from parent to current
118- vim .cmd (" DiffviewOpen HEAD~..HEAD" )
119- end
120- end
152+ -- Find the merge base with the parent branch
153+ local merge_base = vim .fn .system (" git merge-base HEAD " .. parent_branch ):gsub (" %s+" , " " )
154+ if vim .v .shell_error == 0 and merge_base ~= " " then
155+ vim .cmd (" DiffviewOpen " .. merge_base .. " ..HEAD" )
121156 else
122- -- For git, find the merge base with main/master
123- local main_branch = vim .fn .system (" git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@'" ):gsub (" %s+" , " " )
124- if main_branch == " " then
125- main_branch = " main"
126- -- Try master if main doesn't exist
127- local branch_exists = vim .fn .system (" git rev-parse --verify " .. main_branch .. " 2>/dev/null" )
128- if vim .v .shell_error ~= 0 then
129- main_branch = " master"
130- end
131- end
132-
133- local merge_base = vim .fn .system (" git merge-base HEAD " .. main_branch ):gsub (" %s+" , " " )
134- if vim .v .shell_error == 0 and merge_base ~= " " then
135- vim .cmd (" DiffviewOpen " .. merge_base .. " ..HEAD" )
136- else
137- vim .notify (" Could not find merge base with " .. main_branch , vim .log .levels .ERROR )
138- end
157+ vim .notify (" Could not find merge base with " .. parent_branch , vim .log .levels .ERROR )
139158 end
140159end
141160
@@ -165,4 +184,86 @@ function M.delete_worktree()
165184 })
166185end
167186
187+ -- Visual selection history
188+ function M .visual_selection_history ()
189+ -- Get visual selection range
190+ local start_line = vim .fn .line (" '<" )
191+ local end_line = vim .fn .line (" '>" )
192+
193+ -- Use line range for file history
194+ vim .cmd (string.format (" DiffviewFileHistory %%:%d,%d" , start_line , end_line ))
195+ end
196+
197+ -- Single line history using Gitsigns
198+ function M .line_history ()
199+ local ok , gitsigns = pcall (require , " gitsigns" )
200+ if ok then
201+ gitsigns .blame_line ({ full = true })
202+ else
203+ -- Fallback to DiffviewFileHistory for current line
204+ local line = vim .fn .line (" ." )
205+ vim .cmd (string.format (" DiffviewFileHistory %%:%d,%d" , line , line ))
206+ end
207+ end
208+
209+ -- Compare buffer/selection with clipboard
210+ function M .compare_with_clipboard ()
211+ local mode = vim .fn .mode ()
212+ local clipboard = vim .fn .getreg (" +" )
213+
214+ if clipboard == " " then
215+ vim .notify (" Clipboard is empty" , vim .log .levels .WARN )
216+ return
217+ end
218+
219+ -- Create a temporary file with clipboard content
220+ local tmp_file = vim .fn .tempname ()
221+ vim .fn .writefile (vim .split (clipboard , " \n " ), tmp_file )
222+
223+ if mode == " v" or mode == " V" then
224+ -- Visual mode: compare selection with clipboard
225+ -- Get visual selection
226+ local start_line = vim .fn .line (" '<" )
227+ local end_line = vim .fn .line (" '>" )
228+ local lines = vim .fn .getline (start_line , end_line )
229+
230+ -- Create temp file with selection
231+ local selection_file = vim .fn .tempname ()
232+ vim .fn .writefile (lines , selection_file )
233+
234+ -- Open diff view
235+ vim .cmd (" tabnew" )
236+ vim .cmd (" edit " .. selection_file )
237+ vim .cmd (" diffthis" )
238+ vim .cmd (" vsplit " .. tmp_file )
239+ vim .cmd (" diffthis" )
240+ else
241+ -- Normal mode: compare entire buffer with clipboard
242+ vim .cmd (" tabnew %" )
243+ vim .cmd (" diffthis" )
244+ vim .cmd (" vsplit " .. tmp_file )
245+ vim .cmd (" diffthis" )
246+ end
247+ end
248+
249+ -- Diff against parent branch (dev > develop > main > master)
250+ function M .diff_against_default ()
251+ local parent_branch = M .get_parent_branch ()
252+
253+ -- Find the merge base to show only our changes
254+ local merge_base = vim .fn .system (" git merge-base HEAD " .. parent_branch ):gsub (" %s+" , " " )
255+ if vim .v .shell_error == 0 and merge_base ~= " " then
256+ -- Show changes from merge base to HEAD (just our changes)
257+ vim .cmd (" DiffviewOpen " .. merge_base .. " ..HEAD" )
258+ else
259+ -- Fallback to showing diff against branch tip
260+ vim .cmd (" DiffviewOpen " .. parent_branch )
261+ end
262+ end
263+
264+ -- Show all repository history
265+ function M .repo_history ()
266+ vim .cmd (" DiffviewFileHistory" )
267+ end
268+
168269return M
0 commit comments