├── .gitignore ├── LICENSE.txt ├── README.md ├── RELEASE_NOTES.md ├── azure-pipelines.yml ├── build └── build.fsx ├── build_v15.bat ├── build_v16.bat ├── build_v17.bat ├── lib ├── 15.0 │ ├── Microsoft.TeamFoundation.Client.dll │ ├── Microsoft.TeamFoundation.Common.dll │ ├── Microsoft.TeamFoundation.Controls.dll │ ├── Microsoft.TeamFoundation.VersionControl.Client.dll │ ├── Microsoft.TeamFoundation.VersionControl.Common.dll │ └── Microsoft.TeamFoundation.WorkItemTracking.Client.dll ├── 16.0 │ ├── Microsoft.TeamFoundation.Client.dll │ ├── Microsoft.TeamFoundation.Common.dll │ ├── Microsoft.TeamFoundation.Controls.dll │ ├── Microsoft.TeamFoundation.VersionControl.Client.dll │ ├── Microsoft.TeamFoundation.VersionControl.Common.dll │ └── Microsoft.TeamFoundation.WorkItemTracking.Client.dll └── 17.0 │ ├── Microsoft.TeamFoundation.Client.dll │ ├── Microsoft.TeamFoundation.Common.dll │ ├── Microsoft.TeamFoundation.Controls.dll │ ├── Microsoft.TeamFoundation.VersionControl.Client.dll │ ├── Microsoft.TeamFoundation.VersionControl.Common.dll │ └── Microsoft.TeamFoundation.WorkItemTracking.Client.dll ├── screenshots ├── automerge_main.png └── automerge_teamexplorer.png ├── src ├── .editorconfig ├── AutoMerge.Tests │ ├── AutoMerge.Tests.csproj │ └── JsonParserTests.cs ├── AutoMerge.sln └── AutoMerge │ ├── AutoMerge.csproj │ ├── AutoMergeNavigationItem.cs │ ├── AutoMergePackage.cs │ ├── AutoMergePage.cs │ ├── Base │ ├── TeamExplorerBaseNavigationLink.cs │ ├── TeamExplorerNavigationItemBase.cs │ ├── TeamExplorerPageBase.cs │ ├── TeamExplorerSectionBase.cs │ └── TeamExplorerSectionViewModelBase.cs │ ├── Behaviours │ ├── GridViewColumnResize.cs │ └── ScrollDeligateBehavior.cs │ ├── Branches │ ├── BranchFactory.cs │ ├── BranchValidationResult.cs │ ├── BranchValidator.cs │ ├── BranchesSection.cs │ ├── BranchesView.xaml │ ├── BranchesView.xaml.cs │ ├── BranchesViewModel.cs │ ├── BranchesViewModelContext.cs │ ├── CheckInResult.cs │ ├── EscapeMnemonicConverter.cs │ ├── FileMergeInfo.cs │ ├── MergeInfoViewModel.cs │ ├── MergeMode.cs │ ├── MergeModeToStringConverter.cs │ ├── MergeOption.cs │ ├── MergeOptionToCheckedConverter.cs │ ├── MergeOptionToStringConverter.cs │ ├── MergeRelation.cs │ ├── MergeResult.cs │ ├── MergeResultModel.cs │ ├── Notification.cs │ └── TrackMergeInfo.cs │ ├── Commands │ └── ShowAutoMergeWindow.cs │ ├── CommentFormater.cs │ ├── Configuration │ ├── CommentFormat.cs │ ├── FileSettingProvider.cs │ ├── ISettingProvider.cs │ ├── JsonParser.cs │ └── Settings.cs │ ├── Controls │ ├── SplitButton.xaml │ └── SplitButton.xaml.cs │ ├── Events │ ├── BranchSelectedChangedEvent.cs │ ├── EventAggregatorFactory.cs │ ├── MergeCompleteEvent.cs │ └── SelectChangesetEvent.cs │ ├── GlobalSuppressions.cs │ ├── Guids.cs │ ├── Helpers │ ├── BranchHelper.cs │ ├── EnumerableExtensions.cs │ ├── VersionControlNavigationHelper.cs │ └── WorkspaceHelper.cs │ ├── Key.snk │ ├── Prism │ ├── BindableBase.cs │ ├── Command │ │ ├── DelegateCommand.cs │ │ ├── DelegateCommandBase .cs │ │ ├── IActiveAware.cs │ │ └── WeakEventHandlerManager.cs │ ├── Events │ │ ├── BackgroundEventSubscription.cs │ │ ├── DelegateReference.cs │ │ ├── DispatcherEventSubscription.cs │ │ ├── EventAggregator.cs │ │ ├── EventBase.cs │ │ ├── EventSubscription.cs │ │ ├── IDelegateReference.cs │ │ ├── IEventAggregator.cs │ │ ├── IEventSubscription.cs │ │ ├── PubSubEvent.cs │ │ ├── SubscriptionToken.cs │ │ └── ThreadOption.cs │ └── PropertySupport.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── RecentChangesets │ ├── ChangesetByIdChangesetProvider.cs │ ├── ChangesetCommentConverter.cs │ ├── ChangesetProviderBase.cs │ ├── ChangesetViewModel.cs │ ├── IChangesetProvider.cs │ ├── MyChangesetChangesetProvider.cs │ ├── RecentChangesetFocusableControlNames.cs │ ├── RecentChangesetsSection.cs │ ├── RecentChangesetsView.xaml │ ├── RecentChangesetsView.xaml.cs │ ├── RecentChangesetsViewModel.cs │ └── RecentChangesetsViewModelContext.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Resources │ ├── Package.ico │ ├── merge.png │ └── preview.png │ ├── Services │ ├── ChangesetService.cs │ ├── ILogger.cs │ ├── LoggerBase.cs │ └── VsLogger.cs │ ├── Styles │ └── CommonStyles.xaml │ ├── VSCommandTable.vsct │ ├── VSPackage.resx │ ├── VersionControl │ └── VersionControlProvider.cs │ ├── source.extension.vsixmanifest │ ├── source.vs15.0.extension.vsixmanifest │ ├── source.vs16.0.extension.vsixmanifest │ └── source.vs17.0.extension.vsixmanifest └── tools └── NuGet ├── LICENSE.txt └── NuGet.exe /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | .fake 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.sln.docstates 10 | 11 | # Build results 12 | 13 | [Dd]ebug/ 14 | [Rr]elease/ 15 | build/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 20 | !packages/*/build/ 21 | 22 | # MSTest test Results 23 | [Tt]est[Rr]esult*/ 24 | [Bb]uild[Ll]og.* 25 | 26 | *_i.c 27 | *_p.c 28 | *.ilk 29 | *.meta 30 | *.obj 31 | *.pch 32 | *.pdb 33 | *.pgc 34 | *.pgd 35 | *.rsp 36 | *.sbr 37 | *.tlb 38 | *.tli 39 | *.tlh 40 | *.tmp 41 | *.tmp_proj 42 | *.log 43 | *.vspscc 44 | *.vssscc 45 | .builds 46 | *.pidb 47 | *.log 48 | *.scc 49 | 50 | # Visual Studio profiler 51 | *.psess 52 | *.vsp 53 | *.vspx 54 | 55 | # Guidance Automation Toolkit 56 | *.gpState 57 | 58 | # ReSharper is a .NET coding add-in 59 | _ReSharper*/ 60 | *.[Rr]e[Ss]harper 61 | 62 | # TeamCity is a build add-in 63 | _TeamCity* 64 | 65 | # DotCover is a Code Coverage Tool 66 | *.dotCover 67 | 68 | # NCrunch 69 | *.ncrunch* 70 | .*crunch*.local.xml 71 | 72 | # Click-Once directory 73 | publish/ 74 | 75 | # NuGet Packages Directory 76 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 77 | packages/ 78 | [Ll]ib/ 79 | 80 | # Backup & report files from converting an old project file to a newer 81 | # Visual Studio version. Backup files are not needed, because we have git ;-) 82 | _UpgradeReport_Files/ 83 | Backup*/ 84 | UpgradeLog*.XML 85 | UpgradeLog*.htm 86 | 87 | # ========================= 88 | # Windows detritus 89 | # ========================= 90 | 91 | # Windows image file caches 92 | Thumbs.db 93 | ehthumbs.db 94 | 95 | # Folder config file 96 | Desktop.ini 97 | 98 | # Recycle Bin used on file shares 99 | $RECYCLE.BIN/ 100 | 101 | # Mac desktop service store files 102 | .DS_Store 103 | 104 | # Tools 105 | [tT]ools/ 106 | 107 | # ide temp 108 | *.sln.ide/ 109 | 110 | .vs 111 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014 Kulikov Denis 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Merge 2 | **Easy way to merge changeset** 3 | 4 | [![Build Status](https://cduke.visualstudio.com/AutoMerge/_apis/build/status/CDuke.AutoMerge?branchName=master)](https://cduke.visualstudio.com/AutoMerge/_build/latest?definitionId=12&branchName=master) 5 | 6 | TFS changeset simple merge. 7 | Usefull when need merge bugfix changeset to several branches. 8 | 9 | ## Features 10 | * Validate target folder (mapped, user has rights, already merged) 11 | * Get latest source before merge 12 | * Associate changeset with same workitem 13 | * Add comment 14 | * Easy way discard/force merge 15 | * Check-In after merge or only fill pending changes page 16 | * Double click open changeset details 17 | 18 | ## Usage 19 | On team explorer page select Auto Merge. Extension show last changesets and list of branches. You select branches and press merge. That's all. 20 | 21 | Team explorer home page 22 | 23 | ![Team explorer home page](./screenshots/automerge_teamexplorer.png) 24 | 25 | Auto Merge page 26 | 27 | ![Auto Merge page](./screenshots/automerge_main.png) 28 | 29 | ## Issues 30 | [https://github.com/CDuke/AutoMerge/issues](https://github.com/CDuke/AutoMerge/issues) 31 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | #### 0.2.6.10 (2021-03-19) 2 | * (fix) When you have multiple branches with same prefix, e.g. ABC and ABCDE. When merging to ABC method choses ABCDE instead, and as a result there are multiple branches with the same name in target branch selector 3 | 4 | #### 0.2.6.9 (2017-05-09) 5 | * (fix) If the comment doesn't contain any target branch information the comment will be the same for each target branch. Consolidate duplicate comments when displaying in the Pending Changes view (thanks psaut) 6 | 7 | #### 0.2.6.8 (2017-03-02) 8 | * (enhancement) Order workspaces by name in "Target branches" list (thanks MrLuje) 9 | * (fix) Fix typo at "Resolve" word 10 | 11 | #### 0.2.6.7 (2016-09-02) 12 | * (enhancement) Added the ability to specify the number of changesets shown for merging. Name - changeset_count_show 13 | 14 | #### 0.2.6.6 (2016-03-21) 15 | * (enhancement) Add "{SourceWorkItemTitles}" keyword for comment_format (#22) 16 | 17 | #### 0.2.6.5 (2015-11-26) 18 | * (fix) Null reference when config does not exists 19 | 20 | #### 0.2.6.4 (2015-11-23) 21 | * (fix) Crash when comment template has newline (#18) 22 | 23 | #### 0.2.6.3 (2015-05-18) 24 | * (fix) The extension requires a version of .NET Framework that is not installed. (#15) 25 | 26 | #### 0.2.6.2 (2015-03-30) 27 | * (fix) SQL Server error 2627 28 | 29 | #### 0.2.6.1 (2015-01-19) 30 | * (fix) Showing all changes regardless of the project 31 | 32 | #### 0.2.6 (2015-01-18) 33 | * (enhancement) Change behavior "Merge & Check In". Now it check in if no conflicts, otherwise show resolve conflicts window. 34 | * (enhancement) If found to be restored unexpected file, merging occurs by file. 35 | * (enhancement) After check in show changeset id. 36 | * (fix) Fix refreshing changeset when project changed. 37 | 38 | #### 0.2.5.1 (2014-12-03) 39 | * (enhancement) Allow templating discard merge comment (#11) 40 | 41 | #### 0.2.5 (2014-11-29) 42 | * (enhancement) Allow templating merge comment (#6) 43 | 44 | #### 0.2.4 (2014-09-24) 45 | * (enhancement) Allow set default mode for "Merge" button 46 | 47 | #### 0.2.3 (2014-09-22) 48 | * (enhancement) Double click on branch open it in source control explorer (#7) 49 | * (fix) Multi load branches info, because not unsubscribe events 50 | 51 | #### 0.2.2 (2014-06-23) 52 | * (fix) Button "Merge" not disabled while merging (#3) 53 | * (enhancement) Rename button "Merge" -> "Merge & Check In" 54 | * (enhancement) Save last merge operation and next time it will be default (#4) 55 | 56 | #### 0.2.1 (2014-06-13) 57 | * (fix) In some cases occurs "Cannot add instance of type 'ScrollDeligateBehavior' to a collection of type 'BehaviorCollection'" (#2) 58 | * (enhancement) Add support multi workspace (#1) 59 | 60 | #### 0.2 (2014-05-20) 61 | * Initial Release 62 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # .NET Desktop 2 | # Build and run tests for .NET Desktop or Windows classic desktop solutions. 3 | # Add steps that publish symbols, save build artifacts, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: 'windows-latest' 11 | 12 | variables: 13 | solution: '**/*.sln' 14 | buildPlatform: 'Any CPU' 15 | buildConfiguration: 'Release' 16 | msbuildArgs: '/p:VisualStudioVersion=17.0 -restore' 17 | 18 | steps: 19 | 20 | - task: VSBuild@1 21 | displayName: Build 22 | inputs: 23 | solution: '$(solution)' 24 | platform: '$(buildPlatform)' 25 | configuration: '$(buildConfiguration)' 26 | msbuildArgs: '$(msbuildArgs)' 27 | 28 | - task: CopyFiles@2 29 | inputs: 30 | SourceFolder: '$(Build.SourcesDirectory)' 31 | Contents: '**/bin/**/*.vsix' 32 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 33 | 34 | - task: PublishBuildArtifacts@1 35 | inputs: 36 | pathToPublish: $(Build.ArtifactStagingDirectory) 37 | artifactName: VSIX 38 | 39 | 40 | -------------------------------------------------------------------------------- /build/build.fsx: -------------------------------------------------------------------------------- 1 | #r @"../tools/FAKE/tools/FakeLib.dll" 2 | open Fake 3 | open System.Xml 4 | 5 | // Properties 6 | let vsVersion = environVarOrDefault "VisualStudioVersion" "12.0" 7 | let buildDirBase = "./bin/" 8 | let buildDir = buildDirBase + vsVersion 9 | 10 | 11 | // files 12 | let slnReferences = !!"./src/AutoMerge.sln" 13 | 14 | // Targets 15 | Target "Clean" (fun _ -> 16 | CleanDir buildDir 17 | ) 18 | 19 | Target "RestorePackages" (fun _ -> 20 | !! "./**/packages.config" 21 | |> Seq.iter (fun id -> (RestorePackage (fun p -> {p with OutputPath = "./lib"}) id)) 22 | ) 23 | 24 | Target "BuildApp" (fun _ -> 25 | slnReferences 26 | |> MSBuildRelease buildDir "Build" 27 | |> Log "AppBuild-Output: " 28 | ) 29 | 30 | Target "All" DoNothing 31 | 32 | 33 | // Dependencies 34 | "Clean" 35 | ==> "RestorePackages" 36 | ==> "BuildApp" 37 | ==> "All" 38 | 39 | // start build 40 | RunTargetOrDefault "All" -------------------------------------------------------------------------------- /build_v15.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | IF NOT EXIST "tools/FAKE" ( 4 | "tools/nuget/nuget.exe" "install" "FAKE" "-OutputDirectory" "tools" "-ExcludeVersion" 5 | ) 6 | 7 | "tools/FAKE/tools/Fake.exe" "build/build.fsx" "VisualStudioVersion=15.0" 8 | pause 9 | exit /b %errorlevel% -------------------------------------------------------------------------------- /build_v16.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | IF NOT EXIST "tools/FAKE" ( 4 | "tools/nuget/nuget.exe" "install" "FAKE" "-OutputDirectory" "tools" "-ExcludeVersion" 5 | ) 6 | 7 | "tools/FAKE/tools/Fake.exe" "build/build.fsx" "VisualStudioVersion=16.0" 8 | pause 9 | exit /b %errorlevel% -------------------------------------------------------------------------------- /build_v17.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | IF NOT EXIST "tools/FAKE" ( 4 | "tools/nuget/nuget.exe" "install" "FAKE" "-OutputDirectory" "tools" "-ExcludeVersion" 5 | ) 6 | 7 | "tools/FAKE/tools/Fake.exe" "build/build.fsx" "VisualStudioVersion=17.0" 8 | pause 9 | exit /b %errorlevel% -------------------------------------------------------------------------------- /lib/15.0/Microsoft.TeamFoundation.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/15.0/Microsoft.TeamFoundation.Client.dll -------------------------------------------------------------------------------- /lib/15.0/Microsoft.TeamFoundation.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/15.0/Microsoft.TeamFoundation.Common.dll -------------------------------------------------------------------------------- /lib/15.0/Microsoft.TeamFoundation.Controls.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/15.0/Microsoft.TeamFoundation.Controls.dll -------------------------------------------------------------------------------- /lib/15.0/Microsoft.TeamFoundation.VersionControl.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/15.0/Microsoft.TeamFoundation.VersionControl.Client.dll -------------------------------------------------------------------------------- /lib/15.0/Microsoft.TeamFoundation.VersionControl.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/15.0/Microsoft.TeamFoundation.VersionControl.Common.dll -------------------------------------------------------------------------------- /lib/15.0/Microsoft.TeamFoundation.WorkItemTracking.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/15.0/Microsoft.TeamFoundation.WorkItemTracking.Client.dll -------------------------------------------------------------------------------- /lib/16.0/Microsoft.TeamFoundation.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/16.0/Microsoft.TeamFoundation.Client.dll -------------------------------------------------------------------------------- /lib/16.0/Microsoft.TeamFoundation.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/16.0/Microsoft.TeamFoundation.Common.dll -------------------------------------------------------------------------------- /lib/16.0/Microsoft.TeamFoundation.Controls.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/16.0/Microsoft.TeamFoundation.Controls.dll -------------------------------------------------------------------------------- /lib/16.0/Microsoft.TeamFoundation.VersionControl.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/16.0/Microsoft.TeamFoundation.VersionControl.Client.dll -------------------------------------------------------------------------------- /lib/16.0/Microsoft.TeamFoundation.VersionControl.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/16.0/Microsoft.TeamFoundation.VersionControl.Common.dll -------------------------------------------------------------------------------- /lib/16.0/Microsoft.TeamFoundation.WorkItemTracking.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/16.0/Microsoft.TeamFoundation.WorkItemTracking.Client.dll -------------------------------------------------------------------------------- /lib/17.0/Microsoft.TeamFoundation.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/17.0/Microsoft.TeamFoundation.Client.dll -------------------------------------------------------------------------------- /lib/17.0/Microsoft.TeamFoundation.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/17.0/Microsoft.TeamFoundation.Common.dll -------------------------------------------------------------------------------- /lib/17.0/Microsoft.TeamFoundation.Controls.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/17.0/Microsoft.TeamFoundation.Controls.dll -------------------------------------------------------------------------------- /lib/17.0/Microsoft.TeamFoundation.VersionControl.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/17.0/Microsoft.TeamFoundation.VersionControl.Client.dll -------------------------------------------------------------------------------- /lib/17.0/Microsoft.TeamFoundation.VersionControl.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/17.0/Microsoft.TeamFoundation.VersionControl.Common.dll -------------------------------------------------------------------------------- /lib/17.0/Microsoft.TeamFoundation.WorkItemTracking.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/lib/17.0/Microsoft.TeamFoundation.WorkItemTracking.Client.dll -------------------------------------------------------------------------------- /screenshots/automerge_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/screenshots/automerge_main.png -------------------------------------------------------------------------------- /screenshots/automerge_teamexplorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/screenshots/automerge_teamexplorer.png -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | end_of_line = crlf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | 12 | [*.xaml] 13 | indent_size = 2 14 | 15 | [*.json] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /src/AutoMerge.Tests/AutoMerge.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | net472 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/AutoMerge.Tests/JsonParserTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace AutoMerge.Tests 5 | { 6 | public class JsonParserTests 7 | { 8 | [Fact] 9 | public void WhenValueHasNewLine_ShouldCorrectParse() 10 | { 11 | var value = string.Format("{0}Patch Back", Environment.NewLine); 12 | var json = string.Format("{{\"comment_format\": \"{0}\"}}", value); 13 | 14 | var values = JsonParser.ParseJson(json); 15 | 16 | Assert.NotEmpty(values); 17 | Assert.Equal(value, values["comment_format"]); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/AutoMerge.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29905.134 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoMerge", "AutoMerge\AutoMerge.csproj", "{726ED85E-2274-4D95-B822-B2CFE2CE44B9}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5101B5A3-BBBF-474C-8BBF-A61EF46643D0}" 9 | ProjectSection(SolutionItems) = preProject 10 | ..\build.fsx = ..\build.fsx 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoMerge.Tests", "AutoMerge.Tests\AutoMerge.Tests.csproj", "{4CD4D5BB-FE08-4DAF-A952-93E78022FC12}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {726ED85E-2274-4D95-B822-B2CFE2CE44B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {726ED85E-2274-4D95-B822-B2CFE2CE44B9}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {726ED85E-2274-4D95-B822-B2CFE2CE44B9}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {726ED85E-2274-4D95-B822-B2CFE2CE44B9}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {4CD4D5BB-FE08-4DAF-A952-93E78022FC12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {4CD4D5BB-FE08-4DAF-A952-93E78022FC12}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {4CD4D5BB-FE08-4DAF-A952-93E78022FC12}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {4CD4D5BB-FE08-4DAF-A952-93E78022FC12}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /src/AutoMerge/AutoMergeNavigationItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Composition; 3 | using AutoMerge.Base; 4 | using AutoMerge.VersionControl; 5 | using Microsoft.TeamFoundation.Controls; 6 | using Microsoft.VisualStudio.Shell; 7 | 8 | namespace AutoMerge 9 | { 10 | [TeamExplorerNavigationItem(GuidList.AutoMergeNavigationItemId, 210, TargetPageId = GuidList.AutoMergePageId)] 11 | public class AutoMergeNavigationItem : TeamExplorerNavigationItemBase 12 | { 13 | 14 | [ImportingConstructor] 15 | public AutoMergeNavigationItem( 16 | [Import(typeof(SVsServiceProvider))] 17 | IServiceProvider serviceProvider) 18 | : base(serviceProvider, GuidList.AutoMergePageId, VersionControlProvider.TeamFoundation) 19 | { 20 | Text = Resources.AutoMergePageName; 21 | Image = Resources.MergeImage; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/AutoMerge/AutoMergePackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading; 4 | using AutoMerge.Commands; 5 | using Microsoft.VisualStudio.Shell; 6 | 7 | namespace AutoMerge 8 | { 9 | /// 10 | /// This is the class that implements the package exposed by this assembly. 11 | /// 12 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 13 | /// is to implement the IVsPackage interface and register itself with the shell. 14 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 15 | /// to do it: it derives from the Package class that provides the implementation of the 16 | /// IVsPackage interface and uses the registration attributes defined in the framework to 17 | /// register itself and its components with the shell. 18 | /// 19 | // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is 20 | // a package. 21 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 22 | // This attribute is used to register the information needed to show this package 23 | // in the Help/About dialog of Visual Studio. 24 | [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] 25 | [Guid(GuidList.guidAutoMergePkgString)] 26 | [ProvideMenuResource("Menus.ctmenu", 1)] 27 | [ProvideBindingPath] 28 | public sealed class AutoMergePackage : AsyncPackage 29 | { 30 | protected override async System.Threading.Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 31 | { 32 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 33 | 34 | await ShowAutoMergeWindow.InitializeAsync(this); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/AutoMerge/AutoMergePage.cs: -------------------------------------------------------------------------------- 1 |  2 | using AutoMerge.Base; 3 | using Microsoft.TeamFoundation.Controls; 4 | 5 | namespace AutoMerge 6 | { 7 | [TeamExplorerPage(GuidList.AutoMergePageId)] 8 | public class AutoMergePage : TeamExplorerPageBase 9 | { 10 | 11 | /// 12 | /// Constructor. 13 | /// 14 | public AutoMergePage() 15 | { 16 | Title = Resources.AutoMergePageName; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AutoMerge/Base/TeamExplorerBaseNavigationLink.cs: -------------------------------------------------------------------------------- 1 | using TfsTeamExplorerNavigationLinkBase = Microsoft.TeamFoundation.Controls.WPF.TeamExplorer.TeamExplorerNavigationLinkBase; 2 | 3 | namespace AutoMerge.Base 4 | { 5 | /// 6 | /// Team Explorer base navigation link class. 7 | /// 8 | public abstract class TeamExplorerBaseNavigationLink : TfsTeamExplorerNavigationLinkBase 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/AutoMerge/Base/TeamExplorerNavigationItemBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AutoMerge.VersionControl; 3 | using Microsoft.TeamFoundation.Controls.WPF.TeamExplorer; 4 | 5 | using TfsTeamExplorerNavigationItemBase = Microsoft.TeamFoundation.Controls.WPF.TeamExplorer.TeamExplorerNavigationItemBase; 6 | 7 | namespace AutoMerge.Base 8 | { 9 | public abstract class TeamExplorerNavigationItemBase : TfsTeamExplorerNavigationItemBase 10 | { 11 | private readonly VersionControlProvider _versionControlProvider; 12 | private Guid _pageId; 13 | 14 | protected IServiceProvider ServiceProvider { get; private set; } 15 | 16 | protected TeamExplorerNavigationItemBase(IServiceProvider serviceProvider, 17 | string pageId, VersionControlProvider versionControlProvider) 18 | { 19 | _versionControlProvider = versionControlProvider; 20 | ServiceProvider = serviceProvider; 21 | _pageId = new Guid(pageId); 22 | } 23 | 24 | public override void Execute() 25 | { 26 | TeamExplorerUtils.Instance.NavigateToPage(_pageId.ToString(), ServiceProvider, null); 27 | } 28 | 29 | public override void Invalidate() 30 | { 31 | base.Invalidate(); 32 | IsVisible = CalculateVisible(); 33 | } 34 | 35 | private bool CalculateVisible() 36 | { 37 | return VersionControlNavigationHelper.IsProviderActive(ServiceProvider, _versionControlProvider) 38 | && VersionControlNavigationHelper.IsConnectedToTfsCollectionAndProject(ServiceProvider); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AutoMerge/Base/TeamExplorerPageBase.cs: -------------------------------------------------------------------------------- 1 | using TfsTeamExplorerPageBase = Microsoft.TeamFoundation.Controls.WPF.TeamExplorer.TeamExplorerPageBase; 2 | 3 | namespace AutoMerge.Base 4 | { 5 | /// 6 | /// Team Explorer page base class. 7 | /// 8 | public abstract class TeamExplorerPageBase : TfsTeamExplorerPageBase 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AutoMerge/Base/TeamExplorerSectionBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.Controls; 2 | using Microsoft.TeamFoundation.MVVM; 3 | using TfsTeamExplorerSectionBase = Microsoft.TeamFoundation.Controls.WPF.TeamExplorer.TeamExplorerSectionBase; 4 | 5 | namespace AutoMerge.Base 6 | { 7 | public abstract class TeamExplorerSectionBase : TfsTeamExplorerSectionBase 8 | { 9 | public override void Initialize(object sender, SectionInitializeEventArgs e) 10 | { 11 | base.Initialize(sender, e); 12 | var viewModelBase = ViewModel as ViewModelBase; 13 | if (View != null && viewModelBase != null) 14 | { 15 | viewModelBase.RegisterService(View); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AutoMerge/Base/TeamExplorerSectionViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.TeamFoundation.Client; 4 | using Microsoft.TeamFoundation.Common.Internal; 5 | using Microsoft.TeamFoundation.Controls; 6 | using Microsoft.TeamFoundation.Controls.MVVM; 7 | using TfsTeamExplorerSectionViewModelBase = Microsoft.TeamFoundation.Controls.WPF.TeamExplorer.TeamExplorerSectionViewModelBase; 8 | 9 | namespace AutoMerge.Base 10 | { 11 | public abstract class TeamExplorerSectionViewModelBase : TfsTeamExplorerSectionViewModelBase 12 | { 13 | private readonly ILogger _logger; 14 | private static readonly Task _emptyTask = Task.FromResult(0); 15 | 16 | private ITeamFoundationContextManager TfsContextManager { get; set; } 17 | protected ITeamFoundationContext Context { get; private set; } 18 | 19 | protected TeamExplorerSectionViewModelBase(ILogger logger) 20 | { 21 | _logger = logger; 22 | } 23 | 24 | protected ILogger Logger { get { return _logger; } } 25 | 26 | protected virtual Task RefreshAsync() 27 | { 28 | return _emptyTask; 29 | } 30 | 31 | protected virtual Task InitializeAsync(object sender, SectionInitializeEventArgs e) 32 | { 33 | return _emptyTask; 34 | } 35 | 36 | public async override void Initialize(object sender, SectionInitializeEventArgs e) 37 | { 38 | ShowBusy(); 39 | 40 | try 41 | { 42 | base.Initialize(sender, e); 43 | if (ServiceProvider != null) 44 | { 45 | TfsContextManager = ServiceProvider.GetService(); 46 | if (TfsContextManager != null) 47 | { 48 | TfsContextManager.ContextChanged -= OnContextChanged; 49 | TfsContextManager.ContextChanged += OnContextChanged; 50 | var context = TfsContextManager.CurrentContext; 51 | Context = context; 52 | } 53 | } 54 | await InitializeAsync(sender, e); 55 | } 56 | catch (Exception ex) 57 | { 58 | ShowError(ex.Message); 59 | } 60 | 61 | HideBusy(); 62 | } 63 | 64 | public async override void Refresh() 65 | { 66 | ShowBusy(); 67 | 68 | try 69 | { 70 | await RefreshAsync(); 71 | } 72 | catch (Exception ex) 73 | { 74 | ShowError(ex.Message); 75 | _logger.Error(ex.Message, ex); 76 | } 77 | 78 | HideBusy(); 79 | } 80 | 81 | protected void SetMvvmFocus(string id, params object[] args) 82 | { 83 | var focusService = TryResolveService(); 84 | if (focusService == null) 85 | return; 86 | focusService.SetFocus(id, args); 87 | } 88 | 89 | public override void Dispose() 90 | { 91 | base.Dispose(); 92 | if (TfsContextManager != null) 93 | { 94 | TfsContextManager.ContextChanged -= OnContextChanged; 95 | } 96 | } 97 | 98 | protected virtual void OnContextChanged(object sender, ContextChangedEventArgs e) 99 | { 100 | 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/AutoMerge/Behaviours/GridViewColumnResize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | 8 | namespace AutoMerge.Behaviours 9 | { 10 | /// 11 | /// Static class used to attach to wpf control 12 | /// 13 | public static class GridViewColumnResize 14 | { 15 | #region DependencyProperties 16 | 17 | public static readonly DependencyProperty WidthProperty = 18 | DependencyProperty.RegisterAttached("Width", typeof(string), typeof(GridViewColumnResize), 19 | new PropertyMetadata(OnSetWidthCallback)); 20 | 21 | public static readonly DependencyProperty GridViewColumnResizeBehaviorProperty = 22 | DependencyProperty.RegisterAttached("GridViewColumnResizeBehavior", 23 | typeof(GridViewColumnResizeBehavior), typeof(GridViewColumnResize), 24 | null); 25 | 26 | public static readonly DependencyProperty EnabledProperty = 27 | DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(GridViewColumnResize), 28 | new PropertyMetadata(OnSetEnabledCallback)); 29 | 30 | public static readonly DependencyProperty ListViewResizeBehaviorProperty = 31 | DependencyProperty.RegisterAttached("ListViewResizeBehaviorProperty", 32 | typeof(ListViewResizeBehavior), typeof(GridViewColumnResize), null); 33 | 34 | #endregion 35 | 36 | public static string GetWidth(DependencyObject obj) 37 | { 38 | return (string)obj.GetValue(WidthProperty); 39 | } 40 | 41 | public static void SetWidth(DependencyObject obj, string value) 42 | { 43 | obj.SetValue(WidthProperty, value); 44 | } 45 | 46 | public static bool GetEnabled(DependencyObject obj) 47 | { 48 | return (bool)obj.GetValue(EnabledProperty); 49 | } 50 | 51 | public static void SetEnabled(DependencyObject obj, bool value) 52 | { 53 | obj.SetValue(EnabledProperty, value); 54 | } 55 | 56 | #region CallBack 57 | 58 | private static void OnSetWidthCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) 59 | { 60 | var element = dependencyObject as GridViewColumn; 61 | if (element != null) 62 | { 63 | GridViewColumnResizeBehavior behavior = GetOrCreateBehavior(element); 64 | behavior.Width = e.NewValue as string; 65 | } 66 | else 67 | { 68 | Console.Error.WriteLine("Error: Expected type GridViewColumn but found " + 69 | dependencyObject.GetType().Name); 70 | } 71 | } 72 | 73 | private static void OnSetEnabledCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) 74 | { 75 | var element = dependencyObject as ListView; 76 | if (element != null) 77 | { 78 | ListViewResizeBehavior behavior = GetOrCreateBehavior(element); 79 | behavior.Enabled = (bool)e.NewValue; 80 | } 81 | else 82 | { 83 | Console.Error.WriteLine("Error: Expected type ListView but found " + dependencyObject.GetType().Name); 84 | } 85 | } 86 | 87 | 88 | private static ListViewResizeBehavior GetOrCreateBehavior(ListView element) 89 | { 90 | var behavior = element.GetValue(GridViewColumnResizeBehaviorProperty) as ListViewResizeBehavior; 91 | if (behavior == null) 92 | { 93 | behavior = new ListViewResizeBehavior(element); 94 | element.SetValue(ListViewResizeBehaviorProperty, behavior); 95 | } 96 | 97 | return behavior; 98 | } 99 | 100 | private static GridViewColumnResizeBehavior GetOrCreateBehavior(GridViewColumn element) 101 | { 102 | var behavior = element.GetValue(GridViewColumnResizeBehaviorProperty) as GridViewColumnResizeBehavior; 103 | if (behavior == null) 104 | { 105 | behavior = new GridViewColumnResizeBehavior(element); 106 | element.SetValue(GridViewColumnResizeBehaviorProperty, behavior); 107 | } 108 | 109 | return behavior; 110 | } 111 | 112 | #endregion 113 | 114 | #region Nested type: GridViewColumnResizeBehavior 115 | 116 | /// 117 | /// GridViewColumn class that gets attached to the GridViewColumn control 118 | /// 119 | public class GridViewColumnResizeBehavior 120 | { 121 | private readonly GridViewColumn _element; 122 | 123 | public GridViewColumnResizeBehavior(GridViewColumn element) 124 | { 125 | _element = element; 126 | } 127 | 128 | public string Width { get; set; } 129 | 130 | public bool IsStatic 131 | { 132 | get { return StaticWidth >= 0; } 133 | } 134 | 135 | public double StaticWidth 136 | { 137 | get 138 | { 139 | double result; 140 | return double.TryParse(Width, out result) ? result : -1; 141 | } 142 | } 143 | 144 | public double Percentage 145 | { 146 | get 147 | { 148 | if (!IsStatic) 149 | { 150 | return Mulitplier * 100; 151 | } 152 | return 0; 153 | } 154 | } 155 | 156 | public double Mulitplier 157 | { 158 | get 159 | { 160 | if (Width == "*" || Width == "1*") return 1; 161 | if (Width.EndsWith("*")) 162 | { 163 | double perc; 164 | if (double.TryParse(Width.Substring(0, Width.Length - 1), out perc)) 165 | { 166 | return perc; 167 | } 168 | } 169 | return 1; 170 | } 171 | } 172 | 173 | public void SetWidth(double allowedSpace, double totalPercentage) 174 | { 175 | if (IsStatic) 176 | { 177 | _element.Width = StaticWidth; 178 | } 179 | else 180 | { 181 | double width = allowedSpace * (Percentage / totalPercentage); 182 | _element.Width = width; 183 | } 184 | } 185 | } 186 | 187 | #endregion 188 | 189 | #region Nested type: ListViewResizeBehavior 190 | 191 | /// 192 | /// ListViewResizeBehavior class that gets attached to the ListView control 193 | /// 194 | public sealed class ListViewResizeBehavior : IDisposable 195 | { 196 | private bool _disposed; 197 | private const int Margin = 25; 198 | private const long RefreshTime = Timeout.Infinite; 199 | private const long Delay = 500; 200 | 201 | private readonly ListView _element; 202 | private readonly Timer _timer; 203 | 204 | public ListViewResizeBehavior(ListView element) 205 | { 206 | if (element == null) throw new ArgumentNullException("element"); 207 | _element = element; 208 | element.Loaded += OnLoaded; 209 | 210 | // Action for resizing and re-enable the size lookup 211 | // This stops the columns from constantly resizing to improve performance 212 | Action resizeAndEnableSize = () => 213 | { 214 | Resize(); 215 | _element.SizeChanged += OnSizeChanged; 216 | }; 217 | _timer = new Timer(x => Application.Current.Dispatcher.BeginInvoke(resizeAndEnableSize), null, Delay, 218 | RefreshTime); 219 | } 220 | 221 | public bool Enabled { get; set; } 222 | 223 | 224 | private void OnLoaded(object sender, RoutedEventArgs e) 225 | { 226 | _element.SizeChanged += OnSizeChanged; 227 | } 228 | 229 | private void OnSizeChanged(object sender, SizeChangedEventArgs e) 230 | { 231 | if (e.WidthChanged) 232 | { 233 | _element.SizeChanged -= OnSizeChanged; 234 | _timer.Change(Delay, RefreshTime); 235 | } 236 | } 237 | 238 | private void Resize() 239 | { 240 | if (Enabled) 241 | { 242 | double totalWidth = _element.ActualWidth; 243 | var gv = _element.View as GridView; 244 | if (gv != null) 245 | { 246 | double allowedSpace = totalWidth - GetAllocatedSpace(gv); 247 | allowedSpace = allowedSpace - Margin; 248 | double totalPercentage = GridViewColumnResizeBehaviors(gv).Sum(x => x.Percentage); 249 | foreach (GridViewColumnResizeBehavior behavior in GridViewColumnResizeBehaviors(gv)) 250 | { 251 | behavior.SetWidth(allowedSpace, totalPercentage); 252 | } 253 | } 254 | } 255 | } 256 | 257 | private static IEnumerable GridViewColumnResizeBehaviors(GridView gv) 258 | { 259 | foreach (GridViewColumn t in gv.Columns) 260 | { 261 | var gridViewColumnResizeBehavior = 262 | t.GetValue(GridViewColumnResizeBehaviorProperty) as GridViewColumnResizeBehavior; 263 | if (gridViewColumnResizeBehavior != null) 264 | { 265 | yield return gridViewColumnResizeBehavior; 266 | } 267 | } 268 | } 269 | 270 | private static double GetAllocatedSpace(GridView gv) 271 | { 272 | double totalWidth = 0; 273 | foreach (GridViewColumn t in gv.Columns) 274 | { 275 | var gridViewColumnResizeBehavior = 276 | t.GetValue(GridViewColumnResizeBehaviorProperty) as GridViewColumnResizeBehavior; 277 | if (gridViewColumnResizeBehavior != null) 278 | { 279 | if (gridViewColumnResizeBehavior.IsStatic) 280 | { 281 | totalWidth += gridViewColumnResizeBehavior.StaticWidth; 282 | } 283 | } 284 | else 285 | { 286 | totalWidth += t.ActualWidth; 287 | } 288 | } 289 | return totalWidth; 290 | } 291 | 292 | public void Dispose() 293 | { 294 | if (!_disposed) 295 | { 296 | _timer.Dispose(); 297 | } 298 | 299 | _disposed = true; 300 | } 301 | } 302 | 303 | #endregion 304 | } 305 | } -------------------------------------------------------------------------------- /src/AutoMerge/Behaviours/ScrollDeligateBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Input; 3 | using System.Windows.Interactivity; 4 | 5 | 6 | namespace AutoMerge.Behaviours 7 | { 8 | public class ScrollDeligateBehavior : Behavior 9 | { 10 | protected override void OnAttached() 11 | { 12 | base.OnAttached(); 13 | AssociatedObject.PreviewMouseWheel += ScrollDeligatePreviewMouseWheel; 14 | } 15 | 16 | protected override void OnDetaching() 17 | { 18 | AssociatedObject.PreviewMouseWheel -= ScrollDeligatePreviewMouseWheel; 19 | base.OnDetaching(); 20 | } 21 | 22 | private void ScrollDeligatePreviewMouseWheel(object sender, MouseWheelEventArgs e) 23 | { 24 | if (!e.Handled) 25 | { 26 | var sourceElement = sender as FrameworkElement; 27 | if (sourceElement != null) 28 | { 29 | var uIElement = sourceElement.Parent as UIElement; 30 | if (uIElement != null) 31 | { 32 | e.Handled = true; 33 | uIElement.RaiseEvent(new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) 34 | { 35 | RoutedEvent = UIElement.MouseWheelEvent, 36 | Source = this 37 | }); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/AutoMerge/Branches/BranchFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AutoMerge.Prism.Events; 3 | using Microsoft.TeamFoundation.VersionControl.Client; 4 | 5 | namespace AutoMerge 6 | { 7 | public class BranchFactory 8 | { 9 | private readonly string _sourceBranch; 10 | private readonly string _sourceFolder; 11 | private readonly ChangesetVersionSpec _changesetVersion; 12 | private readonly BranchValidator _branchValidator; 13 | private readonly IEventAggregator _eventAggregator; 14 | 15 | public BranchFactory(string sourceBranch, 16 | string sourceFolder, 17 | ChangesetVersionSpec changesetVersion, 18 | BranchValidator branchValidator, 19 | IEventAggregator eventAggregator) 20 | { 21 | _sourceBranch = sourceBranch; 22 | _sourceFolder = sourceFolder; 23 | _changesetVersion = changesetVersion; 24 | _branchValidator = branchValidator; 25 | _eventAggregator = eventAggregator; 26 | } 27 | 28 | public MergeInfoViewModel CreateTargetBranchInfo(ItemIdentifier targetBranch, ItemIdentifier targetPath) 29 | { 30 | return CreateBranch(targetBranch.Item, targetPath.Item); 31 | } 32 | 33 | public MergeInfoViewModel CreateSourceBranch() 34 | { 35 | return CreateBranch(_sourceBranch, _sourceFolder); 36 | } 37 | 38 | private MergeInfoViewModel CreateBranch(string targetBranch, string targetPath) 39 | { 40 | var mergeInfo = new MergeInfoViewModel(_eventAggregator) 41 | { 42 | SourceBranch = _sourceBranch, 43 | TargetBranch = targetBranch, 44 | SourcePath = _sourceFolder, 45 | TargetPath = targetPath, 46 | ChangesetVersionSpec = _changesetVersion, 47 | ValidationResult = BranchValidationResult.Success, 48 | }; 49 | 50 | if (_sourceBranch != targetBranch) 51 | { 52 | mergeInfo = _branchValidator.Validate(mergeInfo); 53 | } 54 | 55 | return mergeInfo; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/BranchValidationResult.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMerge 2 | { 3 | public enum BranchValidationResult 4 | { 5 | Undefined, 6 | 7 | Success, 8 | 9 | BranchNotMapped, 10 | 11 | ItemHasLocalChanges, 12 | 13 | AlreadyMerged, 14 | 15 | NoAccess 16 | } 17 | } -------------------------------------------------------------------------------- /src/AutoMerge/Branches/BranchValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.TeamFoundation.VersionControl.Client; 4 | 5 | namespace AutoMerge 6 | { 7 | public class BranchValidator 8 | { 9 | private readonly Workspace _workspace; 10 | private readonly IEnumerable _trackMerges; 11 | 12 | public BranchValidator(Workspace workspace, IEnumerable trackMerges) 13 | { 14 | _workspace = workspace; 15 | _trackMerges = trackMerges; 16 | } 17 | 18 | public MergeInfoViewModel Validate(MergeInfoViewModel branchInfo) 19 | { 20 | branchInfo.ValidationResult = ValidateItem(_workspace, branchInfo, _trackMerges); 21 | branchInfo.ValidationMessage = ToMessage(branchInfo.ValidationResult); 22 | 23 | return branchInfo; 24 | } 25 | 26 | private static BranchValidationResult ValidateItem(Workspace workspace, MergeInfoViewModel mergeInfoViewModel, IEnumerable trackMerges) 27 | { 28 | var result = BranchValidationResult.Success; 29 | 30 | if (result == BranchValidationResult.Success) 31 | { 32 | var isMerged = IsMerged(mergeInfoViewModel.SourcePath, mergeInfoViewModel.TargetPath, trackMerges); 33 | if (isMerged) 34 | result = BranchValidationResult.AlreadyMerged; 35 | } 36 | 37 | if (result == BranchValidationResult.Success) 38 | { 39 | var userHasAccess = UserHasAccess(workspace.VersionControlServer, mergeInfoViewModel.TargetPath); 40 | if (!userHasAccess) 41 | result = BranchValidationResult.NoAccess; 42 | } 43 | 44 | if (result == BranchValidationResult.Success) 45 | { 46 | var isMapped = IsMapped(workspace, mergeInfoViewModel.TargetPath); 47 | if (!isMapped) 48 | result = BranchValidationResult.BranchNotMapped; 49 | } 50 | 51 | if (result == BranchValidationResult.Success) 52 | { 53 | var hasLocalChanges = HasLocalChanges(workspace, mergeInfoViewModel.TargetPath); 54 | if (hasLocalChanges) 55 | result = BranchValidationResult.ItemHasLocalChanges; 56 | } 57 | return result; 58 | } 59 | 60 | private static bool IsMerged(string sourcePath, string targetPath, IEnumerable trackMerges) 61 | { 62 | if (trackMerges == null) 63 | return false; 64 | 65 | return trackMerges.Any(m => (m.TargetItem.Item == sourcePath && m.SourceItem.Item.ServerItem == targetPath) 66 | || (m.TargetItem.Item == targetPath && m.SourceItem.Item.ServerItem == sourcePath)); 67 | } 68 | 69 | private static bool UserHasAccess(VersionControlServer versionControlServer, string targetPath) 70 | { 71 | var permissions = versionControlServer.GetEffectivePermissions(versionControlServer.AuthorizedUser, targetPath); 72 | 73 | if (permissions == null || permissions.Length < 4) 74 | return false; 75 | 76 | return permissions.Contains("Read") 77 | && permissions.Contains("PendChange") 78 | && permissions.Contains("Checkin") 79 | && permissions.Contains("Merge"); 80 | } 81 | 82 | private static bool IsMapped(Workspace workspace, string targetItem) 83 | { 84 | return workspace.IsServerPathMapped(targetItem); 85 | } 86 | 87 | private static bool HasLocalChanges(Workspace workspace, string targetPath) 88 | { 89 | return workspace.GetPendingChangesEnumerable(targetPath, RecursionType.Full).Any(); 90 | } 91 | 92 | private static string ToMessage(BranchValidationResult validationResult) 93 | { 94 | switch (validationResult) 95 | { 96 | case BranchValidationResult.Success: 97 | return null; 98 | case BranchValidationResult.AlreadyMerged: 99 | return "Already merged"; 100 | case BranchValidationResult.BranchNotMapped: 101 | return "Branch not mapped"; 102 | case BranchValidationResult.ItemHasLocalChanges: 103 | return "Folder has local changes. Check-in or undo it"; 104 | case BranchValidationResult.NoAccess: 105 | return "You have not rights for edit"; 106 | default: 107 | return "Unknown error"; 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/BranchesSection.cs: -------------------------------------------------------------------------------- 1 | using AutoMerge.Base; 2 | using Microsoft.TeamFoundation.Controls; 3 | 4 | namespace AutoMerge 5 | { 6 | [TeamExplorerSection(GuidList.BranchesSectionId, GuidList.AutoMergePageId, 20)] 7 | public class BranchesSection : TeamExplorerSectionBase 8 | { 9 | protected override object CreateView(SectionInitializeEventArgs e) 10 | { 11 | return new BranchesView(); 12 | } 13 | 14 | protected override ITeamExplorerSection CreateViewModel(SectionInitializeEventArgs e) 15 | { 16 | var viewModel = base.CreateViewModel(e) ?? new BranchesViewModel(new VsLogger(ServiceProvider)); 17 | 18 | return viewModel; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/BranchesView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace AutoMerge 4 | { 5 | /// 6 | /// Interaction logic for BranchesView.xaml 7 | /// 8 | public partial class BranchesView : UserControl 9 | { 10 | public BranchesView() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/BranchesViewModelContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using Microsoft.TeamFoundation.VersionControl.Client; 3 | 4 | namespace AutoMerge 5 | { 6 | public class BranchesViewModelContext 7 | { 8 | public ObservableCollection Branches { get; set; } 9 | 10 | public MergeInfoViewModel SelectedBranch { get; set; } 11 | 12 | public MergeOption MergeOption { get; set; } 13 | 14 | public string ErrorMessage { get; set; } 15 | 16 | public Workspace Workspace { get; set; } 17 | 18 | public ObservableCollection Workspaces { get; set; } 19 | 20 | public bool ShowWorkspaceChooser { get; set; } 21 | 22 | public MergeMode MergeMode { get; set; } 23 | 24 | public ObservableCollection MergeModes { get; set; } 25 | 26 | public ChangesetViewModel Changeset { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/CheckInResult.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMerge 2 | { 3 | public class CheckInResult 4 | { 5 | public MergeResult CheckinResult { get; set; } 6 | 7 | public int? ChangesetId { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/AutoMerge/Branches/EscapeMnemonicConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Data; 3 | 4 | namespace AutoMerge 5 | { 6 | /// 7 | /// Escape the label mnemonics such as underscore 8 | /// 9 | public class EscapeMnemonicConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, 12 | System.Globalization.CultureInfo culture) 13 | { 14 | var label = (value is string) ? (string)value : String.Empty; 15 | return label.Replace("_", "__"); 16 | } 17 | 18 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 19 | { 20 | return string.Empty; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/AutoMerge/Branches/FileMergeInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.VersionControl.Client; 2 | 3 | namespace AutoMerge 4 | { 5 | public class FileMergeInfo 6 | { 7 | public string SourceFile { get; set; } 8 | 9 | public string TargetFile { get; set; } 10 | 11 | public ChangesetVersionSpec ChangesetVersionSpec { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/AutoMerge/Branches/MergeInfoViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AutoMerge.Events; 3 | using AutoMerge.Prism.Events; 4 | using Microsoft.TeamFoundation.VersionControl.Client; 5 | 6 | namespace AutoMerge 7 | { 8 | public class MergeInfoViewModel 9 | { 10 | private readonly IEventAggregator _eventAggregator; 11 | internal bool _checked; 12 | 13 | public MergeInfoViewModel(IEventAggregator eventAggregator) 14 | { 15 | _eventAggregator = eventAggregator; 16 | } 17 | 18 | public bool Checked 19 | { 20 | get 21 | { 22 | return _checked; 23 | } 24 | set 25 | { 26 | _checked = value; 27 | _eventAggregator.GetEvent().Publish(this); 28 | } 29 | } 30 | 31 | public string SourcePath { get; set; } 32 | 33 | public string TargetPath { get; set; } 34 | 35 | public string SourceBranch { get; set; } 36 | 37 | public string TargetBranch { get; set; } 38 | 39 | public ChangesetVersionSpec ChangesetVersionSpec { get; set; } 40 | 41 | 42 | public string DisplayBranchName 43 | { 44 | get 45 | { 46 | return BranchHelper.GetShortBranchName(TargetBranch); 47 | } 48 | } 49 | 50 | public BranchValidationResult ValidationResult { get; set; } 51 | 52 | public string ValidationMessage { get; set; } 53 | 54 | public bool IsSourceBranch 55 | { 56 | get 57 | { 58 | return string.Equals(SourceBranch, TargetBranch, StringComparison.OrdinalIgnoreCase); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/MergeMode.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMerge 2 | { 3 | public enum MergeMode 4 | { 5 | Merge, 6 | 7 | MergeAndCheckIn 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/MergeModeToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace AutoMerge 6 | { 7 | public class MergeModeToStringConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | if (value == null) 12 | return null; 13 | var mergeMode = (MergeMode)value; 14 | switch (mergeMode) 15 | { 16 | case MergeMode.Merge: 17 | return "Merge"; 18 | case MergeMode.MergeAndCheckIn: 19 | return "Merge & Check In"; 20 | default: 21 | return "Unknown mode"; 22 | } 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/MergeOption.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMerge 2 | { 3 | public enum MergeOption 4 | { 5 | ManualResolveConflict, 6 | KeepTarget, 7 | OverwriteTarget 8 | } 9 | } -------------------------------------------------------------------------------- /src/AutoMerge/Branches/MergeOptionToCheckedConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace AutoMerge 6 | { 7 | public class MergeOptionToCheckedConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | return (MergeOption) value == (MergeOption) parameter; 12 | } 13 | 14 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | return parameter; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/AutoMerge/Branches/MergeOptionToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace AutoMerge 6 | { 7 | public class MergeOptionToStringConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | if (value != null) 12 | { 13 | var mergeOption = (MergeOption)value; 14 | switch (mergeOption) 15 | { 16 | case MergeOption.KeepTarget: 17 | return "Keep target (discard)"; 18 | case MergeOption.ManualResolveConflict: 19 | return "Manual"; 20 | case MergeOption.OverwriteTarget: 21 | return "Overwrite target"; 22 | } 23 | } 24 | 25 | return "Unknown mode"; 26 | } 27 | 28 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 29 | { 30 | if (value != null) 31 | { 32 | var mergeOptionDisplay = (string) value; 33 | if (mergeOptionDisplay == "Keep target (discard)") 34 | return MergeOption.KeepTarget; 35 | 36 | if (mergeOptionDisplay == "Manual") 37 | return MergeOption.ManualResolveConflict; 38 | 39 | if (mergeOptionDisplay == "Overwrite target") 40 | return MergeOption.OverwriteTarget; 41 | 42 | } 43 | 44 | return MergeOption.ManualResolveConflict; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/MergeRelation.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.VersionControl.Client; 2 | 3 | namespace AutoMerge 4 | { 5 | public class MergeRelation 6 | { 7 | public string Item { get; set; } 8 | 9 | public string Source { get; set; } 10 | 11 | public string Target { get; set; } 12 | 13 | public ItemType TargetItemType { get; set; } 14 | 15 | public string GetLatesPath { get; set; } 16 | 17 | public bool Recursively { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/MergeResult.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMerge 2 | { 3 | public enum MergeResult 4 | { 5 | CheckIn, 6 | Merged, 7 | NothingMerge, 8 | CheckInFail, 9 | CheckInEvaluateFail, 10 | HasConflicts, 11 | CanNotGetLatest, 12 | HasLocalChanges, 13 | UnexpectedFileRestored, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/MergeResultModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.TeamFoundation.VersionControl.Client; 3 | 4 | namespace AutoMerge 5 | { 6 | public class MergeResultModel 7 | { 8 | public MergeResult MergeResult { get; set; } 9 | 10 | public List PendingChanges { get; set; } 11 | 12 | public MergeInfoViewModel BranchInfo { get; set; } 13 | 14 | public string Comment { get; set; } 15 | 16 | public List WorkItemIds { get; set; } 17 | 18 | public int SourceChangesetId { get; set; } 19 | 20 | public int? TagetChangesetId { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/Notification.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | using Microsoft.TeamFoundation.Controls; 3 | 4 | namespace AutoMerge 5 | { 6 | internal class Notification 7 | { 8 | public string Message { get; set; } 9 | 10 | public NotificationType NotificationType { get; set; } 11 | 12 | public ICommand Command { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AutoMerge/Branches/TrackMergeInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace AutoMerge 4 | { 5 | public class TrackMergeInfo 6 | { 7 | public string OriginaBranch { get; set; } 8 | 9 | public string OriginalComment { get; set; } 10 | 11 | public List FromOriginalToSourceBranches { get; set; } 12 | 13 | public string SourceBranch { get; set; } 14 | 15 | public string SourceComment { get; set; } 16 | 17 | public long SourceChangesetId { get; set; } 18 | 19 | public List SourceWorkItemIds { get; set; } 20 | 21 | public List SourceWorkItemTitles { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/AutoMerge/Commands/ShowAutoMergeWindow.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.Design; 2 | using Microsoft.TeamFoundation.Common.Internal; 3 | using Microsoft.TeamFoundation.Controls; 4 | using Microsoft.VisualStudio.Shell; 5 | using Task = System.Threading.Tasks.Task; 6 | 7 | namespace AutoMerge.Commands 8 | { 9 | internal sealed class ShowAutoMergeWindow 10 | { 11 | public static async Task InitializeAsync(AsyncPackage package) 12 | { 13 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 14 | 15 | var commandService = await package.GetServiceAsync((typeof(IMenuCommandService))) as OleMenuCommandService; 16 | 17 | // must match the button GUID and ID specified in the .vsct file 18 | var cmdId = new CommandID(GuidList.ShowAutoMergeCmdSet, 0x0100); 19 | var cmd = new MenuCommand((s, e) => Execute(package), cmdId); 20 | commandService.AddCommand(cmd); 21 | } 22 | 23 | private static void Execute(AsyncPackage package) 24 | { 25 | ThreadHelper.ThrowIfNotOnUIThread(); 26 | 27 | var teamExplorer = package.GetService(); 28 | teamExplorer.NavigateToPage(GuidList.AutoMergePageGuid, null); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/AutoMerge/CommentFormater.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | using System.Linq; 4 | 5 | namespace AutoMerge 6 | { 7 | public class CommentFormater 8 | { 9 | private readonly CommentFormat _format; 10 | 11 | public CommentFormater(CommentFormat format) 12 | { 13 | _format = format; 14 | } 15 | 16 | public string Format(TrackMergeInfo trackMergeInfo, string targetBranch, MergeOption mergeOption) 17 | { 18 | var comment = mergeOption == MergeOption.KeepTarget ? _format.DiscardFormat : _format.Format; 19 | comment = comment 20 | .Replace("{OriginalBranch}", BranchHelper.GetShortBranchName(trackMergeInfo.OriginaBranch)) 21 | .Replace("{OriginalBranchFull}", trackMergeInfo.OriginaBranch) 22 | .Replace("{SourceBranch}", BranchHelper.GetShortBranchName(trackMergeInfo.SourceBranch)) 23 | .Replace("{SourceBranchFull}", trackMergeInfo.SourceBranch) 24 | .Replace("{TargetBranch}", BranchHelper.GetShortBranchName(targetBranch)) 25 | .Replace("{TargetBranchFull}", targetBranch) 26 | .Replace("{FromOriginalToTarget}", FromOriginalToTarget(trackMergeInfo, targetBranch)) 27 | .Replace("{FromOriginalToTargetFull}", FromOriginalToTargetFull(trackMergeInfo, targetBranch)) 28 | .Replace("{OriginalComment}", trackMergeInfo.OriginalComment) 29 | .Replace("{SourceComment}", trackMergeInfo.SourceComment) 30 | .Replace("{SourceChangesetId}", trackMergeInfo.SourceChangesetId.ToString(CultureInfo.InvariantCulture)) 31 | .Replace("{SourceWorkItemIds}", GetWorkItemIds(trackMergeInfo.SourceWorkItemIds)) 32 | .Replace("{SourceWorkItemTitles}", GetWorkItemTitles(trackMergeInfo.SourceWorkItemTitles)); 33 | 34 | return comment; 35 | } 36 | 37 | private static string GetWorkItemIds(List sourceWorkItemIds) 38 | { 39 | return string.Join(", ", sourceWorkItemIds.Select(id => id.ToString(CultureInfo.InvariantCulture))); 40 | } 41 | 42 | private static string GetWorkItemTitles(List sourceWorkItemTitles) 43 | { 44 | return string.Join("; ", sourceWorkItemTitles); 45 | } 46 | 47 | private string FromOriginalToTarget(TrackMergeInfo trackMergeInfo, string targetBranch) 48 | { 49 | var mergePath = trackMergeInfo.FromOriginalToSourceBranches.Concat(new[] { trackMergeInfo.SourceBranch, targetBranch }) 50 | .Select(fullBranchName => BranchHelper.GetShortBranchName(fullBranchName)); 51 | var mergePathString = string.Join(_format.BranchDelimiter, mergePath); 52 | return mergePathString; 53 | } 54 | 55 | private string FromOriginalToTargetFull(TrackMergeInfo trackMergeInfo, string targetBranch) 56 | { 57 | var mergePath = trackMergeInfo.FromOriginalToSourceBranches.Concat(new[] { trackMergeInfo.SourceBranch, targetBranch }); 58 | var mergePathString = string.Join(_format.BranchDelimiter, mergePath); 59 | return mergePathString; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/AutoMerge/Configuration/CommentFormat.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMerge 2 | { 3 | public class CommentFormat 4 | { 5 | public string Format { get; set; } 6 | 7 | public string DiscardFormat { get; set; } 8 | 9 | public string BranchDelimiter { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AutoMerge/Configuration/FileSettingProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace AutoMerge 5 | { 6 | internal class FileSettingProvider : ISettingProvider 7 | { 8 | public T ReadValue(string key) 9 | { 10 | if (key == null) 11 | throw new ArgumentNullException("key"); 12 | 13 | T value; 14 | if (!TryReadValue(key, out value)) 15 | throw new InvalidOperationException(string.Format("Setting {0} not found", key)); 16 | 17 | return value; 18 | } 19 | 20 | public bool TryReadValue(string key, out T value) 21 | { 22 | if (key == null) 23 | throw new ArgumentNullException("key"); 24 | 25 | value = default(T); 26 | var path = GetSettingFilePath(); 27 | var settingJson = File.ReadAllText(path); 28 | var settings = JsonParser.ParseJson(settingJson); 29 | 30 | string stringValue; 31 | if (!settings.TryGetValue(key, out stringValue)) 32 | return false; 33 | 34 | value = (T) Convert.ChangeType(stringValue, typeof(T)); 35 | return true; 36 | } 37 | 38 | public void WriteValue(string key, T value) 39 | { 40 | if (key == null) 41 | throw new ArgumentNullException("key"); 42 | 43 | var path = GetSettingFilePath(); 44 | var settingJson = File.ReadAllText(path); 45 | var settings = JsonParser.ParseJson(settingJson); 46 | 47 | settings[key] = value.ToString(); 48 | 49 | settingJson = JsonParser.ToJson(settings); 50 | File.WriteAllText(path, settingJson); 51 | } 52 | 53 | private static string GetSettingFilePath() 54 | { 55 | var roamingPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 56 | var autoMergeFolder = Path.Combine(roamingPath, "Visual Studio Auto Merge"); 57 | if (!Directory.Exists(autoMergeFolder)) 58 | { 59 | Directory.CreateDirectory(autoMergeFolder); 60 | } 61 | var settingFilePath = Path.Combine(autoMergeFolder, "automerge.conf"); 62 | if (!File.Exists(settingFilePath)) 63 | { 64 | using (File.Create(settingFilePath)) 65 | { 66 | } 67 | } 68 | return settingFilePath; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/AutoMerge/Configuration/ISettingProvider.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMerge 2 | { 3 | internal interface ISettingProvider 4 | { 5 | T ReadValue(string key); 6 | bool TryReadValue(string key, out T value); 7 | void WriteValue(string key, T value); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/AutoMerge/Configuration/JsonParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web.Script.Serialization; 5 | 6 | namespace AutoMerge 7 | { 8 | public static class JsonParser 9 | { 10 | public static Dictionary ParseJson(string jsonText) 11 | { 12 | var jss = new JavaScriptSerializer(); 13 | return jss.Deserialize>(jsonText) ?? new Dictionary(); 14 | } 15 | 16 | public static string ToJson(Dictionary dict) 17 | { 18 | var entries = dict.Select(d => 19 | string.Format("\"{0}\": \"{1}\"", d.Key, d.Value)); 20 | return "{" + Environment.NewLine + " " + string.Join(",\r\n ", entries) + Environment.NewLine + "}"; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/AutoMerge/Configuration/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace AutoMerge 5 | { 6 | internal class Settings 7 | { 8 | private readonly ISettingProvider _settingProvider; 9 | private static readonly Lazy _instance; 10 | 11 | private MergeMode? _lastMergeOperation; 12 | 13 | private const string lastMergeOperationKey = "last_operation"; 14 | private const string mergeModeMerge = "merge"; 15 | private const string mergeModeMergeAndCheckin = "merge_checkin"; 16 | 17 | private const string mergeOperationDefaultKey = "merge_operation_default"; 18 | private const string mergeOperationDefaultLast = "last"; 19 | private const string mergeOperationDefaultMerge = mergeModeMerge; 20 | private const string mergeOperationDefaultMergeCheckin = mergeModeMergeAndCheckin; 21 | private readonly string[] _mergeOperationDefaultValues; 22 | 23 | private const string commentFormatKey = "comment_format"; 24 | private const string commentFormatDefault = "MERGE {FromOriginalToTarget} ({OriginalComment})"; 25 | private const string commentFormatDiscardKey = "comment_format_discard"; 26 | private const string commentFormatDiscardDefault = "DISCARD {" + commentFormatKey + "}"; 27 | private const string branchDelimiterKey = "branch_delimiter"; 28 | private const string branchDelimiterDefault = " -> "; 29 | 30 | private const string changesetCountKey = "changeset_count_show"; 31 | private const int changesetCountDefault = 20; 32 | 33 | static Settings() 34 | { 35 | _instance = new Lazy(() => new Settings()); 36 | } 37 | 38 | private Settings() 39 | { 40 | _settingProvider = new FileSettingProvider(); 41 | _mergeOperationDefaultValues = new[] 42 | {mergeOperationDefaultLast, mergeOperationDefaultMerge, mergeOperationDefaultMergeCheckin}; 43 | } 44 | 45 | public static Settings Instance 46 | { 47 | get { return _instance.Value; } 48 | } 49 | 50 | public MergeMode LastMergeOperation { 51 | get 52 | { 53 | return LastMergeOperationGet(); 54 | } 55 | set 56 | { 57 | LastMergeOperationSet(value); 58 | } 59 | } 60 | 61 | public CommentFormat CommentFormat 62 | { 63 | get { return CommentFormatGet(); } 64 | } 65 | 66 | public int ChangesetCount 67 | { 68 | get { return ChangesetCountGet(); } 69 | } 70 | 71 | private CommentFormat CommentFormatGet() 72 | { 73 | string commentFormat; 74 | if (!_settingProvider.TryReadValue(commentFormatKey, out commentFormat)) 75 | { 76 | commentFormat = commentFormatDefault; 77 | } 78 | 79 | string commentFormatDiscard; 80 | if (!_settingProvider.TryReadValue(commentFormatDiscardKey, out commentFormatDiscard)) 81 | { 82 | commentFormatDiscard = commentFormatDiscardDefault; 83 | } 84 | 85 | commentFormatDiscard = commentFormatDiscard.Replace("{" + commentFormatKey + "}", commentFormat); 86 | 87 | string branchDelimiter; 88 | if (!_settingProvider.TryReadValue(branchDelimiterKey, out branchDelimiter)) 89 | { 90 | branchDelimiter = branchDelimiterDefault; 91 | } 92 | 93 | return new CommentFormat 94 | { 95 | Format = commentFormat, 96 | BranchDelimiter = branchDelimiter, 97 | DiscardFormat = commentFormatDiscard 98 | }; 99 | 100 | } 101 | 102 | private MergeMode LastMergeOperationGet() 103 | { 104 | MergeMode result; 105 | var mergeOperationDefaultValue = MergeOperationDefaultGet(); 106 | if (mergeOperationDefaultValue == mergeOperationDefaultLast) 107 | { 108 | if (!_lastMergeOperation.HasValue) 109 | { 110 | string stringValue; 111 | if (!_settingProvider.TryReadValue(lastMergeOperationKey, out stringValue)) 112 | { 113 | stringValue = mergeModeMergeAndCheckin; 114 | } 115 | 116 | _lastMergeOperation = ToMergeMode(stringValue); 117 | } 118 | 119 | result = _lastMergeOperation.Value; 120 | } 121 | else 122 | { 123 | result = ToMergeMode(mergeOperationDefaultValue); 124 | } 125 | 126 | return result; 127 | } 128 | 129 | private void LastMergeOperationSet(MergeMode mergeMode) 130 | { 131 | if (_lastMergeOperation != mergeMode) 132 | { 133 | var stringValue = ToString(mergeMode); 134 | _settingProvider.WriteValue(lastMergeOperationKey, stringValue); 135 | _lastMergeOperation = mergeMode; 136 | } 137 | } 138 | 139 | private static MergeMode ToMergeMode(string stringValue) 140 | { 141 | if (stringValue == mergeModeMergeAndCheckin) 142 | return MergeMode.MergeAndCheckIn; 143 | return MergeMode.Merge; 144 | } 145 | 146 | private static string ToString(MergeMode mergeMode) 147 | { 148 | switch (mergeMode) 149 | { 150 | case MergeMode.Merge: 151 | return mergeModeMerge; 152 | case MergeMode.MergeAndCheckIn: 153 | return mergeModeMergeAndCheckin; 154 | default: 155 | return "unknown"; 156 | } 157 | } 158 | 159 | private string MergeOperationDefaultGet() 160 | { 161 | string mergeOperationDefaultValue; 162 | if (!_settingProvider.TryReadValue(mergeOperationDefaultKey, out mergeOperationDefaultValue)) 163 | { 164 | mergeOperationDefaultValue = mergeOperationDefaultLast; 165 | _settingProvider.WriteValue(mergeOperationDefaultKey, mergeOperationDefaultValue); 166 | } 167 | 168 | if (!_mergeOperationDefaultValues.Contains(mergeOperationDefaultValue)) 169 | mergeOperationDefaultValue = "mergeOperationDefaultLast"; 170 | 171 | return mergeOperationDefaultValue; 172 | } 173 | 174 | private int ChangesetCountGet() 175 | { 176 | int changesetCount; 177 | if (!_settingProvider.TryReadValue(changesetCountKey, out changesetCount)) 178 | { 179 | changesetCount = changesetCountDefault; 180 | } 181 | 182 | return changesetCount; 183 | } 184 | } 185 | } 186 | 187 | -------------------------------------------------------------------------------- /src/AutoMerge/Controls/SplitButton.xaml: -------------------------------------------------------------------------------- 1 |  140 | -------------------------------------------------------------------------------- /src/AutoMerge/Controls/SplitButton.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Controls.Primitives; 5 | using System.Windows.Input; 6 | using Microsoft.TeamFoundation.Controls.WPF.TeamExplorer; 7 | 8 | namespace AutoMerge.Controls 9 | { 10 | /// 11 | /// Interaction logic for SplitButton.xaml 12 | /// 13 | public partial class SplitButton : Button 14 | { 15 | public static readonly DependencyProperty DropDownMenuCommandProperty; 16 | public static readonly DependencyProperty DropDownMenuProperty; 17 | public static readonly DependencyProperty DropDownMenuCommandParameterProperty; 18 | public static readonly DependencyProperty ShowArrowProperty; 19 | private bool _dropDownOpen; 20 | 21 | public bool ShowArrow 22 | { 23 | get 24 | { 25 | return (bool)GetValue(ShowArrowProperty); 26 | } 27 | set 28 | { 29 | SetValue(ShowArrowProperty, value); 30 | } 31 | } 32 | 33 | public ContextMenu DropDownMenu 34 | { 35 | get 36 | { 37 | return (ContextMenu)GetValue(DropDownMenuProperty); 38 | } 39 | set 40 | { 41 | SetValue(DropDownMenuProperty, value); 42 | } 43 | } 44 | 45 | public ICommand DropDownMenuCommand 46 | { 47 | get 48 | { 49 | return (ICommand)GetValue(DropDownMenuCommandProperty); 50 | } 51 | set 52 | { 53 | SetValue(DropDownMenuCommandProperty, value); 54 | } 55 | } 56 | 57 | public object DropDownMenuCommandParameter 58 | { 59 | get 60 | { 61 | return GetValue(DropDownMenuCommandParameterProperty); 62 | } 63 | set 64 | { 65 | SetValue(DropDownMenuCommandParameterProperty, value); 66 | } 67 | } 68 | 69 | static SplitButton() 70 | { 71 | DropDownMenuCommandProperty 72 | = DependencyProperty.Register("DropDownMenuCommand", typeof(ICommand), typeof(SplitButton), new FrameworkPropertyMetadata(null, OnDropDownMenuCommandChanged)); 73 | 74 | DropDownMenuProperty 75 | = DependencyProperty.Register("DropDownMenu", typeof(ContextMenu), typeof(SplitButton), new PropertyMetadata(OnDropDownMenuChanged)); 76 | 77 | DropDownMenuCommandParameterProperty 78 | = DependencyProperty.Register("DropDownMenuCommandParameter", typeof(object), typeof(SplitButton)); 79 | 80 | ShowArrowProperty 81 | = DependencyProperty.Register("ShowArrow", typeof(bool), typeof(SplitButton), new PropertyMetadata(true)); 82 | } 83 | 84 | public SplitButton() 85 | { 86 | InitializeComponent(); 87 | KeyDown += SplitButton_KeyDown; 88 | } 89 | 90 | private static void OnDropDownMenuChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 91 | { 92 | var splitButton = d as SplitButton; 93 | if (splitButton == null) 94 | return; 95 | 96 | var contextMenu1 = e.OldValue as ContextMenu; 97 | if (contextMenu1 != null) 98 | { 99 | contextMenu1.Opened -= splitButton.OnContextMenuOpened; 100 | contextMenu1.Closed -= splitButton.OnContextMenuClosed; 101 | } 102 | 103 | var contextMenu2 = e.NewValue as ContextMenu; 104 | if (contextMenu2 == null) 105 | return; 106 | contextMenu2.Opened += splitButton.OnContextMenuOpened; 107 | contextMenu2.Closed += splitButton.OnContextMenuClosed; 108 | } 109 | 110 | private static void OnDropDownMenuCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 111 | { 112 | var splitButton = d as SplitButton; 113 | if (splitButton == null) 114 | return; 115 | splitButton.OnDropDownMenuCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue); 116 | } 117 | 118 | private void OnDropDownMenuCommandChanged(ICommand oldCommand, ICommand newCommand) 119 | { 120 | if (oldCommand != null) 121 | oldCommand.CanExecuteChanged -= OnCanExecuteChanged; 122 | if (newCommand != null) 123 | newCommand.CanExecuteChanged += OnCanExecuteChanged; 124 | UpdateCanExecute(); 125 | } 126 | 127 | private void OnCanExecuteChanged(object sender, EventArgs e) 128 | { 129 | UpdateCanExecute(); 130 | } 131 | 132 | private void UpdateCanExecute() 133 | { 134 | IsEnabled = DropDownMenuCommand != null && DropDownMenuCommand.CanExecute(DropDownMenuCommandParameter); 135 | } 136 | 137 | private void SplitButton_KeyDown(object sender, KeyEventArgs e) 138 | { 139 | if (!e.Handled && e.Key == Key.Down) 140 | { 141 | e.Handled = true; 142 | ToggleDropDown(); 143 | } 144 | if ((e.Handled || e.Key != Key.Right) && e.Key != Key.Left) 145 | return; 146 | e.Handled = true; 147 | if (!IsFocused || e.Key != Key.Right) 148 | return; 149 | var uiElement = Template.FindName("SplitButtonDropDownButton", this) as UIElement; 150 | if (uiElement == null) 151 | return; 152 | uiElement.Focus(); 153 | } 154 | 155 | private void SplitButtonDropDownButton_KeyDown(object sender, KeyEventArgs e) 156 | { 157 | if (e.Handled) 158 | return; 159 | if (e.Key == Key.Return || e.Key == Key.Space || e.Key == Key.Down) 160 | { 161 | e.Handled = true; 162 | ToggleDropDown(); 163 | } 164 | else if (e.Key == Key.Left) 165 | { 166 | e.Handled = true; 167 | Focus(); 168 | } 169 | else 170 | { 171 | if (e.Key != Key.Right) 172 | return; 173 | e.Handled = true; 174 | } 175 | } 176 | 177 | private void SplitButtonDropDownButton_Click(object sender, RoutedEventArgs e) 178 | { 179 | if (e.Handled) 180 | return; 181 | e.Handled = true; 182 | ToggleDropDown(); 183 | } 184 | 185 | private void ToggleDropDown() 186 | { 187 | var dropDownMenuCommand = DropDownMenuCommand; 188 | if (dropDownMenuCommand != null) 189 | { 190 | if (!dropDownMenuCommand.CanExecute(null)) 191 | return; 192 | var point = PointToScreen(new Point(0.0, ActualHeight)); 193 | if (DropDownMenuCommandParameter != null) 194 | dropDownMenuCommand.Execute(new DropDownLink.DropDownMenuCommandParameters 195 | { 196 | Point = point, 197 | Parameter = DropDownMenuCommandParameter 198 | }); 199 | else 200 | dropDownMenuCommand.Execute(point); 201 | } 202 | else 203 | { 204 | if (DropDownMenu == null) 205 | return; 206 | if (_dropDownOpen) 207 | { 208 | DropDownMenu.IsOpen = false; 209 | } 210 | else 211 | { 212 | DropDownMenu.FontFamily = FontFamily; 213 | DropDownMenu.FontSize = FontSize; 214 | DropDownMenu.PlacementTarget = this; 215 | DropDownMenu.Placement = PlacementMode.Bottom; 216 | DropDownMenu.IsOpen = true; 217 | } 218 | } 219 | } 220 | 221 | private void OnContextMenuOpened(object sender, RoutedEventArgs e) 222 | { 223 | _dropDownOpen = true; 224 | } 225 | 226 | private void OnContextMenuClosed(object sender, RoutedEventArgs e) 227 | { 228 | _dropDownOpen = false; 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/AutoMerge/Events/BranchSelectedChangedEvent.cs: -------------------------------------------------------------------------------- 1 | using AutoMerge.Prism.Events; 2 | 3 | namespace AutoMerge.Events 4 | { 5 | public class BranchSelectedChangedEvent : PubSubEvent 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/AutoMerge/Events/EventAggregatorFactory.cs: -------------------------------------------------------------------------------- 1 | using AutoMerge.Prism.Events; 2 | 3 | namespace AutoMerge.Events 4 | { 5 | internal static class EventAggregatorFactory 6 | { 7 | private static readonly IEventAggregator _eventAggregator = new EventAggregator(); 8 | 9 | public static IEventAggregator Get() 10 | { 11 | return _eventAggregator; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AutoMerge/Events/MergeCompleteEvent.cs: -------------------------------------------------------------------------------- 1 | using AutoMerge.Prism.Events; 2 | 3 | namespace AutoMerge.Events 4 | { 5 | public class MergeCompleteEvent : PubSubEvent 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/AutoMerge/Events/SelectChangesetEvent.cs: -------------------------------------------------------------------------------- 1 | using AutoMerge.Prism.Events; 2 | 3 | namespace AutoMerge.Events 4 | { 5 | public class SelectChangesetEvent : PubSubEvent 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/AutoMerge/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. Project-level 3 | // suppressions either have no target or are given a specific target 4 | // and scoped to a namespace, type, member, etc. 5 | // 6 | // To add a suppression to this file, right-click the message in the 7 | // Error List, point to "Suppress Message(s)", and click "In Project 8 | // Suppression File". You do not need to add suppressions to this 9 | // file manually. 10 | 11 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1017:MarkAssembliesWithComVisible")] 12 | -------------------------------------------------------------------------------- /src/AutoMerge/Guids.cs: -------------------------------------------------------------------------------- 1 | // Guids.cs 2 | // MUST match guids.h 3 | using System; 4 | 5 | namespace AutoMerge 6 | { 7 | public static class GuidList 8 | { 9 | public const string guidAutoMergePkgString = "f05bac3e-6794-4a9e-9ee7-1b8a200778ee"; 10 | public const string guidAutoMergeCmdSetString = "550e8690-9fae-46d1-8ff7-d6d0edf9449c"; 11 | 12 | public static readonly Guid ShowAutoMergeCmdSet = new Guid(guidAutoMergeCmdSetString); 13 | 14 | public const string AutoMergeNavigationItemId = "02A9D8B3-287B-4C55-83E7-7BFDB435546D"; 15 | public const string AutoMergePageId = "3B582638-5F12-4715-8719-5E5777AB4581"; 16 | public static readonly Guid AutoMergePageGuid = new Guid(AutoMergePageId); 17 | public const string RecentChangesetsSectionId = "8DA59790-3996-465E-A13F-27D64B3C2A9D"; 18 | 19 | public const string BranchesSectionId = "36BF6F52-F4AC-44A0-9985-817B2A65B3B0"; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/AutoMerge/Helpers/BranchHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace AutoMerge 4 | { 5 | internal static class BranchHelper 6 | { 7 | public static string GetShortBranchName(string branchFullName) 8 | { 9 | var pos = branchFullName.LastIndexOf('/'); 10 | var name = branchFullName.Substring(pos + 1); 11 | return name; 12 | } 13 | 14 | public static string GetDisplayBranchName(List branches) 15 | { 16 | if (branches == null || branches.Count == 0) 17 | return string.Empty; 18 | 19 | if (branches.Count == 1) 20 | { 21 | return GetShortBranchName(branches[0]); 22 | } 23 | return "multi"; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/AutoMerge/Helpers/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace AutoMerge 5 | { 6 | internal static class EnumerableExtensions 7 | { 8 | public static bool IsNullOrEmpty(this IEnumerable items) 9 | { 10 | return items == null || !items.Any(); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/AutoMerge/Helpers/VersionControlNavigationHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AutoMerge.VersionControl; 3 | using Microsoft.TeamFoundation.Client; 4 | using Microsoft.TeamFoundation.Common.Internal; 5 | using Microsoft.TeamFoundation.VersionControl.Client; 6 | using Microsoft.VisualStudio.Shell; 7 | using Microsoft.VisualStudio.Shell.Interop; 8 | 9 | namespace AutoMerge 10 | { 11 | public class VersionControlNavigationHelper 12 | { 13 | private static readonly Guid TfsProviderGuid = new Guid("4CA58AB2-18FA-4F8D-95D4-32DDF27D184C"); 14 | private static readonly Guid GitProviderGuid = new Guid("11b8e6d7-c08b-4385-b321-321078cdd1f8"); 15 | 16 | static VersionControlNavigationHelper() 17 | { 18 | } 19 | 20 | public static bool IsProviderActive(IServiceProvider serviceProvider, VersionControlProvider provider) 21 | { 22 | if (serviceProvider != null) 23 | { 24 | var service = serviceProvider.GetService(); 25 | if (service != null) 26 | { 27 | // ISSUE: variable of a compiler-generated type 28 | var providerInterface = service as IVsGetScciProviderInterface; 29 | if (providerInterface != null) 30 | { 31 | Guid pguidSCCProvider; 32 | // ISSUE: reference to a compiler-generated method 33 | providerInterface.GetSourceControlProviderID(out pguidSCCProvider); 34 | if (pguidSCCProvider == GetProviderGuid(provider)) 35 | return true; 36 | if (Guid.Empty.Equals(pguidSCCProvider)) 37 | return provider == VersionControlProvider.TeamFoundation; 38 | return false; 39 | } 40 | } 41 | } 42 | return true; 43 | } 44 | 45 | public static UIContext GetProviderUIContext(VersionControlProvider provider) 46 | { 47 | return UIContext.FromUIContextGuid(GetProviderGuid(provider)); 48 | } 49 | 50 | public static bool IsConnectedToTfsCollectionAndProject(IServiceProvider provider) 51 | { 52 | var context = GetTeamFoundationContext(provider); 53 | if (context != null) 54 | { 55 | return context.HasCollection && context.HasTeamProject; 56 | } 57 | 58 | return false; 59 | } 60 | 61 | public static bool IsConnectedToTfsCollectionAndProject(ITeamFoundationContext context) 62 | { 63 | if (context != null) 64 | { 65 | return context.HasCollection && context.HasTeamProject; 66 | } 67 | 68 | return false; 69 | } 70 | 71 | private static Guid GetProviderGuid(VersionControlProvider provider) 72 | { 73 | switch (provider) 74 | { 75 | case VersionControlProvider.TeamFoundation: 76 | return TfsProviderGuid; 77 | case VersionControlProvider.Git: 78 | return GitProviderGuid; 79 | default: 80 | return Guid.Empty; 81 | } 82 | } 83 | 84 | public static ITeamFoundationContext GetTeamFoundationContext(IServiceProvider serviceProvider) 85 | { 86 | if (serviceProvider != null) 87 | { 88 | var tfContextManager = serviceProvider.GetService(); 89 | if (tfContextManager != null) 90 | { 91 | var context = tfContextManager.CurrentContext; 92 | return context; 93 | } 94 | } 95 | 96 | return null; 97 | } 98 | 99 | public static string GetAuthorizedUser(IServiceProvider serviceProvider) 100 | { 101 | var context = GetTeamFoundationContext(serviceProvider); 102 | if (context != null && IsConnectedToTfsCollectionAndProject(context)) 103 | { 104 | var vcs = context.TeamProjectCollection.GetService(); 105 | return vcs.AuthorizedUser; 106 | } 107 | 108 | return string.Empty; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/AutoMerge/Helpers/WorkspaceHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.TeamFoundation.Client; 5 | using Microsoft.TeamFoundation.VersionControl.Client; 6 | using Microsoft.TeamFoundation.VersionControl.Common; 7 | using Microsoft.Win32; 8 | 9 | namespace AutoMerge 10 | { 11 | public static class WorkspaceHelper 12 | { 13 | private const string VCRegistryRootDefault = "TeamFoundation\\SourceControl"; 14 | private const string GeneralLastWorkspaceString = "LastWorkspace"; 15 | 16 | public static Workspace GetWorkspace(VersionControlServer versionControlServer, 17 | IEnumerable userWorkspaces) 18 | { 19 | var workspace = GetLastPendingChangesWorkspace(versionControlServer); 20 | if (workspace != null) 21 | { 22 | return FindWorkspace(userWorkspaces, workspace); 23 | } 24 | workspace = GetLastSourceControlExplorerWorkspace(versionControlServer); 25 | if (workspace != null) 26 | { 27 | return FindWorkspace(userWorkspaces, workspace); 28 | } 29 | 30 | return userWorkspaces.FirstOrDefault(); 31 | } 32 | 33 | private static Workspace FindWorkspace(IEnumerable userWorkspaces, Workspace workspace) 34 | { 35 | return userWorkspaces.FirstOrDefault(w => w.QualifiedName == workspace.QualifiedName); 36 | } 37 | 38 | private static Workspace GetLastSourceControlExplorerWorkspace(VersionControlServer versionControlServer) 39 | { 40 | return GetLastWorkspaceForServer(versionControlServer, "Explorer"); 41 | } 42 | 43 | private static Workspace GetLastPendingChangesWorkspace(VersionControlServer versionControlServer) 44 | { 45 | return GetLastWorkspaceForServer(versionControlServer, "PendingCheckins"); 46 | } 47 | 48 | private static Workspace GetLastWorkspaceForServer(VersionControlServer versionControlServer, string viewName) 49 | { 50 | var workspace = (Workspace)null; 51 | try 52 | { 53 | var workspaceSpecForServer = GetLastWorkspaceSpecForServer(versionControlServer, viewName); 54 | if (!string.IsNullOrEmpty(workspaceSpecForServer)) 55 | { 56 | 57 | string workspaceName; 58 | string workspaceOwner; 59 | WorkspaceSpec.Parse(workspaceSpecForServer, versionControlServer.AuthorizedUser, out workspaceName, out workspaceOwner); 60 | var localWorkspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(versionControlServer, workspaceName, workspaceOwner); 61 | if (localWorkspaceInfo != null) 62 | workspace = localWorkspaceInfo.GetWorkspace(versionControlServer.TeamProjectCollection); 63 | } 64 | } 65 | catch (Exception) 66 | { 67 | } 68 | return workspace; 69 | } 70 | 71 | private static string GetLastWorkspaceSpecForServer(VersionControlServer vcServer, string viewName) 72 | { 73 | var str1 = (string)null; 74 | try 75 | { 76 | if (vcServer != null) 77 | { 78 | if (!string.IsNullOrEmpty(viewName)) 79 | { 80 | using (var featureServerKey = GetFeatureServerKey(viewName, vcServer.ServerGuid.ToString())) 81 | { 82 | if (featureServerKey != null) 83 | { 84 | var str2 = featureServerKey.GetValue(GeneralLastWorkspaceString) as string; 85 | if (!string.IsNullOrEmpty(str2)) 86 | str1 = str2; 87 | } 88 | } 89 | } 90 | } 91 | } 92 | catch (Exception) 93 | { 94 | } 95 | return str1; 96 | } 97 | 98 | private static RegistryKey GetFeatureServerKey(string featurePath, string serverGuid) 99 | { 100 | using (var registryKey = UIHost.UserRegistryRoot) 101 | { 102 | if (registryKey != null) 103 | { 104 | var subKey = string.Join("\\", VCRegistryRootDefault, featurePath, serverGuid); 105 | return registryKey.OpenSubKey(subKey, true); 106 | } 107 | } 108 | return null; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/AutoMerge/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/src/AutoMerge/Key.snk -------------------------------------------------------------------------------- /src/AutoMerge/Prism/BindableBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | using System.ComponentModel; 5 | using System.Linq.Expressions; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace AutoMerge.Prism 9 | { 10 | /// 11 | /// Implementation of to simplify models. 12 | /// 13 | public abstract class BindableBase : INotifyPropertyChanged 14 | { 15 | /// 16 | /// Occurs when a property value changes. 17 | /// 18 | public event PropertyChangedEventHandler PropertyChanged; 19 | 20 | /// 21 | /// Checks if a property already matches a desired value. Sets the property and 22 | /// notifies listeners only when necessary. 23 | /// 24 | /// Type of the property. 25 | /// Reference to a property with both getter and setter. 26 | /// Desired value for the property. 27 | /// Name of the property used to notify listeners. This 28 | /// value is optional and can be provided automatically when invoked from compilers that 29 | /// support CallerMemberName. 30 | /// True if the value was changed, false if the existing value matched the 31 | /// desired value. 32 | protected virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) 33 | { 34 | if (object.Equals(storage, value)) return false; 35 | 36 | storage = value; 37 | this.OnPropertyChanged(propertyName); 38 | 39 | return true; 40 | } 41 | 42 | /// 43 | /// Notifies listeners that a property value has changed. 44 | /// 45 | /// Name of the property used to notify listeners. This 46 | /// value is optional and can be provided automatically when invoked from compilers 47 | /// that support . 48 | protected void OnPropertyChanged(string propertyName) 49 | { 50 | var eventHandler = this.PropertyChanged; 51 | if (eventHandler != null) 52 | { 53 | eventHandler(this, new PropertyChangedEventArgs(propertyName)); 54 | } 55 | } 56 | 57 | /// 58 | /// Raises this object's PropertyChanged event. 59 | /// 60 | /// The type of the property that has a new value 61 | /// A Lambda expression representing the property that has a new value. 62 | protected void OnPropertyChanged(Expression> propertyExpression) 63 | { 64 | var propertyName = PropertySupport.ExtractPropertyName(propertyExpression); 65 | this.OnPropertyChanged(propertyName); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Command/DelegateCommandBase .cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using System.Windows.Input; 5 | 6 | namespace AutoMerge.Prism.Command 7 | { 8 | /// 9 | /// An whose delegates can be attached for and . 10 | /// 11 | public abstract class DelegateCommandBase : ICommand, IActiveAware 12 | { 13 | private bool _isActive; 14 | private List _canExecuteChangedHandlers; 15 | 16 | protected readonly Func _executeMethod; 17 | protected readonly Func _canExecuteMethod; 18 | 19 | /// 20 | /// Creates a new instance of a , specifying both the execute action and the can execute function. 21 | /// 22 | /// The to execute when is invoked. 23 | /// The to invoked when is invoked. 24 | protected DelegateCommandBase(Action executeMethod, Func canExecuteMethod) 25 | { 26 | if (executeMethod == null || canExecuteMethod == null) 27 | throw new ArgumentNullException("executeMethod"); 28 | 29 | _executeMethod = (arg) => { executeMethod(arg); return Task.Delay(0); }; 30 | _canExecuteMethod = canExecuteMethod; 31 | } 32 | 33 | /// 34 | /// Creates a new instance of a , specifying both the Execute action as an awaitable Task and the CanExecute function. 35 | /// 36 | /// The to execute when is invoked. 37 | /// The to invoked when is invoked. 38 | protected DelegateCommandBase(Func executeMethod, Func canExecuteMethod) 39 | { 40 | if (executeMethod == null || canExecuteMethod == null) 41 | throw new ArgumentNullException("executeMethod"); 42 | 43 | _executeMethod = executeMethod; 44 | _canExecuteMethod = canExecuteMethod; 45 | } 46 | 47 | /// 48 | /// Raises on the UI thread so every 49 | /// command invoker can requery . 50 | /// 51 | protected virtual void OnCanExecuteChanged() 52 | { 53 | WeakEventHandlerManager.CallWeakReferenceHandlers(this, _canExecuteChangedHandlers); 54 | } 55 | 56 | 57 | /// 58 | /// Raises on the UI thread so every command invoker 59 | /// can requery to check if the command can execute. 60 | /// Note that this will trigger the execution of once for each invoker. 61 | /// 62 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] 63 | public void RaiseCanExecuteChanged() 64 | { 65 | OnCanExecuteChanged(); 66 | } 67 | 68 | async void ICommand.Execute(object parameter) 69 | { 70 | await Execute(parameter); 71 | } 72 | 73 | bool ICommand.CanExecute(object parameter) 74 | { 75 | return CanExecute(parameter); 76 | } 77 | 78 | /// 79 | /// Executes the command with the provided parameter by invoking the supplied during construction. 80 | /// 81 | /// 82 | protected async Task Execute(object parameter) 83 | { 84 | await _executeMethod(parameter); 85 | } 86 | 87 | /// 88 | /// Determines if the command can execute with the provided parameter by invoking the supplied during construction. 89 | /// 90 | /// The parameter to use when determining if this command can execute. 91 | /// Returns if the command can execute. otherwise. 92 | protected bool CanExecute(object parameter) 93 | { 94 | return _canExecuteMethod == null || _canExecuteMethod(parameter); 95 | } 96 | 97 | /// 98 | /// Occurs when changes occur that affect whether or not the command should execute. You must keep a hard 99 | /// reference to the handler to avoid garbage collection and unexpected results. See remarks for more information. 100 | /// 101 | /// 102 | /// When subscribing to the event using 103 | /// code (not when binding using XAML) will need to keep a hard reference to the event handler. This is to prevent 104 | /// garbage collection of the event handler because the command implements the Weak Event pattern so it does not have 105 | /// a hard reference to this handler. An example implementation can be seen in the CompositeCommand and CommandBehaviorBase 106 | /// classes. In most scenarios, there is no reason to sign up to the CanExecuteChanged event directly, but if you do, you 107 | /// are responsible for maintaining the reference. 108 | /// 109 | /// 110 | /// The following code holds a reference to the event handler. The myEventHandlerReference value should be stored 111 | /// in an instance member to avoid it from being garbage collected. 112 | /// 113 | /// EventHandler myEventHandlerReference = new EventHandler(this.OnCanExecuteChanged); 114 | /// command.CanExecuteChanged += myEventHandlerReference; 115 | /// 116 | /// 117 | public virtual event EventHandler CanExecuteChanged 118 | { 119 | add 120 | { 121 | WeakEventHandlerManager.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); 122 | } 123 | remove 124 | { 125 | WeakEventHandlerManager.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); 126 | } 127 | } 128 | 129 | #region IsActive 130 | /// 131 | /// Gets or sets a value indicating whether the object is active. 132 | /// 133 | /// if the object is active; otherwise . 134 | public bool IsActive 135 | { 136 | get { return _isActive; } 137 | set 138 | { 139 | if (_isActive != value) 140 | { 141 | _isActive = value; 142 | OnIsActiveChanged(); 143 | } 144 | } 145 | } 146 | 147 | /// 148 | /// Fired if the property changes. 149 | /// 150 | public virtual event EventHandler IsActiveChanged; 151 | 152 | /// 153 | /// This raises the event. 154 | /// 155 | protected virtual void OnIsActiveChanged() 156 | { 157 | EventHandler isActiveChangedHandler = IsActiveChanged; 158 | if (isActiveChangedHandler != null) isActiveChangedHandler(this, EventArgs.Empty); 159 | } 160 | #endregion 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Command/IActiveAware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AutoMerge.Prism.Command 4 | { 5 | /// 6 | /// Interface that defines if the object instance is active 7 | /// and notifies when the activity changes. 8 | /// 9 | /// 10 | public interface IActiveAware 11 | { 12 | /// 13 | /// Gets or sets a value indicating whether the object is active. 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// if the object is active; otherwise . 19 | /// 20 | bool IsActive { get; set; } 21 | 22 | /// 23 | /// Notifies that the value for property has changed. 24 | /// 25 | /// 26 | event EventHandler IsActiveChanged; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Command/WeakEventHandlerManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | namespace AutoMerge.Prism.Command 6 | { 7 | /// 8 | /// Handles management and dispatching of EventHandlers in a weak way. 9 | /// 10 | public static class WeakEventHandlerManager 11 | { 12 | private static readonly SynchronizationContext _syncContext = SynchronizationContext.Current; 13 | 14 | /// 15 | /// Invokes the handlers 16 | /// 17 | /// 18 | /// 19 | public static void CallWeakReferenceHandlers(object sender, List handlers) 20 | { 21 | if (handlers != null) 22 | { 23 | // Take a snapshot of the handlers before we call out to them since the handlers 24 | // could cause the array to me modified while we are reading it. 25 | var callees = new EventHandler[handlers.Count]; 26 | var count = 0; 27 | 28 | //Clean up handlers 29 | count = CleanupOldHandlers(handlers, callees, count); 30 | 31 | // Call the handlers that we snapshotted 32 | for (var i = 0; i < count; i++) 33 | { 34 | CallHandler(sender, callees[i]); 35 | } 36 | } 37 | } 38 | 39 | private static void CallHandler(object sender, EventHandler eventHandler) 40 | { 41 | if (eventHandler != null) 42 | { 43 | if (_syncContext != null) 44 | { 45 | _syncContext.Post((o) => eventHandler(sender, EventArgs.Empty), null); 46 | } 47 | else 48 | { 49 | eventHandler(sender, EventArgs.Empty); 50 | } 51 | } 52 | } 53 | 54 | private static int CleanupOldHandlers(List handlers, EventHandler[] callees, int count) 55 | { 56 | for (var i = handlers.Count - 1; i >= 0; i--) 57 | { 58 | var reference = handlers[i]; 59 | var handler = reference.Target as EventHandler; 60 | if (handler == null) 61 | { 62 | // Clean up old handlers that have been collected 63 | handlers.RemoveAt(i); 64 | } 65 | else 66 | { 67 | callees[count] = handler; 68 | count++; 69 | } 70 | } 71 | return count; 72 | } 73 | 74 | /// 75 | /// Adds a handler to the supplied list in a weak way. 76 | /// 77 | ///Existing handler list. It will be created if null. 78 | ///Handler to add. 79 | ///Default list size. 80 | public static void AddWeakReferenceHandler(ref List handlers, EventHandler handler, int defaultListSize) 81 | { 82 | if (handlers == null) 83 | { 84 | handlers = (defaultListSize > 0 ? new List(defaultListSize) : new List()); 85 | } 86 | 87 | handlers.Add(new WeakReference(handler)); 88 | } 89 | 90 | /// 91 | /// Removes an event handler from the reference list. 92 | /// 93 | ///Handler list to remove reference from. 94 | ///Handler to remove. 95 | public static void RemoveWeakReferenceHandler(List handlers, EventHandler handler) 96 | { 97 | if (handlers != null) 98 | { 99 | for (var i = handlers.Count - 1; i >= 0; i--) 100 | { 101 | var reference = handlers[i]; 102 | var existingHandler = reference.Target as EventHandler; 103 | if ((existingHandler == null) || (existingHandler == handler)) 104 | { 105 | // Clean up old handlers that have been collected 106 | // in addition to the handler that is to be removed. 107 | handlers.RemoveAt(i); 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/BackgroundEventSubscription.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | using System.Threading; 5 | 6 | namespace AutoMerge.Prism.Events 7 | { 8 | /// 9 | /// Extends to invoke the delegate in a background thread. 10 | /// 11 | /// The type to use for the generic and types. 12 | public class BackgroundEventSubscription : EventSubscription 13 | { 14 | /// 15 | /// Creates a new instance of . 16 | /// 17 | /// A reference to a delegate of type . 18 | /// A reference to a delegate of type . 19 | /// When or are . 20 | /// When the target of is not of type , 21 | /// or the target of is not of type . 22 | public BackgroundEventSubscription(IDelegateReference actionReference, IDelegateReference filterReference) 23 | : base(actionReference, filterReference) 24 | { 25 | } 26 | 27 | /// 28 | /// Invokes the specified in an asynchronous thread by using a . 29 | /// 30 | /// The action to execute. 31 | /// The payload to pass while invoking it. 32 | public override void InvokeAction(Action action, TPayload argument) 33 | { 34 | ThreadPool.QueueUserWorkItem( (o) => action(argument) ); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/DelegateReference.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | using System.Reflection; 5 | 6 | namespace AutoMerge.Prism.Events 7 | { 8 | /// 9 | /// Represents a reference to a that may contain a 10 | /// to the target. This class is used 11 | /// internally by the Prism Library. 12 | /// 13 | public class DelegateReference : IDelegateReference 14 | { 15 | private readonly Delegate _delegate; 16 | private readonly WeakReference _weakReference; 17 | private readonly MethodInfo _method; 18 | private readonly Type _delegateType; 19 | 20 | /// 21 | /// Initializes a new instance of . 22 | /// 23 | /// The original to create a reference for. 24 | /// If the class will create a weak reference to the delegate, allowing it to be garbage collected. Otherwise it will keep a strong reference to the target. 25 | /// If the passed is not assignable to . 26 | public DelegateReference(Delegate @delegate, bool keepReferenceAlive) 27 | { 28 | if (@delegate == null) 29 | throw new ArgumentNullException("delegate"); 30 | 31 | if (keepReferenceAlive) 32 | { 33 | this._delegate = @delegate; 34 | } 35 | else 36 | { 37 | _weakReference = new WeakReference(@delegate.Target); 38 | _method = @delegate.Method; 39 | _delegateType = @delegate.GetType(); 40 | } 41 | } 42 | 43 | /// 44 | /// Gets the (the target) referenced by the current object. 45 | /// 46 | /// if the object referenced by the current object has been garbage collected; otherwise, a reference to the referenced by the current object. 47 | public Delegate Target 48 | { 49 | get 50 | { 51 | if (_delegate != null) 52 | { 53 | return _delegate; 54 | } 55 | else 56 | { 57 | return TryGetDelegate(); 58 | } 59 | } 60 | } 61 | 62 | private Delegate TryGetDelegate() 63 | { 64 | if (_method.IsStatic) 65 | { 66 | return Delegate.CreateDelegate(_delegateType, null, _method); 67 | } 68 | object target = _weakReference.Target; 69 | if (target != null) 70 | { 71 | return Delegate.CreateDelegate(_delegateType, target, _method); 72 | } 73 | return null; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/DispatcherEventSubscription.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | using System.Threading; 5 | 6 | namespace AutoMerge.Prism.Events 7 | { 8 | /// 9 | /// Extends to invoke the delegate 10 | /// in a specific . 11 | /// 12 | /// The type to use for the generic and types. 13 | public class DispatcherEventSubscription : EventSubscription 14 | { 15 | private readonly SynchronizationContext _syncContext; 16 | 17 | /// 18 | /// Creates a new instance of . 19 | /// 20 | ///A reference to a delegate of type . 21 | ///A reference to a delegate of type . 22 | ///The synchronization context to use for UI thread dispatching. 23 | ///When or are . 24 | ///When the target of is not of type , 25 | ///or the target of is not of type . 26 | public DispatcherEventSubscription(IDelegateReference actionReference, IDelegateReference filterReference, SynchronizationContext context) 27 | : base(actionReference, filterReference) 28 | { 29 | _syncContext = context; 30 | } 31 | 32 | /// 33 | /// Invokes the specified asynchronously in the specified . 34 | /// 35 | /// The action to execute. 36 | /// The payload to pass while invoking it. 37 | public override void InvokeAction(Action action, TPayload argument) 38 | { 39 | _syncContext.Post((o) => action((TPayload)o), argument); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/EventAggregator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading; 6 | 7 | namespace AutoMerge.Prism.Events 8 | { 9 | /// 10 | /// Implements . 11 | /// 12 | public class EventAggregator : IEventAggregator 13 | { 14 | private readonly Dictionary _events = new Dictionary(); 15 | // Captures the sync context for the UI thread when constructed on the UI thread 16 | // in a platform agnositc way so it can be used for UI thread dispatching 17 | private readonly SynchronizationContext _syncContext = SynchronizationContext.Current; 18 | 19 | /// 20 | /// Gets the single instance of the event managed by this EventAggregator. Multiple calls to this method with the same returns the same event instance. 21 | /// 22 | /// The type of event to get. This must inherit from . 23 | /// A singleton instance of an event object of type . 24 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] 25 | public TEventType GetEvent() where TEventType : EventBase, new() 26 | { 27 | lock (_events) 28 | { 29 | EventBase existingEvent = null; 30 | 31 | if (!_events.TryGetValue(typeof(TEventType), out existingEvent)) 32 | { 33 | TEventType newEvent = new TEventType(); 34 | newEvent.SynchronizationContext = _syncContext; 35 | _events[typeof(TEventType)] = newEvent; 36 | 37 | return newEvent; 38 | } 39 | else 40 | { 41 | return (TEventType)existingEvent; 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/EventBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | 8 | namespace AutoMerge.Prism.Events 9 | { 10 | /// 11 | /// Defines a base class to publish and subscribe to events. 12 | /// 13 | public abstract class EventBase 14 | { 15 | private readonly List _subscriptions = new List(); 16 | 17 | /// 18 | /// Allows the SynchronizationContext to be set by the EventAggregator for UI Thread Dispatching 19 | /// 20 | public SynchronizationContext SynchronizationContext { get; set; } 21 | 22 | /// 23 | /// Gets the list of current subscriptions. 24 | /// 25 | /// The current subscribers. 26 | protected ICollection Subscriptions 27 | { 28 | get { return _subscriptions; } 29 | } 30 | 31 | /// 32 | /// Adds the specified to the subscribers' collection. 33 | /// 34 | /// The subscriber. 35 | /// The that uniquely identifies every subscriber. 36 | /// 37 | /// Adds the subscription to the internal list and assigns it a new . 38 | /// 39 | protected virtual SubscriptionToken InternalSubscribe(IEventSubscription eventSubscription) 40 | { 41 | if (eventSubscription == null) throw new System.ArgumentNullException("eventSubscription"); 42 | 43 | eventSubscription.SubscriptionToken = new SubscriptionToken(Unsubscribe); 44 | 45 | lock (Subscriptions) 46 | { 47 | Subscriptions.Add(eventSubscription); 48 | } 49 | return eventSubscription.SubscriptionToken; 50 | } 51 | 52 | /// 53 | /// Calls all the execution strategies exposed by the list of . 54 | /// 55 | /// The arguments that will be passed to the listeners. 56 | /// Before executing the strategies, this class will prune all the subscribers from the 57 | /// list that return a when calling the 58 | /// method. 59 | protected virtual void InternalPublish(params object[] arguments) 60 | { 61 | List> executionStrategies = PruneAndReturnStrategies(); 62 | foreach (var executionStrategy in executionStrategies) 63 | { 64 | executionStrategy(arguments); 65 | } 66 | } 67 | 68 | /// 69 | /// Removes the subscriber matching the . 70 | /// 71 | /// The returned by while subscribing to the event. 72 | public virtual void Unsubscribe(SubscriptionToken token) 73 | { 74 | lock (Subscriptions) 75 | { 76 | IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token); 77 | if (subscription != null) 78 | { 79 | Subscriptions.Remove(subscription); 80 | } 81 | } 82 | } 83 | 84 | /// 85 | /// Returns if there is a subscriber matching . 86 | /// 87 | /// The returned by while subscribing to the event. 88 | /// if there is a that matches; otherwise . 89 | public virtual bool Contains(SubscriptionToken token) 90 | { 91 | lock (Subscriptions) 92 | { 93 | IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token); 94 | return subscription != null; 95 | } 96 | } 97 | 98 | private List> PruneAndReturnStrategies() 99 | { 100 | List> returnList = new List>(); 101 | 102 | lock (Subscriptions) 103 | { 104 | for (var i = Subscriptions.Count - 1; i >= 0; i--) 105 | { 106 | Action listItem = 107 | _subscriptions[i].GetExecutionStrategy(); 108 | 109 | if (listItem == null) 110 | { 111 | // Prune from main list. Log? 112 | _subscriptions.RemoveAt(i); 113 | } 114 | else 115 | { 116 | returnList.Add(listItem); 117 | } 118 | } 119 | } 120 | 121 | return returnList; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/EventSubscription.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | using System.Globalization; 5 | 6 | namespace AutoMerge.Prism.Events 7 | { 8 | /// 9 | /// Provides a way to retrieve a to execute an action depending 10 | /// on the value of a second filter predicate that returns true if the action should execute. 11 | /// 12 | /// The type to use for the generic and types. 13 | public class EventSubscription : IEventSubscription 14 | { 15 | private readonly IDelegateReference _actionReference; 16 | private readonly IDelegateReference _filterReference; 17 | 18 | /// 19 | /// Creates a new instance of . 20 | /// 21 | ///A reference to a delegate of type . 22 | ///A reference to a delegate of type . 23 | ///When or are . 24 | ///When the target of is not of type , 25 | ///or the target of is not of type . 26 | public EventSubscription(IDelegateReference actionReference, IDelegateReference filterReference) 27 | { 28 | if (actionReference == null) 29 | throw new ArgumentNullException("actionReference"); 30 | if (!(actionReference.Target is Action)) 31 | throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Invalid Delegate Reference Type Exception", typeof(Action).FullName), "actionReference"); 32 | 33 | if (filterReference == null) 34 | throw new ArgumentNullException("filterReference"); 35 | if (!(filterReference.Target is Predicate)) 36 | throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Invalid Delegate Reference Type Exception", typeof(Predicate).FullName), "filterReference"); 37 | 38 | _actionReference = actionReference; 39 | _filterReference = filterReference; 40 | } 41 | 42 | /// 43 | /// Gets the target that is referenced by the . 44 | /// 45 | /// An or if the referenced target is not alive. 46 | public Action Action 47 | { 48 | get { return (Action)_actionReference.Target; } 49 | } 50 | 51 | /// 52 | /// Gets the target that is referenced by the . 53 | /// 54 | /// An or if the referenced target is not alive. 55 | public Predicate Filter 56 | { 57 | get { return (Predicate)_filterReference.Target; } 58 | } 59 | 60 | /// 61 | /// Gets or sets a that identifies this . 62 | /// 63 | /// A token that identifies this . 64 | public SubscriptionToken SubscriptionToken { get; set; } 65 | 66 | /// 67 | /// Gets the execution strategy to publish this event. 68 | /// 69 | /// An with the execution strategy, or if the is no longer valid. 70 | /// 71 | /// If or are no longer valid because they were 72 | /// garbage collected, this method will return . 73 | /// Otherwise it will return a delegate that evaluates the and if it 74 | /// returns will then call . The returned 75 | /// delegate holds hard references to the and target 76 | /// delegates. As long as the returned delegate is not garbage collected, 77 | /// the and references delegates won't get collected either. 78 | /// 79 | public virtual Action GetExecutionStrategy() 80 | { 81 | Action action = this.Action; 82 | Predicate filter = this.Filter; 83 | if (action != null && filter != null) 84 | { 85 | return arguments => 86 | { 87 | TPayload argument = default(TPayload); 88 | if (arguments != null && arguments.Length > 0 && arguments[0] != null) 89 | { 90 | argument = (TPayload)arguments[0]; 91 | } 92 | if (filter(argument)) 93 | { 94 | InvokeAction(action, argument); 95 | } 96 | }; 97 | } 98 | return null; 99 | } 100 | 101 | /// 102 | /// Invokes the specified synchronously when not overridden. 103 | /// 104 | /// The action to execute. 105 | /// The payload to pass while invoking it. 106 | /// An is thrown if is null. 107 | public virtual void InvokeAction(Action action, TPayload argument) 108 | { 109 | if (action == null) throw new System.ArgumentNullException("action"); 110 | 111 | action(argument); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/IDelegateReference.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | 5 | namespace AutoMerge.Prism.Events 6 | { 7 | /// 8 | /// Represents a reference to a . 9 | /// 10 | public interface IDelegateReference 11 | { 12 | /// 13 | /// Gets the referenced object. 14 | /// 15 | /// A instance if the target is valid; otherwise . 16 | Delegate Target { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/IEventAggregator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace AutoMerge.Prism.Events 4 | { 5 | /// 6 | /// Defines an interface to get instances of an event type. 7 | /// 8 | public interface IEventAggregator 9 | { 10 | /// 11 | /// Gets an instance of an event type. 12 | /// 13 | /// The type of event to get. 14 | /// An instance of an event object of type . 15 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] 16 | TEventType GetEvent() where TEventType : EventBase, new(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/IEventSubscription.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | 5 | namespace AutoMerge.Prism.Events 6 | { 7 | /// 8 | /// Defines a contract for an event subscription to be used by . 9 | /// 10 | public interface IEventSubscription 11 | { 12 | /// 13 | /// Gets or sets a that identifies this . 14 | /// 15 | /// A token that identifies this . 16 | SubscriptionToken SubscriptionToken { get; set; } 17 | 18 | /// 19 | /// Gets the execution strategy to publish this event. 20 | /// 21 | /// An with the execution strategy, or if the is no longer valid. 22 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] 23 | Action GetExecutionStrategy(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/SubscriptionToken.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | 5 | namespace AutoMerge.Prism.Events 6 | { 7 | /// 8 | /// Subscription token returned from on subscribe. 9 | /// 10 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Should never have a need for a finalizer, hence no need for Dispole(bool)")] 11 | public class SubscriptionToken : IEquatable, IDisposable 12 | { 13 | private readonly Guid _token; 14 | private Action _unsubscribeAction; 15 | 16 | /// 17 | /// Initializes a new instance of . 18 | /// 19 | public SubscriptionToken(Action unsubscribeAction) 20 | { 21 | _unsubscribeAction = unsubscribeAction; 22 | _token = Guid.NewGuid(); 23 | } 24 | 25 | /// 26 | ///Indicates whether the current object is equal to another object of the same type. 27 | /// 28 | /// 29 | /// if the current object is equal to the parameter; otherwise, . 30 | /// 31 | ///An object to compare with this object. 32 | public bool Equals(SubscriptionToken other) 33 | { 34 | if (other == null) return false; 35 | return Equals(_token, other._token); 36 | } 37 | 38 | /// 39 | ///Determines whether the specified is equal to the current . 40 | /// 41 | /// 42 | ///true if the specified is equal to the current ; otherwise, false. 43 | /// 44 | ///The to compare with the current . 45 | ///The parameter is null.2 46 | public override bool Equals(object obj) 47 | { 48 | if (ReferenceEquals(this, obj)) return true; 49 | return Equals(obj as SubscriptionToken); 50 | } 51 | 52 | /// 53 | ///Serves as a hash function for a particular type. 54 | /// 55 | /// 56 | ///A hash code for the current . 57 | /// 58 | ///2 59 | public override int GetHashCode() 60 | { 61 | return _token.GetHashCode(); 62 | } 63 | 64 | /// 65 | /// Disposes the SubscriptionToken, removing the subscription from the corresponding . 66 | /// 67 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Should never have need for a finalizer, hence no need for Dispose(bool).")] 68 | public virtual void Dispose() 69 | { 70 | // While the SubsctiptionToken class implements IDisposable, in the case of weak subscriptions 71 | // (i.e. keepSubscriberReferenceAlive set to false in the Subscribe method) it's not necessary to unsubscribe, 72 | // as no resources should be kept alive by the event subscription. 73 | // In such cases, if a warning is issued, it could be suppressed. 74 | 75 | if (this._unsubscribeAction != null) 76 | { 77 | this._unsubscribeAction(this); 78 | this._unsubscribeAction = null; 79 | } 80 | 81 | GC.SuppressFinalize(this); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/Events/ThreadOption.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | 4 | namespace AutoMerge.Prism.Events 5 | { 6 | /// 7 | /// Specifies on which thread a subscriber will be called. 8 | /// 9 | public enum ThreadOption 10 | { 11 | /// 12 | /// The call is done on the same thread on which the was published. 13 | /// 14 | PublisherThread, 15 | 16 | /// 17 | /// The call is done on the UI thread. 18 | /// 19 | UIThread, 20 | 21 | /// 22 | /// The call is done asynchronously on a background thread. 23 | /// 24 | BackgroundThread 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/AutoMerge/Prism/PropertySupport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | namespace AutoMerge.Prism 8 | { 9 | /// 10 | /// Provides support for extracting property information based on a property expression. 11 | /// 12 | public static class PropertySupport 13 | { 14 | /// 15 | /// Extracts the property name from a property expression. 16 | /// 17 | /// The object type containing the property specified in the expression. 18 | /// The property expression (e.g. p => p.PropertyName) 19 | /// The name of the property. 20 | /// Thrown if the is null. 21 | /// Thrown when the expression is:
22 | /// Not a
23 | /// The does not represent a property.
24 | /// Or, the property is static. 25 | ///
26 | public static string ExtractPropertyName(Expression> propertyExpression) 27 | { 28 | if (propertyExpression == null) 29 | { 30 | throw new ArgumentNullException("propertyExpression"); 31 | } 32 | 33 | var memberExpression = propertyExpression.Body as MemberExpression; 34 | if (memberExpression == null) 35 | { 36 | throw new ArgumentException(@">The expression is not a member access expression.", "propertyExpression"); 37 | } 38 | 39 | var property = memberExpression.Member as PropertyInfo; 40 | if (property == null) 41 | { 42 | throw new ArgumentException(@"The member access expression does not access a property.", "propertyExpression"); 43 | } 44 | 45 | var getMethod = property.GetMethod; 46 | if (getMethod.IsStatic) 47 | { 48 | throw new ArgumentException(@"The referenced property is a static property.", "propertyExpression"); 49 | } 50 | 51 | return memberExpression.Member.Name; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/AutoMerge/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Resources; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("AutoMerge")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("Kulikov Denis")] 14 | [assembly: AssemblyProduct("AutoMerge")] 15 | [assembly: AssemblyCopyright("")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | [assembly: ComVisible(false)] 19 | [assembly: CLSCompliant(false)] 20 | [assembly: NeutralResourcesLanguage("en-US")] 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 Revision and Build Numbers 30 | // by using the '*' as shown below: 31 | 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | 35 | [assembly: InternalsVisibleTo("AutoMerge_IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e7ae2c7f4bfb47fbe6f0d52701c52067de068a5f5a209befa519efe840955ce682c06506a01bb81fa8fbf12ec6cea9d3eee417b763cc575661ee9d68a52fbbf7d3a67e57778fc5bdfb4b9e40c0258821402fb12ba1d9bacdc6fd9893baaac5d07819eb69f62658378293d52ce6959b3311620dca3c9be3642974ab29576022ce")] 36 | [assembly: InternalsVisibleTo("AutoMerge_UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e7ae2c7f4bfb47fbe6f0d52701c52067de068a5f5a209befa519efe840955ce682c06506a01bb81fa8fbf12ec6cea9d3eee417b763cc575661ee9d68a52fbbf7d3a67e57778fc5bdfb4b9e40c0258821402fb12ba1d9bacdc6fd9893baaac5d07819eb69f62658378293d52ce6959b3311620dca3c9be3642974ab29576022ce")] 37 | -------------------------------------------------------------------------------- /src/AutoMerge/RecentChangesets/ChangesetByIdChangesetProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace AutoMerge 6 | { 7 | public class ChangesetByIdChangesetProvider : ChangesetProviderBase 8 | { 9 | private readonly IEnumerable _changesetIds; 10 | 11 | public ChangesetByIdChangesetProvider(IServiceProvider serviceProvider, IEnumerable changesetIds) 12 | : base(serviceProvider) 13 | { 14 | if (changesetIds == null) 15 | throw new ArgumentNullException("changesetIds"); 16 | 17 | _changesetIds = changesetIds; 18 | } 19 | 20 | protected override List GetChangesetsInternal(string userLogin) 21 | { 22 | var changesets = new List(); 23 | var changesetService = GetChangesetService(); 24 | if (changesetService != null) 25 | { 26 | changesets = _changesetIds 27 | .Select(changesetService.GetChangeset) 28 | .Where(c => c != null) 29 | .Select(tfsChangeset => ToChangesetViewModel(tfsChangeset, changesetService)) 30 | .ToList(); 31 | } 32 | 33 | return changesets; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/AutoMerge/RecentChangesets/ChangesetCommentConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Windows.Data; 4 | 5 | namespace AutoMerge 6 | { 7 | /// 8 | /// Changeset comment converter class. 9 | /// 10 | public class ChangesetCommentConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 13 | { 14 | var comment = (value is string) ? (string)value : String.Empty; 15 | var sb = new StringBuilder(comment); 16 | sb.Replace('\r', ' '); 17 | sb.Replace('\n', ' '); 18 | sb.Replace('\t', ' '); 19 | 20 | if (sb.Length > 64) 21 | { 22 | sb.Remove(61, sb.Length - 61); 23 | sb.Append("..."); 24 | } 25 | 26 | return sb.ToString(); 27 | } 28 | 29 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 30 | { 31 | return null; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/AutoMerge/RecentChangesets/ChangesetProviderBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.TeamFoundation.VersionControl.Client; 6 | 7 | namespace AutoMerge 8 | { 9 | public abstract class ChangesetProviderBase : IChangesetProvider 10 | { 11 | private readonly IServiceProvider _serviceProvider; 12 | private readonly Lazy _changesetService; 13 | 14 | protected ChangesetProviderBase(IServiceProvider serviceProvider) 15 | { 16 | _serviceProvider = serviceProvider; 17 | _changesetService = new Lazy(InitChangesetService); 18 | } 19 | 20 | public Task> GetChangesets(string userLogin) 21 | { 22 | return Task.Run(() => GetChangesetsInternal(userLogin)); 23 | } 24 | 25 | protected abstract List GetChangesetsInternal(string userLogin); 26 | 27 | protected ChangesetViewModel ToChangesetViewModel(Changeset tfsChangeset, ChangesetService changesetService) 28 | { 29 | var changesetViewModel = new ChangesetViewModel 30 | { 31 | ChangesetId = tfsChangeset.ChangesetId, 32 | Comment = tfsChangeset.Comment, 33 | Branches = changesetService.GetAssociatedBranches(tfsChangeset.ChangesetId) 34 | .Select(i => i.Item) 35 | .ToList() 36 | }; 37 | 38 | return changesetViewModel; 39 | } 40 | 41 | protected ChangesetService GetChangesetService() 42 | { 43 | return _changesetService.Value; 44 | } 45 | 46 | private ChangesetService InitChangesetService() 47 | { 48 | var context = VersionControlNavigationHelper.GetTeamFoundationContext(_serviceProvider); 49 | if (context != null && VersionControlNavigationHelper.IsConnectedToTfsCollectionAndProject(context)) 50 | { 51 | var vcs = context.TeamProjectCollection.GetService(); 52 | if (vcs != null) 53 | { 54 | return new ChangesetService(vcs); 55 | } 56 | } 57 | return null; 58 | } 59 | 60 | protected string GetProjectName() 61 | { 62 | var context = VersionControlNavigationHelper.GetTeamFoundationContext(_serviceProvider); 63 | if (context != null) 64 | { 65 | return context.TeamProjectName; 66 | } 67 | return null; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/AutoMerge/RecentChangesets/ChangesetViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace AutoMerge 4 | { 5 | public class ChangesetViewModel 6 | { 7 | public int ChangesetId { get; set; } 8 | 9 | public string Comment { get; set; } 10 | 11 | public List Branches { get; set; } 12 | 13 | public string DisplayBranchName 14 | { 15 | get { return BranchHelper.GetDisplayBranchName(Branches); } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/AutoMerge/RecentChangesets/IChangesetProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace AutoMerge 5 | { 6 | public interface IChangesetProvider 7 | { 8 | Task> GetChangesets(string userLogin); 9 | } 10 | } -------------------------------------------------------------------------------- /src/AutoMerge/RecentChangesets/MyChangesetChangesetProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace AutoMerge 6 | { 7 | public class MyChangesetChangesetProvider : ChangesetProviderBase 8 | { 9 | private readonly int _maxChangesetCount; 10 | 11 | public MyChangesetChangesetProvider(IServiceProvider serviceProvider, int maxChangesetCount) 12 | : base(serviceProvider) 13 | { 14 | _maxChangesetCount = maxChangesetCount; 15 | } 16 | 17 | protected override List GetChangesetsInternal(string userLogin) 18 | { 19 | var changesets = new List(); 20 | 21 | if (!string.IsNullOrEmpty(userLogin)) 22 | { 23 | var changesetService = GetChangesetService(); 24 | 25 | if (changesetService != null) 26 | { 27 | var projectName = GetProjectName(); 28 | var tfsChangesets = changesetService.GetUserChangesets(projectName, userLogin, _maxChangesetCount); 29 | changesets = tfsChangesets 30 | .Select(tfsChangeset => ToChangesetViewModel(tfsChangeset, changesetService)) 31 | .ToList(); 32 | } 33 | } 34 | 35 | return changesets; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/AutoMerge/RecentChangesets/RecentChangesetFocusableControlNames.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMerge 2 | { 3 | public class RecentChangesetFocusableControlNames 4 | { 5 | public const string ChangesetIdTextBox = "changesetIdTextBox"; 6 | public const string AddChangesetByIdLink = "addChangesetByIdLink"; 7 | public const string ChangesetList = "changesetList"; 8 | } 9 | } -------------------------------------------------------------------------------- /src/AutoMerge/RecentChangesets/RecentChangesetsSection.cs: -------------------------------------------------------------------------------- 1 | using AutoMerge.Base; 2 | using Microsoft.TeamFoundation.Controls; 3 | 4 | namespace AutoMerge 5 | { 6 | [TeamExplorerSection(GuidList.RecentChangesetsSectionId, GuidList.AutoMergePageId, 10)] 7 | public class RecentChangesetsSection : TeamExplorerSectionBase 8 | { 9 | protected override ITeamExplorerSection CreateViewModel(SectionInitializeEventArgs e) 10 | { 11 | var viewModel = base.CreateViewModel(e) ?? new RecentChangesetsViewModel(new VsLogger(ServiceProvider)); 12 | 13 | return viewModel; 14 | } 15 | 16 | protected override object CreateView(SectionInitializeEventArgs e) 17 | { 18 | return new RecentChangesetsView(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/AutoMerge/RecentChangesets/RecentChangesetsView.xaml: -------------------------------------------------------------------------------- 1 |  17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 42 | 43 | 44 | 45 | 56 | 57 | 58 | 59 | 60 | 66 | 67 | 72 | 73 | 81 | 82 | 83 | 84 | 85 | 86 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/AutoMerge/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 | AutoMerge 133 | 134 | 135 | Let auto merge changesets 136 | 137 | 138 | Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 139 | 140 | -------------------------------------------------------------------------------- /src/AutoMerge/VersionControl/VersionControlProvider.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMerge.VersionControl 2 | { 3 | public enum VersionControlProvider 4 | { 5 | TeamFoundation = 1, 6 | Git = 2, 7 | } 8 | } -------------------------------------------------------------------------------- /src/AutoMerge/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Auto Merge for Visual Studio 2022 6 | Easy way to merge changeset 7 | LICENSE.txt 8 | https://github.com/CDuke/AutoMerge/blob/master/RELEASE_NOTES.md 9 | Resources/merge.png 10 | Resources/preview.png 11 | merge code, merge, merging, branch, changeset, merge by work item, tfs 12 | 13 | 14 | 15 | amd64 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/AutoMerge/source.vs15.0.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Auto Merge for Visual Studio 2017 6 | Easy way to merge changeset 7 | LICENSE.txt 8 | https://github.com/CDuke/AutoMerge/blob/master/RELEASE_NOTES.md 9 | Resources/merge.png 10 | Resources/preview.png 11 | merge code, merge, merging, branch, changeset, merge by work item, tfs 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/AutoMerge/source.vs16.0.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Auto Merge for Visual Studio 2019 6 | Easy way to merge changeset 7 | LICENSE.txt 8 | https://github.com/CDuke/AutoMerge/blob/master/RELEASE_NOTES.md 9 | Resources/merge.png 10 | Resources/preview.png 11 | merge code, merge, merging, branch, changeset, merge by work item, tfs 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/AutoMerge/source.vs17.0.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Auto Merge for Visual Studio 2022 6 | Easy way to merge changeset 7 | LICENSE.txt 8 | https://github.com/CDuke/AutoMerge/blob/master/RELEASE_NOTES.md 9 | Resources/merge.png 10 | Resources/preview.png 11 | merge code, merge, merging, branch, changeset, merge by work item, tfs 12 | 13 | 14 | 15 | amd64 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tools/NuGet/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CDuke/AutoMerge/9550b96365ede7440bbda52efea1a73f49391c3f/tools/NuGet/NuGet.exe --------------------------------------------------------------------------------