Commits

dabide  committed e58a1dd

Initial, very imperfect version... Feel free to improve!

  • Participants

Comments (0)

Files changed (11)

+syntax: glob
+Obj/
+obj/
+Bin/
+bin/
+*.user
+*.suo
+*.Cache
+*.cache
+*.bak
+*.ncb
+*.log 
+*.DS_Store
+Thumbs.db 
+thumbs.db 
+_ReSharper.*
+*.resharper
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tfs2Hg", "Tfs2Hg\Tfs2Hg.csproj", "{3A059368-FB70-4A12-B43D-38606D73D61E}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x86 = Debug|x86
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{3A059368-FB70-4A12-B43D-38606D73D61E}.Debug|x86.ActiveCfg = Debug|x86
+		{3A059368-FB70-4A12-B43D-38606D73D61E}.Debug|x86.Build.0 = Debug|x86
+		{3A059368-FB70-4A12-B43D-38606D73D61E}.Release|x86.ActiveCfg = Release|x86
+		{3A059368-FB70-4A12-B43D-38606D73D61E}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal

File Tfs2Hg/CommandLineArguments.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Tfs2Hg
+{
+    public class CommandLineArguments
+    {
+        public bool Parsed { get; private set; }
+        private readonly ILookup<String, String> arguments;
+
+        public IEnumerable<String> this[string key]
+        {
+            get { return arguments[key]; }
+        }
+
+        public bool Contains(string key)
+        {
+            return arguments.Contains(key);
+        }
+
+        public CommandLineArguments(IEnumerable<String> args)
+        {
+            try
+            {
+                var argValues = from x in args
+                                let parts = x.Split('=')
+                                select new
+                                {
+                                    Argument = parts[0],
+                                    Value = parts[1]
+                                };
+
+                arguments = argValues.ToLookup(x => x.Argument, x => x.Value);
+
+                Parsed = true;
+            }
+            catch (Exception)
+            {
+                return;
+            }
+        }
+
+        public override string ToString()
+        {
+            StringBuilder toStringBuilder = new StringBuilder();
+            foreach (var argument in arguments)
+            {
+                foreach (var argumentValue in arguments[argument.Key])
+                {
+                    toStringBuilder.AppendLine(String.Format("{0} = {1}", argument.Key, argumentValue));
+                }
+            }
+            return toStringBuilder.ToString();
+        }
+
+        public bool ContainsAll(params string[] args)
+        {
+            return args.All(argument => arguments.Contains(argument));
+        }
+    }
+}

File Tfs2Hg/HgUtility.cs

