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"}}