public LocalHistoryControl() { InitializeComponent(); DocumentItems = new ObservableCollection <DocumentNode>(); // PropertyChanged event propagation DocumentItems.CollectionChanged += (o, e) => { LocalHistoryPackage.LogTrace("DocumentItems collection changed."); OnPropertyChanged(nameof(DocumentItems)); OnPropertyChanged(nameof(ShowOnlyLabeled)); OnPropertyChanged(nameof(DocumentItemsViewSource)); OnPropertyChanged(nameof(VisibleItemsCount)); }; DocumentItemsViewSource = new CollectionViewSource { Source = DocumentItems }; DocumentItemsViewSource.Filter -= LabeledOnlyFilter; DocumentItemsViewSource.Filter += LabeledOnlyFilter; // Set the DataContext for binding properties MainPanel.DataContext = this; RefreshXamlItemsVisibility(); }
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); }
/// <summary> /// When this event is triggered on a project item, a copy of the file is saved to the /// <see cref="documentRepository" />. /// </summary> public override int OnBeforeSave(uint docCookie) { LocalHistoryPackage.Log($"Entering {nameof(OnBeforeSave)}() on {nameof(LocalHistoryDocumentListener)}"); uint pgrfRDTFlags; uint pdwReadLocks; uint pdwEditLocks; string pbstrMkDocument; IVsHierarchy ppHier; uint pitemid; IntPtr ppunkDocData; documentTable.GetDocumentInfo( docCookie, out pgrfRDTFlags, out pdwReadLocks, out pdwEditLocks, out pbstrMkDocument, out ppHier, out pitemid, out ppunkDocData); var filePath = Utils.NormalizePath(pbstrMkDocument); if (LocalHistoryPackage.Instance.OptionsPage.CreateRevisionOnlyIfDirty) { if (!_dirtyDocCookie.Contains(docCookie)) { LocalHistoryPackage.Log($"File \"{filePath}\" is not dirty. Will not create version."); return(VSConstants.S_OK); } _dirtyDocCookie.Remove(docCookie); } LocalHistoryPackage.Log($"Creating version for file \"{filePath}\" (is dirty)."); var revNode = documentRepository.CreateRevision(filePath); var content = LocalHistoryPackage.Instance.ToolWindow?.Content as LocalHistoryControl; //only insert if this revision is different from all the others //otherwise we would be inserting duplicates in the list //remember that DocumentNode has its own GetHashCode ObservableCollection <DocumentNode> items = content?.DocumentItems; if (revNode != null && items?.Contains(revNode) == false) { LocalHistoryPackage.Log($"Adding file \"{filePath}\" to list."); items.Insert(0, revNode); LocalHistoryPackage.Instance.UpdateToolWindow("", true); return(VSConstants.S_OK); } LocalHistoryPackage.Log($"File \"{filePath}\" is already in list. Doing nothing."); return(VSConstants.S_OK); }
/// <summary> /// Initialization of the package; this method is called right after the package is sited. /// </summary> protected override void Initialize() { base.Initialize(); //Log needs the OptionsPage OptionsPage = (OptionsPage)GetDialogPage(typeof(OptionsPage)); Instance = this; //Log needs the dte object dte = GetGlobalService(typeof(DTE)) as DTE; if (dte == null) { //this log will only log with Debug.WriteLine, since we failed to get the DTE for some reason Log("Could not get DTE object. Will not initialize."); return; } //This is the earliest we can safely log (that will go the output window) //previous logs will be Debug.WriteLine only Log($"Entering {nameof(Initialize)}"); var solution = (IVsSolution)GetService(typeof(SVsSolution)); var adviseResult = solution.AdviseSolutionEvents(this, out solutionCookie); if (adviseResult != VSConstants.S_OK) { Log($"Failed to AdviseSolutionEvents. Will not initialize. Error code is: {adviseResult}"); return; } // Add our command handlers for menu (commands must exist in the .vsct file) Log("Adding tool menu handler."); var mcs = (OleMenuCommandService)GetService(typeof(IMenuCommandService)); if (null != mcs) { // Create the command for the menu item. var menuCommandID = new CommandID(GuidList.guidLocalHistoryCmdSet, (int)PkgCmdIDList.cmdidLocalHistoryMenuItem); var menuItem = new MenuCommand(ProjectItemContextMenuHandler, menuCommandID); mcs.AddCommand(menuItem); Log("Added context menu command."); // Create the command for the tool window var toolwndCommandID = new CommandID(GuidList.guidLocalHistoryCmdSet, (int)PkgCmdIDList.cmdidLocalHistoryWindow); var menuToolWin = new MenuCommand(ToolWindowMenuItemHandler, toolwndCommandID); mcs.AddCommand(menuToolWin); Log("Added menu command."); } else { Log("Could not get IMenuCommandService. Tool menu will not work."); } ShowToolWindow(false); }
public DocumentNode CreateRevision([CanBeNull] string filePath) { if (string.IsNullOrEmpty(filePath)) { return(null); } else { filePath = Utils.NormalizePath(filePath); DocumentNode newNode = null; try { var dateTime = DateTime.Now; newNode = this.CreateRevisionNode(filePath, dateTime); if (newNode is null) { return(null); } else { // Create the parent directory if it doesn't exist if (!Directory.Exists(newNode.RepositoryPath)) { Directory.CreateDirectory(newNode.RepositoryPath); } // Copy the file to the repository File.Copy(filePath, newNode.VersionFileFullFilePath, true); if (this.Control == null) { this.Control = (LocalHistoryControl)LocalHistoryPackage.Instance.ToolWindow?.Content; } if (this.Control?.LatestDocument?.OriginalPath?.Equals(newNode.OriginalPath) == true) { this.Control.DocumentItems.Insert(0, newNode); } } } catch (Exception ex) { LocalHistoryPackage.Log(ex.Message); } return(newNode); } }
public DocumentNode CreateRevision(string filePath) { LocalHistoryPackage.Log("CreateRevision(" + filePath + ")"); if (string.IsNullOrEmpty(filePath)) { return(null); } filePath = Utils.NormalizePath(filePath); DocumentNode newNode = null; try { var dateTime = DateTime.Now; newNode = CreateRevisionNode(filePath, dateTime); if (newNode == null) { return(null); } // Create the parent directory if it doesn't exist if (!Directory.Exists(newNode.RepositoryPath)) { LocalHistoryPackage.Log($"Creating (because it doesn't exist) \"{newNode.RepositoryPath}\""); Directory.CreateDirectory(newNode.RepositoryPath); } // Copy the file to the repository LocalHistoryPackage.Log($"Copying \"{filePath}\" to \"{newNode.VersionFileFullFilePath}\""); File.Copy(filePath, newNode.VersionFileFullFilePath, true); if (Control == null) { Control = (LocalHistoryControl)LocalHistoryPackage.Instance.ToolWindow?.Content; } if (Control?.LatestDocument.OriginalPath.Equals(newNode.OriginalPath) == true) { Control.DocumentItems.Insert(0, newNode); } } catch (Exception ex) { LocalHistoryPackage.Log(ex.Message); } return(newNode); }
public DocumentNode CreateRevisionNode(string filePath, DateTime dateTime) { LocalHistoryPackage.Log( $"CreateRevisionNode(filePath:\"{filePath}\", dateTime:{dateTime}), SolutionDirectory:\"{SolutionDirectory}\", RepositoryDirectory:\"{RepositoryDirectory}\""); 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); LocalHistoryPackage.Log($"filePath:\"{filePath}\", repositoryPath:\"{repositoryPath}\""); return(new DocumentNode(repositoryPath, originalFilePath, fileName, dateTime)); }
/// <summary> /// Returns all DocumentNode objects in the repository for the given project item. /// </summary> public IEnumerable <DocumentNode> GetRevisions([CanBeNull] string filePath) { var revisions = new List <DocumentNode>(); if (string.IsNullOrEmpty(filePath)) { LocalHistoryPackage.Log( $"Empty {nameof(filePath)}. Returning empty list."); return(revisions); } 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(oldFormatRevisionsPath) && !Directory.Exists(revisionsPath)) { LocalHistoryPackage.LogTrace($"Neither revisionsPath \"{revisionsPath}\" nor oldFormatRevisionsPath \"{oldFormatRevisionsPath}\" exist." + " Returning empty list."); return(revisions); } 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 //i.e., it can ignore the file name's case. //Thus, the only way to retrieve everything here is to ignore case //Remember that Windows is case insensitive by default, so we can't really //have two files with names that differ only in case in the same dir. if (!normalizedFullFilePath.EndsWith(fileName, StringComparison.OrdinalIgnoreCase)) { //LocalHistoryPackage.Log($"Not a revision:\"{normalizedFullFilePath}\""); continue; } LocalHistoryPackage.LogTrace($"Found revision \"{fullFilePath}\""); var node = CreateDocumentNodeForFilePath(fullFilePath); if (node != null) { revisions.Add(node); } else { LocalHistoryPackage.LogTrace("Not adding revision because node is null."); } } revisions.Reverse(); return(revisions); }
public DocumentNode CreateDocumentNodeForFilePath([CanBeNull] string versionedFullFilePath) { if (versionedFullFilePath == null) { return(null); } LocalHistoryPackage.LogTrace($"Trying to create {nameof(DocumentNode)} for \"{versionedFullFilePath}\""); 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 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}\" " + $"(versionedFullFilePath:\"{versionedFullFilePath}\", originalFullFilePath:\"{originalFullFilePath}\")" ); return(new DocumentNode(repositoryPath, originalFullFilePath, fileName, dateFromFileName, label)); }
/// <summary> /// Initialization of the package; this method is called right after the package is sited. /// </summary> protected override async System.Threading.Tasks.Task InitializeAsync( CancellationToken cancellationToken, IProgress <ServiceProgressData> progress) { await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); //Log needs the OptionsPage OptionsPage = (OptionsPage)GetDialogPage(typeof(OptionsPage)); Instance = this; //Log needs the dte object dte = GetGlobalService(typeof(DTE)) as DTE; if (dte == null) { //this log will only log with Debug.WriteLine, since we failed to get the DTE for some reason Log("Could not get DTE object. Will not initialize."); return; } //This is the earliest we can safely log (that will go the output window) //previous logs will be Debug.WriteLine only Log($"Entering {nameof(InitializeAsync)}"); var isSlnLoaded = await IsSolutionLoadedAsync(); if (isSlnLoaded) { //already loaded, so we need to handle it asap HandleSolutionOpen(); } //it's recommended to keep refs to Events objects to avoid the GC eating them up //https://stackoverflow.com/a/32600629/2573470 solutionEvents = dte.Events.SolutionEvents; if (solutionEvents == null) { Log("Could not get te.Events.SolutionEvents. Will not initialize."); return; } solutionEvents.Opened += HandleSolutionOpen; solutionEvents.BeforeClosing += HandleSolutionClose; // Add our command handlers for menu (commands must exist in the .vsct file) Log("Adding tool menu handler."); var mcs = await GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; if (mcs != null) { // Create the command for the menu item. var menuCommandID = new CommandID(GuidList.guidLocalHistoryCmdSet, (int)PkgCmdIDList.cmdidLocalHistoryMenuItem); var menuItem = new MenuCommand(ProjectItemContextMenuHandler, menuCommandID); mcs.AddCommand(menuItem); Log("Added context menu command."); // Create the command for the tool window var toolwndCommandID = new CommandID(GuidList.guidLocalHistoryCmdSet, (int)PkgCmdIDList.cmdidLocalHistoryWindow); var menuToolWin = new MenuCommand(ToolWindowMenuItemHandler, toolwndCommandID); mcs.AddCommand(menuToolWin); Log("Added menu command."); } else { Log("Could not get IMenuCommandService. Tool menu will not work."); } ShowToolWindow(false, cancellationToken); dte.Events.DocumentEvents.DocumentOpened += HandleDocumentOpen; }