Beispiel #1
0
        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);
        }
Beispiel #4
0
        /// <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);
            }
        }
Beispiel #6
0
        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);
        }
Beispiel #7
0
        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));
        }
Beispiel #8
0
        /// <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);
        }
Beispiel #9
0
        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;
        }