about summary refs log tree commit diff
path: root/third_party/git/git-gui/lib/index.tcl
# git-gui index (add/remove) support
# Copyright (C) 2006, 2007 Shawn Pearce

proc _delete_indexlock {} {
	if {[catch {file delete -- [gitdir index.lock]} err]} {
		error_popup [strcat [mc "Unable to unlock the index."] "\n\n$err"]
	}
}

proc _close_updateindex {fd after} {
	global use_ttk NS
	fconfigure $fd -blocking 1
	if {[catch {close $fd} err]} {
		set w .indexfried
		Dialog $w
		wm withdraw $w
		wm title $w [strcat "[appname] ([reponame]): " [mc "Index Error"]]
		wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
		set s [mc "Updating the Git index failed.  A rescan will be automatically started to resynchronize git-gui."]
		text $w.msg -yscrollcommand [list $w.vs set] \
			-width [string length $s] -relief flat \
			-borderwidth 0 -highlightthickness 0 \
			-background [get_bg_color $w]
		$w.msg tag configure bold -font font_uibold -justify center
		${NS}::scrollbar $w.vs -command [list $w.msg yview]
		$w.msg insert end $s bold \n\n$err {}
		$w.msg configure -state disabled

		${NS}::button $w.continue \
			-text [mc "Continue"] \
			-command [list destroy $w]
		${NS}::button $w.unlock \
			-text [mc "Unlock Index"] \
			-command "destroy $w; _delete_indexlock"
		grid $w.msg - $w.vs -sticky news
		grid $w.unlock $w.continue - -sticky se -padx 2 -pady 2
		grid columnconfigure $w 0 -weight 1
		grid rowconfigure $w 0 -weight 1

		wm protocol $w WM_DELETE_WINDOW update
		bind $w.continue <Visibility> "
			grab $w
			focus %W
		"
		wm deiconify $w
		tkwait window $w

		$::main_status stop
		unlock_index
		rescan $after 0
		return
	}

	$::main_status stop
	unlock_index
	uplevel #0 $after
}

proc update_indexinfo {msg pathList after} {
	global update_index_cp

	if {![lock_index update]} return

	set update_index_cp 0
	set pathList [lsort $pathList]
	set totalCnt [llength $pathList]
	set batch [expr {int($totalCnt * .01) + 1}]
	if {$batch > 25} {set batch 25}

	$::main_status start $msg [mc "files"]
	set fd [git_write update-index -z --index-info]
	fconfigure $fd \
		-blocking 0 \
		-buffering full \
		-buffersize 512 \
		-encoding binary \
		-translation binary
	fileevent $fd writable [list \
		write_update_indexinfo \
		$fd \
		$pathList \
		$totalCnt \
		$batch \
		$after \
		]
}

proc write_update_indexinfo {fd pathList totalCnt batch after} {
	global update_index_cp
	global file_states current_diff_path

	if {$update_index_cp >= $totalCnt} {
		_close_updateindex $fd $after
		return
	}

	for {set i $batch} \
		{$update_index_cp < $totalCnt && $i > 0} \
		{incr i -1} {
		set path [lindex $pathList $update_index_cp]
		incr update_index_cp

		set s $file_states($path)
		switch -glob -- [lindex $s 0] {
		A? {set new _O}
		MT -
		TM -
		T_ {set new _T}
		M? {set new _M}
		TD -
		D_ {set new _D}
		D? {set new _?}
		?? {continue}
		}
		set info [lindex $s 2]
		if {$info eq {}} continue

		puts -nonewline $fd "$info\t[encoding convertto utf-8 $path]\0"
		display_file $path $new
	}

	$::main_status update $update_index_cp $totalCnt
}

proc update_index {msg pathList after} {
	global update_index_cp

	if {![lock_index update]} return

	set update_index_cp 0
	set pathList [lsort $pathList]
	set totalCnt [llength $pathList]
	set batch [expr {int($totalCnt * .01) + 1}]
	if {$batch > 25} {set batch 25}

	$::main_status start $msg [mc "files"]
	set fd [git_write update-index --add --remove -z --stdin]
	fconfigure $fd \
		-blocking 0 \
		-buffering full \
		-buffersize 512 \
		-encoding binary \
		-translation binary
	fileevent $fd writable [list \
		write_update_index \
		$fd \
		$pathList \
		$totalCnt \
		$batch \
		$after \
		]
}

