* https://github.com/prati0100/git-gui: git-gui: allow opening currently selected file in default app git-gui: allow closing console window with Escape git gui: fix branch name encoding error git-gui: revert untracked files by deleting them git-gui: update status bar to track operations git-gui: consolidate naming conventions
		
			
				
	
	
		
			647 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Tcl
		
	
	
	
	
	
			
		
		
	
	
			647 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Tcl
		
	
	
	
	
	
# git-gui commit checkout support
 | 
						|
# Copyright (C) 2007 Shawn Pearce
 | 
						|
 | 
						|
class checkout_op {
 | 
						|
 | 
						|
field w        {}; # our window (if we have one)
 | 
						|
field w_cons   {}; # embedded console window object
 | 
						|
 | 
						|
field new_expr   ; # expression the user saw/thinks this is
 | 
						|
field new_hash   ; # commit SHA-1 we are switching to
 | 
						|
field new_ref    ; # ref we are updating/creating
 | 
						|
field old_hash   ; # commit SHA-1 that was checked out when we started
 | 
						|
 | 
						|
field parent_w      .; # window that started us
 | 
						|
field merge_type none; # type of merge to apply to existing branch
 | 
						|
field merge_base   {}; # merge base if we have another ref involved
 | 
						|
field fetch_spec   {}; # refetch tracking branch if used?
 | 
						|
field checkout      1; # actually checkout the branch?
 | 
						|
field create        0; # create the branch if it doesn't exist?
 | 
						|
field remote_source {}; # same as fetch_spec, to setup tracking
 | 
						|
 | 
						|
field reset_ok      0; # did the user agree to reset?
 | 
						|
field fetch_ok      0; # did the fetch succeed?
 | 
						|
 | 
						|
field readtree_d   {}; # buffered output from read-tree
 | 
						|
field update_old   {}; # was the update-ref call deferred?
 | 
						|
field reflog_msg   {}; # log message for the update-ref call
 | 
						|
 | 
						|
constructor new {expr hash {ref {}}} {
 | 
						|
	set new_expr $expr
 | 
						|
	set new_hash $hash
 | 
						|
	set new_ref  $ref
 | 
						|
 | 
						|
	return $this
 | 
						|
}
 | 
						|
 | 
						|
method parent {path} {
 | 
						|
	set parent_w [winfo toplevel $path]
 | 
						|
}
 | 
						|
 | 
						|
method enable_merge {type} {
 | 
						|
	set merge_type $type
 | 
						|
}
 | 
						|
 | 
						|
method enable_fetch {spec} {
 | 
						|
	set fetch_spec $spec
 | 
						|
}
 | 
						|
 | 
						|
method remote_source {spec} {
 | 
						|
	set remote_source $spec
 | 
						|
}
 | 
						|
 | 
						|
method enable_checkout {co} {
 | 
						|
	set checkout $co
 | 
						|
}
 | 
						|
 | 
						|
method enable_create {co} {
 | 
						|
	set create $co
 | 
						|
}
 | 
						|
 | 
						|
method run {} {
 | 
						|
	if {$fetch_spec ne {}} {
 | 
						|
		global M1B
 | 
						|
 | 
						|
		# We were asked to refresh a single tracking branch
 | 
						|
		# before we get to work.  We should do that before we
 | 
						|
		# consider any ref updating.
 | 
						|
		#
 | 
						|
		set fetch_ok 0
 | 
						|
		set l_trck [lindex $fetch_spec 0]
 | 
						|
		set remote [lindex $fetch_spec 1]
 | 
						|
		set r_head [lindex $fetch_spec 2]
 | 
						|
		regsub ^refs/heads/ $r_head {} r_name
 | 
						|
 | 
						|
		set cmd [list git fetch $remote]
 | 
						|
		if {$l_trck ne {}} {
 | 
						|
			lappend cmd +$r_head:$l_trck
 | 
						|
		} else {
 | 
						|
			lappend cmd $r_head
 | 
						|
		}
 | 
						|
 | 
						|
		_toplevel $this {Refreshing Tracking Branch}
 | 
						|
		set w_cons [::console::embed \
 | 
						|
			$w.console \
 | 
						|
			[mc "Fetching %s from %s" $r_name $remote]]
 | 
						|
		pack $w.console -fill both -expand 1
 | 
						|
		$w_cons exec $cmd [cb _finish_fetch]
 | 
						|
 | 
						|
		bind $w <$M1B-Key-w> break
 | 
						|
		bind $w <$M1B-Key-W> break
 | 
						|
		bind $w <Visibility> "
 | 
						|
			[list grab $w]
 | 
						|
			[list focus $w]
 | 
						|
		"
 | 
						|
		wm protocol $w WM_DELETE_WINDOW [cb _noop]
 | 
						|
		tkwait window $w
 | 
						|
 | 
						|
		if {!$fetch_ok} {
 | 
						|
			delete_this
 | 
						|
			return 0
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if {$new_ref ne {}} {
 | 
						|
		# If we have a ref we need to update it before we can
 | 
						|
		# proceed with a checkout (if one was enabled).
 | 
						|
		#
 | 
						|
		if {![_update_ref $this]} {
 | 
						|
			delete_this
 | 
						|
			return 0
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if {$checkout} {
 | 
						|
		_checkout $this
 | 
						|
		return 1
 | 
						|
	}
 | 
						|
 | 
						|
	delete_this
 | 
						|
	return 1
 | 
						|
}
 | 
						|
 | 
						|
method _noop {} {}
 | 
						|
 | 
						|
method _finish_fetch {ok} {
 | 
						|
	if {$ok} {
 | 
						|
		set l_trck [lindex $fetch_spec 0]
 | 
						|
		if {$l_trck eq {}} {
 | 
						|
			set l_trck FETCH_HEAD
 | 
						|
		}
 | 
						|
		if {[catch {set new_hash [git rev-parse --verify "$l_trck^0"]} err]} {
 | 
						|
			set ok 0
 | 
						|
			$w_cons insert [mc "fatal: Cannot resolve %s" $l_trck]
 | 
						|
			$w_cons insert $err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	$w_cons done $ok
 | 
						|
	set w_cons {}
 | 
						|
	wm protocol $w WM_DELETE_WINDOW {}
 | 
						|
 | 
						|
	if {$ok} {
 | 
						|
		destroy $w
 | 
						|
		set w {}
 | 
						|
	} else {
 | 
						|
		button $w.close -text [mc Close] -command [list destroy $w]
 | 
						|
		pack $w.close -side bottom -anchor e -padx 10 -pady 10
 | 
						|
	}
 | 
						|
 | 
						|
	set fetch_ok $ok
 | 
						|
}
 | 
						|
 | 
						|
method _update_ref {} {
 | 
						|
	global null_sha1 current_branch repo_config
 | 
						|
 | 
						|
	set ref $new_ref
 | 
						|
	set new $new_hash
 | 
						|
 | 
						|
	set is_current 0
 | 
						|
	set rh refs/heads/
 | 
						|
	set rn [string length $rh]
 | 
						|
	if {[string equal -length $rn $rh $ref]} {
 | 
						|
		set newbranch [string range $ref $rn end]
 | 
						|
		if {$current_branch eq $newbranch} {
 | 
						|
			set is_current 1
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		set newbranch $ref
 | 
						|
	}
 | 
						|
 | 
						|
	if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} {
 | 
						|
		# Assume it does not exist, and that is what the error was.
 | 
						|
		#
 | 
						|
		if {!$create} {
 | 
						|
			_error $this [mc "Branch '%s' does not exist." $newbranch]
 | 
						|
			return 0
 | 
						|
		}
 | 
						|
 | 
						|
		set reflog_msg "branch: Created from $new_expr"
 | 
						|
		set cur $null_sha1
 | 
						|
 | 
						|
		if {($repo_config(branch.autosetupmerge) eq {true}
 | 
						|
			|| $repo_config(branch.autosetupmerge) eq {always})
 | 
						|
			&& $remote_source ne {}
 | 
						|
			&& "refs/heads/$newbranch" eq $ref} {
 | 
						|
 | 
						|
			set c_remote [lindex $remote_source 1]
 | 
						|
			set c_merge [lindex $remote_source 2]
 | 
						|
			if {[catch {
 | 
						|
					git config branch.$newbranch.remote $c_remote
 | 
						|
					git config branch.$newbranch.merge  $c_merge
 | 
						|
				} err]} {
 | 
						|
				_error $this [strcat \
 | 
						|
				[mc "Failed to configure simplified git-pull for '%s'." $newbranch] \
 | 
						|
				"\n\n$err"]
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} elseif {$create && $merge_type eq {none}} {
 | 
						|
		# We were told to create it, but not do a merge.
 | 
						|
		# Bad.  Name shouldn't have existed.
 | 
						|
		#
 | 
						|
		_error $this [mc "Branch '%s' already exists." $newbranch]
 | 
						|
		return 0
 | 
						|
	} elseif {!$create && $merge_type eq {none}} {
 | 
						|
		# We aren't creating, it exists and we don't merge.
 | 
						|
		# We are probably just a simple branch switch.
 | 
						|
		# Use whatever value we just read.
 | 
						|
		#
 | 
						|
		set new      $cur
 | 
						|
		set new_hash $cur
 | 
						|
	} elseif {$new eq $cur} {
 | 
						|
		# No merge would be required, don't compute anything.
 | 
						|
		#
 | 
						|
	} else {
 | 
						|
		catch {set merge_base [git merge-base $new $cur]}
 | 
						|
		if {$merge_base eq $cur} {
 | 
						|
			# The current branch is older.
 | 
						|
			#
 | 
						|
			set reflog_msg "merge $new_expr: Fast-forward"
 | 
						|
		} else {
 | 
						|
			switch -- $merge_type {
 | 
						|
			ff {
 | 
						|
				if {$merge_base eq $new} {
 | 
						|
					# The current branch is actually newer.
 | 
						|
					#
 | 
						|
					set new $cur
 | 
						|
					set new_hash $cur
 | 
						|
				} else {
 | 
						|
					_error $this [mc "Branch '%s' already exists.\n\nIt cannot fast-forward to %s.\nA merge is required." $newbranch $new_expr]
 | 
						|
					return 0
 | 
						|
				}
 | 
						|
			}
 | 
						|
			reset {
 | 
						|
				# The current branch will lose things.
 | 
						|
				#
 | 
						|
				if {[_confirm_reset $this $cur]} {
 | 
						|
					set reflog_msg "reset $new_expr"
 | 
						|
				} else {
 | 
						|
					return 0
 | 
						|
				}
 | 
						|
			}
 | 
						|
			default {
 | 
						|
				_error $this [mc "Merge strategy '%s' not supported." $merge_type]
 | 
						|
				return 0
 | 
						|
			}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if {$new ne $cur} {
 | 
						|
		if {$is_current} {
 | 
						|
			# No so fast.  We should defer this in case
 | 
						|
			# we cannot update the working directory.
 | 
						|
			#
 | 
						|
			set update_old $cur
 | 
						|
			return 1
 | 
						|
		}
 | 
						|
 | 
						|
		if {[catch {
 | 
						|
				git update-ref -m $reflog_msg $ref $new $cur
 | 
						|
			} err]} {
 | 
						|
			_error $this [strcat [mc "Failed to update '%s'." $newbranch] "\n\n$err"]
 | 
						|
			return 0
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return 1
 | 
						|
}
 | 
						|
 | 
						|
method _checkout {} {
 | 
						|
	if {[lock_index checkout_op]} {
 | 
						|
		after idle [cb _start_checkout]
 | 
						|
	} else {
 | 
						|
		_error $this [mc "Staging area (index) is already locked."]
 | 
						|
		delete_this
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
method _start_checkout {} {
 | 
						|
	global HEAD commit_type
 | 
						|
 | 
						|
	# -- Our in memory state should match the repository.
 | 
						|
	#
 | 
						|
	repository_state curType old_hash curMERGE_HEAD
 | 
						|
	if {[string match amend* $commit_type]
 | 
						|
		&& $curType eq {normal}
 | 
						|
		&& $old_hash eq $HEAD} {
 | 
						|
	} elseif {$commit_type ne $curType || $HEAD ne $old_hash} {
 | 
						|
		info_popup [mc "Last scanned state does not match repository state.
 | 
						|
 | 
						|
Another Git program has modified this repository since the last scan.  A rescan must be performed before the current branch can be changed.
 | 
						|
 | 
						|
The rescan will be automatically started now.
 | 
						|
"]
 | 
						|
		unlock_index
 | 
						|
		rescan ui_ready
 | 
						|
		delete_this
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if {$old_hash eq $new_hash} {
 | 
						|
		_after_readtree $this
 | 
						|
	} elseif {[is_config_true gui.trustmtime]} {
 | 
						|
		_readtree $this
 | 
						|
	} else {
 | 
						|
		ui_status [mc "Refreshing file status..."]
 | 
						|
		set fd [git_read update-index \
 | 
						|
			-q \
 | 
						|
			--unmerged \
 | 
						|
			--ignore-missing \
 | 
						|
			--refresh \
 | 
						|
			]
 | 
						|
		fconfigure $fd -blocking 0 -translation binary
 | 
						|
		fileevent $fd readable [cb _refresh_wait $fd]
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
method _refresh_wait {fd} {
 | 
						|
	read $fd
 | 
						|
	if {[eof $fd]} {
 | 
						|
		close $fd
 | 
						|
		_readtree $this
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
method _name {} {
 | 
						|
	if {$new_ref eq {}} {
 | 
						|
		return [string range $new_hash 0 7]
 | 
						|
	}
 | 
						|
 | 
						|
	set rh refs/heads/
 | 
						|
	set rn [string length $rh]
 | 
						|
	if {[string equal -length $rn $rh $new_ref]} {
 | 
						|
		return [string range $new_ref $rn end]
 | 
						|
	} else {
 | 
						|
		return $new_ref
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
method _readtree {} {
 | 
						|
	global HEAD
 | 
						|
 | 
						|
	set readtree_d {}
 | 
						|
	set status_bar_operation [$::main_status start \
 | 
						|
		[mc "Updating working directory to '%s'..." [_name $this]] \
 | 
						|
		[mc "files checked out"]]
 | 
						|
 | 
						|
	set fd [git_read --stderr read-tree \
 | 
						|
		-m \
 | 
						|
		-u \
 | 
						|
		-v \
 | 
						|
		--exclude-per-directory=.gitignore \
 | 
						|
		$HEAD \
 | 
						|
		$new_hash \
 | 
						|
		]
 | 
						|
	fconfigure $fd -blocking 0 -translation binary
 | 
						|
	fileevent $fd readable [cb _readtree_wait $fd $status_bar_operation]
 | 
						|
}
 | 
						|
 | 
						|
method _readtree_wait {fd status_bar_operation} {
 | 
						|
	global current_branch
 | 
						|
 | 
						|
	set buf [read $fd]
 | 
						|
	$status_bar_operation update_meter $buf
 | 
						|
	append readtree_d $buf
 | 
						|
 | 
						|
	fconfigure $fd -blocking 1
 | 
						|
	if {![eof $fd]} {
 | 
						|
		fconfigure $fd -blocking 0
 | 
						|
		$status_bar_operation stop
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if {[catch {close $fd}]} {
 | 
						|
		set err $readtree_d
 | 
						|
		regsub {^fatal: } $err {} err
 | 
						|
		$status_bar_operation stop [mc "Aborted checkout of '%s' (file level merging is required)." [_name $this]]
 | 
						|
		warn_popup [strcat [mc "File level merge required."] "
 | 
						|
 | 
						|
$err
 | 
						|
 | 
						|
" [mc "Staying on branch '%s'." $current_branch]]
 | 
						|
		unlock_index
 | 
						|
		delete_this
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	$status_bar_operation stop
 | 
						|
	_after_readtree $this
 | 
						|
}
 | 
						|
 | 
						|
method _after_readtree {} {
 | 
						|
	global commit_type HEAD MERGE_HEAD PARENT
 | 
						|
	global current_branch is_detached
 | 
						|
	global ui_comm
 | 
						|
 | 
						|
	set name [_name $this]
 | 
						|
	set log "checkout: moving"
 | 
						|
	if {!$is_detached} {
 | 
						|
		append log " from $current_branch"
 | 
						|
	}
 | 
						|
 | 
						|
	# -- Move/create HEAD as a symbolic ref.  Core git does not
 | 
						|
	#    even check for failure here, it Just Works(tm).  If it
 | 
						|
	#    doesn't we are in some really ugly state that is difficult
 | 
						|
	#    to recover from within git-gui.
 | 
						|
	#
 | 
						|
	set rh refs/heads/
 | 
						|
	set rn [string length $rh]
 | 
						|
	if {[string equal -length $rn $rh $new_ref]} {
 | 
						|
		set new_branch [string range $new_ref $rn end]
 | 
						|
		if {$is_detached || $current_branch ne $new_branch} {
 | 
						|
			append log " to $new_branch"
 | 
						|
			if {[catch {
 | 
						|
					git symbolic-ref -m $log HEAD $new_ref
 | 
						|
				} err]} {
 | 
						|
				_fatal $this $err
 | 
						|
			}
 | 
						|
			set current_branch $new_branch
 | 
						|
			set is_detached 0
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		if {!$is_detached || $new_hash ne $HEAD} {
 | 
						|
			append log " to $new_expr"
 | 
						|
			if {[catch {
 | 
						|
					_detach_HEAD $log $new_hash
 | 
						|
				} err]} {
 | 
						|
				_fatal $this $err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		set current_branch HEAD
 | 
						|
		set is_detached 1
 | 
						|
	}
 | 
						|
 | 
						|
	# -- We had to defer updating the branch itself until we
 | 
						|
	#    knew the working directory would update.  So now we
 | 
						|
	#    need to finish that work.  If it fails we're in big
 | 
						|
	#    trouble.
 | 
						|
	#
 | 
						|
	if {$update_old ne {}} {
 | 
						|
		if {[catch {
 | 
						|
				git update-ref \
 | 
						|
					-m $reflog_msg \
 | 
						|
					$new_ref \
 | 
						|
					$new_hash \
 | 
						|
					$update_old
 | 
						|
			} err]} {
 | 
						|
			_fatal $this $err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if {$is_detached} {
 | 
						|
		info_popup [mc "You are no longer on a local branch.
 | 
						|
 | 
						|
If you wanted to be on a branch, create one now starting from 'This Detached Checkout'."]
 | 
						|
	}
 | 
						|
 | 
						|
	# -- Run the post-checkout hook.
 | 
						|
	#
 | 
						|
	set fd_ph [githook_read post-checkout $old_hash $new_hash 1]
 | 
						|
	if {$fd_ph ne {}} {
 | 
						|
		global pch_error
 | 
						|
		set pch_error {}
 | 
						|
		fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
 | 
						|
		fileevent $fd_ph readable [cb _postcheckout_wait $fd_ph]
 | 
						|
	} else {
 | 
						|
		_update_repo_state $this
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
method _postcheckout_wait {fd_ph} {
 | 
						|
	global pch_error
 | 
						|
 | 
						|
	append pch_error [read $fd_ph]
 | 
						|
	fconfigure $fd_ph -blocking 1
 | 
						|
	if {[eof $fd_ph]} {
 | 
						|
		if {[catch {close $fd_ph}]} {
 | 
						|
			hook_failed_popup post-checkout $pch_error 0
 | 
						|
		}
 | 
						|
		unset pch_error
 | 
						|
		_update_repo_state $this
 | 
						|
		return
 | 
						|
	}
 | 
						|
	fconfigure $fd_ph -blocking 0
 | 
						|
}
 | 
						|
 | 
						|
method _update_repo_state {} {
 | 
						|
	# -- Update our repository state.  If we were previously in
 | 
						|
	#    amend mode we need to toss the current buffer and do a
 | 
						|
	#    full rescan to update our file lists.  If we weren't in
 | 
						|
	#    amend mode our file lists are accurate and we can avoid
 | 
						|
	#    the rescan.
 | 
						|
	#
 | 
						|
	global commit_type_is_amend commit_type HEAD MERGE_HEAD PARENT
 | 
						|
	global ui_comm
 | 
						|
 | 
						|
	unlock_index
 | 
						|
	set name [_name $this]
 | 
						|
	set commit_type_is_amend 0
 | 
						|
	if {[string match amend* $commit_type]} {
 | 
						|
		$ui_comm delete 0.0 end
 | 
						|
		$ui_comm edit reset
 | 
						|
		$ui_comm edit modified false
 | 
						|
		rescan [list ui_status [mc "Checked out '%s'." $name]]
 | 
						|
	} else {
 | 
						|
		repository_state commit_type HEAD MERGE_HEAD
 | 
						|
		set PARENT $HEAD
 | 
						|
		ui_status [mc "Checked out '%s'." $name]
 | 
						|
	}
 | 
						|
	delete_this
 | 
						|
}
 | 
						|
 | 
						|
git-version proc _detach_HEAD {log new} {
 | 
						|
	>= 1.5.3 {
 | 
						|
		git update-ref --no-deref -m $log HEAD $new
 | 
						|
	}
 | 
						|
	default {
 | 
						|
		set p [gitdir HEAD]
 | 
						|
		file delete $p
 | 
						|
		set fd [open $p w]
 | 
						|
		fconfigure $fd -translation lf -encoding utf-8
 | 
						|
		puts $fd $new
 | 
						|
		close $fd
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
method _confirm_reset {cur} {
 | 
						|
	set reset_ok 0
 | 
						|
	set name [_name $this]
 | 
						|
	set gitk [list do_gitk [list $cur ^$new_hash]]
 | 
						|
 | 
						|
	_toplevel $this {Confirm Branch Reset}
 | 
						|
	pack [label $w.msg1 \
 | 
						|
		-anchor w \
 | 
						|
		-justify left \
 | 
						|
		-text [mc "Resetting '%s' to '%s' will lose the following commits:" $name $new_expr]\
 | 
						|
		] -anchor w
 | 
						|
 | 
						|
	set list $w.list.l
 | 
						|
	frame $w.list
 | 
						|
	text $list \
 | 
						|
		-font font_diff \
 | 
						|
		-width 80 \
 | 
						|
		-height 10 \
 | 
						|
		-wrap none \
 | 
						|
		-xscrollcommand [list $w.list.sbx set] \
 | 
						|
		-yscrollcommand [list $w.list.sby set]
 | 
						|
	scrollbar $w.list.sbx -orient h -command [list $list xview]
 | 
						|
	scrollbar $w.list.sby -orient v -command [list $list yview]
 | 
						|
	pack $w.list.sbx -fill x -side bottom
 | 
						|
	pack $w.list.sby -fill y -side right
 | 
						|
	pack $list -fill both -expand 1
 | 
						|
	pack $w.list -fill both -expand 1 -padx 5 -pady 5
 | 
						|
 | 
						|
	pack [label $w.msg2 \
 | 
						|
		-anchor w \
 | 
						|
		-justify left \
 | 
						|
		-text [mc "Recovering lost commits may not be easy."] \
 | 
						|
		]
 | 
						|
	pack [label $w.msg3 \
 | 
						|
		-anchor w \
 | 
						|
		-justify left \
 | 
						|
		-text [mc "Reset '%s'?" $name] \
 | 
						|
		]
 | 
						|
 | 
						|
	frame $w.buttons
 | 
						|
	button $w.buttons.visualize \
 | 
						|
		-text [mc Visualize] \
 | 
						|
		-command $gitk
 | 
						|
	pack $w.buttons.visualize -side left
 | 
						|
	button $w.buttons.reset \
 | 
						|
		-text [mc Reset] \
 | 
						|
		-command "
 | 
						|
			set @reset_ok 1
 | 
						|
			destroy $w
 | 
						|
		"
 | 
						|
	pack $w.buttons.reset -side right
 | 
						|
	button $w.buttons.cancel \
 | 
						|
		-default active \
 | 
						|
		-text [mc Cancel] \
 | 
						|
		-command [list destroy $w]
 | 
						|
	pack $w.buttons.cancel -side right -padx 5
 | 
						|
	pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 | 
						|
 | 
						|
	set fd [git_read rev-list --pretty=oneline $cur ^$new_hash]
 | 
						|
	while {[gets $fd line] > 0} {
 | 
						|
		set abbr [string range $line 0 7]
 | 
						|
		set subj [string range $line 41 end]
 | 
						|
		$list insert end "$abbr  $subj\n"
 | 
						|
	}
 | 
						|
	close $fd
 | 
						|
	$list configure -state disabled
 | 
						|
 | 
						|
	bind $w    <Key-v> $gitk
 | 
						|
	bind $w <Visibility> "
 | 
						|
		grab $w
 | 
						|
		focus $w.buttons.cancel
 | 
						|
	"
 | 
						|
	bind $w <Key-Return> [list destroy $w]
 | 
						|
	bind $w <Key-Escape> [list destroy $w]
 | 
						|
	tkwait window $w
 | 
						|
	return $reset_ok
 | 
						|
}
 | 
						|
 | 
						|
method _error {msg} {
 | 
						|
	if {[winfo ismapped $parent_w]} {
 | 
						|
		set p $parent_w
 | 
						|
	} else {
 | 
						|
		set p .
 | 
						|
	}
 | 
						|
 | 
						|
	tk_messageBox \
 | 
						|
		-icon error \
 | 
						|
		-type ok \
 | 
						|
		-title [wm title $p] \
 | 
						|
		-parent $p \
 | 
						|
		-message $msg
 | 
						|
}
 | 
						|
 | 
						|
method _toplevel {title} {
 | 
						|
	regsub -all {::} $this {__} w
 | 
						|
	set w .$w
 | 
						|
 | 
						|
	if {[winfo ismapped $parent_w]} {
 | 
						|
		set p $parent_w
 | 
						|
	} else {
 | 
						|
		set p .
 | 
						|
	}
 | 
						|
 | 
						|
	toplevel $w
 | 
						|
	wm title $w $title
 | 
						|
	wm geometry $w "+[winfo rootx $p]+[winfo rooty $p]"
 | 
						|
}
 | 
						|
 | 
						|
method _fatal {err} {
 | 
						|
	error_popup [strcat [mc "Failed to set current branch.
 | 
						|
 | 
						|
This working directory is only partially switched.  We successfully updated your files, but failed to update an internal Git file.
 | 
						|
 | 
						|
This should not have occurred.  %s will now close and give up." [appname]] "
 | 
						|
 | 
						|
$err"]
 | 
						|
	exit 1
 | 
						|
}
 | 
						|
 | 
						|
}
 |