+using System;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using Microsoft.TeamFoundation.VersionControl.Client;
+
+namespace Tfs2Hg
+{
+    public class HgUtility
+    {
+        private readonly string repositoryPath;
+        private readonly string hgCommand;
+
+        public HgUtility(string repositoryPath) :  this(repositoryPath, "hg.exe")
+        {
+        }
+
+        public HgUtility(string repositoryPath, string hgCommand)
+        {
+            this.repositoryPath = repositoryPath;
+            this.hgCommand = hgCommand;
+        }
+
+        public void UpdateHgIgnore(string ignoreSpec)
+        {
+            FileInfo hgIgnoreFile = new FileInfo(Path.Combine(repositoryPath, ".hgignore"));
+            StringBuilder fileBuilder = new StringBuilder();
+            bool writeFile = false;
+            if (hgIgnoreFile.Exists)
+            {
+                fileBuilder.AppendLine(File.ReadAllText(hgIgnoreFile.FullName));
+            }
+            else
+            {
+                fileBuilder.AppendLine("syntax: glob");
+                writeFile = true;
+            }
+
+            Regex re = new Regex(@"^\s*" + Regex.Escape(ignoreSpec) + @"\s*$", RegexOptions.Multiline);
+            if (!re.IsMatch(fileBuilder.ToString()))
+            {
+                writeFile = true;
+                fileBuilder.AppendLine(ignoreSpec);
+            }
+
+            if (writeFile)
+                File.WriteAllText(hgIgnoreFile.FullName, fileBuilder.ToString());
+        }
+
+        public int CommitToHg(Changeset changeset)
+        {
+            Console.WriteLine("Committing changes to Mercurial server");
+            Directory.SetCurrentDirectory(repositoryPath);
+
+            string output;
+            string comment = String.IsNullOrEmpty(changeset.Comment) ? "(no comment)" : changeset.Comment;
+            string user = changeset.Committer.Split('\\').Last();
+            
+            double creationTimestamp = Math.Floor(changeset.CreationDate.ToTimestamp());
+            double utcOffsetSeconds = 0 - TimeZone.CurrentTimeZone.GetUtcOffset(changeset.CreationDate).TotalSeconds;
+            string date = String.Format(CultureInfo.InvariantCulture, "{0:0} {1}", creationTimestamp, utcOffsetSeconds);
+            
+            string arguments = String.Format(@"commit --addremove --message ""C{0} - {1}"" --date ""{2}"" --user ""{3}""", changeset.ChangesetId, comment.Replace("\"", "\\\""), date, user);
+
+            int returnCode = Tools.ExecuteCommand(hgCommand, arguments, out output);
+            Console.WriteLine(output);
+
+            if (returnCode == 0 || output.StartsWith("nothing changed"))
+            {
+                Console.WriteLine("Commit OK");
+                return 0;
+            }
+            else
+            {
+                Console.WriteLine("Commit failed - exiting");
+                return returnCode;
+            }
+        }
+
+        public int Initialize()
+        {
+            Console.WriteLine("Initializing repository");
+            Directory.SetCurrentDirectory(repositoryPath);
+            string output;
+            int returnCode = Tools.ExecuteCommand(hgCommand, "init", out output);
+            Console.WriteLine(output);
+            return returnCode;
+        }
+
+        public bool IsRepository()
+        {
+            return Directory.Exists(Path.Combine(repositoryPath, ".hg"));
+        }
+    }
+}

File Tfs2Hg/LICENSE.txt

+Copyright (c) 2010 Bouvet ASA / David A. Sjøen <david.sjoen@bouvet.no>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

File Tfs2Hg/Program.cs

