Visual Studio 2008 と Visual Source Safe(VSS)のバインド情報を取得する。

サンプルコード

Visual Studio 2008 と Visual Source Safe はチェックイン・チェックアウトの操作をIDE上から実施できるように、プロジェクトとソリューションをVSSのプロジェクト(フォルダ)と対応付けして連携して処理ができるようにしています。このバインド情報は、ツールバーの「ソース管理」-[ソース管理の変更]から設定します。

今回は、このバインド情報を解析して整理した上で、以下のクラスに取り込む処理を記載します。

まずは、バインド情報がどのような形式でVisualStudioが記録しているかを見ていきます。調べてみると、バインド情報はソリューションファイル(.sln)とソースコードコントロールファイル(mssccprj.scc)に記録されています。mssccprj.sccファイルは、.slnと同じフォルダに存在します。

ソリューションファイル(GlobalSection(SourceCodeControl)セクションに該当情報が設定されています。)

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "VssTest", "VssTest\VssTest.vbproj", "{75C5AB46-8C4F-4B56-8456-826462859A43}"
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "VssTest2", "VssTest2\VssTest2.vbproj", "{D671519E-B335-4AAD-AE3F-CB6B16CA48B9}"
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "WindowsApplication1", "WindowsApplication1\WindowsApplication1.vbproj", "{D7547178-6BD0-459C-A604-3963E72BD1D7}"
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "WindowsApplication2", "WindowsApplication2\WindowsApplication2.vbproj", "{8F29E98F-A144-428E-ADDD-E62E4B45B077}"
EndProject
Global


GlobalSection(SourceCodeControl) = preSolution
SccNumberOfProjects = 5
SccLocalPath0 = .
SccProjectUniqueName1 = VssTest2\\VssTest2.vbproj
SccProjectName1 = \u0022$/VssEraser/VssTest2\u0022,\u0020QAAAAAAA
SccLocalPath1 = VssTest2
SccProjectUniqueName2 = VssTest\\VssTest.vbproj
SccProjectName2 = \u0022$/VssEraser/VssTest\u0022,\u0020CAAAAAAA
SccLocalPath2 = VssTest
SccProjectUniqueName3 = WindowsApplication1\\WindowsApplication1.vbproj
SccLocalPath3 = .
SccProjectFilePathRelativizedFromConnection3 = WindowsApplication1\\
SccProjectUniqueName4 = WindowsApplication2\\WindowsApplication2.vbproj
SccLocalPath4 = .
SccProjectFilePathRelativizedFromConnection4 = WindowsApplication2\\
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{75C5AB46-8C4F-4B56-8456-826462859A43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75C5AB46-8C4F-4B56-8456-826462859A43}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C5AB46-8C4F-4B56-8456-826462859A43}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C5AB46-8C4F-4B56-8456-826462859A43}.Release|Any CPU.Build.0 = Release|Any CPU
{D671519E-B335-4AAD-AE3F-CB6B16CA48B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D671519E-B335-4AAD-AE3F-CB6B16CA48B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D671519E-B335-4AAD-AE3F-CB6B16CA48B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D671519E-B335-4AAD-AE3F-CB6B16CA48B9}.Release|Any CPU.Build.0 = Release|Any CPU
{D7547178-6BD0-459C-A604-3963E72BD1D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7547178-6BD0-459C-A604-3963E72BD1D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7547178-6BD0-459C-A604-3963E72BD1D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7547178-6BD0-459C-A604-3963E72BD1D7}.Release|Any CPU.Build.0 = Release|Any CPU
{8F29E98F-A144-428E-ADDD-E62E4B45B077}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F29E98F-A144-428E-ADDD-E62E4B45B077}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F29E98F-A144-428E-ADDD-E62E4B45B077}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F29E98F-A144-428E-ADDD-E62E4B45B077}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

ソースコードコントロールファイル(mssccprj.scc)


SCC = This is a Source Code Control file

[VssTest.sln]
SCC_Aux_Path = "C:\Users\Administrator\Documents\vss"
SCC_Project_Name = "$/VssEraser", BAAAAAAA

後は上記の情報を抜き出して整理します。整理用のコードは以下となります。


