Skip to content

Commit ac87ecd

Browse files
authored
Merge pull request #758 from pcambra/fix-370
Show real diffs for a staged addition instead of the whole file (issue #370)
2 parents cd87cb3 + 69b36ac commit ac87ecd

3 files changed

Lines changed: 67 additions & 10 deletions

File tree

‎Classes/git/PBGitIndex.m‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -737,13 +737,9 @@ - (NSString *)readDiffForFile:(PBChangedFile *)file staged:(BOOL)staged contextL
737737
{
738738
NSString *parameter = [NSString stringWithFormat:@"-U%lu", context];
739739
if (staged) {
740-
NSArray *arguments = nil;
741-
if (file.status == NEW) {
742-
NSString *indexPath = [@":0:" stringByAppendingString:file.path];
743-
arguments = @[ @"show", indexPath ];
744-
} else {
745-
arguments = @[ @"diff-index", parameter, @"--cached", self.parentTree, @"--", file.path ];
746-
}
740+
// A staged addition is diffed against the parent tree (the empty tree in
741+
// an unborn repository), not dumped from the index blob
742+
NSArray *arguments = @[ @"diff-index", parameter, @"--cached", self.parentTree, @"--", file.path ];
747743

748744
NSError *error = nil;
749745
NSString *output = [self.repository outputOfTaskWithArguments:arguments error:&error];
@@ -754,7 +750,10 @@ - (NSString *)readDiffForFile:(PBChangedFile *)file staged:(BOOL)staged contextL
754750
}
755751

756752
// unstaged
757-
if (file.status == NEW) {
753+
// NEW only means absent from the parent tree; once staged there is an index
754+
// entry to diff against, so only an untracked file is shown whole
755+
BOOL untracked = file.status == NEW && !file.hasStagedChanges;
756+
if (untracked) {
758757
NSStringEncoding encoding;
759758
NSError *error = nil;
760759
NSURL *fileURL = [self.repository.workingDirectoryURL URLByAppendingPathComponent:file.path];

‎GitXTests/PBGitIndexDiffCacheTests.m‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,41 @@ - (void)testAmendingIsAskedForAgain
184184
XCTAssertEqual(self.repository.askedFor.count, 2u);
185185
}
186186

187+
#pragma mark A staged addition is diffed, only an untracked file is read whole
188+
189+
- (PBChangedFile *)newFileStaged:(BOOL)staged
190+
{
191+
PBChangedFile *file = [[PBChangedFile alloc] initWithPath:@"a.txt"];
192+
file.status = NEW;
193+
file.hasStagedChanges = staged;
194+
file.hasUnstagedChanges = YES;
195+
return file;
196+
}
197+
198+
- (void)testUntrackedFileIsReadFromDiskWithoutAskingGit
199+
{
200+
NSString *diff = [self.gitIndex diffForFile:[self newFileStaged:NO] staged:NO contextLines:3];
201+
202+
XCTAssertEqual(self.repository.askedFor.count, 0u, @"git has no index entry to diff an untracked file against");
203+
XCTAssertEqualObjects(diff, @"one\n");
204+
}
205+
206+
- (void)testPartiallyStagedAdditionAsksForTheWorkingTreeDiff
207+
{
208+
[self.gitIndex diffForFile:[self newFileStaged:YES] staged:NO contextLines:3];
209+
210+
NSArray *expected = @[ @"diff-files", @"-U3", @"--", @"a.txt" ];
211+
XCTAssertEqualObjects(self.repository.askedFor.lastObject, expected,
212+
@"the edits since staging are a diff against the index, not the whole file again");
213+
}
214+
215+
- (void)testStagedAdditionIsDiffedAgainstTheParentTree
216+
{
217+
[self.gitIndex diffForFile:[self newFileStaged:YES] staged:YES contextLines:3];
218+
219+
NSArray *expected = @[ @"diff-index", @"-U3", @"--cached", @"HEAD", @"--", @"a.txt" ];
220+
XCTAssertEqualObjects(self.repository.askedFor.lastObject, expected,
221+
@"shown as added lines like any staged change, not as a bare blob");
222+
}
223+
187224
@end

‎Resources/html/views/commit/commit.js‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ var showFileChanges = function(file, cached) {
6161
Controller.refresh();
6262
}
6363

64-
if (file.status == 0) // New file?
64+
// Only an untracked file is shown whole; a staged addition has a real diff on both sides
65+
if (file.status == 0 && !file.hasStagedChanges) // Untracked file?
6566
return showNewFile(file);
6667

6768
setTitle((cached ? "Staged" : "Unstaged") + " changes for " + file.path);
@@ -378,12 +379,32 @@ var stageLines = function(reverse) {
378379
patch += l+"\n";
379380
}
380381
}
381-
patch = diffHeader + '\n' + "@@ -" + start_old.toString() + "," + count[0].toString() +
382+
var header = diffHeader;
383+
if (start_old == 0 && count[0] > 0) {
384+
// Old side empty (addition, empty file gaining lines, insert at top with
385+
// context 0): git won't partially reverse a creation header, so unstage the
386+
// selection as a modification of the index entry. A full selection keeps
387+
// the header and removes the entry, like the hunk button
388+
header = modificationHeader(diffHeader);
389+
start_old = 1;
390+
}
391+
patch = header + '\n' + "@@ -" + start_old.toString() + "," + count[0].toString() +
382392
" +" + start_new.toString() + "," + count[1].toString() + " @@\n"+patch;
383393

384394
addHunkText(patch,reverse);
385395
}
386396

397+
/* Plain modification header for the path of any diff header. Git writes both
398+
* names on the "diff --git A B" line the same length, so splitting at the
399+
* middle is safe for paths with spaces or quotes */
400+
var modificationHeader = function(header)
401+
{
402+
var names = header.split("\n")[0].replace(/^diff --git /, "");
403+
var half = (names.length - 1) / 2;
404+
var a = names.substr(0, half), b = names.substr(half + 1);
405+
return "diff --git " + a + " " + b + "\n--- " + a + "\n+++ " + b;
406+
}
407+
387408
/* Compute the selection before actually making it. Return as object
388409
* with 2-element array "bounds", and "good", which indicates if the
389410
* selection contains add/del lines. */

0 commit comments

Comments
 (0)