git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 2/2] git-gui: revert untracked files by deleting them

From
Jonathan Gilbert via GitGitGadget <gitgitgadget@gmail.com>
Date
Oct 30, 2019, 06:48 UTC
Message-ID
<0190f6f2f978a674a29a1e2013d00bc289851c76.1572418123.git.gitgitgadget@gmail.com>
In-Reply-To
<pull.436.git.1572418123.gitgitgadget@gmail.com>
From: Jonathan Gilbert <JonathanG@iQmetrix.com>

Updates the revert_helper procedure to also detect untracked files. If files are present, the user is asked if they want them deleted. A new proc delete_files with helper delete_helper performs the deletion in batches, to allow the UI to remain responsive.

Signed-off-by: Jonathan Gilbert <JonathanG@iQmetrix.com>
---
 lib/index.tcl | 255 +++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 222 insertions(+), 33 deletions(-)
diff --git a/lib/index.tcl b/lib/index.tcl
index 28d4d2a54e..9661ddb556 100644
--- a/lib/index.tcl
+++ b/lib/index.tcl
@@ -393,11 +393,20 @@ proc revert_helper {txt paths} {
 
 	if {![lock_index begin-update]} return
 
+	# The index is now locked. Some of the paths below include calls that
+	# unlock the index (e.g. checked_index). If we reach the end and the
+	# index is still locked, we need to unlock it before returning.
+	set need_unlock_index 1
+
 	set path_list [list]
+	set untracked_list [list]
 	set after {}
 	foreach path $paths {
 		switch -glob -- [lindex $file_states($path) 0] {
 		U? {continue}
+		?O {
+			lappend untracked_list $path
+		}
 		?M -
 		?T -
 		?D {
@@ -409,45 +418,225 @@ proc revert_helper {txt paths} {
 		}
 	}
 
+	set path_cnt [llength $path_list]
+	set untracked_cnt [llength $untracked_list]
 
-	# 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 $path_list]
-	if {$n == 0} {
-		unlock_index
-		return
-	} elseif {$n == 1} {
-		set query [mc "Revert changes in file %s?" [short_path [lindex $path_list]]]
-	} else {
-		set query [mc "Revert changes in these %i files?" $n]
-	}
+	if {$path_cnt > 0} {
+		# 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.
+		#
+		if {$path_cnt == 1} {
+			set query [mc \
+				"Revert changes in file %s?" \
+				[short_path [lindex $path_list]] \
+				]
+		} else {
+			set query [mc \
+				"Revert changes in these %i files?" \
+				$path_cnt]
+		}
 
-	set reply [tk_dialog \
-		.confirm_revert \
-		"[appname] ([reponame])" \
-		"$query
+		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 \
+			question \
+			1 \
+			[mc "Do Nothing"] \
+			[mc "Revert Changes"] \
+			]
+
+		if {$reply == 1} {
+			checkout_index \
+				$txt \
+				$path_list \
+				[concat $after [list ui_ready]]
+
+			set need_unlock_index 0
+		}
+	}
+
+	if {$need_unlock_index} { unlock_index }
+
+	if {$untracked_cnt > 0} {
+		# Split question between singular and plural cases, because
+		# such distinction is needed in some languages.
+		#
+		# 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.
+		#
+		if {$untracked_cnt == 1} {
+			set query [mc \
+				"Delete untracked file %s?" \
+				[short_path [lindex $untracked_list]] \
+				]
+		} else {
+			set query [mc \
+				"Delete these %i untracked files?" \
+				$untracked_cnt \
+				]
+		}
+
+		set reply [tk_dialog \
+			.confirm_revert \
+			"[appname] ([reponame])" \
+			"$query
+
+[mc "Files will be permanently deleted."]" \
+			question \
+			1 \
+			[mc "Do Nothing"] \
+			[mc "Delete Files"] \
+			]
+
+		if {$reply == 1} {
+			delete_files $untracked_list
+		}
+	}
+}
+
+# Delete all of the specified files, performing deletion in batches to allow the
+# UI to remain responsive and updated.
+proc delete_files {path_list} {
+	# Enable progress bar status updates
+	$::main_status start [mc "Deleting"] [mc "files"]
+
+	set path_index 0
+	set deletion_errors [list]
+	set deletion_error_path "not yet captured"
+	set batch_size 50
+
+	delete_helper \
+		$path_list \
+		$path_index \
+		$deletion_errors \
+		$deletion_error_path \
+		$batch_size
+}
+
+# Helper function to delete a list of files in batches. Each call deletes one
+# batch of files, and then schedules a call for the next batch after any UI
+# messages have been processed.
+proc delete_helper \
+	{path_list path_index deletion_errors deletion_error_path batch_size} {
+	global file_states
+
+	set path_cnt [llength $path_list]
+
+	set batch_remaining $batch_size
+
+	while {$batch_remaining > 0} {
+		if {$path_index >= $path_cnt} { break }
+
+		set path [lindex $path_list $path_index]
+
+		set deletion_failed [catch {file delete -- $path} deletion_error]
+
+		if {$deletion_failed} {
+			lappend deletion_errors $deletion_error
+
+			# Optimistically capture the path that failed, in case
+			# there's only one.
+			set deletion_error_path $path
+		} else {
+			remove_empty_directories [file dirname $path]
+
+			# Don't assume the deletion worked. Remove the file from
+			# the UI, but only if it no longer exists.
+			if {![lexists $path]} {
+				unset file_states($path)
+				display_file $path __
+			}
+		}
+
+		incr path_index 1
+		incr batch_remaining -1
+	}
+
+	# Update the progress bar to indicate that this batch has been
+	# completed. The update will be visible when this procedure returns
+	# and allows the UI thread to process messages.
+	$::main_status update $path_index $path_cnt
+
+	if {$path_index < $path_cnt} {
+		# The Tcler's Wiki lists this as the best practice for keeping
+		# a UI active and processing messages during a long-running
+		# operation.
+
+		after idle [list after 0 [list \
+			delete_helper \
 			$path_list \
-			[concat $after [list ui_ready]]
+			$path_index \
+			$deletion_errors \
+			$deletion_error_path \
+			$batch_size \
+			]]
 	} else {
-		unlock_index
+		# Finish the status bar operation.
+		$::main_status stop
+
+		# Report error, if any, based on how many deletions failed.
+		set deletion_error_cnt [llength $deletion_errors]
+
+		if {$deletion_error_cnt == 1} {
+			error_popup [mc \
+				"File %s could not be deleted: %s" \
+				$deletion_error_path \
+				[lindex $deletion_errors 0] \
+				]
+		} elseif {$deletion_error_cnt == $path_cnt} {
+			error_popup [mc \
+				"None of the selected files could be deleted." \
+				]
+		} elseif {$deletion_error_cnt > 1} {
+			error_popup [mc \
+				"%d of the selected files could not be deleted." \
+				$deletion_error_cnt]
+		}
+
+		reshow_diff
+		ui_ready
+	}
+}
+
+# This function is from the TCL documentation:
+#
+#   https://wiki.tcl-lang.org/page/file+exists
+#
+# [file exists] returns false if the path does exist but is a symlink to a path
+# that doesn't exist. This proc returns true if the path exists, regardless of
+# whether it is a symlink and whether it is broken.
+proc lexists name {
+	expr {![catch {file lstat $name finfo}]}
+}
+
+# Remove as many empty directories as we can starting at the specified path.
+# If we encounter a directory that is not empty, or if a directory deletion
+# fails, then we stop the operation and return to the caller. Even if this
+# procedure fails to delete any directories at all, it does not report failure.
+proc remove_empty_directories {directory_path} {
+	set parent_path [file dirname $directory_path]
+
+	while {$parent_path != $directory_path} {
+		set contents [glob -nocomplain -dir $directory_path *]
+
+		if {[llength $contents] > 0} { break }
+		if {[catch {file delete -- $directory_path}]} { break }
+
+		set directory_path $parent_path
+		set parent_path [file dirname $directory_path]
 	}
 }
 
-- 
gitgitgadget
Previous: Pratyush YadavNext: Pratyush Yadav
Message 4 of 57 in “git-gui: revert untracked files by deleting them”
  1. 0/2 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Oct 30, 2019
  2. 1/2 git-gui: consolidate naming conventionsJonathan Gilbert via GitGitGadget, Oct 30, 2019
  3. Pratyush YadavNov 3, 2019
  4. 2/2 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Oct 30, 2019
  5. Pratyush YadavNov 3, 2019
  6. Jonathan GilbertNov 4, 2019
  7. Jonathan GilbertNov 4, 2019
  8. Bert WesargOct 30, 2019
  9. Jonathan GilbertOct 30, 2019
  10. Pratyush YadavNov 3, 2019
  11. Jonathan GilbertNov 3, 2019
  12. Pratyush YadavNov 3, 2019
  13. 0/2 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Nov 7, 2019
  14. 1/2 git-gui: consolidate naming conventionsJonathan Gilbert via GitGitGadget, Nov 7, 2019
  15. 2/2 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Nov 7, 2019
  16. Pratyush YadavNov 11, 2019
  17. Jonathan GilbertNov 11, 2019
  18. Philip OakleyNov 11, 2019
  19. Jonathan GilbertNov 12, 2019
  20. Philip OakleyNov 12, 2019
  21. Jonathan GilbertNov 12, 2019
  22. Philip OakleyNov 26, 2019
  23. Pratyush YadavNov 12, 2019
  24. Pratyush YadavNov 11, 2019
  25. 0/2 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Nov 13, 2019
  26. 2/2 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Nov 13, 2019
  27. Pratyush YadavNov 16, 2019
  28. Jonathan GilbertNov 16, 2019
  29. 1/2 git-gui: consolidate naming conventionsJonathan Gilbert via GitGitGadget, Nov 13, 2019
  30. 0/2 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Nov 17, 2019
  31. 1/2 git-gui: consolidate naming conventionsJonathan Gilbert via GitGitGadget, Nov 17, 2019
  32. 2/2 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Nov 17, 2019
  33. Pratyush YadavNov 24, 2019
  34. Pratyush YadavNov 19, 2019
  35. Jonathan GilbertNov 19, 2019
  36. 0/3 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Nov 24, 2019
  37. 1/3 git-gui: consolidate naming conventionsJonathan Gilbert via GitGitGadget, Nov 24, 2019
  38. 2/3 git-gui: update status bar to track operationsJonathan Gilbert via GitGitGadget, Nov 24, 2019
  39. Pratyush YadavNov 27, 2019
  40. Jonathan GilbertNov 28, 2019
  41. 3/3 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Nov 24, 2019
  42. Pratyush YadavNov 27, 2019
  43. 0/3 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Nov 28, 2019
  44. 1/3 git-gui: consolidate naming conventionsJonathan Gilbert via GitGitGadget, Nov 28, 2019
  45. 2/3 git-gui: update status bar to track operationsJonathan Gilbert via GitGitGadget, Nov 28, 2019
  46. Pratyush YadavNov 30, 2019
  47. Jonathan GilbertDec 1, 2019
  48. Philip OakleyDec 1, 2019
  49. Jonathan GilbertDec 1, 2019
  50. 3/3 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Nov 28, 2019
  51. 0/3 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Dec 1, 2019
  52. 1/3 git-gui: consolidate naming conventionsJonathan Gilbert via GitGitGadget, Dec 1, 2019
  53. 3/3 git-gui: revert untracked files by deleting themJonathan Gilbert via GitGitGadget, Dec 1, 2019
  54. 2/3 git-gui: update status bar to track operationsJonathan Gilbert via GitGitGadget, Dec 1, 2019
  55. Benjamin PoirierFeb 26, 2020
  56. Pratyush YadavMar 2, 2020
  57. Pratyush YadavDec 5, 2019

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.