From d8c2bbfa44b5a00c5d1f9a50ef433c150e353d9a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 11 Jan 2014 01:24:03 +0100 Subject: [PATCH] Fix #252 - Adding an existing project to a solution uses a new GUID instead of the Also adds code to detect+fix duplicate GUIDs. (which might exist due to users creating new projects by copying old ones) --- .../Editor/CodeCompletion/ICompletionItem.cs | 1 + .../Base/Project/Project/ISolutionItem.cs | 3 +- .../Src/Project/MSBuildBasedProject.cs | 28 +++++++++++++++++-- .../Test/Project/AddExistingProjectTests.cs | 17 +++++++---- .../SharpDevelop/Project/SolutionFileItem.cs | 7 ++--- .../SharpDevelop/Project/SolutionFolder.cs | 8 ++++-- .../SharpDevelop/Project/SolutionLoader.cs | 24 ++++++++++++++-- 7 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/Main/Base/Project/Editor/CodeCompletion/ICompletionItem.cs b/src/Main/Base/Project/Editor/CodeCompletion/ICompletionItem.cs index 068bacde20..c7871342b7 100644 --- a/src/Main/Base/Project/Editor/CodeCompletion/ICompletionItem.cs +++ b/src/Main/Base/Project/Editor/CodeCompletion/ICompletionItem.cs @@ -56,6 +56,7 @@ namespace ICSharpCode.SharpDevelop.Editor.CodeCompletion public virtual void Complete(CompletionContext context) { context.Editor.Document.Replace(context.StartOffset, context.Length, this.Text); + // In case someone calls base.Complete() and then continues using the context, update EndOffset: context.EndOffset = context.StartOffset + this.Text.Length; } } diff --git a/src/Main/Base/Project/Project/ISolutionItem.cs b/src/Main/Base/Project/Project/ISolutionItem.cs index 23eacad503..dca469f672 100644 --- a/src/Main/Base/Project/Project/ISolutionItem.cs +++ b/src/Main/Base/Project/Project/ISolutionItem.cs @@ -36,7 +36,8 @@ namespace ICSharpCode.SharpDevelop.Project /// /// Gets the ID GUID of this solution item. /// - Guid IdGuid { get; } + /// SharpDevelop will change an item's GUID in order to automatically solve GUID conflicts. + Guid IdGuid { get; set; } /// /// Gets the type GUID of this solution item. diff --git a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs index 01e8633922..a58e262b50 100644 --- a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs @@ -122,6 +122,19 @@ namespace ICSharpCode.SharpDevelop.Project public event EventHandler MinimumSolutionVersionChanged; + public override Guid IdGuid { + get { + return base.IdGuid; + } + set { + if (base.IdGuid != value) { + base.IdGuid = value; + // Save changed GUID to project file + SetPropertyInternal(null, null, ProjectGuidPropertyName, value.ToString("B").ToUpperInvariant(), PropertyStorageLocations.Base, true); + } + } + } + public string ToolsVersion { get { return projectFile.ToolsVersion; } protected internal set { @@ -181,6 +194,7 @@ namespace ICSharpCode.SharpDevelop.Project else SetProperty(null, platform, "PlatformTarget", "AnyCPU", PropertyStorageLocations.PlatformSpecific, false); LoadConfigurationPlatformNamesFromMSBuild(); + isLoading = false; } /// @@ -914,7 +928,7 @@ namespace ICSharpCode.SharpDevelop.Project propertyRemoved = true; } } - if (propertyRemoved && propertyGroup.Children.Count() == 0) + if (propertyRemoved && propertyGroup.Children.Count == 0) project.RemoveChild(propertyGroup); } } @@ -1188,7 +1202,7 @@ namespace ICSharpCode.SharpDevelop.Project #endregion #region Loading - protected bool isLoading; + protected bool isLoading = true; public MSBuildBasedProject(ProjectLoadInformation loadInformation) : base(loadInformation) @@ -1197,7 +1211,6 @@ namespace ICSharpCode.SharpDevelop.Project this.configurationNames = new MSBuildConfigurationOrPlatformNameCollection(this, false); this.platformNames = new MSBuildConfigurationOrPlatformNameCollection(this, true); - isLoading = true; bool success = false; try { try { @@ -1277,6 +1290,15 @@ namespace ICSharpCode.SharpDevelop.Project userProjectFile = ProjectRootElement.Create(userFileName, MSBuildProjectCollection); } + // Read IdGuid from project file. + string idGuidString = GetEvaluatedProperty(ProjectGuidPropertyName); + if (idGuidString != null) { + Guid idGuid; + if (Guid.TryParse(idGuidString, out idGuid)) { + // Use 'base.' to avoid writing the changed ID back into the project file + base.IdGuid = idGuid; + } + } CreateItemsListFromMSBuild(); LoadConfigurationPlatformNamesFromMSBuild(); } diff --git a/src/Main/Base/Test/Project/AddExistingProjectTests.cs b/src/Main/Base/Test/Project/AddExistingProjectTests.cs index 4857d91822..63788e0882 100644 --- a/src/Main/Base/Test/Project/AddExistingProjectTests.cs +++ b/src/Main/Base/Test/Project/AddExistingProjectTests.cs @@ -30,7 +30,8 @@ namespace ICSharpCode.SharpDevelop.Project IProject LoadProject(ProjectLoadInformation info) { var project = MockRepository.GenerateStrictMock(); - project.Stub(p => p.IdGuid).Return(projectGuid); + project.Stub(p => p.IdGuid).PropertyBehavior(); + project.IdGuid = projectGuid; project.Stub(p => p.FileName).Return(info.FileName); project.Stub(p => p.ParentSolution).Return(info.Solution); project.Stub(p => p.ParentFolder).PropertyBehavior(); @@ -67,10 +68,14 @@ namespace ICSharpCode.SharpDevelop.Project solution.AddExistingProject(project1FileName); } -// [Test] -// public void AddTwoProjectsWithSameGUID() -// { -// -// } + [Test] + public void AddTwoProjectsWithSameGUID() + { + var solution = CreateSolution(); + var project1 = solution.AddExistingProject(project1FileName); + var project2 = solution.AddExistingProject(project2FileName); + Assert.AreEqual(projectGuid, project1.IdGuid); + Assert.AreNotEqual(projectGuid, project2.IdGuid); + } } } diff --git a/src/Main/SharpDevelop/Project/SolutionFileItem.cs b/src/Main/SharpDevelop/Project/SolutionFileItem.cs index b9945d64f6..0947336c2c 100644 --- a/src/Main/SharpDevelop/Project/SolutionFileItem.cs +++ b/src/Main/SharpDevelop/Project/SolutionFileItem.cs @@ -9,12 +9,11 @@ namespace ICSharpCode.SharpDevelop.Project class SolutionFileItem : ISolutionFileItem { readonly Solution parentSolution; - readonly Guid idGuid; public SolutionFileItem(Solution parentSolution) { this.parentSolution = parentSolution; - this.idGuid = Guid.NewGuid(); + this.IdGuid = Guid.NewGuid(); } FileName fileName; @@ -35,9 +34,7 @@ namespace ICSharpCode.SharpDevelop.Project get { return parentSolution; } } - public Guid IdGuid { - get { return idGuid; } - } + public Guid IdGuid { get; set; } public Guid TypeGuid { get { return Guid.Empty; } diff --git a/src/Main/SharpDevelop/Project/SolutionFolder.cs b/src/Main/SharpDevelop/Project/SolutionFolder.cs index 3cefe1543b..c915847490 100644 --- a/src/Main/SharpDevelop/Project/SolutionFolder.cs +++ b/src/Main/SharpDevelop/Project/SolutionFolder.cs @@ -16,7 +16,7 @@ namespace ICSharpCode.SharpDevelop.Project class SolutionFolder : ISolutionFolder { readonly Solution parentSolution; - readonly Guid idGuid; + Guid idGuid; public SolutionFolder(Solution parentSolution, Guid idGuid) { @@ -114,6 +114,7 @@ namespace ICSharpCode.SharpDevelop.Project public Guid IdGuid { get { return idGuid; } + set { idGuid = value; } } public Guid TypeGuid { @@ -135,7 +136,10 @@ namespace ICSharpCode.SharpDevelop.Project throw new ProjectLoadException("Project " + fileName + " is already part of this solution."); ProjectLoadInformation loadInfo = new ProjectLoadInformation(parentSolution, fileName, fileName.GetFileNameWithoutExtension()); IProject project = SD.ProjectService.LoadProject(loadInfo); - Debug.Assert(project.IdGuid != Guid.Empty); + if (parentSolution.GetItemByGuid(project.IdGuid) != null) { + SD.Log.Warn("Added project has duplicate GUID; a new GUID will be generated."); + project.IdGuid = Guid.NewGuid(); + } this.Items.Add(project); project.ProjectLoaded(); ProjectBrowserPad.RefreshViewAsync(); diff --git a/src/Main/SharpDevelop/Project/SolutionLoader.cs b/src/Main/SharpDevelop/Project/SolutionLoader.cs index 8fb6a39d5f..6f1f96b8fd 100644 --- a/src/Main/SharpDevelop/Project/SolutionLoader.cs +++ b/src/Main/SharpDevelop/Project/SolutionLoader.cs @@ -74,6 +74,7 @@ namespace ICSharpCode.SharpDevelop.Project solutionEntries.Add(information); if (projectInfoDict.ContainsKey(information.IdGuid)) { // resolve GUID conflicts + SD.Log.WarnFormatted("Detected duplicate GUID in .sln file: {0} is used for {1} and {2}", information.IdGuid, information.ProjectName, projectInfoDict[information.IdGuid].ProjectName); information.IdGuid = Guid.NewGuid(); fixedGuidConflicts = true; } @@ -131,9 +132,11 @@ namespace ICSharpCode.SharpDevelop.Project // Now that the project configurations have been set, we can actually load the projects: int projectsLoaded = 0; foreach (var projectInfo in solutionEntries) { + // Make copy of IdGuid just in case the project binding writes to projectInfo.IdGuid + Guid idGuid = projectInfo.IdGuid; ISolutionItem solutionItem; if (projectInfo.TypeGuid == ProjectTypeGuids.SolutionFolder) { - solutionItem = solutionFolderDict[projectInfo.IdGuid]; + solutionItem = solutionFolderDict[idGuid]; } else { // Load project: projectInfo.ActiveProjectConfiguration = projectInfo.ConfigurationMapping.GetProjectConfiguration(solution.ActiveConfiguration); @@ -141,12 +144,29 @@ namespace ICSharpCode.SharpDevelop.Project using (projectInfo.ProgressMonitor = progress.CreateSubTask(1.0 / projectCount)) { solutionItem = LoadProjectWithErrorHandling(projectInfo); } + if (solutionItem.IdGuid != idGuid) { + Guid projectFileGuid = solutionItem.IdGuid; + if (!projectInfoDict.ContainsKey(projectFileGuid)) { + // We'll use the GUID from the project file. + // Register that GUID in the dictionary to avoid its use by multiple projects. + projectInfoDict.Add(projectFileGuid, projectInfo); + } else { + // Cannot use GUID from project file due to conflict. + // To fix the problem without potentially introducing new conflicts in other .sln files that contain the project, + // we generate a brand new GUID: + solutionItem.IdGuid = Guid.NewGuid(); + } + SD.Log.WarnFormatted(" in project '{0}' is '{1}' and does not match the GUID stored in the solution ({2}). " + + "The conflict was resolved using the GUID {3}", + projectInfo.ProjectName, projectFileGuid, idGuid, solutionItem.IdGuid); + fixedGuidConflicts = true; + } projectsLoaded++; progress.Progress = (double)projectsLoaded / projectCount; } // Add solutionItem to solution: SolutionFolder folder; - if (guidToParentFolderDict != null && guidToParentFolderDict.TryGetValue(projectInfo.IdGuid, out folder)) { + if (guidToParentFolderDict != null && guidToParentFolderDict.TryGetValue(idGuid, out folder)) { folder.Items.Add(solutionItem); } else { solution.Items.Add(solutionItem);