[PATCH 4/4] pacdiff: Learn the (M)erge mode

Denton Liu liu.denton at gmail.com
Sat Dec 26 07:27:20 UTC 2020


Currently, pacdiff only allows users to diff between the current file
and the new file. However, the merging of files could be automated by
the use of some 3-way merge utility.

Teach pacdiff the (M)erge mode which performs a 3-way merge using a
given $MERGEPROG (`diff3 -m` by default). The base file is taken from
from the second-newest package in the cache.

Signed-off-by: Denton Liu <liu.denton at gmail.com>
---

Notes:
    There are a few things I'd like some comments on:
    
    	* Is there a better way of getting the base file? I assume that
    	  most people will run pacdiff right after an update but what if
    	  they run pacdiff after two or more updates?
    
    	* How is the UI flow for the merge command? Do we want to let
    	  automerges write out without warning or do we want to do
    	  something else?
    
    	* When displaying conflicts, I currently display a diff against
    	  the base and the merged version. Do we want to display a diff
    	  against the merged and the current version?

 doc/pacdiff.8.txt |  9 ++++--
 src/pacdiff.sh.in | 81 +++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 85 insertions(+), 5 deletions(-)

diff --git a/doc/pacdiff.8.txt b/doc/pacdiff.8.txt
index a89c0e7..592da72 100644
--- a/doc/pacdiff.8.txt
+++ b/doc/pacdiff.8.txt
@@ -18,8 +18,8 @@ Description
 -----------
 pacdiff is a script which looks for pacorig, pacnew and pacsave files from the
 backup entries found in the local Pacman db. For every found file the option is
-given to view, skip, diff, remove or overwrite the found pacorig, pacnew or
-pacsave file.
+given to view, merge, skip, diff, remove or overwrite the found pacorig, pacnew
+or pacsave file.
 
 Environment
 -----------
@@ -29,6 +29,9 @@ Environment
 *DIFFSEARCHPATH*::
 	Override the default search path '/etc', only when using find.
 
+*MERGEPROG*::
+	Override the default 'diff3 -m' 3-way merge program. One possible
+	alternative is 'git merge-file -p'.
 
 Options
 -------
@@ -47,6 +50,8 @@ Options
 *\--nocolor*::
 	Remove colors from output.
 
+*-c, \--cachedir <dir>*::
+	Scan 'dir' instead as the pacman cache for 3-way merge base candidates.
 
 See Also
 --------
diff --git a/src/pacdiff.sh.in b/src/pacdiff.sh.in
index a50cb93..cdecdb4 100644
--- a/src/pacdiff.sh.in
+++ b/src/pacdiff.sh.in
@@ -27,6 +27,8 @@ LIBRARY=${LIBRARY:-'@libmakepkgdir@'}
 
 diffprog=${DIFFPROG:-'vim -d'}
 diffsearchpath=${DIFFSEARCHPATH:-/etc}
+mergeprog=${MERGEPROG:-'diff3 -m'}
+cachedir=
 USE_COLOR='y'
 declare -a oldsaves
 declare -i USE_FIND=0 USE_LOCATE=0 USE_PACDB=0 OUTPUTONLY=0
@@ -53,13 +55,16 @@ Search Options:     select one (default: --pacmandb)
   -p/--pacmandb     scan active config files from pacman database
 
 General Options:
-  -o/--output       print files instead of merging them
-  --nocolor         remove colors from output
+  -o/--output         print files instead of merging them
+  --nocolor           remove colors from output
+  -c/--cachedir <dir> scan "dir" for 3-way merge base candidates.
+                      (default: read from @sysconfdir@/pacman.conf)
 
 Environment Variables:
   DIFFPROG          override the merge program: (default: 'vim -d')
   DIFFSEARCHPATH    override the search path. (only when using find)
                     (default: /etc)
+  MERGEPROG         override the 3-way merge program: (default: 'diff3 -m')
 
 Example: DIFFPROG=meld DIFFSEARCHPATH="/boot /etc /usr" $myname
 Example: $myname --output --locate
@@ -83,6 +88,62 @@ print_existing_pacsave(){
 	done
 }
 
