6caf25f2bea19efbad2190587ea5c414604871f0
1# git-gui branch merge support
2# Copyright (C) 2006, 2007 Shawn Pearce
3
4class merge {
5
6field w ; # top level window
7field w_list ; # widget of available branches
8field list ; # list of available branches
9
10method _can_merge {} {
11 global HEAD commit_type file_states
12
13 if {[string match amend* $commit_type]} {
14 info_popup {Cannot merge while amending.
15
16You must finish amending this commit before starting any type of merge.
17}
18 return 0
19 }
20
21 if {[committer_ident] eq {}} {return 0}
22 if {![lock_index merge]} {return 0}
23
24 # -- Our in memory state should match the repository.
25 #
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.
29
30Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed.
31
32The rescan will be automatically started now.
33}
34 unlock_index
35 rescan ui_ready
36 return 0
37 }
38
39 foreach path [array names file_states] {
40 switch -glob -- [lindex $file_states($path) 0] {
41 _O {
42 continue; # and pray it works!
43 }
44 U? {
45 error_popup "You are in the middle of a conflicted merge.
46
47File [short_path $path] has merge conflicts.
48
49You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge.
50"
51 unlock_index
52 return 0
53 }
54 ?? {
55 error_popup "You are in the middle of a change.
56
57File [short_path $path] is modified.
58
59You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise.
60"
61 unlock_index
62 return 0
63 }
64 }
65 }
66
67 return 1
68}
69
70method _refs {} {
71 set r {}
72 foreach i [$w_list curselection] {
73 lappend r [lindex [lindex $list $i] 0]
74 }
75 return $r
76}
77
78method _visualize {} {
79 set revs [_refs $this]
80 if {$revs eq {}} return
81 lappend revs --not HEAD
82 do_gitk $revs
83}
84
85method _start {} {
86 global HEAD current_branch
87
88 set cmd [list git merge]
89 set names [_refs $this]
90 set revcnt [llength $names]
91 append cmd { } $names
92
93 if {$revcnt == 0} {
94 return
95 } elseif {$revcnt == 1} {
96 set unit branch
97 } elseif {$revcnt <= 15} {
98 set unit branches
99
100 if {[tk_dialog \
101 $w.confirm_octopus \
102 [wm title $w] \
103 "Use octopus merge strategy?
104
105You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts.
106" \
107 question \
108 0 \
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.
119
120You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge.
121
122Please select fewer branches. To merge more than 15 branches, merge the branches in batches.
123"
124 return
125 }
126
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]
131
132 wm protocol $w WM_DELETE_WINDOW {}
133 destroy $w
134}
135
136method _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.
143
144Your merge of $revcnt branches has failed.
145
146There are file-level conflicts between the branches which must be resolved manually.
147
148The working directory will now be reset.
149
150You can attempt this merge again by merging only one branch at a time." $w
151
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
157 }
158
159 set msg {Merge failed. Conflict resolution is required.}
160 }
161 unlock_index
162 rescan [list ui_status $msg]
163 delete_this
164}
165
166constructor dialog {} {
167 global current_branch
168 global M1B
169
170 if {![_can_merge $this]} {
171 delete_this
172 return
173 }
174
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 \
182 ]
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
192 }
193 }
194 close $fr_fd
195
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]
202 }
203 }
204 close $fr_fd
205 set list [lsort -unique $list]
206
207 make_toplevel top w
208 wm title $top "[appname] ([reponame]): Merge"
209 if {$top ne {.}} {
210 wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
211 }
212
213 set _visualize [cb _visualize]
214 set _start [cb _start]
215
216 label $w.header \
217 -text "Merge Into $current_branch" \
218 -font font_uibold
219 pack $w.header -side top -fill x
220
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
231
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
244
245 foreach ref $list {
246 set n [lindex $ref 0]
247 if {[string length $n] > 20} {
248 set n "[string range $n 0 16]..."
249 }
250 $w_list insert end [format {%s %-20s %s} \
251 [string range [lindex $ref 1] 0 5] \
252 $n \
253 $subj([lindex $ref 0])]
254 }
255
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
263
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
269}
270
271method _visible {} {
272 grab $w
273 focus $w_list
274}
275
276method _cancel {} {
277 wm protocol $w WM_DELETE_WINDOW {}
278 unlock_index
279 destroy $w
280 delete_this
281}
282
283}
284
285namespace eval merge {
286
287proc reset_hard {} {
288 global HEAD commit_type file_states
289
290 if {[string match amend* $commit_type]} {
291 info_popup {Cannot abort while amending.
292
293You must finish amending this commit.
294}
295 return
296 }
297
298 if {![lock_index abort]} return
299
300 if {[string match *merge* $commit_type]} {
301 set op merge
302 } else {
303 set op commit
304 }
305
306 if {[ask_popup "Abort $op?
307
308Aborting the current $op will cause *ALL* uncommitted changes to be lost.
309
310Continue 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
317 }
318}
319
320proc _reset_wait {fd} {
321 global ui_comm
322
323 read $fd
324 if {[eof $fd]} {
325 close $fd
326 unlock_index
327
328 $ui_comm delete 0.0 end
329 $ui_comm edit modified false
330
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]}
336
337 rescan {ui_status {Abort completed. Ready.}}
338 }
339}
340
341}