From 7bb48ae00b07db2e79fe46f4b38bf71141f8c95f Mon Sep 17 00:00:00 2001
From: Eric Amodio <eamodio@gmail.com>
Date: Wed, 11 Oct 2017 18:11:22 -0400
Subject: [PATCH] Fixes stashed file diffs

---
 CHANGELOG.md                        |  2 ++
 src/commands/diffLineWithWorking.ts |  8 +++++++-
 src/git/git.ts                      |  9 +++++----
 src/git/models/commit.ts            | 22 ++++++++++++++++------
 src/git/models/logCommit.ts         | 26 ++++++++++++++++++++++++++
 src/git/parsers/stashParser.ts      |  2 +-
 src/views/stashFileNode.ts          | 10 +++++-----
 src/views/stashNode.ts              |  2 +-
 8 files changed, 63 insertions(+), 18 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5eaf02c..67489f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased]
+### Fixed
+- Fixes issue where diffs for stashed files were often wrong (missing)
 
 ## [5.6.0] - 2017-10-11
 ### Added
diff --git a/src/commands/diffLineWithWorking.ts b/src/commands/diffLineWithWorking.ts
index 1df8f89..6c65b3f 100644
--- a/src/commands/diffLineWithWorking.ts
+++ b/src/commands/diffLineWithWorking.ts
@@ -43,7 +43,13 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
                 args.commit = blame.commit;
                 // If the line is uncommitted, find the previous commit
                 if (args.commit.isUncommitted) {
-                    args.commit = new GitCommit(args.commit.type, args.commit.repoPath, args.commit.previousSha!, args.commit.previousFileName!, args.commit.author, args.commit.date, args.commit.message);
+                    args.commit = args.commit.with({
+                        sha: args.commit.previousSha!,
+                        fileName: args.commit.previousFileName!,
+                        originalFileName: null,
+                        previousSha: null,
+                        previousFileName: null
+                    });
                     args.line = blame.line.line + 1;
                 }
             }
