lib / branch_create.tclon commit git-gui: Automatically refresh tracking branches when needed (ba1964b)
   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 opt_fetch     1; # refetch tracking branch if used?
  16field reset_ok      0; # did the user agree to reset?
  17
  18constructor dialog {} {
  19        global repo_config
  20
  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 .]"
  25        }
  26
  27        label $w.header -text {Create New Branch} -font font_uibold
  28        pack $w.header -side top -fill x
  29
  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
  39
  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}
  55
  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
  62
  63        grid columnconfigure $w.desc 1 -weight 1
  64        pack $w.desc -anchor nw -fill x -pady 5 -padx 5
  65
  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
  68
  69        labelframe $w.options -text {Options}
  70
  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
  90
  91        checkbutton $w.options.fetch \
  92                -text {Fetch Tracking Branch} \
  93                -variable @opt_fetch
  94        pack $w.options.fetch -anchor nw
  95
  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
 101
 102        trace add variable @name_type write [cb _select]
 103
 104        set name $repo_config(gui.newbranchtemplate)
 105        if {[is_config_true gui.matchtrackingbranch]} {
 106                set name_type match
 107        }
 108
 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
 113}
 114
 115method _create {} {
 116        global repo_config current_branch
 117        global M1B
 118
 119        set spec [$w_rev get_tracking_branch]
 120        switch -- $name_type {
 121        user {
 122                set newbranch $name
 123        }
 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
 133                }
 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
 142                }
 143        }
 144        }
 145
 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
 156        }
 157
 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
 167        }
 168
 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
 178        }
 179
 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
 185
 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]"
 190
 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]
 198
 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
 205        }
 206}
 207
 208method _noop {} {}
 209
 210method _finish_fetch {newbranch c e ok} {
 211        wm protocol $c WM_DELETE_WINDOW {}
 212
 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
 220        }
 221}
 222
 223method _finish_create {newbranch} {
 224        global null_sha1 all_heads
 225
 226        if {[catch {set new [$w_rev commit_or_die]}]} {
 227                return
 228        }
 229
 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.
 233                #
 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.
 252                                #
 253                                set new $cur
 254                        } elseif {$mrb eq $cur} {
 255                                # The current branch is older.
 256                                #
 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
 267                        }
 268                }
 269                reset {
 270                        if {$mrb eq $cur} {
 271                                # The current branch is older.
 272                                #
 273                                set reflog_msg "merge [$w_rev get]: Fast-forward"
 274                        } else {
 275                                # The current branch will lose things.
 276                                #
 277                                if {[_confirm_reset $this $newbranch $cur $new]} {
 278                                        set reflog_msg "reset [$w_rev get]"
 279                                } else {
 280                                        return
 281                                }
 282                        }
 283                }
 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
 293                }
 294                }
 295        }
 296
 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
 308                }
 309        }
 310
 311        if {$cur eq $null_sha1} {
 312                lappend all_heads $newbranch
 313                set all_heads [lsort -uniq $all_heads]
 314                populate_branch_menu
 315        }
 316
 317        destroy $w
 318        if {$opt_checkout} {
 319                switch_branch $newbranch
 320        }
 321}
 322
 323method _confirm_reset {newbranch cur new} {
 324        set reset_ok 0
 325        set gitk [list do_gitk [list $cur ^$new]]
 326
 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]"
 331
 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
 337
 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
 353
 354        pack [label $c.msg2 \
 355                -anchor w \
 356                -justify left \
 357                -text "Recovering lost commits may not be easy." \
 358                ]
 359        pack [label $c.msg3 \
 360                -anchor w \
 361                -justify left \
 362                -text "Reset '$newbranch'?" \
 363                ]
 364
 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
 375                "
 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
 383
 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"
 389        }
 390        close $fd
 391        $list configure -state disabled
 392
 393        bind $c    <Key-v> $gitk
 394
 395        bind $c <Visibility> "
 396                grab $c
 397                focus $c.buttons.cancel
 398        "
 399        bind $c <Key-Return> [list destroy $c]
 400        bind $c <Key-Escape> [list destroy $c]
 401        tkwait window $c
 402        return $reset_ok
 403}
 404
 405method _validate {d S} {
 406        if {$d == 1} {
 407                if {[regexp {[~^:?*\[\0- ]} $S]} {
 408                        return 0
 409                }
 410                if {[string length $S] > 0} {
 411                        set name_type user
 412                }
 413        }
 414        return 1
 415}
 416
 417method _select {args} {
 418        if {$name_type eq {match}} {
 419                $w_rev pick_tracking_branch
 420        }
 421}
 422
 423method _visible {} {
 424        grab $w
 425        if {$name_type eq {user}} {
 426                $w_name icursor end
 427                focus $w_name
 428        }
 429}
 430
 431}