proc write_update_index {fd pathList totalCnt batch after} {
	global update_index_cp
	global file_states current_diff_path

	if {$update_index_cp >= $totalCnt} {
		_close_updateindex $fd $after
		return
	}

	for {set i $batch} \
		{$update_index_cp < $totalCnt && $i > 0} \
		{incr i -1} {
		set path [lindex $pathList $update_index_cp]
		incr update_index_cp

		switch -glob -- [lindex $file_states($path) 0] {
		AD {set new __}
		?D {set new D_}
		_O -
		AT -
		AM {set new A_}
		TM -
		MT -
		_T {set new T_}
		_U -
		U? {
			if {[file exists $path]} {
				set new M_
			} else {
				set new D_
			}
		}
		?M {set new M_}
		?? {continue}
		}
		puts -nonewline $fd "[encoding convertto utf-8 $path]\0"
		display_file $path $new
	}

	$::main_status update $update_index_cp $totalCnt
}

proc checkout_index {msg pathList after} {
	global update_index_cp

	if {![lock_index update]} return

	set update_index_cp 0
	set pathList [lsort $pathList]
	set totalCnt [llength $pathList]
	set batch [expr {int($totalCnt * .01) + 1}]
	if {$batch > 25} {set batch 25}

	$::main_status start $msg [mc "files"]
	set fd [git_write checkout-index \
		--index \
		--quiet \
		--force \
		-z \
		--stdin \
		]
	fconfigure $fd \
		-blocking 0 \
		-buffering full \
		-buffersize 512 \
		-encoding binary \
		-translation binary
	fileevent $fd writable [list \
		write_checkout_index \
		$fd \
		$pathList \
		$totalCnt \
		$batch \
		$after \
		]
}

proc write_checkout_index {fd pathList totalCnt batch after} {
	global update_index_cp
	global file_states current_diff_path

	if {$update_index_cp >= $totalCnt} {
		_close_updateindex $fd $after
		return
	}

	for {set i $batch} \
		{$update_index_cp < $totalCnt && $i > 0} \
		{incr i -1} {
		set path [lindex $pathList $update_index_cp]
		incr update_index_cp
		switch -glob -- [lindex $file_states($path) 0] {
		U? {continue}
		?M -
		?T -
		?D {
			puts -nonewline $fd "[encoding convertto utf-8 $path]\0"
			display_file $path ?_
		}
		}
	}

	$::main_status update $update_index_cp $totalCnt
}

proc unstage_helper {txt paths} {
	global file_states current_diff_path

	if {![lock_index begin-update]} return

	set pathList [list]
	set after {}
	foreach path $paths {
		switch -glob -- [lindex $file_states($path) 0] {
		A? -
		M? -
		T? -
		D? {
			lappend pathList $path
			if {$path eq $current_diff_path} {
				set after {reshow_diff;}
			}
		}
		}
	}
	if {$pathList eq {}} {
		unlock_index
	} else {
		update_indexinfo \
			$txt \
			$pathList \
			[concat $after [list ui_ready]]
	}
}

proc do_unstage_selection {} {
	global current_diff_path selected_paths

	if {[array size selected_paths] > 0} {
		unstage_helper \
			[mc "Unstaging selected files from commit"] \
			[array names selected_paths]
	} elseif {$current_diff_path ne {}} {
		unstage_helper \
			[mc "Unstaging %s from commit" [short_path $current_diff_path]] \
			[list $current_diff_path]
	}
}

