e56f674c6dc3348d9ee9e23e8424947fa4a321e7
   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 switch_branch {new_branch} {
  70        global HEAD commit_type current_branch repo_config
  71
  72        if {![lock_index switch]} return
  73
  74        # -- Our in memory state should match the repository.
  75        #
  76        repository_state curType curHEAD curMERGE_HEAD
  77        if {[string match amend* $commit_type]
  78                && $curType eq {normal}
  79                && $curHEAD eq $HEAD} {
  80        } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
  81                info_popup {Last scanned state does not match repository state.
  82
  83Another Git program has modified this repository since the last scan.  A rescan must be performed before the current branch can be changed.
  84
  85The rescan will be automatically started now.
  86}
  87                unlock_index
  88                rescan {set ui_status_value {Ready.}}
  89                return
  90        }
  91
  92        # -- Don't do a pointless switch.
  93        #
  94        if {$current_branch eq $new_branch} {
  95                unlock_index
  96                return
  97        }
  98
  99        if {$repo_config(gui.trustmtime) eq {true}} {
 100                switch_branch_stage2 {} $new_branch
 101        } else {
 102                set ui_status_value {Refreshing file status...}
 103                set cmd [list git update-index]
 104                lappend cmd -q
 105                lappend cmd --unmerged
 106                lappend cmd --ignore-missing
 107                lappend cmd --refresh
 108                set fd_rf [open "| $cmd" r]
 109                fconfigure $fd_rf -blocking 0 -translation binary
 110                fileevent $fd_rf readable \
 111                        [list switch_branch_stage2 $fd_rf $new_branch]
 112        }
 113}
 114
 115proc switch_branch_stage2 {fd_rf new_branch} {
 116        global ui_status_value HEAD
 117
 118        if {$fd_rf ne {}} {
 119                read $fd_rf
 120                if {![eof $fd_rf]} return
 121                close $fd_rf
 122        }
 123
 124        set ui_status_value "Updating working directory to '$new_branch'..."
 125        set cmd [list git read-tree]
 126        lappend cmd -m
 127        lappend cmd -u
 128        lappend cmd --exclude-per-directory=.gitignore
 129        lappend cmd $HEAD
 130        lappend cmd $new_branch
 131        set fd_rt [open "| $cmd" r]
 132        fconfigure $fd_rt -blocking 0 -translation binary
 133        fileevent $fd_rt readable \
 134                [list switch_branch_readtree_wait $fd_rt $new_branch]
 135}
 136
 137proc switch_branch_readtree_wait {fd_rt new_branch} {
 138        global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
 139        global current_branch
 140        global ui_comm ui_status_value
 141
 142        # -- We never get interesting output on stdout; only stderr.
 143        #
 144        read $fd_rt
 145        fconfigure $fd_rt -blocking 1
 146        if {![eof $fd_rt]} {
 147                fconfigure $fd_rt -blocking 0
 148                return
 149        }
 150
 151        # -- The working directory wasn't in sync with the index and
 152        #    we'd have to overwrite something to make the switch. A
 153        #    merge is required.
 154        #
 155        if {[catch {close $fd_rt} err]} {
 156                regsub {^fatal: } $err {} err
 157                warn_popup "File level merge required.
 158
 159$err
 160
 161Staying on branch '$current_branch'."
 162                set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)."
 163                unlock_index
 164                return
 165        }
 166
 167        # -- Update the symbolic ref.  Core git doesn't even check for failure
 168        #    here, it Just Works(tm).  If it doesn't we are in some really ugly
 169        #    state that is difficult to recover from within git-gui.
 170        #
 171        if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} {
 172                error_popup "Failed to set current branch.
 173
 174This working directory is only partially switched.  We successfully updated your files, but failed to update an internal Git file.
 175
 176This should not have occurred.  [appname] will now close and give up.
 177
 178$err"
 179                do_quit
 180                return
 181        }
 182
 183        # -- Update our repository state.  If we were previously in amend mode
 184        #    we need to toss the current buffer and do a full rescan to update
 185        #    our file lists.  If we weren't in amend mode our file lists are
 186        #    accurate and we can avoid the rescan.
 187        #
 188        unlock_index
 189        set selected_commit_type new
 190        if {[string match amend* $commit_type]} {
 191                $ui_comm delete 0.0 end
 192                $ui_comm edit reset
 193                $ui_comm edit modified false
 194                rescan {set ui_status_value "Checked out branch '$current_branch'."}
 195        } else {
 196                repository_state commit_type HEAD MERGE_HEAD
 197                set PARENT $HEAD
 198                set ui_status_value "Checked out branch '$current_branch'."
 199        }
 200}