跟踪 Microsoft 解决方案( 一个较新的 SCM 工具)
一个小型实用程序,

引言
Newer
是一款用于比较随时间推移拍摄的解决方案快照的应用程序。当您的任务是快速评估新开发发生在哪里、先前快照何时过时以及何时发生这些更改(无论是否连接到您的 SCM 服务器)时,此工具将非常有用。在紧急情况下,Newer
可以用作您的 SCM 系统,而不是作为 SCM 系统的补充。
注意:我使用“软件配置管理 (SCM)”一词而不是“修订或版本控制软件或系统 (VCS)”,因为 Newer
主要是一个变更跟踪工具,而不是数据库文件差异工具。
背景
此工具最初是为了在特定开发环境中快速检查较新的源文件。我想要一个可以让我轻松看到哪些文件比给定日期和时间“更新”的工具。因此,应用程序的名字就叫 Newer
,尽管功能不断扩展,我也没有更改它。我的动机是需要逐日查看大型解决方案中哪些文件被更新了。我们当前使用的 SCM 系统 **Seapine Surround SCM** 允许您从存储库分支根目录或子文件夹获取最新版本的文件副本,并附带签入时间戳。Newer
的第一个版本(如上所示)有一个按钮用于选择“获取到”文件夹,一个日期选择器和小时选择器,以及两个按钮用于显示比选定日期和小时更新或更旧的文件列表。这对于跟踪单个快照中的签入时间戳来说都很好。
第一次扩展
我以为我完成了一个小小的临时解决方案,但然后我想到了一个方法来比较在不同日期拍摄的两个快照。嘿,为什么不扩展 Newer
工具并添加该功能呢?(听起来有点熟悉吧?)我添加了第二个文件夹选择器和更多的按钮,用于列出第一个文件夹中独有的文件、第二个文件夹中独有的文件、两个文件夹中内容相同的共同文件,以及内容不同但相同的共同文件——即更新。我添加了复选框,用于选择每个列表中显示的文件类型,并添加了计数逻辑来显示每种文件类型的数量。由于列表可能很长,我添加了一个搜索功能,用于查找文件列表中字符串的第一个或下一个出现(不区分大小写),以及一个按钮,用于将搜索的起始点设置为已选择文本所在的行或开始的行。Newer
现在看起来是这样的

越多越好
很好,然后我想检查我们混合平台、混合时代的解决方案中使用的已保存的可执行文件和其他一些文件类型。请注意独立的 .vcproj 和 .vcxproj 复选框。为什么不添加一两个复选框和计数器呢?(这应该听起来也很熟悉。)此外,我发现我需要一种方法来使内部字典在单独添加文件、删除文件或更改选定文件夹中的任何文件时间戳时失效。而且我想确保在找到搜索字符串时正确编号行。但这会破坏文件列表的“选择和复制”功能,如果行号是硬编码的。因此,对于这两者,我添加了一个“Renew”按钮来清除和重建内部字典,以及一个“line #'s”复选框。现在,显示两个快照的“Diff”文件,Newer
看起来是这样的

担忧的蔓延
说到想确保,我开始怀疑Newer
在受到挑战比较“特殊”文件夹(无论是意外还是故意)时,会如何处理文件列表。如果我简单地使用 MessageBox.Show
来处理异常情况,Newer
用户会不会厌倦确认或取消对话框?直接退出 Newer
,然后重新启动,并避免重新选择特殊文件夹,会不会更快一些?这是我想避免的事情。所以,代替显示消息框(最初),我添加了将 IO 异常记录在内部目录中的功能,以及正常的文件条目。然后,文件列表代码可以一次性处理所有异常条目。至少,异常条目可以与“良好”条目一起列出。我认为“最低限度”对我来说是不错的。(请注意,在其他一些我仍然认为有意义的地方,我仍然使用了 MessageBox.Show
。)
以下是我 C:\Windows 中早于 2014 年 1 月 15 日的文件列表。内部使用了 SortedDirectory<string, DateTime>
。这就是为什么所有异常的“*”条目都显示在列表的开头。列表已向下滚动了一些,以显示编号的“良好”条目从哪里开始。

