Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
2aa4441
cleanup old code
zetroot Apr 5, 2021
3871b7d
added sample blazor app and extracted razor components library from it
zetroot Apr 5, 2021
a991721
drop previous attempts
zetroot Apr 6, 2021
bbb391f
new template project
zetroot Apr 6, 2021
c194341
wip
zetroot Apr 7, 2021
140acc2
github client wip
zetroot Apr 8, 2021
526159b
finally got how to create pr between forks
zetroot Apr 8, 2021
7f2372f
implementing ifilesystem
zetroot Apr 9, 2021
ca6c96e
renaming to commune
zetroot Apr 12, 2021
fd5a694
building github filesystem
zetroot Apr 12, 2021
c0b948f
wip
zetroot Apr 20, 2021
131d1e3
test wip
zetroot Apr 20, 2021
0891954
tests wip
zetroot Apr 21, 2021
9dab8a0
rebuild stream creation
zetroot Apr 23, 2021
637bc56
removed setting data in stream constructor
zetroot Apr 24, 2021
e93fa65
me failed with git
zetroot Apr 29, 2021
fa1e982
GithubFile tests implementation
zetroot Apr 30, 2021
9170b3a
removed template project to reduce PR size
zetroot Apr 30, 2021
0c8697b
DirectoryContents tests implementation
zetroot Apr 30, 2021
f7d672f
Merge remote-tracking branch 'origin/serverles-poc' into serverless-poc
zetroot May 3, 2021
eaeae5d
classes visibility tuning, docs added, added specific exception type
zetroot May 3, 2021
69c7cb5
added commit implementation to context
zetroot May 3, 2021
a8cb923
added commit method to GitHubFilesystem, added tests for it
zetroot May 3, 2021
4d36765
Logging and configuration (#2)
zetroot May 5, 2021
c464cd9
Merge remote-tracking branch 'upstream/master'
zetroot May 5, 2021
26b1b73
updated from upstream
zetroot May 8, 2021
3d98bd0
Merge branch 'DotNetRu:master' into master
zetroot May 8, 2021
eee0010
added bunit
zetroot May 8, 2021
43a1f53
added sample blazor app and extracted razor components library from it
zetroot Apr 5, 2021
7201ec2
drop previous attempts
zetroot Apr 6, 2021
08cf882
new template project
zetroot Apr 6, 2021
43e1ba7
wip
zetroot Apr 7, 2021
65c2360
github client wip
zetroot Apr 8, 2021
356c1e5
finally got how to create pr between forks
zetroot Apr 8, 2021
08becf8
implementing ifilesystem
zetroot Apr 9, 2021
adf57f1
renaming to commune
zetroot Apr 12, 2021
e01722e
building github filesystem
zetroot Apr 12, 2021
f94d740
wip
zetroot Apr 20, 2021
759c906
test wip
zetroot Apr 20, 2021
d279514
tests wip
zetroot Apr 21, 2021
f7dcb5e
rebuild stream creation
zetroot Apr 23, 2021
d6922ef
removed setting data in stream constructor
zetroot Apr 24, 2021
c418cfb
me failed with git
zetroot Apr 29, 2021
89dba2a
GithubFile tests implementation
zetroot Apr 30, 2021
802504b
removed template project to reduce PR size
zetroot Apr 30, 2021
439aaf2
DirectoryContents tests implementation
zetroot Apr 30, 2021
f443f67
classes visibility tuning, docs added, added specific exception type
zetroot May 3, 2021
f0b8d8a
added commit implementation to context
zetroot May 3, 2021
d3b1285
added commit method to GitHubFilesystem, added tests for it
zetroot May 3, 2021
3ccd3ed
Merge remote-tracking branch 'origin/serverles-poc' into serverless-poc
zetroot May 8, 2021
4d1f74a
fixed solution file
zetroot May 8, 2021
3a5f8e2
first hello world app wip
zetroot May 8, 2021
58e49b9
directory viewer rebuilt
zetroot May 27, 2021
cf85578
file contents viewer
zetroot May 27, 2021
7289b0a
trying to change files
zetroot May 28, 2021
2a2cbbf
reading and writing file
zetroot Jun 1, 2021
aa071c6
rebuilt file viewer, added file creation, refactored github file abst…
zetroot Jun 2, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions DotNetRu.Commune.GitHubFilesystem/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("DotNetRu.Commune.Test.GitHubFilesystem")]
[assembly:InternalsVisibleTo("DotNetRu.Commune.Test.Fs.GitHubFilesystem")]
24 changes: 24 additions & 0 deletions DotNetRu.Commune.GitHubFilesystem/DirectoryContents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.FileProviders;

