├── .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 |
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 |
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