git-gui: Automatically refresh tracking branches when needed
[git-gui/me-and.git] / lib / branch_create.tcl
blobf708957b229962d5e6e3086fdab3e51ba4956feb
1 # git-gui branch create support
2 # Copyright (C) 2006, 2007 Shawn Pearce
4 class branch_create {
6 field w ; # widget path
7 field w_rev ; # mega-widget to pick the initial revision
8 field w_name ; # new branch name widget
10 field name {}; # name of the branch the user has chosen
11 field name_type user; # type of branch name to use
13 field opt_merge ff; # type of merge to apply to existing branch
14 field opt_checkout 1; # automatically checkout the new branch?
15 field opt_fetch 1; # refetch tracking branch if used?
16 field reset_ok 0; # did the user agree to reset?
18 constructor dialog {} {
19 global repo_config
21 make_toplevel top w
22 wm title $top "[appname] ([reponame]): Create Branch"
23 if {$top ne {.}} {
24 wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
27 label $w.header -text {Create New Branch} -font font_uibold
28 pack $w.header -side top -fill x
30 frame $w.buttons
31 button $w.buttons.create -text Create \
32 -default active \
33 -command [cb _create]
34 pack $w.buttons.create -side right
35 button $w.buttons.cancel -text {Cancel} \
36 -command [list destroy $w]
37 pack $w.buttons.cancel -side right -padx 5
38 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
40 labelframe $w.desc -text {Branch Name}
41 radiobutton $w.desc.name_r \
42 -anchor w \
43 -text {Name:} \
44 -value user \
45 -variable @name_type
46 set w_name $w.desc.name_t
47 entry $w_name \
48 -borderwidth 1 \
49 -relief sunken \
50 -width 40 \
51 -textvariable @name \
52 -validate key \
53 -validatecommand [cb _validate %d %S]
54 grid $w.desc.name_r $w_name -sticky we -padx {0 5}
56 radiobutton $w.desc.match_r \
57 -anchor w \
58 -text {Match Tracking Branch Name} \
59 -value match \
60 -variable @name_type
61 grid $w.desc.match_r -sticky we -padx {0 5} -columnspan 2
63 grid columnconfigure $w.desc 1 -weight 1
64 pack $w.desc -anchor nw -fill x -pady 5 -padx 5
66 set w_rev [::choose_rev::new $w.rev {Starting Revision}]
67 pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
69 labelframe $w.options -text {Options}
71 frame $w.options.merge
72 label $w.options.merge.l -text {Update Existing Branch:}
73 pack $w.options.merge.l -side left
74 radiobutton $w.options.merge.no \
75 -text No \
76 -value no \
77 -variable @opt_merge
78 pack $w.options.merge.no -side left
79 radiobutton $w.options.merge.ff \
80 -text {Fast Forward Only} \
81 -value ff \
82 -variable @opt_merge
83 pack $w.options.merge.ff -side left
84 radiobutton $w.options.merge.reset \
85 -text {Reset} \
86 -value reset \
87 -variable @opt_merge
88 pack $w.options.merge.reset -side left
89 pack $w.options.merge -anchor nw
91 checkbutton $w.options.fetch \
92 -text {Fetch Tracking Branch} \
93 -variable @opt_fetch
94 pack $w.options.fetch -anchor nw
96 checkbutton $w.options.checkout \
97 -text {Checkout After Creation} \
98 -variable @opt_checkout
99 pack $w.options.checkout -anchor nw
100 pack $w.options -anchor nw -fill x -pady 5 -padx 5
102 trace add variable @name_type write [cb _select]
104 set name $repo_config(gui.newbranchtemplate)
105 if {[is_config_true gui.matchtrackingbranch]} {
106 set name_type match
109 bind $w <Visibility> [cb _visible]
110 bind $w <Key-Escape> [list destroy $w]
111 bind $w <Key-Return> [cb _create]\;break
112 tkwait window $w
115 method _create {} {
116 global repo_config current_branch
117 global M1B
119 set spec [$w_rev get_tracking_branch]
120 switch -- $name_type {
121 user {
122 set newbranch $name
124 match {
125 if {$spec eq {}} {
126 tk_messageBox \
127 -icon error \
128 -type ok \
129 -title [wm title $w] \
130 -parent $w \
131 -message "Please select a tracking branch."
132 return
134 if {![regsub ^refs/heads/ [lindex $spec 2] {} newbranch]} {
135 tk_messageBox \
136 -icon error \
137 -type ok \
138 -title [wm title $w] \
139 -parent $w \
140 -message "Tracking branch [$w get] is not a branch in the remote repository."
141 return
146 if {$newbranch eq {}
147 || $newbranch eq $repo_config(gui.newbranchtemplate)} {
148 tk_messageBox \
149 -icon error \
150 -type ok \
151 -title [wm title $w] \
152 -parent $w \
153 -message "Please supply a branch name."
154 focus $w_name
155 return
158 if {$newbranch eq $current_branch} {
159 tk_messageBox \
160 -icon error \
161 -type ok \
162 -title [wm title $w] \
163 -parent $w \
164 -message "'$newbranch' already exists and is the current branch."
165 focus $w_name
166 return
169 if {[catch {git check-ref-format "heads/$newbranch"}]} {
170 tk_messageBox \
171 -icon error \
172 -type ok \
173 -title [wm title $w] \
174 -parent $w \
175 -message "'$newbranch' is not an acceptable branch name."
176 focus $w_name
177 return
180 if {$spec ne {} && $opt_fetch} {
181 set l_trck [lindex $spec 0]
182 set remote [lindex $spec 1]
183 set r_head [lindex $spec 2]
184 regsub ^refs/heads/ $r_head {} r_head
186 set c $w.fetch_trck
187 toplevel $c
188 wm title $c "Refreshing Tracking Branch"
189 wm geometry $c "+[winfo rootx $w]+[winfo rooty $w]"
191 set e [::console::embed \
192 $c.console \
193 "Fetching $r_head from $remote"]
194 pack $c.console -fill both -expand 1
195 $e exec \
196 [list git fetch $remote +$r_head:$l_trck] \
197 [cb _finish_fetch $newbranch $c $e]
199 bind $c <Visibility> [list grab $c]
200 bind $c <$M1B-Key-w> break
201 bind $c <$M1B-Key-W> break
202 wm protocol $c WM_DELETE_WINDOW [cb _noop]
203 } else {
204 _finish_create $this $newbranch
208 method _noop {} {}
210 method _finish_fetch {newbranch c e ok} {
211 wm protocol $c WM_DELETE_WINDOW {}
213 if {$ok} {
214 destroy $c
215 _finish_create $this $newbranch
216 } else {
217 $e done $ok
218 button $c.close -text Close -command [list destroy $c]
219 pack $c.close -side bottom -anchor e -padx 10 -pady 10
223 method _finish_create {newbranch} {
224 global null_sha1 all_heads
226 if {[catch {set new [$w_rev commit_or_die]}]} {
227 return
230 set ref refs/heads/$newbranch
231 if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} {
232 # Assume it does not exist, and that is what the error was.
234 set reflog_msg "branch: Created from [$w_rev get]"
235 set cur $null_sha1
236 } elseif {$opt_merge eq {no}} {
237 tk_messageBox \
238 -icon error \
239 -type ok \
240 -title [wm title $w] \
241 -parent $w \
242 -message "Branch '$newbranch' already exists."
243 focus $w_name
244 return
245 } else {
246 set mrb {}
247 catch {set mrb [git merge-base $new $cur]}
248 switch -- $opt_merge {
249 ff {
250 if {$mrb eq $new} {
251 # The current branch is actually newer.
253 set new $cur
254 } elseif {$mrb eq $cur} {
255 # The current branch is older.
257 set reflog_msg "merge [$w_rev get]: Fast-forward"
258 } else {
259 tk_messageBox \
260 -icon error \
261 -type ok \
262 -title [wm title $w] \
263 -parent $w \
264 -message "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to [$w_rev get].\nA merge is required."
265 focus $w_name
266 return
269 reset {
270 if {$mrb eq $cur} {
271 # The current branch is older.
273 set reflog_msg "merge [$w_rev get]: Fast-forward"
274 } else {
275 # The current branch will lose things.
277 if {[_confirm_reset $this $newbranch $cur $new]} {
278 set reflog_msg "reset [$w_rev get]"
279 } else {
280 return
284 default {
285 tk_messageBox \
286 -icon error \
287 -type ok \
288 -title [wm title $w] \
289 -parent $w \
290 -message "Branch '$newbranch' already exists."
291 focus $w_name
292 return
297 if {$new ne $cur} {
298 if {[catch {
299 git update-ref -m $reflog_msg $ref $new $cur
300 } err]} {
301 tk_messageBox \
302 -icon error \
303 -type ok \
304 -title [wm title $w] \
305 -parent $w \
306 -message "Failed to create '$newbranch'.\n\n$err"
307 return
311 if {$cur eq $null_sha1} {
312 lappend all_heads $newbranch
313 set all_heads [lsort -uniq $all_heads]
314 populate_branch_menu
317 destroy $w
318 if {$opt_checkout} {
319 switch_branch $newbranch
323 method _confirm_reset {newbranch cur new} {
324 set reset_ok 0
325 set gitk [list do_gitk [list $cur ^$new]]
327 set c $w.confirm_reset
328 toplevel $c
329 wm title $c "Confirm Branch Reset"
330 wm geometry $c "+[winfo rootx $w]+[winfo rooty $w]"
332 pack [label $c.msg1 \
333 -anchor w \
334 -justify left \
335 -text "Resetting '$newbranch' to [$w_rev get] will lose the following commits:" \
336 ] -anchor w
338 set list $c.list.l
339 frame $c.list
340 text $list \
341 -font font_diff \
342 -width 80 \
343 -height 10 \
344 -wrap none \
345 -xscrollcommand [list $c.list.sbx set] \
346 -yscrollcommand [list $c.list.sby set]
347 scrollbar $c.list.sbx -orient h -command [list $list xview]
348 scrollbar $c.list.sby -orient v -command [list $list yview]
349 pack $c.list.sbx -fill x -side bottom
350 pack $c.list.sby -fill y -side right
351 pack $list -fill both -expand 1
352 pack $c.list -fill both -expand 1 -padx 5 -pady 5
354 pack [label $c.msg2 \
355 -anchor w \
356 -justify left \
357 -text "Recovering lost commits may not be easy." \
359 pack [label $c.msg3 \
360 -anchor w \
361 -justify left \
362 -text "Reset '$newbranch'?" \
365 frame $c.buttons
366 button $c.buttons.visualize \
367 -text Visualize \
368 -command $gitk
369 pack $c.buttons.visualize -side left
370 button $c.buttons.reset \
371 -text Reset \
372 -command "
373 set @reset_ok 1
374 destroy $c
376 pack $c.buttons.reset -side right
377 button $c.buttons.cancel \
378 -default active \
379 -text Cancel \
380 -command [list destroy $c]
381 pack $c.buttons.cancel -side right -padx 5
382 pack $c.buttons -side bottom -fill x -pady 10 -padx 10
384 set fd [open "| git rev-list --pretty=oneline $cur ^$new" r]
385 while {[gets $fd line] > 0} {
386 set abbr [string range $line 0 7]
387 set subj [string range $line 41 end]
388 $list insert end "$abbr $subj\n"
390 close $fd
391 $list configure -state disabled
393 bind $c <Key-v> $gitk
395 bind $c <Visibility> "
396 grab $c
397 focus $c.buttons.cancel
399 bind $c <Key-Return> [list destroy $c]
400 bind $c <Key-Escape> [list destroy $c]
401 tkwait window $c
402 return $reset_ok
405 method _validate {d S} {
406 if {$d == 1} {
407 if {[regexp {[~^:?*\[\0- ]} $S]} {
408 return 0
410 if {[string length $S] > 0} {
411 set name_type user
414 return 1
417 method _select {args} {
418 if {$name_type eq {match}} {
419 $w_rev pick_tracking_branch
423 method _visible {} {
424 grab $w
425 if {$name_type eq {user}} {
426 $w_name icursor end
427 focus $w_name