VSSからの移行処理をC#で書いてみた

http://miau.s9.xrea.com/blog/index.php?itemid=528のvss2svn.plを参考に、VSS2005しかなくても動き、ついでにhgへの移行も出来るようにしてみた。ただしVSSのCOMモジュールは必要。

まずはVSSの履歴を取得するモジュール

using System;
using System.Data;
using System.Data.SqlClient;

using System.Collections;
using System.Collections.Generic;
using System.Text;

using System.Configuration;
using Microsoft.VisualStudio.SourceSafe.Interop;

namespace vss2svn
{
    public class VssHistory
    {
        public string VssRepositoryPath;
        public string TargetProject;
        public string WorkingDirectory;

        VSSDatabase db;


        List<IVSSItem> items;

        SqlConnection connection;
        SqlCommand historyAddCommand;
        SqlCommand historyClearCommand;
        SqlCommand historyListCommand;


        
        public VssHistory()
        {

        }
        public void init()
        {
            db = new VSSDatabase();
            db.Open(System.IO.Path.Combine(VssRepositoryPath, "srcsafe.ini"), "Admin", "");

            items = new List<IVSSItem>();

            connection = new SqlConnection();
            connection.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["default"].ConnectionString;
            connection.Open();

            historyAddCommand = new SqlCommand();
            historyAddCommand.CommandType = CommandType.Text;
            historyAddCommand.CommandText = "insert into history values(@date,@time,@file,@version,@user,@comment,@gcount)";
            historyAddCommand.Parameters.Add("@date", SqlDbType.Char, 10);
            historyAddCommand.Parameters.Add("@time", SqlDbType.Char, 5);
            historyAddCommand.Parameters.Add("@file", SqlDbType.VarChar, 1024);
            historyAddCommand.Parameters.Add("@version", SqlDbType.BigInt);
            historyAddCommand.Parameters.Add("@user", SqlDbType.VarChar, 256);
            historyAddCommand.Parameters.Add("@comment", SqlDbType.NText);
            historyAddCommand.Parameters.Add("@gcount", SqlDbType.BigInt);

            historyListCommand = new SqlCommand( "select * from history order by date, time, [file],user, version");
            historyListCommand.CommandType = CommandType.Text;

            historyClearCommand = new SqlCommand("truncate table history");
            historyClearCommand.CommandType = CommandType.Text;

            historyClearCommand.Connection = connection;
            historyClearCommand.ExecuteNonQuery();

        
        }

        public void term()
        {
            db.Close();

            historyListCommand.Dispose();
            historyAddCommand.Dispose();
            connection.Close();
            connection.Dispose();
        }
        public void loadItems(IVSSItem item)
        {
            if (item == null)
            {
                item = db.get_VSSItem(this.TargetProject, false);
            }


            if (item.Type == (int)VSSItemType.VSSITEM_PROJECT)
            {
                IVSSItems children = item.get_Items(false);
                IEnumerator e = children.GetEnumerator();
                while (e.MoveNext())
                {
                    IVSSItem child = (IVSSItem)e.Current;
                    loadItems(child);

                }
            }
            else
            {
                items.Add(item);
            }


        }
        public void BuildHistory()
        {
            for (int i = 0; i < items.Count; i++)
            {
                AddHistory(items[i]);
            }
        }
        void AddHistory(IVSSItem item)
        {
            IVSSVersions vers = item.get_Versions(0);
            IEnumerator e = vers.GetEnumerator();
            while (e.MoveNext())
            {
                IVSSVersion ver = (IVSSVersion)e.Current;
                SqlParameterCollection parameters = historyAddCommand.Parameters;

                parameters["@date"].Value = ver.Date.ToString("yyyy/MM/dd");
                parameters["@time"].Value = ver.Date.ToString("HH:mm");
                parameters["@file"].Value = item.Spec;
                parameters["@version"].Value = ver.VersionNumber;
                parameters["@user"].Value = ver.Username;
                parameters["@comment"].Value = ver.Comment;
                parameters["@gcount"].Value = 0L;

                historyAddCommand.Connection = connection;
                historyAddCommand.ExecuteNonQuery();


                System.Diagnostics.Trace.Write(ver.Date.ToString("yyyy/MM/dd") + "\t");
                System.Diagnostics.Trace.Write(ver.Date.ToString("HH:mm") + "\t");
                System.Diagnostics.Trace.Write(item.Spec + "\t");
                System.Diagnostics.Trace.Write(ver.VersionNumber + "\t");
                System.Diagnostics.Trace.Write(ver.Username + "\t");
                System.Diagnostics.Trace.WriteLine("");

            }

            
        }

