├── .gitignore ├── LICENSE ├── README.md └── VisualStudio.GitStashExtension ├── Dependencies ├── Microsoft.TeamFoundation.Client.dll ├── Microsoft.TeamFoundation.Controls.dll └── Microsoft.TeamFoundation.Git.Provider.dll ├── VisualStudio.GitStashExtension.sln ├── VisualStudio.GitStashExtension.sln.DotSettings └── VisualStudio.GitStashExtension ├── Commands ├── StashStagedCommand.cs ├── StashStagedCommandPackage.cs ├── StashStagedCommandPackage.vsct └── VSPackage.resx ├── Constants.cs ├── Converters ├── NullToCollapsedConverter.cs ├── NullToVisibleConverter.cs └── TextToBoolConverter.cs ├── Extensions ├── EnumerableExtensions.cs ├── FrameworkElementExtension.cs ├── GitServiceExtension.cs └── TreeNodeExtension.cs ├── GitHelpers ├── GitCommandConstants.cs ├── GitCommandExecuter.cs ├── GitPathHelper.cs └── GitResultParser.cs ├── Helpers └── RemovedStashesContainer.cs ├── Logger └── Logger.cs ├── Models ├── ChangedFile.cs ├── FileAttributes.cs ├── FilesTreeViewItem.cs ├── GitCommandResult.cs ├── Stash.cs ├── StashNavigationContext.cs └── VsColorTheme.cs ├── NotifyPropertyChangeBase.cs ├── Properties ├── Annotations.cs └── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Resources ├── ResourcesDictionary.xaml ├── SearchIcon.png ├── SearchIcon_white.png └── TeamExplorerIcon.png ├── Services ├── FileIconsService.cs └── VisualStudioGitService.cs ├── TeamExplorerExtensions ├── GitStashListStashesSection.cs ├── StashInfoChangesSection.cs ├── StashInfoTeamExplorerPage.cs ├── StashListTeamExplorerNavigationItem.cs ├── StashListTeamExplorerPage.cs ├── StashStagedChangesSection.cs └── TeamExplorerBase.cs ├── VS.UI.Commands ├── DelegateCommand.cs └── ToggleStashStagedSectionVisibilityCommand.cs ├── VS.UI ├── CreateStashSection.xaml ├── CreateStashSection.xaml.cs ├── StashInfoChangesSectionUI.xaml ├── StashInfoChangesSectionUI.xaml.cs ├── StashInfoPage.xaml ├── StashInfoPage.xaml.cs ├── StashListTeamExplorerSectionUI.xaml ├── StashListTeamExplorerSectionUI.xaml.cs ├── StashStagedSection.xaml └── StashStagedSection.xaml.cs ├── VS.ViewModels ├── CreateStashSectionViewModel.cs ├── StashInfoChangesSectionViewModel.cs ├── StashInfoPageViewModel.cs ├── StashListItemViewModel.cs ├── StashListSectionViewModel.cs ├── StashStagedSectionViewModel.cs └── TreeViewItemWithIconViewModel.cs ├── VSHelpers ├── VisualStudioPathHelper.cs └── VisualStudioThemeHelper.cs ├── VisualStudio.GitStashExtension.csproj ├── app.config ├── packages.config └── source.extension.vsixmanifest /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | *.snk 290 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 VityAka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VisualStudio.GitStashExtension 2 | Team explorer extension for stashes 3 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/Dependencies/Microsoft.TeamFoundation.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voshchinskiyvitya/VisualStudio.GitStashExtension/ffcebdde46d9e4060396b8ab570a31a299b2b551/VisualStudio.GitStashExtension/Dependencies/Microsoft.TeamFoundation.Client.dll -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/Dependencies/Microsoft.TeamFoundation.Controls.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voshchinskiyvitya/VisualStudio.GitStashExtension/ffcebdde46d9e4060396b8ab570a31a299b2b551/VisualStudio.GitStashExtension/Dependencies/Microsoft.TeamFoundation.Controls.dll -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/Dependencies/Microsoft.TeamFoundation.Git.Provider.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voshchinskiyvitya/VisualStudio.GitStashExtension/ffcebdde46d9e4060396b8ab570a31a299b2b551/VisualStudio.GitStashExtension/Dependencies/Microsoft.TeamFoundation.Git.Provider.dll -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2003 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualStudio.GitStashExtension", "VisualStudio.GitStashExtension\VisualStudio.GitStashExtension.csproj", "{09EE8379-354D-4BEA-8E1F-9F0DC47EB455}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {09EE8379-354D-4BEA-8E1F-9F0DC47EB455}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {09EE8379-354D-4BEA-8E1F-9F0DC47EB455}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {09EE8379-354D-4BEA-8E1F-9F0DC47EB455}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {09EE8379-354D-4BEA-8E1F-9F0DC47EB455}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {A16FCBEF-5FB4-4FC5-A7F9-8585F5D0B149} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Commands/StashStagedCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.Windows.Input; 4 | using Microsoft.TeamFoundation.Controls; 5 | using Microsoft.VisualStudio.Shell; 6 | using VisualStudio.GitStashExtension.VS.UI.Commands; 7 | using Task = System.Threading.Tasks.Task; 8 | 9 | namespace VisualStudio.GitStashExtension.Commands 10 | { 11 | /// 12 | /// Command handler 13 | /// 14 | internal sealed class StashStagedCommand 15 | { 16 | /// 17 | /// Command ID. 18 | /// 19 | public const int CommandId = 0x0100; 20 | 21 | /// 22 | /// Command menu group (command set GUID). 23 | /// 24 | public static readonly Guid CommandSet = new Guid("8d0a9a23-f158-4aa4-9b6e-3fcc779eb26d"); 25 | 26 | /// 27 | /// VS Package that provides this command, not null. 28 | /// 29 | private readonly AsyncPackage package; 30 | 31 | /// 32 | /// Initializes a new instance of the class. 33 | /// Adds our command handlers for menu (commands must exist in the command table file) 34 | /// 35 | /// Owner package, not null. 36 | /// Command service to add command to, not null. 37 | private StashStagedCommand(AsyncPackage package, OleMenuCommandService commandService) 38 | { 39 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 40 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 41 | 42 | var menuCommandID = new CommandID(CommandSet, CommandId); 43 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 44 | commandService.AddCommand(menuItem); 45 | 46 | ToggleStashStagedSectionVisibilityCommand = new ToggleStashStagedSectionVisibilityCommand(TeamExplorer); 47 | } 48 | 49 | /// 50 | /// Gets the instance of the command. 51 | /// 52 | public static StashStagedCommand Instance 53 | { 54 | get; 55 | private set; 56 | } 57 | 58 | /// 59 | /// Gets the service provider from the owner package. 60 | /// 61 | private IServiceProvider ServiceProvider 62 | { 63 | get 64 | { 65 | return package; 66 | } 67 | } 68 | 69 | private ITeamExplorer TeamExplorer => ServiceProvider?.GetService(typeof(ITeamExplorer)) as ITeamExplorer; 70 | private ICommand ToggleStashStagedSectionVisibilityCommand { get; } 71 | 72 | /// 73 | /// Initializes the singleton instance of the command. 74 | /// 75 | /// Owner package, not null. 76 | public static async Task InitializeAsync(AsyncPackage package) 77 | { 78 | // Switch to the main thread - the call to AddCommand in StashStagedCommand's constructor requires 79 | // the UI thread. 80 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 81 | 82 | OleMenuCommandService commandService = await package.GetServiceAsync((typeof(IMenuCommandService))) as OleMenuCommandService; 83 | Instance = new StashStagedCommand(package, commandService); 84 | } 85 | 86 | /// 87 | /// This function is the callback used to execute the command when the menu item is clicked. 88 | /// See the constructor to see how the menu item is associated with this function using 89 | /// OleMenuCommandService service and MenuCommand class. 90 | /// 91 | /// Event sender. 92 | /// Event args. 93 | public void Execute(object sender, EventArgs e) 94 | { 95 | ThreadHelper.ThrowIfNotOnUIThread(); 96 | 97 | ToggleStashStagedSectionVisibilityCommand.Execute(null); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Commands/StashStagedCommandPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.Diagnostics; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Globalization; 6 | using System.Runtime.InteropServices; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.VisualStudio; 10 | using Microsoft.VisualStudio.OLE.Interop; 11 | using Microsoft.VisualStudio.Shell; 12 | using Microsoft.VisualStudio.Shell.Interop; 13 | using Microsoft.Win32; 14 | using Task = System.Threading.Tasks.Task; 15 | 16 | namespace VisualStudio.GitStashExtension.Commands 17 | { 18 | /// 19 | /// This is the class that implements the package exposed by this assembly. 20 | /// 21 | /// 22 | /// 23 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 24 | /// is to implement the IVsPackage interface and register itself with the shell. 25 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 26 | /// to do it: it derives from the Package class that provides the implementation of the 27 | /// IVsPackage interface and uses the registration attributes defined in the framework to 28 | /// register itself and its components with the shell. These attributes tell the pkgdef creation 29 | /// utility what data to put into .pkgdef file. 30 | /// 31 | /// 32 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. 33 | /// 34 | /// 35 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 36 | [InstalledProductRegistration("#110", "#112", "1.0")] // Info on this package for Help/About 37 | [ProvideMenuResource("Menus.ctmenu", 1)] 38 | [ProvideAutoLoad("2dc9b780-2911-46e9-bd66-508dfd5f68a3", PackageAutoLoadFlags.BackgroundLoad)] 39 | [Guid(StashStagedCommandPackage.PackageGuidString)] 40 | [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")] 41 | public sealed class StashStagedCommandPackage : AsyncPackage 42 | { 43 | /// 44 | /// StashStagedCommandPackage GUID string. 45 | /// 46 | public const string PackageGuidString = "bc5f09cb-4370-408f-a5b8-dd077b5682f4"; 47 | 48 | /// 49 | /// Initializes a new instance of the class. 50 | /// 51 | public StashStagedCommandPackage() 52 | { 53 | // Inside this method you can place any initialization code that does not require 54 | // any Visual Studio service because at this point the package object is created but 55 | // not sited yet inside Visual Studio environment. The place to do all the other 56 | // initialization is the Initialize method. 57 | } 58 | 59 | #region Package Members 60 | 61 | /// 62 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 63 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 64 | /// 65 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. 66 | /// A provider for progress updates. 67 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. 68 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 69 | { 70 | // When initialized asynchronously, the current thread may be a background thread at this point. 71 | // Do any initialization that requires the UI thread after switching to the UI thread. 72 | await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 73 | await StashStagedCommand.InitializeAsync(this); 74 | } 75 | 76 | #endregion 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Commands/StashStagedCommandPackage.vsct: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 48 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Commands/VSPackage.resx: -------------------------------------------------------------------------------- 1 |  2 | 12 | 13 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | text/microsoft-resx 120 | 121 | 122 | 2.0 123 | 124 | 125 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 129 | 130 | 131 | 132 | StashStagedCommand Extension 133 | 134 | 135 | StashStagedCommand Visual Studio Extension Detailed Info 136 | 137 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace VisualStudio.GitStashExtension 5 | { 6 | /// 7 | /// Represents container for constants. 8 | /// 9 | public static class Constants 10 | { 11 | public const string StashNavigationItemId = "350FF356-5C4E-4861-B4C7-9CC97438F31F"; 12 | 13 | public const string StashPageId = "00FAA5EB-3EC5-4098-B550-D58C7035DDBE"; 14 | 15 | public const string StashInfoPageId = "ABBF38A6-1272-4C92-AC7A-79FBCFC796A9"; 16 | 17 | public const string HomePageId = "312e8a59-2712-48a1-863e-0ef4e67961fc"; 18 | 19 | public const string StashListSectionId = "A94AE67A-AF52-42F2-B6E4-C433732AAEB3"; 20 | 21 | public const string StashInfoChangesSectionId = "43CB2B08-DBEE-4096-B091-EBAE5E2E07D2"; 22 | 23 | public const string StashStagedChangesSectionId = "2EA62FC8-499C-415F-A521-DD14DF2D6A4E"; 24 | 25 | public const string StashesLabel = "Stashes"; 26 | 27 | public const string StashesInfoLabel = "Stashes Info"; 28 | 29 | public const string StashesListSectionLabel = "Stash list"; 30 | 31 | public const string StashesInfoChangesSectionLabel = "Files"; 32 | 33 | public static Color NavigationItemColorArgb = Color.FromArgb(0, 213, 66); // green 34 | 35 | public static int NavigationItemColorArgbBit = BitConverter.ToInt32( 36 | new[] { 37 | NavigationItemColorArgb.B, 38 | NavigationItemColorArgb.G, 39 | NavigationItemColorArgb.R, 40 | NavigationItemColorArgb.A, 41 | }, 0); 42 | 43 | public static Color BlueThemeColor = Color.FromArgb(255, 236, 181); 44 | 45 | public static Color LightThemeColor = Color.FromArgb(238, 238, 242); 46 | 47 | public static Color DarkThemeColor = Color.FromArgb(45, 45, 48); 48 | 49 | public const string UnknownRepositoryErrorMessage = "Select repository to find stashes."; 50 | 51 | public const string UnexpectedErrorMessage = "Unexpected error."; 52 | 53 | public const string UnableFindGitMessage = "git.exe wasn't found. Please, verify that git was installed on your computer."; 54 | 55 | public const string DiffToolErrorMessage = "Can't run vsDiffMerge.exe to compare files. \n" + 56 | "Please check tool install path: \n" + 57 | "%visual_studio_install-dir%Common7\\IDE\\CommonExtensions\\Microsoft\\TeamFoundation\\Team Explorer\\vsDiffMerge.exe"; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Converters/NullToCollapsedConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | 6 | namespace VisualStudio.GitStashExtension.Converters 7 | { 8 | /// 9 | /// Converts object value to Visibility, ensures that value is not null. 10 | /// 11 | public class NullToCollapsedConverter: IValueConverter 12 | { 13 | /// 14 | /// Ensures that object value is not null and returns Visible if not null otherwise - Collapsed. 15 | /// 16 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 17 | { 18 | return value != null ? Visibility.Visible : Visibility.Collapsed; 19 | } 20 | 21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 22 | { 23 | return null; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Converters/NullToVisibleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | 6 | namespace VisualStudio.GitStashExtension.Converters 7 | { 8 | /// 9 | /// Converts object value to Visibility, ensures that value is null. 10 | /// 11 | public class NullToVisibleConverter : IValueConverter 12 | { 13 | /// 14 | /// Ensures that object value is null and returns Visible if null otherwise - Collapsed. 15 | /// 16 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 17 | { 18 | return value == null ? Visibility.Visible : Visibility.Collapsed; 19 | } 20 | 21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 22 | { 23 | return null; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Converters/TextToBoolConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace VisualStudio.GitStashExtension.Converters 6 | { 7 | /// 8 | /// Converts text (string) value to bool, ensures that string value is not empty. 9 | /// 10 | public class TextToBoolConverter : IValueConverter 11 | { 12 | /// 13 | /// Ensures that string value is not empty and returns true if text is empty otherwise - false. 14 | /// 15 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 16 | { 17 | var text = value.ToString(); 18 | 19 | return string.IsNullOrEmpty(text); 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | return null; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace VisualStudio.GitStashExtension.Extensions 9 | { 10 | /// 11 | /// Extension class for IEnumerable. 12 | /// 13 | public static class EnumerableExtensions 14 | { 15 | /// 16 | /// Converts Emumerable to ObservableCollection. 17 | /// 18 | /// Type of Enumerable elements. 19 | /// Enumerable to convert. 20 | /// ObservableCollection. 21 | public static ObservableCollection ToObservableCollection(this IEnumerable enumerable) 22 | { 23 | return new ObservableCollection(enumerable); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Extensions/FrameworkElementExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace VisualStudio.GitStashExtension.Extensions 4 | { 5 | /// 6 | /// Class for the extensions of the . 7 | /// 8 | public static class FrameworkElementExtension 9 | { 10 | /// 11 | /// Extension method for . Returns needed parent of the specified type. 12 | /// 13 | /// Type of the parent control. 14 | /// Element to find arent for. 15 | public static T FindParentByType(this FrameworkElement child) where T : FrameworkElement 16 | { 17 | var parentNode = child?.Parent; 18 | 19 | if (parentNode == null) 20 | return null; 21 | 22 | var parent = parentNode as T; 23 | if (parent != null) 24 | { 25 | return parent; 26 | } 27 | else 28 | { 29 | return FindParentByType(parentNode as FrameworkElement); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Extensions/GitServiceExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; 3 | 4 | namespace VisualStudio.GitStashExtension.Extensions 5 | { 6 | /// 7 | /// Extension class for IGitExt. 8 | /// 9 | public static class GitServiceExtension 10 | { 11 | /// 12 | /// Checks existence of any active git repository. 13 | /// 14 | public static bool AnyActiveRepository(this IGitExt service) 15 | { 16 | var activeRepository = service.ActiveRepositories.FirstOrDefault(); 17 | 18 | return activeRepository != null; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Extensions/TreeNodeExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Windows.Forms; 4 | using VisualStudio.GitStashExtension.Models; 5 | 6 | namespace VisualStudio.GitStashExtension.Extensions 7 | { 8 | public static class TreeNodeExtension 9 | { 10 | public static FilesTreeViewItem ToTreeViewItemStructure(this IList changedFiles) 11 | { 12 | return changedFiles.ToTreeNodeStructure().ToTreeViewItem(false); 13 | } 14 | 15 | private static FilesTreeViewItem ToTreeViewItem(this TreeNode node, bool isFile) 16 | { 17 | var attributes = node.Tag as FileAttributes; 18 | 19 | var treeViewItem = new FilesTreeViewItem 20 | { 21 | Text = node.Text, 22 | FullPath = node.GetTreeViewNodeFullPath(), 23 | IsFile = isFile, 24 | IsNew = attributes?.IsNew, 25 | IsStaged = attributes?.IsStaged, 26 | Items = node.Nodes?.OfType()?.Select(n => ToTreeViewItem(n, n.Nodes.Count == 0))?.ToList() ?? new List() 27 | }; 28 | 29 | return treeViewItem; 30 | } 31 | 32 | private static TreeNode ToTreeNodeStructure(this IList changedFiles) 33 | { 34 | var separator = '/'; 35 | var rootNode = new TreeNode(); 36 | 37 | foreach (var file in changedFiles) 38 | { 39 | if (string.IsNullOrEmpty(file.Path.Trim())) 40 | { 41 | continue; 42 | } 43 | 44 | var currentNode = rootNode; 45 | var pathNodes = file.Path.Split(separator); 46 | foreach (var item in pathNodes) 47 | { 48 | var foundedNode = currentNode.Nodes.OfType().FirstOrDefault(x => x.Text == item); 49 | if (foundedNode != null) 50 | { 51 | currentNode = foundedNode; 52 | } 53 | else 54 | { 55 | currentNode = currentNode.Nodes.Add(item); 56 | // Last node in the path -> file. 57 | if (item == pathNodes.LastOrDefault()) 58 | { 59 | // Additional file info 60 | currentNode.Tag = new FileAttributes 61 | { 62 | IsNew = file.IsNew, 63 | IsStaged = file.IsStaged 64 | }; 65 | } 66 | } 67 | } 68 | } 69 | 70 | return rootNode; 71 | } 72 | 73 | private static string GetTreeViewNodeFullPath(this TreeNode node) 74 | { 75 | var fullPath = string.Empty; 76 | if (!string.IsNullOrEmpty(node.Parent?.Text)) 77 | { 78 | fullPath += GetTreeViewNodeFullPath(node.Parent) + "/"; 79 | } 80 | 81 | fullPath += node.Text; 82 | 83 | return fullPath; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/GitHelpers/GitCommandConstants.cs: -------------------------------------------------------------------------------- 1 | namespace VisualStudio.GitStashExtension.GitHelpers 2 | { 3 | /// 4 | /// Represents container for git commmands. 5 | /// 6 | public class GitCommandConstants 7 | { 8 | public const string StashList = "stash list"; 9 | 10 | public const string StashApplyFormatted = "stash apply stash@{{{0}}}"; 11 | 12 | public const string StashPopFormatted = "stash pop stash@{{{0}}}"; 13 | 14 | public const string Stash = "stash"; 15 | 16 | public const string StashIncludeUntracked = "stash --include-untracked"; 17 | 18 | public const string StashKeepIndex = "stash --keep-index --include-untracked"; 19 | 20 | public const string StashSaveFormatted = "stash save {0}"; 21 | 22 | public const string StashSaveFormattedIncludeUntracked = "stash save --include-untracked {0}"; 23 | 24 | public const string StashDeleteFormatted = "stash drop stash@{{{0}}}"; 25 | 26 | /// 27 | /// Stash with new and staged files contains files in the different parts: 28 | /// 1. Regular files in stash@{n} 29 | /// 2. New files in stash@{n}^3 30 | /// 3. Staged files in stash@{n}^2 31 | /// 32 | 33 | #region Stash info (files list) 34 | /// Regular files. 35 | public const string StashInfoFormatted = "show stash@{{{0}}} --name-only --pretty=\"\""; 36 | 37 | /// Staged only new files. 38 | public const string StashUntrackedAndStagedInfoFormatted = "show stash@{{{0}}}^^2 --name-only --pretty=\"\" --diff-filter=A"; 39 | 40 | /// Staged regular and new files. 41 | public const string StashStagedInfoFormatted = "show stash@{{{0}}}^^2 --name-only --pretty=\"\""; 42 | 43 | /// New files. 44 | public const string StashUntrackedInfoFormatted = "show stash@{{{0}}}^^3 --name-only --pretty=\"\""; 45 | #endregion 46 | 47 | #region Stash parts existing validation (new files, staged, etc) 48 | /// Staged files. 49 | public const string CatFileStashCheckUntrackedStagedFilesExist = "cat-file -t stash@{{{0}}}^^2"; 50 | 51 | /// New untracked files. 52 | public const string CatFileStashCheckUntrackedFilesExist = "cat-file -t stash@{{{0}}}^^3"; 53 | #endregion 54 | 55 | #region Stash files diff 56 | /// Regular or Staged regular files after stash file version 57 | public const string AfterStashFileVersionSaveTempFormatted = "show stash@{{{0}}}:\"{1}\" > {2}"; 58 | 59 | /// Regular or Staged regular files before stash file version 60 | public const string BeforeStashFileVersionSaveTempFormatted = "show stash@{{{0}}}^^:\"{1}\" > {2}"; 61 | 62 | /// Staged new file version 63 | public const string UntrackedStagedStashFileVersionSaveTempFormatted = "show stash@{{{0}}}^^2:\"{1}\" > {2}"; 64 | 65 | /// Regular new file version 66 | public const string UntrackedStashFileVersionSaveTempFormatted = "show stash@{{{0}}}^^3:\"{1}\" > {2}"; 67 | #endregion 68 | 69 | public const string StashFileDiffFormatted = "difftool --trust-exit-code -y -x \"'{0}' //t\" stash@{{{1}}}^^ stash@{{{1}}} -- {2}"; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/GitHelpers/GitCommandExecuter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using VisualStudio.GitStashExtension.Models; 7 | using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; 8 | using System.Threading.Tasks; 9 | using Log = VisualStudio.GitStashExtension.Logger.Logger; 10 | 11 | namespace VisualStudio.GitStashExtension.GitHelpers 12 | { 13 | /// 14 | /// Represents service for executing git commands on current repositoty. 15 | /// 16 | public class GitCommandExecuter 17 | { 18 | private readonly IServiceProvider _serviceProvider; 19 | private readonly IGitExt _gitService; 20 | 21 | public GitCommandExecuter(IServiceProvider serviceProvider) 22 | { 23 | _serviceProvider = serviceProvider; 24 | _gitService = (IGitExt)_serviceProvider.GetService(typeof(IGitExt)); 25 | } 26 | 27 | /// 28 | /// Gets all stashes for current repositoty. 29 | /// 30 | /// List of stashes for current repositoty. 31 | /// Error message. 32 | /// Bool value that indicates whether command execution was succeeded. 33 | public bool TryGetAllStashes(out IList stashes, out string errorMessage) 34 | { 35 | var commandResult = Execute(GitCommandConstants.StashList); 36 | if (commandResult.IsError) 37 | { 38 | errorMessage = commandResult.ErrorMessage; 39 | stashes = null; 40 | return false; 41 | } 42 | 43 | stashes = GitResultParser.ParseStashListResult(commandResult.OutputMessage); 44 | errorMessage = string.Empty; 45 | return true; 46 | } 47 | 48 | /// 49 | /// Applies stash to current repository state by stash id. 50 | /// 51 | /// Stash Id. 52 | /// Error message. 53 | /// Bool value that indicates whether command execution was succeeded. 54 | public bool TryApplyStash(int stashId, out string errorMessage) 55 | { 56 | var applyCommand = string.Format(GitCommandConstants.StashApplyFormatted, stashId); 57 | var commandResult = Execute(applyCommand); 58 | 59 | errorMessage = commandResult.ErrorMessage; 60 | return !commandResult.IsError; 61 | } 62 | 63 | /// 64 | /// Pops stash for the current repository by stash id. 65 | /// 66 | /// Stash Id. 67 | /// Error message. 68 | /// Bool value that indicates whether command execution was succeeded. 69 | public bool TryPopStash(int stashId, out string errorMessage) 70 | { 71 | var applyCommand = string.Format(GitCommandConstants.StashPopFormatted, stashId); 72 | var commandResult = Execute(applyCommand); 73 | 74 | errorMessage = commandResult.ErrorMessage; 75 | return !commandResult.IsError; 76 | } 77 | 78 | /// 79 | /// Creates stash on current branch. 80 | /// 81 | /// Save message for stash. 82 | /// Flag indicates that we should include untracked files in stash. 83 | /// Error message. 84 | /// Bool value that indicates whether command execution was succeeded. 85 | public bool TryCreateStash(string message, bool includeUntrackedFiles, out string errorMessage) 86 | { 87 | string createCommand; 88 | 89 | if (string.IsNullOrEmpty(message)) 90 | { 91 | createCommand = includeUntrackedFiles 92 | ? GitCommandConstants.StashIncludeUntracked 93 | : GitCommandConstants.Stash; 94 | } 95 | else 96 | { 97 | createCommand = includeUntrackedFiles 98 | ? string.Format(GitCommandConstants.StashSaveFormattedIncludeUntracked, message) 99 | : string.Format(GitCommandConstants.StashSaveFormatted, message); 100 | } 101 | 102 | var commandResult = Execute(createCommand); 103 | 104 | errorMessage = commandResult.ErrorMessage; 105 | return !commandResult.IsError; 106 | } 107 | 108 | /// 109 | /// Creates stash on current branch and keeps index state. 110 | /// 111 | /// Error message. 112 | /// Bool value that indicates whether command execution was succeeded. 113 | public bool TryCreateStashKeepIndex(out string errorMessage) 114 | { 115 | var commandResult = Execute(GitCommandConstants.StashKeepIndex); 116 | 117 | errorMessage = commandResult.ErrorMessage; 118 | return !commandResult.IsError; 119 | } 120 | 121 | /// 122 | /// Delete stash by id. 123 | /// 124 | /// Stash id. 125 | /// Error message. 126 | /// Bool value that indicates whether command execution was succeeded. 127 | public bool TryDeleteStash(int id, out string errorMessage) 128 | { 129 | var deleteCommand = string.Format(GitCommandConstants.StashDeleteFormatted, id); 130 | 131 | var commandResult = Execute(deleteCommand); 132 | 133 | errorMessage = commandResult.ErrorMessage; 134 | return !commandResult.IsError; 135 | } 136 | 137 | /// 138 | /// Gets stash info by id. 139 | /// 140 | /// Stash id. 141 | /// Stash model. 142 | /// Error message. 143 | /// Bool value that indicates whether command execution was succeeded. 144 | public bool TryGetStashInfo(int id, out Stash stash, out string errorMessage) 145 | { 146 | var infoCommand = string.Format(GitCommandConstants.StashInfoFormatted, id); 147 | 148 | var commandResult = Execute(infoCommand); 149 | 150 | if (commandResult.IsError) 151 | { 152 | errorMessage = commandResult.ErrorMessage; 153 | stash = null; 154 | return false; 155 | } 156 | 157 | errorMessage = string.Empty; 158 | stash = GitResultParser.ParseStashInfoResult(commandResult.OutputMessage); 159 | 160 | if (AreUntrackedFilesExist(id)) 161 | { 162 | if (!TryGetStashUntrackedContent(id, out var untrackedInfo, out errorMessage)) 163 | { 164 | return false; 165 | } 166 | 167 | foreach (var file in untrackedInfo.ChangedFiles) 168 | { 169 | stash.ChangedFiles.Add(file); 170 | } 171 | 172 | } 173 | 174 | if (AreStagedFilesExist(id)) 175 | { 176 | if (!TryGetStashStagedContent(id, out var stagedInfo, out errorMessage)) 177 | { 178 | return false; 179 | } 180 | 181 | foreach (var file in stagedInfo.ChangedFiles) 182 | { 183 | stash.ChangedFiles.Add(file); 184 | } 185 | } 186 | 187 | return true; 188 | } 189 | 190 | /// 191 | /// Checks that stash contains untracked files (new files, created before the stash). 192 | /// 193 | /// Stash id. 194 | /// Bool value that indicates whether command execution was succeeded. 195 | public bool AreUntrackedFilesExist(int id) 196 | { 197 | var checkCommand = string.Format(GitCommandConstants.CatFileStashCheckUntrackedFilesExist, id); 198 | 199 | var commandResult = ExecuteWithCmd(checkCommand); 200 | 201 | if (commandResult.IsError) 202 | { 203 | return false; 204 | } 205 | 206 | return !string.IsNullOrEmpty(commandResult.OutputMessage) && commandResult.OutputMessage.Contains("commit"); 207 | } 208 | 209 | /// 210 | /// Checks that stash contains untracked and staged (before stash) files (new files, created before the stash). 211 | /// 212 | /// Stash id. 213 | /// Bool value that indicates whether command execution was succeeded. 214 | public bool AreStagedFilesExist(int id) 215 | { 216 | var checkCommand = string.Format(GitCommandConstants.CatFileStashCheckUntrackedStagedFilesExist, id); 217 | 218 | var commandResult = ExecuteWithCmd(checkCommand); 219 | 220 | if (commandResult.IsError) 221 | { 222 | return false; 223 | } 224 | 225 | return !string.IsNullOrEmpty(commandResult.OutputMessage) && commandResult.OutputMessage.Contains("commit"); 226 | } 227 | 228 | /// 229 | /// Gets stash info by id. 230 | /// 231 | /// Stash id. 232 | /// Stash model. 233 | /// Error message. 234 | /// Bool value that indicates whether command execution was succeeded. 235 | public bool TryGetStashUntrackedContent(int id, out Stash stash, out string errorMessage) 236 | { 237 | var infoCommand = string.Format(GitCommandConstants.StashUntrackedInfoFormatted, id); 238 | 239 | var commandResult = ExecuteWithCmd(infoCommand); 240 | 241 | if (commandResult.IsError) 242 | { 243 | errorMessage = commandResult.ErrorMessage; 244 | stash = null; 245 | return false; 246 | } 247 | 248 | errorMessage = string.Empty; 249 | stash = GitResultParser.ParseStashUntrackedInfoResult(commandResult.OutputMessage); 250 | 251 | return true; 252 | } 253 | 254 | /// 255 | /// Gets stash info by id. 256 | /// 257 | /// Stash id. 258 | /// Stash model. 259 | /// Error message. 260 | /// Bool value that indicates whether command execution was succeeded. 261 | public bool TryGetStashStagedContent(int id, out Stash stash, out string errorMessage) 262 | { 263 | var infoCommand = string.Format(GitCommandConstants.StashStagedInfoFormatted, id); 264 | 265 | var commandResult = ExecuteWithCmd(infoCommand); 266 | 267 | if (commandResult.IsError) 268 | { 269 | errorMessage = commandResult.ErrorMessage; 270 | stash = null; 271 | return false; 272 | } 273 | 274 | errorMessage = string.Empty; 275 | stash = GitResultParser.ParseStashInfoResult(commandResult.OutputMessage); 276 | 277 | infoCommand = string.Format(GitCommandConstants.StashUntrackedAndStagedInfoFormatted, id); 278 | 279 | commandResult = ExecuteWithCmd(infoCommand); 280 | 281 | if (commandResult.IsError) 282 | { 283 | errorMessage = commandResult.ErrorMessage; 284 | stash = null; 285 | return false; 286 | } 287 | 288 | errorMessage = string.Empty; 289 | var stashWithOnlyUntrackedFiles = GitResultParser.ParseStashInfoResult(commandResult.OutputMessage); 290 | var untrackedFilesNames = stashWithOnlyUntrackedFiles.ChangedFiles.Select(f => f.Path).ToList(); 291 | 292 | foreach (var file in stash.ChangedFiles) 293 | { 294 | if (untrackedFilesNames.Contains(file.Path)) 295 | { 296 | file.IsNew = true; 297 | } 298 | 299 | file.IsStaged = true; 300 | } 301 | 302 | return true; 303 | } 304 | 305 | /// 306 | /// Saves temp file after stash version of specific file. 307 | /// 308 | /// Stash id. 309 | /// Path to the specific file. 310 | /// Path for saving temp file. 311 | /// Error message. 312 | /// Bool value that indicates whether command execution was succeeded. 313 | public bool TrySaveFileAfterStashVersion(int id, string filePath, string pathToSave, out string errorMessage) 314 | { 315 | var afterFileCreateCommand = string.Format(GitCommandConstants.AfterStashFileVersionSaveTempFormatted, id, filePath, pathToSave); 316 | 317 | var commandResult = ExecuteWithCmd(afterFileCreateCommand); 318 | 319 | if (commandResult.IsError) 320 | { 321 | errorMessage = commandResult.ErrorMessage; 322 | return false; 323 | } 324 | 325 | errorMessage = string.Empty; 326 | return true; 327 | } 328 | 329 | /// 330 | /// Saves temp file before stash version of specific file. 331 | /// 332 | /// Stash id. 333 | /// Path to the specific file. 334 | /// Path for saving temp file. 335 | /// Error message. 336 | /// Bool value that indicates whether command execution was succeeded. 337 | public bool TrySaveFileBeforeStashVersion(int id, string filePath, string pathToSave, out string errorMessage) 338 | { 339 | var beforeFileCreateCommand = string.Format(GitCommandConstants.BeforeStashFileVersionSaveTempFormatted, id, filePath, pathToSave); 340 | 341 | var commandResult = ExecuteWithCmd(beforeFileCreateCommand); 342 | 343 | if (commandResult.IsError) 344 | { 345 | errorMessage = commandResult.ErrorMessage; 346 | return false; 347 | } 348 | 349 | errorMessage = string.Empty; 350 | return true; 351 | } 352 | 353 | /// 354 | /// Saves temp file untracked stash version of specific file. 355 | /// 356 | /// Stash id. 357 | /// Path to the specific file. 358 | /// Path for saving temp file. 359 | /// Flag indicates that file was staged before the stash. 360 | /// Error message. 361 | /// Bool value that indicates whether command execution was succeeded. 362 | public bool TrySaveFileUntrackedStashVersion(int id, string filePath, string pathToSave, bool staged, out string errorMessage) 363 | { 364 | var command = staged ? GitCommandConstants.UntrackedStagedStashFileVersionSaveTempFormatted : GitCommandConstants.UntrackedStashFileVersionSaveTempFormatted; 365 | var beforeFileCreateCommand = string.Format(command, id, filePath, pathToSave); 366 | 367 | var commandResult = ExecuteWithCmd(beforeFileCreateCommand); 368 | 369 | if (commandResult.IsError) 370 | { 371 | errorMessage = commandResult.ErrorMessage; 372 | return false; 373 | } 374 | 375 | errorMessage = string.Empty; 376 | return true; 377 | } 378 | 379 | private GitCommandResult Execute(string gitCommand) 380 | { 381 | try 382 | { 383 | var activeRepository = _gitService.ActiveRepositories.FirstOrDefault(); 384 | if (activeRepository == null) 385 | return new GitCommandResult { ErrorMessage = Constants.UnknownRepositoryErrorMessage }; 386 | 387 | var gitExePath = GitPathHelper.GetGitPath(); 388 | 389 | var gitStartInfo = new ProcessStartInfo 390 | { 391 | CreateNoWindow = true, 392 | RedirectStandardError = true, 393 | RedirectStandardOutput = true, 394 | UseShellExecute = false, 395 | FileName = (gitExePath ?? "git.exe"), 396 | Arguments = gitCommand, 397 | WorkingDirectory = activeRepository.RepositoryPath 398 | }; 399 | 400 | using (var gitProcess = Process.Start(gitStartInfo)) 401 | { 402 | var errorMessage = Task.Run(() => gitProcess.StandardError.ReadToEndAsync()); 403 | var outputMessage = Task.Run(() => gitProcess.StandardOutput.ReadToEndAsync()); 404 | 405 | gitProcess.WaitForExit(); 406 | 407 | return new GitCommandResult 408 | { 409 | OutputMessage = outputMessage.Result, 410 | ErrorMessage = errorMessage.Result 411 | }; 412 | } 413 | } 414 | catch (Win32Exception e) 415 | { 416 | Log.LogException(e); 417 | if (e.TargetSite.Name != "StartWithCreateProcess") 418 | throw; 419 | 420 | return new GitCommandResult { ErrorMessage = Constants.UnableFindGitMessage }; 421 | } 422 | catch (Exception e) 423 | { 424 | Log.LogException(e); 425 | return new GitCommandResult { ErrorMessage = Constants.UnexpectedErrorMessage + Environment.NewLine + $"Find error info in {Log.GetLogFilePath()}" }; 426 | } 427 | } 428 | 429 | /// 430 | /// This method is a part of new functionality. To save backward compatibility previous implementation was left. 431 | /// 432 | /// 433 | /// 434 | private GitCommandResult ExecuteWithCmd(string gitCommand) 435 | { 436 | try 437 | { 438 | var activeRepository = _gitService.ActiveRepositories.FirstOrDefault(); 439 | if (activeRepository == null) 440 | return new GitCommandResult {ErrorMessage = Constants.UnknownRepositoryErrorMessage }; 441 | 442 | var gitExePath = GitPathHelper.GetGitPath(); 443 | var cmdCommand = "/C \"\"" + (gitExePath ?? "git.exe") + "\" " + gitCommand + "\""; 444 | 445 | var gitStartInfo = new ProcessStartInfo 446 | { 447 | CreateNoWindow = true, 448 | RedirectStandardError = true, 449 | RedirectStandardOutput = true, 450 | UseShellExecute = false, 451 | FileName = "cmd.exe", 452 | Arguments = cmdCommand, 453 | WorkingDirectory = activeRepository.RepositoryPath 454 | }; 455 | 456 | using (var gitProcess = Process.Start(gitStartInfo)) 457 | { 458 | var errorMessage = Task.Run(() => gitProcess.StandardError.ReadToEndAsync()); 459 | var outputMessage = Task.Run(() => gitProcess.StandardOutput.ReadToEndAsync()); 460 | 461 | gitProcess.WaitForExit(); 462 | 463 | return new GitCommandResult 464 | { 465 | OutputMessage = outputMessage.Result, 466 | ErrorMessage = errorMessage.Result 467 | }; 468 | } 469 | } 470 | catch(Exception e) 471 | { 472 | Log.LogException(e); 473 | return new GitCommandResult { ErrorMessage = Constants.UnexpectedErrorMessage + Environment.NewLine + $"Find error info in {Log.GetLogFilePath()}" }; 474 | } 475 | } 476 | 477 | } 478 | } 479 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/GitHelpers/GitPathHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.IO; 4 | 5 | namespace VisualStudio.GitStashExtension.GitHelpers 6 | { 7 | /// 8 | /// Helper class for finding git.exe file. 9 | /// 10 | public static class GitPathHelper 11 | { 12 | /// 13 | /// Returns git.exe path. 14 | /// 15 | /// String path representation. 16 | public static string GetGitPath() 17 | { 18 | var gitPath = GetGitPathFromProgramFiles(); 19 | 20 | if (!string.IsNullOrEmpty(gitPath)) 21 | return GetGitExeFilePath(gitPath); 22 | 23 | gitPath = GetGitPathFromRegistryValues(); 24 | 25 | return GetGitExeFilePath(gitPath); 26 | } 27 | 28 | public static string GetGitPathFromProgramFiles() 29 | { 30 | string gitPath; 31 | if (Environment.Is64BitOperatingSystem) 32 | { 33 | var programFilesx64 = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion", "ProgramW6432Dir", string.Empty)?.ToString(); 34 | if (!string.IsNullOrEmpty(programFilesx64)) 35 | { 36 | gitPath = programFilesx64 + @"\git"; 37 | return Directory.Exists(gitPath) ? gitPath : string.Empty; 38 | } 39 | } 40 | 41 | gitPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\git"; 42 | return Directory.Exists(gitPath) ? gitPath : string.Empty; 43 | } 44 | 45 | 46 | public static string GetGitPathFromRegistryValues() 47 | { 48 | var gitPath = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1", "InstallLocation", string.Empty)?.ToString(); 49 | if(!string.IsNullOrEmpty(gitPath)) 50 | return Directory.Exists(gitPath) ? gitPath : string.Empty; 51 | 52 | gitPath = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1", "InstallLocation", string.Empty)?.ToString(); 53 | if (!string.IsNullOrEmpty(gitPath)) 54 | return Directory.Exists(gitPath) ? gitPath : string.Empty; 55 | 56 | return string.Empty; 57 | } 58 | 59 | 60 | private static string GetGitExeFilePath(string gitInstallPath) 61 | { 62 | if (Directory.Exists(gitInstallPath)) 63 | { 64 | return gitInstallPath + @"\bin\git.exe"; 65 | } 66 | 67 | return null; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/GitHelpers/GitResultParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using VisualStudio.GitStashExtension.Models; 7 | 8 | namespace VisualStudio.GitStashExtension.GitHelpers 9 | { 10 | /// 11 | /// Represents parser for git commands result. 12 | /// 13 | public static class GitResultParser 14 | { 15 | /// 16 | /// Converst git stash list command string result to list of Stash models. 17 | /// 18 | /// String data. 19 | /// List of Stash models. 20 | public static IList ParseStashListResult(string data) 21 | { 22 | var bytes = Encoding.Default.GetBytes(data); 23 | data = Encoding.UTF8.GetString(bytes); 24 | 25 | var stashesInfo = data.Split('\n') 26 | .Where(s => !string.IsNullOrEmpty(s)) 27 | .ToList(); 28 | 29 | var stashes = new List(); 30 | 31 | foreach (var stashData in stashesInfo) 32 | { 33 | var info = stashData.Split(new[] { ": " }, StringSplitOptions.RemoveEmptyEntries); 34 | var firstSectionMatch = Regex.Match(info[0], @"stash@{(.*)}"); 35 | var thirdSectionMatch = Regex.Match(info[1], @"On (.*)"); 36 | 37 | var stash = new Stash(); 38 | 39 | if (firstSectionMatch.Success) 40 | { 41 | if (int.TryParse(firstSectionMatch.Groups[1].Value, out var stashId)) 42 | { 43 | stash.Id = stashId; 44 | } 45 | else 46 | { 47 | continue; 48 | } 49 | } 50 | 51 | if (thirdSectionMatch.Success) 52 | { 53 | stash.BranchName = thirdSectionMatch.Groups[1].Value; 54 | } 55 | 56 | stash.Message = info[2]; 57 | 58 | stashes.Add(stash); 59 | } 60 | 61 | return stashes; 62 | } 63 | 64 | /// 65 | /// Converst git stash info command string result to Stash model. 66 | /// 67 | /// String data. 68 | /// Stash model. 69 | public static Stash ParseStashInfoResult(string data) 70 | { 71 | var bytes = Encoding.Default.GetBytes(data); 72 | data = Encoding.UTF8.GetString(bytes); 73 | 74 | var filePaths = data.Split('\n') 75 | .Where(s => !string.IsNullOrEmpty(s)) 76 | .ToList(); 77 | 78 | return new Stash 79 | { 80 | ChangedFiles = filePaths.Select(p => new ChangedFile { Path = p }).ToList() 81 | }; 82 | } 83 | 84 | /// 85 | /// Converst git stash untrackedinfo command string result to Stash model. 86 | /// 87 | /// String data. 88 | /// Stash model. 89 | public static Stash ParseStashUntrackedInfoResult(string data) 90 | { 91 | var stash = ParseStashInfoResult(data); 92 | foreach (var file in stash.ChangedFiles) 93 | { 94 | file.IsNew = true; 95 | } 96 | 97 | return stash; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Helpers/RemovedStashesContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace VisualStudio.GitStashExtension.Helpers 4 | { 5 | /// 6 | /// Represents container for the stashes that were removed during the last Stash List page visiting. 7 | /// 8 | /// 9 | /// Team explorer extensibility doesn't provide simple and straightforward way for managing navigation history, 10 | /// so we need to save deleted stashes and check them bafore viewing Stash Info page to avoid navigation to deleted Stash page. 11 | /// 12 | public static class RemovedStashesContainer 13 | { 14 | private static HashSet _stashIds = new HashSet(); 15 | 16 | /// 17 | /// Adds new deleted stash id to the container. 18 | /// 19 | /// Stash id. 20 | public static void AddDeletedStash(int id) 21 | { 22 | _stashIds.Add(id); 23 | } 24 | 25 | /// 26 | /// Checks whether or not stash with this Id was removed. 27 | /// 28 | /// Stash Id. 29 | public static bool Contains(int id) 30 | { 31 | return _stashIds.Contains(id); 32 | } 33 | 34 | /// 35 | /// Resets container state (removes all deleted stash ids). 36 | /// 37 | public static void ResetContainer() 38 | { 39 | _stashIds.Clear(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Logger/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace VisualStudio.GitStashExtension.Logger 5 | { 6 | /// 7 | /// Represents simple file logger fro saving exception info. 8 | /// 9 | public static class Logger 10 | { 11 | private static readonly string LogFilePath; 12 | private static readonly string LogFileName; 13 | private static string LogFile => Path.Combine(LogFilePath, LogFileName); 14 | 15 | static Logger() 16 | { 17 | LogFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "VisualStudioGitStashExtension"); 18 | LogFileName = "extension.log"; 19 | 20 | var directoryInfo = new FileInfo(LogFile).Directory; 21 | directoryInfo?.Create(); 22 | } 23 | 24 | /// 25 | /// Saves exception info to log file. 26 | /// 27 | /// Exception. 28 | public static void LogException(Exception e) 29 | { 30 | var exceptionString = $"At {DateTime.Now:dd/MM/yyyy hh:mm:ss}" + Environment.NewLine + 31 | $"Exception:" + Environment.NewLine + 32 | $"Message: {e.Message}" + Environment.NewLine + 33 | $"Stack trace: {e.StackTrace}" + Environment.NewLine; 34 | 35 | File.WriteAllText(LogFile, exceptionString); 36 | } 37 | 38 | /// 39 | /// Gets log file location. 40 | /// 41 | /// Log file path string. 42 | public static string GetLogFilePath() 43 | { 44 | return LogFile; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Models/ChangedFile.cs: -------------------------------------------------------------------------------- 1 | namespace VisualStudio.GitStashExtension.Models 2 | { 3 | public class ChangedFile 4 | { 5 | /// 6 | /// File path. 7 | /// 8 | public string Path { get; set; } 9 | 10 | /// 11 | /// Flag indicates whether this file is new (untracked) or not. 12 | /// 13 | public bool IsNew { get; set; } 14 | 15 | /// 16 | /// Flag indicates whether file was staged before the stash or not. 17 | /// 18 | public bool IsStaged { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Models/FileAttributes.cs: -------------------------------------------------------------------------------- 1 | namespace VisualStudio.GitStashExtension.Models 2 | { 3 | /// 4 | /// Simple container for Stash file attributes (isNew, isStaged, etc.). 5 | /// 6 | public class FileAttributes 7 | { 8 | /// 9 | /// Flag indicates whether this file is new (untracked) or not. 10 | /// 11 | public bool IsNew { get; set; } 12 | 13 | /// 14 | /// Flag indicates whether file was staged before the stash or not. 15 | /// 16 | public bool IsStaged { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Models/FilesTreeViewItem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace VisualStudio.GitStashExtension.Models 5 | { 6 | /// 7 | /// Represents file tree view item model. 8 | /// 9 | public class FilesTreeViewItem 10 | { 11 | #region Properties 12 | /// 13 | /// File or folder name for treeview. 14 | /// 15 | public string Text { get; set; } 16 | 17 | /// 18 | /// Full path for current file or folder. 19 | /// 20 | public string FullPath { get; set; } 21 | 22 | /// 23 | /// Indicates whether current item is file. 24 | /// 25 | public bool IsFile { get; set; } 26 | 27 | /// 28 | /// Flag indicates whether this file is new (untracked) or not. 29 | /// 30 | public bool? IsNew { get; set; } 31 | 32 | /// 33 | /// Flag indicates whether this file is new (untracked) and staged or not. 34 | /// 35 | public bool? IsStaged { get; set; } 36 | 37 | /// 38 | /// File extension (.cs, .txt, etc.). 39 | /// 40 | public string FileExtension => FullPath?.Split('.')?.LastOrDefault(); 41 | 42 | /// 43 | /// Child items. 44 | /// 45 | /// 46 | /// Only folder items (IsFile == False) can contain Child items. 47 | /// 48 | public IList Items { get; set; } 49 | #endregion 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Models/GitCommandResult.cs: -------------------------------------------------------------------------------- 1 | namespace VisualStudio.GitStashExtension.Models 2 | { 3 | /// 4 | /// Represents model for git command execution result info. 5 | /// 6 | public class GitCommandResult 7 | { 8 | /// 9 | /// Message after git command execution. 10 | /// 11 | public string OutputMessage { get; set; } 12 | 13 | /// 14 | /// Error message after git command execution. 15 | /// 16 | public string ErrorMessage { get; set; } 17 | 18 | /// 19 | /// Flag indicates whether or not git command was finished with errors. 20 | /// 21 | public bool IsError => !string.IsNullOrEmpty(ErrorMessage); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Models/Stash.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace VisualStudio.GitStashExtension.Models 4 | { 5 | /// 6 | /// Represents simple model for stash info. 7 | /// 8 | public class Stash 9 | { 10 | /// 11 | /// Stash ID. 12 | /// 13 | public int Id { get; set; } 14 | 15 | /// 16 | /// Stashe message. 17 | /// 18 | public string Message { get; set; } 19 | 20 | /// 21 | /// Branch name on which stash was created. 22 | /// 23 | public string BranchName { get; set; } 24 | 25 | /// 26 | /// List of changed files. 27 | /// 28 | public IList ChangedFiles { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Models/StashNavigationContext.cs: -------------------------------------------------------------------------------- 1 | namespace VisualStudio.GitStashExtension.Models 2 | { 3 | /// 4 | /// Represents simple model for storing Stash navigation data. 5 | /// 6 | public class StashNavigationContext 7 | { 8 | /// 9 | /// Stash data. 10 | /// 11 | public Stash Stash { get; set; } 12 | 13 | /// 14 | /// Flag indicates that user navigated directly to the Stash Info page, using Stash info link. 15 | /// 16 | /// 17 | /// If this flag is false, this means that user navigated using Team Explorer history panel. 18 | /// 19 | public bool NavigatedDirectly { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Models/VsColorTheme.cs: -------------------------------------------------------------------------------- 1 | namespace VisualStudio.GitStashExtension.Models 2 | { 3 | /// 4 | /// Reperesents simple enum of possible VS themes 5 | /// 6 | public enum VsColorTheme 7 | { 8 | Dark, 9 | Blue, 10 | Light 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/NotifyPropertyChangeBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace VisualStudio.GitStashExtension 6 | { 7 | /// 8 | /// Base implementation of interface. 9 | /// Contains general methods for Notifiable properties change. 10 | /// 11 | public class NotifyPropertyChangeBase: INotifyPropertyChanged 12 | { 13 | /// 14 | /// Occurs when a property value changes. 15 | /// 16 | public event PropertyChangedEventHandler PropertyChanged; 17 | 18 | /// 19 | /// Sets property value and notifies all subscribers if the value was changed. 20 | /// 21 | /// Type of the changed property. 22 | /// New property value. 23 | /// Propery reference. 24 | /// Action that will be executed after property change. 25 | /// Property name. 26 | /// True if the value was changed, otherwise false. 27 | protected bool SetPropertyValue(T newValue, ref T property, Action changeCallback, [CallerMemberName] string propertyName = null) 28 | { 29 | var valueWasChanged = SetPropertyValue(newValue, ref property, propertyName); 30 | 31 | if (valueWasChanged) 32 | { 33 | changeCallback?.Invoke(); 34 | } 35 | 36 | return valueWasChanged; 37 | } 38 | 39 | /// 40 | /// Sets property value and notifies all subscribers if the value was changed. 41 | /// 42 | /// Type of the changed property. 43 | /// New property value. 44 | /// Propery reference. 45 | /// Property name. 46 | /// True if the value was changed, otherwise false. 47 | protected bool SetPropertyValue(T newValue, ref T property, [CallerMemberName] string propertyName = null) 48 | { 49 | // Notify subscribers only if the value was changed. 50 | if ((Equals(property, default(T)) && Equals(newValue, default(T))) || 51 | Equals(property, newValue)) 52 | { 53 | return false; 54 | } 55 | 56 | property = newValue; 57 | 58 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 59 | 60 | return true; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("VisualStudio.GitStashExtension")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("VisualStudio.GitStashExtension")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace VisualStudio.GitStashExtension { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("VisualStudio.GitStashExtension.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to . 65 | /// 66 | internal static string Resource { 67 | get { 68 | return ResourceManager.GetString("Resource", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized resource of type System.Drawing.Bitmap. 74 | /// 75 | internal static System.Drawing.Bitmap SearchIcon { 76 | get { 77 | object obj = ResourceManager.GetObject("SearchIcon", resourceCulture); 78 | return ((System.Drawing.Bitmap)(obj)); 79 | } 80 | } 81 | 82 | /// 83 | /// Looks up a localized resource of type System.Drawing.Bitmap. 84 | /// 85 | internal static System.Drawing.Bitmap SearchIcon_white { 86 | get { 87 | object obj = ResourceManager.GetObject("SearchIcon_white", resourceCulture); 88 | return ((System.Drawing.Bitmap)(obj)); 89 | } 90 | } 91 | 92 | /// 93 | /// Looks up a localized resource of type System.Drawing.Bitmap. 94 | /// 95 | internal static System.Drawing.Bitmap TeamExplorerIcon { 96 | get { 97 | object obj = ResourceManager.GetObject("TeamExplorerIcon", resourceCulture); 98 | return ((System.Drawing.Bitmap)(obj)); 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | 123 | 124 | 125 | resources\teamexplorericon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 126 | 127 | 128 | Resources\SearchIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 129 | 130 | 131 | resources\searchicon_white.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 132 | 133 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Resources/ResourcesDictionary.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 38 | 39 | 44 | 45 | 48 | 49 | 116 | 117 | 123 | 124 | 177 | 178 | 181 | 182 | 191 | 192 | 199 | 200 | 201 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 229 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Resources/SearchIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voshchinskiyvitya/VisualStudio.GitStashExtension/ffcebdde46d9e4060396b8ab570a31a299b2b551/VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Resources/SearchIcon.png -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Resources/SearchIcon_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voshchinskiyvitya/VisualStudio.GitStashExtension/ffcebdde46d9e4060396b8ab570a31a299b2b551/VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Resources/SearchIcon_white.png -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Resources/TeamExplorerIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voshchinskiyvitya/VisualStudio.GitStashExtension/ffcebdde46d9e4060396b8ab570a31a299b2b551/VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Resources/TeamExplorerIcon.png -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Services/FileIconsService.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Windows.Media.Imaging; 3 | using Microsoft.Internal.VisualStudio.PlatformUI; 4 | using Microsoft.VisualStudio.Imaging; 5 | using Microsoft.VisualStudio.Imaging.Interop; 6 | using Microsoft.VisualStudio.Shell.Interop; 7 | 8 | namespace VisualStudio.GitStashExtension.Services 9 | { 10 | /// 11 | /// File icon service for fetching Visual Studio file icons. 12 | /// 13 | public class FileIconsService 14 | { 15 | private readonly IVsImageService2 _vsImageService; 16 | 17 | public FileIconsService(IVsImageService2 vsImageService) 18 | { 19 | _vsImageService = vsImageService; 20 | } 21 | 22 | /// 23 | /// Returns visual studio file icon. 24 | /// 25 | /// File extension (.cs, .txt, etc.). 26 | public BitmapSource GetFileIcon(string fileExtension) 27 | { 28 | var image = _vsImageService.GetIconForFileEx(fileExtension, __VSUIDATAFORMAT.VSDF_WPF, out var _) as WpfPropertyValue; 29 | return image?.Value as BitmapSource; 30 | } 31 | 32 | /// 33 | /// Returns visual studio foder icon. 34 | /// 35 | /// Parameter indicates that flder is expanded or not. 36 | public BitmapSource GetFolderIcon(bool isExpanded) 37 | { 38 | var folderImageMoniker = isExpanded ? KnownMonikers.FolderOpened : KnownMonikers.FolderClosed; 39 | 40 | var vsImage = _vsImageService.GetImage(folderImageMoniker, 41 | new ImageAttributes 42 | { 43 | StructSize = Marshal.SizeOf(typeof(ImageAttributes)), 44 | ImageType = (uint) _UIImageType.IT_Bitmap, 45 | Format = (uint) _UIDataFormat.DF_WPF, 46 | LogicalWidth = 16, 47 | LogicalHeight = 16, 48 | Background = 0xFFFFFFFF, 49 | Flags = (uint) _ImageAttributesFlags.IAF_RequiredFlags | 50 | unchecked((uint) _ImageAttributesFlags.IAF_Background) 51 | }); 52 | var image = vsImage as WpfPropertyValue; 53 | 54 | return image?.Value as BitmapSource; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/Services/VisualStudioGitService.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Microsoft.TeamFoundation.Controls; 3 | using Microsoft.VisualStudio.Shell.Interop; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using VisualStudio.GitStashExtension.GitHelpers; 9 | using VisualStudio.GitStashExtension.Helpers; 10 | using VisualStudio.GitStashExtension.Models; 11 | using Log = VisualStudio.GitStashExtension.Logger.Logger; 12 | 13 | namespace VisualStudio.GitStashExtension.Services 14 | { 15 | /// 16 | /// Represents simple service for git commands execution. 17 | /// Actually, it's just a middle layer between and team explorer page/section view models. 18 | /// 19 | public class VisualStudioGitService 20 | { 21 | private readonly GitCommandExecuter _gitCommandExecuter; 22 | private readonly ITeamExplorer _teamExplorer; 23 | private readonly DTE _dte; 24 | private readonly IVsDifferenceService _vsDiffService; 25 | private readonly IVsThreadedWaitDialogFactory _dialogFactory; 26 | 27 | public VisualStudioGitService(IServiceProvider serviceProvider) 28 | { 29 | _dialogFactory = serviceProvider.GetService(typeof(SVsThreadedWaitDialogFactory)) as IVsThreadedWaitDialogFactory; 30 | _gitCommandExecuter = new GitCommandExecuter(serviceProvider); 31 | _teamExplorer = serviceProvider.GetService(typeof(ITeamExplorer)) as ITeamExplorer; 32 | _dte = serviceProvider.GetService(typeof(DTE)) as DTE; 33 | _vsDiffService = serviceProvider.GetService(typeof(SVsDifferenceService)) as IVsDifferenceService; 34 | } 35 | 36 | /// 37 | /// Tries to create stash (if operatiopn wasn't successful - shows Team Explorer notification). 38 | /// 39 | /// message that should be assigned to the Stash. 40 | /// Flag indicates that untracked files should be included. 41 | /// True if operation was successful, otherwise - false. 42 | public bool TryCreateStash(string message, bool includeUntrackedFiles) 43 | { 44 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 45 | 46 | var result = _dte.ItemOperations.PromptToSave; 47 | if (_gitCommandExecuter.TryCreateStash(message, includeUntrackedFiles, out var errorMessage)) 48 | { 49 | _teamExplorer.CurrentPage.RefreshPageAndSections(); 50 | return true; 51 | } 52 | else 53 | { 54 | _teamExplorer?.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 55 | return false; 56 | } 57 | } 58 | 59 | /// 60 | /// Tries to create stash staged (if operatiopn wasn't successful - shows Team Explorer notification). 61 | /// 62 | /// message that should be assigned to the Stash. 63 | /// True if operation was successful, otherwise - false. 64 | public bool TryCreateStashStaged(string message) 65 | { 66 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 67 | 68 | IVsThreadedWaitDialog2 dialog = null; 69 | if (_dialogFactory != null) 70 | { 71 | _dialogFactory.CreateInstance(out dialog); 72 | } 73 | 74 | // Step 1. git stash keep staged. 75 | if (dialog != null) 76 | { 77 | dialog.StartWaitDialogWithPercentageProgress("Stash staged", "Step 1: git stash --keep-index", "Waiting...", null, "Waiting", false, 0, 4, 1); 78 | } 79 | 80 | if (!_gitCommandExecuter.TryCreateStashKeepIndex(out var errorMessage)) 81 | { 82 | dialog?.EndWaitDialog(out _); 83 | _teamExplorer.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 84 | return false; 85 | } 86 | 87 | // Step 2. git stash 88 | if (dialog != null) 89 | { 90 | dialog.UpdateProgress($"Step 2: git stash save '{message}'", "Waiting...", "Waiting", 2, 4, true, out _); 91 | } 92 | 93 | if (!_gitCommandExecuter.TryCreateStash(message, true, out errorMessage)) 94 | { 95 | dialog?.EndWaitDialog(out _); 96 | _teamExplorer.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 97 | return false; 98 | } 99 | 100 | // Step 3. git stash apply 101 | if (dialog != null) 102 | { 103 | dialog.UpdateProgress("Step 3: git stash apply stash@{1}", "Waiting...", "Waiting", 3, 4, true, out _); 104 | } 105 | 106 | if (!_gitCommandExecuter.TryApplyStash(1, out errorMessage)) 107 | { 108 | dialog?.EndWaitDialog(out _); 109 | _teamExplorer.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 110 | return false; 111 | } 112 | 113 | // Step 4. git stash drop 114 | if (dialog != null) 115 | { 116 | dialog.UpdateProgress("Step 4: git stash drop stash@{1}", "Waiting...", "Waiting", 4, 4, true, out _); 117 | } 118 | 119 | if (!_gitCommandExecuter.TryDeleteStash(1, out errorMessage)) 120 | { 121 | dialog?.EndWaitDialog(out _); 122 | _teamExplorer.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 123 | return false; 124 | } 125 | 126 | dialog?.EndWaitDialog(out _); 127 | return true; 128 | } 129 | 130 | /// 131 | /// Run file diff for the specified file in the specified stash. 132 | /// 133 | /// Id of the stash. 134 | /// File path. 135 | /// File name. 136 | /// Indicates that file is new and doesn't have previous version. 137 | /// Indicates that file was staged before the stash. 138 | public void RunDiff(int stashId, string filePath, string fileName, bool isNew, bool isStaged) 139 | { 140 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 141 | 142 | var beforeTempPath = Path.GetTempFileName(); 143 | var afterTempPath = Path.GetTempFileName(); 144 | var untrackedTempPath = Path.GetTempFileName(); 145 | 146 | try 147 | { 148 | if (isNew) 149 | { 150 | if (isStaged) 151 | { 152 | if (!_gitCommandExecuter.TrySaveFileUntrackedStashVersion(stashId, filePath, untrackedTempPath, true, out var error)) 153 | { 154 | _teamExplorer?.ShowNotification(error, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 155 | return; 156 | } 157 | else 158 | { 159 | _dte.ItemOperations.OpenFile(untrackedTempPath); 160 | return; 161 | } 162 | } 163 | else 164 | { 165 | if (!_gitCommandExecuter.TrySaveFileUntrackedStashVersion(stashId, filePath, untrackedTempPath, false, out var error)) 166 | { 167 | _teamExplorer?.ShowNotification(error, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 168 | return; 169 | } 170 | else 171 | { 172 | _dte.ItemOperations.OpenFile(untrackedTempPath); 173 | return; 174 | } 175 | } 176 | } 177 | 178 | if (!_gitCommandExecuter.TrySaveFileBeforeStashVersion(stashId, filePath, beforeTempPath, out var errorMessage)) 179 | { 180 | _teamExplorer?.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 181 | return; 182 | } 183 | 184 | if (!_gitCommandExecuter.TrySaveFileAfterStashVersion(stashId, filePath, afterTempPath, out errorMessage)) 185 | { 186 | _teamExplorer?.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 187 | return; 188 | } 189 | 190 | _vsDiffService.OpenComparisonWindow2(beforeTempPath, afterTempPath, fileName + " stash diff", "Stash diff", fileName + " before stash", fileName + " after stash", "Stash file content", "", 0); 191 | 192 | } 193 | catch (Exception e) 194 | { 195 | Log.LogException(e); 196 | _teamExplorer?.ShowNotification(Constants.UnexpectedErrorMessage + Environment.NewLine + $"Find error info in {Log.GetLogFilePath()}", NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 197 | } 198 | finally 199 | { 200 | File.Delete(beforeTempPath); 201 | File.Delete(afterTempPath); 202 | } 203 | } 204 | 205 | /// 206 | /// Get stash list content based on search text (if operatiopn wasn't successful - shows Team Explorer notification). 207 | /// 208 | /// Search text. 209 | public IList GetAllStashes(string searchText) 210 | { 211 | if (_gitCommandExecuter.TryGetAllStashes(out var stashes, out var error)) 212 | { 213 | return stashes.Where(s => string.IsNullOrEmpty(searchText) || s.Message.Contains(searchText)).ToList(); 214 | } 215 | 216 | _teamExplorer?.ShowNotification(error, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 217 | return new List(); 218 | } 219 | 220 | /// 221 | /// Applies stash to current repository state (if operatiopn wasn't successful - shows Team Explorer notification). 222 | /// 223 | /// Stash Id. 224 | public void ApplyStash(int stashId) 225 | { 226 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 227 | 228 | if (_gitCommandExecuter.TryApplyStash(stashId, out var errorMessage)) 229 | { 230 | _teamExplorer.NavigateToPage(new Guid(TeamExplorerPageIds.GitChanges), null); 231 | } 232 | else 233 | { 234 | _teamExplorer?.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 235 | } 236 | } 237 | 238 | /// 239 | /// Pops (applies and removes) stash by id (if operatiopn wasn't successful - shows Team Explorer notification). 240 | /// 241 | /// Stash Id. 242 | public void PopStash(int stashId) 243 | { 244 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 245 | 246 | if (_gitCommandExecuter.TryPopStash(stashId, out var errorMessage)) 247 | { 248 | _teamExplorer.NavigateToPage(new Guid(TeamExplorerPageIds.GitChanges), null); 249 | RemovedStashesContainer.AddDeletedStash(stashId); 250 | } 251 | else 252 | { 253 | _teamExplorer?.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 254 | } 255 | } 256 | 257 | /// 258 | /// Removes stash by id (if operatiopn wasn't successful - shows Team Explorer notification). 259 | /// 260 | /// Stash Id. 261 | public void DeleteStash(int stashId) 262 | { 263 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 264 | 265 | if (_gitCommandExecuter.TryDeleteStash(stashId, out var errorMessage)) 266 | { 267 | _teamExplorer.CurrentPage.RefreshPageAndSections(); 268 | RemovedStashesContainer.AddDeletedStash(stashId); 269 | } 270 | else 271 | { 272 | _teamExplorer?.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 273 | } 274 | } 275 | 276 | /// 277 | /// Gets stash info by id (if operatiopn wasn't successful - shows Team Explorer notification). 278 | /// 279 | /// Stash Id. 280 | public Stash GetStashInfo(int stashId) 281 | { 282 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 283 | 284 | if (!_gitCommandExecuter.TryGetStashInfo(stashId, out var stash, out var errorMessage)) 285 | { 286 | _teamExplorer?.ShowNotification(errorMessage, NotificationType.Error, NotificationFlags.None, null, Guid.NewGuid()); 287 | } 288 | 289 | return stash; 290 | } 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/TeamExplorerExtensions/GitStashListStashesSection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.Controls; 2 | using Microsoft.VisualStudio.Shell; 3 | using System; 4 | using System.ComponentModel.Composition; 5 | using VisualStudio.GitStashExtension.VS.UI; 6 | 7 | namespace VisualStudio.GitStashExtension.TeamExplorerExtensions 8 | { 9 | /// 10 | /// Section that contains list of the created stashes. 11 | /// 12 | [TeamExplorerSection(Constants.StashListSectionId, Constants.StashPageId, 100)] 13 | public class GitStashListStashesSection : TeamExplorerBase, ITeamExplorerSection 14 | { 15 | private readonly StashListTeamExplorerSectionUI _sectionContent; 16 | 17 | [ImportingConstructor] 18 | public GitStashListStashesSection([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) 19 | { 20 | SectionContent = _sectionContent = new StashListTeamExplorerSectionUI(serviceProvider); 21 | } 22 | 23 | #region Section properties 24 | public string Title => Constants.StashesListSectionLabel; 25 | 26 | public object SectionContent { get; } 27 | 28 | private bool _isVisible = true; 29 | public bool IsVisible 30 | { 31 | get => _isVisible; 32 | set => SetPropertyValue(value, ref _isVisible); 33 | } 34 | 35 | private bool _isExpanded = true; 36 | public bool IsExpanded 37 | { 38 | get => _isExpanded; 39 | set => SetPropertyValue(value, ref _isExpanded); 40 | } 41 | 42 | public bool IsBusy => false; 43 | #endregion 44 | 45 | #region Public methods 46 | public void Cancel() 47 | { 48 | } 49 | 50 | public object GetExtensibilityService(Type serviceType) 51 | { 52 | return null; 53 | } 54 | 55 | public void Initialize(object sender, SectionInitializeEventArgs e) 56 | { 57 | } 58 | 59 | public void Loaded(object sender, SectionLoadedEventArgs e) 60 | { 61 | } 62 | 63 | public void Refresh() 64 | { 65 | _sectionContent.Refresh(); 66 | } 67 | 68 | public void SaveContext(object sender, SectionSaveContextEventArgs e) 69 | { 70 | } 71 | 72 | public void Dispose() 73 | { 74 | } 75 | #endregion 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/TeamExplorerExtensions/StashInfoChangesSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Composition; 3 | using Microsoft.TeamFoundation.Controls; 4 | using Microsoft.VisualStudio.Shell; 5 | using VisualStudio.GitStashExtension.Models; 6 | using VisualStudio.GitStashExtension.VS.UI; 7 | 8 | namespace VisualStudio.GitStashExtension.TeamExplorerExtensions 9 | { 10 | /// 11 | /// Section that contains information about Stash (name, id, branch, etc.). 12 | /// 13 | [TeamExplorerSection(Constants.StashInfoChangesSectionId, Constants.StashInfoPageId, 100)] 14 | public class StashInfoChangesSection : TeamExplorerBase, ITeamExplorerSection 15 | { 16 | private readonly IServiceProvider _serviceProvider; 17 | 18 | [ImportingConstructor] 19 | public StashInfoChangesSection([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) 20 | { 21 | _serviceProvider = serviceProvider; 22 | } 23 | 24 | #region Section properties 25 | public string Title => Constants.StashesInfoChangesSectionLabel; 26 | 27 | private object _sectionContent; 28 | public object SectionContent 29 | { 30 | get => _sectionContent; 31 | private set => SetPropertyValue(value, ref _sectionContent); 32 | } 33 | 34 | private bool _isVisible = true; 35 | public bool IsVisible 36 | { 37 | get => _isVisible; 38 | set => SetPropertyValue(value, ref _isVisible); 39 | } 40 | 41 | private bool _isExpanded = true; 42 | public bool IsExpanded 43 | { 44 | get => _isExpanded; 45 | set => SetPropertyValue(value, ref _isExpanded); 46 | } 47 | 48 | public bool IsBusy => false; 49 | #endregion 50 | 51 | #region Public methods 52 | public void Initialize(object sender, SectionInitializeEventArgs e) 53 | { 54 | } 55 | 56 | public void Loaded(object sender, SectionLoadedEventArgs e) 57 | { 58 | } 59 | 60 | public void SaveContext(object sender, SectionSaveContextEventArgs e) 61 | { 62 | SectionContent = new StashInfoChangesSectionUI(e.Context as Stash, _serviceProvider); 63 | } 64 | 65 | public void Refresh() 66 | { 67 | } 68 | 69 | public void Cancel() 70 | { 71 | } 72 | 73 | public object GetExtensibilityService(Type serviceType) 74 | { 75 | throw new NotImplementedException(); 76 | } 77 | 78 | public void Dispose() 79 | { 80 | } 81 | #endregion 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/TeamExplorerExtensions/StashInfoTeamExplorerPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.TeamFoundation.Controls; 3 | using VisualStudio.GitStashExtension.Helpers; 4 | using VisualStudio.GitStashExtension.Models; 5 | using VisualStudio.GitStashExtension.VS.UI; 6 | 7 | namespace VisualStudio.GitStashExtension.TeamExplorerExtensions 8 | { 9 | /// 10 | /// Stash information Team explorer page. 11 | /// 12 | [TeamExplorerPage(Constants.StashInfoPageId, Undockable = true, MultiInstances = false)] 13 | public class StashInfoTeamExplorerPage : TeamExplorerBase, ITeamExplorerPage 14 | { 15 | private Stash _stashInfo; 16 | private object _pageContent; 17 | 18 | #region Page properties 19 | public string Title => Constants.StashesInfoLabel; 20 | 21 | 22 | public object PageContent 23 | { 24 | get => _pageContent; 25 | private set => SetPropertyValue(value, ref _pageContent); 26 | } 27 | 28 | public bool IsBusy { get; } 29 | 30 | /// 31 | /// This property can be used to track that page properly initialized. 32 | /// 33 | /// 34 | /// There is a inicialization issue for undocked page, when new Stash Ingo data is not loaded correctly, so we should manually trigger method. 35 | /// 36 | public int? StashId => _stashInfo?.Id; 37 | #endregion 38 | 39 | #region Public methods 40 | public void Initialize(object sender, PageInitializeEventArgs e) 41 | { 42 | var context = e.Context as StashNavigationContext; 43 | _stashInfo = !context.NavigatedDirectly && context.Stash != null && RemovedStashesContainer.Contains(context.Stash.Id) ? null : context.Stash; 44 | PageContent = new StashInfoPage(_stashInfo); 45 | 46 | var changesSection = this.GetSection(new Guid(Constants.StashInfoChangesSectionId)); 47 | changesSection.SaveContext(this, new SectionSaveContextEventArgs 48 | { 49 | Context = _stashInfo 50 | }); 51 | } 52 | 53 | public void Loaded(object sender, PageLoadedEventArgs e) 54 | { 55 | } 56 | 57 | public void SaveContext(object sender, PageSaveContextEventArgs e) 58 | { 59 | e.Context = new StashNavigationContext { Stash = _stashInfo }; 60 | } 61 | 62 | public void Refresh() 63 | { 64 | } 65 | 66 | public void Cancel() 67 | { 68 | } 69 | 70 | public object GetExtensibilityService(Type serviceType) 71 | { 72 | throw new NotImplementedException(); 73 | } 74 | 75 | public void Dispose() 76 | { 77 | } 78 | #endregion 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/TeamExplorerExtensions/StashListTeamExplorerNavigationItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.ComponentModel.Composition; 4 | using Microsoft.TeamFoundation.Controls; 5 | using System.Drawing; 6 | using Microsoft.VisualStudio.Shell; 7 | using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; 8 | using VisualStudio.GitStashExtension.Extensions; 9 | using System.Windows.Threading; 10 | 11 | namespace VisualStudio.GitStashExtension.TeamExplorerExtensions 12 | { 13 | /// 14 | /// Git stash navigation item (redirects user to the Git stash page ). 15 | /// 16 | [TeamExplorerNavigationItem(Constants.StashNavigationItemId, 1000, TargetPageId = Constants.StashPageId)] 17 | public class StashListTeamExplorerNavigationItem : TeamExplorerBase, ITeamExplorerNavigationItem2 18 | { 19 | private readonly ITeamExplorer _teamExplorer; 20 | private readonly IServiceProvider _serviceProvider; 21 | private readonly IGitExt _gitService; 22 | 23 | [ImportingConstructor] 24 | public StashListTeamExplorerNavigationItem([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) 25 | { 26 | _serviceProvider = serviceProvider; 27 | _teamExplorer = _serviceProvider.GetService(typeof(ITeamExplorer)) as ITeamExplorer; 28 | _gitService = _serviceProvider.GetService(typeof(IGitExt)) as IGitExt; 29 | Image = Resources.TeamExplorerIcon; 30 | 31 | IsVisible = _gitService.AnyActiveRepository(); 32 | _gitService.PropertyChanged += GitServicePropertyChanged; 33 | } 34 | 35 | #region Navigation item properties 36 | public string Text => Constants.StashesLabel; 37 | 38 | public Image Image { get; } 39 | 40 | private bool _isVisible; 41 | public bool IsVisible 42 | { 43 | get => _isVisible; 44 | set => SetPropertyValue(value, ref _isVisible); 45 | } 46 | 47 | public bool IsEnabled => true; 48 | 49 | public int ArgbColor => Constants.NavigationItemColorArgbBit; 50 | 51 | public object Icon => null; 52 | #endregion 53 | 54 | #region Public methods 55 | public void Dispose() 56 | { 57 | } 58 | 59 | public void Execute() 60 | { 61 | _teamExplorer.NavigateToPage(new Guid(Constants.StashPageId), null); 62 | } 63 | 64 | public void Invalidate() 65 | { 66 | } 67 | #endregion 68 | 69 | #region Private methods 70 | private void GitServicePropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) 71 | { 72 | var previousValue = IsVisible; 73 | IsVisible = propertyChangedEventArgs.PropertyName == nameof(_gitService.ActiveRepositories) && 74 | _gitService.AnyActiveRepository(); 75 | 76 | // Refresh page only if Stash navigation become visible. 77 | if (!previousValue && IsVisible) 78 | { 79 | Dispatcher.CurrentDispatcher.Invoke(() => 80 | { 81 | if (_teamExplorer.CurrentPage.GetId() == new Guid(Constants.HomePageId)) 82 | _teamExplorer.CurrentPage.Refresh(); 83 | }); 84 | } 85 | } 86 | #endregion 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/TeamExplorerExtensions/StashListTeamExplorerPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.ComponentModel.Composition; 4 | using Microsoft.TeamFoundation.Controls; 5 | using Microsoft.VisualStudio.Shell; 6 | using VisualStudio.GitStashExtension.VS.UI; 7 | 8 | namespace VisualStudio.GitStashExtension.TeamExplorerExtensions 9 | { 10 | /// 11 | /// Stash list Team explorer page. 12 | /// 13 | [TeamExplorerPage(Constants.StashPageId, Undockable = true, MultiInstances = false)] 14 | public class StashListTeamExplorerPage : TeamExplorerBase, ITeamExplorerPage 15 | { 16 | private readonly IServiceProvider _serviceProvider; 17 | private readonly CreateStashSection _pageContent; 18 | 19 | [ImportingConstructor] 20 | public StashListTeamExplorerPage([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) 21 | { 22 | _serviceProvider = serviceProvider; 23 | PageContent = _pageContent = new CreateStashSection(_serviceProvider); 24 | } 25 | 26 | #region Page properties 27 | public string Title => Constants.StashesLabel; 28 | 29 | public object PageContent { get; } 30 | 31 | public bool IsBusy { get; } 32 | #endregion 33 | 34 | #region Page public methods 35 | public void Initialize(object sender, PageInitializeEventArgs e) 36 | { 37 | } 38 | 39 | public void Loaded(object sender, PageLoadedEventArgs e) 40 | { 41 | } 42 | 43 | public void SaveContext(object sender, PageSaveContextEventArgs e) 44 | { 45 | } 46 | 47 | public void Refresh() 48 | { 49 | } 50 | 51 | public void Cancel() 52 | { 53 | } 54 | 55 | public object GetExtensibilityService(Type serviceType) 56 | { 57 | throw new NotImplementedException(); 58 | } 59 | 60 | public void Dispose() 61 | { 62 | } 63 | #endregion 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/TeamExplorerExtensions/StashStagedChangesSection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.Controls; 2 | using Microsoft.TeamFoundation.Controls.WPF.TeamExplorer; 3 | using Microsoft.TeamFoundation.Controls.WPF.TeamExplorer.Framework; 4 | using Microsoft.VisualStudio.Shell; 5 | using System; 6 | using System.ComponentModel.Composition; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using VisualStudio.GitStashExtension.Commands; 10 | using VisualStudio.GitStashExtension.VS.UI; 11 | using VisualStudio.GitStashExtension.Extensions; 12 | using VisualStudio.GitStashExtension.VS.UI.Commands; 13 | 14 | namespace VisualStudio.GitStashExtension.TeamExplorerExtensions 15 | { 16 | /// 17 | /// Section that is responcible for Stash Staged files operation. 18 | /// 19 | [TeamExplorerSection(Constants.StashStagedChangesSectionId, TeamExplorerPageIds.GitChanges, 1)] 20 | public class StashStagedChangesSection : TeamExplorerBase, ITeamExplorerSection 21 | { 22 | private readonly ITeamExplorer _teamExplorer; 23 | private bool StashStagedLinkInitialized = false; 24 | 25 | [ImportingConstructor] 26 | public StashStagedChangesSection([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) 27 | { 28 | SectionContent = new StashStagedSection(serviceProvider, this); 29 | _teamExplorer = serviceProvider.GetService(typeof(ITeamExplorer)) as ITeamExplorer; 30 | } 31 | 32 | #region Section properties 33 | public string Title => string.Empty; 34 | 35 | public object SectionContent { get; } 36 | 37 | private bool _isVisible = false; 38 | public bool IsVisible 39 | { 40 | get => _isVisible; 41 | set => SetPropertyValue(value, ref _isVisible); 42 | } 43 | 44 | private bool _isExpanded = true; 45 | public bool IsExpanded 46 | { 47 | get => _isExpanded; 48 | set => SetPropertyValue(value, ref _isExpanded); 49 | } 50 | 51 | public bool IsBusy => false; 52 | #endregion 53 | 54 | #region Public methods 55 | public void Cancel() 56 | { 57 | IsVisible = false; 58 | } 59 | 60 | public void Dispose() 61 | { 62 | } 63 | 64 | public object GetExtensibilityService(Type serviceType) 65 | { 66 | return null; 67 | } 68 | 69 | public void Initialize(object sender, SectionInitializeEventArgs e) 70 | { 71 | } 72 | 73 | public void Loaded(object sender, SectionLoadedEventArgs e) 74 | { 75 | if (_teamExplorer.CurrentPage.GetId() != new Guid(TeamExplorerPageIds.GitChanges) && !StashStagedLinkInitialized) 76 | { 77 | return; 78 | } 79 | 80 | InitStashStagedChangesPageLink(); 81 | HideStashStagedSectionExpanderLink(); 82 | } 83 | 84 | public void Refresh() 85 | { 86 | } 87 | 88 | public void SaveContext(object sender, SectionSaveContextEventArgs e) 89 | { 90 | } 91 | #endregion 92 | 93 | /// 94 | /// The approach used below is not appropriate, because I've implemented direct WPF tree code modification. 95 | /// This code won't work if any changes to TeamExplorer page structure will be applied by Visual Studio development team. 96 | /// But there is no way to extend/modify current page/section layout, so for now and for better usability current code will be here. 97 | /// Also, I've implemented the second way to open stash staged section (see ), so the same operation can be executed using another approach 98 | /// (in the case if something is changed or error happened). 99 | /// 100 | #region Page modification methods 101 | private void InitStashStagedChangesPageLink() 102 | { 103 | var currentPage = _teamExplorer.CurrentPage.PageContent as UserControl; 104 | 105 | if (currentPage == null) 106 | return; 107 | 108 | var actionsLink = currentPage?.FindName("actionsLink") as TextBlock; 109 | var linksPanel = actionsLink?.Parent as Panel; 110 | 111 | if (linksPanel != null) 112 | { 113 | var stashStagedButton = new DropDownLink 114 | { 115 | Text = "Stash staged", 116 | Margin = new Thickness(10, 0, 0, 0), 117 | DropDownMenuCommand = new ToggleStashStagedSectionVisibilityCommand(_teamExplorer) 118 | }; 119 | 120 | linksPanel.Children.Add(stashStagedButton); 121 | StashStagedLinkInitialized = true; 122 | } 123 | } 124 | 125 | private void HideStashStagedSectionExpanderLink() 126 | { 127 | var section = (SectionContent as UserControl).FindParentByType(); 128 | if (section != null) 129 | { 130 | section.ExpanderButtonVisibility = Visibility.Collapsed; 131 | } 132 | } 133 | #endregion 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/TeamExplorerExtensions/TeamExplorerBase.cs: -------------------------------------------------------------------------------- 1 | namespace VisualStudio.GitStashExtension.TeamExplorerExtensions 2 | { 3 | /// 4 | /// Base class for all TeamExplorer Page/Section/navigation extensions. 5 | /// It provides general properties and methods. 6 | /// 7 | public class TeamExplorerBase: NotifyPropertyChangeBase 8 | { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.UI.Commands/DelegateCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace VisualStudio.GitStashExtension.VS.UI.Commands 5 | { 6 | /// 7 | /// Сommand which takes as a parameter Action delegate that will be executed when the command is invoked. 8 | /// 9 | public class DelegateCommand : ICommand 10 | { 11 | private readonly Predicate canExecute; 12 | private readonly Action execute; 13 | 14 | /// 15 | /// Occurs when changes occur that affect whether or not the command should execute. 16 | /// 17 | public event EventHandler CanExecuteChanged; 18 | 19 | #region Constructors 20 | public DelegateCommand(Action execute) 21 | : this(execute, null) 22 | { 23 | } 24 | 25 | public DelegateCommand(Action execute, 26 | Predicate canExecute) 27 | { 28 | this.execute = execute; 29 | this.canExecute = canExecute; 30 | } 31 | #endregion 32 | 33 | #region Public methods 34 | /// 35 | /// Defines the method that determines whether the command can execute in its current state. 36 | /// 37 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 38 | /// True if this command can be executed, otherwise - false. 39 | public bool CanExecute(object parameter) 40 | { 41 | if (canExecute == null) 42 | { 43 | return true; 44 | } 45 | 46 | return canExecute(parameter); 47 | } 48 | 49 | /// 50 | /// Defines the method to be called when the command is invoked. 51 | /// 52 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 53 | public void Execute(object parameter) 54 | { 55 | execute(parameter); 56 | } 57 | 58 | /// 59 | /// Raises event. 60 | /// 61 | public void RaiseCanExecuteChanged() 62 | { 63 | CanExecuteChanged?.Invoke(this, EventArgs.Empty); 64 | } 65 | #endregion 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.UI.Commands/ToggleStashStagedSectionVisibilityCommand.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.Controls; 2 | using System; 3 | using System.Windows.Input; 4 | 5 | namespace VisualStudio.GitStashExtension.VS.UI.Commands 6 | { 7 | /// 8 | /// Command fot toggling stash staged section visibility. 9 | /// 10 | public class ToggleStashStagedSectionVisibilityCommand : ICommand 11 | { 12 | private readonly ITeamExplorer _teamExplorer; 13 | 14 | public ToggleStashStagedSectionVisibilityCommand(ITeamExplorer teamExplorer) 15 | { 16 | _teamExplorer = teamExplorer; 17 | } 18 | 19 | public event EventHandler CanExecuteChanged; 20 | 21 | public bool CanExecute(object parameter) 22 | { 23 | return true; 24 | } 25 | 26 | public void Execute(object parameter) 27 | { 28 | var stashStagedSection = _teamExplorer?.CurrentPage?.GetSection(new Guid(Constants.StashStagedChangesSectionId)); 29 | if (stashStagedSection != null) 30 | { 31 | stashStagedSection.IsVisible = !stashStagedSection.IsVisible; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.UI/CreateStashSection.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 46 | 47 | 48 | 49 | 50 | 51 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.UI/StashStagedSection.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.Controls; 2 | using System; 3 | using System.Windows.Controls; 4 | using VisualStudio.GitStashExtension.VS.ViewModels; 5 | 6 | namespace VisualStudio.GitStashExtension.VS.UI 7 | { 8 | public partial class StashStagedSection : UserControl 9 | { 10 | private readonly StashStagedSectionViewModel _viewModel; 11 | 12 | public StashStagedSection(IServiceProvider serviceProvider, ITeamExplorerSection currentSection) 13 | { 14 | InitializeComponent(); 15 | 16 | DataContext = _viewModel = new StashStagedSectionViewModel(serviceProvider, currentSection); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.ViewModels/CreateStashSectionViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | using VisualStudio.GitStashExtension.VS.UI.Commands; 4 | using VisualStudio.GitStashExtension.Services; 5 | 6 | namespace VisualStudio.GitStashExtension.VS.ViewModels 7 | { 8 | public class CreateStashSectionViewModel : NotifyPropertyChangeBase 9 | { 10 | private readonly VisualStudioGitService _gitService; 11 | 12 | public CreateStashSectionViewModel(IServiceProvider serviceProvider) 13 | { 14 | _gitService = new VisualStudioGitService(serviceProvider); 15 | 16 | CreateStashCommand = new DelegateCommand(o => { 17 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 18 | 19 | if (_gitService.TryCreateStash(Message, IncludeUntrackedFiles)) 20 | { 21 | Message = string.Empty; 22 | IncludeUntrackedFiles = false; 23 | } 24 | }); 25 | } 26 | 27 | #region View Model properties 28 | /// 29 | /// The message that should be assigned to the stash. 30 | /// 31 | private string _message; 32 | public string Message 33 | { 34 | get => _message; 35 | set => SetPropertyValue(value, ref _message); 36 | } 37 | 38 | /// 39 | /// Flag indicates whether or not stash should include untracked files. 40 | /// 41 | private bool _includeUntrackedFiles; 42 | public bool IncludeUntrackedFiles 43 | { 44 | get => _includeUntrackedFiles; 45 | set => SetPropertyValue(value, ref _includeUntrackedFiles); 46 | } 47 | 48 | /// 49 | /// Command that executes Create stash operation. 50 | /// 51 | public ICommand CreateStashCommand { get; } 52 | #endregion 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.ViewModels/StashInfoChangesSectionViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Windows.Input; 4 | using Microsoft.VisualStudio.Shell.Interop; 5 | using VisualStudio.GitStashExtension.Extensions; 6 | using VisualStudio.GitStashExtension.Models; 7 | using VisualStudio.GitStashExtension.Services; 8 | using VisualStudio.GitStashExtension.VS.UI.Commands; 9 | 10 | namespace VisualStudio.GitStashExtension.VS.ViewModels 11 | { 12 | public class StashInfoChangesSectionViewModel: NotifyPropertyChangeBase 13 | { 14 | private readonly Stash _stash; 15 | private readonly VisualStudioGitService _gitService; 16 | 17 | public StashInfoChangesSectionViewModel(Stash stash, IServiceProvider serviceProvider) 18 | { 19 | _stash = stash; 20 | _gitService = new VisualStudioGitService(serviceProvider); 21 | 22 | if (stash?.ChangedFiles == null) 23 | return; 24 | 25 | var vsImageService = serviceProvider.GetService(typeof(SVsImageService)) as IVsImageService2; 26 | var fileIconsService = new FileIconsService(vsImageService); 27 | 28 | var rootTreeViewItem = stash.ChangedFiles.ToTreeViewItemStructure(); 29 | var rootViewModel = new TreeViewItemWithIconViewModel(rootTreeViewItem, stash.Id, fileIconsService, _gitService); 30 | 31 | ChangeItems = rootViewModel.Items; 32 | } 33 | 34 | /// 35 | /// Stash change items collection. 36 | /// 37 | public ObservableCollection ChangeItems { get; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.ViewModels/StashInfoPageViewModel.cs: -------------------------------------------------------------------------------- 1 | using VisualStudio.GitStashExtension.Models; 2 | 3 | namespace VisualStudio.GitStashExtension.VS.ViewModels 4 | { 5 | public class StashInfoPageViewModel: NotifyPropertyChangeBase 6 | { 7 | public StashInfoPageViewModel(Stash stash) 8 | { 9 | Stash = stash; 10 | } 11 | 12 | private Stash _stash; 13 | public Stash Stash 14 | { 15 | get => _stash; 16 | set => SetPropertyValue(value, ref _stash); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.ViewModels/StashListItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.Controls; 2 | using System; 3 | using System.Windows; 4 | using System.Windows.Input; 5 | using VisualStudio.GitStashExtension.Models; 6 | using VisualStudio.GitStashExtension.Services; 7 | using VisualStudio.GitStashExtension.TeamExplorerExtensions; 8 | using VisualStudio.GitStashExtension.VS.UI.Commands; 9 | 10 | namespace VisualStudio.GitStashExtension.VS.ViewModels 11 | { 12 | /// 13 | /// Stash list item view model. 14 | /// 15 | public class StashListItemViewModel : NotifyPropertyChangeBase 16 | { 17 | private readonly Stash _stash; 18 | private readonly VisualStudioGitService _gitService; 19 | private readonly ITeamExplorer _teamExplorer; 20 | private readonly IServiceProvider _serviceProvider; 21 | 22 | public StashListItemViewModel(Stash stash, IServiceProvider serviceProvider) 23 | { 24 | _stash = stash; 25 | _serviceProvider = serviceProvider; 26 | _gitService = new VisualStudioGitService(serviceProvider); 27 | _teamExplorer = serviceProvider.GetService(typeof(ITeamExplorer)) as ITeamExplorer; 28 | } 29 | 30 | /// 31 | /// Stash ID. 32 | /// 33 | public int Id => _stash.Id; 34 | 35 | /// 36 | /// Stash message. 37 | /// 38 | public string Message => _stash.Message; 39 | 40 | #region Commands 41 | 42 | /// 43 | /// Applies stash to current repository state. 44 | /// 45 | public ICommand ApplyStashCommand => new DelegateCommand(o => _gitService.ApplyStash(Id)); 46 | 47 | /// 48 | /// Pops (applies and removes) stash. 49 | /// 50 | public ICommand PopStashCommand => new DelegateCommand(o => 51 | { 52 | var popStashPromptResult = MessageBox.Show($"Are you sure you want to pop the stash? {Environment.NewLine}All stashed changes will be applied and then deleted!", 53 | "Pop stash", MessageBoxButton.YesNo, MessageBoxImage.Warning); 54 | if (popStashPromptResult == MessageBoxResult.Yes) 55 | { 56 | _gitService.PopStash(Id); 57 | } 58 | }); 59 | 60 | /// 61 | /// Removes stash. 62 | /// 63 | public ICommand DeleteStashCommand => new DelegateCommand(o => 64 | { 65 | var deleteStashPromptResult = MessageBox.Show($"Are you sure you want to delete the stash? {Environment.NewLine}All stashed changes also will be deleted!", 66 | "Delete stash", MessageBoxButton.YesNo, MessageBoxImage.Warning); 67 | if (deleteStashPromptResult == MessageBoxResult.Yes) 68 | { 69 | _gitService.DeleteStash(Id); 70 | } 71 | }); 72 | 73 | /// 74 | /// Open stash info. 75 | /// 76 | public ICommand OpenStashInfoCommand => new DelegateCommand(o => 77 | { 78 | var stashInfo = _gitService.GetStashInfo(Id); 79 | 80 | if (stashInfo != null) 81 | { 82 | _stash.ChangedFiles = stashInfo.ChangedFiles; 83 | var stashContext = new StashNavigationContext { Stash = _stash, NavigatedDirectly = true }; 84 | var page = _teamExplorer.NavigateToPage(new Guid(Constants.StashInfoPageId), stashContext) as StashInfoTeamExplorerPage; 85 | if (page != null && page.StashId != _stash.Id) 86 | { 87 | page.Initialize(this, new PageInitializeEventArgs(_serviceProvider, stashContext)); 88 | } 89 | } 90 | }); 91 | 92 | #endregion 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.ViewModels/StashListSectionViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.Controls; 2 | using System; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System.Windows.Input; 7 | using System.Windows.Media; 8 | using System.Windows.Media.Imaging; 9 | using VisualStudio.GitStashExtension.Extensions; 10 | using VisualStudio.GitStashExtension.GitHelpers; 11 | using VisualStudio.GitStashExtension.Helpers; 12 | using VisualStudio.GitStashExtension.Models; 13 | using VisualStudio.GitStashExtension.Services; 14 | using VisualStudio.GitStashExtension.VSHelpers; 15 | 16 | namespace VisualStudio.GitStashExtension.VS.ViewModels 17 | { 18 | public class StashListSectionViewModel : NotifyPropertyChangeBase 19 | { 20 | private readonly ITeamExplorer _teamExplorer; 21 | private readonly IServiceProvider _serviceProvider; 22 | private readonly GitCommandExecuter _gitCommandExecuter; 23 | private readonly VisualStudioGitService _gitService; 24 | 25 | public StashListSectionViewModel(IServiceProvider serviceProvider) 26 | { 27 | _serviceProvider = serviceProvider; 28 | _teamExplorer = _serviceProvider.GetService(typeof(ITeamExplorer)) as ITeamExplorer; 29 | _gitCommandExecuter = new GitCommandExecuter(serviceProvider); 30 | _gitService = new VisualStudioGitService(_serviceProvider); 31 | 32 | UpdateStashList(string.Empty); 33 | RemovedStashesContainer.ResetContainer(); 34 | 35 | PropertyChanged += (e, s) => 36 | { 37 | if (s.PropertyName == nameof(SearchText)) 38 | { 39 | UpdateStashList(SearchText); 40 | } 41 | }; 42 | } 43 | 44 | #region View Model properties 45 | private ObservableCollection _stashList = new ObservableCollection(); 46 | public ObservableCollection Stashes 47 | { 48 | get => _stashList; 49 | set => SetPropertyValue(value, ref _stashList); 50 | } 51 | 52 | private string _searchText; 53 | public string SearchText 54 | { 55 | get => _searchText; 56 | set => SetPropertyValue(value, ref _searchText); 57 | } 58 | 59 | public ImageSource SearchIconSource 60 | { 61 | get 62 | { 63 | var theme = VisualStudioThemeHelper.GetCurrentTheme(); 64 | 65 | var path = theme == VsColorTheme.Dark ? 66 | @"pack://application:,,,/VisualStudio.GitStashExtension;component/Resources/SearchIcon_white.png" : 67 | @"pack://application:,,,/VisualStudio.GitStashExtension;component/Resources/SearchIcon.png"; 68 | 69 | var uriSource = new Uri(path, UriKind.Absolute); 70 | return new BitmapImage(uriSource); 71 | } 72 | } 73 | 74 | #endregion 75 | 76 | #region Public methods 77 | /// 78 | /// Refreshes stash list content. 79 | /// 80 | public void RefreshList() 81 | { 82 | UpdateStashList(string.Empty); 83 | } 84 | #endregion 85 | 86 | #region Private methods 87 | /// 88 | /// Updates stash list content based on search text. 89 | /// 90 | /// Search text. 91 | private void UpdateStashList(string searchText) 92 | { 93 | Task.Run(() => 94 | { 95 | Stashes = _gitService 96 | .GetAllStashes(searchText) 97 | .Select(s => new StashListItemViewModel(s, _serviceProvider)) 98 | .ToObservableCollection(); 99 | }); 100 | } 101 | #endregion 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.ViewModels/StashStagedSectionViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.Controls; 2 | using System; 3 | using System.Windows.Input; 4 | using VisualStudio.GitStashExtension.Services; 5 | using VisualStudio.GitStashExtension.VS.UI.Commands; 6 | 7 | namespace VisualStudio.GitStashExtension.VS.ViewModels 8 | { 9 | public class StashStagedSectionViewModel : NotifyPropertyChangeBase 10 | { 11 | private readonly ITeamExplorer _teamExplorer; 12 | private readonly IServiceProvider _serviceProvider; 13 | private readonly VisualStudioGitService _gitService; 14 | private readonly ITeamExplorerSection _section; 15 | 16 | public StashStagedSectionViewModel(IServiceProvider serviceProvider, ITeamExplorerSection section) 17 | { 18 | _serviceProvider = serviceProvider; 19 | _section = section; 20 | _teamExplorer = _serviceProvider.GetService(typeof(ITeamExplorer)) as ITeamExplorer; 21 | _gitService = new VisualStudioGitService(serviceProvider); 22 | } 23 | 24 | private string _message; 25 | public string Message 26 | { 27 | get => _message; 28 | set => SetPropertyValue(value, ref _message); 29 | } 30 | 31 | /// 32 | /// Stash staged command. 33 | /// 34 | public ICommand StashStagedCommand => new DelegateCommand(o => 35 | { 36 | if (_gitService.TryCreateStashStaged(Message)) 37 | { 38 | Message = string.Empty; 39 | } 40 | }); 41 | 42 | /// 43 | /// Cancel stash staged command. 44 | /// 45 | public ICommand CancelCommand => new DelegateCommand(o => 46 | { 47 | Message = string.Empty; 48 | _section.Cancel(); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VS.ViewModels/TreeViewItemWithIconViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Linq; 3 | using System.Windows.Input; 4 | using System.Windows.Media.Imaging; 5 | using VisualStudio.GitStashExtension.Models; 6 | using VisualStudio.GitStashExtension.Services; 7 | using VisualStudio.GitStashExtension.VS.UI.Commands; 8 | 9 | namespace VisualStudio.GitStashExtension.VS.ViewModels 10 | { 11 | public class TreeViewItemWithIconViewModel: NotifyPropertyChangeBase 12 | { 13 | private readonly FilesTreeViewItem _internalModel; 14 | private readonly FileIconsService _fileIconService; 15 | private readonly VisualStudioGitService _gitService; 16 | 17 | public TreeViewItemWithIconViewModel(FilesTreeViewItem model, int stashId, FileIconsService fileIconService, VisualStudioGitService gitService) 18 | { 19 | _internalModel = model; 20 | _fileIconService = fileIconService; 21 | _gitService = gitService; 22 | 23 | Source = IsFile ? 24 | _fileIconService.GetFileIcon("." + FileExtension) : 25 | _fileIconService.GetFolderIcon(IsExpanded); 26 | 27 | var childNodes = model.Items.Select(m => new TreeViewItemWithIconViewModel(m, stashId, fileIconService, gitService)).ToList(); 28 | Items = new ObservableCollection(childNodes); 29 | 30 | CompareWithPreviousVersionCommand = new DelegateCommand(o => 31 | { 32 | var treeItem = o as TreeViewItemWithIconViewModel; 33 | var filePath = treeItem?.FullPath; 34 | var fileName = treeItem?.Text; 35 | var isNew = treeItem?.IsNew ?? false; 36 | var isStaged = treeItem?.IsStaged ?? false; 37 | 38 | _gitService.RunDiff(stashId, filePath, fileName, isNew, isStaged); 39 | }); 40 | } 41 | 42 | #region View Model properties 43 | /// 44 | /// Icon source for file or folder. 45 | /// 46 | private BitmapSource _source; 47 | public BitmapSource Source 48 | { 49 | get => _source; 50 | set => SetPropertyValue(value, ref _source); 51 | } 52 | 53 | /// 54 | /// Flag indicates whether or not tree item is expanded. 55 | /// 56 | private bool _isExpanded = true; 57 | public bool IsExpanded 58 | { 59 | get => _isExpanded; 60 | set => SetPropertyValue(value, ref _isExpanded, () => 61 | { 62 | if (!IsFile) 63 | { 64 | Source = _fileIconService.GetFolderIcon(IsExpanded); 65 | } 66 | }); 67 | } 68 | 69 | /// 70 | /// Child items. 71 | /// 72 | public ObservableCollection Items { get; } 73 | 74 | /// 75 | /// Command that will be executed when user selects Compare with previous version option. 76 | /// 77 | public ICommand CompareWithPreviousVersionCommand { get; } 78 | #endregion 79 | 80 | #region Properties 81 | /// 82 | /// File or folder name for treeview. 83 | /// 84 | public string Text => _internalModel.Text; 85 | 86 | /// 87 | /// Full path for current file or folder. 88 | /// 89 | public string FullPath => _internalModel.FullPath; 90 | 91 | /// 92 | /// Indicates whether current item is file. 93 | /// 94 | public bool IsFile => _internalModel.IsFile; 95 | 96 | /// 97 | /// Flag indicates whether this file is new (untracked) or not. 98 | /// 99 | public bool? IsNew => _internalModel.IsNew; 100 | 101 | /// 102 | /// Flag indicates whether this file is new (untracked) and staged or not. 103 | /// 104 | public bool? IsStaged => _internalModel.IsStaged; 105 | 106 | /// 107 | /// File extension (.cs, .txt, etc.). 108 | /// 109 | public string FileExtension => _internalModel.FileExtension; 110 | 111 | /// 112 | /// Context menu header text for file comparing/opening. 113 | /// 114 | public string ContextMenuText => IsNew ?? false ? "Open" : "Compare with previous"; 115 | #endregion 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VSHelpers/VisualStudioPathHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.Win32; 3 | 4 | namespace VisualStudio.GitStashExtension.VSHelpers 5 | { 6 | /// 7 | /// Helper class for getting paths visual studio or visual studio tools. 8 | /// 9 | public static class VisualStudioPathHelper 10 | { 11 | /// 12 | /// Gets VS install dir from registry value. 13 | /// 14 | public static string GetVisualStudioInstallPath() 15 | { 16 | var visualStudioInstalledPath = string.Empty; 17 | var visualStudioRegistryPath = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7"); 18 | if (visualStudioRegistryPath != null) 19 | { 20 | visualStudioInstalledPath = visualStudioRegistryPath.GetValue("15.0", string.Empty) as string; 21 | } 22 | 23 | return visualStudioInstalledPath; 24 | } 25 | 26 | /// 27 | /// Gets vsDiffMerge.exe tool dir using VS install dir. 28 | /// 29 | public static string GetVsDiffMergeToolPath() 30 | { 31 | var toolPath = GetVisualStudioInstallPath() + @"Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\vsDiffMerge.exe"; 32 | return File.Exists(toolPath) ? toolPath : string.Empty; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/VSHelpers/VisualStudioThemeHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Microsoft.VisualStudio.PlatformUI; 3 | using VisualStudio.GitStashExtension.Models; 4 | 5 | namespace VisualStudio.GitStashExtension.VSHelpers 6 | { 7 | /// 8 | /// Helper class for getting visual studio theme color. 9 | /// 10 | public class VisualStudioThemeHelper 11 | { 12 | /// 13 | /// Gets VS theme. 14 | /// 15 | public static VsColorTheme GetCurrentTheme() 16 | { 17 | var color = VSColorTheme.GetThemedColor(EnvironmentColors.AccentMediumColorKey); 18 | var col = Color.FromArgb(color.A, color.R, color.G, color.B); 19 | 20 | if (col == Constants.BlueThemeColor) 21 | return VsColorTheme.Blue; 22 | if (col == Constants.LightThemeColor) 23 | return VsColorTheme.Light; 24 | if (col == Constants.DarkThemeColor) 25 | return VsColorTheme.Dark; 26 | 27 | var colorBrightness = color.GetBrightness(); 28 | var isDark = colorBrightness < 0.5; 29 | return isDark ? VsColorTheme.Dark : VsColorTheme.Light; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /VisualStudio.GitStashExtension/VisualStudio.GitStashExtension/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | VisualStudio.GitStashExtension 6 | Team explorer extension for stashes 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | --------------------------------------------------------------------------------