diff --git a/GitTfs.VsCommon/TfsHelper.Common.cs b/GitTfs.VsCommon/TfsHelper.Common.cs index 298b6ddd1..f3c479c46 100644 --- a/GitTfs.VsCommon/TfsHelper.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.Common.cs @@ -12,6 +12,7 @@ using Sep.Git.Tfs.Core; using Sep.Git.Tfs.Core.TfsInterop; using StructureMap; +using ChangeType = Microsoft.TeamFoundation.Server.ChangeType; namespace Sep.Git.Tfs.VsCommon { @@ -183,20 +184,183 @@ public bool HasShelveset(string shelvesetName) public abstract bool CanShowCheckinDialog { get; } - public void Unshelve(Sep.Git.Tfs.Commands.Unshelve unshelve, IList args) + [Obsolete("TODO: un-spike-ify this.")] + public int Unshelve(Sep.Git.Tfs.Commands.Unshelve unshelve, IGitTfsRemote remote, IList args) { + var ListShelvesets = new Action(shelvesets => + { + foreach (var shelveset in shelvesets) + { + _stdout.WriteLine(" {0,-20} {1,-20}", + shelveset.OwnerName, + shelveset.Name); + } + }); + var shelvesetOwner = unshelve.Owner == "all" ? null : (unshelve.Owner ?? VersionControl.AuthenticatedUser); if(unshelve.List) { - var user = unshelve.Owner == "all" ? null : (unshelve.Owner ?? VersionControl.AuthenticatedUser); - var shelvesets = VersionControl.QueryShelvesets(null, user); - foreach(var shelveset in shelvesets) + var shelvesets = VersionControl.QueryShelvesets(null, shelvesetOwner); + ListShelvesets(shelvesets); + } + else + { + if(args.Count != 2) + { + _stdout.WriteLine("ERROR: Two arguments are required."); + return GitTfsExitCodes.InvalidArguments; + } + var shelvesetName = args[0]; + var destinationBranch = args[1]; + + var shelvesets = VersionControl.QueryShelvesets(shelvesetName, shelvesetOwner); + if(shelvesets.Length != 1) + { + _stdout.WriteLine("ERROR: Unable to find shelveset \"" + shelvesetName + "\" (" + shelvesets.Length + " matches)."); + ListShelvesets(shelvesets); + return GitTfsExitCodes.InvalidArguments; + } + var shelveset = shelvesets.First(); + + var destinationRef = "refs/heads/" + destinationBranch; + if (File.Exists(Path.Combine(remote.Repository.GitDir, destinationRef))) { - _stdout.WriteLine(" {0,-20} {1,-20}", shelveset.OwnerName, shelveset.Name); + _stdout.WriteLine("ERROR: Destination branch (" + destinationBranch + ") already exists!"); + return GitTfsExitCodes.ForceRequired; } + + // Don't need this because we're just adding a branch. + //var worktreeStatus = remote.Repository.Command("ls-files", "--deleted", "--modified", "--others", + // "--exclude-standard"); + //if(!string.IsNullOrEmpty(worktreeStatus)) + //{ + // _stdout.WriteLine("ERROR: You have a dirty working tree:"); + // _stdout.Write(worktreeStatus); + // return GitTfsExitCodes.InvalidPrecondition; + //} + + var change = VersionControl.QueryShelvedChanges(shelveset).Single(); + var gremote = (GitTfsRemote) remote; + //var tfsChangeset = new FakeTfsChangeset(change); + var wrapperForVersionControlServer = + _bridge.Wrap(VersionControl); + var fakeChangeset = new FakeChangeset(shelveset, change, wrapperForVersionControlServer, _bridge); + var tfsChangeset = new TfsChangeset(remote.Tfs, fakeChangeset) + {Summary = new TfsChangesetInfo {Remote = remote}}; + gremote.Apply(tfsChangeset, destinationRef); + _stdout.WriteLine("Created branch " + destinationBranch + " from shelveset \"" + shelvesetName + "\"."); } - else + return GitTfsExitCodes.OK; + } + class FakeChangeset : IChangeset + { + private readonly Shelveset _shelveset; + private readonly PendingSet _pendingSet; + private readonly IVersionControlServer _versionControlServer; + private readonly TfsApiBridge _bridge; + + public FakeChangeset(Shelveset shelveset, PendingSet pendingSet, IVersionControlServer versionControlServer, TfsApiBridge bridge) + { + _shelveset = shelveset; + _versionControlServer = versionControlServer; + _bridge = bridge; + _pendingSet = pendingSet; + } + + public IChange[] Changes + { + get { return _pendingSet.PendingChanges.Select(x => new FakeChange(x, _bridge)).Cast().ToArray(); } + } + + public string Committer + { + get { return _pendingSet.OwnerName; } + } + + public DateTime CreationDate + { + get { return _shelveset.CreationDate; } + } + + public string Comment + { + get { return _shelveset.Comment; } + } + + public int ChangesetId + { + get { return -1; } + } + + public IVersionControlServer VersionControlServer + { + get { return _versionControlServer; } + } + } + class FakeChange : IChange + { + private readonly PendingChange _pendingChange; + private readonly TfsApiBridge _bridge; + + public FakeChange(PendingChange pendingChange, TfsApiBridge bridge) + { + _pendingChange = pendingChange; + _bridge = bridge; + } + + public TfsChangeType ChangeType + { + get { return _bridge.Convert(_pendingChange.ChangeType); } + } + + public IItem Item + { + get { return new FakeItem(_pendingChange, _bridge); } + } + } + class FakeItem : IItem + { + private readonly PendingChange _pendingChange; + private readonly TfsApiBridge _bridge; + + public FakeItem(PendingChange pendingChange, TfsApiBridge bridge) + { + _pendingChange = pendingChange; + _bridge = bridge; + } + + public IVersionControlServer VersionControlServer + { + get { throw new NotImplementedException(); } + } + + public int ChangesetId + { + get { throw new NotImplementedException(); } + } + + public string ServerItem + { + get { return _pendingChange.ServerItem; } + } + + public decimal DeletionId + { + get { return _pendingChange.DeletionId; } + } + + public TfsItemType ItemType + { + get { return _bridge.Convert(_pendingChange.ItemType); } + } + + public int ItemId + { + get { throw new NotImplementedException(); } + } + + public void DownloadFile(string file) { - throw new NotImplementedException(); + _pendingChange.DownloadShelvedFile(file); } } diff --git a/GitTfs/Commands/Unshelve.cs b/GitTfs/Commands/Unshelve.cs index daba4bae5..92b78b834 100644 --- a/GitTfs/Commands/Unshelve.cs +++ b/GitTfs/Commands/Unshelve.cs @@ -10,7 +10,7 @@ namespace Sep.Git.Tfs.Commands { [Pluggable("unshelve")] - [Description("unshelve [options] (-l | shelveset-name [ref-to-shelve]")] + [Description("unshelve [options] (-l | shelveset-name destination-branch)")] [RequiresValidGitRepository] public class Unshelve : GitTfsCommand { @@ -42,8 +42,7 @@ public int Run(IList args) { // TODO -- let the remote be specified on the command line. var remote = _globals.Repository.ReadAllTfsRemotes().First(); - remote.Tfs.Unshelve(this, args); - return GitTfsExitCodes.OK; + return remote.Tfs.Unshelve(this, remote, args); } } } diff --git a/GitTfs/Core/GitRepository.cs b/GitTfs/Core/GitRepository.cs index b5661b013..cb128bbf4 100644 --- a/GitTfs/Core/GitRepository.cs +++ b/GitTfs/Core/GitRepository.cs @@ -28,7 +28,7 @@ public GitRepository(TextWriter stdout, string gitDir, IContainer container, Glo _repository = new Repository(new DirectoryInfo(gitDir)); } - private string GitDir { get; set; } + public string GitDir { get; set; } public string WorkingCopyPath { get; set; } public string WorkingCopySubdir { get; set; } diff --git a/GitTfs/Core/GitTfsRemote.cs b/GitTfs/Core/GitTfsRemote.cs index de7a03553..bfbc3430d 100644 --- a/GitTfs/Core/GitTfsRemote.cs +++ b/GitTfs/Core/GitTfsRemote.cs @@ -170,6 +170,13 @@ public void FetchWithMerge(long mergeChangesetId, params string[] parentCommitsH } } + public void Apply(ITfsChangeset changeset, string destinationRef) + { + var log = Apply(MaxCommitHash, changeset); + var commit = Commit(log); + Repository.CommandNoisy("update-ref", destinationRef, commit); + } + public void QuickFetch() { var changeset = Tfs.GetLatestChangeset(this); diff --git a/GitTfs/Core/IGitHelpers.cs b/GitTfs/Core/IGitHelpers.cs index fccfc8e42..b118939c4 100644 --- a/GitTfs/Core/IGitHelpers.cs +++ b/GitTfs/Core/IGitHelpers.cs @@ -5,9 +5,21 @@ namespace Sep.Git.Tfs.Core { public interface IGitHelpers { + /// + /// Runs the given git command, and returns the contents of its STDOUT. + /// string Command(params string[] command); + /// + /// Runs the given git command, and returns the first line of its STDOUT. + /// string CommandOneline(params string[] command); + /// + /// Runs the given git command, and passes STDOUT through to the current process's STDOUT. + /// void CommandNoisy(params string[] command); + /// + /// Runs the given git command, and redirects STDOUT to the provided action. + /// void CommandOutputPipe(Action func, params string[] command); void CommandInputPipe(Action action, params string[] command); void CommandInputOutputPipe(Action interact, params string[] command); diff --git a/GitTfs/Core/IGitRepository.cs b/GitTfs/Core/IGitRepository.cs index ae6d4720d..64ca9a8d0 100644 --- a/GitTfs/Core/IGitRepository.cs +++ b/GitTfs/Core/IGitRepository.cs @@ -6,6 +6,7 @@ namespace Sep.Git.Tfs.Core { public interface IGitRepository : IGitHelpers { + string GitDir { get; set; } IEnumerable ReadAllTfsRemotes(); IGitTfsRemote ReadTfsRemote(string remoteId); void /*or IGitTfsRemote*/ CreateTfsRemote(string remoteId, string tfsUrl, string tfsRepositoryPath, RemoteOptions remoteOptions); diff --git a/GitTfs/Core/TfsInterop/ITfsHelper.cs b/GitTfs/Core/TfsInterop/ITfsHelper.cs index 9b9d73f19..b01621aac 100644 --- a/GitTfs/Core/TfsInterop/ITfsHelper.cs +++ b/GitTfs/Core/TfsInterop/ITfsHelper.cs @@ -22,7 +22,7 @@ public interface ITfsHelper IChangeset GetChangeset(int changesetId); bool MatchesUrl(string tfsUrl); bool HasShelveset(string shelvesetName); - void Unshelve(Unshelve unshelve, IList args); + int Unshelve(Unshelve unshelve, IGitTfsRemote remote, IList args); bool CanShowCheckinDialog { get; } long ShowCheckinDialog(IWorkspace workspace, IPendingChange[] pendingChanges, IEnumerable checkedInfos, string checkinComment); void CleanupWorkspaces(string workingDirectory); diff --git a/GitTfs/GitTfsExitCodes.cs b/GitTfs/GitTfsExitCodes.cs index fc95e0034..36e02bbc6 100644 --- a/GitTfs/GitTfsExitCodes.cs +++ b/GitTfs/GitTfsExitCodes.cs @@ -8,5 +8,6 @@ public static class GitTfsExitCodes public const int Help = 1; public const int InvalidArguments = 2; public const int ForceRequired = 3; + public const int InvalidPrecondition = 4; } }