1# git-gui branch create support
2# Copyright (C) 2006, 2007 Shawn Pearce
3
4class branch_create {
5
6field w ; # widget path
7field w_rev ; # mega-widget to pick the initial revision
8field w_name ; # new branch name widget
9
10field name {}; # name of the branch the user has chosen
11field name_type user; # type of branch name to use
12
13field opt_merge ff; # type of merge to apply to existing branch
14field opt_checkout 1; # automatically checkout the new branch?
15field reset_ok 0; # did the user agree to reset?
16
17constructor dialog {} {
18 global repo_config
19
20 make_toplevel top w
21 wm title $top "[appname] ([reponame]): Create Branch"
22 if {$top ne {.}} {
23 wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
24 }
25
26 label $w.header -text {Create New Branch} -font font_uibold
27 pack $w.header -side top -fill x
28
29 frame $w.buttons
30 button $w.buttons.create -text Create \
31 -default active \
32 -command [cb _create]
33 pack $w.buttons.create -side right
34 button $w.buttons.cancel -text {Cancel} \
35 -command [list destroy $w]
36 pack $w.buttons.cancel -side right -padx 5
37 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
38
39 labelframe $w.desc -text {Branch Name}
40 radiobutton $w.desc.name_r \
41 -anchor w \
42 -text {Name:} \
43 -value user \
44 -variable @name_type
45 set w_name $w.desc.name_t
46 entry $w_name \
47 -borderwidth 1 \
48 -relief sunken \
49 -width 40 \
50 -textvariable @name \
51 -validate key \
52 -validatecommand [cb _validate %d %S]
53 grid $w.desc.name_r $w_name -sticky we -padx {0 5}
54
55 radiobutton $w.desc.match_r \
56 -anchor w \
57 -text {Match Tracking Branch Name} \
58 -value match \
59 -variable @name_type
60 grid $w.desc.match_r -sticky we -padx {0 5} -columnspan 2
61
62 grid columnconfigure $w.desc 1 -weight 1
63 pack $w.desc -anchor nw -fill x -pady 5 -padx 5
64
65 set w_rev [::choose_rev::new $w.rev {Starting Revision}]
66 pack $w.rev -anchor nw -fill x -pady 5 -padx 5
67
68 labelframe $w.options -text {Options}
69
70 frame $w.options.merge
71 label $w.options.merge.l -text {Update Existing Branch:}
72 pack $w.options.merge.l -side left
73 radiobutton $w.options.merge.no \
74 -text No \
75 -value no \
76 -variable @opt_merge
77 pack $w.options.merge.no -side left
78 radiobutton $w.options.merge.ff \
79 -text {Fast Forward Only} \
80 -value ff \
81 -variable @opt_merge
82 pack $w.options.merge.ff -side left
83 radiobutton $w.options.merge.reset \
84 -text {Reset} \
85 -value reset \
86 -variable @opt_merge
87 pack $w.options.merge.reset -side left
88 pack $w.options.merge -anchor nw
89
90 checkbutton $w.options.checkout \
91 -text {Checkout After Creation} \
92 -variable @opt_checkout
93 pack $w.options.checkout -anchor nw
94 pack $w.options -anchor nw -fill x -pady 5 -padx 5
95
96 set name $repo_config(gui.newbranchtemplate)
97
98 bind $w <Visibility> "
99 grab $w
100 $w_name icursor end
101 focus $w_name
102 "
103 bind $w <Key-Escape> [list destroy $w]
104 bind $w <Key-Return> [cb _create]\;break
105 tkwait window $w
106}
107
108method _create {} {
109 global null_sha1 repo_config
110 global all_heads current_branch
111
112 switch -- $name_type {
113 user {
114 set newbranch $name
115 }
116 match {
117 set spec [$w_rev get_tracking_branch]
118 if {$spec eq {}} {
119 tk_messageBox \
120 -icon error \
121 -type ok \
122 -title [wm title $w] \
123 -parent $w \
124 -message "Please select a tracking branch."
125 return
126 }
127 if {![regsub ^refs/heads/ [lindex $spec 2] {} newbranch]} {
128 tk_messageBox \
129 -icon error \
130 -type ok \
131 -title [wm title $w] \
132 -parent $w \
133 -message "Tracking branch [$w get] is not a branch in the remote repository."
134 return
135 }
136 }
137 }
138
139 if {$newbranch eq {}
140 || $newbranch eq $repo_config(gui.newbranchtemplate)} {
141 tk_messageBox \
142 -icon error \
143 -type ok \
144 -title [wm title $w] \
145 -parent $w \
146 -message "Please supply a branch name."
147 focus $w_name
148 return
149 }
150
151 if {$newbranch eq $current_branch} {
152 tk_messageBox \
153 -icon error \
154 -type ok \
155 -title [wm title $w] \
156 -parent $w \
157 -message "'$newbranch' already exists and is the current branch."
158 focus $w_name
159 return
160 }
161
162 if {[catch {git check-ref-format "heads/$newbranch"}]} {
163 tk_messageBox \
164 -icon error \
165 -type ok \
166 -title [wm title $w] \
167 -parent $w \
168 -message "'$newbranch' is not an acceptable branch name."
169 focus $w_name
170 return
171 }
172
173 if {[catch {set new [$w_rev get_commit]}]} {
174 tk_messageBox \
175 -icon error \
176 -type ok \
177 -title [wm title $w] \
178 -parent $w \
179 -message "Invalid revision: [$w_rev get]"
180 return
181 }
182
183 set ref refs/heads/$newbranch
184 if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} {
185 # Assume it does not exist, and that is what the error was.
186 #
187 set reflog_msg "branch: Created from [$w_rev get]"
188 set cur $null_sha1
189 } elseif {$opt_merge eq {no}} {
190 tk_messageBox \
191 -icon error \
192 -type ok \
193 -title [wm title $w] \
194 -parent $w \
195 -message "Branch '$newbranch' already exists."
196 focus $w_name
197 return
198 } else {
199 set mrb {}
200 catch {set mrb [git merge-base $new $cur]}
201 switch -- $opt_merge {
202 ff {
203 if {$mrb eq $new} {
204 # The current branch is actually newer.
205 #
206 set new $cur
207 } elseif {$mrb eq $cur} {
208 # The current branch is older.
209 #
210 set reflog_msg "merge [$w_rev get]: Fast-forward"
211 } else {
212 tk_messageBox \
213 -icon error \
214 -type ok \
215 -title [wm title $w] \
216 -parent $w \
217 -message "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to [$w_rev get].\nA merge is required."
218 focus $w_name
219 return
220 }
221 }
222 reset {
223 if {$mrb eq $cur} {
224 # The current branch is older.
225 #
226 set reflog_msg "merge [$w_rev get]: Fast-forward"
227 } else {
228 # The current branch will lose things.
229 #
230 if {[_confirm_reset $this $newbranch $cur $new]} {
231 set reflog_msg "reset [$w_rev get]"
232 } else {
233 return
234 }
235 }
236 }
237 default {
238 tk_messageBox \
239 -icon error \
240 -type ok \
241 -title [wm title $w] \
242 -parent $w \
243 -message "Branch '$newbranch' already exists."
244 focus $w_name
245 return
246 }
247 }
248 }
249
250 if {$new ne $cur} {
251 if {[catch {
252 git update-ref -m $reflog_msg $ref $new $cur
253 } err]} {
254 tk_messageBox \
255 -icon error \
256 -type ok \
257 -title [wm title $w] \
258 -parent $w \
259 -message "Failed to create '$newbranch'.\n\n$err"
260 return
261 }
262 }
263
264 if {$cur eq $null_sha1} {
265 lappend all_heads $newbranch
266 set all_heads [lsort -uniq $all_heads]
267 populate_branch_menu
268 }
269
270 destroy $w
271 if {$opt_checkout} {
272 switch_branch $newbranch
273 }
274}
275
276method _confirm_reset {newbranch cur new} {
277 set reset_ok 0
278 set gitk [list do_gitk [list $cur ^$new]]
279
280 set c $w.confirm_reset
281 toplevel $c
282 wm title $c "Confirm Branch Reset"
283 wm geometry $c "+[winfo rootx $w]+[winfo rooty $w]"
284
285 pack [label $c.msg1 \
286 -anchor w \
287 -justify left \
288 -text "Resetting '$newbranch' to [$w_rev get] will lose the following commits:" \
289 ] -anchor w
290
291 set list $c.list.l
292 frame $c.list
293 text $list \
294 -font font_diff \
295 -width 80 \
296 -height 10 \
297 -wrap none \
298 -xscrollcommand [list $c.list.sbx set] \
299 -yscrollcommand [list $c.list.sby set]
300 scrollbar $c.list.sbx -orient h -command [list $list xview]
301 scrollbar $c.list.sby -orient v -command [list $list yview]
302 pack $c.list.sbx -fill x -side bottom
303 pack $c.list.sby -fill y -side right
304 pack $list -fill both -expand 1
305 pack $c.list -fill both -expand 1 -padx 5 -pady 5
306
307 pack [label $c.msg2 \
308 -anchor w \
309 -justify left \
310 -text "Recovering lost commits may not be easy." \
311 ]
312 pack [label $c.msg3 \
313 -anchor w \
314 -justify left \
315 -text "Reset '$newbranch'?" \
316 ]
317
318 frame $c.buttons
319 button $c.buttons.visualize \
320 -text Visualize \
321 -command $gitk
322 pack $c.buttons.visualize -side left
323 button $c.buttons.reset \
324 -text Reset \
325 -command "
326 set @reset_ok 1
327 destroy $c
328 "
329 pack $c.buttons.reset -side right
330 button $c.buttons.cancel \
331 -default active \
332 -text Cancel \
333 -command [list destroy $c]
334 pack $c.buttons.cancel -side right -padx 5
335 pack $c.buttons -side bottom -fill x -pady 10 -padx 10
336
337 set fd [open "| git rev-list --pretty=oneline $cur ^$new" r]
338 while {[gets $fd line] > 0} {
339 set abbr [string range $line 0 7]
340 set subj [string range $line 41 end]
341 $list insert end "$abbr $subj\n"
342 }
343 close $fd
344 $list configure -state disabled
345
346 bind $c <Key-v> $gitk
347
348 bind $c <Visibility> "
349 grab $c
350 focus $c.buttons.cancel
351 "
352 bind $c <Key-Return> [list destroy $c]
353 bind $c <Key-Escape> [list destroy $c]
354 tkwait window $c
355 return $reset_ok
356}
357
358method _validate {d S} {
359 if {$d == 1} {
360 if {[regexp {[~^:?*\[\0- ]} $S]} {
361 return 0
362 }
363 if {[string length $S] > 0} {
364 set name_type user
365 }
366 }
367 return 1
368}
369
370}