+base_cache_tar() {
+	package="$1"
+
+	[[ -d $cachedir ]] ||
+		die "cachedir '%s' does not exist or is not a directory" "$cachedir"
+
+	# unlikely that this will fail, but better make sure
+	pushd "$cachedir" &>/dev/null || die "failed to chdir to '%s'" "$cachedir"
+
+	find "$PWD" -name "$package-[0-9]*.pkg.tar*" | pacsort --files | sed -ne '2p'
+
+	popd &>/dev/null
+}
+
+merge_file() {
+	pacfile="$1"
+	file="$2"
+
+	package="$(pacman -Qoq "$file")" || return 1
+	base_tar="$(base_cache_tar "$package")"
+
+	if [[ -z $base_tar ]]; then
+		msg2 "Unable to find a base package."
+		return 1
+	fi
+
+	basename="$(basename "$file")"
+	base="$(mktemp --tmpdir "$basename.base.XXX")"
+	merged="$(mktemp --tmpdir "$basename.merged.XXX")"
+
+	tar -xOf "$base_tar" "${file#/}" >"$base"
+	if $mergeprog "$file" "$base" "$pacfile" >"$merged"; then
+		msg2 "Merged without conflicts."
+	else
+		$diffprog "$file" "$merged"
+
+		while :; do
+			ask "Would you like to use the results of the merge? [y/n] "
+
+			read c || return 1
+			case $c in
+				y|Y) break ;;
+				n|N) return 1 ;;
+				*) msg2 "Invalid answer." ;;
+			esac
+		done
+	fi
+
+	if ! cp -v "$merged" "$file"; then
+		warning "Unable to write merged file to %s. Merged file is preserved at %s" "$file" "$merged"
+		return 1
+	fi
+	rm -v "$pacfile" "$base" "$merged"
+	return 0
+}
+
 cmd() {
 	if (( USE_LOCATE )); then
 		locate -0 -e -b \*.pacnew \*.pacorig \*.pacsave '*.pacsave.[0-9]*'
@@ -114,6 +175,8 @@ while [[ -n "$1" ]]; do
 			OUTPUTONLY=1;;
 		--nocolor)
 			USE_COLOR='n';;
+		-c|--cachedir)
+			cachedir="$2"; shift;;
 		-V|--version)
 			version; exit 0;;
 		-h|--help)
@@ -135,6 +198,10 @@ if ! type -p ${diffprog%% *} >/dev/null && (( ! OUTPUTONLY )); then
 	die "Cannot find the $diffprog binary required for viewing differences."
 fi
 
+if ! type -p ${mergeprog%% *} >/dev/null && (( ! OUTPUTONLY )); then
+	die "Cannot find the $mergeprog binary required for merging differences."
+fi
+
 case $(( USE_FIND + USE_LOCATE + USE_PACDB )) in
 	0) USE_PACDB=1;; # set the default search option
 	[^1]) error "Only one search option may be used at a time"
@@ -153,6 +220,10 @@ if (( USE_PACDB )); then
 	fi
 fi
 
+if [[ -z $cachedir ]]; then
+	cachedir="$(pacman-conf CacheDir)"
+fi
+
 # see http://mywiki.wooledge.org/BashFAQ/020
 while IFS= read -u 3 -r -d '' pacfile; do
 	file="${pacfile%.pac*}"
@@ -181,7 +252,7 @@ while IFS= read -u 3 -r -d '' pacfile; do
 		rm -v "$pacfile"
 	else
 		while :; do
-			ask "(V)iew, (S)kip, (R)emove %s, (O)verwrite with %s, (Q)uit: [v/s/r/o/q] " "$file_type" "$file_type"
+			ask "(V)iew, (M)erge, (S)kip, (R)emove %s, (O)verwrite with %s, (Q)uit: [v/m/s/r/o/q] " "$file_type" "$file_type"
 			read c || break
 			case $c in
 				q|Q) exit 0;;
@@ -194,6 +265,10 @@ while IFS= read -u 3 -r -d '' pacfile; do
 						rm -v "$pacfile"
 						break
 					fi ;;
+				m|M)
+					if merge_file "$pacfile" "$file"; then
+						break
+					fi ;;
 				s|S) break ;;
 				*) msg2 "Invalid answer." ;;
 			esac
-- 
2.29.2.889.g5298b911bd


More information about the pacman-contrib mailing list