proc add_helper {txt paths} {
	global file_states current_diff_path

	if {![lock_index begin-update]} return

	set pathList [list]
	set after {}
	foreach path $paths {
		switch -glob -- [lindex $file_states($path) 0] {
		_U -
		U? {
			if {$path eq $current_diff_path} {
				unlock_index
				merge_stage_workdir $path
				return
			}
		}
		_O -
		?M -
		?D -
		?T {
			lappend pathList $path
			if {$path eq $current_diff_path} {
				set after {reshow_diff;}
			}
		}
		}
	}
	if {$pathList eq {}} {
		unlock_index
	} else {
		update_index \
			$txt \
			$pathList \
			[concat $after {ui_status [mc "Ready to commit."]}]
	}
}

proc do_add_selection {} {
	global current_diff_path selected_paths

	if {[array size selected_paths] > 0} {
		add_helper \
			[mc "Adding selected files"] \
			[array names selected_paths]
	} elseif {$current_diff_path ne {}} {
		add_helper \
			[mc "Adding %s" [short_path $current_diff_path]] \
			[list $current_diff_path]
	}
}

proc do_add_all {} {
	global file_states

	set paths [list]
	set untracked_paths [list]
	foreach path [array names file_states] {
		switch -glob -- [lindex $file_states($path) 0] {
		U? {continue}
		?M -
		?T -
		?D {lappend paths $path}
		?O {lappend untracked_paths $path}
		}
	}
	if {[llength $untracked_paths]} {
		set reply 0
		switch -- [get_config gui.stageuntracked] {
		no {
			set reply 0
		}
		yes {
			set reply 1
		}
		ask -
		default {
			set reply [ask_popup [mc "Stage %d untracked files?" \
									  [llength $untracked_paths]]]
		}
		}
		if {$reply} {
			set paths [concat $paths $untracked_paths]
		}
	}
	add_helper [mc "Adding all changed files"] $paths
}

proc revert_helper {txt paths} {
	global file_states current_diff_path

	if {![lock_index begin-update]} return

	set pathList [list]
	set after {}
	foreach path $paths {
		switch -glob -- [lindex $file_states($path) 0] {
		U? {continue}
		?M -
		?T -
		?D {
			lappend pathList $path
			if {$path eq $current_diff_path} {
				set after {reshow_diff;}
			}
		}
		}
	}


	# Split question between singular and plural cases, because
	# such distinction is needed in some languages. Previously, the
	# code used "Revert changes in" for both, but that can't work
	# in languages where 'in' must be combined with word from
	# rest of string (in different way for both cases of course).
	#
	# FIXME: Unfortunately, even that isn't enough in some languages
	# as they have quite complex plural-form rules. Unfortunately,
	# msgcat doesn't seem to support that kind of string translation.
	#
	set n [llength $pathList]
	if {$n == 0} {
		unlock_index
		return
	} elseif {$n == 1} {
		set query [mc "Revert changes in file %s?" [short_path [lindex $pathList]]]
	} else {
		set query [mc "Revert changes in these %i files?" $n]
	}

	set reply [tk_dialog \
		.confirm_revert \
		"[appname] ([reponame])" \
		"$query

[mc "Any unstaged changes will be permanently lost by the revert."]" \
		question \
		1 \
		[mc "Do Nothing"] \
		[mc "Revert Changes"] \
		]
	if {$reply == 1} {
		checkout_index \
			$txt \
			$pathList \
			[concat $after [list ui_ready]]
	} else {
		unlock_index
	}
}

proc do_revert_selection {} {
	global current_diff_path selected_paths

	if {[array size selected_paths] > 0} {
		revert_helper \
			[mc "Reverting selected files"] \
			[array names selected_paths]
	} elseif {$current_diff_path ne {}} {
		revert_helper \
			[mc "Reverting %s" [short_path $current_diff_path]] \
			[list $current_diff_path]
	}
}

proc do_select_commit_type {} {
	global commit_type selected_commit_type

	if {$selected_commit_type eq {new}
		&& [string match amend* $commit_type]} {
		create_new_commit
	} elseif {$selected_commit_type eq {amend}
		&& ![string match amend* $commit_type]} {
		load_last_commit

		# The amend request was rejected...
		#
		if {![string match amend* $commit_type]} {
			set selected_commit_type new
		}
	}
}