diff --git a/src/git/git.ts b/src/git/git.ts
index e2b252b..6c4237b 100644
--- a/src/git/git.ts
+++ b/src/git/git.ts
@@ -94,8 +94,8 @@ function gitCommandDefaultErrorHandler(ex: Error, options: GitCommandOptions, ..
 
 export class Git {
 
-    static shaRegex = /^[0-9a-f]{40}( -)?$/;
-    static uncommittedRegex = /^[0]+$/;
+    static shaRegex = /^[0-9a-f]{40}(\^[0-9]*?)??( -)?$/;
+    static uncommittedRegex = /^[0]{40}(\^[0-9]*?)??$/;
 
     static gitInfo(): IGit {
         return git;
@@ -156,6 +156,9 @@ export class Git {
     }
 
     static shortenSha(sha: string) {
+        const index = sha.indexOf('^');
+        // This is lame, but assume there is only 1 character after the ^
+        if (index > 6) return `${sha.substring(0, 6)}${sha.substring(index)}`;
         return sha.substring(0, 8);
     }
 
@@ -385,8 +388,6 @@ export class Git {
 
     static async show(repoPath: string | undefined, fileName: string, branchOrSha: string, encoding?: string) {
         const [file, root] = Git.splitPath(fileName, repoPath);
-        branchOrSha = branchOrSha.replace('^', '');
-
         if (Git.isUncommitted(branchOrSha)) throw new Error(`sha=${branchOrSha} is uncommitted`);
 
         const opts = { cwd: root, encoding: encoding || defaultEncoding, overrideErrorHandling: true };
diff --git a/src/git/models/commit.ts b/src/git/models/commit.ts
index 51bccb6..043f883 100644
--- a/src/git/models/commit.ts
+++ b/src/git/models/commit.ts
@@ -98,11 +98,21 @@ export class GitCommit {
         return GitUri.getFormattedPath(this.fileName, separator);
     }
 
-    with(changes: { type?: GitCommitType, fileName?: string, sha?: string, originalFileName?: string, previousFileName?: string, previousSha?: string }) {
-        return new GitCommit(changes.type || this.type, this.repoPath,
-            changes.sha || this.sha, changes.fileName || this.fileName,
-            this.author, this.date, this.message,
-            changes.originalFileName || this.originalFileName,
-            changes.previousSha || this.previousSha, changes.previousFileName || this.previousFileName);
+    with(changes: { type?: GitCommitType, sha?: string, fileName?: string, originalFileName?: string | null, previousFileName?: string | null, previousSha?: string | null }): GitCommit {
+        return new GitCommit(changes.type || this.type,
+            this.repoPath,
+            changes.sha || this.sha,
+            changes.fileName || this.fileName,
+            this.author,
+            this.date,
+            this.message,
+            this.getChangedValue(changes.originalFileName, this.originalFileName),
+            this.getChangedValue(changes.previousSha, this.previousSha),
+            this.getChangedValue(changes.previousFileName, this.previousFileName));
+    }
+
+    protected getChangedValue<T>(change: T | null | undefined, original: T | undefined): T | undefined {
+        if (change === undefined) return original;
+        return change !== null ? change : undefined;
     }
 }
\ No newline at end of file
diff --git a/src/git/models/logCommit.ts b/src/git/models/logCommit.ts
index ca7adf7..4b29552 100644
--- a/src/git/models/logCommit.ts
+++ b/src/git/models/logCommit.ts
@@ -68,4 +68,30 @@ export class GitLogCommit extends GitCommit {
         const changed = this.fileStatuses.filter(_ => _.status !== 'A' && _.status !== '?' && _.status !== 'D').length;
         return `+${added} ~${changed} -${deleted}`;
     }
+
+    toFileCommit(status: IGitStatusFile): GitLogCommit {
+        return this.with({
+            type: GitCommitType.File,
+            fileName: status.fileName,
+            originalFileName: status.originalFileName,
+            previousFileName: status.originalFileName || status.fileName,
+            status: status.status,
+            fileStatuses: null
+        });
+    }
+
+    with(changes: { type?: GitCommitType, sha?: string, fileName?: string, originalFileName?: string | null, previousFileName?: string | null, previousSha?: string | null, status?: GitStatusFileStatus, fileStatuses?: IGitStatusFile[] | null }): GitLogCommit {
+        return new GitLogCommit(changes.type || this.type,
+            this.repoPath,
+            changes.sha || this.sha,
+            changes.fileName || this.fileName,
+            this.author,
+            this.date,
+            this.message,
+            changes.status || this.status,
+            this.getChangedValue(changes.fileStatuses, this.fileStatuses),
+            this.getChangedValue(changes.originalFileName, this.originalFileName),
+            this.getChangedValue(changes.previousSha, this.previousSha),
+            this.getChangedValue(changes.previousFileName, this.previousFileName));
+    }
 }
\ No newline at end of file
diff --git a/src/git/parsers/stashParser.ts b/src/git/parsers/stashParser.ts
index 41ae97f..27f01e2 100644
--- a/src/git/parsers/stashParser.ts
+++ b/src/git/parsers/stashParser.ts
@@ -24,7 +24,7 @@ export class GitStashParser {
 
             let commit = commits.get(entry.sha);
             if (commit === undefined) {
-                commit = new GitStashCommit(entry.stashName, repoPath, entry.sha, entry.fileNames, new Date(entry.date! as any * 1000), entry.summary, undefined, entry.fileStatuses) as GitStashCommit;
+                commit = new GitStashCommit(entry.stashName, repoPath, entry.sha, entry.fileNames, new Date(entry.date! as any * 1000), entry.summary, undefined, entry.fileStatuses, undefined, `${entry.sha}^1`) as GitStashCommit;
                 commits.set(entry.sha, commit);
             }
         }
diff --git a/src/views/stashFileNode.ts b/src/views/stashFileNode.ts
index ca8b05b..824eaf6 100644
--- a/src/views/stashFileNode.ts
+++ b/src/views/stashFileNode.ts
@@ -1,7 +1,7 @@
 'use strict';
 import { ExtensionContext } from 'vscode';
 import { ResourceType } from './explorerNode';
-import { GitService, GitStashCommit, IGitStatusFile } from '../gitService';
+import { GitLogCommit, GitService, IGitStatusFile } from '../gitService';
 import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
 
 export class StashFileNode extends CommitFileNode {
@@ -9,10 +9,10 @@ export class StashFileNode extends CommitFileNode {
     readonly resourceType: ResourceType = 'gitlens:stash-file';
 
     constructor(
-        readonly status: IGitStatusFile,
-        readonly commit: GitStashCommit,
-        readonly context: ExtensionContext,
-        readonly git: GitService
+        status: IGitStatusFile,
+        commit: GitLogCommit,
+        context: ExtensionContext,
+        git: GitService
     ) {
         super(status, commit, context, git, CommitFileNodeDisplayAs.File);
     }
diff --git a/src/views/stashNode.ts b/src/views/stashNode.ts
index 03e48a1..a93a51f 100644
--- a/src/views/stashNode.ts
+++ b/src/views/stashNode.ts
@@ -31,7 +31,7 @@ export class StashNode extends ExplorerNode {
             }
         }
 
-        const children = statuses.map(s => new StashFileNode(s, this.commit, this.context, this.git));
+        const children = statuses.map(s => new StashFileNode(s, this.commit.toFileCommit(s), this.context, this.git));
         children.sort((a, b) => a.label!.localeCompare(b.label!));
         return children;
     }