+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using Microsoft.TeamFoundation.VersionControl.Client;
+
+namespace Tfs2Hg
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            if (args.Length == 1 && args[0] == "--license")
+            {
+                DisplayLicense();
+                return;
+            }
+
+            CommandLineArguments commandLineArgs = new CommandLineArguments(args);
+  
+            string tfsUriString = commandLineArgs["--tfsUri"].FirstOrDefault();
+            string repositoryPath = commandLineArgs["--repositoryPath"].FirstOrDefault();
+            string user = commandLineArgs["--user"].FirstOrDefault();
+            string password = commandLineArgs["--password"].FirstOrDefault();
+            string hgCommand = commandLineArgs["--hgPath"].FirstOrDefault();
+
+            if (!commandLineArgs.Parsed
+                || !Uri.IsWellFormedUriString(tfsUriString, UriKind.Absolute)
+                || String.IsNullOrEmpty(repositoryPath)
+                || String.IsNullOrEmpty(user) && !String.IsNullOrEmpty(password)
+                || !String.IsNullOrEmpty(user) && String.IsNullOrEmpty(password)
+                )
+            {
+                Console.WriteLine(Usage.Short);
+                Environment.Exit(255);
+            }
+
+            Uri tfsUri = new Uri(tfsUriString);
+            NetworkCredential credentials = (user != null) ? new NetworkCredential(user, password) : null;
+        
+            try
+            {
+                if (!Directory.Exists(repositoryPath))
+                {
+                    Directory.CreateDirectory(repositoryPath);
+                }
+
+                HgUtility hgUtility = String.IsNullOrEmpty(hgCommand) ? new HgUtility(repositoryPath) : new HgUtility(repositoryPath, hgCommand);
+                TfsUtility tfsUtility = new TfsUtility(repositoryPath, tfsUri, credentials);
+
+                if (!hgUtility.IsRepository())
+                {
+                    int returnCode = hgUtility.Initialize();
+                    if (returnCode != 0)
+                        Environment.Exit(returnCode);
+                }
+
+                hgUtility.UpdateHgIgnore(".lastTfsChangeSet");
+
+                FileInfo lastTfsChangeSetFile = new FileInfo(Path.Combine(repositoryPath, ".lastTfsChangeSet"));
+
+                ChangesetVersionSpec firstUnfectchedChangesetVersion = GetFromVersionSpec(lastTfsChangeSetFile);
+
+                Console.WriteLine("Getting history - this might take a couple of seconds...");
+                var lastChangeSetInTfs = tfsUtility.GetChangesetHistory(VersionSpec.Latest, VersionSpec.Latest).SingleOrDefault();
+
+                if (lastChangeSetInTfs != null && lastChangeSetInTfs.ChangesetId < firstUnfectchedChangesetVersion.ChangesetId)
+                {
+                    Console.WriteLine("No newer changesets available");
+                    return;
+                }
+
+                var changeSets = tfsUtility.GetChangesetHistory(firstUnfectchedChangesetVersion, null);
+
+                foreach (Changeset changeset in changeSets)
+                {
+                    Console.WriteLine("Getting changeset {0} ({1}) by {2} on {3}", changeset.ChangesetId, changeset.Comment, changeset.Committer, changeset.CreationDate);
+                    
+                    tfsUtility.GetChangeset(changeset);
+
+                    int returnCode = hgUtility.CommitToHg(changeset);
+
+                    Console.WriteLine("-------------------------------------");
+
+                    if (returnCode == 0)
+                    {
+                        // Write latest changeset version to file
+                        File.WriteAllText(lastTfsChangeSetFile.FullName, changeset.ChangesetId.ToString());
+                    }
+                    else
+                    {
+                        Environment.Exit(returnCode);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine("An exception occurred: {0}", ex);
+                Environment.Exit(255);
+            }
+        }
+
+        private static void DisplayLicense()
+        {
+            Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Tfs2Hg.LICENSE.txt");
+            if (resourceStream != null)
+            {
+                using (StreamReader streamReader = new StreamReader(resourceStream))
+                {
+                    string license = streamReader.ReadToEnd();
+                    Console.WriteLine(license);
+                }
+            }
+        }
+
+        private static ChangesetVersionSpec GetFromVersionSpec(FileInfo lastTfsChangesetFile)
+        {
+            int lastFetchedChangeset = lastTfsChangesetFile.Exists ? Int32.Parse(File.ReadAllText(lastTfsChangesetFile.FullName).Trim()) : 0;
+
+            return new ChangesetVersionSpec(lastFetchedChangeset + 1);
+        }
+    }
+}

File Tfs2Hg/Properties/AssemblyInfo.cs

+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Tfs2Hg")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("Tfs2Hg")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2010")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("12ee354c-4888-43e2-88a7-4e6af739b212")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

File Tfs2Hg/Tfs2Hg.csproj

+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <ProductVersion>8.0.30703</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{3A059368-FB70-4A12-B43D-38606D73D61E}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Tfs2Hg</RootNamespace>
+    <AssemblyName>Tfs2Hg</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <TargetFrameworkProfile>
+    </TargetFrameworkProfile>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <PlatformTarget>x86</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <PlatformTarget>x86</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="DevExpress.Data.v10.1, Version=10.1.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a, processorArchitecture=MSIL" />
+    <Reference Include="DevExpress.Utils.v10.1, Version=10.1.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a, processorArchitecture=MSIL" />
+    <Reference Include="DevExpress.XtraEditors.v10.1, Version=10.1.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a, processorArchitecture=MSIL" />
+    <Reference Include="Microsoft.TeamFoundation.Client, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
+    <Reference Include="Microsoft.TeamFoundation.VersionControl.Client, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="CommandLineArguments.cs" />
+    <Compile Include="HgUtility.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="TfsUtility.cs" />
+    <Compile Include="Tools.cs" />
+    <Compile Include="Usage.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="LICENSE.txt" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