        public void GetVssFile(string spec,string localFileName,int versionNumber)
        {
            IVSSItem item = db.get_VSSItem(spec, false);
            IVSSItem currentVer = item.get_Version(versionNumber);

            System.IO.FileInfo fi = new System.IO.FileInfo(localFileName);
            string dir = fi.DirectoryName;
            if (!System.IO.Directory.Exists(dir)) System.IO.Directory.CreateDirectory(dir);

            currentVer.Get(ref localFileName,(int)VSSFlags.VSSFLAG_UPDUNCH);
           

        }
        public string getLocalName(string spec)
        {
            string filename = spec.Replace(TargetProject, WorkingDirectory).Replace("/", "\\");
            return filename;
        }
        public DataSet GetHistory()
        {
            historyListCommand.Connection = connection;
            SqlDataAdapter da = new SqlDataAdapter(historyListCommand);
            DataSet result = new DataSet();
            da.Fill(result);

            return result;
        }

    }

}

svnへ履歴を登録するモジュール

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Diagnostics;

namespace vss2svn
{
    public class Vss2Svn
    {
        public string VssRepositoryPath;
        public string TargetProject;
        public string WorkingDirectory;

        public string SvnPath;

        VssHistory history;

        public Vss2Svn()
        {
            history = new VssHistory();
        }

        public void DoMigrate()
        {
            history.VssRepositoryPath = this.VssRepositoryPath;
            history.TargetProject = this.TargetProject;
            history.WorkingDirectory = this.WorkingDirectory;
            history.init();

            history.loadItems(null);
            history.BuildHistory();

            Environment.CurrentDirectory = WorkingDirectory;
            this.import();

        }
        void import()
        {
            DataSet historySet = history.GetHistory();
            string prevDate = "";
            string prevUser = "";
            string prevTime = "";
            string prevComment = "";

            string date = "";
            string time = "";
            string file = "";
            int version = 0;
            string user = "";
            string comment = "";
            int g = 0;

            DataRowCollection rows = historySet.Tables[0].Rows;
            for (int i = 0; i < rows.Count; i++)
            {
                DataRow row = rows[i];
                date = row["date"].ToString();
                time = row["time"].ToString();
                file = row["file"].ToString();
                version  = Int32.Parse( row["version"].ToString());
                user = row["user"].ToString();
                comment = row["comment"].ToString();
                g = Int32.Parse( row["gcount"].ToString());

                if ((prevDate != "") &&
                    (date == prevDate) &&
                    (user == prevUser) &&
                    (comment == prevComment))
                {
                }
                else
                {
                    SvnCommit(prevDate, prevTime, prevUser, prevComment);
                }

                string localFileName = history.getLocalName(file);
                history.GetVssFile(file, localFileName, version);
                System.Diagnostics.Trace.WriteLine("get: " + localFileName);


                prevDate = date;
                prevTime = time;
                prevUser = user;
                prevComment = comment;



            }
            //do last
            SvnCommit(prevDate, prevTime, prevUser, prevComment);
        }
        void SvnCommit(string date,string time,string user,string comment)
        {
            string cmd = "add . --force ";
            doSvnCmd(string.Format(cmd));


            cmd = "commit . -m \"{0}\" --username {1} ";
            doSvnCmd(string.Format(cmd,comment,user));
        }
        void doSvnCmd(string cmd)
        {
            System.Diagnostics.Trace.WriteLine("cmd: " + cmd);

            Process p = new Process();
            // Redirect the output stream of the child process.
            
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError= true;
            p.StartInfo.FileName = "svn";
            p.StartInfo.Arguments = cmd;
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.StartInfo.CreateNoWindow = true;
            p.Start();
            // Do not wait for the child process to exit before
            // reading to the end of its redirected stream.
            // p.WaitForExit();
            // Read the output stream first and then wait.
            string output = p.StandardOutput.ReadToEnd();
            string erroutput = p.StandardError.ReadToEnd();
            p.WaitForExit();

            System.Diagnostics.Trace.WriteLine("result: " + output + erroutput);
        }
    }
}

hgへ履歴を登録するモジュール

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Diagnostics;

namespace vss2svn
{
    public class Vss2Hg
    {
        public string VssRepositoryPath;
        public string TargetProject;
        public string WorkingDirectory;

        public string SvnPath;

