Skip to content

Commit

Permalink
Add configuration and support for GitHub
Browse files Browse the repository at this point in the history
  • Loading branch information
Scepheo committed Jun 5, 2019
1 parent 4a562ed commit 9087910
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 11 deletions.
51 changes: 40 additions & 11 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ Currently supported features are:
- Open .sln solution file
- Clone remote repositories, currently supports:
- Azure DevOps (using a personal access token)
- GitHub ( using a personal access token)

## Roadmap
## Road map

Planned features:

- Clone from GitHub
- Configuration GUI
- More actions on repositories

Expand All @@ -46,49 +46,78 @@ An example config file for someone named "John Smith" with the Windows username
"user.name": "John Smith"
}
}
],
"GitHubProviders": [
{
"Username": "john-smith",
"PersonalAccessToken": "abcdefghijkl1234567890",
"DefaultConfig": {
"user.email": "john@my-email.com",
"user.name": "John"
}
}
]
}
```

### `RepositoryFolder`
### RepositoryFolder

Path to the folder that contains all your git repositories. Defaults to
`%USERPROFILE%/Source/Repos`, which is the default clone location used by Visual
Studio.

### `VsCodePath`
### VsCodePath

Path to your installation of Visual Studio Code. Defaults to
`%USERPROFILE%/AppData/Local/Programs/Microsoft VS Code/Code.exe`, which is the
default location of a user installation of Visual Studio Code.

### `GitBashPath`
### GitBashPath

Path to your installation of Git Bash. Defaults to
`C:/Program FIles/Git/git-bash.exe`, which is where Git for Windows installs Git
`C:/Program Files/Git/git-bash.exe`, which is where Git for Windows installs Git
Bash by default.

### `AzureProviders`
### AzureProviders

Array of Azure DevOps remotes. These will be available to clone from.

#### `Organization`
#### AzureProviders - Organization

Name of the organization that contains the project. Is the `{project}` part of
`https://dev.azure.com/{project}/{organization}`.

#### `Project`
#### AzureProviders - Project

Name of the project that has the repositories you want to access. Is the
`{organization}` part of `https://dev.azure.com/{project}/{organization}`.

#### `PersonalAccessToken`
#### AzureProviders - PersonalAccessToken

An Azure DevOps personal access token that will be used for authentication. Can
be generated by going to <https://dev.azure.com/{project}/_usersSettings/tokens>
(replace `{project}` with the name of your project) and clicking "New Token".

#### `DefaultConfig`
#### AzureProviders - DefaultConfig

Keys and values for git config that should be applied to any repositories cloned
from this provider. Can be used to e.g. set your company email and proxy server.

### GitHubProviders

Array of GitHub accounts. These will be available to clone from.

#### GitHubProviders - Username

GitHub username that you want to authenticate as. All repositories that this
user has access to will be available for cloning.

#### GitHubProviders - PersonalAccessToken

A GitHub personal access token that will be used for authentication. Can be
generated by going to <https://github.com/settings/tokens>.

#### GitHubProviders - DefaultConfig

Keys and values for git config that should be applied to any repositories cloned
from this provider. Can be used to e.g. set your personal email.
91 changes: 91 additions & 0 deletions src/GitMan/Clients/GitHubClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Text.Json;

namespace GitMan.Clients
{
internal class GitHubClient
{
private readonly GitHubClientConfig _config;

public GitHubClient(GitHubClientConfig config)
{
_config = config;
}

private HttpClient GetClient()
{
var client = new HttpClient();

var acceptHeader = new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json");
client.DefaultRequestHeaders.Accept.Add(acceptHeader);

var assemblyName = Assembly.GetExecutingAssembly().GetName();
var userAgentHeader = new ProductInfoHeaderValue(assemblyName.Name, assemblyName.Version.ToString());
client.DefaultRequestHeaders.UserAgent.Add(userAgentHeader);

var patBytes = Encoding.ASCII.GetBytes(_config.Username + ":" + _config.PersonalAccessToken);
var authParameter = Convert.ToBase64String(patBytes);
var authHeader = new AuthenticationHeaderValue("Basic", authParameter);
client.DefaultRequestHeaders.Authorization = authHeader;

client.Timeout = TimeSpan.FromSeconds(30);

return client;
}

private Uri BuildUri(string path)
{
var builder = new UriBuilder
{
Host = "api.github.com",
Scheme = "https",
Path = path
};

var uriString = builder.ToString();
var uri = new Uri(uriString);
return uri;
}

private JsonDocument GetResponse(string path)
{
var uri = BuildUri(path);

using (var client = GetClient())
{
using (var response = client.GetAsync(uri).Result)
{
var json = response.Content.ReadAsStringAsync().Result;
var document = JsonDocument.Parse(json);
return document;
}
}
}

public GitHubRepository[] GetRepositories()
{
var document = GetResponse("user/repos");
var repositories = document.RootElement;

var count = repositories.GetArrayLength();
var GitHubRepos = new GitHubRepository[count];
var index = 0;

foreach (var repository in repositories.EnumerateArray())
{
var name = repository.GetProperty("name").GetString();
var fullName = repository.GetProperty("full_name").GetString();
var remoteUrl = repository.GetProperty("clone_url").GetString();
var GitHubRepo = new GitHubRepository(name, fullName, remoteUrl);
GitHubRepos[index] = GitHubRepo;
index++;
}

return GitHubRepos;
}
}
}
8 changes: 8 additions & 0 deletions src/GitMan/Clients/GitHubClientConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace GitMan.Clients
{
internal class GitHubClientConfig
{
public string Username { get; set; }
public string PersonalAccessToken { get; set; }
}
}
16 changes: 16 additions & 0 deletions src/GitMan/Clients/GitHubRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace GitMan.Clients
{
public class GitHubRepository
{
public string Name { get; }
public string FullName { get; }
public string RemoteUrl { get; }

public GitHubRepository(string name, string fullName, string remoteUrl)
{
Name = name;
FullName = fullName;
RemoteUrl = remoteUrl;
}
}
}
11 changes: 11 additions & 0 deletions src/GitMan/Config/GitHubProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.Generic;

