Code

git-gui: Optimize for newstyle refs/remotes layout
[git.git] / lib / branch.tcl
1 # git-gui branch (create/delete) support
2 # Copyright (C) 2006, 2007 Shawn Pearce
4 proc load_all_heads {} {
5         global all_heads
6         global some_heads_tracking
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
19         set all_heads [lsort $all_heads]
20 }
22 proc 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
31         return [lsort $all_tags]
32 }
34 proc populate_branch_menu {} {
35         global all_heads disable_on_lock
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         }
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 }
67 proc radio_selector {varname value args} {
68         upvar #0 $varname var
69         set var $value
70 }
72 proc switch_branch {new_branch} {
73         global HEAD commit_type current_branch repo_config
75         if {![lock_index switch]} return
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.
86 Another Git program has modified this repository since the last scan.  A rescan must be performed before the current branch can be changed.
88 The rescan will be automatically started now.
89 }
90                 unlock_index
91                 rescan {set ui_status_value {Ready.}}
92                 return
93         }
95         # -- Don't do a pointless switch.
96         #
97         if {$current_branch eq $new_branch} {
98                 unlock_index
99                 return
100         }
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         }
118 proc switch_branch_stage2 {fd_rf new_branch} {
119         global ui_status_value HEAD
121         if {$fd_rf ne {}} {
122                 read $fd_rf
123                 if {![eof $fd_rf]} return
124                 close $fd_rf
125         }
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]
140 proc 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
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         }
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.
162 $err
164 Staying 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         }
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.
177 This working directory is only partially switched.  We successfully updated your files, but failed to update an internal Git file.
179 This should not have occurred.  [appname] will now close and give up.
181 $err"
182                 do_quit
183                 return
184         }
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         }