        VssHistory history;

        public Vss2Hg()
        {
            history = new VssHistory();
        }

        public void DoMigrate()
        {
            history.VssRepositoryPath = this.VssRepositoryPath;
            history.TargetProject = this.TargetProject;
            history.WorkingDirectory = this.WorkingDirectory;
            history.init();

            history.loadItems(null);
            history.BuildHistory();

            Environment.CurrentDirectory = WorkingDirectory;
            this.import();

        }
        void import()
        {
            DataSet historySet = history.GetHistory();
            string prevDate = "";
            string prevUser = "";
            string prevTime = "";
            string prevComment = "";

            string date = "";
            string time = "";
            string file = "";
            int version = 0;
            string user = "";
            string comment = "";
            int g = 0;

            DataRowCollection rows = historySet.Tables[0].Rows;
            for (int i = 0; i < rows.Count; i++)
            {
                DataRow row = rows[i];
                date = row["date"].ToString();
                time = row["time"].ToString();
                file = row["file"].ToString();
                version = Int32.Parse(row["version"].ToString());
                user = row["user"].ToString();
                comment = row["comment"].ToString();
                g = Int32.Parse(row["gcount"].ToString());

                if ((prevDate != "") &&
                    ((date != prevDate) ||
                    (user != prevUser) ||
                    (comment != prevComment) ))
                {
                    HgCommit(prevDate, prevTime, prevUser, prevComment);
                }

                string localFileName = history.getLocalName(file);
                history.GetVssFile(file, localFileName, version);
                System.Diagnostics.Trace.WriteLine("get: " + localFileName);


                prevDate = date;
                prevTime = time;
                prevUser = user;
                prevComment = comment;



            }
            //do last
            HgCommit(prevDate, prevTime, prevUser, prevComment);
        }
        void HgCommit(string date, string time, string user, string comment)
        {
            string cmd = "add ";
            doHgCmd(string.Format(cmd));

            DateTime commitdate = DateTime.Parse(date + " " + time);


            cmd = "commit -m \"{0}\" --user {1} -d {2:MM/dd/yyyy}";
            doHgCmd(string.Format(cmd, ( comment == "") ? "something changed" : comment , user,commitdate));
        }
        void doHgCmd(string cmd)
        {
            System.Diagnostics.Trace.WriteLine("cmd: " + cmd);

            Process p = new Process();
            // Redirect the output stream of the child process.

            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.FileName = "hg";
            p.StartInfo.Arguments = cmd;
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.StartInfo.CreateNoWindow = true;
            p.Start();
            // Do not wait for the child process to exit before
            // reading to the end of its redirected stream.
            // p.WaitForExit();
            // Read the output stream first and then wait.
            string output = p.StandardOutput.ReadToEnd();
            string erroutput = p.StandardError.ReadToEnd();
            p.WaitForExit();

            System.Diagnostics.Trace.WriteLine("result: " + output + erroutput);
        }
    }
}

Vss2SvnかVss2Hgのインスタンスを作って必要なプロパティ設定後DoMigrate()を呼び出す。
こんな風に。

Vss2Svn migrator = new Vss2Svn();
migrator.WorkingDirectory = workingDirectory.Text;
migrator.TargetRepositoryPath = repositoryPath.Text;
migrator.TargetProject = targetProjectName.Text;
migrator.DoMigrate();

svnの場合は既存のリポジトリから移行先のディレクトリをチェックアウトし、そのディレクトリをWorkingDirectoryに設定。hgの場合は"hg init"し、そのディレクトリをWorkingDirectoryに設定。

データベースを使うのであらかじめ準備。接続設定はApp.configに書いておく。
テーブルの生成はこんな感じ

CREATE TABLE [dbo].[history](
	[date] [char](10) COLLATE Japanese_CI_AS NULL,
	[time] [char](5) COLLATE Japanese_CI_AS NULL,
	[file] [varchar](1024) COLLATE Japanese_CI_AS NULL,
	[version] [bigint] NULL,
	[user] [varchar](256) COLLATE Japanese_CI_AS NULL,
	[comment] [ntext] COLLATE Japanese_CI_AS NULL,
	[gcount] [bigint] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

mdfが使えれば多少楽になりそう、ってかSQLiteC#(.net frameworl版)にしたら良かったけど気力がない。

空のディレクトリはをサポートしない。
namespaceとかクラスメイトか適宜変更してもいい。変換クラスはtemplateMethodパターンにしたら良い感じになるかもね。