Skip to content

Commit

Permalink
Merge pull request #43 from BluewireTechnologies/feature/overwrite-st…
Browse files Browse the repository at this point in the history
…ash-INF-1149

Add more tooling around remote stash management
  • Loading branch information
alex-davidson authored Oct 21, 2022
2 parents 2a8572a + 09faf24 commit d709b47
Show file tree
Hide file tree
Showing 33 changed files with 394 additions and 55 deletions.
4 changes: 2 additions & 2 deletions Bluewire.Conventions.UnitTests/BranchSemanticsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void ThrowsExceptionIfSemTagIsMissing()
{
var sut = new BranchSemantics();

Assert.Throws<InvalidOperationException>(() =>
Assert.Throws<ArgumentException>(() =>
{
var semVer = new SemanticVersion("17", "05", 123, null);
sut.GetVersionLatestBranchNames(semVer);
Expand All @@ -52,7 +52,7 @@ public void ThrowsExceptionIfSemTagIsEmpty()
{
var sut = new BranchSemantics();

Assert.Throws<InvalidOperationException>(() =>
Assert.Throws<ArgumentException>(() =>
{
var semVer = new SemanticVersion("17", "05", 123, "");
sut.GetVersionLatestBranchNames(semVer);
Expand Down
2 changes: 1 addition & 1 deletion Bluewire.Conventions/Bluewire.Conventions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.0</TargetFrameworks>
<VersionPrefix>2.1.0</VersionPrefix>
<VersionPrefix>2.2.0</VersionPrefix>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions Bluewire.Conventions/BranchSemantics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public string GetVersionZeroBranchName(SemanticVersion semVer)
// Assumes sementics defined in BranchType.cs
private IEnumerable<string> GetVersionLatestBranchNamesInternal(SemanticVersion semVer)
{
if (string.IsNullOrEmpty(semVer.SemanticTag)) throw new ArgumentException($"No semantic tag specified: {semVer}");
switch (semVer.SemanticTag)
{
case "beta":
Expand Down
4 changes: 2 additions & 2 deletions Bluewire.Stash.IntegrationTests/LocalStashRepositoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public async Task CanListStashes()
}

[Test]
public async Task CanListStashesForMajorMinorVersion()
public async Task CanListPossibleAncestorsForVersion()
{
var stashRepository = new LocalStashRepository(Default.TemporaryDirectory);

Expand All @@ -119,7 +119,7 @@ public async Task CanListStashesForMajorMinorVersion()
await CreateEmptyStash(stashRepository, new VersionMarker(SemanticVersion.FromString("20.20.12-beta")));
await CreateEmptyStash(stashRepository, new VersionMarker("commit3"));

var list = await stashRepository.List(SemanticVersion.FromString("20.20.0"));
var list = await stashRepository.ListPossibleAncestors(SemanticVersion.FromString("20.21.0"));

Assert.That(list,
Is.EqualTo(new []
Expand Down
1 change: 1 addition & 0 deletions Bluewire.Stash.IntegrationTests/Tool/StubApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class StubApplication : IApplication
public async Task List(TextWriter stdout, TextWriter stderr, ListArguments model, CancellationToken token) => Invocations.Add(model);
public async Task Show(TextWriter stdout, TextWriter stderr, ShowArguments model, CancellationToken token) => Invocations.Add(model);
public async Task Delete(TextWriter stderr, DeleteArguments model, CancellationToken token) => Invocations.Add(model);
public async Task RemoteDelete(TextWriter stderr, RemoteDeleteArguments model, CancellationToken token) => Invocations.Add(model);
public async Task GarbageCollect(TextWriter stderr, GCArguments model, CancellationToken token) => Invocations.Add(model);
public async Task Push(TextWriter stderr, PushArguments model, CancellationToken token) => Invocations.Add(model);
public async Task Pull(TextWriter stderr, PullArguments model, CancellationToken token) => Invocations.Add(model);
Expand Down
15 changes: 15 additions & 0 deletions Bluewire.Stash.Service/Controllers/ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,21 @@ public async Task<IActionResult> Exists(string name, string entry)
return NotFound();
}

[HttpDelete]
[Authorize(Roles = "Stash.Admin")]
[Route("delete/{name}/{entry}")]
public async Task<IActionResult> Delete(string name, string entry)
{
var stash = service.GetNamed(name);
var marker = ParseEntry(entry);
if (await stash.Exists(marker))
{
await stash.Delete(marker);
return Ok();
}
return NotFound();
}

[HttpPost]
[Authorize]
[Route("ping")]
Expand Down
8 changes: 8 additions & 0 deletions Bluewire.Stash.Tool/ArgumentsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ public ArgumentValue<bool> GetFlag(CommandOption flagOption)
return new ArgumentValue<bool>(true, ArgumentSource.Argument);
}

public ArgumentValue<ExistsBehaviour> GetExistsBehaviour(CommandOption ignoreOption, CommandOption overwriteOption)
{
// '--overwrite' takes precedence over '--ignore'
if (overwriteOption.Values.Any()) return new ArgumentValue<ExistsBehaviour>(ExistsBehaviour.Overwrite, ArgumentSource.Argument);
if (ignoreOption.Values.Any()) return new ArgumentValue<ExistsBehaviour>(ExistsBehaviour.Ignore, ArgumentSource.Argument);
return new ArgumentValue<ExistsBehaviour>(ExistsBehaviour.Error, ArgumentSource.Default);
}

public ArgumentValue<int> GetVerbosityLevel(CommandOption verbosityOption)
{
if (!verbosityOption.Values.Any()) return new ArgumentValue<int>(0, ArgumentSource.Default);
Expand Down
2 changes: 2 additions & 0 deletions Bluewire.Stash.Tool/AuthenticateArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ public AuthenticateArguments(AppEnvironment appEnvironment)
}

public AppEnvironment AppEnvironment { get; }

public ArgumentValue<bool> Renew { get; set; }
}
}
2 changes: 2 additions & 0 deletions Bluewire.Stash.Tool/ClientSecretAuthenticationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,7 @@ public async Task<AuthenticationResult> Authenticate(CancellationToken token)
return await app.AcquireTokenForClient(AuthenticationSettings.ConfidentialScopes)
.ExecuteAsync(token);
}

public Task Clear() => Task.CompletedTask;
}
}
9 changes: 9 additions & 0 deletions Bluewire.Stash.Tool/ExistsBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Bluewire.Stash.Tool
{
public enum ExistsBehaviour
{
Error = 0,
Ignore,
Overwrite,
}
}
11 changes: 11 additions & 0 deletions Bluewire.Stash.Tool/HttpRemoteStashRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ public async Task<bool> Exists(VersionMarker entry, CancellationToken token = de
return true;
}

public async Task Delete(VersionMarker entry, CancellationToken token = default)
{
var uri = new Uri(rootUri, $"delete/{Uri.EscapeDataString(name)}/{ConvertVersionMarkerToUriSegment(entry)}");
var message = CreateMessage(HttpMethod.Delete, uri, authResult);

using (var response = await httpClient.SendAsync(message, token))
{
response.EnsureSuccessStatusCode();
}
}

public static async Task<string> Ping(Uri rootUri, AuthenticationResult authResult, CancellationToken token = default)
{
var uri = new Uri(rootUri, "ping");
Expand Down
1 change: 1 addition & 0 deletions Bluewire.Stash.Tool/IApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public interface IApplication
Task List(TextWriter stdout, TextWriter stderr, ListArguments model, CancellationToken token);
Task Show(TextWriter stdout, TextWriter stderr, ShowArguments model, CancellationToken token);
Task Delete(TextWriter stderr, DeleteArguments model, CancellationToken token);
Task RemoteDelete(TextWriter stderr, RemoteDeleteArguments model, CancellationToken token);
Task GarbageCollect(TextWriter stderr, GCArguments model, CancellationToken token);
Task Push(TextWriter stderr, PushArguments model, CancellationToken token);
Task Pull(TextWriter stderr, PullArguments model, CancellationToken token);
Expand Down
1 change: 1 addition & 0 deletions Bluewire.Stash.Tool/IAuthenticationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ namespace Bluewire.Stash.Tool
public interface IAuthenticationProvider
{
Task<AuthenticationResult> Authenticate(CancellationToken token);
Task Clear();
}
}
37 changes: 34 additions & 3 deletions Bluewire.Stash.Tool/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,13 @@ public static CommandLineApplication Configure(IApplication application, Command
app.Command("authenticate", c =>
{
c.AddName("auth");
var renewOption = c.Option("--renew", "Clear existing token (if cached) and re-authenticate.", CommandOptionType.NoValue);
c.OnExecuteAsync(async token =>
{
var model = new AuthenticateArguments(argumentsProvider.GetAppEnvironment(gitTopologyPathOption, stashRootOption, remoteStashRootOption, clientSecretOption));
var model = new AuthenticateArguments(argumentsProvider.GetAppEnvironment(gitTopologyPathOption, stashRootOption, remoteStashRootOption, clientSecretOption))
{
Renew = argumentsProvider.GetFlag(renewOption),
};
await application.Authenticate(originalStdout, model, token);
});
});
Expand Down Expand Up @@ -185,6 +189,25 @@ public static CommandLineApplication Configure(IApplication application, Command
});
});

app.Command("remote-delete", c =>
{
var remoteStashNameArgument = c.Argument<string>("remote stash name", "The name of the remote stash to use.", o => o.IsRequired());
var semanticVersionOption = c.Option<SemanticVersion?>("--version <version>", "The version to delete.", CommandOptionType.SingleValue);
var commitHashOption = c.Option<string>("--hash <hash>", "The commit hash to delete.", CommandOptionType.SingleValue);
var versionMarkerOption = c.Option<VersionMarker?>("--identifier <identifier>", "The version identifier to delete.", CommandOptionType.SingleValue);

c.OnExecuteAsync(async token =>
{
var model = new RemoteDeleteArguments(argumentsProvider.GetAppEnvironment(gitTopologyPathOption, stashRootOption, remoteStashRootOption, clientSecretOption))
{
RemoteStashName = argumentsProvider.GetStashName(remoteStashNameArgument),
Version = argumentsProvider.GetRequiredVersionMarker(semanticVersionOption, commitHashOption, versionMarkerOption),
Verbosity = argumentsProvider.GetVerbosityLevel(verbosityOption),
};
await application.RemoteDelete(app.Error, model, token);
});
});

app.Command("gc", c =>
{
var stashNameArgument = c.Argument<string>("stash name", "The name of the stash to use.", o => o.IsRequired());
Expand All @@ -210,6 +233,7 @@ public static CommandLineApplication Configure(IApplication application, Command
var commitHashOption = c.Option<string>("--hash <hash>", "The commit hash to copy.", CommandOptionType.SingleValue);
var versionMarkerOption = c.Option<VersionMarker?>("--identifier <identifier>", "The version identifier to copy.", CommandOptionType.SingleValue);
var ignoreIfExists = c.Option("-i|--ignore", "If the stash already exists on the remote, treat as success.", CommandOptionType.NoValue);
var overwriteIfExists = c.Option("--overwrite", "If the stash already exists on the remote, replace it.", CommandOptionType.NoValue);

c.OnExecuteAsync(async token =>
{
Expand All @@ -218,7 +242,7 @@ public static CommandLineApplication Configure(IApplication application, Command
StashName = argumentsProvider.GetStashName(stashNameArgument),
RemoteStashName = argumentsProvider.GetStashName(remoteStashNameArgument),
Version = argumentsProvider.GetRequiredVersionMarker(semanticVersionOption, commitHashOption, versionMarkerOption),
IgnoreIfExists = argumentsProvider.GetFlag(ignoreIfExists),
ExistsRemotelyBehaviour = argumentsProvider.GetExistsBehaviour(ignoreIfExists, overwriteIfExists),
Verbosity = argumentsProvider.GetVerbosityLevel(verbosityOption),
};
await application.Push(app.Error, model, token);
Expand All @@ -233,6 +257,7 @@ public static CommandLineApplication Configure(IApplication application, Command
var commitHashOption = c.Option<string>("--hash <hash>", "The commit hash to copy.", CommandOptionType.SingleValue);
var versionMarkerOption = c.Option<VersionMarker?>("--identifier <identifier>", "The version identifier to copy.", CommandOptionType.SingleValue);
var ignoreIfExists = c.Option("-i|--ignore", "If the stash already exists locally, treat as success.", CommandOptionType.NoValue);
var overwriteIfExists = c.Option("--overwrite", "If the stash already exists locally, delete it and re-download it.", CommandOptionType.NoValue);

c.OnExecuteAsync(async token =>
{
Expand All @@ -241,7 +266,7 @@ public static CommandLineApplication Configure(IApplication application, Command
StashName = argumentsProvider.GetStashName(stashNameArgument),
RemoteStashName = argumentsProvider.GetStashName(remoteStashNameArgument),
Version = argumentsProvider.GetVersionMarker(semanticVersionOption, commitHashOption, versionMarkerOption),
IgnoreIfExists = argumentsProvider.GetFlag(ignoreIfExists),
ExistsLocallyBehaviour = argumentsProvider.GetExistsBehaviour(ignoreIfExists, overwriteIfExists),
Verbosity = argumentsProvider.GetVerbosityLevel(verbosityOption),
};
await application.Pull(app.Error, model, token);
Expand Down Expand Up @@ -273,6 +298,7 @@ public async Task ShowDiagnostics(TextWriter stdout, DiagnosticsArguments model,
public async Task Authenticate(TextWriter stdout, AuthenticateArguments model, CancellationToken token)
{
var auth = await model.AppEnvironment.Authentication.Create();
if (model.Renew.Value) await auth.Clear();
await auth.Authenticate(token);
}

Expand Down Expand Up @@ -301,6 +327,11 @@ public async Task Delete(TextWriter stderr, DeleteArguments model, CancellationT
await new DeleteCommand().Execute(model, new VerboseLogger(stderr, model.Verbosity.Value), token);
}

public async Task RemoteDelete(TextWriter stderr, RemoteDeleteArguments model, CancellationToken token)
{
await new RemoteDeleteCommand().Execute(model, new VerboseLogger(stderr, model.Verbosity.Value), token);
}

public async Task GarbageCollect(TextWriter stderr, GCArguments model, CancellationToken token)
{
await new GCCommand().Execute(model, new VerboseLogger(stderr, model.Verbosity.Value), token);
Expand Down
9 changes: 9 additions & 0 deletions Bluewire.Stash.Tool/PublicClientAuthenticationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ public async Task<AuthenticationResult> Authenticate(CancellationToken token)
}
}

public async Task Clear()
{
var accounts = await app.GetAccountsAsync();
foreach (var account in accounts)
{
await app.RemoveAsync(account);
}
}

public static class CacheSettings
{
private static readonly string cacheFilePath =
Expand Down
6 changes: 4 additions & 2 deletions Bluewire.Stash.Tool/PullArguments.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Bluewire.Stash.Tool
using static Bluewire.Stash.Tool.PullCommand;

namespace Bluewire.Stash.Tool
{
public class PullArguments
{
Expand All @@ -12,7 +14,7 @@ public PullArguments(AppEnvironment appEnvironment)
public ArgumentValue<string> StashName { get; set; }
public ArgumentValue<string> RemoteStashName { get; set; }
public ArgumentValue<VersionMarker?> Version { get; set; }
public ArgumentValue<bool> IgnoreIfExists { get; set; }
public ArgumentValue<ExistsBehaviour> ExistsLocallyBehaviour { get; set; }
public ArgumentValue<int> Verbosity { get; set; }
}
}
13 changes: 8 additions & 5 deletions Bluewire.Stash.Tool/PullCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public async Task Execute(PullArguments model, VerboseLogger logger, Cancellatio
logger.WriteLine(VerbosityLevels.ShowArguments, $"Stash name: {model.StashName}");
logger.WriteLine(VerbosityLevels.ShowArguments, $"Remote stash name: {model.RemoteStashName}");
logger.WriteLine(VerbosityLevels.ShowArguments, $"Version: {model.Version}");
logger.WriteLine(VerbosityLevels.ShowArguments, $"Ignore if exists: {model.IgnoreIfExists}");
logger.WriteLine(VerbosityLevels.ShowArguments, $"If exists locally: {model.ExistsLocallyBehaviour}");

var services = await SetUpServices(model, logger, token);
using var gc = new GarbageCollection(logger).RunBackground(services.StashRepository);
Expand All @@ -40,14 +40,17 @@ public async Task Execute(PullArguments model, VerboseLogger logger, Cancellatio

logger.WriteLine(VerbosityLevels.DescribeActions, $"Found matching stash: {stashVersionMarker}");

var localStash = await services.StashRepository.GetOrCreate(stashVersionMarker.Value);
if (await services.StashRepository.TryGet(stashVersionMarker.Value) != null)
var existsLocally = await services.StashRepository.TryGet(stashVersionMarker.Value) != null;
if (existsLocally)
{
logger.WriteLine(VerbosityLevels.DescribeActions, $"The stash {stashVersionMarker.Value} already exists locally.");
if (!model.IgnoreIfExists.Value) throw new ApplicationException($"The stash {stashVersionMarker.Value} already exists locally.");
return;
if (model.ExistsLocallyBehaviour.Value == ExistsBehaviour.Error) throw new ApplicationException($"The stash {stashVersionMarker.Value} already exists locally.");
if (model.ExistsLocallyBehaviour.Value == ExistsBehaviour.Ignore) return;
await services.StashRepository.Delete(stashVersionMarker.Value);
}

var localStash = await services.StashRepository.GetOrCreate(stashVersionMarker.Value);

await foreach (var relativePath in services.RemoteStashRepository.ListFiles(stashVersionMarker.Value, token))
{
using (var sourceStream = await services.RemoteStashRepository.Pull(stashVersionMarker.Value, relativePath, token))
Expand Down
2 changes: 1 addition & 1 deletion Bluewire.Stash.Tool/PushArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public PushArguments(AppEnvironment appEnvironment)
public ArgumentValue<string> StashName { get; set; }
public ArgumentValue<string> RemoteStashName { get; set; }
public ArgumentValue<VersionMarker> Version { get; set; }
public ArgumentValue<bool> IgnoreIfExists { get; set; }
public ArgumentValue<ExistsBehaviour> ExistsRemotelyBehaviour { get; set; }
public ArgumentValue<int> Verbosity { get; set; }
}
}
Loading

0 comments on commit d709b47

Please sign in to comment.