1 # git-gui branch (create/delete) support
2 # Copyright (C) 2006, 2007 Shawn Pearce
4 proc load_all_heads
{} {
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
16 set all_heads
[lsort $all_heads]
19 proc load_all_tags
{} {
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
28 return [lsort $all_tags]
31 proc populate_branch_menu
{} {
32 global all_heads disable_on_lock
35 set last
[$m index last
]
36 for {set i
0} {$i <= $last} {incr i
} {
37 if {[$m type
$i] eq
{separator
}} {
40 foreach a
$disable_on_lock {
41 if {[lindex $a 0] ne
$m ||
[lindex $a 2] < $i} {
45 set disable_on_lock
$new_dol
50 if {$all_heads ne
{}} {
53 foreach b
$all_heads {
56 -command [list switch_branch
$b] \
57 -variable current_branch
\
59 lappend disable_on_lock
\
60 [list $m entryconf
[$m index last
] -state]
64 proc radio_selector
{varname value args
} {
69 proc do_delete_branch_action
{w
} {
71 global delete_branch_checktype delete_branch_head delete_branch_trackinghead
74 switch -- $delete_branch_checktype {
75 head
{set check_rev
$delete_branch_head}
76 tracking
{set check_rev
$delete_branch_trackinghead}
77 always
{set check_rev
{:none
}}
79 if {$check_rev eq
{:none
}} {
81 } elseif
{[catch {set check_cmt
[git rev-parse
--verify "${check_rev}^0"]}]} {
85 -title [wm title
$w] \
87 -message "Invalid check revision: $check_rev"
93 foreach i
[$w.
list.l curselection
] {
94 set b
[$w.
list.l get
$i]
95 if {[catch {set o
[git rev-parse
--verify $b]}]} continue
96 if {$check_cmt ne
{}} {
97 if {$b eq
$check_rev} continue
98 if {[catch {set m
[git merge-base
$o $check_cmt]}]} continue
100 lappend not_merged
$b
104 lappend to_delete
[list $b $o]
106 if {$not_merged ne
{}} {
107 set msg
"The following branches are not completely merged into $check_rev:
109 - [join $not_merged "\n - "]"
113 -title [wm title
$w] \
117 if {$to_delete eq
{}} return
118 if {$delete_branch_checktype eq
{always
}} {
119 set msg
{Recovering deleted branches is difficult.
121 Delete the selected branches?
}
125 -title [wm title
$w] \
127 -message $msg] ne yes
} {
133 foreach i
$to_delete {
136 if {[catch {git update-ref
-d "refs/heads/$b" $o} err
]} {
137 append failed
" - $b: $err\n"
139 set x
[lsearch -sorted -exact $all_heads $b]
141 set all_heads
[lreplace $all_heads $x $x]
150 -title [wm title
$w] \
152 -message "Failed to delete branches:\n$failed"
155 set all_heads
[lsort $all_heads]
160 proc do_delete_branch
{} {
161 global all_heads tracking_branches current_branch
162 global delete_branch_checktype delete_branch_head delete_branch_trackinghead
166 wm geometry
$w "+[winfo rootx .]+[winfo rooty .]"
168 label $w.header
-text {Delete Local Branch
} \
170 pack $w.header
-side top
-fill x
173 button $w.buttons.create
-text Delete
\
174 -command [list do_delete_branch_action
$w]
175 pack $w.buttons.create
-side right
176 button $w.buttons.cancel
-text {Cancel
} \
177 -command [list destroy $w]
178 pack $w.buttons.cancel
-side right
-padx 5
179 pack $w.buttons
-side bottom
-fill x
-pady 10 -padx 10
181 labelframe $w.
list -text {Local Branches
}
185 -selectmode extended
\
186 -yscrollcommand [list $w.
list.sby
set]
187 foreach h
$all_heads {
188 if {$h ne
$current_branch} {
189 $w.
list.l insert end
$h
192 scrollbar $w.
list.sby
-command [list $w.
list.l yview
]
193 pack $w.
list.sby
-side right
-fill y
194 pack $w.
list.l
-side left
-fill both
-expand 1
195 pack $w.
list -fill both
-expand 1 -pady 5 -padx 5
197 labelframe $w.validate
-text {Delete Only If
}
198 radiobutton $w.validate.head_r
\
199 -text {Merged Into Local Branch
:} \
201 -variable delete_branch_checktype
202 eval tk_optionMenu $w.validate.head_m delete_branch_head
$all_heads
203 grid $w.validate.head_r
$w.validate.head_m
-sticky w
204 set all_trackings
[all_tracking_branches
]
205 if {$all_trackings ne
{}} {
206 set delete_branch_trackinghead
[lindex $all_trackings 0]
207 radiobutton $w.validate.tracking_r
\
208 -text {Merged Into Tracking Branch
:} \
210 -variable delete_branch_checktype
211 eval tk_optionMenu $w.validate.tracking_m
\
212 delete_branch_trackinghead
\
214 grid $w.validate.tracking_r
$w.validate.tracking_m
-sticky w
216 radiobutton $w.validate.always_r
\
217 -text {Always
(Do not perform merge checks
)} \
219 -variable delete_branch_checktype
220 grid $w.validate.always_r
-columnspan 2 -sticky w
221 grid columnconfigure
$w.validate
1 -weight 1
222 pack $w.validate
-anchor nw
-fill x
-pady 5 -padx 5
224 set delete_branch_head
$current_branch
225 set delete_branch_checktype head
227 bind $w <Visibility
> "grab $w; focus $w"
228 bind $w <Key-Escape
> "destroy $w"
229 wm title
$w "[appname] ([reponame]): Delete Branch"
233 proc switch_branch
{new_branch
} {
234 global HEAD commit_type current_branch repo_config
236 if {![lock_index
switch]} return
238 # -- Our in memory state should match the repository.
240 repository_state curType curHEAD curMERGE_HEAD
241 if {[string match amend
* $commit_type]
242 && $curType eq
{normal
}
243 && $curHEAD eq
$HEAD} {
244 } elseif
{$commit_type ne
$curType ||
$HEAD ne
$curHEAD} {
245 info_popup
{Last scanned state does not match repository state.
247 Another Git program has modified this repository since the last
scan. A rescan must be performed before the current branch can be changed.
249 The rescan will be automatically started now.
252 rescan
{set ui_status_value
{Ready.
}}
256 # -- Don't do a pointless switch.
258 if {$current_branch eq
$new_branch} {
263 if {$repo_config(gui.trustmtime
) eq
{true
}} {
264 switch_branch_stage2
{} $new_branch
266 set ui_status_value
{Refreshing
file status...
}
267 set cmd
[list git update-index
]
269 lappend cmd
--unmerged
270 lappend cmd
--ignore-missing
271 lappend cmd
--refresh
272 set fd_rf
[open "| $cmd" r
]
273 fconfigure $fd_rf -blocking 0 -translation binary
274 fileevent $fd_rf readable
\
275 [list switch_branch_stage2
$fd_rf $new_branch]
279 proc switch_branch_stage2
{fd_rf new_branch
} {
280 global ui_status_value HEAD
284 if {![eof $fd_rf]} return
288 set ui_status_value
"Updating working directory to '$new_branch'..."
289 set cmd
[list git read-tree
]
292 lappend cmd
--exclude-per
-directory
=.gitignore
294 lappend cmd
$new_branch
295 set fd_rt
[open "| $cmd" r
]
296 fconfigure $fd_rt -blocking 0 -translation binary
297 fileevent $fd_rt readable
\
298 [list switch_branch_readtree_wait
$fd_rt $new_branch]
301 proc switch_branch_readtree_wait
{fd_rt new_branch
} {
302 global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
303 global current_branch
304 global ui_comm ui_status_value
306 # -- We never get interesting output on stdout; only stderr.
309 fconfigure $fd_rt -blocking 1
311 fconfigure $fd_rt -blocking 0
315 # -- The working directory wasn't in sync with the index and
316 # we'd have to overwrite something to make the switch. A
319 if {[catch {close $fd_rt} err
]} {
320 regsub {^fatal
: } $err {} err
321 warn_popup
"File level merge required.
325 Staying on branch '$current_branch'."
326 set ui_status_value
"Aborted checkout of '$new_branch' (file level merging is required)."
331 # -- Update the symbolic ref. Core git doesn't even check for failure
332 # here, it Just Works(tm). If it doesn't we are in some really ugly
333 # state that is difficult to recover from within git-gui.
335 if {[catch {git symbolic-ref HEAD
"refs/heads/$new_branch"} err
]} {
336 error_popup
"Failed to set current branch.
338 This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file.
340 This should not have occurred. [appname] will now close and give up.
347 # -- Update our repository state. If we were previously in amend mode
348 # we need to toss the current buffer and do a full rescan to update
349 # our file lists. If we weren't in amend mode our file lists are
350 # accurate and we can avoid the rescan.
353 set selected_commit_type new
354 if {[string match amend
* $commit_type]} {
355 $ui_comm delete
0.0 end
357 $ui_comm edit modified false
358 rescan
{set ui_status_value
"Checked out branch '$current_branch'."}
360 repository_state commit_type HEAD MERGE_HEAD
362 set ui_status_value
"Checked out branch '$current_branch'."