git-gui: Avoid unnecessary global statements when possible
[git-gui.git] / lib / merge.tcl
blob6caf25f2bea19efbad2190587ea5c414604871f0
1 # git-gui branch merge support
2 # Copyright (C) 2006, 2007 Shawn Pearce
4 class merge {
6 field w ; # top level window
7 field w_list ; # widget of available branches
8 field list ; # list of available branches
10 method _can_merge {} {
11 global HEAD commit_type file_states
13 if {[string match amend* $commit_type]} {
14 info_popup {Cannot merge while amending.
16 You must finish amending this commit before starting any type of merge.
18 return 0
21 if {[committer_ident] eq {}} {return 0}
22 if {![lock_index merge]} {return 0}
24 # -- Our in memory state should match the repository.
26 repository_state curType curHEAD curMERGE_HEAD
27 if {$commit_type ne $curType || $HEAD ne $curHEAD} {
28 info_popup {Last scanned state does not match repository state.
30 Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed.
32 The rescan will be automatically started now.
34 unlock_index
35 rescan ui_ready
36 return 0
39 foreach path [array names file_states] {
40 switch -glob -- [lindex $file_states($path) 0] {
41 _O {
42 continue; # and pray it works!
44 U? {
45 error_popup "You are in the middle of a conflicted merge.
47 File [short_path $path] has merge conflicts.
49 You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge.
51 unlock_index
52 return 0
54 ?? {
55 error_popup "You are in the middle of a change.
57 File [short_path $path] is modified.
59 You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise.
61 unlock_index
62 return 0
67 return 1
70 method _refs {} {
71 set r {}
72 foreach i [$w_list curselection] {
73 lappend r [lindex [lindex $list $i] 0]
75 return $r
78 method _visualize {} {
79 set revs [_refs $this]
80 if {$revs eq {}} return
81 lappend revs --not HEAD
82 do_gitk $revs
85 method _start {} {
86 global HEAD current_branch
88 set cmd [list git merge]
89 set names [_refs $this]
90 set revcnt [llength $names]
91 append cmd { } $names
93 if {$revcnt == 0} {
94 return
95 } elseif {$revcnt == 1} {
96 set unit branch
97 } elseif {$revcnt <= 15} {
98 set unit branches
100 if {[tk_dialog \
101 $w.confirm_octopus \
102 [wm title $w] \
103 "Use octopus merge strategy?
105 You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts.
107 question \
109 {Cancel} \
110 {Use octopus} \
111 ] != 1} return
112 } else {
113 tk_messageBox \
114 -icon error \
115 -type ok \
116 -title [wm title $w] \
117 -parent $w \
118 -message "Too many branches selected.
120 You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge.
122 Please select fewer branches. To merge more than 15 branches, merge the branches in batches.
124 return
127 set msg "Merging $current_branch, [join $names {, }]"
128 ui_status "$msg..."
129 set cons [console::new "Merge" $msg]
130 console::exec $cons $cmd [cb _finish $revcnt $cons]
132 wm protocol $w WM_DELETE_WINDOW {}
133 destroy $w
136 method _finish {revcnt cons ok} {
137 console::done $cons $ok
138 if {$ok} {
139 set msg {Merge completed successfully.}
140 } else {
141 if {$revcnt != 1} {
142 info_popup "Octopus merge failed.
144 Your merge of $revcnt branches has failed.
146 There are file-level conflicts between the branches which must be resolved manually.
148 The working directory will now be reset.
150 You can attempt this merge again by merging only one branch at a time." $w
152 set fd [git_read read-tree --reset -u HEAD]
153 fconfigure $fd -blocking 0 -translation binary
154 fileevent $fd readable [cb _reset_wait $fd]
155 ui_status {Aborting... please wait...}
156 return
159 set msg {Merge failed. Conflict resolution is required.}
161 unlock_index
162 rescan [list ui_status $msg]
163 delete_this
166 constructor dialog {} {
167 global current_branch
168 global M1B
170 if {![_can_merge $this]} {
171 delete_this
172 return
175 set fmt {list %(objectname) %(*objectname) %(refname) %(subject)}
176 set fr_fd [git_read for-each-ref \
177 --tcl \
178 --format=$fmt \
179 refs/heads \
180 refs/remotes \
181 refs/tags \
183 fconfigure $fr_fd -translation binary
184 while {[gets $fr_fd line] > 0} {
185 set line [eval $line]
186 set ref [lindex $line 2]
187 regsub ^refs/(heads|remotes|tags)/ $ref {} ref
188 set subj($ref) [lindex $line 3]
189 lappend sha1([lindex $line 0]) $ref
190 if {[lindex $line 1] ne {}} {
191 lappend sha1([lindex $line 1]) $ref
194 close $fr_fd
196 set list [list]
197 set fr_fd [git_read rev-list --all --not HEAD]
198 while {[gets $fr_fd line] > 0} {
199 if {[catch {set ref $sha1($line)}]} continue
200 foreach n $ref {
201 lappend list [list $n $line]
204 close $fr_fd
205 set list [lsort -unique $list]
207 make_toplevel top w
208 wm title $top "[appname] ([reponame]): Merge"
209 if {$top ne {.}} {
210 wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
213 set _visualize [cb _visualize]
214 set _start [cb _start]
216 label $w.header \
217 -text "Merge Into $current_branch" \
218 -font font_uibold
219 pack $w.header -side top -fill x
221 frame $w.buttons
222 button $w.buttons.visualize -text Visualize -command $_visualize
223 pack $w.buttons.visualize -side left
224 button $w.buttons.create -text Merge -command $_start
225 pack $w.buttons.create -side right
226 button $w.buttons.cancel \
227 -text {Cancel} \
228 -command [cb _cancel]
229 pack $w.buttons.cancel -side right -padx 5
230 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
232 labelframe $w.source -text {Source Branches}
233 set w_list $w.source.l
234 listbox $w_list \
235 -height 10 \
236 -width 70 \
237 -font font_diff \
238 -selectmode extended \
239 -yscrollcommand [list $w.source.sby set]
240 scrollbar $w.source.sby -command [list $w_list yview]
241 pack $w.source.sby -side right -fill y
242 pack $w_list -side left -fill both -expand 1
243 pack $w.source -fill both -expand 1 -pady 5 -padx 5
245 foreach ref $list {
246 set n [lindex $ref 0]
247 if {[string length $n] > 20} {
248 set n "[string range $n 0 16]..."
250 $w_list insert end [format {%s %-20s %s} \
251 [string range [lindex $ref 1] 0 5] \
252 $n \
253 $subj([lindex $ref 0])]
256 bind $w_list <Key-K> [list event generate %W <Shift-Key-Up>]
257 bind $w_list <Key-J> [list event generate %W <Shift-Key-Down>]
258 bind $w_list <Key-k> [list event generate %W <Key-Up>]
259 bind $w_list <Key-j> [list event generate %W <Key-Down>]
260 bind $w_list <Key-h> [list event generate %W <Key-Left>]
261 bind $w_list <Key-l> [list event generate %W <Key-Right>]
262 bind $w_list <Key-v> $_visualize
264 bind $w <$M1B-Key-Return> $_start
265 bind $w <Visibility> [cb _visible]
266 bind $w <Key-Escape> [cb _cancel]
267 wm protocol $w WM_DELETE_WINDOW [cb _cancel]
268 tkwait window $w
271 method _visible {} {
272 grab $w
273 focus $w_list
276 method _cancel {} {
277 wm protocol $w WM_DELETE_WINDOW {}
278 unlock_index
279 destroy $w
280 delete_this
285 namespace eval merge {
287 proc reset_hard {} {
288 global HEAD commit_type file_states
290 if {[string match amend* $commit_type]} {
291 info_popup {Cannot abort while amending.
293 You must finish amending this commit.
295 return
298 if {![lock_index abort]} return
300 if {[string match *merge* $commit_type]} {
301 set op merge
302 } else {
303 set op commit
306 if {[ask_popup "Abort $op?
308 Aborting the current $op will cause *ALL* uncommitted changes to be lost.
310 Continue with aborting the current $op?"] eq {yes}} {
311 set fd [git_read read-tree --reset -u HEAD]
312 fconfigure $fd -blocking 0 -translation binary
313 fileevent $fd readable [namespace code [list _reset_wait $fd]]
314 ui_status {Aborting... please wait...}
315 } else {
316 unlock_index
320 proc _reset_wait {fd} {
321 global ui_comm
323 read $fd
324 if {[eof $fd]} {
325 close $fd
326 unlock_index
328 $ui_comm delete 0.0 end
329 $ui_comm edit modified false
331 catch {file delete [gitdir MERGE_HEAD]}
332 catch {file delete [gitdir rr-cache MERGE_RR]}
333 catch {file delete [gitdir SQUASH_MSG]}
334 catch {file delete [gitdir MERGE_MSG]}
335 catch {file delete [gitdir GITGUI_MSG]}
337 rescan {ui_status {Abort completed. Ready.}}