@matheusbsbs / matheus-bagliano / commits / 7e30682ce0
Fork 0

git-gui: Support calling merge tools.

Adds an item to the diff context menu in conflict mode, which invokes a merge tool for the selected file. Tool command-line handling code was ported from git-mergetool. Automatic default tool selection and custom merge tools are not supported. If merge.tool is not set, git-gui defaults to meld. This implementation uses a checkout-index hack in order to retrieve all stages with autocrlf and filters properly applied. It requires temporarily moving the original conflict file out of the way. Signed-off-by: Alexander Gavrilov <angavrilov@gmail.com> Signed-off-by: Shawn O. Pearce <spearce@spearce.org>

Alexander Gavrilov committed Aug 31, 2008 at 00:56 UTC 7e30682ce042fdea1ff2c76226b17d9affe2f9bf
3 files changed +260
git-gui.sh
+7
@@ -657,6 +657,8 @@ proc apply_config {} {
657 }
658
659 set default_config(branch.autosetupmerge) true
660 +set default_config(merge.tool) {}
661 +set default_config(merge.keepbackup) true
662 set default_config(merge.diffstat) true
663 set default_config(merge.summary) false
664 set default_config(merge.verbosity) 2
@@ -2775,6 +2777,11 @@ create_common_diff_popup $ctxm
2777
2778 set ctxmmg .vpane.lower.diff.body.ctxmmg
2779 menu $ctxmmg -tearoff 0
2780 +$ctxmmg add command \
2781 + -label [mc "Run Merge Tool"] \
2782 + -command {merge_resolve_tool}
2783 +lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
2784 +$ctxmmg add separator
2785 $ctxmmg add command \
2786 -label [mc "Use Remote Version"] \
2787 -command {merge_resolve_one 3}
lib/mergetool.tcl
+252
@@ -96,3 +96,255 @@ proc read_merge_stages {fd cont} {
96 eval $cont
97 }
98 }
99 +
100 +proc merge_resolve_tool {} {
101 + global current_diff_path
102 +
103 + merge_load_stages $current_diff_path [list merge_resolve_tool2]
104 +}
105 +
106 +proc merge_resolve_tool2 {} {
107 + global current_diff_path merge_stages
108 +
109 + # Validate the stages
110 + if {$merge_stages(2) eq {} ||
111 + [lindex $merge_stages(2) 0] eq {120000} ||
112 + [lindex $merge_stages(2) 0] eq {160000} ||
113 + $merge_stages(3) eq {} ||
114 + [lindex $merge_stages(3) 0] eq {120000} ||
115 + [lindex $merge_stages(3) 0] eq {160000}
116 + } {
117 + error_popup [mc "Cannot resolve deletion or link conflicts using a tool"]
118 + return
119 + }
120 +
121 + if {![file exists $current_diff_path]} {
122 + error_popup [mc "Conflict file does not exist"]
123 + return
124 + }
125 +
126 + # Determine the tool to use
127 + set tool [get_config merge.tool]
128 + if {$tool eq {}} { set tool meld }
129 +
130 + set merge_tool_path [get_config "mergetool.$tool.path"]
131 + if {$merge_tool_path eq {}} {
132 + switch -- $tool {
133 + emerge { set merge_tool_path "emacs" }
134 + default { set merge_tool_path $tool }
135 + }
136 + }
137 +
138 + # Make file names
139 + set filebase [file rootname $current_diff_path]
140 + set fileext [file extension $current_diff_path]
141 + set basename [lindex [file split $current_diff_path] end]
142 +
143 + set MERGED $current_diff_path
144 + set BASE "./$MERGED.BASE$fileext"
145 + set LOCAL "./$MERGED.LOCAL$fileext"
146 + set REMOTE "./$MERGED.REMOTE$fileext"
147 + set BACKUP "./$MERGED.BACKUP$fileext"
148 +
149 + set base_stage $merge_stages(1)
150 +
151 + # Build the command line
152 + switch -- $tool {
153 + kdiff3 {
154 + if {$base_stage ne {}} {
155 + set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Base)" \
156 + --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE"]
157 + } else {
158 + set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Local)" \
159 + --L2 "$MERGED (Remote)" -o "$MERGED" "$LOCAL" "$REMOTE"]
160 + }
161 + }
162 + tkdiff {
163 + if {$base_stage ne {}} {
164 + set cmdline [list "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"]
165 + } else {
166 + set cmdline [list "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"]
167 + }
168 + }
169 + meld {
170 + set cmdline [list "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"]
171 + }
172 + gvimdiff {
173 + set cmdline [list "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE"]
174 + }
175 + xxdiff {
176 + if {$base_stage ne {}} {
177 + set cmdline [list "$merge_tool_path" -X --show-merged-pane \
178 + -R {Accel.SaveAsMerged: "Ctrl-S"} \
179 + -R {Accel.Search: "Ctrl+F"} \
180 + -R {Accel.SearchForward: "Ctrl-G"} \
181 + --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"]
182 + } else {
183 + set cmdline [list "$merge_tool_path" -X --show-merged-pane \
184 + -R {Accel.SaveAsMerged: "Ctrl-S"} \
185 + -R {Accel.Search: "Ctrl+F"} \
186 + -R {Accel.SearchForward: "Ctrl-G"} \
187 + --merged-file "$MERGED" "$LOCAL" "$REMOTE"]
188 + }
189 + }
190 + opendiff {
191 + if {$base_stage ne {}} {
192 + set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED"]
193 + } else {
194 + set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED"]
195 + }
196 + }
197 + ecmerge {
198 + if {$base_stage ne {}} {
199 + set cmdline [list "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED"]
200 + } else {
201 + set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED"]
202 + }
203 + }
204 + emerge {
205 + if {$base_stage ne {}} {
206 + set cmdline [list "$merge_tool_path" -f emerge-files-with-ancestor-command \
207 + "$LOCAL" "$REMOTE" "$BASE" "$basename"]
208 + } else {
209 + set cmdline [list "$merge_tool_path" -f emerge-files-command \
210 + "$LOCAL" "$REMOTE" "$basename"]
211 + }
212 + }
213 + vimdiff {
214 + error_popup [mc "Not a GUI merge tool: '%s'" $tool]
215 + return
216 + }
217 + default {
218 + error_popup [mc "Unsupported merge tool '%s'" $tool]
219 + return
220 + }
221 + }
222 +
223 + merge_tool_start $cmdline $MERGED $BACKUP [list $BASE $LOCAL $REMOTE]
224 +}
225 +
226 +proc delete_temp_files {files} {
227 + foreach fname $files {
228 + file delete $fname
229 + }
230 +}
231 +
232 +proc merge_tool_get_stages {target stages} {
233 + global merge_stages
234 +
235 + set i 1
236 + foreach fname $stages {
237 + if {$merge_stages($i) eq {}} {
238 + file delete $fname
239 + } else {
240 + # A hack to support autocrlf properly
241 + git checkout-index -f --stage=$i -- $target
242 + file rename -force -- $target $fname
243 + }
244 + incr i
245 + }
246 +}
247 +
248 +proc merge_tool_start {cmdline target backup stages} {
249 + global merge_stages mtool_target mtool_tmpfiles mtool_fd mtool_mtime
250 +
251 + if {[info exists mtool_fd]} {
252 + if {[ask_popup [mc "Merge tool is already running, terminate it?"]] eq {yes}} {
253 + catch { kill_file_process $mtool_fd }
254 + catch { close $mtool_fd }
255 + unset mtool_fd
256 +
257 + set old_backup [lindex $mtool_tmpfiles end]
258 + file rename -force -- $old_backup $mtool_target
259 + delete_temp_files $mtool_tmpfiles
260 + } else {
261 + return
262 + }
263 + }
264 +
265 + # Save the original file
266 + file rename -force -- $target $backup
267 +
268 + # Get the blobs; it destroys $target
269 + if {[catch {merge_tool_get_stages $target $stages} err]} {
270 + file rename -force -- $backup $target
271 + delete_temp_files $stages
272 + error_popup [mc "Error retrieving versions:\n%s" $err]
273 + return
274 + }
275 +
276 + # Restore the conflict file
277 + file copy -force -- $backup $target
278 +
279 + # Initialize global state
280 + set mtool_target $target
281 + set mtool_mtime [file mtime $target]
282 + set mtool_tmpfiles $stages
283 +
284 + lappend mtool_tmpfiles $backup
285 +
286 + # Force redirection to avoid interpreting output on stderr
287 + # as an error, and launch the tool
288 + lappend cmdline {2>@1}
289 +
290 + if {[catch { set mtool_fd [_open_stdout_stderr $cmdline] } err]} {
291 + delete_temp_files $mtool_tmpfiles
292 + error_popup [mc "Could not start the merge tool:\n\n%s" $err]
293 + return
294 + }
295 +
296 + ui_status [mc "Running merge tool..."]
297 +
298 + fconfigure $mtool_fd -blocking 0 -translation binary -encoding binary
299 + fileevent $mtool_fd readable [list read_mtool_output $mtool_fd]
300 +}
301 +
302 +proc read_mtool_output {fd} {
303 + global mtool_fd mtool_tmpfiles
304 +
305 + read $fd
306 + if {[eof $fd]} {
307 + unset mtool_fd
308 +
309 + fconfigure $fd -blocking 1
310 + merge_tool_finish $fd
311 + }
312 +}
313 +
314 +proc merge_tool_finish {fd} {
315 + global mtool_tmpfiles mtool_target mtool_mtime
316 +
317 + set backup [lindex $mtool_tmpfiles end]
318 + set failed 0
319 +
320 + # Check the return code
321 + if {[catch {close $fd} err]} {
322 + set failed 1
323 + if {$err ne {child process exited abnormally}} {
324 + error_popup [strcat [mc "Merge tool failed."] "\n\n$err"]
325 + }
326 + }
327 +
328 + # Check the modification time of the target file
329 + if {!$failed && [file mtime $mtool_target] eq $mtool_mtime} {
330 + if {[ask_popup [mc "File %s unchanged, still accept as resolved?" \
331 + [short_path $mtool_target]]] ne {yes}} {
332 + set failed 1
333 + }
334 + }
335 +
336 + # Finish
337 + if {$failed} {
338 + file rename -force -- $backup $mtool_target
339 + delete_temp_files $mtool_tmpfiles
340 + ui_status [mc "Merge tool failed."]
341 + } else {
342 + if {[is_config_true merge.keepbackup]} {
343 + file rename -force -- $backup "$mtool_target.orig"
344 + }
345 +
346 + delete_temp_files $mtool_tmpfiles
347 +
348 + merge_add_resolution $mtool_target
349 + }
350 +}
lib/option.tcl
+1
@@ -119,6 +119,7 @@ proc do_options {} {
119 {b merge.summary {mc "Summarize Merge Commits"}}
120 {i-1..5 merge.verbosity {mc "Merge Verbosity"}}
121 {b merge.diffstat {mc "Show Diffstat After Merge"}}
122 + {t merge.tool {mc "Use Merge Tool"}}
123
124 {b gui.trustmtime {mc "Trust File Modification Timestamps"}}
125 {b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}}