Files
git-tfs/GitTfs/Commands/Branch.cs
2013-04-05 11:05:39 +02:00

294 lines
12 KiB
C#

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using NDesk.Options;
using Sep.Git.Tfs.Core;
using Sep.Git.Tfs.Core.TfsInterop;
using StructureMap;
namespace Sep.Git.Tfs.Commands
{
[Pluggable("branch")]
[Description("branch\n\n" +
" * Display remote TFS branches:\n git tfs branch -r\n git tfs branch -r -all\n\n" +
" * Create a TFS branch from current commit:\n git tfs branch $/Repository/ProjectBranchToCreate <myWishedRemoteName> --comment=\"Creation of my branch\"\n\n" +
" * Rename a remote branch:\n git tfs branch --move oldTfsRemoteName newTfsRemoteName\n\n" +
" * Delete a remote branche:\n git tfs branch --delete tfsRemoteName\n\n" +
" * Initialise an existing remote TFS branch:\n git tfs branch --init $/Repository/ProjectBranch\n git tfs branch --init $/Repository/ProjectBranch myNewBranch\n git tfs branch --init --all\n git tfs branch --init --tfs-parent-branch=$/Repository/ProjectParentBranch $/Repository/ProjectBranch\n")]
[RequiresValidGitRepository]
public class Branch : GitTfsCommand
{
private Globals globals;
private TextWriter stdout;
private readonly Help helper;
private readonly Cleanup cleanup;
private readonly InitBranch initBranch;
public bool DisplayRemotes { get; set; }
public bool ManageAll { get; set; }
public bool ShouldRenameRemote { get; set; }
public bool ShouldDeleteRemote { get; set; }
public bool ShouldInitBranch { get; set; }
public string Comment { get; set; }
public string TfsUsername { get; set; }
public string TfsPassword { get; set; }
public string AuthorsFilePath { get; set; }
public string ParentBranch { get; set; }
public OptionSet OptionSet
{
get {
return new OptionSet
{
{ "r|remotes", "Display the TFS branches of the current TFS root branch existing on the TFS server", v => DisplayRemotes = (v != null) },
{ "all", "Display (used with option --remotes) the TFS branches of all the root branches existing on the TFS server\n or Initialize (used with option --init) all existing TFS branches (For TFS 2010 and later)", v => ManageAll = (v != null) },
{ "comment=", "Comment used for the creation of the TFS branch ", v => Comment = v },
{ "m|move", "Rename a TFS remote", v => ShouldRenameRemote = (v != null) },
{ "delete", "Delete a TFS remote", v => ShouldDeleteRemote = (v != null) },
{ "init", "Initialize an existing TFS branch", v => ShouldInitBranch = (v != null) },
{ "b|tfs-parent-branch=", "TFS Parent branch of the TFS branch to clone (TFS 2008 only! And required!!) ex: $/Repository/ProjectParentBranch", v => ParentBranch = v },
{ "u|username=", "TFS username", v => TfsUsername = v },
{ "p|password=", "TFS password", v => TfsPassword = v },
{ "a|authors=", "Path to an Authors file to map TFS users to Git users", v => AuthorsFilePath = v },
}
.Merge(globals.OptionSet);
}
}
public Branch(Globals globals, TextWriter stdout, Help helper, Cleanup cleanup, InitBranch initBranch)
{
this.globals = globals;
this.stdout = stdout;
this.helper = helper;
this.cleanup = cleanup;
this.initBranch = initBranch;
}
public void SetInitBranchParameters()
{
initBranch.TfsUsername = TfsUsername;
initBranch.TfsPassword = TfsPassword;
initBranch.AuthorsFilePath = AuthorsFilePath;
initBranch.CloneAllBranches = ManageAll;
initBranch.ParentBranch = ParentBranch;
}
public bool IsCommandWellUsed()
{
//Verify that some mutual exclusive options are not used together
return new[] {ShouldDeleteRemote, ShouldInitBranch, ShouldRenameRemote}.Count(b => b) <= 1;
}
public int Run()
{
if (!IsCommandWellUsed())
return helper.Run(this);
if (ShouldRenameRemote || ShouldDeleteRemote)
return helper.Run(this);
if (ShouldInitBranch)
{
SetInitBranchParameters();
return initBranch.Run();
}
return DisplayBranchData();
}
public int Run(string param)
{
if (!IsCommandWellUsed())
return helper.Run(this);
if (ShouldRenameRemote)
return helper.Run(this);
if (ShouldInitBranch)
{
SetInitBranchParameters();
return initBranch.Run(param);
}
if (ShouldDeleteRemote)
return DeleteRemote(param);
return CreateRemote(param);
}
public int Run(string param1, string param2)
{
if (!IsCommandWellUsed())
return helper.Run(this);
if (ShouldDeleteRemote)
return helper.Run(this);
if (ShouldInitBranch)
{
SetInitBranchParameters();
return initBranch.Run(param1, param2);
}
if (ShouldRenameRemote)
return RenameRemote(param1, param2);
return CreateRemote(param1, param2);
}
private int RenameRemote(string oldRemoteName, string newRemoteName)
{
var newRemoteNameExpected = globals.Repository.AssertValidBranchName(newRemoteName.ToGitRefName());
if (newRemoteNameExpected != newRemoteName)
stdout.WriteLine("The name of the branch after renaming will be : " + newRemoteNameExpected);
if (globals.Repository.HasRemote(newRemoteNameExpected))
{
throw new GitTfsException("error: this remote name is already used!");
}
stdout.WriteLine("Cleaning before processing rename...");
cleanup.Run();
globals.Repository.MoveRemote(oldRemoteName, newRemoteNameExpected);
if(globals.Repository.RenameBranch(oldRemoteName, newRemoteName) == null)
stdout.WriteLine("warning: no local branch found to rename");
return GitTfsExitCodes.OK;
}
private int CreateRemote(string tfsPath, string gitBranchNameExpected = null)
{
tfsPath.AssertValidTfsPath();
Trace.WriteLine("Getting commit informations...");
var commit = globals.Repository.GetCurrentTfsCommit();
if(commit == null)
throw new GitTfsException("error : the current commit is not checked in TFS!");
var remote = commit.Remote;
Trace.WriteLine("Creating branch in TFS...");
remote.Tfs.CreateBranch(remote.TfsRepositoryPath, tfsPath, (int)commit.ChangesetId, Comment ?? "Creation branch " + tfsPath);
Trace.WriteLine("Init branch in local repository...");
return initBranch.Run(tfsPath, gitBranchNameExpected);
}
private int DeleteRemote(string remoteName)
{
var remote = globals.Repository.ReadTfsRemote(remoteName);
if (remote == null)
{
throw new GitTfsException(string.Format("Error: Remote \"{0}\" not found!", remoteName));
}
stdout.WriteLine("Cleaning before processing delete...");
cleanup.Run();
globals.Repository.DeleteTfsRemote(remote);
return GitTfsExitCodes.OK;
}
public int DisplayBranchData()
{
// should probably pull this from options so that it is settable from the command-line
const string remoteId = GitTfsConstants.DefaultRepositoryId;
var tfsRemotes = globals.Repository.ReadAllTfsRemotes();
if (DisplayRemotes)
{
if (!ManageAll)
{
var remote = globals.Repository.ReadTfsRemote(remoteId);
stdout.WriteLine("\nTFS branch structure:");
WriteRemoteTfsBranchStructure(remote.Tfs, stdout, remote.TfsRepositoryPath, tfsRemotes);
return GitTfsExitCodes.OK;
}
else
{
var remote = tfsRemotes.First(r => r.Id == remoteId);
if (!remote.Tfs.CanGetBranchInformation)
{
throw new GitTfsException("error: this version of TFS doesn't support this functionality");
}
foreach (var branch in remote.Tfs.GetBranches().Where(b=>b.IsRoot))
{
var root = remote.Tfs.GetRootTfsBranchForRemotePath(branch.Path);
var visitor = new WriteBranchStructureTreeVisitor(remote.TfsRepositoryPath, stdout, tfsRemotes);
root.AcceptVisitor(visitor);
}
return GitTfsExitCodes.OK;
}
}
WriteTfsRemoteDetails(stdout, tfsRemotes);
return GitTfsExitCodes.OK;
}
public static void WriteRemoteTfsBranchStructure(ITfsHelper tfsHelper, TextWriter writer, string tfsRepositoryPath, IEnumerable<IGitTfsRemote> tfsRemotes = null)
{
var root = tfsHelper.GetRootTfsBranchForRemotePath(tfsRepositoryPath);
if (!tfsHelper.CanGetBranchInformation)
{
throw new GitTfsException("error: this version of TFS doesn't support this functionality");
}
var visitor = new WriteBranchStructureTreeVisitor(tfsRepositoryPath, writer, tfsRemotes);
root.AcceptVisitor(visitor);
}
private void WriteTfsRemoteDetails(TextWriter writer, IEnumerable<IGitTfsRemote> tfsRemotes)
{
writer.WriteLine("\nGit-tfs remote details:");
foreach (var remote in tfsRemotes)
{
writer.WriteLine("\n {0} -> {1} {2}", remote.Id, remote.TfsUrl, remote.TfsRepositoryPath);
writer.WriteLine(" {0} - {1} @ {2}", remote.RemoteRef, remote.MaxCommitHash, remote.MaxChangesetId);
}
}
private class WriteBranchStructureTreeVisitor : IBranchTreeVisitor
{
private readonly TextWriter _stdout;
private readonly string _targetPath;
private readonly IEnumerable<IGitTfsRemote> _tfsRemotes;
public WriteBranchStructureTreeVisitor(string targetPath, TextWriter writer, IEnumerable<IGitTfsRemote> tfsRemotes = null)
{
_targetPath = targetPath;
_stdout = writer;
_tfsRemotes = tfsRemotes;
}
public void Visit(BranchTree branch, int level)
{
for (var i = 0; i < level; i++ )
_stdout.Write(" | ");
_stdout.WriteLine();
for (var i = 0; i < level - 1; i++)
_stdout.Write(" | ");
if (level > 0)
_stdout.Write(" +-");
_stdout.Write(" {0}", branch.Path);
if (_tfsRemotes != null)
{
var remote = _tfsRemotes.FirstOrDefault(r => r.TfsRepositoryPath == branch.Path);
if (remote != null)
_stdout.Write(" -> " + remote.Id);
}
if (branch.Path.Equals(_targetPath))
_stdout.Write(" [*]");
_stdout.WriteLine();
}
}
}
}