Skip to content

Commit c7f7457

Browse files
Johannes Sixtspearce
authored andcommitted
git-gui: "Stage Line": Treat independent changes in adjacent lines better
Assume that we want to commit these states: Old state == HEAD Intermediate state New state -------------------------------------------------------- context before context before context before old 1 new 1 new 1 old 2 old 2 new 2 context after context after context after that is, want to commit two changes in this order: 1. transform "old 1" into "new 1" 2. transform "old 2" into "new 2" [This discussion and this patch is about this very case and one other case as outlined below; any other intermediate states that one could imagine are not affected by this patch.] Now assume further, that we have not staged and commited anything, but we have already changed the working file to the new state. Then we will see this hunk in the "Unstaged Changes": @@ -1,4 +1,4 @@ context before -old 1 -old 2 +new 1 +new 2 context after The obvious way to stage the intermediate state is to apply "Stage This Line" to "-old 1" and "+new 1". Unfortunately, this resulted in this intermediate state: context before old 2 new 1 context after which is not what we wanted. In fact, it was impossible to stage the intermediate state using "Stage Line". The crux was that if a "+" line was staged, then the "-" lines were converted to context lines and arranged *before* the "+" line in the forged hunk that we fed to 'git apply'. With this patch we now treat "+" lines that are staged differently. In particular, the "-" lines before the "+" block are moved *after* the staged "+" line. Now it is possible to get the correct intermediate state by staging "-old 1" and "+new 1". Problem solved. But there is a catch. Noticing that we didn't get the right intermediate state by staging "-old 1" and "+new 1", we could have had the idea to stage the complete hunk and to *unstage* "-old 2" and "+new 2". But... the result is the same. The reason is that there is the exact symmetric problem with unstaging the last "-" and "+" line that are in adjacent blocks of "-" and "+" lines. This patch does *not* change the way in which "-" lines are *unstaged*. Why? Because if we did (i.e. move "+" lines before the "-" line after converting them to context lines), then it would be impossible to stage this intermediate state: context before old 1 new 2 context after that is, it would be impossible to stage the two independet changes in the opposite order. Let's look at this case a bit further: The obvious way to get this intermediate state would be to apply "Stage This Line" to "-old 2" and "+new 2". Before this patch, this worked as expected. With this patch, it does not work as expected, but it can still be achieved by first staging the entire hunk, then *unstaging* "-old 1" and "+new 1". In summary, this patch makes a common case possible, at the expense that a less common case is made more complicated for the user. Signed-off-by: Johannes Sixt <johannes.sixt@telecom.at> Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
1 parent fa6b5b3 commit c7f7457

1 file changed

Lines changed: 58 additions & 3 deletions

File tree

lib/diff.tcl

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,53 @@ proc apply_line {x y} {
411411
set hh [lindex [split $hh ,] 0]
412412
set hln [lindex [split $hh -] 1]
413413

414+
# There is a special situation to take care of. Consider this hunk:
415+
#
416+
# @@ -10,4 +10,4 @@
417+
# context before
418+
# -old 1
419+
# -old 2
420+
# +new 1
421+
# +new 2
422+
# context after
423+
#
424+
# We used to keep the context lines in the order they appear in the
425+
# hunk. But then it is not possible to correctly stage only
426+
# "-old 1" and "+new 1" - it would result in this staged text:
427+
#
428+
# context before
429+
# old 2
430+
# new 1
431+
# context after
432+
#
433+
# (By symmetry it is not possible to *un*stage "old 2" and "new 2".)
434+
#
435+
# We resolve the problem by introducing an asymmetry, namely, when
436+
# a "+" line is *staged*, it is moved in front of the context lines
437+
# that are generated from the "-" lines that are immediately before
438+
# the "+" block. That is, we construct this patch:
439+
#
440+
# @@ -10,4 +10,5 @@
441+
# context before
442+
# +new 1
443+
# old 1
444+
# old 2
445+
# context after
446+
#
447+
# But we do *not* treat "-" lines that are *un*staged in a special
448+
# way.
449+
#
450+
# With this asymmetry it is possible to stage the change
451+
# "old 1" -> "new 1" directly, and to stage the change
452+
# "old 2" -> "new 2" by first staging the entire hunk and
453+
# then unstaging the change "old 1" -> "new 1".
454+
455+
# This is non-empty if and only if we are _staging_ changes;
456+
# then it accumulates the consecutive "-" lines (after converting
457+
# them to context lines) in order to be moved after the "+" change
458+
# line.
459+
set pre_context {}
460+
414461
set n 0
415462
set i_l [$ui_diff index "$i_l + 1 lines"]
416463
set patch {}
@@ -422,19 +469,27 @@ proc apply_line {x y} {
422469
[$ui_diff compare $the_l < $next_l]} {
423470
# the line to stage/unstage
424471
set ln [$ui_diff get $i_l $next_l]
425-
set patch "$patch$ln"
426472
if {$c1 eq {-}} {
427473
set n [expr $n+1]
474+
set patch "$patch$pre_context$ln"
475+
} else {
476+
set patch "$patch$ln$pre_context"
428477
}
478+
set pre_context {}
429479
} elseif {$c1 ne {-} && $c1 ne {+}} {
430480
# context line
431481
set ln [$ui_diff get $i_l $next_l]
432-
set patch "$patch$ln"
482+
set patch "$patch$pre_context$ln"
433483
set n [expr $n+1]
484+
set pre_context {}
434485
} elseif {$c1 eq $to_context} {
435486
# turn change line into context line
436487
set ln [$ui_diff get "$i_l + 1 chars" $next_l]
437-
set patch "$patch $ln"
488+
if {$c1 eq {-}} {
489+
set pre_context "$pre_context $ln"
490+
} else {
491+
set patch "$patch $ln"
492+
}
438493
set n [expr $n+1]
439494
}
440495
set i_l $next_l

0 commit comments

Comments
 (0)