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}