namespace GitMan.Config
{
internal class GitHubProvider
{
public string Username { get; set; }
public string PersonalAccessToken { get; set; }
public Dictionary<string, string> DefaultConfig { get; set; } = new Dictionary<string, string>();
}
}
3 changes: 3 additions & 0 deletions src/GitMan/Config/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal class Settings
public string VsCodePath { get; set; }
public string GitBashPath { get; set; }
public AzureProvider[] AzureProviders { get; set; }
public GitHubProvider[] GitHubProviders { get; set; }

private static Settings CreateDefault()
{
Expand All @@ -18,13 +19,15 @@ private static Settings CreateDefault()
var vsCodePath = Path.Combine(userProfile, "./AppData/Local/Programs/Microsoft VS Code/Code.exe");
const string gitBashPath = "C:/Program Files/Git/git-bash.exe";
var azureProviders = Array.Empty<AzureProvider>();
var gitHubProviders = Array.Empty<GitHubProvider>();

var settings = new Settings
{
RepositoryFolder = repositoryFolder,
VsCodePath = vsCodePath,
GitBashPath = gitBashPath,
AzureProviders = azureProviders,
GitHubProviders = gitHubProviders,
};

return settings;
Expand Down
88 changes: 88 additions & 0 deletions src/GitMan/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ private MenuItem MakeCloneItem(RepositoryList existingRepositories)
items.Add(item);
}

foreach (var provider in _settings.GitHubProviders)
{
var item = MakeGitHubProviderItem(existingRepositories, provider);
items.Add(item);
}

const string name = "Clone";
var itemArray = items.ToArray();

Expand Down Expand Up @@ -207,5 +213,87 @@ void onClick(object sender, EventArgs eventArgs)
var menuItem = new MenuItem(name, onClick);
return menuItem;
}

private MenuItem MakeGitHubProviderItem(
RepositoryList existingRepositories,
GitHubProvider provider)
{
var loadItem = new MenuItem("Loading...");
var dummyItems = new[] { loadItem };

MenuItem menuItem = default;

var name = $"{provider.Username}";
var mergeType = MenuMerge.Add;
var mergeOrder = 0;
var shortcut = Shortcut.None;

void onClick(object sender, EventArgs eventArgs) { }

void onPopup(object sender, EventArgs eventArgs)
{
var originalCursor = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;

var clientConfig = new GitHubClientConfig
{
Username = provider.Username,
PersonalAccessToken = provider.PersonalAccessToken,
};

var client = new GitHubClient(clientConfig);

var repositories = client.GetRepositories()
.Where(repository => !existingRepositories.Any(existing => existing.Name == repository.Name))
.OrderBy(repository => repository.Name);

var menuItems = repositories
.Select(repository => MakeGitHubRepositoryItem(repository, provider))
.ToArray();

menuItem.MenuItems.Clear();
menuItem.MenuItems.AddRange(menuItems);

Cursor.Current = originalCursor;
}

void onSelect(object sender, EventArgs eventArgs) { }

menuItem = new MenuItem(
mergeType,
mergeOrder,
shortcut,
name,
onClick,
onPopup,
onSelect,
dummyItems);

return menuItem;
}

private MenuItem MakeGitHubRepositoryItem(GitHubRepository repository, GitHubProvider provider)
{
var name = repository.Name;

void onClick(object sender, EventArgs eventArgs)
{
var configs = provider.DefaultConfig.Select(pair => $"--config \"{pair.Key}={pair.Value}\"");
var argument = $"clone \"{repository.RemoteUrl}\" {string.Join(' ', configs)}";

var startInfo = new ProcessStartInfo
{
FileName = "git",
Arguments = argument,
UseShellExecute = true,
WorkingDirectory = _settings.RepositoryFolder,
};

Process.Start(startInfo);
}

var menuItem = new MenuItem(name, onClick);
return menuItem;
}
}
}

0 comments on commit 9087910

Please sign in to comment.