git-gui: Fast-forward existing branch in branch create dialog
[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 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
108 method _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
276 method _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
358 method _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 }