我想指出代码中实现这一点的地方。以下是 TransverseTree
方法开头的简化列表,该方法对 Newer
中的列表生成至关重要,并且很大程度上直接复制自 MSDN 中 TraverseTree
的非递归方法。请参见参考 (1)。
private DateTime TransverseTree(string root, SortedDictionary<string, DateTime> fileDict ... )
{
Queue<string> dirs = new Queue<string>(20);
if (!Directory.Exists(root))
{
throw new ArgumentException(); // let system handle
}
dirs.Enqueue(root);
while (dirs.Count > 0)
{
string currentDir = dirs.Dequeue();
string[] subDirs;
try
{
subDirs = Directory.GetDirectories(currentDir); // absolute paths
}
catch (Exception e)
{
//Save exception in fileDict.
fileDict["* " + currentDir + " GetDirectories Exception " + e.Message] = DateTime.Now;
continue;
}
...
越来越严重
Newer
很有用,它已经发展到我想要认真考虑可用性(但我不想真正做到重新设计、重构或重写它)。所以,本着良好的意图和最佳计划,我决定进一步“增强”它。
技巧 #1
Newer
的主窗口宽度已达 1030 像素。我不想给假设的用户带来一个过宽且包含他们不需要或无法使用的功能的应用程序。这是我的技巧:将主 Window
的宽度增加到例如 1330,添加额外的功能控件和代码隐藏,然后通过将 Window
宽度设置回 1030 来隐藏 UI 的那部分,并在 CodeProject 文章中告知潜在用户,如果需要,他们可以拖宽应用程序以查看添加的功能。主窗口的初始宽度很容易在 **MainWindow.xaml** 中控制。这是该文件开头的内容
<Window x:Name="Newer" x:Class="Newer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Newer" Height="492" Width="1030"
Icon="Newer.ico" SizeChanged="Newer_SizeChanged">
我想解释 SizeChanged
的处理程序,但首先让我展示一下经过改造的 Newer
界面的两面,显示一个“Same”列表


您会注意到两个按钮和一个右侧的复选框上的红色文本。当 Newer
变宽以显示这些控件时,左侧“Choose root 1”的 `Foreground` 也在 Newer_SizeChanged
处理程序中设置为红色。这是为了指示根目录 1 中的文件和目录如果按下“Del EqEqvR1”或“Clean”,实际上是可以被删除的。通往“Del EqEqvR1”含义的路径有点曲折。
“我希望我很特别”
步骤 1:我想消除那些编译成相同代码但显示为不同的文件。在我们公司,我们正在进行 Doxygen 热潮,双斜杠和三斜杠在不同文件版本(.h、.hpp、.c、.cpp、.cs)中飞来飞去。
如果从列表中选择一个通用文件名并按“Eqv1”按钮,Newer
将用根目录 2 中文件的内容重写根目录 1 中选定的文件,如果它们逐行等效。这里的“等效”意味着在语法上等效,例如可能不同的空格或注释,但语义相同。我们可以争论这个按钮的 `Content` 是否也应该是红色的。
步骤 2:如果我想消除整个解决方案中不重要的区别,为什么还要要求选择每个通用文件名并单击一个按钮?如果选中“Eqv1All on Diff”框,则在按“Diff”时,“Eqv1”功能将针对每个可能的等效文件对调用,如果它们等效,则替换根目录 1 文件,否则文件名将显示在“Diff”列表中。
步骤 3:为什么还要保留根目录 1 中与根目录 2 中的文件二进制相同或语法等效的文件?如果您按下“Del EqEqvR1”,只有本质上不同的选定文件类型才会保留在根目录 1 中。未选定的文件类型不会被触及。这是紧急 SCM 的基础。
3a:制作或获取开发根目录的较新副本。
3b:在 Newer
中,选择旧副本作为根目录 1,选择新副本作为根目录 2。选中所有文件类型(包括“other”)。单击“Del EqEqvR1”。
3c:归档现在缩减的文件集,并在需要时恢复它们。在我的情况下,一个较新的根目录为 300+ MB,而缩减后的集合通常为 10 MB 左右,适用于典型的几周开发。
步骤 4:通过仅保留可恢复的文件差异而不是整个文件来进一步扩展(对我来说太多了——此时我将依赖真正的 SCM)。
步骤 0:默认情况下,**Surround SCM** 的 'get' 会保存只读副本。如果您打算在此文件夹中进行更新或删除,请务必更改根目录 1 文件夹以删除只读属性。选择“将更改应用于此文件夹、子文件夹和文件”。其他 SCM(git clone、perforce Get Latest Revision、svn checkout 等)可能需要您考虑其他因素。
比较扩展
如果还不明显,Newer
也是试图从任何单个 SCM 中夺取功能的尝试。有许多 SCM。它们都有一些或许多功能。对于 SCM 列表,请参阅下面参考 (2) 的索引。
我认为将功能保留在任何单个 SCM 之外会更容易,也更可取,当它们可以包含在像 Newer
这样的东西中时。也许我的下一个世界将不再使用 **Surround SCM**。Newer
应该仍然可用。
因此,“Diff”按钮会查找两个快照中不同的文件。该怎么办?让我们来看看。一个很好的工具是 **Beyond Compare**。Newer
将首先在 Environment.SpecialFolder.ProgramFilesX86
中查找可执行文件“BCompare.exe”。如果找不到,它会在 Environment.SpecialFolder.ProgramFiles
中查找。如果找不到,它就完了。
比较文件差异:选择“Diff”或“Same”列表中文件的部分文本。然后单击“BC”按钮。它在“other”复选框上方。
为什么要比较“Same”文件?嗯,有完全相同和语法上相同。后者可能值得比较以检查注释更改。难道不知道哪些文件是相同的,哪些只是等效的,会更好吗?以下是“Same”列表的详细信息
注意两个以“~”开头的 .cpp 文件名。这两个文件在根目录 1 和根目录 2 中仅语法等效。比较其中一个将显示注释或空格差异。
简短说明:**Beyond Compare** 有一个“忽略不重要的差异”视图按钮, 或(在 4.0 版本中)
,将不重要的差异视为相同。这是我用来检查等效性代码的一个很好的二次意见。**Beyond Compare** 还提供对包括 .ico 在内的几种图像文件类型的视觉比较。在提交本文时,Scooter Software 刚刚开始进行 4.0 版本的公开 Beta 测试。我仍在研究其“Ad Hoc Unimportant Text”功能。4.0 还可以访问程序内的子版本存储库和 Dropbox 存储。
为了不过于赞美 **Beyond Compare**,您可能会问:“为什么不全部在 BC 中完成呢?” 经过一点工作,您可以设置一个“几天前”的过滤器来检查日期。我不知道如何计算一个根文件夹中给定类型的文件。重新初始化内部字典需要文件夹选择和菜单选择(或按 Shift-F5)。只需单击几下,您就可以向下钻取文件夹路径来查找一些文件差异,但不是全部。很难或不可能看到一组被拒绝访问的文件。选择和清理一组文件类型很困难。删除两个文件夹之间所有相同或等效的文件对我来说是不可能的,或者我不知道。总之,Newer
超越了 **Beyond Compare**。
“Same”列表中的其他文件名,即相同文件的文件名,前面实际上有一个空格。我决定违反我的“不加修饰的选择和复制”目标,以便在类似 cmd sort
的情况下对相同和等效的名称进行排序。您可以处理这个额外的第一个字符,或者您可以更改 bw_Same
处理程序中的代码。这是相关部分
string f1 = Path.Combine(root1, s);
string f2 = Path.Combine(root2, s);
if (FilesAreEqual(f1, f2)) // without regarding any BOM
names.Add(" " + s); // denote identical with " "
else if (ext == ".cpp" || ext == ".cs" ||
ext == ".h" || ext == ".c" || ext == ".hpp")
{
if (CommentableTextFilesEqual(f1, f2, false))
names.Add("~" + s); // denote equivalent with "~"
}
我确实想指出代码中的一个功能,我没想到会需要它。(功能扩展产生小扩展)。当 **Beyond Compare** 作为单独的进程启动并在您完成比较或编辑后关闭时,Newer
文件列表中的文本选择就会丢失。这是我们想避免的“我刚才在哪?”中断之一。快速修复是保存全局最后选择的起始索引和选择长度。这些会在文件列表文本框失去焦点时保存,并在启动 **Beyond Compare** 后恢复。
如果您想使用与 **Beyond Compare** 不同的比较工具,我将指出发生这种情况的处理程序
private void bcButton_Click(object sender, RoutedEventArgs e)
{
string filename = CkComparable(); // verify a comparable file pair
if (filename.Length == 0)
return;
string bcPath = GetBCPath(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86));
if (bcPath.Length == 0)
bcPath = GetBCPath(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles));
if (bcPath.Length == 0)
{
searchString.Text = "wah";
return;
}
string argString = "\"" + Path.Combine(root1, filename) + "\" \"" +
Path.Combine(root2, filename) + "\"";
try
{
System.Diagnostics.Process.Start("\"" + bcPath + "\"", argString); //start Beyond Compare
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
textBox1.Focus();
textBox1.SelectionStart = lastTextBoxSelectionStart;
textBox1.SelectionLength = lastTextBoxSelectionLength;
}
BOM 是否是一个值得关注的问题?嗯,是的。如果我们比较源文件,一个快照可能有一个文件的 ACSII 版本,而第二个快照可能有一个 UTF-8 或 UNICODE 版本的文件。在我检查过的代码库中,我看到了文件类型 .h、.cpp、.cs、.xsd、.xml、.sln、.xaml 和 .csproj 的混合 BOM/无 BOM 情况,但只有一两个情况下 BOM 的使用情况发生了变化。
真正的问题是,许多 UTF-8 源文件没有 BOM。如果没有对非 BOM 的源文件进行预扫描以查找非 ASCII 字符(单字节值大于 127),那么很难知道正确的编码来将文件读取到内存中,而内存中的一切都是 Unicode 字符。
对于等效性测试,Newer
尝试通过 BOM 来处理编码,但它不会进行完整的预扫描来查找非 BOM 的 UTF-8 文件。非常罕见的情况下,“Same”可能会出错,“Diff”可能会列出一个实际上相同或等效的文件,其中 BOM 的使用已更改。我认为性能上的权衡证明不进行完整预扫描是合理的。
要查看这种罕见情况,文件 **superAscii.txt**(一种“other”文件类型)在 **CompareFolder1** 中不带 BOM,而在 **CompareFolder2** 中带 BOM,包含在解决方案下载中。它们是相同的 UTF-8 文件。您可能还有兴趣查看参考 (3) 中的表格。
清除
如果您在一个选定的根文件夹中进行构建或分析,您可能会累积 Visual Studio 没有删除的文件。我添加了一个“Clean”按钮来执行一些清理,这样这些文件就不会干扰“Same”和“Diff”。实际上使用了两个字典来执行此操作。一个处理具有单个扩展名的可清理文件。需要第二个字典来处理像 **MyCSharpFile.g.i.cs** 这样的过度点文件。这些字典在 MainWindow
构造函数中的 InitializeComponent
之后立即初始化。以下是 initFixedDicts
方法的简化部分
private void initFixedDicts()
{
...
cleanAnyDict[".baml"] = 1;
cleanAnyDict[".bi"] = 1;
cleanAnyDict[".bsc"] = 1;
cleanAnyDict[".cache"] = 1;
cleanAnyDict[".cdf"] = 1;
cleanAnyDict[".exp"] = 1;
cleanAnyDict[".idb"] = 1;
cleanAnyDict[".ilk"] = 1;
...
cleanAnyDict2[".CodeAnalysisLog.xml"] = 1;
cleanAnyDict2[".csproj.GenerateResource.Cache"] = 1;
cleanAnyDict2[".csprojResolveAssemblyReference.cache"] = 1;
cleanAnyDict2[".exe.config"] = 1;
cleanAnyDict2[".g.cs"] = 1;
cleanAnyDict2[".g.i.cs"] = 1;
...
}
善意提醒:当您单击“Clean”时,Newer
中没有确认。如果您打算使用此按钮,请仔细检查这两个目录中的文件类型,确保不包含您不想清理的文件。您可以根据需要添加或删除这些字典中的文件类型,以适应您的环境。
还有一个“delEmpty dirs on clean”复选框,当清理后留下或发现子文件夹为空时,它将删除 bin、obj 和其他目录。如果您要最小化一个旧快照,我建议勾选此框,然后在单击“Del EqEqvR1”后单击“Clean”。
Newer 是否还在扩展?它是如何工作的?
最后两个增强功能显示了活动和一个帮助窗体。
创建“Same”或“Diff”文件列表或删除相等和等效文件可能需要一段时间。Newer
在执行某些操作时会显示省略号。


但您如何知道它没有挂起呢?如果您在单击“Root 1 unique”、“Root 2 unique”……“Root 2 older”或“Del EqEqvR1”之前勾选了“Show activity”框,Newer
将在检查文件以显示其工作状态时在 Search string TextBox 中显示文件名。不要在启动其中一个长时间的比较或删除功能后勾选此框,并期望活动立即显示。Newer
将忙于无法注意到(目前)。
最后,如果您忘记了所有这些描述,单击“?”按钮将显示一些功能的备忘单。注意:**HelpForm.exe** 是它自己的可执行文件,需要位于您启动 **Newer.exe** 或 **NewerPy.exe** 的目录中。下载应该已经处理了这一点。
最后最后,我添加了两个“->clip”按钮,用于将任何文件列表的完整路径复制到剪贴板。我决定不为带空格的路径添加周围的引号,但这也很容易进一步扩展。如果您已列出“Root 1 unique”文件并单击根目录 2 的“->clip”,您将获得不存在文件的路径。但这也许正是您想要的。最后,这当然证明了文章创作比功能扩展花费的时间更长。
防止扩展
仅供参考。如果您使用的是 Windows 8 或更高版本,您可能已经遇到过运行从 CodeProject 下载的可执行文件时出现的问题。我以前提供过二进制文件以及 Visual Studio 解决方案。在新系统上重新构建解决方案的好处是您不会遇到这个问题。坏处是您需要 Visual Studio 才能轻松构建“已知”到您系统的程序。
我认为提供预构建的二进制文件仍然是有价值的。这是快速查看某物是否值得使用的简便方法,或者在阅读文章时进行跟读。在 Win 8 及以上版本上,如果程序未签名,则只需多执行两个步骤(一次性)即可完成此操作。
步骤 1。双击 .exe 文件,“Windows Smart Screen”会显示“Windows protected your PC”。单击“More info”。“More info”不会显示为一个链接,但它确实是一个链接,它将带您进入步骤 2。
步骤 2。您应该可以选择“Run Anyway”或“Don't Run”。由于可执行文件来自 CodeProject,并且作者是可识别的,我认为运行它相当安全。它和 Win 8 之前一样安全,一旦您选择“Run anyway”,Win 8[+] 将不再就该程序打扰您。
也许我们下次可以一起研究签名。一个起点是参考 (4) 中的 5 星提示。
修改代码
好吧,这都可以,但我真的解决了用其他语言跟踪解决方案的问题吗(还没有)。我想展示一个小型、简化的表格,其中包含一些其他语言
语言 |
注释 |
扩展 |
F# |
// /// (*...*) |
.fs .fsi |
Python |
# |
.py |
VB |
' |
.vb .vbs |
HTML, XML |
<!-- ... --> |
.htm .html .xml |
JavaScript |
// /*...*/ |
.js |
显示需要更改以处理新文件扩展名和检查其他语言等效性的 Newer
代码片段将是繁琐且无趣的。我所做的替代方法是在可下载的解决方案中包含一个 NewerPy
项目。
作为代码更改位置的示例,NewerPy
已将“.h, .hpp”复选框和计数器替换为“.py”的计数器。在 CommentableTextFilesEqual
用于类 C 文件的地方,NewerPy
有一个 CommentablePyFilesEqual
方法来比较 .py 文件的等效性。
顺便说一句,一些有用的 Python 脚本的来源在参考 (5) 中。一篇关于使用 Python 和 C# 的 CodeProject 文章在参考 (6) 中。
如果您想定制适合您的开发环境的 Newer
版本,这应该会有帮助。只需使用 **Beyond Compare** 比较 Newer
和 NewerPy
文件夹即可。大多数更改都在 **MainWindow.xaml** 和 **MainWindow.xaml.cs** 中。我说使用 Newer
本身来检查差异,但在这种情况下,Newer
提供的比管理 **Beyond Compare** 的执行能力多不了多少。
对于一个免费的差异工具(而不是 **Beyond Compare**),总有 **WinMerge**。请参阅下面的参考 (7)。
如果您只想查看界面更改并尝试比较几个 Python 快照,请在 Solution Explorer 中将 Startup Project 更改为 NewerPy
并试用。
参考文献
(1) 如何:迭代目录树 (C# 编程指南) 我认为最初使用队列处理目录(更好的目录顺序)。现在使用了堆栈。MSDN 中的方法也命名为“TraverseTree”,而不是 Newer
中的“TransverseTree”。“Traverse”是更准确的词。
(2) 将 Beyond Compare 与版本控制系统结合使用
(3) 比较 Windows-1252、ISO-8859-1、IS0-8859-15 中的字符,比较表
(4) 如何成为自己的证书颁发机构并创建您自己的证书来签名代码文件。作者: , 2013 年 3 月 1 日
(5) ActiveState Code >> 热门 Python 脚本的食谱
(6) Python、Visual Studio 和 C#……太棒了 作者: , 2013 年 9 月 23 日
(7) WinMerge 命令行 WinMerge 首页在此 此处
(8) 关于拥有和想要偏爱
历史
- 提交至 CodeProject 2014 年 2 月 12 日