Merge pull request #281 from pmiossec/extend_branch_command

Extend branch command
This commit is contained in:
Matt Burke
2013-02-05 04:42:34 -08:00
13 changed files with 317 additions and 16 deletions
+6
View File
@@ -590,5 +590,11 @@ namespace Sep.Git.Tfs.VsCommon
});
}
public virtual void CreateBranch(string sourcePath, string targetPath, int changesetId, string comment = null)
{
throw new NotImplementedException();
}
}
}
@@ -119,6 +119,19 @@ namespace Sep.Git.Tfs.VsCommon
merge.SourceItem.ChangeType + "': https://github.com/git-tfs/git-tfs/issues"
});
}
public override void CreateBranch(string sourcePath, string targetPath, int changesetId, string comment = null)
{
var changesetToBranch = new ChangesetVersionSpec(changesetId);
int branchChangesetId = VersionControl.CreateBranch(sourcePath, targetPath, changesetToBranch);
if (comment != null)
{
Changeset changeset = VersionControl.GetChangeset(branchChangesetId);
changeset.Comment = comment;
changeset.Update();
}
}
}
}
+5
View File
@@ -366,6 +366,11 @@ namespace Sep.Git.Tfs.VsFake
{
throw new NotImplementedException();
}
public void CreateBranch(string sourcePath, string targetPath, int changesetId, string comment = null)
{
throw new NotImplementedException();
}
#endregion
}
}
+184 -8
View File
@@ -1,4 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Collections.Generic;
@@ -10,32 +12,184 @@ using StructureMap;
namespace Sep.Git.Tfs.Commands
{
[Pluggable("branch")]
[Description("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 --init $/Repository/ProjectBranch\n git tfs --init $/Repository/ProjectBranch myNewBranch\n git tfs --init --all\n git tfs --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 all the TFS branch of the current TFS server", v => DisplayRemotes = (v != null) }
{ "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)
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;
@@ -43,11 +197,29 @@ namespace Sep.Git.Tfs.Commands
var tfsRemotes = globals.Repository.ReadAllTfsRemotes();
if (DisplayRemotes)
{
var remote = globals.Repository.ReadTfsRemote(remoteId);
if (!ManageAll)
{
var remote = globals.Repository.ReadTfsRemote(remoteId);
stdout.WriteLine("\nTFS branch structure:");
WriteRemoteTfsBranchStructure(remote.Tfs, stdout, remote.TfsRepositoryPath, tfsRemotes);
return GitTfsExitCodes.OK;
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);
@@ -58,6 +230,10 @@ namespace Sep.Git.Tfs.Commands
{
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);
}
@@ -114,4 +290,4 @@ namespace Sep.Git.Tfs.Commands
}
}
}
}
}
+6 -6
View File
@@ -26,14 +26,14 @@ namespace Sep.Git.Tfs.Commands
public string TfsPassword { get; set; }
public string ParentBranch { get; set; }
public bool CloneAllBranches { get; set; }
string AuthorsFilePath { get; set; }
public string AuthorsFilePath { get; set; }
public InitBranch(TextWriter stdout, Globals globals, Help helper, AuthorsFile authors)
{
this._stdout = stdout;
this._globals = globals;
this._helper = helper;
this._authors = authors;
_stdout = stdout;
_globals = globals;
_helper = helper;
_authors = authors;
}
public OptionSet OptionSet
@@ -87,7 +87,7 @@ namespace Sep.Git.Tfs.Commands
if (defaultRemote.TfsRepositoryPath.ToLower() != rootBranch.Path.ToLower())
throw new GitTfsException(string.Format("error: Init all the branches is only possible when 'git tfs clone' was done from the trunk!!! Please clone again from '{0}'...", rootBranch.Path));
var childBranchPaths = rootBranch.GetAllChildren().Select(b=>b.Path);
var childBranchPaths = rootBranch.GetAllChildren().Select(b=>b.Path).ToList();
_stdout.WriteLine("Tfs branches found:");
foreach (var tfsBranchPath in childBranchPaths)
+6
View File
@@ -233,5 +233,11 @@ namespace Sep.Git.Tfs.Core
}
#endregion
public RemoteInfo RemoteInfo
{
get { throw new NotImplementedException(); }
}
}
}
+60
View File
@@ -8,6 +8,7 @@ using Sep.Git.Tfs.Commands;
using Sep.Git.Tfs.Core.TfsInterop;
using StructureMap;
using LibGit2Sharp;
using Branch = LibGit2Sharp.Branch;
namespace Sep.Git.Tfs.Core
{
@@ -136,6 +137,59 @@ namespace Sep.Git.Tfs.Core
return _cachedRemotes[remote.Id] = gitTfsRemote;
}
public void DeleteTfsRemote(IGitTfsRemote remote)
{
if (remote == null)
throw new GitTfsException("error: the name of the remote to delete is invalid!");
UnsetTfsRemoteConfig(remote.Id);
_repository.Refs.Remove(remote.RemoteRef);
}
private void UnsetTfsRemoteConfig(string remoteId)
{
foreach (var entry in _remoteConfigReader.Delete(remoteId))
{
_repository.Config.Unset(entry.Key);
}
_cachedRemotes = null;
}
public void MoveRemote(string oldRemoteName, string newRemoteName)
{
if (!_repository.Refs.IsValidName("refs/heads/" + oldRemoteName))
throw new GitTfsException("error: the name of the remote to move is invalid!");
if (!_repository.Refs.IsValidName("refs/heads/" + newRemoteName))
throw new GitTfsException("error: the new name of the remote is invalid!");
if (HasRemote(newRemoteName))
throw new GitTfsException(string.Format("error: this remote name \"{0}\" is already used!", newRemoteName));
var oldRemote = ReadTfsRemote(oldRemoteName);
if(oldRemote == null)
throw new GitTfsException(string.Format("error: the remote \"{0}\" doesn't exist!", oldRemoteName));
var remoteInfo = oldRemote.RemoteInfo;
remoteInfo.Id = newRemoteName;
CreateTfsRemote(remoteInfo);
var newRemote = ReadTfsRemote(newRemoteName);
_repository.Refs.Move(oldRemote.RemoteRef, newRemote.RemoteRef);
UnsetTfsRemoteConfig(oldRemoteName);
}
public Branch RenameBranch(string oldName, string newName)
{
var branch = _repository.Branches[oldName];
if (branch == null)
return null;
return _repository.Branches.Move(branch, newName);
}
private IDictionary<string, IGitTfsRemote> ReadTfsRemotes()
{
// does this need to ensuretfsauthenticated?
@@ -206,6 +260,12 @@ namespace Sep.Git.Tfs.Core
return tfsCommits;
}
public TfsChangesetInfo GetCurrentTfsCommit()
{
var currentCommit = _repository.Head.Commits.First();
return TryParseChangesetInfo(currentCommit.Message, currentCommit.Sha, false);
}
private void FindTfsCommits(TextReader stdout, ICollection<TfsChangesetInfo> tfsCommits, bool includeStubRemotes)
{
string currentCommit = null;
+2
View File
@@ -20,6 +20,7 @@ namespace Sep.Git.Tfs.Core
private long? maxChangesetId;
private string maxCommitHash;
private bool isTfsAuthenticated;
public RemoteInfo RemoteInfo { get; private set; }
public GitTfsRemote(RemoteInfo info, IGitRepository repository, RemoteOptions remoteOptions, Globals globals, ITfsHelper tfsHelper, TextWriter stdout)
{
@@ -29,6 +30,7 @@ namespace Sep.Git.Tfs.Core
Tfs = tfsHelper;
Repository = repository;
RemoteInfo = info;
Id = info.Id;
TfsUrl = info.Url;
TfsRepositoryPath = info.Repository;
+6 -1
View File
@@ -1,7 +1,8 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using Sep.Git.Tfs.Commands;
using Branch = LibGit2Sharp.Branch;
namespace Sep.Git.Tfs.Core
{
@@ -12,11 +13,13 @@ namespace Sep.Git.Tfs.Core
IEnumerable<IGitTfsRemote> ReadAllTfsRemotes();
IGitTfsRemote ReadTfsRemote(string remoteId);
IGitTfsRemote CreateTfsRemote(RemoteInfo remoteInfo);
void DeleteTfsRemote(IGitTfsRemote remoteId);
bool HasRemote(string remoteId);
bool HasRef(string gitRef);
void MoveTfsRefForwardIfNeeded(IGitTfsRemote remote);
IEnumerable<TfsChangesetInfo> GetLastParentTfsCommits(string head);
IEnumerable<TfsChangesetInfo> GetLastParentTfsCommits(string head, bool includeStubRemotes);
TfsChangesetInfo GetCurrentTfsCommit();
IDictionary<string, GitObject> GetObjects(string commit);
string HashAndInsertObject(string filename);
IEnumerable<IGitChangedFile> GetChangedFiles(string from, string to);
@@ -27,8 +30,10 @@ namespace Sep.Git.Tfs.Core
string GetCommitMessage(string head, string parentCommitish);
string AssertValidBranchName(string gitBranchName);
bool CreateBranch(string gitBranchName, string target);
Branch RenameBranch(string oldName, string newName);
string FindCommitHashByCommitMessage(string patternToFind);
void CreateTag(string name, string sha, string comment, string Owner, string emailOwner, System.DateTime creationDate);
void CreateNote(string sha, string content, string owner, string emailOwner, DateTime creationDate);
void MoveRemote(string oldRemoteName, string newRemoteName);
}
}
+1
View File
@@ -8,6 +8,7 @@ namespace Sep.Git.Tfs.Core
public interface IGitTfsRemote
{
bool IsDerived { get; }
RemoteInfo RemoteInfo { get; }
string Id { get; set; }
string TfsUrl { get; set; }
string TfsRepositoryPath { get; set; }
+9 -1
View File
@@ -36,7 +36,7 @@ namespace Sep.Git.Tfs.Core
remote.Autotag = bool.Parse(entry.Value);
}
}
return remotes.Values;
return remotes.Values.Where(r => !string.IsNullOrWhiteSpace(r.Url) && !string.IsNullOrWhiteSpace(r.Repository));
}
public IEnumerable<KeyValuePair<string, string>> Dump(RemoteInfo remote)
@@ -58,5 +58,13 @@ namespace Sep.Git.Tfs.Core
{
return new KeyValuePair<string, string>(key, value);
}
public IEnumerable<KeyValuePair<string, string>> Delete(string remoteId)
{
if (string.IsNullOrWhiteSpace(remoteId))
return new List<KeyValuePair<string, string>>();
return Dump(new RemoteInfo {Id = remoteId});
}
}
}
+1
View File
@@ -32,5 +32,6 @@ namespace Sep.Git.Tfs.Core.TfsInterop
IEnumerable<string> GetAllTfsRootBranchesOrderedByCreation();
IEnumerable<IBranchObject> GetBranches();
void EnsureAuthenticated();
void CreateBranch(string sourcePath, string targetPath, int changesetId, string comment = null);
}
}
@@ -134,6 +134,24 @@ namespace Sep.Git.Tfs.Test.Core
Assert.Equal(new string[] { "http://old:8080/", "http://other/" }, remote.Aliases);
Assert.True(remote.Autotag);
}
[Fact]
public void ShouldNotReturnLackingTfsUrlRemote()
{
var remotes = Load(
c("tfs-remote.default.repository", "$/project"));
Assert.Equal(0, remotes.Count());
}
[Fact]
public void ShouldNotReturnLackingTfsRepositoryRemote()
{
var remotes = Load(
c("tfs-remote.default.url", "http://server/path"));
Assert.Equal(0, remotes.Count());
}
}
RemoteConfigConverter _converter = new RemoteConfigConverter();