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 _rev {} {
71 set i [$w_list curselection]
72 if {$i >= 0} {
73 return [lindex [lindex $list $i] 0]
74 }
75 return {}
76}
77
78method _visualize {} {
79 set rev [_rev $this]
80 if {$rev ne {}} {
81 do_gitk [list $rev --not HEAD]
82 }
83}
84
85method _start {} {
86 global HEAD current_branch
87
88 set name [_rev $this]
89 if {$name eq {}} {
90 return
91 }
92
93 set cmd [list git merge $name]
94 set msg "Merging $current_branch and $name"
95 ui_status "$msg..."
96 set cons [console::new "Merge" $cmd]
97 console::exec $cons $cmd [cb _finish $cons]
98
99 wm protocol $w WM_DELETE_WINDOW {}
100 destroy $w
101}
102
103method _finish {cons ok} {
104 console::done $cons $ok
105 if {$ok} {
106 set msg {Merge completed successfully.}
107 } else {
108 set msg {Merge failed. Conflict resolution is required.}
109 }
110 unlock_index
111 rescan [list ui_status $msg]
112 delete_this
113}
114
115constructor dialog {} {
116 global current_branch
117 global M1B
118
119 if {![_can_merge $this]} {
120 delete_this
121 return
122 }
123
124 set fmt {list %(objectname) %(*objectname) %(refname) %(subject)}
125 set fr_fd [git_read for-each-ref \
126 --tcl \
127 --format=$fmt \
128 refs/heads \
129 refs/remotes \
130 refs/tags \
131 ]
132 fconfigure $fr_fd -translation binary
133 while {[gets $fr_fd line] > 0} {
134 set line [eval $line]
135 set ref [lindex $line 2]
136 regsub ^refs/(heads|remotes|tags)/ $ref {} ref
137 set subj($ref) [lindex $line 3]
138 lappend sha1([lindex $line 0]) $ref
139 if {[lindex $line 1] ne {}} {
140 lappend sha1([lindex $line 1]) $ref
141 }
142 }
143 close $fr_fd
144
145 set list [list]
146 set fr_fd [git_read rev-list --all --not HEAD]
147 while {[gets $fr_fd line] > 0} {
148 if {[catch {set ref $sha1($line)}]} continue
149 foreach n $ref {
150 lappend list [list $n $line]
151 }
152 }
153 close $fr_fd
154 set list [lsort -unique $list]
155
156 make_toplevel top w
157 wm title $top "[appname] ([reponame]): Merge"
158 if {$top ne {.}} {
159 wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
160 }
161
162 set _visualize [cb _visualize]
163 set _start [cb _start]
164
165 label $w.header \
166 -text "Merge Into $current_branch" \
167 -font font_uibold
168 pack $w.header -side top -fill x
169
170 frame $w.buttons
171 button $w.buttons.visualize -text Visualize -command $_visualize
172 pack $w.buttons.visualize -side left
173 button $w.buttons.create -text Merge -command $_start
174 pack $w.buttons.create -side right
175 button $w.buttons.cancel \
176 -text {Cancel} \
177 -command [cb _cancel]
178 pack $w.buttons.cancel -side right -padx 5
179 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
180
181 labelframe $w.source -text {Source Branches}
182 set w_list $w.source.l
183 listbox $w_list \
184 -height 10 \
185 -width 70 \
186 -font font_diff \
187 -selectmode browse \
188 -yscrollcommand [list $w.source.sby set]
189 scrollbar $w.source.sby -command [list $w_list yview]
190 pack $w.source.sby -side right -fill y
191 pack $w_list -side left -fill both -expand 1
192 pack $w.source -fill both -expand 1 -pady 5 -padx 5
193
194 foreach ref $list {
195 set n [lindex $ref 0]
196 if {[string length $n] > 20} {
197 set n "[string range $n 0 16]..."
198 }
199 $w_list insert end [format {%s %-20s %s} \
200 [string range [lindex $ref 1] 0 5] \
201 $n \
202 $subj([lindex $ref 0])]
203 }
204
205 bind $w_list <Key-K> [list event generate %W <Shift-Key-Up>]
206 bind $w_list <Key-J> [list event generate %W <Shift-Key-Down>]
207 bind $w_list <Key-k> [list event generate %W <Key-Up>]
208 bind $w_list <Key-j> [list event generate %W <Key-Down>]
209 bind $w_list <Key-h> [list event generate %W <Key-Left>]
210 bind $w_list <Key-l> [list event generate %W <Key-Right>]
211 bind $w_list <Key-v> $_visualize
212
213 bind $w <$M1B-Key-Return> $_start
214 bind $w <Visibility> [cb _visible]
215 bind $w <Key-Escape> [cb _cancel]
216 wm protocol $w WM_DELETE_WINDOW [cb _cancel]
217 tkwait window $w
218}
219
220method _visible {} {
221 grab $w
222 focus $w_list
223}
224
225method _cancel {} {
226 wm protocol $w WM_DELETE_WINDOW {}
227 unlock_index
228 destroy $w
229 delete_this
230}
231
232}
233
234namespace eval merge {
235
236proc reset_hard {} {
237 global HEAD commit_type file_states
238
239 if {[string match amend* $commit_type]} {
240 info_popup {Cannot abort while amending.
241
242You must finish amending this commit.
243}
244 return
245 }
246
247 if {![lock_index abort]} return
248
249 if {[string match *merge* $commit_type]} {
250 set op merge
251 } else {
252 set op commit
253 }
254
255 if {[ask_popup "Abort $op?
256
257Aborting the current $op will cause *ALL* uncommitted changes to be lost.
258
259Continue with aborting the current $op?"] eq {yes}} {
260 set fd [git_read read-tree --reset -u HEAD]
261 fconfigure $fd -blocking 0 -translation binary
262 fileevent $fd readable [namespace code [list _reset_wait $fd]]
263 ui_status {Aborting... please wait...}
264 } else {
265 unlock_index
266 }
267}
268
269proc _reset_wait {fd} {
270 global ui_comm
271
272 read $fd
273 if {[eof $fd]} {
274 close $fd
275 unlock_index
276
277 $ui_comm delete 0.0 end
278 $ui_comm edit modified false
279
280 catch {file delete [gitdir MERGE_HEAD]}
281 catch {file delete [gitdir rr-cache MERGE_RR]}
282 catch {file delete [gitdir SQUASH_MSG]}
283 catch {file delete [gitdir MERGE_MSG]}
284 catch {file delete [gitdir GITGUI_MSG]}
285
286 rescan {ui_status {Abort completed. Ready.}}
287 }
288}
289
290}