Skip to content

Commit ce72894

Browse files
authored
feat(error): add error handling and rollback support (#33)
1 parent 7c1cbb4 commit ce72894

File tree

1 file changed

+97
-57
lines changed

1 file changed

+97
-57
lines changed

src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs

Lines changed: 97 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ private void RenameProject(Project project, DTE2 dte)
8080

8181
var newName = dialog.NewProjectName;
8282
var projectFilePath = project.FullName;
83+
var originalProjectFilePath = projectFilePath;
8384

8485
// Show progress dialog
8586
var progressDialog = new RenameProgressDialog(currentName);
@@ -90,77 +91,116 @@ private void RenameProject(Project project, DTE2 dte)
9091
progressDialog.Show();
9192

9293
var stepIndex = 0;
94+
var projectRemovedFromSolution = false;
95+
var projectReaddedToSolution = false;
96+
System.Collections.Generic.List<string> referencingProjects = null;
97+
Project parentSolutionFolder = null;
9398

94-
// Step 1: Collect projects that reference this project before removal
95-
ExecuteStep(progressDialog, stepIndex++, () =>
99+
try
96100
{
97-
return ProjectReferenceService.FindProjectsReferencingTarget(dte.Solution, projectFilePath);
98-
}, out var referencingProjects);
99-
var oldProjectFilePath = projectFilePath;
101+
// Step 1: Collect projects that reference this project before removal
102+
ExecuteStep(progressDialog, stepIndex++, () =>
103+
{
104+
return ProjectReferenceService.FindProjectsReferencingTarget(dte.Solution, projectFilePath);
105+
}, out referencingProjects);
106+
var oldProjectFilePath = projectFilePath;
100107

101-
// Step 2: Capture the parent solution folder before removal
102-
ExecuteStep(progressDialog, stepIndex++, () =>
103-
{
104-
return SolutionFolderService.GetParentSolutionFolder(project);
105-
}, out var parentSolutionFolder);
108+
// Step 2: Capture the parent solution folder before removal
109+
ExecuteStep(progressDialog, stepIndex++, () =>
110+
{
111+
return SolutionFolderService.GetParentSolutionFolder(project);
112+
}, out parentSolutionFolder);
106113

107-
// Step 3: Remove project from solution before file operations
108-
ExecuteStep(progressDialog, stepIndex++, () =>
109-
{
110-
dte.Solution.Remove(project);
111-
});
114+
// Step 3: Remove project from solution before file operations
115+
ExecuteStep(progressDialog, stepIndex++, () =>
116+
{
117+
dte.Solution.Remove(project);
118+
projectRemovedFromSolution = true;
119+
});
112120

113-
// Step 4: Update RootNamespace and AssemblyName in .csproj
114-
ExecuteStep(progressDialog, stepIndex++, () =>
115-
{
116-
ProjectFileService.UpdateProjectFile(projectFilePath, currentName, newName);
117-
});
121+
// Step 4: Update RootNamespace and AssemblyName in .csproj
122+
ExecuteStep(progressDialog, stepIndex++, () =>
123+
{
124+
ProjectFileService.UpdateProjectFile(projectFilePath, currentName, newName);
125+
});
118126

119-
// Step 5: Update namespace declarations in source files
120-
ExecuteStep(progressDialog, stepIndex++, () =>
121-
{
122-
SourceFileService.UpdateNamespacesInProject(projectFilePath, currentName, newName);
123-
});
127+
// Step 5: Update namespace declarations in source files
128+
ExecuteStep(progressDialog, stepIndex++, () =>
129+
{
130+
SourceFileService.UpdateNamespacesInProject(projectFilePath, currentName, newName);
131+
});
124132

125-
// Step 6: Rename the project file on disk
126-
ExecuteStep(progressDialog, stepIndex++, () =>
127-
{
128-
return ProjectFileService.RenameProjectFile(projectFilePath, newName);
129-
}, out projectFilePath);
133+
// Step 6: Rename the project file on disk
134+
ExecuteStep(progressDialog, stepIndex++, () =>
135+
{
136+
return ProjectFileService.RenameProjectFile(projectFilePath, newName);
137+
}, out projectFilePath);
130138

131-
// Step 7: Rename parent directory if it matches the old project name
132-
ExecuteStep(progressDialog, stepIndex++, () =>
133-
{
134-
return ProjectFileService.RenameParentDirectoryIfMatches(projectFilePath, currentName, newName);
135-
}, out projectFilePath);
139+
// Step 7: Rename parent directory if it matches the old project name
140+
ExecuteStep(progressDialog, stepIndex++, () =>
141+
{
142+
return ProjectFileService.RenameParentDirectoryIfMatches(projectFilePath, currentName, newName);
143+
}, out projectFilePath);
136144

137-
// Step 8: Update references in projects that referenced this project
138-
ExecuteStep(progressDialog, stepIndex++, () =>
139-
{
140-
ProjectReferenceService.UpdateProjectReferences(referencingProjects, oldProjectFilePath, projectFilePath);
141-
});
145+
// Step 8: Update references in projects that referenced this project
146+
ExecuteStep(progressDialog, stepIndex++, () =>
147+
{
148+
ProjectReferenceService.UpdateProjectReferences(referencingProjects, oldProjectFilePath, projectFilePath);
149+
});
142150

143-
// Step 9: Re-add project to solution, preserving solution folder location
144-
ExecuteStep(progressDialog, stepIndex++, () =>
145-
{
146-
SolutionFolderService.AddProjectToSolution(dte.Solution, projectFilePath, parentSolutionFolder);
147-
});
151+
// Step 9: Re-add project to solution, preserving solution folder location
152+
ExecuteStep(progressDialog, stepIndex++, () =>
153+
{
154+
SolutionFolderService.AddProjectToSolution(dte.Solution, projectFilePath, parentSolutionFolder);
155+
projectReaddedToSolution = true;
156+
});
148157

149-
// Step 10: Update using statements across the entire solution
150-
ExecuteStep(progressDialog, stepIndex++, () =>
158+
// Step 10: Update using statements across the entire solution
159+
ExecuteStep(progressDialog, stepIndex++, () =>
160+
{
161+
SourceFileService.UpdateUsingStatementsInSolution(dte.Solution, currentName, newName);
162+
});
163+
164+
// Mark as complete and close after a brief delay
165+
progressDialog.Complete();
166+
DoEvents();
167+
System.Threading.Thread.Sleep(500);
168+
progressDialog.Close();
169+
}
170+
catch (Exception ex)
151171
{
152-
SourceFileService.UpdateUsingStatementsInSolution(dte.Solution, currentName, newName);
153-
});
172+
// Mark the current step as failed
173+
progressDialog.FailStep(stepIndex, ex.Message);
174+
DoEvents();
154175

