e7559f978912c143453391bca98330818cc13394
   1# git-gui branch (create/delete) support
   2# Copyright (C) 2006, 2007 Shawn Pearce
   3
   4proc load_all_heads {} {
   5        global all_heads
   6
   7        set all_heads [list]
   8        set fd [open "| git for-each-ref --format=%(refname) refs/heads" r]
   9        while {[gets $fd line] > 0} {
  10                if {[is_tracking_branch $line]} continue
  11                if {![regsub ^refs/heads/ $line {} name]} continue
  12                lappend all_heads $name
  13        }
  14        close $fd
  15
  16        set all_heads [lsort $all_heads]
  17}
  18
  19proc load_all_tags {} {
  20        set all_tags [list]
  21        set fd [open "| git for-each-ref --format=%(refname) refs/tags" r]
  22        while {[gets $fd line] > 0} {
  23                if {![regsub ^refs/tags/ $line {} name]} continue
  24                lappend all_tags $name
  25        }
  26        close $fd
  27
  28        return [lsort $all_tags]
  29}
  30
  31proc populate_branch_menu {} {
  32        global all_heads disable_on_lock
  33
  34        set m .mbar.branch
  35        set last [$m index last]
  36        for {set i 0} {$i <= $last} {incr i} {
  37                if {[$m type $i] eq {separator}} {
  38                        $m delete $i last
  39                        set new_dol [list]
  40                        foreach a $disable_on_lock {
  41                                if {[lindex $a 0] ne $m || [lindex $a 2] < $i} {
  42                                        lappend new_dol $a
  43                                }
  44                        }
  45                        set disable_on_lock $new_dol
  46                        break
  47                }
  48        }
  49
  50        if {$all_heads ne {}} {
  51                $m add separator
  52        }
  53        foreach b $all_heads {
  54                $m add radiobutton \
  55                        -label $b \
  56                        -command [list switch_branch $b] \
  57                        -variable current_branch \
  58                        -value $b
  59                lappend disable_on_lock \
  60                        [list $m entryconf [$m index last] -state]
  61        }
  62}
  63
  64proc radio_selector {varname value args} {
  65        upvar #0 $varname var
  66        set var $value
  67}
  68
  69proc do_delete_branch_action {w} {
  70        global all_heads
  71        global delete_branch_checktype delete_branch_head delete_branch_trackinghead
  72
  73        set check_rev {}
  74        switch -- $delete_branch_checktype {
  75        head {set check_rev $delete_branch_head}
  76        tracking {set check_rev $delete_branch_trackinghead}
  77        always {set check_rev {:none}}
  78        }
  79        if {$check_rev eq {:none}} {
  80                set check_cmt {}
  81        } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} {
  82                tk_messageBox \
  83                        -icon error \
  84                        -type ok \
  85                        -title [wm title $w] \
  86                        -parent $w \
  87                        -message "Invalid check revision: $check_rev"
  88                return
  89        }
  90
  91        set to_delete [list]
  92        set not_merged [list]
  93        foreach i [$w.list.l curselection] {
  94                set b [$w.list.l get $i]
  95                if {[catch {set o [git rev-parse --verify $b]}]} continue
  96                if {$check_cmt ne {}} {
  97                        if {$b eq $check_rev} continue
  98                        if {[catch {set m [git merge-base $o $check_cmt]}]} continue
  99                        if {$o ne $m} {
 100                                lappend not_merged $b
 101                                continue
 102                        }
 103                }
 104                lappend to_delete [list $b $o]
 105        }
 106        if {$not_merged ne {}} {
 107                set msg "The following branches are not completely merged into $check_rev:
 108
 109 - [join $not_merged "\n - "]"
 110                tk_messageBox \
 111                        -icon info \
 112                        -type ok \
 113                        -title [wm title $w] \
 114                        -parent $w \
 115                        -message $msg
 116        }
 117        if {$to_delete eq {}} return
 118        if {$delete_branch_checktype eq {always}} {
 119                set msg {Recovering deleted branches is difficult.
 120
 121Delete the selected branches?}
 122                if {[tk_messageBox \
 123                        -icon warning \
 124                        -type yesno \
 125                        -title [wm title $w] \
 126                        -parent $w \
 127                        -message $msg] ne yes} {
 128                        return
 129                }
 130        }
 131
 132        set failed {}
 133        foreach i $to_delete {
 134                set b [lindex $i 0]
 135                set o [lindex $i 1]
 136                if {[catch {git update-ref -d "refs/heads/$b" $o} err]} {
 137                        append failed " - $b: $err\n"
 138                } else {
 139                        set x [lsearch -sorted -exact $all_heads $b]
 140                        if {$x >= 0} {
 141                                set all_heads [lreplace $all_heads $x $x]
 142                        }
 143                }
 144        }
 145
 146        if {$failed ne {}} {
 147                tk_messageBox \
 148                        -icon error \
 149                        -type ok \
 150                        -title [wm title $w] \
 151                        -parent $w \
 152                        -message "Failed to delete branches:\n$failed"
 153        }
 154
 155        set all_heads [lsort $all_heads]
 156        populate_branch_menu
 157        destroy $w
 158}
 159
 160proc do_delete_branch {} {
 161        global all_heads tracking_branches current_branch
 162        global delete_branch_checktype delete_branch_head delete_branch_trackinghead
 163
 164        set w .branch_editor
 165        toplevel $w
 166        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
 167
 168        label $w.header -text {Delete Local Branch} \
 169                -font font_uibold
 170        pack $w.header -side top -fill x
 171
 172        frame $w.buttons
 173        button $w.buttons.create -text Delete \
 174                -command [list do_delete_branch_action $w]
 175        pack $w.buttons.create -side right
 176        button $w.buttons.cancel -text {Cancel} \
 177                -command [list destroy $w]
 178        pack $w.buttons.cancel -side right -padx 5
 179        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 180
 181        labelframe $w.list -text {Local Branches}
 182        listbox $w.list.l \
 183                -height 10 \
 184                -width 70 \
 185                -selectmode extended \
 186                -yscrollcommand [list $w.list.sby set]
 187        foreach h $all_heads {
 188                if {$h ne $current_branch} {
 189                        $w.list.l insert end $h
 190                }
 191        }
 192        scrollbar $w.list.sby -command [list $w.list.l yview]
 193        pack $w.list.sby -side right -fill y
 194        pack $w.list.l -side left -fill both -expand 1
 195        pack $w.list -fill both -expand 1 -pady 5 -padx 5
 196
 197        labelframe $w.validate -text {Delete Only If}
 198        radiobutton $w.validate.head_r \
 199                -text {Merged Into Local Branch:} \
 200                -value head \
 201                -variable delete_branch_checktype
 202        eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads
 203        grid $w.validate.head_r $w.validate.head_m -sticky w
 204        set all_trackings [all_tracking_branches]
 205        if {$all_trackings ne {}} {
 206                set delete_branch_trackinghead [lindex $all_trackings 0]
 207                radiobutton $w.validate.tracking_r \
 208                        -text {Merged Into Tracking Branch:} \
 209                        -value tracking \
 210                        -variable delete_branch_checktype
 211                eval tk_optionMenu $w.validate.tracking_m \
 212                        delete_branch_trackinghead \
 213                        $all_trackings
 214                grid $w.validate.tracking_r $w.validate.tracking_m -sticky w
 215        }
 216        radiobutton $w.validate.always_r \
 217                -text {Always (Do not perform merge checks)} \
 218                -value always \
 219                -variable delete_branch_checktype
 220        grid $w.validate.always_r -columnspan 2 -sticky w
 221        grid columnconfigure $w.validate 1 -weight 1
 222        pack $w.validate -anchor nw -fill x -pady 5 -padx 5
 223
 224        set delete_branch_head $current_branch
 225        set delete_branch_checktype head
 226
 227        bind $w <Visibility> "grab $w; focus $w"
 228        bind $w <Key-Escape> "destroy $w"
 229        wm title $w "[appname] ([reponame]): Delete Branch"
 230        tkwait window $w
 231}
 232
 233proc switch_branch {new_branch} {
 234        global HEAD commit_type current_branch repo_config
 235
 236        if {![lock_index switch]} return
 237
 238        # -- Our in memory state should match the repository.
 239        #
 240        repository_state curType curHEAD curMERGE_HEAD
 241        if {[string match amend* $commit_type]
 242                && $curType eq {normal}
 243                && $curHEAD eq $HEAD} {
 244        } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
 245                info_popup {Last scanned state does not match repository state.
 246
 247Another Git program has modified this repository since the last scan.  A rescan must be performed before the current branch can be changed.
 248
 249The rescan will be automatically started now.
 250}
 251                unlock_index
 252                rescan {set ui_status_value {Ready.}}
 253                return
 254        }
 255
 256        # -- Don't do a pointless switch.
 257        #
 258        if {$current_branch eq $new_branch} {
 259                unlock_index
 260                return
 261        }
 262
 263        if {$repo_config(gui.trustmtime) eq {true}} {
 264                switch_branch_stage2 {} $new_branch
 265        } else {
 266                set ui_status_value {Refreshing file status...}
 267                set cmd [list git update-index]
 268                lappend cmd -q
 269                lappend cmd --unmerged
 270                lappend cmd --ignore-missing
 271                lappend cmd --refresh
 272                set fd_rf [open "| $cmd" r]
 273                fconfigure $fd_rf -blocking 0 -translation binary
 274                fileevent $fd_rf readable \
 275                        [list switch_branch_stage2 $fd_rf $new_branch]
 276        }
 277}
 278
 279proc switch_branch_stage2 {fd_rf new_branch} {
 280        global ui_status_value HEAD
 281
 282        if {$fd_rf ne {}} {
 283                read $fd_rf
 284                if {![eof $fd_rf]} return
 285                close $fd_rf
 286        }
 287
 288        set ui_status_value "Updating working directory to '$new_branch'..."
 289        set cmd [list git read-tree]
 290        lappend cmd -m
 291        lappend cmd -u
 292        lappend cmd --exclude-per-directory=.gitignore
 293        lappend cmd $HEAD
 294        lappend cmd $new_branch
 295        set fd_rt [open "| $cmd" r]
 296        fconfigure $fd_rt -blocking 0 -translation binary
 297        fileevent $fd_rt readable \
 298                [list switch_branch_readtree_wait $fd_rt $new_branch]
 299}
 300
 301proc switch_branch_readtree_wait {fd_rt new_branch} {
 302        global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
 303        global current_branch
 304        global ui_comm ui_status_value
 305
 306        # -- We never get interesting output on stdout; only stderr.
 307        #
 308        read $fd_rt
 309        fconfigure $fd_rt -blocking 1
 310        if {![eof $fd_rt]} {
 311                fconfigure $fd_rt -blocking 0
 312                return
 313        }
 314
 315        # -- The working directory wasn't in sync with the index and
 316        #    we'd have to overwrite something to make the switch. A
 317        #    merge is required.
 318        #
 319        if {[catch {close $fd_rt} err]} {
 320                regsub {^fatal: } $err {} err
 321                warn_popup "File level merge required.
 322
 323$err
 324
 325Staying on branch '$current_branch'."
 326                set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)."
 327                unlock_index
 328                return
 329        }
 330
 331        # -- Update the symbolic ref.  Core git doesn't even check for failure
 332        #    here, it Just Works(tm).  If it doesn't we are in some really ugly
 333        #    state that is difficult to recover from within git-gui.
 334        #
 335        if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} {
 336                error_popup "Failed to set current branch.
 337
 338This working directory is only partially switched.  We successfully updated your files, but failed to update an internal Git file.
 339
 340This should not have occurred.  [appname] will now close and give up.
 341
 342$err"
 343                do_quit
 344                return
 345        }
 346
 347        # -- Update our repository state.  If we were previously in amend mode
 348        #    we need to toss the current buffer and do a full rescan to update
 349        #    our file lists.  If we weren't in amend mode our file lists are
 350        #    accurate and we can avoid the rescan.
 351        #
 352        unlock_index
 353        set selected_commit_type new
 354        if {[string match amend* $commit_type]} {
 355                $ui_comm delete 0.0 end
 356                $ui_comm edit reset
 357                $ui_comm edit modified false
 358                rescan {set ui_status_value "Checked out branch '$current_branch'."}
 359        } else {
 360                repository_state commit_type HEAD MERGE_HEAD
 361                set PARENT $HEAD
 362                set ui_status_value "Checked out branch '$current_branch'."
 363        }
 364}