/Main/TeamProjectManager.Modules.SourceControl/SourceControlViewModel.cs

# · C# · 380 lines · 345 code · 35 blank · 0 comment · 31 complexity · 1541c1751f8d870c108ec4e46ce63390 MD5 · raw file

  1. using Microsoft.Practices.Prism.Events;
  2. using Microsoft.TeamFoundation.Client;
  3. using Microsoft.TeamFoundation.VersionControl.Client;
  4. using Microsoft.Win32;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Collections.ObjectModel;
  8. using System.ComponentModel;
  9. using System.ComponentModel.Composition;
  10. using System.Globalization;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Windows;
  14. using TeamProjectManager.Common.Events;
  15. using TeamProjectManager.Common.Infrastructure;
  16. using TeamProjectManager.Common.ObjectModel;
  17. namespace TeamProjectManager.Modules.SourceControl
  18. {
  19. [Export]
  20. public class SourceControlViewModel : ViewModelBase
  21. {
  22. #region Properties
  23. public RelayCommand GetSourceControlSettingsCommand { get; private set; }
  24. public RelayCommand LoadSourceControlSettingsCommand { get; private set; }
  25. public RelayCommand UpdateSourceControlSettingsCommand { get; private set; }
  26. public RelayCommand ViewBranchHierarchiesCommand { get; private set; }
  27. public RelayCommand ExportBranchHierarchiesCommand { get; private set; }
  28. #endregion
  29. #region Observable Properties
  30. public ObservableCollection<SourceControlSettings> SourceControlSettings
  31. {
  32. get { return this.GetValue(SourceControlSettingsProperty); }
  33. set { this.SetValue(SourceControlSettingsProperty, value); }
  34. }
  35. public static ObservableProperty<ObservableCollection<SourceControlSettings>> SourceControlSettingsProperty = new ObservableProperty<ObservableCollection<SourceControlSettings>, SourceControlViewModel>(o => o.SourceControlSettings);
  36. public SourceControlSettings SelectedSourceControlSettings
  37. {
  38. get { return this.GetValue(SelectedSourceControlSettingsProperty); }
  39. set { this.SetValue(SelectedSourceControlSettingsProperty, value); }
  40. }
  41. public static ObservableProperty<SourceControlSettings> SelectedSourceControlSettingsProperty = new ObservableProperty<SourceControlSettings, SourceControlViewModel>(o => o.SelectedSourceControlSettings, new SourceControlSettings());
  42. public string BranchHierarchiesInfoMessage
  43. {
  44. get { return this.GetValue(BranchHierarchiesInfoMessageProperty); }
  45. set { this.SetValue(BranchHierarchiesInfoMessageProperty, value); }
  46. }
  47. public static readonly ObservableProperty<string> BranchHierarchiesInfoMessageProperty = new ObservableProperty<string, SourceControlViewModel>(o => o.BranchHierarchiesInfoMessage);
  48. public IList<BranchInfo> BranchHierarchies
  49. {
  50. get { return this.GetValue(BranchHierarchiesProperty); }
  51. set { this.SetValue(BranchHierarchiesProperty, value); }
  52. }
  53. public static readonly ObservableProperty<IList<BranchInfo>> BranchHierarchiesProperty = new ObservableProperty<IList<BranchInfo>, SourceControlViewModel>(o => o.BranchHierarchies);
  54. public BranchHierarchyExportFormat ExportFormat
  55. {
  56. get { return this.GetValue(ExportFormatProperty); }
  57. set { this.SetValue(ExportFormatProperty, value); }
  58. }
  59. public static readonly ObservableProperty<BranchHierarchyExportFormat> ExportFormatProperty = new ObservableProperty<BranchHierarchyExportFormat, SourceControlViewModel>(o => o.ExportFormat);
  60. public bool ExportBranchHierarchiesPerTeamProject
  61. {
  62. get { return this.GetValue(ExportBranchHierarchiesPerTeamProjectProperty); }
  63. set { this.SetValue(ExportBranchHierarchiesPerTeamProjectProperty, value); }
  64. }
  65. public static readonly ObservableProperty<bool> ExportBranchHierarchiesPerTeamProjectProperty = new ObservableProperty<bool, SourceControlViewModel>(o => o.ExportBranchHierarchiesPerTeamProject);
  66. #endregion
  67. #region Constructors
  68. [ImportingConstructor]
  69. public SourceControlViewModel(IEventAggregator eventAggregator, ILogger logger)
  70. : base(eventAggregator, logger, "Source Control", "Allows you to manage Source Control settings for Team Projects and see branch hierarchies.")
  71. {
  72. this.GetSourceControlSettingsCommand = new RelayCommand(GetSourceControlSettings, CanGetSourceControlSettings);
  73. this.LoadSourceControlSettingsCommand = new RelayCommand(LoadSourceControlSettings, CanLoadSourceControlSettings);
  74. this.UpdateSourceControlSettingsCommand = new RelayCommand(UpdateSourceControlSettings, CanUpdateSourceControlSettings);
  75. this.ViewBranchHierarchiesCommand = new RelayCommand(ViewBranchHierarchies, CanViewBranchHierarchies);
  76. this.ExportBranchHierarchiesCommand = new RelayCommand(ExportBranchHierarchies, CanExportBranchHierarchies);
  77. }
  78. #endregion
  79. #region Commands
  80. private bool CanGetSourceControlSettings(object argument)
  81. {
  82. return IsAnyTeamProjectSelected();
  83. }
  84. private void GetSourceControlSettings(object argument)
  85. {
  86. var teamProjectNames = this.SelectedTeamProjects.Select(p => p.Name).ToList();
  87. var task = new ApplicationTask("Retrieving source control settings", teamProjectNames.Count, true);
  88. this.PublishStatus(new StatusEventArgs(task));
  89. var worker = new BackgroundWorker();
  90. worker.DoWork += (sender, e) =>
  91. {
  92. var settings = new ObservableCollection<SourceControlSettings>();
  93. var step = 0;
  94. var tfs = GetSelectedTfsTeamProjectCollection();
  95. var vcs = tfs.GetService<VersionControlServer>();
  96. foreach (var teamProjectName in teamProjectNames)
  97. {
  98. task.SetProgress(step++, string.Format(CultureInfo.CurrentCulture, "Processing Team Project \"{0}\"", teamProjectName));
  99. try
  100. {
  101. var projectSettings = GetSourceControlSettings(vcs, teamProjectName);
  102. settings.Add(projectSettings);
  103. }
  104. catch (Exception exc)
  105. {
  106. task.SetWarning(string.Format(CultureInfo.CurrentCulture, "An error occurred while processing Team Project \"{0}\"", teamProjectName), exc);
  107. }
  108. if (task.IsCanceled)
  109. {
  110. task.Status = "Canceled";
  111. break;
  112. }
  113. }
  114. e.Result = settings;
  115. };
  116. worker.RunWorkerCompleted += (sender, e) =>
  117. {
  118. if (e.Error != null)
  119. {
  120. Logger.Log("An unexpected exception occurred while retrieving source control settings", e.Error);
  121. task.SetError(e.Error);
  122. task.SetComplete("An unexpected exception occurred");
  123. }
  124. else
  125. {
  126. this.SourceControlSettings = (ObservableCollection<SourceControlSettings>)e.Result;
  127. task.SetComplete("Done");
  128. }
  129. };
  130. worker.RunWorkerAsync();
  131. }
  132. private static SourceControlSettings GetSourceControlSettings(VersionControlServer vcs, string teamProjectName)
  133. {
  134. var teamProject = vcs.GetTeamProject(teamProjectName);
  135. var checkinNoteFields = teamProject.GetCheckinNoteFields().Select(f => new CheckinNoteField(f.DisplayOrder, f.Name, f.Required));
  136. return new SourceControlSettings(teamProject.Name, !teamProject.ExclusiveCheckout, teamProject.GetLatestOnCheckout, checkinNoteFields);
  137. }
  138. private bool CanLoadSourceControlSettings(object argument)
  139. {
  140. return true;
  141. }
  142. private void LoadSourceControlSettings(object argument)
  143. {
  144. using (var dialog = new TeamProjectPicker(TeamProjectPickerMode.SingleProject, false))
  145. {
  146. var result = dialog.ShowDialog(Application.Current.MainWindow.GetIWin32Window());
  147. if (result == System.Windows.Forms.DialogResult.OK && dialog.SelectedProjects != null && dialog.SelectedProjects.Length > 0)
  148. {
  149. var teamProjectCollection = dialog.SelectedTeamProjectCollection;
  150. var teamProject = dialog.SelectedProjects.First();
  151. var task = new ApplicationTask("Loading source control settings");
  152. PublishStatus(new StatusEventArgs(task));
  153. var worker = new BackgroundWorker();
  154. worker.DoWork += (sender, e) =>
  155. {
  156. var tfs = GetSelectedTfsTeamProjectCollection();
  157. var vcs = tfs.GetService<VersionControlServer>();
  158. var projectSettings = GetSourceControlSettings(vcs, teamProject.Name);
  159. e.Result = projectSettings;
  160. };
  161. worker.RunWorkerCompleted += (sender, e) =>
  162. {
  163. if (e.Error != null)
  164. {
  165. Logger.Log("An unexpected exception occurred while loading source control settings", e.Error);
  166. task.SetError(e.Error);
  167. task.SetComplete("An unexpected exception occurred");
  168. }
  169. else
  170. {
  171. this.SelectedSourceControlSettings = (SourceControlSettings)e.Result;
  172. task.SetComplete("Done");
  173. }
  174. };
  175. worker.RunWorkerAsync();
  176. }
  177. }
  178. }
  179. private bool CanUpdateSourceControlSettings(object argument)
  180. {
  181. return IsAnyTeamProjectSelected() && this.SelectedSourceControlSettings != null;
  182. }
  183. private void UpdateSourceControlSettings(object argument)
  184. {
  185. var result = MessageBox.Show("This will update the source control settings in all selected Team Projects. Are you sure you want to continue?", "Confirm Update", MessageBoxButton.YesNo, MessageBoxImage.Warning);
  186. if (result == MessageBoxResult.Yes)
  187. {
  188. var teamProjectNames = this.SelectedTeamProjects.Select(p => p.Name).ToList();
  189. var settings = this.SelectedSourceControlSettings;
  190. var task = new ApplicationTask("Updating source control settings", teamProjectNames.Count, true);
  191. this.PublishStatus(new StatusEventArgs(task));
  192. var worker = new BackgroundWorker();
  193. worker.DoWork += (sender, e) =>
  194. {
  195. var step = 0;
  196. var tfs = GetSelectedTfsTeamProjectCollection();
  197. var vcs = tfs.GetService<VersionControlServer>();
  198. foreach (var teamProjectName in teamProjectNames)
  199. {
  200. task.SetProgress(step++, string.Format(CultureInfo.CurrentCulture, "Processing Team Project \"{0}\"", teamProjectName));
  201. try
  202. {
  203. var project = vcs.GetTeamProject(teamProjectName);
  204. project.ExclusiveCheckout = !settings.EnableMultipleCheckout;
  205. project.GetLatestOnCheckout = settings.EnableGetLatestOnCheckout;
  206. var checkinNoteFields = settings.CheckinNoteFields.Select(f => new CheckinNoteFieldDefinition(f.Name, f.Required, f.DisplayOrder)).ToArray();
  207. project.SetCheckinNoteFields(checkinNoteFields);
  208. }
  209. catch (Exception exc)
  210. {
  211. task.SetError(string.Format(CultureInfo.CurrentCulture, "An error occurred while processing Team Project \"{0}\"", teamProjectName), exc);
  212. }
  213. if (task.IsCanceled)
  214. {
  215. task.Status = "Canceled";
  216. break;
  217. }
  218. }
  219. };
  220. worker.RunWorkerCompleted += (sender, e) =>
  221. {
  222. if (e.Error != null)
  223. {
  224. Logger.Log("An unexpected exception occurred while updating source control settings", e.Error);
  225. task.SetError(e.Error);
  226. task.SetComplete("An unexpected exception occurred");
  227. }
  228. else
  229. {
  230. task.SetComplete("Done");
  231. }
  232. };
  233. worker.RunWorkerAsync();
  234. }
  235. }
  236. private bool CanViewBranchHierarchies(object argument)
  237. {
  238. return this.IsAnyTeamProjectSelected();
  239. }
  240. private void ViewBranchHierarchies(object argument)
  241. {
  242. var teamProjectNames = this.SelectedTeamProjects.Select(p => p.Name).ToList();
  243. var task = new ApplicationTask("Retrieving branch hierarchies", null, true);
  244. this.PublishStatus(new StatusEventArgs(task));
  245. var worker = new BackgroundWorker();
  246. worker.DoWork += (sender, e) =>
  247. {
  248. var tfs = GetSelectedTfsTeamProjectCollection();
  249. var vcs = tfs.GetService<VersionControlServer>();
  250. var teamProjectRootPaths = teamProjectNames.Select(t => string.Concat("$/", t, "/")).ToArray();
  251. var branchHierarchies = new List<BranchInfo>();
  252. foreach (var rootBranch in vcs.QueryRootBranchObjects(RecursionType.OneLevel).Where(b => !teamProjectRootPaths.Any() || teamProjectRootPaths.Any(t => (b.Properties.RootItem.Item + "/").StartsWith(t, StringComparison.OrdinalIgnoreCase))).OrderBy(b => b.Properties.RootItem.Item))
  253. {
  254. branchHierarchies.Add(GetBranchInfo(rootBranch, null, vcs, task));
  255. if (task.IsCanceled)
  256. {
  257. task.Status = "Canceled";
  258. break;
  259. }
  260. }
  261. e.Result = branchHierarchies;
  262. };
  263. worker.RunWorkerCompleted += (sender, e) =>
  264. {
  265. if (e.Error != null)
  266. {
  267. Logger.Log("An unexpected exception occurred while retrieving branch hierarchies", e.Error);
  268. task.SetError(e.Error);
  269. task.SetComplete("An unexpected exception occurred");
  270. this.BranchHierarchiesInfoMessage = null;
  271. }
  272. else
  273. {
  274. this.BranchHierarchies = (IList<BranchInfo>)e.Result;
  275. var totalBranchCount = this.BranchHierarchies.Count + this.BranchHierarchies.Sum(b => b.RecursiveChildCount);
  276. var maxTreeDepth = this.BranchHierarchies.Any() ? this.BranchHierarchies.Max(b => b.MaxTreeDepth) : 0;
  277. var infoMessage = "Retrieved {0} with a total of {1} and a maximum depth of {2}".FormatCurrent(this.BranchHierarchies.Count.ToCountString("branch hierarchy"), totalBranchCount.ToCountString("branch"), maxTreeDepth);
  278. task.SetComplete(infoMessage);
  279. this.BranchHierarchiesInfoMessage = infoMessage;
  280. }
  281. };
  282. worker.RunWorkerAsync();
  283. }
  284. private static BranchInfo GetBranchInfo(BranchObject branch, BranchInfo parent, VersionControlServer vcs, ApplicationTask task)
  285. {
  286. var branchPath = branch.Properties.RootItem.Item;
  287. task.Status = "Processing " + branchPath;
  288. var current = new BranchInfo(parent, branchPath, branch.Properties.Description, branch.DateCreated, branch.Properties.Owner);
  289. var children = new List<BranchInfo>();
  290. foreach (var childBranch in vcs.QueryBranchObjects(branch.Properties.RootItem, RecursionType.OneLevel).Where(c => c.Properties.RootItem.Item != branchPath).OrderBy(c => c.Properties.RootItem.Item))
  291. {
  292. if (task.IsCanceled)
  293. {
  294. task.Status = "Canceled";
  295. break;
  296. }
  297. children.Add(GetBranchInfo(childBranch, current, vcs, task));
  298. }
  299. current.Children = children.ToArray();
  300. return current;
  301. }
  302. private bool CanExportBranchHierarchies(object argument)
  303. {
  304. return this.BranchHierarchies != null && this.BranchHierarchies.Any();
  305. }
  306. private void ExportBranchHierarchies(object argument)
  307. {
  308. if (!this.ExportBranchHierarchiesPerTeamProject)
  309. {
  310. var dialog = new SaveFileDialog();
  311. dialog.Title = "Please select the branch hierarchy {0} file to save.".FormatCurrent(this.ExportFormat.ToString().ToUpperInvariant());
  312. dialog.Filter = (this.ExportFormat == BranchHierarchyExportFormat.Dgml ? "DGML Files (*.dgml)|*.dgml" : "XML Files (*.xml)|*.xml");
  313. var result = dialog.ShowDialog(Application.Current.MainWindow);
  314. if (result == true)
  315. {
  316. try
  317. {
  318. BranchHierarchyExporter.Export(this.BranchHierarchies, dialog.FileName, this.ExportFormat);
  319. }
  320. catch (Exception exc)
  321. {
  322. this.Logger.Log(string.Format(CultureInfo.CurrentCulture, "An error occurred while saving the branch hierarchies to \"{0}\"", dialog.FileName), exc);
  323. MessageBox.Show("An error occurred while saving the branch hierarchies. See the log file for details", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
  324. }
  325. }
  326. }
  327. else
  328. {
  329. var dialog = new System.Windows.Forms.FolderBrowserDialog();
  330. dialog.Description = "Please select the path where to save the branch hierarchy {0} files. They will be stored in a file per Team Project.".FormatCurrent(this.ExportFormat.ToString().ToUpperInvariant());
  331. var result = dialog.ShowDialog();
  332. if (result == System.Windows.Forms.DialogResult.OK)
  333. {
  334. var rootFolder = dialog.SelectedPath;
  335. foreach (var teamProjectWithBranchHierarchies in this.BranchHierarchies.GroupBy(h => h.TeamProjectName))
  336. {
  337. var fileName = Path.Combine(rootFolder, teamProjectWithBranchHierarchies.Key + "." + this.ExportFormat.ToString().ToLower());
  338. BranchHierarchyExporter.Export(teamProjectWithBranchHierarchies, fileName, this.ExportFormat);
  339. }
  340. }
  341. }
  342. }
  343. #endregion
  344. }
  345. }