Skip to content

Commit

Permalink
fix for #3
Browse files Browse the repository at this point in the history
  • Loading branch information
SugoiDev committed Jun 29, 2018
1 parent c6c8425 commit 03793b6
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 78 deletions.
5 changes: 4 additions & 1 deletion LocalHistory/CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
02/14/2018 v.2.0.2
06/28/2018 v2.1.1 (beta)
* Fixed history not being saved when the file is not in a subdir relative to the sln file's dir. More info on this issue: https://github.com/LostAlloy/LocalHistory-for-Visual-Studio/issues/3

02/14/2018 v2.0.2
* Fixed internal diff not opening.
* Added option to force the internal diff tool to use a single window.

Expand Down
144 changes: 102 additions & 42 deletions LocalHistory/DocumentRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace LOSTALLOY.LocalHistory {
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using JetBrains.Annotations;


Expand Down Expand Up @@ -94,24 +95,16 @@ public DocumentNode CreateRevisionNode(string filePath, DateTime dateTime) {
LocalHistoryPackage.Log(
$"CreateRevisionNode(filePath:\"{filePath}\", dateTime:{dateTime}), SolutionDirectory:\"{SolutionDirectory}\", RepositoryDirectory:\"{RepositoryDirectory}\"");

// ReSharper disable once MergeSequentialChecksWhenPossible
if (string.IsNullOrEmpty(filePath) || !filePath.IsSubPathOf(SolutionDirectory)) {
LocalHistoryPackage.Log("file \"{filePath}\" is not a sub-file of the sln dir. Will not create revision.");
if (string.IsNullOrEmpty(filePath)) {
LocalHistoryPackage.Log("Empty path. Will not create revision.");
return null;
}

var repositoryPath = Utils.GetRepositoryPathForFile(filePath, SolutionDirectory);

var originalFilePath = filePath;
var fileName = Path.GetFileName(filePath);
var repositoryPath =
Path.GetDirectoryName(filePath)
?.Replace(
SolutionDirectory,
RepositoryDirectory,
StringComparison.InvariantCultureIgnoreCase) ?? RepositoryDirectory;

LocalHistoryPackage.Log($"Path.GetDirectoryName(filePath):\"{Path.GetDirectoryName(filePath)}\"");
LocalHistoryPackage.Log($"fileName:\"{fileName}\", repositoryPath:\"{repositoryPath}\"");

LocalHistoryPackage.Log($"filePath:\"{filePath}\", repositoryPath:\"{repositoryPath}\"");
return new DocumentNode(repositoryPath, originalFilePath, fileName, dateTime);
}

Expand All @@ -129,22 +122,76 @@ public DocumentNode CreateDocumentNodeForFilePath([CanBeNull] string versionedFu

versionedFullFilePath = Utils.NormalizePath(versionedFullFilePath);
string[] parts = Path.GetFileName(versionedFullFilePath).Split('$');
if (parts.Length <= 1) {
LocalHistoryPackage.Log($"Will not create {nameof(DocumentNode)} because filename \"{versionedFullFilePath}\" is in the wrong format", true);
return null;
}

var dateFromFileName = parts[0];
var fileName = parts[1];
var label = parts.Length == 3 ? parts[2] : null;


string originalFullFilePath = null;
var repositoryPath = Path.GetDirectoryName(versionedFullFilePath) ?? RepositoryDirectory;
var originalFullFilePath =
Path.GetDirectoryName(versionedFullFilePath)
?.Replace(
RepositoryDirectory,
SolutionDirectory,
StringComparison.InvariantCultureIgnoreCase) ?? SolutionDirectory;
originalFullFilePath = Path.Combine(originalFullFilePath, fileName);
var versionedFileDir = Utils.NormalizePath(Path.GetDirectoryName(versionedFullFilePath));
var shouldTryOldFormat = false;
if (!string.IsNullOrEmpty(versionedFileDir)) {
originalFullFilePath = versionedFileDir.Replace(Utils.GetRootRepositoryPath(SolutionDirectory), string.Empty);
string[] splitOriginalFullFilePath = originalFullFilePath.Split(Path.DirectorySeparatorChar);
var driveLetter = $"{splitOriginalFullFilePath[1]}{Path.VolumeSeparatorChar}{Path.DirectorySeparatorChar}";
if (!Directory.Exists(driveLetter)) {
LocalHistoryPackage.LogTrace($"Could not get versionedFileDir for \"{versionedFullFilePath}\". \"{driveLetter}\" is not a valid drive leter. Will try old format");

shouldTryOldFormat = true;
} else {
//reconstruct full path, without drive letter
originalFullFilePath = string.Join(
Path.DirectorySeparatorChar.ToString(),
splitOriginalFullFilePath,
2,
splitOriginalFullFilePath.Length - 2);

//reconstruct the drive leter
originalFullFilePath = Path.Combine(driveLetter, originalFullFilePath);
originalFullFilePath = Path.Combine(originalFullFilePath, fileName);
originalFullFilePath = Utils.NormalizePath(originalFullFilePath);

if (!File.Exists(originalFullFilePath)) {
LocalHistoryPackage.LogTrace($"Could not get versionedFileDir for \"{versionedFullFilePath}\". \"{originalFullFilePath}\" does not exist. Will try old format");
shouldTryOldFormat = true;
}
}
} else {
LocalHistoryPackage.Log($"Could not get versionedFileDir for \"{versionedFullFilePath}\". Will not create {nameof(DocumentNode)}.", true);
return null;
}


if (shouldTryOldFormat && !File.Exists(originalFullFilePath)) {
LocalHistoryPackage.LogTrace($"Trying to get original file path for \"{versionedFullFilePath}\". using old format.");

//try old format (using non-absolute paths)
originalFullFilePath = versionedFileDir.Replace(Utils.GetRootRepositoryPath(SolutionDirectory), SolutionDirectory);
originalFullFilePath = Path.Combine(originalFullFilePath, fileName);
originalFullFilePath = Utils.NormalizePath(originalFullFilePath);

if (File.Exists(originalFullFilePath)) {
LocalHistoryPackage.LogTrace(
$"Got original file path for \"{versionedFullFilePath}\" in \"{originalFullFilePath}\" using old format!");
}
}

if (!File.Exists(originalFullFilePath)) {
LocalHistoryPackage.Log(
$"Failed to retrieve original path for versioned file \"{versionedFullFilePath}\". Will not create {nameof(DocumentNode)}. File \"{originalFullFilePath}\" does not exist.",
true);

return null;
}

LocalHistoryPackage.LogTrace(
$"Creating {nameof(DocumentNode)} for \"{fileName}\" " +
$"(repositoryPath:\"{repositoryPath}\", originalFullFilePath:\"{originalFullFilePath}\")"
$"Creating {nameof(DocumentNode)} for \"{fileName}\" "
+ $"(versionedFullFilePath:\"{versionedFullFilePath}\", originalFullFilePath:\"{originalFullFilePath}\")"
);

return new DocumentNode(repositoryPath, originalFullFilePath, fileName, dateFromFileName, label);
Expand All @@ -171,36 +218,44 @@ public DocumentNode CreateDocumentNodeForFilePath([CanBeNull] string versionedFu
/// Returns all DocumentNode objects in the repository for the given project item.
/// </summary>
public IEnumerable<DocumentNode> GetRevisions([CanBeNull] string filePath) {
LocalHistoryPackage.Log($"Trying to get revisions for \"{filePath}\"");
var revisions = new List<DocumentNode>();

// ReSharper disable once MergeSequentialChecksWhenPossible
if (string.IsNullOrEmpty(filePath) || !filePath.IsSubPathOf(SolutionDirectory)) {
if (string.IsNullOrEmpty(filePath)) {
LocalHistoryPackage.Log(
$"filePath \"{filePath}\" is not a" +
$" sub-path of the solution directory \"{SolutionDirectory}\". Returning empty list.");
$"Empty {nameof(filePath)}. Returning empty list.");

return revisions;
}

var revisionsPath = Path.GetDirectoryName(filePath);
revisionsPath =
revisionsPath?.Replace(
SolutionDirectory,
RepositoryDirectory,
StringComparison.InvariantCultureIgnoreCase) ?? RepositoryDirectory;
LocalHistoryPackage.Log($"Trying to get revisions for \"{filePath}\"");

var fileName = Path.GetFileName(filePath);
var revisionsPath = Utils.GetRepositoryPathForFile(filePath, SolutionDirectory);
var fileBasePath = Path.GetDirectoryName(filePath);
var oldFormatRevisionsPath = fileBasePath?.Replace(SolutionDirectory, RepositoryDirectory, StringComparison.InvariantCultureIgnoreCase);

if (!Directory.Exists(revisionsPath)) {
LocalHistoryPackage.Log(
"revisionsPath does not exist." +
$" Returning empty list. \"{revisionsPath}\"");
if (!Directory.Exists(oldFormatRevisionsPath) && !Directory.Exists(revisionsPath)) {
LocalHistoryPackage.LogTrace($"Neither revisionsPath \"{revisionsPath}\" nor oldFormatRevisionsPath \"{oldFormatRevisionsPath}\" exist." + " Returning empty list.");
return revisions;
}

LocalHistoryPackage.Log($"Searching for revisions for \"{fileName}\" in \"{revisionsPath}\"");
foreach (var fullFilePath in Directory.GetFiles(revisionsPath)) {
string[] revisionFiles = Directory.GetFiles(revisionsPath);
if (Directory.Exists(oldFormatRevisionsPath)) {
revisionFiles = revisionFiles.Union(Directory.GetFiles(oldFormatRevisionsPath)).ToArray();
LocalHistoryPackage.Log(
$"Searching for revisions for \"{fileName}\" in \"{revisionsPath}\" and \"{oldFormatRevisionsPath}\" (using old format)");
} else {
LocalHistoryPackage.Log(
$"Searching for revisions for \"{fileName}\" in \"{revisionsPath}\"");
}

foreach (var fullFilePath in revisionFiles) {
var normalizedFullFilePath = Utils.NormalizePath(fullFilePath);
string[] splitFileName = normalizedFullFilePath.Split('$');
if (splitFileName.Length <= 1) {
LocalHistoryPackage.LogTrace($"Ignoring revision \"{normalizedFullFilePath}\" because it is not in the correct format.");
continue;
}

normalizedFullFilePath = $"{splitFileName[0]}{splitFileName[1]}";//remove the label part

//when running the OnBeforeSave, VS can return the filename as lower
Expand All @@ -214,7 +269,12 @@ public IEnumerable<DocumentNode> GetRevisions([CanBeNull] string filePath) {
}

LocalHistoryPackage.LogTrace($"Found revision \"{fullFilePath}\"");
revisions.Add(CreateDocumentNodeForFilePath(fullFilePath));
var node = CreateDocumentNodeForFilePath(fullFilePath);
if (node != null) {
revisions.Add(node);
} else {
LocalHistoryPackage.LogTrace("Not adding revision because node is null.");
}
}

revisions.Reverse();
Expand Down
50 changes: 21 additions & 29 deletions LocalHistory/LocalHistoryPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,12 @@ public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) {
Log("Entering OnAfterOpenSolution()");

// The solution name can be empty if the user opens a file without opening a solution
if (dte?.Solution != null && File.Exists(dte?.Solution.FullName)) {
var maybeSolution = dte?.Solution;
if (maybeSolution != null && File.Exists(maybeSolution.FullName)) {
RegisterDocumentListener();

RegisterSelectionListener();
} else {
Log(
$"Did not register document listener. dte.Solution==null?{dte?.Solution == null}, " +
$"dte.Solution.FullName:\"{dte?.Solution?.FullName ?? ""}\"");
Log($"Did not register document listener. dte.Solution==null? {(maybeSolution == null ? "YES" : $"NO (dte.Solution.FullName: {maybeSolution?.FullName})")}, dte.Solution.FullName:\"{maybeSolution?.FullName ?? "EMPTY"}\"");
}

return VSConstants.S_OK;
Expand All @@ -194,17 +192,17 @@ public int OnAfterCloseSolution(object pUnkReserved) {
public void RegisterDocumentListener() {
var documentTable = (IVsRunningDocumentTable) GetGlobalService(typeof(SVsRunningDocumentTable));

Log($"dte.Solution.FullName: \"{dte?.Solution.FullName}\"");
var maybeSolution = dte?.Solution;
Log($"dte.Solution.FullName: \"{maybeSolution?.FullName}\"");

// Create a new document repository for the solution
var solutionDirectory = Path.GetDirectoryName(dte?.Solution.FullName);
Debug.Assert(solutionDirectory != null, "solutionDirectory != null");
var repositoryDirectory = Path.Combine(solutionDirectory, ".localhistory");
var solutionDirectory = Path.GetDirectoryName(maybeSolution?.FullName);
Debug.Assert(solutionDirectory != null, $"{nameof(solutionDirectory)} != null");
var repositoryDirectory = Utils.GetRootRepositoryPath(solutionDirectory);
Log(
$"Creating {nameof(DocumentRepository)} " +
$"with solutionDirectory: \"{solutionDirectory}\" " +
$"and repositoryDirectory: \"{repositoryDirectory}\""
);
$"Creating {nameof(DocumentRepository)} "
+ $"with {nameof(solutionDirectory)}: \"{solutionDirectory}\" "
+ $"and {nameof(repositoryDirectory)}: \"{repositoryDirectory}\"");
documentRepository = new DocumentRepository(solutionDirectory, repositoryDirectory);

// Create and register a document listener that will handle save events
Expand Down Expand Up @@ -321,29 +319,23 @@ public void UpdateToolWindow([CanBeNull] string filePath = "", bool fileCountOnl
// Remove all revisions from the revision list that belong to the previous document
control.DocumentItems.Clear();

foreach (var revision in documentRepository.GetRevisions(filePath)) {
var revisions = documentRepository.GetRevisions(filePath);
foreach (var revision in revisions) {
LogTrace($"Adding revision \"{revision.VersionFileFullFilePath}\"");
control.DocumentItems.Add(revision);
}

// Add the project item and its history to the revision list
var repositoryPath =
Path.GetDirectoryName(filePath)
?.Replace(
// ReSharper disable once PossibleNullReferenceException
documentRepository.SolutionDirectory,
documentRepository.RepositoryDirectory,
StringComparison.InvariantCultureIgnoreCase
);

// ReSharper disable once PossibleNullReferenceException
repositoryPath = repositoryPath ?? documentRepository.RepositoryDirectory;
var repositoryPath = Utils.GetRepositoryPathForFile(
filePath,
documentRepository.SolutionDirectory);

Log(
"Setting LatestDocument to: " +
$"repositoryPath:\"{repositoryPath}\", " +
$"filePath:\"{filePath}\", " +
$"filename\"{Path.GetFileName(filePath)}\"");
"Setting LatestDocument to: "
+ $"repositoryPath:\"{repositoryPath}\", "
+ $"filePath:\"{filePath}\", "
+ $"filename\"{Path.GetFileName(filePath)}\"");

control.LatestDocument = new DocumentNode(
repositoryPath,
filePath,
Expand Down
32 changes: 29 additions & 3 deletions LocalHistory/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,46 @@
// limitations under the License.

namespace LOSTALLOY.LocalHistory {
using System;
using System.IO;
using JetBrains.Annotations;


internal static class Utils {

#region Public Methods and Operators


[NotNull]
public static string NormalizePath(string path) {
return Path.GetFullPath(path.Replace('/', '\\'));
}

#endregion

public static string GetRepositoryPathForFile(string filePath, string solutionDirectory) {
var fileParentPath = Path.GetDirectoryName(filePath);
string repositoryPath = null;
if (!string.IsNullOrEmpty(fileParentPath)) {
repositoryPath =
fileParentPath
.Replace(
Path.VolumeSeparatorChar,
Path.DirectorySeparatorChar);
}

var rootRepositoryPath = GetRootRepositoryPath(solutionDirectory);
if (repositoryPath == null) {
repositoryPath = rootRepositoryPath;
} else {
repositoryPath = Path.Combine(rootRepositoryPath, repositoryPath);
}

LocalHistoryPackage.Log($"{nameof(repositoryPath)} for \"{filePath}\" is \"{repositoryPath}\"");
return repositoryPath;
}


public static string GetRootRepositoryPath(string solutionDirectory) {
return Path.Combine(solutionDirectory, ".localhistory");
}

}
}
5 changes: 2 additions & 3 deletions LocalHistory/source.extension.vsixmanifest
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Id="48473bd6-841e-4368-8c7a-a5ea9fad7081" Version="2.0.2" Language="en-US" Publisher="LOSTALLOY" />
<Identity Id="48473bd6-841e-4368-8c7a-a5ea9fad7081" Version="2.1.1" Language="en-US" Publisher="LOSTALLOY" />
<DisplayName>Local History for Visual Studio</DisplayName>
<Description xml:space="preserve">Local History for Visual Studio automatically creates a copy everytime you save a file.
</Description>
<Description xml:space="preserve">Local History for Visual Studio automatically creates a copy everytime you save a file.</Description>
<MoreInfo>https://github.com/LostAlloy/LocalHistory-for-Visual-Studio</MoreInfo>
<License>License.txt</License>
<ReleaseNotes>CHANGELOG.MD</ReleaseNotes>
Expand Down

0 comments on commit 03793b6

Please sign in to comment.