lib / branch.tclon commit git-gui: Maintain remote and source ref for tracking branches (79a060e)
   1# git-gui branch (create/delete) support
   2# Copyright (C) 2006, 2007 Shawn Pearce
   3
   4proc load_all_heads {} {
   5        global all_heads
   6        global some_heads_tracking
   7
   8        set rh refs/heads
   9        set rh_len [expr {[string length $rh] + 1}]
  10        set all_heads [list]
  11        set fd [open "| git for-each-ref --format=%(refname) $rh" r]
  12        while {[gets $fd line] > 0} {
  13                if {!$some_heads_tracking || ![is_tracking_branch $line]} {
  14                        lappend all_heads [string range $line $rh_len end]
  15                }
  16        }
  17        close $fd
  18
  19        set all_heads [lsort $all_heads]
  20}
  21
  22proc load_all_tags {} {
  23        set all_tags [list]
  24        set fd [open "| git for-each-ref --format=%(refname) refs/tags" r]
  25        while {[gets $fd line] > 0} {
  26                if {![regsub ^refs/tags/ $line {} name]} continue
  27                lappend all_tags $name
  28        }
  29        close $fd
  30
  31        return [lsort $all_tags]
  32}
  33
  34proc populate_branch_menu {} {
  35        global all_heads disable_on_lock
  36
  37        set m .mbar.branch
  38        set last [$m index last]
  39        for {set i 0} {$i <= $last} {incr i} {
  40                if {[$m type $i] eq {separator}} {
  41                        $m delete $i last
  42                        set new_dol [list]
  43                        foreach a $disable_on_lock {
  44                                if {[lindex $a 0] ne $m || [lindex $a 2] < $i} {
  45                                        lappend new_dol $a
  46                                }
  47                        }
  48                        set disable_on_lock $new_dol
  49                        break
  50                }
  51        }
  52
  53        if {$all_heads ne {}} {
  54                $m add separator
  55        }
  56        foreach b $all_heads {
  57                $m add radiobutton \
  58                        -label $b \
  59                        -command [list switch_branch $b] \
  60                        -variable current_branch \
  61                        -value $b
  62                lappend disable_on_lock \
  63                        [list $m entryconf [$m index last] -state]
  64        }
  65}
  66
  67proc radio_selector {varname value args} {
  68        upvar #0 $varname var
  69        set var $value
  70}
  71
  72proc switch_branch {new_branch} {
  73        global HEAD commit_type current_branch repo_config
  74
  75        if {![lock_index switch]} return
  76
  77        # -- Our in memory state should match the repository.
  78        #
  79        repository_state curType curHEAD curMERGE_HEAD
  80        if {[string match amend* $commit_type]
  81                && $curType eq {normal}
  82                && $curHEAD eq $HEAD} {
  83        } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
  84                info_popup {Last scanned state does not match repository state.
  85
  86Another Git program has modified this repository since the last scan.  A rescan must be performed before the current branch can be changed.
  87
  88The rescan will be automatically started now.
  89}
  90                unlock_index
  91                rescan {set ui_status_value {Ready.}}
  92                return
  93        }
  94
  95        # -- Don't do a pointless switch.
  96        #
  97        if {$current_branch eq $new_branch} {
  98                unlock_index
  99                return
 100        }
 101
 102        if {$repo_config(gui.trustmtime) eq {true}} {
 103                switch_branch_stage2 {} $new_branch
 104        } else {
 105                set ui_status_value {Refreshing file status...}
 106                set cmd [list git update-index]
 107                lappend cmd -q
 108                lappend cmd --unmerged
 109                lappend cmd --ignore-missing
 110                lappend cmd --refresh
 111                set fd_rf [open "| $cmd" r]
 112                fconfigure $fd_rf -blocking 0 -translation binary
 113                fileevent $fd_rf readable \
 114                        [list switch_branch_stage2 $fd_rf $new_branch]
 115        }
 116}
 117
 118proc switch_branch_stage2 {fd_rf new_branch} {
 119        global ui_status_value HEAD
 120
 121        if {$fd_rf ne {}} {
 122                read $fd_rf
 123                if {![eof $fd_rf]} return
 124                close $fd_rf
 125        }
 126
 127        set ui_status_value "Updating working directory to '$new_branch'..."
 128        set cmd [list git read-tree]
 129        lappend cmd -m
 130        lappend cmd -u
 131        lappend cmd --exclude-per-directory=.gitignore
 132        lappend cmd $HEAD
 133        lappend cmd $new_branch
 134        set fd_rt [open "| $cmd" r]
 135        fconfigure $fd_rt -blocking 0 -translation binary
 136        fileevent $fd_rt readable \
 137                [list switch_branch_readtree_wait $fd_rt $new_branch]
 138}
 139
 140proc switch_branch_readtree_wait {fd_rt new_branch} {
 141        global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
 142        global current_branch
 143        global ui_comm ui_status_value
 144
 145        # -- We never get interesting output on stdout; only stderr.
 146        #
 147        read $fd_rt
 148        fconfigure $fd_rt -blocking 1
 149        if {![eof $fd_rt]} {
 150                fconfigure $fd_rt -blocking 0
 151                return
 152        }
 153
 154        # -- The working directory wasn't in sync with the index and
 155        #    we'd have to overwrite something to make the switch. A
 156        #    merge is required.
 157        #
 158        if {[catch {close $fd_rt} err]} {
 159                regsub {^fatal: } $err {} err
 160                warn_popup "File level merge required.
 161
 162$err
 163
 164Staying on branch '$current_branch'."
 165                set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)."
 166                unlock_index
 167                return
 168        }
 169
 170        # -- Update the symbolic ref.  Core git doesn't even check for failure
 171        #    here, it Just Works(tm).  If it doesn't we are in some really ugly
 172        #    state that is difficult to recover from within git-gui.
 173        #
 174        if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} {
 175                error_popup "Failed to set current branch.
 176
 177This working directory is only partially switched.  We successfully updated your files, but failed to update an internal Git file.
 178
 179This should not have occurred.  [appname] will now close and give up.
 180
 181$err"
 182                do_quit
 183                return
 184        }
 185
 186        # -- Update our repository state.  If we were previously in amend mode
 187        #    we need to toss the current buffer and do a full rescan to update
 188        #    our file lists.  If we weren't in amend mode our file lists are
 189        #    accurate and we can avoid the rescan.
 190        #
 191        unlock_index
 192        set selected_commit_type new
 193        if {[string match amend* $commit_type]} {
 194                $ui_comm delete 0.0 end
 195                $ui_comm edit reset
 196                $ui_comm edit modified false
 197                rescan {set ui_status_value "Checked out branch '$current_branch'."}
 198        } else {
 199                repository_state commit_type HEAD MERGE_HEAD
 200                set PARENT $HEAD
 201                set ui_status_value "Checked out branch '$current_branch'."
 202        }
 203}