namespace DotNetRu.Commune.GitHubFilesystem
{
public class DirectoryContents : IDirectoryContents
{
private readonly IReadOnlyCollection<IFileInfo> files;

public DirectoryContents(IEnumerable<IFileInfo> files)
{
this.files = files?.ToList() ?? throw new ArgumentNullException(nameof(files));
}

public IEnumerator<IFileInfo> GetEnumerator() => files.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public bool Exists => true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
<AssemblyName>DotNetRu.Commune.GitHubFilesystem</AssemblyName>
<RootNamespace>DotNetRu.Commune.GitHubFilesystem</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="5.0.0" />
<PackageReference Include="Octokit" Version="0.50.0" />
<PackageReference Include="System.IO.Abstractions" Version="13.2.28" />
</ItemGroup>

</Project>
67 changes: 67 additions & 0 deletions DotNetRu.Commune.GitHubFilesystem/EditingContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Threading.Tasks;
using Octokit;

namespace DotNetRu.Commune.GitHubFilesystem
{
/// <summary>
/// Сессия работы с файловой системой
/// </summary>
internal class EditingContext
{
/// <summary>
/// Клиент доступа к github
/// </summary>
public IGitHubClient Client { get; }

/// <summary>
/// Клиент доступа к контенту репозитория
/// </summary>
public virtual IRepositoryContentsClient ContentClient => Client.Repository.Content;

/// <summary>
/// Клиент доступа к списку pull request
/// </summary>
public virtual IPullRequestsClient PullRequestsClient => Client.PullRequest;

/// <summary>
/// исходный репозиторий, откуда был сделан форк
/// </summary>
public Repository OriginRepo { get; }

/// <summary>
/// Исходная ветвь. Туда будет сделан PR призавершении контекста
/// </summary>
public Reference OriginBranch { get; }

/// <summary>
/// Локальный форк репозитория в репозиториях пользователя
/// </summary>
public Repository LocalRepo { get; }

/// <summary>
/// Текущая ветка, в которой будут производится манипуляции с файлами
/// </summary>
public Reference CurrentBranch { get; }

/// <summary>
/// Применить изменения, создав PR в основную ветвь основного репозитория
/// </summary>
public Task Commit()
{
var head = $"{LocalRepo.Owner.Login}:{CurrentBranch.Ref}";
return PullRequestsClient.Create(OriginRepo.Id,
new("AUTOMATED PR", head, OriginBranch.Ref) {Draft = true});
}

public EditingContext(IGitHubClient client, Repository originRepo, Reference originBranch, Repository localRepo,
Reference currentBranch)
{
Client = client ?? throw new ArgumentNullException(nameof(client));
OriginRepo = originRepo ?? throw new ArgumentNullException(nameof(originRepo));
OriginBranch = originBranch ?? throw new ArgumentNullException(nameof(originBranch));
LocalRepo = localRepo ?? throw new ArgumentNullException(nameof(localRepo));
CurrentBranch = currentBranch ?? throw new ArgumentNullException(nameof(currentBranch));
}
}
}
55 changes: 55 additions & 0 deletions DotNetRu.Commune.GitHubFilesystem/GitHubFileStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Octokit;

namespace DotNetRu.Commune.GitHubFilesystem
{
/// <summary>
/// Стрим данных в памяти, связанный с файлом в github репозитории
/// </summary>
internal class GitHubFileStream : MemoryStream
{
/// <summary>
/// Путь к файлу в репозитории
/// </summary>
public string Path { get; }

/// <summary>
/// SHA файла в репозитории
/// </summary>
public string RepoFileSha { get; private set; }

/// <summary>
/// Контекст в котором существует файл
/// </summary>
public EditingContext Context { get; }

public GitHubFileStream(string path, EditingContext context, string originSha)
{
Path = path ?? throw new ArgumentNullException(nameof(path));
Context = context ?? throw new ArgumentNullException(nameof(context));
RepoFileSha = originSha ?? throw new ArgumentNullException(nameof(originSha));
}

/// <inheritdoc />
public override void Flush()
{
throw new NotSupportedException();
}

/// <inheritdoc />
public override Task FlushAsync(CancellationToken cancellationToken) => FlushInternal();

protected virtual async Task FlushInternal()
{
var content = Convert.ToBase64String(this.ToArray());
var updateRequest = new UpdateFileRequest($"Flush @ {DateTime.Now}", content, RepoFileSha, Context.CurrentBranch.Ref, false);
var updateResult = await Context.ContentClient.UpdateFile(Context.LocalRepo.Id, Path, updateRequest);
RepoFileSha = updateResult.Content.Sha;
}
}
}
90 changes: 90 additions & 0 deletions DotNetRu.Commune.GitHubFilesystem/GitHubFilesystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using Octokit;
using Octokit.Helpers;
using Octokit.Internal;

namespace DotNetRu.Commune.GitHubFilesystem
{
/// <summary>
/// Провайдер файлов с github
/// </summary>
public class GitHubFilesystem : IFileProvider
{
private EditingContext? _editingContext;

/// <summary>
/// Начать сессию работы с файловой системой
/// </summary>
/// <param name="token">PAT-токен</param>
/// <param name="originRepo">наименование исходного репозитория</param>
/// <param name="originOwner">владелец исходного репозитория</param>
public async Task StartContext(string token, string originRepo, string originOwner)
{
var credStore = new InMemoryCredentialStore(new(token, AuthenticationType.Bearer));
var client = new GitHubClient(new Connection(new ProductHeaderValue("DotNetRuCommune"),
GitHubClient.GitHubApiUrl, credStore,
new HttpClientAdapter(Net5HttpMessageHandlerFactory.CreateDefault), new SimpleJsonSerializer()));

var originalRepo = await client.Repository.Get(originOwner, originRepo).ConfigureAwait(false);
var originalBranch = await client.Git.Reference.Get(originalRepo.Id, "heads/master").ConfigureAwait(false);
var fork = await client.Repository.Forks.Create(originalRepo.Id, new ()).ConfigureAwait(false);
var currentBranch = await client.Git.Reference.CreateBranch(fork.Owner.Login, fork.Name, Guid.NewGuid().ToString("N")).ConfigureAwait(false);
_editingContext = new EditingContext(client, originalRepo, originalBranch, fork, currentBranch);
}

public IFileInfo GetFileInfo(string subpath)
{
throw new NotSupportedException();
}

public async Task<GithubFile> GetFileInfoAsync(string subpath) =>
await GithubFile.Open(_editingContext ?? throw new InvalidOperationException(),subpath);

public async Task<GithubFile> CreateFileAsync(string subpath) =>
await GithubFile.Create(_editingContext ?? throw new InvalidOperationException(),subpath);

public IDirectoryContents GetDirectoryContents(string subpath) // TODO blazor не может в GetAwaiter().GetResult(). Поэтому надо реализовать свой асинхронный IFileProvider
{
throw new NotSupportedException();
}

public async Task<IDirectoryContents> GetDirectoryContentsAsync(string subpath)
{
if (_editingContext == null) throw new InvalidOperationException();
var foundContents = await _editingContext.ContentClient
.GetAllContents(_editingContext.LocalRepo.Id, subpath).ConfigureAwait(false);
return new DirectoryContents(foundContents.Select(FileFactory));
}

private GithubFile FileFactory(RepositoryContent content) =>
new(_editingContext ?? throw new InvalidOperationException(),
content.Path,
content.Name,
content.Type.Value == ContentType.Dir, false, content.Sha, false);

public IChangeToken Watch(string filter) => throw new NotSupportedException();

/// <summary>
/// Внести изменения создав новый PR
/// </summary>
/// <exception cref="InvalidOperationException">выбрасывается если не начат контекст редактирования репозитория</exception>
public async Task CommitChanges()
{
if (_editingContext == null) throw new InvalidOperationException();
try
{
await _editingContext.Commit().ConfigureAwait(false);
}
catch (Exception)
{
throw;
}
}
}
}
128 changes: 128 additions & 0 deletions DotNetRu.Commune.GitHubFilesystem/GithubFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.FileProviders;
using Octokit;

namespace DotNetRu.Commune.GitHubFilesystem
{
/// <summary>
/// реализация абстракции файла, хранящегося в репозитории github
/// </summary>
public class GithubFile : MemoryStream, IFileInfo
{
private readonly EditingContext _context;

/// <summary>
/// SHA файла в репозитории
/// </summary>
public string RepoFileSha { get; private set; }

/// <summary>
/// Содержимое файла в стриме. Можно чмитать и писать. Не забывайте делать flush
/// </summary>
/// <returns>The file stream</returns>
public Stream CreateReadStream()
{
throw new NotSupportedException();
}

public bool ContentsLoaded { get; private set; }
/// <inheritdoc />
public bool Exists { get; private set; }

/// <inheritdoc />
public string PhysicalPath { get; }

/// <inheritdoc />
public string Name { get; }

/// <inheritdoc />
public DateTimeOffset LastModified { get; }

/// <inheritdoc />
public bool IsDirectory { get; }

/// <inheritdoc />
public override void Flush()
{
throw new NotSupportedException();
}

//public override long Length => -1;

/// <inheritdoc />
public override Task FlushAsync(CancellationToken cancellationToken) => FlushInternal();

protected virtual async Task FlushInternal()
{
var content = Convert.ToBase64String(this.ToArray());
if (Exists)
{
var updateRequest = new UpdateFileRequest($"Updated {PhysicalPath} @ {DateTime.Now}", content, RepoFileSha,
_context.CurrentBranch.Ref, false);
var updateResult =
await _context.ContentClient.UpdateFile(_context.LocalRepo.Id, PhysicalPath, updateRequest);
RepoFileSha = updateResult.Content.Sha;
}
else
{
var createRequest = new CreateFileRequest($"Created {PhysicalPath} @ {DateTime.Now}", content,
_context.CurrentBranch.Ref, false);
var changeSet = await _context.ContentClient.CreateFile(_context.LocalRepo.Id, PhysicalPath, createRequest);
RepoFileSha = changeSet.Content.Sha;
Exists = true;
}
}

/// <summary>
/// Констурктор файла
/// </summary>
/// <param name="context">Контекст редактирования</param>
/// <param name="length">размер файла</param>
/// <param name="physicalPath">путь к файлу в файловой системе</param>
/// <param name="name">имя файла</param>
/// <param name="isDirectory">если истина - то это папка</param>
/// <exception cref="ArgumentNullException">выбрасывается если в аргументах преедан null</exception>
internal GithubFile([NotNull] EditingContext context,
[NotNull] string physicalPath,
[NotNull] string name,
bool isDirectory, bool isNew, string originalSha, bool isLoaded) : base()
{
_context = context ?? throw new ArgumentNullException(nameof(context));
PhysicalPath = physicalPath ?? throw new ArgumentNullException(nameof(physicalPath));
Name = name ?? throw new ArgumentNullException(nameof(name));
IsDirectory = isDirectory;
Exists = !isNew;
RepoFileSha = originalSha ?? throw new ArgumentNullException(nameof(originalSha));
ContentsLoaded = isLoaded;
}

internal static async Task<GithubFile> Open([NotNull] EditingContext context,
[NotNull] string physicalPath)
{
var contents = await context.ContentClient
.GetAllContentsByRef(context.LocalRepo.Id, physicalPath, context.CurrentBranch.Ref).ConfigureAwait(false);
var content = contents.Single();
var fs = new GithubFile(context, content.Path, content.Name, false, false, content.Sha, true);

var originalContent = await context.ContentClient
.GetRawContentByRef(context.LocalRepo.Owner.Login, context.LocalRepo.Name, physicalPath, context.CurrentBranch.Ref);
fs.Write(originalContent);
fs.Position = 0;
return fs;
}

internal static Task<GithubFile> Create([NotNull] EditingContext context,
[NotNull] string physicalPath)
{
var fs = new GithubFile(context, physicalPath, physicalPath, false, true, string.Empty, true);
fs.Position = 0;
return Task.FromResult(fs);
}
}
}
Loading