Imports System.Collections.Generic
Imports System.Runtime.InteropServices

Public Class VssBindInformation

Public SolutionFullName As String = String.Empty
Public SolutionVssSpecPath As String = String.Empty
Public SolutionVssSpec As String = String.Empty
Public Projects As New Dictionary(Of String, VssProjectBindInfo)

Public Class VssProjectBindInfo
Public ProjectFullName As String = String.Empty
Public LocalPath As String = String.Empty
Public VssSpecPath As String = String.Empty
End Class

_
Private Shared Function GetPrivateProfileString(ByVal lpAppName As String, _
ByVal lpKeyName As String, _
ByVal lpDefault As String, _
ByVal lpReturnedString As System.Text.StringBuilder, _
ByVal nSize As Integer, _
ByVal lpFileName As String) As Integer
End Function

Public Sub New(ByVal solutionFileName As String)

Me.SolutionFullName = solutionFileName

' Solution 情報の抽出
Dim solutionFolder As String = System.IO.Path.GetDirectoryName(solutionFileName)
Dim solutionVssFile As String = System.IO.Path.Combine(solutionFolder, "mssccprj.scc")

If System.IO.File.Exists(solutionVssFile) = True Then

Dim sb As New System.Text.StringBuilder
sb.Capacity = 255

GetPrivateProfileString(solutionFileName, _
"SCC_Project_Name", _
String.Empty, _
sb, _
sb.Capacity, _
solutionVssFile)

Me.SolutionVssSpecPath = GetSolutionVssSpecPath(sb.ToString) + "/"
Me.SolutionVssSpec = GetSolutionVssSpecPath(sb.ToString) + "/" + solutionFileName

End If

' Project 情報の抽出
Dim lines As String() = System.IO.File.ReadAllLines(solutionFileName)

Dim isStart As Boolean = False
Dim sccSection As New Dictionary(Of String, String)
For i As Integer = 0 To lines.Length - 1

Dim line As String = lines(i)

If line.IndexOf("GlobalSection(SourceCodeControl)", StringComparison.CurrentCultureIgnoreCase) > 0 Then
isStart = True
Continue For
End If

If isStart = True AndAlso line.IndexOf("EndGlobalSection", StringComparison.CurrentCultureIgnoreCase) > 0 Then
Exit For
End If

If isStart = True Then

Dim key As String = String.Empty
Dim value As String = String.Empty

Dim splitPos As Integer = line.IndexOf("=")
If splitPos > 0 Then

key = line.Substring(0, splitPos).TrimEnd.Replace(vbTab, String.Empty)
value = line.Substring(splitPos + 1).TrimStart

sccSection.Add(key, value)

End If

End If

Next

If sccSection.ContainsKey("SccNumberOfProjects") = True Then

Dim projectCount As Integer = CInt(sccSection.Item("SccNumberOfProjects"))

For i As Integer = 0 To projectCount - 1

Dim projectKeyName As String = "SccProjectName" + i.ToString
Dim projectUniqueKeyName As String = "SccProjectUniqueName" + i.ToString
Dim projectRelativeKeyName As String = "SccProjectFilePathRelativizedFromConnection" + i.ToString

If ((sccSection.ContainsKey(projectKeyName) = True OrElse sccSection.ContainsKey(projectRelativeKeyName))) AndAlso _
(sccSection.ContainsKey(projectUniqueKeyName)) = True Then

Dim item As New VssProjectBindInfo

