diff options
Diffstat (limited to 'git-gui/lib/diff.tcl')
| -rw-r--r-- | git-gui/lib/diff.tcl | 336 | 
1 files changed, 336 insertions, 0 deletions
| diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl new file mode 100644 index 0000000000..7e715a6865 --- /dev/null +++ b/git-gui/lib/diff.tcl @@ -0,0 +1,336 @@ +# git-gui diff viewer +# Copyright (C) 2006, 2007 Shawn Pearce + +proc clear_diff {} { +	global ui_diff current_diff_path current_diff_header +	global ui_index ui_workdir + +	$ui_diff conf -state normal +	$ui_diff delete 0.0 end +	$ui_diff conf -state disabled + +	set current_diff_path {} +	set current_diff_header {} + +	$ui_index tag remove in_diff 0.0 end +	$ui_workdir tag remove in_diff 0.0 end +} + +proc reshow_diff {} { +	global ui_status_value file_states file_lists +	global current_diff_path current_diff_side + +	set p $current_diff_path +	if {$p eq {}} { +		# No diff is being shown. +	} elseif {$current_diff_side eq {} +		|| [catch {set s $file_states($p)}] +		|| [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { +		clear_diff +	} else { +		show_diff $p $current_diff_side +	} +} + +proc handle_empty_diff {} { +	global current_diff_path file_states file_lists + +	set path $current_diff_path +	set s $file_states($path) +	if {[lindex $s 0] ne {_M}} return + +	info_popup "No differences detected. + +[short_path $path] has no changes. + +The modification date of this file was updated by another application, but the content within the file was not changed. + +A rescan will be automatically started to find other files which may have the same state." + +	clear_diff +	display_file $path __ +	rescan {set ui_status_value {Ready.}} 0 +} + +proc show_diff {path w {lno {}}} { +	global file_states file_lists +	global is_3way_diff diff_active repo_config +	global ui_diff ui_status_value ui_index ui_workdir +	global current_diff_path current_diff_side current_diff_header + +	if {$diff_active || ![lock_index read]} return + +	clear_diff +	if {$lno == {}} { +		set lno [lsearch -sorted -exact $file_lists($w) $path] +		if {$lno >= 0} { +			incr lno +		} +	} +	if {$lno >= 1} { +		$w tag add in_diff $lno.0 [expr {$lno + 1}].0 +	} + +	set s $file_states($path) +	set m [lindex $s 0] +	set is_3way_diff 0 +	set diff_active 1 +	set current_diff_path $path +	set current_diff_side $w +	set current_diff_header {} +	set ui_status_value "Loading diff of [escape_path $path]..." + +	# - Git won't give us the diff, there's nothing to compare to! +	# +	if {$m eq {_O}} { +		set max_sz [expr {128 * 1024}] +		if {[catch { +				set fd [open $path r] +				set content [read $fd $max_sz] +				close $fd +				set sz [file size $path] +			} err ]} { +			set diff_active 0 +			unlock_index +			set ui_status_value "Unable to display [escape_path $path]" +			error_popup "Error loading file:\n\n$err" +			return +		} +		$ui_diff conf -state normal +		if {![catch {set type [exec file $path]}]} { +			set n [string length $path] +			if {[string equal -length $n $path $type]} { +				set type [string range $type $n end] +				regsub {^:?\s*} $type {} type +			} +			$ui_diff insert end "* $type\n" d_@ +		} +		if {[string first "\0" $content] != -1} { +			$ui_diff insert end \ +				"* Binary file (not showing content)." \ +				d_@ +		} else { +			if {$sz > $max_sz} { +				$ui_diff insert end \ +"* Untracked file is $sz bytes. +* Showing only first $max_sz bytes. +" d_@ +			} +			$ui_diff insert end $content +			if {$sz > $max_sz} { +				$ui_diff insert end " +* Untracked file clipped here by [appname]. +* To see the entire file, use an external editor. +" d_@ +			} +		} +		$ui_diff conf -state disabled +		set diff_active 0 +		unlock_index +		set ui_status_value {Ready.} +		return +	} + +	set cmd [list | git] +	if {$w eq $ui_index} { +		lappend cmd diff-index +		lappend cmd --cached +	} elseif {$w eq $ui_workdir} { +		if {[string index $m 0] eq {U}} { +			lappend cmd diff +		} else { +			lappend cmd diff-files +		} +	} + +	lappend cmd -p +	lappend cmd --no-color +	if {$repo_config(gui.diffcontext) > 0} { +		lappend cmd "-U$repo_config(gui.diffcontext)" +	} +	if {$w eq $ui_index} { +		lappend cmd [PARENT] +	} +	lappend cmd -- +	lappend cmd $path + +	if {[catch {set fd [open $cmd r]} err]} { +		set diff_active 0 +		unlock_index +		set ui_status_value "Unable to display [escape_path $path]" +		error_popup "Error loading diff:\n\n$err" +		return +	} + +	fconfigure $fd \ +		-blocking 0 \ +		-encoding binary \ +		-translation binary +	fileevent $fd readable [list read_diff $fd] +} + +proc read_diff {fd} { +	global ui_diff ui_status_value diff_active +	global is_3way_diff current_diff_header + +	$ui_diff conf -state normal +	while {[gets $fd line] >= 0} { +		# -- Cleanup uninteresting diff header lines. +		# +		if {   [string match {diff --git *}      $line] +			|| [string match {diff --cc *}       $line] +			|| [string match {diff --combined *} $line] +			|| [string match {--- *}             $line] +			|| [string match {+++ *}             $line]} { +			append current_diff_header $line "\n" +			continue +		} +		if {[string match {index *} $line]} continue +		if {$line eq {deleted file mode 120000}} { +			set line "deleted symlink" +		} + +		# -- Automatically detect if this is a 3 way diff. +		# +		if {[string match {@@@ *} $line]} {set is_3way_diff 1} + +		if {[string match {mode *} $line] +			|| [string match {new file *} $line] +			|| [string match {deleted file *} $line] +			|| [string match {Binary files * and * differ} $line] +			|| $line eq {\ No newline at end of file} +			|| [regexp {^\* Unmerged path } $line]} { +			set tags {} +		} elseif {$is_3way_diff} { +			set op [string range $line 0 1] +			switch -- $op { +			{  } {set tags {}} +			{@@} {set tags d_@} +			{ +} {set tags d_s+} +			{ -} {set tags d_s-} +			{+ } {set tags d_+s} +			{- } {set tags d_-s} +			{--} {set tags d_--} +			{++} { +				if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} { +					set line [string replace $line 0 1 {  }] +					set tags d$op +				} else { +					set tags d_++ +				} +			} +			default { +				puts "error: Unhandled 3 way diff marker: {$op}" +				set tags {} +			} +			} +		} else { +			set op [string index $line 0] +			switch -- $op { +			{ } {set tags {}} +			{@} {set tags d_@} +			{-} {set tags d_-} +			{+} { +				if {[regexp {^\+([<>]{7} |={7})} $line _g op]} { +					set line [string replace $line 0 0 { }] +					set tags d$op +				} else { +					set tags d_+ +				} +			} +			default { +				puts "error: Unhandled 2 way diff marker: {$op}" +				set tags {} +			} +			} +		} +		$ui_diff insert end $line $tags +		if {[string index $line end] eq "\r"} { +			$ui_diff tag add d_cr {end - 2c} +		} +		$ui_diff insert end "\n" $tags +	} +	$ui_diff conf -state disabled + +	if {[eof $fd]} { +		close $fd +		set diff_active 0 +		unlock_index +		set ui_status_value {Ready.} + +		if {[$ui_diff index end] eq {2.0}} { +			handle_empty_diff +		} +	} +} + +proc apply_hunk {x y} { +	global current_diff_path current_diff_header current_diff_side +	global ui_diff ui_index file_states + +	if {$current_diff_path eq {} || $current_diff_header eq {}} return +	if {![lock_index apply_hunk]} return + +	set apply_cmd {git apply --cached --whitespace=nowarn} +	set mi [lindex $file_states($current_diff_path) 0] +	if {$current_diff_side eq $ui_index} { +		set mode unstage +		lappend apply_cmd --reverse +		if {[string index $mi 0] ne {M}} { +			unlock_index +			return +		} +	} else { +		set mode stage +		if {[string index $mi 1] ne {M}} { +			unlock_index +			return +		} +	} + +	set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0] +	set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0] +	if {$s_lno eq {}} { +		unlock_index +		return +	} + +	set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end] +	if {$e_lno eq {}} { +		set e_lno end +	} + +	if {[catch { +		set p [open "| $apply_cmd" w] +		fconfigure $p -translation binary -encoding binary +		puts -nonewline $p $current_diff_header +		puts -nonewline $p [$ui_diff get $s_lno $e_lno] +		close $p} err]} { +		error_popup "Failed to $mode selected hunk.\n\n$err" +		unlock_index +		return +	} + +	$ui_diff conf -state normal +	$ui_diff delete $s_lno $e_lno +	$ui_diff conf -state disabled + +	if {[$ui_diff get 1.0 end] eq "\n"} { +		set o _ +	} else { +		set o ? +	} + +	if {$current_diff_side eq $ui_index} { +		set mi ${o}M +	} elseif {[string index $mi 0] eq {_}} { +		set mi M$o +	} else { +		set mi ?$o +	} +	unlock_index +	display_file $current_diff_path $mi +	if {$o eq {_}} { +		clear_diff +	} +} | 
