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