155-
// Mark as complete and close after a brief delay
156-
progressDialog.Complete();
157-
DoEvents();
158-
System.Threading.Thread.Sleep(500);
159-
progressDialog.Close();
176+
// Attempt rollback if project was removed but not re-added
177+
if (projectRemovedFromSolution && !projectReaddedToSolution)
178+
{
179+
try
180+
{
181+
// Try to re-add the project at its current location
182+
var currentProjectPath = File.Exists(projectFilePath) ? projectFilePath : originalProjectFilePath;
183+
if (File.Exists(currentProjectPath))
184+
{
185+
SolutionFolderService.AddProjectToSolution(dte.Solution, currentProjectPath, parentSolutionFolder);
186+
}
187+
}
188+
catch
189+
{
190+
// Rollback failed, nothing more we can do
191+
}
192+
}
193+
194+
// Show error message
195+
System.Windows.MessageBox.Show(
196+
$"An error occurred while renaming the project:\n\n{ex.Message}\n\n" +
197+
"The operation has been aborted. The project may be in a partially renamed state.",
198+
"Rename Failed",
199+
System.Windows.MessageBoxButton.OK,
200+
System.Windows.MessageBoxImage.Error);
160201

161-
// TODO: Implement remaining rename operations
162-
// See open issues for requirements:
163-
// - #13: Error handling and rollback
202+
progressDialog.Close();
203+
}
164204
}
165205

166206
private void ExecuteStep(RenameProgressDialog dialog, int stepIndex, Action action)

0 commit comments

Comments
 (0)