File Tfs2Hg/TfsUtility.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Web;
+using Microsoft.TeamFoundation.Client;
+using Microsoft.TeamFoundation.VersionControl.Client;
+
+namespace Tfs2Hg
+{
+    public class TfsUtility
+    {
+        private readonly string workspaceFolder;
+        private readonly TfsTeamProjectCollection teamProjectCollection;
+        private readonly VersionControlServer versionControlServer;
+        private readonly Workspace workspace;
+
+        public TfsUtility(string workspaceFolder, Uri tfsUri)
+            : this(workspaceFolder, tfsUri, null)
+        {
+        }
+
+        public TfsUtility(string workspaceFolder, Uri tfsUri, NetworkCredential credentials)
+        {
+            this.workspaceFolder = workspaceFolder;
+
+            Uri tfsServerUri = GetServerUri(tfsUri);
+            teamProjectCollection = (credentials == null) ? new TfsTeamProjectCollection(tfsServerUri) : new TfsTeamProjectCollection(tfsServerUri, credentials);
+
+            teamProjectCollection.Authenticate();
+
+            versionControlServer = teamProjectCollection.GetService<VersionControlServer>();
+            workspace = versionControlServer.TryGetWorkspace(workspaceFolder);
+            if (workspace == null)
+            {
+                string workspaceName = String.Format("Tfs2Hg_{0:N}", Guid.NewGuid());
+                string userName = (credentials != null) ? credentials.UserName : Environment.UserName;
+
+                string serverItem = GetServerItem(tfsUri);
+
+                workspace = versionControlServer.CreateWorkspace(workspaceName, userName, null, new[] { new WorkingFolder(serverItem, workspaceFolder) });
+            }
+        }
+
+        private static string GetServerItem(Uri tfsUri)
+        {
+            string path = HttpUtility.UrlDecode(tfsUri.PathAndQuery);
+            return String.Format("${0}", path);
+        }
+
+        private static Uri GetServerUri(Uri tfsUri)
+        {
+            return new Uri(tfsUri.GetLeftPart(UriPartial.Authority));
+        }
+
+        public IEnumerable<Changeset> GetChangesetHistory(VersionSpec fromVersionSpec, VersionSpec toVersionSpec)
+        {
+            return versionControlServer.QueryHistory(workspaceFolder, VersionSpec.Latest, 0, RecursionType.Full, null, fromVersionSpec, toVersionSpec, Int32.MaxValue, false, false)
+                .OfType<Changeset>()
+                .OrderBy(x => x.ChangesetId);
+        }
+
+        public void GetChangeset(Changeset changeset)
+        {
+            ChangesetVersionSpec changesetVersionSpec = new ChangesetVersionSpec(changeset.ChangesetId);
+            workspace.Get(changesetVersionSpec, GetOptions.Overwrite);
+        }
+    }
+}

File Tfs2Hg/Tools.cs

+using System;
+using System.Diagnostics;
+
+namespace Tfs2Hg
+{
+    public static class Tools
+    {
+        public static int ExecuteCommand(string fileName, string arguments, out string output)
+        {
+            Process p = new Process
+                            {
+                                StartInfo = {UseShellExecute = false, RedirectStandardOutput = true, FileName = fileName, Arguments = arguments}
+                            };
+            p.Start();
+            output = p.StandardOutput.ReadToEnd();
+            p.WaitForExit();
+
+            return p.ExitCode;
+        }
+        
+        /// <summary>
+        /// method for converting a System.DateTime value to a UNIX Timestamp
+        /// </summary>
+        /// <param name="value">date to convert</param>
+        /// <returns></returns>
+        public static double ToTimestamp(this DateTime value)
+        {
+            //create Timespan by subtracting the value provided from
+            //the Unix Epoch
+            TimeSpan span = (value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0));
+
+            //return the total seconds (which is a UNIX timestamp)
+            return span.TotalSeconds;
+        }
+    }
+}

File Tfs2Hg/Usage.cs

+namespace Tfs2Hg
+{
+    public static class Usage
+    {
+        public static string Short
+        {
+            get
+            {
+                return
+                    @"Tfs2Hg - Get all changesets from a TFS project and commit them to a Mercurial repository, one by one.
+If missing, the repository folder is created, mapped to a new workspace and a Mercurial repository is initialized.
+
+Usage:
+Tfs2Hg --tfsUri=""URI of the project in TFS"" --repositoryPath=""path to local repository folder"" [--hgPath=""path to hg.exe""] [--user=""TFS user name"" --password=""TFS password""]
+Tfs2Hg --license";
+            }
+        }
+    }
+}