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}