item.ProjectFullName = GetAbsolutePath(solutionFolder, sccSection.Item(projectUniqueKeyName).Replace("\\", "\"))
item.LocalPath = System.IO.Path.GetDirectoryName(item.ProjectFullName)

If sccSection.ContainsKey(projectKeyName) = True Then
item.VssSpecPath = GetVssSpecPath(sccSection.Item(projectKeyName))
Else
item.VssSpecPath = Me.SolutionVssSpecPath + sccSection.Item(projectRelativeKeyName).Replace("\\", String.Empty)
End If

Me.Projects.Add(item.ProjectFullName.ToLower, item)

End If

Next

End If

End Sub

Private Function GetAbsolutePath(ByVal basePath As String, ByVal relatePath As String) As String

Dim targetBasePath As String = basePath
If basePath.Length > 0 Then
If (basePath.Substring(basePath.Length - 1, 1) <> "\") AndAlso (basePath.Substring(basePath.Length - 1, 1) <> "/") Then
targetBasePath += "\"
End If
End If

Dim baseUri As New System.Uri(targetBasePath)
Dim absUri As New System.Uri(baseUri, relatePath)

Return absUri.LocalPath

End Function

Private Function GetSolutionVssSpecPath(ByVal sccProjectName As String) As String

Dim line As String = sccProjectName
Dim pos As Integer = 0

pos = sccProjectName.IndexOf(",")
If pos > 0 Then
line = line.Substring(0, pos)
End If

line = line.Replace("""", String.Empty)

Return line

End Function

Private Function GetVssSpecPath(ByVal projectKeyName As String) As String

Dim line As String = projectKeyName
Dim pos As Integer = 0

pos = projectKeyName.IndexOf(",")
If pos > 0 Then
line = line.Substring(0, pos)
End If

pos = projectKeyName.IndexOf("$")
If pos > 0 Then
line = line.Replace(line.Substring(0, pos), String.Empty)
End If

Return line

End Function

End Class

WinMergeをコマンドラインから利用する。

WinMergeは各種のツールと連携ができるようにコマンドラインインタフェースを備えています。今回は、コマンドラインインタフェースを使用してVisualSourceSafeから最新を取得したフォルダとチェックイン前のローカルの作業フォルダを渡して比較する例を記載してみます。


Dim args As String = String.Empty
args = " /r /u /wl /wr /dl ""{0}"" /dr ""{1}"" ""{2}"" ""{3}"" "
args = String.Format(args, "VSS(VisualSourceSafe上のファイル)", "Local(編集中のファイル)", _leftPath, _rightPath)
Dim p As System.Diagnostics.Process = System.Diagnostics.Process.Start(WIN_MERGE_PATH, args)

指定しているオプションは、「サブフォルダを再帰的に比較」「ファイルを最近追加したリスト(MRU)に追加しない」「左側を読取専用」「右側を読取専用」「左側のタイトルを"VSS(VisualSourceSafe上のファイル)"」「右側のタイトルを"Local(編集中のファイル)"」に設定しています。コマンドラインの末尾の_leftPathと_rightPathには比較するフォルダのパス名を指定します。上記のオプションでWinMergeを起動すると以下のように表示されます。


以下は、WinMergeのヘルプの引用です。様々な事が出来るようなので参考にどうぞ。

コマンドライン

                                                                                                                                                              • -

WinMergeコマンドラインは、比較するパスに加えいくつかのパラメータを受け付けます。 これらのパラメータのほとんどは、外部比較ツールとして使用したり、バッチファイルから比較操作を 開始できるようにすることを目的としています。

WinMergeコマンドラインから実行するには以下の書式のうちの1つを使用します:

WinMerge[U] [/?]

WinMerge[U] [/r] [/e] [/f filter] [/x] [/s] [/ul] [/ur] [/um] [/u] [/wl] [/wr] [/wm] [/minimize] [/maximize] [/dl leftdesc] [/dr rightdesc] leftpath [middlepath] rightpath [/o outputpath]

WinMerge[U] conflictfile

コマンドには2つの形式があります:

WinMergeU
もし、Unicode版のWinMergeを実行しているならば、WinMergeU を使用してください。Unicode版のWinMergeは最近のWindowsにインストールされます。

WinMerge
もし、ANSI版のWinMergeを実行しているならば、WinMerge を使用してください。ANSI版のWinMergeWindows 9xや WMEにインストールされます。

パラメータなしかパス名を入力すると単にWinMergeウインドウを開きます。 パラメータは、スラッシュ( / ) か、ダッシュ ( - ) 文字が 前に付加されます。パス名にはプリフィックス文字を付加しません。

/? このトピックのWinMergeヘルプを開きます。

/r すべてのサブフォルダ内のすべてのファイルを比較します(再帰比較)。 ユニークフォルダ (片方のみ存在するフォルダ)は、分離された項目として比較結果内にリストされます。 サブフォルダまで含めるとかなり比較時間が増大してしまうことに注意してください。 このパラメータを指定しなかった場合、WinMergeは比較するフォルダ内のファイルとトップレベルのサブフォルダのみリストします。 サブフォルダの中までは比較しません。

/e EscキーでWinMergeが閉じるようにします。 WinMergeを外部比較アプリケーションとして使用する場合に便利です。 (ダイアログのようにすばやくWinMergeを閉じることができます) この引数を指定しなかった場合、すべてのウインドウを閉じるのに何回もEscキーを 押さなければならないことになるかもしれません。

/f 比較を制限するために、指定したフィルタを適用します。 フィルタは*.h *.cppのようなファイルマスクか、 XML/HTML Develのようなファイルフィルタの名前です。 スペースを含むフィルタマスクやフィルタ名はダブルクォーテーションマークで括ってください。

/x 同一ファイルの比較をしたときにWinMergeを閉じます。 (情報ダイアログを表示した後) このパラメータは比較後に効果がなくなります。 例えば、もしファイルがマージか編集の結果として同一となった場合です。 このパラメータは、WinMergeを外部アプリケーションとして使用したり、 差異のないファイルを無視することによって余分なステップを取り除きたい場合に便利です。

/s WinMergeウインドウを1つのインスタンスに制限します。 例えば、WinMergeが既に実行中ならば、新しい比較は同じインスタンス内で実行されます。 この引数を指定しなかった場合、複数のウインドウが開かれる可能性があります: 設定によっては、新しい比較が既に存在するウインドウで実行されることも新しいウインドウで 実行されることもあります。

/ul 左側パスが最近使用した項目(MRU)リストに追加されるのを防ぎます。 外部アプリケーションは、ファイルまたはフォルダの選択ダイアログのMRUリストにパスを 追加するべきではありません。

/ur 右側パスが最近使用した項目(MRU)リストに追加されるのを防ぎます。 外部アプリケーションは、ファイルまたはフォルダの選択ダイアログのMRUリストにパスを 追加するべきではありません。

/um 中央のパスが最近使用した項目(MRU)リストに追加されるのを防ぎます。 外部アプリケーションは、ファイルまたはフォルダの選択ダイアログのMRUリストにパスを 追加するべきではありません。

/u (または/ub) 各々(左、右、中央)のパスが最近使用した項目(MRU)リストに追加されるのを防ぎます。 外部アプリケーションは、ファイルまたはフォルダの選択ダイアログのMRUリストにパスを 追加するべきではありません。

/wl 読み取り専用として左側を開きます。 比較時、左側を変更したくない場合に使用してください。

/wr 読み取り専用として右側を開きます。 比較時、右側を変更したくない場合に使用してください。

/wr 読み取り専用として中央を開きます。 比較時、中央を変更したくない場合に使用してください。

/minimize 最小化状態でWinMergeを開始します。 このオプションは長時間かかる比較を行う場合に便利です。

/maximize 最大化状態でWinMergeを開始します。

/dl 左側タイトルバーの説明を指定します。 デフォルトのフォルダやファイル名テキストに上書きされます。例: /dl "Version 1.0" や /dl WorkingCopy. スペースを含む説明はダブルクォーテーションマークで括ってください。

/dr /dlと同様に右側タイトルバーの説明を指定します。

/dm /dlと同様に中央タイトルバーの説明を指定します。

leftpath 左側で開くフォルダやファイルを指定します。

middle 中央で開くフォルダやファイルを指定します。

right 右側で開くフォルダやファイルを指定します。

WinMergeは、ファイルとフォルダを比較できません。そのためパスパラメータ両方 (leftpath と rightpath) には、同じ種類(フォルダかファイルのどちらか) を指し差なければなりません。 もし、WinMergeが指定したパスのどちらかを見つけることができなければ、 ファイルまたはフォルダ選択ダイアログを開きます。 ファイルまたはフォルダ選択ダイアログでは、正しいパスを選択できます。

ティップ
ファイル比較では、パスパラメータの一つとしてフォルダ名を指定できます。 ただし、フォルダは他方のファイル名と同じファイルを含んでいる必要があります。

例えば、以下のコマンドを考えます:

WinMergeU C:\Folder\File.txt C:\Folder2

もし、C:\Folder2が ファイル名File.txtを含んでいれば、 WinMergeは、暗黙的にファイル指定として第二のパスを解決します。 当然ながら、C:\Folder2がファイル名 File.txtを含んでいなければ、コマンドは不正とみなされます。

outputpath マージした結果のファイルを保存するオプションの出力フォルダを指定します。

出力パスはコマンドラインからWinMergeを開始する時まれにしか必要となりません。 それはバージョンコントロールツールとともに使用されることになります。 結果ファイルとして出力パスを指定する必要があるかもしれません。 もし、出力パスを指定したら、WinMergeはファイル比較ウインドウで2つのファイルのみ を表示します。しかしながら、もしそれらのファイルのいずれかを保存したならば、 それは出力パスに書かれ、2つの元ファイルは前の状態のままになります。

バージョンコントロールシステムは一般的にtheirsや mine、mergedかまたはresolved のような用語を使用し元と結果ファイルを参照します。 もし、WinMergeコマンドラインに出力パスを指定し、バージョンコントロールシステムと連携するならば、 この順番でファイルを並べるべきです。

conflictfile コンフリクトファイルを指定します。 コンフリクトファイルは通常バージョンコントロールシステムによって生成されます。 コンフリクトファイルはファイル比較ウインドウで開かれ、 「コンフリクトファイルの解決」で説明している様にマージやコンフリクトを解決することができます。 コンフリクトファイルと共に他のパスは使用できないことに注意してください。

VssWinMergeComparer。VisualStudio2008アドイン。(VisualSourceSafe2005の最新バージョンと編集中の作業ディレクトリをWinMergeで比較する。)

VisualSourceSafeの最新バージョンと作業フォルダの編集中のファイルをWinMergeで比較するVisualStudio2008のアドインを作成しましたので公開します。

VssWinMergeComparer

バイナリモジュールVssWinMergeComparer-bin.zip 直
ソースコードVssWinMergeComparer-src.zip 直
動作環境:VisualStudio 2005/2008, VisualSourceSafe 2005, WinMerge

■インストール方法(5 step)

  1. バイナリモジュールを解凍する。
  2. Releseフォルダのファイルを、任意の場所に配置します。
  3. VssWinMergeComparer.AddInファイルを、マイドキュメント\Visual Studio 2008\Addinsに配置します。
  4. VssWinMergeComparer.AddInファイルを修正します。[Addin]-[Assembly]セクションのファイルの場所をReleaseフォルダの場所に修正します。
  5. Release/appConfig.xmlを修正します。環境に合わせてWinMergeのパス、vssのパス、vssのユーザ名、vssのパスワードを指定します。
VssWinMergeComparer.AddIn
<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
	<HostApplication>
		<Name>Microsoft Visual Studio</Name>
		<Version>9.0</Version>
	</HostApplication>
	<HostApplication>
		<Name>Microsoft Visual Studio</Name>
		<Version>8.0</Version>
	</HostApplication>
	<Addin>
		<FriendlyName>VssWinMergeComparer</FriendlyName>
		<Description>VisualSourceSafeの最新バージョンと作業ディレクトリをWinMergeで比較します。</Description>
		<Assembly>C:\Tools\VssWinMergeComparer\Release\VssWinMergeComparer.dll</Assembly>
		<FullClassName>VssWinMergeComparer.Connect</FullClassName>
		<LoadBehavior>1</LoadBehavior>
		<CommandPreload>0</CommandPreload>
		<CommandLineSafe>0</CommandLineSafe>
	</Addin>
</Extensibility>
Release/appConfig.xml
<?xml version="1.0" encoding="utf-8" ?>
<config>
  <winmergepath>C:\Program Files\WinMerge\WinMergeU.exe</winmergepath>
  <vsspath>C:\vss\srcsafe.ini</vsspath>
  <vssuser>tekk</vssuser>
  <vsspw>tekkpw</vsspw>
</config>