Introduction
Like mcarbenay, I needed and still need to remove or change source control information for a project or solution. If you have ever done it, you know it's a pain even if it really shouldn't be. The way it is hard coded into the project and solution files is pretty much ridiculous and one can spend a long time trying to do such a simple thing, especially with web applications.
So of course, I looked to see if someone already got frustrated enough to make a nice tool that does it all for me. And there was one! You can get the corresponding article here. Unfortunately, that tool doesn't handle many project types. In particular, template projects (*.etp) are not processed. Also, the way web projects are processed works about two thirds of the time for me.
Following these observations, I downloaded the source to see if I could patch it to suit my needs and then give back my contribution. Well, things being as they are, I figured it would take me less time to code it from scratch, taking into account that I would like to support even more project types in the near future and easily upgrade the parsing code for any new VS.NET versions.
So here you have the result: a tiny 200 lines program that relies heavily on regular expressions. There's not much more to say about it than that. I think the code speaks for itself, so I simply copied it in the following section.
Using the code
#region Using directives
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.DirectoryServices;
using System.IO;
using System.Text.RegularExpressions;
#endregion
namespace RemoveSCCInfo
{
public sealed class SCCInfoRemover
{
#region Constructors
private SCCInfoRemover()
{
}
#endregion
#region Methods
public static void RemoveSCCInfo(string path, bool backup)
{
if (path == null || path == "")
throw new ArgumentNullException("path");
if (!File.Exists(path))
throw new FileNotFoundException("Cannot" +
" find the path specified.", path);
if (Path.GetExtension(path).ToLower() == ".sln")
CleanSolution(path, backup);
else
CleanProject(path, backup);
}
private static void CleanSolution(string path, bool backup)
{
if (path == null || path == "")
throw new ArgumentNullException("path");
if (!File.Exists(path))
throw new FileNotFoundException("Cannot" +
" find the path specified.", path);
string solution;
using (StreamReader sr = new StreamReader(path))
{
solution = sr.ReadToEnd();
}
Regex regex = new Regex(@"
\tGlobalSection\(SourceCodeControl\).*\r\n #begin of SCC section
(
(.+\r\n)*? #any number of lines
\t\tSccProjectUniqueName\d+\s*=\s*(?<name>.+)\r\n #unique name
(.+\r\n)*? #any number of lines
\t\tSccLocalPath\d+\s*=\s*(?<local>.+)\r\n #local name
)* #rinse and repeat for all projects
(.+\r\n)*? #any number of lines
\tEndGlobalSection\r\n #end of SCC section
", RegexOptions.IgnoreCase | RegexOptions.Multiline |
RegexOptions.ExplicitCapture |
RegexOptions.IgnorePatternWhitespace);
Match m = regex.Match(solution);
if (m.Success)
{
HybridDictionary projects = new HybridDictionary();
Group nameGroup = m.Groups["name"];
Group localGroup = m.Groups["local"];
for (int i = 0; i < nameGroup.Captures.Count; i++)
projects.Add(nameGroup.Captures[i].Value.Replace(@"\\",
@"\"), localGroup.Captures[i].Value);
string solutionDir = Path.GetDirectoryName(path);
foreach (DictionaryEntry project in projects)
{
string name = (string) project.Key;
string local = (string) project.Value;
if (name.StartsWith("http://"))
{
if (!local.StartsWith("http://"))
{
int pos = name.LastIndexOf('/') + 1;
string fileName =
name.Substring(pos, name.Length - pos);
CleanProject(Path.Combine(solutionDir, local +
Path.DirectorySeparatorChar + fileName), backup);
}
else
{
local = GetLocalPath(name);
if (local != null)
CleanProject(local, backup);
}
}
else
{
CleanProject(Path.Combine(solutionDir, name), backup);
}
}
if (backup)
BackupFiles(path);
RemoveReadOnlyAttribute(path);
using (StreamWriter sw = new StreamWriter(path, false))
{
sw.Write(solution.Replace(m.Value, ""));
}
CleanSCCFiles(path, backup);
}
}
private static void CleanProject(string path, bool backup)
{
if (path == null || path == "")
throw new ArgumentNullException("path");
if (!File.Exists(path))
throw new FileNotFoundException("Cannot" +
" find the path specified.", path);
string project;
using (StreamReader sr = new StreamReader(path))
{
project = sr.ReadToEnd();
}
Regex regex = new Regex(@"^.*Scc\w+\s*=\s*.+\r\n",
RegexOptions.IgnoreCase | RegexOptions.Multiline |
RegexOptions.ExplicitCapture |
RegexOptions.IgnorePatternWhitespace);
project = regex.Replace(project, "");
regex = new Regex(@"
.*<SOURCECONTROLSETTINGS>\r\n #begin SCC settings section
(.+\r\n)*? #any number of lines
.*</SOURCECONTROLSETTINGS>\r\n #end SCC settings section
", RegexOptions.IgnoreCase | RegexOptions.Multiline |
RegexOptions.ExplicitCapture |
RegexOptions.IgnorePatternWhitespace);
project = regex.Replace(project, "");
if (backup)
BackupFiles(path);
RemoveReadOnlyAttribute(path);
using (StreamWriter sw = new StreamWriter(path, false))
{
sw.Write(project);
}
CleanSCCFiles(path, backup);
}
private static void CleanSCCFiles(string path, bool backup)
{
if (path == null || path == "")
throw new ArgumentNullException("path");
if (!File.Exists(path))
throw new FileNotFoundException("Cannot" +
" find the path specified.", path);
path = Path.GetDirectoryName(path);
DeleteFiles(Directory.GetFiles(path, "*.scc"), backup);
DeleteFiles(Directory.GetFiles(path, "*.vssscc"), backup);
DeleteFiles(Directory.GetFiles(path, "*.vspscc"), backup);
}
private static void DeleteFiles(string[] files, bool backup)
{
if (files == null)
throw new ArgumentNullException("files");
if (files.Length > 0)
{
if (backup)
BackupFiles(files);
foreach (string file in files)
{
RemoveReadOnlyAttribute(file);
File.Delete(file);
}
}
}
private static void BackupFiles(params string[] files)
{
if (files == null)
throw new ArgumentNullException("files");
string backupDirName = Path.DirectorySeparatorChar +
"Backup_" + DateTime.Now.ToString("yyyy-MM-dd") +
Path.DirectorySeparatorChar;
foreach (string file in files)
{
string dir = Path.GetDirectoryName(file) + backupDirName;
Directory.CreateDirectory(dir);
File.Copy(file, dir + Path.GetFileName(file), true);
}
}
private static void RemoveReadOnlyAttribute(string path)
{
if (path == null || path == "")
throw new ArgumentNullException("path");
FileAttributes attributes = File.GetAttributes(path);
if ((attributes & FileAttributes.ReadOnly) != 0)
File.SetAttributes(path, attributes & (~FileAttributes.ReadOnly));
}
private static string GetLocalPath(string url)
{
if (url == null || url == "")
throw new ArgumentNullException("url");
string local = null;
try
{
Uri uri = new Uri(url);
string adsiPath = String.Format("IIS://{0}/W3SVC/1/Root{1}",
uri.Host, Path.GetDirectoryName(uri.AbsolutePath));
DirectoryEntry dirEntry = new DirectoryEntry(adsiPath);
string wwwPath = dirEntry.Properties["Path"].Value.ToString();
local = Path.Combine(wwwPath, Path.GetFileName(uri.AbsolutePath));
}
catch (Exception ex)
{
Trace.WriteLine(ex.Source);
Trace.WriteLine(ex.Message);
Trace.WriteLine(ex.StackTrace);
}
return local;
}
#endregion
}
}
History
- 1.0: supports C#, VB.NET, ASP.NET, deployment projects, and project templates.