git-gui: Option to default new branches to match tracking branches
[git] / lib / branch_create.tcl
1 # git-gui branch create support
2 # Copyright (C) 2006, 2007 Shawn Pearce
3
4 class branch_create {
5
6 field w              ; # widget path
7 field w_rev          ; # mega-widget to pick the initial revision
8 field w_name         ; # new branch name widget
9
10 field name         {}; # name of the branch the user has chosen
11 field name_type  user; # type of branch name to use
12
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 reset_ok      0; # did the user agree to reset?
16
17 constructor 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 both -expand 1 -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         trace add variable @name_type write [cb _select]
97
98         set name $repo_config(gui.newbranchtemplate)
99         if {[is_config_true gui.matchtrackingbranch]} {
100                 set name_type match
101         }
102
103         bind $w <Visibility> [cb _visible]
104         bind $w <Key-Escape> [list destroy $w]
105         bind $w <Key-Return> [cb _create]\;break
106         tkwait window $w
107 }
108
109 method _create {} {
110         global null_sha1 repo_config
111         global all_heads current_branch
112
113         switch -- $name_type {
114         user {
115                 set newbranch $name
116         }
117         match {
118                 set spec [$w_rev get_tracking_branch]
119                 if {$spec eq {}} {
120                         tk_messageBox \
121                                 -icon error \
122                                 -type ok \
123                                 -title [wm title $w] \
124                                 -parent $w \
125                                 -message "Please select a tracking branch."
126                         return
127                 }
128                 if {![regsub ^refs/heads/ [lindex $spec 2] {} newbranch]} {
129                         tk_messageBox \
130                                 -icon error \
131                                 -type ok \
132                                 -title [wm title $w] \
133                                 -parent $w \
134                                 -message "Tracking branch [$w get] is not a branch in the remote repository."
135                         return
136                 }
137         }
138         }
139
140         if {$newbranch eq {}
141                 || $newbranch eq $repo_config(gui.newbranchtemplate)} {
142                 tk_messageBox \
143                         -icon error \
144                         -type ok \
145                         -title [wm title $w] \
146                         -parent $w \
147                         -message "Please supply a branch name."
148                 focus $w_name
149                 return
150         }
151
152         if {$newbranch eq $current_branch} {
153                 tk_messageBox \
154                         -icon error \
155                         -type ok \
156                         -title [wm title $w] \
157                         -parent $w \
158                         -message "'$newbranch' already exists and is the current branch."
159                 focus $w_name
160                 return
161         }
162
163         if {[catch {git check-ref-format "heads/$newbranch"}]} {
164                 tk_messageBox \
165                         -icon error \
166                         -type ok \
167                         -title [wm title $w] \
168                         -parent $w \
169                         -message "'$newbranch' is not an acceptable branch name."
170                 focus $w_name
171                 return
172         }
173
174         if {[catch {set new [$w_rev commit_or_die]}]} {
175                 return
176         }
177
178         set ref refs/heads/$newbranch
179         if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} {
180                 # Assume it does not exist, and that is what the error was.
181                 #
182                 set reflog_msg "branch: Created from [$w_rev get]"
183                 set cur $null_sha1
184         } elseif {$opt_merge eq {no}} {
185                 tk_messageBox \
186                         -icon error \
187                         -type ok \
188                         -title [wm title $w] \
189                         -parent $w \
190                         -message "Branch '$newbranch' already exists."
191                 focus $w_name
192                 return
193         } else {
194                 set mrb {}
195                 catch {set mrb [git merge-base $new $cur]}
196                 switch -- $opt_merge {
197                 ff {
198                         if {$mrb eq $new} {
199                                 # The current branch is actually newer.
200                                 #
201                                 set new $cur
202                         } elseif {$mrb eq $cur} {
203                                 # The current branch is older.
204                                 #
205                                 set reflog_msg "merge [$w_rev get]: Fast-forward"
206                         } else {
207                                 tk_messageBox \
208                                         -icon error \
209                                         -type ok \
210                                         -title [wm title $w] \
211                                         -parent $w \
212                                         -message "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to [$w_rev get].\nA merge is required."
213                                 focus $w_name
214                                 return
215                         }
216                 }
217                 reset {
218                         if {$mrb eq $cur} {
219                                 # The current branch is older.
220                                 #
221                                 set reflog_msg "merge [$w_rev get]: Fast-forward"
222                         } else {
223                                 # The current branch will lose things.
224                                 #
225                                 if {[_confirm_reset $this $newbranch $cur $new]} {
226                                         set reflog_msg "reset [$w_rev get]"
227                                 } else {
228                                         return
229                                 }
230                         }
231                 }
232                 default {
233                         tk_messageBox \
234                                 -icon error \
235                                 -type ok \
236                                 -title [wm title $w] \
237                                 -parent $w \
238                                 -message "Branch '$newbranch' already exists."
239                         focus $w_name
240                         return
241                 }
242                 }
243         }
244
245         if {$new ne $cur} {
246                 if {[catch {
247                                 git update-ref -m $reflog_msg $ref $new $cur
248                         } err]} {
249                         tk_messageBox \
250                                 -icon error \
251                                 -type ok \
252                                 -title [wm title $w] \
253                                 -parent $w \
254                                 -message "Failed to create '$newbranch'.\n\n$err"
255                         return
256                 }
257         }
258
259         if {$cur eq $null_sha1} {
260                 lappend all_heads $newbranch
261                 set all_heads [lsort -uniq $all_heads]
262                 populate_branch_menu
263         }
264
265         destroy $w
266         if {$opt_checkout} {
267                 switch_branch $newbranch
268         }
269 }
270
271 method _confirm_reset {newbranch cur new} {
272         set reset_ok 0
273         set gitk [list do_gitk [list $cur ^$new]]
274
275         set c $w.confirm_reset
276         toplevel $c
277         wm title $c "Confirm Branch Reset"
278         wm geometry $c "+[winfo rootx $w]+[winfo rooty $w]"
279
280         pack [label $c.msg1 \
281                 -anchor w \
282                 -justify left \
283                 -text "Resetting '$newbranch' to [$w_rev get] will lose the following commits:" \
284                 ] -anchor w
285
286         set list $c.list.l
287         frame $c.list
288         text $list \
289                 -font font_diff \
290                 -width 80 \
291                 -height 10 \
292                 -wrap none \
293                 -xscrollcommand [list $c.list.sbx set] \
294                 -yscrollcommand [list $c.list.sby set]
295         scrollbar $c.list.sbx -orient h -command [list $list xview]
296         scrollbar $c.list.sby -orient v -command [list $list yview]
297         pack $c.list.sbx -fill x -side bottom
298         pack $c.list.sby -fill y -side right
299         pack $list -fill both -expand 1
300         pack $c.list -fill both -expand 1 -padx 5 -pady 5
301
302         pack [label $c.msg2 \
303                 -anchor w \
304                 -justify left \
305                 -text "Recovering lost commits may not be easy." \
306                 ]
307         pack [label $c.msg3 \
308                 -anchor w \
309                 -justify left \
310                 -text "Reset '$newbranch'?" \
311                 ]
312
313         frame $c.buttons
314         button $c.buttons.visualize \
315                 -text Visualize \
316                 -command $gitk
317         pack $c.buttons.visualize -side left
318         button $c.buttons.reset \
319                 -text Reset \
320                 -command "
321                         set @reset_ok 1
322                         destroy $c
323                 "
324         pack $c.buttons.reset -side right
325         button $c.buttons.cancel \
326                 -default active \
327                 -text Cancel \
328                 -command [list destroy $c]
329         pack $c.buttons.cancel -side right -padx 5
330         pack $c.buttons -side bottom -fill x -pady 10 -padx 10
331
332         set fd [open "| git rev-list --pretty=oneline $cur ^$new" r]
333         while {[gets $fd line] > 0} {
334                 set abbr [string range $line 0 7]
335                 set subj [string range $line 41 end]
336                 $list insert end "$abbr  $subj\n"
337         }
338         close $fd
339         $list configure -state disabled
340
341         bind $c    <Key-v> $gitk
342
343         bind $c <Visibility> "
344                 grab $c
345                 focus $c.buttons.cancel
346         "
347         bind $c <Key-Return> [list destroy $c]
348         bind $c <Key-Escape> [list destroy $c]
349         tkwait window $c
350         return $reset_ok
351 }
352
353 method _validate {d S} {
354         if {$d == 1} {
355                 if {[regexp {[~^:?*\[\0- ]} $S]} {
356                         return 0
357                 }
358                 if {[string length $S] > 0} {
359                         set name_type user
360                 }
361         }
362         return 1
363 }
364
365 method _select {args} {
366         if {$name_type eq {match}} {
367                 $w_rev pick_tracking_branch
368         }
369 }
370
371 method _visible {} {
372         grab $w
373         if {$name_type eq {user}} {
374                 $w_name icursor end
375                 focus $w_name
376         }
377 }
378
379 }