diff --git a/.editorconfig b/.editorconfig
index 1252530c4..cf2bb0aab 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -3,3 +3,5 @@ root = true
[*.cs]
indent_style = space
indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 3d2be8475..c17e1dc1c 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -9,7 +9,8 @@ Have you read GitHub for Unity's Code of Conduct? By filing an Issue, you are ex
- Be sure to run with tracing enabled to capture runtime details in the log file
- Include the log file in the PR.
- On Windows, the extension log file is at `%LOCALAPPDATA%\GitHubUnity\github-unity.log`
- - On macOS, the extension log file is at `~/Library/Application Support/GitHubUnity/github-unity.log`
+ - On macOS, the extension log file is at `~/Library/Logs/GitHubUnity/github-unity.log`
+ - On linux, the extension log file is at `~/.local/share/GitHubUnity/github-unity.log`
### Description
diff --git a/.gitignore b/.gitignore
index bcd8c5dcd..7fc38bf13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,11 @@
.vs/
.idea/
+.vscode/
packages/
_NCrunch_GitHub.Unity
*.user
.DS_Store
build/
TestResult.xml
-submodules/
\ No newline at end of file
+*.stackdump
+*.lastcodeanalysissucceeded
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index d9e14a3e9..8ecda521f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
[submodule "script"]
path = script
url = git@github.com:github-for-unity/UnityBuildScripts
+[submodule "submodules/packaging"]
+ path = submodules/packaging
+ url = https://github.com/github-for-unity/packaging
diff --git a/GitHub.Unity.sln b/GitHub.Unity.sln
index db12af918..0707a70fa 100644
--- a/GitHub.Unity.sln
+++ b/GitHub.Unity.sln
@@ -5,8 +5,12 @@ VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Unity", "src\UnityExtension\Assets\Editor\GitHub.Unity\GitHub.Unity.csproj", "{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Unity.45", "src\UnityExtension\Assets\Editor\GitHub.Unity\GitHub.Unity.45.csproj", "{ADD7A18B-DD2A-4C22-A2C1-488964EFF30B}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Api", "src\GitHub.Api\GitHub.Api.csproj", "{B389ADAF-62CC-486E-85B4-2D8B078DF763}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Api.45", "src\GitHub.Api\GitHub.Api.45.csproj", "{B389ADAF-62CC-486E-85B4-2D8B078DF76B}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Logging", "src\GitHub.Logging\GitHub.Logging.csproj", "{BB6A8EDA-15D8-471B-A6ED-EE551E0B3BA0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CopyLibrariesToDevelopmentFolder", "src\packaging\CopyLibrariesToDevelopmentFolder\CopyLibrariesToDevelopmentFolder.csproj", "{44257C81-EE4A-4817-9AF4-A26C02AA6DD4}"
@@ -25,83 +29,148 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtils", "src\tests\Test
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TaskSystem", "src\tests\TaskSystemIntegrationTests\TaskSystem.csproj", "{1A382F40-FD9E-43E1-89C1-320073F35CE9}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "src\tests\TestApp\TestApp.csproj", "{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLine", "src\tests\CommandLine\CommandLine.csproj", "{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWebServer", "src\tests\TestWebServer\TestWebServer.csproj", "{3DD3451C-30FA-4294-A3A9-1E080342F867}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityTests", "src\UnityExtension\Assets\Editor\UnityTests\UnityTests.csproj", "{462CDBD4-0DDA-4854-1B13-CFDACBFB66F5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionLoader", "src\UnityExtension\Assets\Editor\GitHub.Unity\ExtensionLoader\ExtensionLoader.csproj", "{6B0EAB30-511A-44C1-87FE-D9AB7E34D115}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityShim", "src\UnityShim\UnityShim.csproj", "{F94F8AE1-C171-4A83-89E8-6557CA91A188}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ DebugNoUnity|Any CPU = DebugNoUnity|Any CPU
dev|Any CPU = dev|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}.dev|Any CPU.ActiveCfg = dev|Any CPU
{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}.dev|Any CPU.Build.0 = dev|Any CPU
{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ADD7A18B-DD2A-4C22-A2C1-488964EFF30B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ADD7A18B-DD2A-4C22-A2C1-488964EFF30B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ADD7A18B-DD2A-4C22-A2C1-488964EFF30B}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {ADD7A18B-DD2A-4C22-A2C1-488964EFF30B}.dev|Any CPU.ActiveCfg = dev|Any CPU
+ {ADD7A18B-DD2A-4C22-A2C1-488964EFF30B}.dev|Any CPU.Build.0 = dev|Any CPU
+ {ADD7A18B-DD2A-4C22-A2C1-488964EFF30B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ADD7A18B-DD2A-4C22-A2C1-488964EFF30B}.Release|Any CPU.Build.0 = Release|Any CPU
{B389ADAF-62CC-486E-85B4-2D8B078DF763}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B389ADAF-62CC-486E-85B4-2D8B078DF763}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B389ADAF-62CC-486E-85B4-2D8B078DF763}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {B389ADAF-62CC-486E-85B4-2D8B078DF763}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
{B389ADAF-62CC-486E-85B4-2D8B078DF763}.dev|Any CPU.ActiveCfg = dev|Any CPU
{B389ADAF-62CC-486E-85B4-2D8B078DF763}.dev|Any CPU.Build.0 = dev|Any CPU
{B389ADAF-62CC-486E-85B4-2D8B078DF763}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B389ADAF-62CC-486E-85B4-2D8B078DF763}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B389ADAF-62CC-486E-85B4-2D8B078DF76B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B389ADAF-62CC-486E-85B4-2D8B078DF76B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B389ADAF-62CC-486E-85B4-2D8B078DF76B}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {B389ADAF-62CC-486E-85B4-2D8B078DF76B}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
+ {B389ADAF-62CC-486E-85B4-2D8B078DF76B}.dev|Any CPU.ActiveCfg = dev|Any CPU
+ {B389ADAF-62CC-486E-85B4-2D8B078DF76B}.dev|Any CPU.Build.0 = dev|Any CPU
+ {B389ADAF-62CC-486E-85B4-2D8B078DF76B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B389ADAF-62CC-486E-85B4-2D8B078DF76B}.Release|Any CPU.Build.0 = Release|Any CPU
{BB6A8EDA-15D8-471B-A6ED-EE551E0B3BA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB6A8EDA-15D8-471B-A6ED-EE551E0B3BA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BB6A8EDA-15D8-471B-A6ED-EE551E0B3BA0}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {BB6A8EDA-15D8-471B-A6ED-EE551E0B3BA0}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
{BB6A8EDA-15D8-471B-A6ED-EE551E0B3BA0}.dev|Any CPU.ActiveCfg = dev|Any CPU
{BB6A8EDA-15D8-471B-A6ED-EE551E0B3BA0}.dev|Any CPU.Build.0 = dev|Any CPU
{BB6A8EDA-15D8-471B-A6ED-EE551E0B3BA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB6A8EDA-15D8-471B-A6ED-EE551E0B3BA0}.Release|Any CPU.Build.0 = Release|Any CPU
{44257C81-EE4A-4817-9AF4-A26C02AA6DD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44257C81-EE4A-4817-9AF4-A26C02AA6DD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {44257C81-EE4A-4817-9AF4-A26C02AA6DD4}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {44257C81-EE4A-4817-9AF4-A26C02AA6DD4}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
{44257C81-EE4A-4817-9AF4-A26C02AA6DD4}.dev|Any CPU.ActiveCfg = Debug|Any CPU
{44257C81-EE4A-4817-9AF4-A26C02AA6DD4}.dev|Any CPU.Build.0 = Debug|Any CPU
{44257C81-EE4A-4817-9AF4-A26C02AA6DD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44257C81-EE4A-4817-9AF4-A26C02AA6DD4}.Release|Any CPU.Build.0 = Release|Any CPU
{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.dev|Any CPU.ActiveCfg = Debug|Any CPU
{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.dev|Any CPU.Build.0 = Debug|Any CPU
{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.Release|Any CPU.Build.0 = Release|Any CPU
{1AC3F82E-AEAE-4C84-825C-207BB264FCFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1AC3F82E-AEAE-4C84-825C-207BB264FCFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1AC3F82E-AEAE-4C84-825C-207BB264FCFA}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {1AC3F82E-AEAE-4C84-825C-207BB264FCFA}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
{1AC3F82E-AEAE-4C84-825C-207BB264FCFA}.dev|Any CPU.ActiveCfg = Debug|Any CPU
{1AC3F82E-AEAE-4C84-825C-207BB264FCFA}.dev|Any CPU.Build.0 = Debug|Any CPU
{1AC3F82E-AEAE-4C84-825C-207BB264FCFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AC3F82E-AEAE-4C84-825C-207BB264FCFA}.Release|Any CPU.Build.0 = Release|Any CPU
{7DEF4226-7740-457F-9199-34174C49A978}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7DEF4226-7740-457F-9199-34174C49A978}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7DEF4226-7740-457F-9199-34174C49A978}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
{7DEF4226-7740-457F-9199-34174C49A978}.dev|Any CPU.ActiveCfg = Debug|Any CPU
{7DEF4226-7740-457F-9199-34174C49A978}.dev|Any CPU.Build.0 = Debug|Any CPU
{7DEF4226-7740-457F-9199-34174C49A978}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7DEF4226-7740-457F-9199-34174C49A978}.Release|Any CPU.Build.0 = Release|Any CPU
{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.dev|Any CPU.ActiveCfg = Debug|Any CPU
{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.dev|Any CPU.Build.0 = Debug|Any CPU
{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.Release|Any CPU.Build.0 = Release|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1A382F40-FD9E-43E1-89C1-320073F35CE9}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {1A382F40-FD9E-43E1-89C1-320073F35CE9}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.dev|Any CPU.ActiveCfg = Debug|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.dev|Any CPU.Build.0 = Debug|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.Release|Any CPU.Build.0 = Release|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.dev|Any CPU.ActiveCfg = Debug|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.dev|Any CPU.Build.0 = Debug|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.Release|Any CPU.Build.0 = Release|Any CPU
{3DD3451C-30FA-4294-A3A9-1E080342F867}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DD3451C-30FA-4294-A3A9-1E080342F867}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3DD3451C-30FA-4294-A3A9-1E080342F867}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {3DD3451C-30FA-4294-A3A9-1E080342F867}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
{3DD3451C-30FA-4294-A3A9-1E080342F867}.dev|Any CPU.ActiveCfg = Debug|Any CPU
{3DD3451C-30FA-4294-A3A9-1E080342F867}.dev|Any CPU.Build.0 = Debug|Any CPU
{3DD3451C-30FA-4294-A3A9-1E080342F867}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DD3451C-30FA-4294-A3A9-1E080342F867}.Release|Any CPU.Build.0 = Release|Any CPU
+ {462CDBD4-0DDA-4854-1B13-CFDACBFB66F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {462CDBD4-0DDA-4854-1B13-CFDACBFB66F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {462CDBD4-0DDA-4854-1B13-CFDACBFB66F5}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {462CDBD4-0DDA-4854-1B13-CFDACBFB66F5}.dev|Any CPU.ActiveCfg = Debug|Any CPU
+ {462CDBD4-0DDA-4854-1B13-CFDACBFB66F5}.dev|Any CPU.Build.0 = Debug|Any CPU
+ {462CDBD4-0DDA-4854-1B13-CFDACBFB66F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {462CDBD4-0DDA-4854-1B13-CFDACBFB66F5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B0EAB30-511A-44C1-87FE-D9AB7E34D115}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B0EAB30-511A-44C1-87FE-D9AB7E34D115}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B0EAB30-511A-44C1-87FE-D9AB7E34D115}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B0EAB30-511A-44C1-87FE-D9AB7E34D115}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
+ {6B0EAB30-511A-44C1-87FE-D9AB7E34D115}.dev|Any CPU.ActiveCfg = dev|Any CPU
+ {6B0EAB30-511A-44C1-87FE-D9AB7E34D115}.dev|Any CPU.Build.0 = dev|Any CPU
+ {6B0EAB30-511A-44C1-87FE-D9AB7E34D115}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B0EAB30-511A-44C1-87FE-D9AB7E34D115}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F94F8AE1-C171-4A83-89E8-6557CA91A188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F94F8AE1-C171-4A83-89E8-6557CA91A188}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F94F8AE1-C171-4A83-89E8-6557CA91A188}.DebugNoUnity|Any CPU.ActiveCfg = Debug|Any CPU
+ {F94F8AE1-C171-4A83-89E8-6557CA91A188}.DebugNoUnity|Any CPU.Build.0 = Debug|Any CPU
+ {F94F8AE1-C171-4A83-89E8-6557CA91A188}.dev|Any CPU.ActiveCfg = dev|Any CPU
+ {F94F8AE1-C171-4A83-89E8-6557CA91A188}.dev|Any CPU.Build.0 = dev|Any CPU
+ {F94F8AE1-C171-4A83-89E8-6557CA91A188}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F94F8AE1-C171-4A83-89E8-6557CA91A188}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -115,5 +184,9 @@ Global
{1A382F40-FD9E-43E1-89C1-320073F35CE9} = {D17F1B4C-42DC-4E78-BCEF-9F239A084C4D}
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8} = {D17F1B4C-42DC-4E78-BCEF-9F239A084C4D}
{3DD3451C-30FA-4294-A3A9-1E080342F867} = {D17F1B4C-42DC-4E78-BCEF-9F239A084C4D}
+ {462CDBD4-0DDA-4854-1B13-CFDACBFB66F5} = {D17F1B4C-42DC-4E78-BCEF-9F239A084C4D}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {66BD4D50-3779-4912-9596-2C838BF24911}
EndGlobalSection
EndGlobal
diff --git a/GitHub.Unity.sln.DotSettings b/GitHub.Unity.sln.DotSettings
index 31c5e56f1..2166c735a 100644
--- a/GitHub.Unity.sln.DotSettings
+++ b/GitHub.Unity.sln.DotSettings
@@ -22,8 +22,11 @@
END_OF_LINE
1
1
+ False
+ False
False
True
+ NEVER
False
True
False
@@ -339,8 +342,13 @@
SSH
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ True
+ True
+ True
+ True
True
True
+ True
True
True
True
diff --git a/LICENSE b/LICENSE
index 9f06ebe84..a306528ee 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2016-2018 GitHub
+Copyright (c) 2016-2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 280831465..1cd67b951 100644
--- a/README.md
+++ b/README.md
@@ -1,184 +1,36 @@
# [GitHub for Unity](https://unity.github.com)
-## Notices
-
-From version 0.19 onwards, the location of the plugin has moved to `Assets/Plugins/GitHub`. If you have version 0.18 or lower, you need to delete the `Assets/Editor/GitHub` folder before you install newer versions. You should exit Unity and delete the folder from Explorer/Finder, as Unity will not unload native libraries while it's running. Also, remember to update your `.gitignore` file.
-
-
+## NOTICE OF DEPRECATION
-[](https://gitter.im/github-for-unity/Unity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[](https://discord.gg/5zH8hVx)
-[](https://www.twitch.tv/sh4na)
+This project is dead y'all! Remove GitHub for Unity from your project, then go to https://github.com/spoiledcat/git-for-unity and install Git for Unity from the instructions there.
-## About
+# What is it
The GitHub for Unity extension brings [Git](https://git-scm.com/) and GitHub into [Unity](https://unity3d.com/), integrating source control into your work with friendly and accessible tools and workflows.
-**Please note:** this software is currently alpha quality. Please refer to the [list of known issues](https://github.com/github-for-unity/Unity/issues?q=is%3Aissue+is%3Aopen+label%3Abug), and make sure you have backups of your work before trying it out.
-
-#### Table Of Contents
-
-[Installing GitHub for Unity](#installing-github-for-unity)
- * [Requirements](#requirements)
- * [Git on macOS](#git-on-macos)
- * [Git on Windows](#git-on-windows)
- * [Installation](#installation)
- * [Log files](#log-files)
- * [Windows](#windows)
- * [macOS](#macos)
-
-[Building and Contributing](#building-and-contributing)
-
-[Quick Guide to GitHub for Unity](#quick-guide-to-github-for-unity)
- * [Opening the GitHub window](#opening-the-github-window)
- * [Initialize Repository](#initialize-repository)
- * [Authentication](#authentication)
- * [Publish a new repository](#publish-a-new-repository)
- * [Commiting your work - Changes tab](#commiting-your-work---changes-tab)
- * [Pushing/pulling your work - History tab](#pushingpulling-your-work---history-tab)
- * [Branches tab](#branches-tab)
- * [Settings tab](#settings-tab)
-
-[More Resources](#more-resources)
-
-[License](#license)
-
-## Installing GitHub for Unity
-
-### Requirements
-
-- Unity 5.4 or higher
- - There's currently an blocker issue opened for 5.3 support, so we know it doesn't run there. Personal edition is fine.
-- Git and Git LFS 2.x
-
-#### Git on macOS
-
-The current release has limited macOS support. macOS users will need to install the latest [Git](https://git-scm.com/downloads) and [Git LFS](https://git-lfs.github.com/) manually, and make sure these are on the path. You can configure the Git location in the Settings tab on the GitHub window.
-
-The easiest way of installing git and git lfs is to install [Homebrew](https://brew.sh/) and then do `brew install git git-lfs`.
+You can reach the team right here by opening a [new issue](https://github.com/github-for-unity/Unity/issues/new). You can also tweet at [@GitHubUnity](https://twitter.com/GitHubUnity)
-Make sure a Git user and email address are set in the `~/.gitconfig` file before you initialize a repository for the first time. You can set these values by opening your `~/.gitconfig` file and adding the following section, if it doesn't exist yet:
+[](https://ci.appveyor.com/project/github-windows/unity)
-```
-[user]
- name = Your Name
- email = Your Email
-```
-
-#### Git on Windows
-
-The GitHub for Unity extension ships with a bundle of Git and Git LFS, to ensure that you have the correct version. These will be installed into `%LOCALAPPDATA%\GitHubUnity` when the extension runs for the first time.
-
-Make sure a Git user and email address are set in the `%HOME%\.gitconfig` file before you initialize a repository for the first time. You can set these values by opening your `%HOME%\.gitconfig` file and adding the following section, if it doesn't exist yet:
-
-```
-[user]
- name = Your Name
- email = Your Email
-```
-
-Once the extension is installed, you can open a command line with the same Git and Git LFS version that the extension uses by going to `Window` -> `GitHub Command Line` in Unity.
-
-### Installation
-
-This extensions needs to be installed (and updated) for each Unity project that you want to version control.
-First step is to download the latest package from [the releases page](https://github.com/github-for-unity/Unity/releases);
-it will be saved as a file with the extension `.unitypackage`.
-To install it, open Unity, then open the project you want to version control, and then double click on the downloaded package.
-Alternatively, import the package by clicking Assets, Import Package, Custom Package, then select the downloaded package.
-
-#### Log files
-
-##### macOS
-
-he extension log file can be found at `~/Library/Application Support/GitHubUnity/github-unity.log`
+## Notices
-##### Windows
+Please refer to the [list of known issues](https://github.com/github-for-unity/Unity/issues?q=is%3Aissue+is%3Aopen+label%3Abug), and make sure you have backups of your work before trying it out.
-The extension log file can be found at `%LOCALAPPDATA%\GitHubUnity\github-unity.log`
+From version 0.19 onwards, the location of the plugin has moved to `Assets/Plugins/GitHub`. If you have version 0.18 or lower, you need to delete the `Assets/Editor/GitHub` folder before you install newer versions. You should exit Unity and delete the folder from Explorer/Finder, as Unity will not unload native libraries while it's running. Also, remember to update your `.gitignore` file.
## Building and Contributing
-The [CONTRIBUTING.md](CONTRIBUTING.md) document will help you get setup and familiar with the source. The [documentation](docs/) folder also contains more resources relevant to the project.
-
Please read the [How to Build](docs/contributing/how-to-build.md) document for information on how to build GitHub for Unity.
-If you're looking for something to work on, check out the [up-for-grabs](https://github.com/github-for-unity/Unity/issues?q=is%3Aopen+is%3Aissue+label%3Aup-for-grabs) label.
-
-
-## I have a problem with GitHub for Unity
-
-First, please search the [open issues](https://github.com/github-for-unity/Unity/issues?q=is%3Aopen)
-and [closed issues](https://github.com/github-for-unity/Unity/issues?q=is%3Aclosed)
-to see if your issue hasn't already been reported (it may also be fixed).
-
-If you can't find an issue that matches what you're seeing, open a [new issue](https://github.com/github-for-unity/Unity/issues/new)
-and fill out the template to provide us with enough information to investigate
-further.
-
-## Quick Guide to GitHub for Unity
-
-### Opening the GitHub window
-
-You can access the GitHub window by going to Windows -> GitHub. The window opens by default next to the Inspector window.
-
-### Initialize Repository
-
-
-
-If the current Unity project is not in a Git repository, the GitHub for Unity extension will offer to initialize the repository for you. This will:
-
-- Initialize a git repository at the Unity project root via `git init`
-- Initialize git-lfs via `git lfs install`
-- Set up a `.gitignore` file at the Unity project root.
-- Set up a `.gitattributes` file at the Unity project root with a large list of known binary filetypes (images, audio, etc) that should be tracked by LFS
-- Configure the project to serialize meta files as text
-- Create an initial commit with the `.gitignore` and `.gitattributes` file.
-
-### Authentication
-
-To set up credentials in Git so you can push and pull, you can sign in to GitHub by going to `Window` -> `GitHub` -> `Account` -> `Sign in`. You only have to sign in successfully once, your credentials will remain on the system for all Git operations in Unity and outside of it. If you've already signed in once but the Account dropdown still says `Sign in`, ignore it, it's a bug.
-
-
-
-### Publish a new repository
-
-1. Go to [github.com](https://github.com) and create a new empty repository - do not add a license, readme or other files during the creation process.
-2. Copy the **https** URL shown in the creation page
-3. In Unity, go to `Windows` -> `GitHub` -> `Settings` and paste the url into the `Remote` textbox.
-3. Click `Save repository`.
-4. Go to the `History` tab and click `Push`.
-
-### Commiting your work - Changes tab
-
-You can see which files have been changed and commit them through the Changes tab. `.meta` files will show up in relation to their files on the tree, so you can select a file for comitting and automatically have their `.meta`
-
-
-
-### Pushing/pulling your work - History tab
-
-The history tab includes a `Push` button to push your work to the server. Make sure you have a remote url configured in the `Settings` tab so that you can push and pull your work.
-
-To receive updates from the server by clicking on the `Pull` button. You cannot pull if you have local changes, so commit your changes before pulling.
-
-
-
-### Branches tab
-
-
-
-### Settings tab
-
-You can configure your user data in the Settings tab, along with the path to the Git installation.
+The [CONTRIBUTING.md](CONTRIBUTING.md) document will help you get setup and familiar with the source. The [documentation](docs/) folder also contains more resources relevant to the project.
-Locked files will appear in a list in the Settings tab. You can see who has locked a file and release file locks after you've pushed your work.
+If you're looking for something to work on, check out the [up-for-grabs](https://github.com/github-for-unity/Unity/issues?q=is%3Aopen+is%3Aissue+label%3Aup-for-grabs) label.
-
+## How to use
-## More Resources
+The [quick guide to GitHub for Unity](docs/using/quick-guide.md)
-See [unity.github.com](https://unity.github.com) for more product-oriented
-information about GitHub for Unity.
+More [in-depth information](docs/readme.md)
## License
@@ -188,6 +40,6 @@ The MIT license grant is not for GitHub's trademarks, which include the logo
designs. GitHub reserves all trademark and copyright rights in and to all
GitHub trademarks. GitHub's logos include, for instance, the stylized
Invertocat designs that include "logo" in the file title in the following
-folder: [IconsAndLogos](https://github.com/github-for-unity/Unity/tree/master/src/UnityExtension/Assets/Editor/GitHub.Unity/IconsAndLogos).
+folder: [IconsAndLogos](src/UnityExtension/Assets/Editor/GitHub.Unity/IconsAndLogos).
Copyright 2015 - 2018 GitHub, Inc.
diff --git a/appveyor.yml b/appveyor.yml
index a5d0c4580..bfb3e1b77 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,35 +1,28 @@
-version: '{build}.{branch}'
environment:
GHFU_KEY:
secure: KFcQA1VOCEMGUgy2dxH8G5O7C9DsAtQrnc6LakFpd9BRFtNnt2E8RSadPoJwQ9gztWaLS8vQLdU7cV5Ivt01LOnPI2kU1fQd2SHtKwJFve8ppvK/yZ/luhiXvIdGeEAiXQyuc1WUwuECoJVA6n7/uQKr1Q+eHniMitHuFpyQ7OqnwF6f+4TeBS6D78fKd3QoeP4XDdxCjNWPNmLv7BvWFMm5CuTK9aAWhOy8em/nVIED8qt36uHnncsDn+DH7uunj+VwmhVS+4yhuKHHz5naiUAHHIziZ4wBW6Q8rcf7xYEeISjlfxJ6TXs4Wwp406AO+n3v1DZaxSXwvoxplfopeGyb6imJfbwdTU/MHf2uj9wXobR8UhDarcrugVW+J3bqZyvkg20HfSe80gfQUBlK5OdMAp58dhWkddvJSO5TnzqmLlo/60gZxjheIbjdLaavSKcM4xOALXQlBbJJgVQrB/F6tYf7pRK3BlS8VoakyOGjJRzsSNdssSVrLVW3rwANORbH6Z1ZYvQw2ObP6/EBMceer0+JV4y9zB5q9C68erlr1NtJB0xKUp9/7I5GQj4lJ+pDsaFdsj40SyyD4yazSZf/3VIhZi/rQTJm0Ft0ifTZGSxnNTOVMZ5fsoJmUL0bn75Xt9q59cKYzK041HtEzRSElnBeuTf+Sm/MLLV28P1sonwntMhcYQ5ZPIuGmKa8jAJ0kxPXyT56MPJpwbNrbCOw2t9hXg5QbYv/+0RcoRoJ7P/5OY3M6mj8Emtu8N0SFKD6lfv9KNLFAyBuF6Ml7RyOs5RRogIdEapegTY7jwGH7igibrP0lt6HYshM7hKQrRYm2saokBV9TfgV9qnPyGs5/zyTTUGW4y0LavxiXl/vQIpxwCg4liCf1Dgw4Zrxvh40bziKGc3X06RJcysJ8cskOi6gB8eK8VTbAy6/Ufgv+pyjIns1iJxmdVbl8MrlgPXmOephdRPYZJDiFd4ynw0Slm1mqbzPWHdQ/mtMGxNRcysIOPKzKaKvK7Syor5SNtv4HU097dyjVQyW9krHaX/DSnx++dMDCIZJEDYxFw7LmTvl6AFWU4C3HM7+26cHBuBMBYS1PwcRijG+hwsHiXIomVuVglcxp5HC2eFbtBF9h1g030/tCeIBhZIVdSVO78319CsL1+aVtI5WjeQglH1OcTS42OF1Nb/4EjaN4I/w6yRJU5dmK7Q+rHQ+7NnPT4n5flQV6oe1XNbLen0raDuGos6v+aaoOQ50HlCSMMBJ3liapVXIAQ+Z/XM/cNZZa1TB6/C363Hjrts6Uq3IjKXmomhA33je+Wl6mTZqBucXUJs76p+ZgKubWvfzK/e6tORJggAgFNoa9Y56r3t7J8UdUolt301I1cCVz9CvYMUsWTmjKTR5SpssbbcuRcFUhgkHrhsSq0rBef/dfn5VEP8sateiqbpMne5iWO4Wc0Qx2/fJfx2zf9oqO3MihAyYDwPI7JCrmccJY9cHV89YlrytZuh87aBQ8d+T5ELdeG3bbYGYbKJ/yOGxMo8cRSomWUp2809Ea/lgu6WjyY1SdEjh20fwONOTRd/AA3h3XIMU7NY999YOEy1zj3yeP+awozDWQU8GSojA/ceLU5HT0U/RT2XJgkzUFb/u8wanNG6InPqvZy01bLR8JZmqXZl/4XgNpEsbzwPxjzuZJP6O4f2Usq1ZmHUfwlouXLWuHTv5/DPYJ9kO91wjzAH46IsoadmQkRDomHYCDPRCnYoS2zBkmBNukCgCKWsSwD5Msw/tpgIorMi5AAFhIOeWt/7tcKZ5nslbbnmZFtDkOBriPEiOjrAziRqFNAdBjecMrckRQrlkYjkpJG/pXZXYq219/8Sy1/HdNrWTCdq7nc947Fvq41CfumT4c2TqhVc/oflJ9SaxIl2A1Vtbw5LkmQKL08vOitsyZgRupcbqLcSYGo2dG+ks0gK5o2rvNp1nyN3ADh7JFmtD7/og55zlsAj7wP6rLEZ44h+dk7+Sh96WoCPfzSJnchg2vsydTpK6sG3Cp5qjEk0Tps88nX0SPvEPxwEpBL08+XIlg5OVxiTIYI0NSeEjAxwip2ptgaeI4c0yPB5SahMArEw/8YeEflWhiDyjoG4Bw0O1v9fRSSYiFhcYwrkr6yBK81hx6uH6DzqDqtKOxwJ3kKhapfwZXStmeOt4AwiSUHO8TiX1t0i7Jqwl7mRduz3LfmqGCeEsNxnLuhc6MPeEva8LO8ILCEcVz8bHlwUMWqabdZRm9UtbWtZp/u8ffPSBbgNFna2kKFr/F7dmXiv18CpNHOGxb/rSdmIaov1nXJR7XUyKPRO548PHE6iNxuNjWjcuFw1L0IUCNEeVUs7tvNHUTYOXRvfXNm3DbhjFnGix2JVCB2xz6QhDV4Hh6y0/rJl0b2dW25iM5HZdwCBGwgGM+9HyD7r+OBiRn+rd996c81+JsWL4jsa//16uwcbEpsF3tAB7b0by4qHbeZ+Gs3M06Sje4UVpLgKQVHSd/hfo4M70v3APhyz0WFBhLLZyouz0OdazKZ4W+HGBcunAPw/sYdMYZLe4ZmA6B+wxtSzojNKFaCFWoh3S5vLClZTraj7Mhh02PPsY0fmo15ceHBwKjMfGZ0pXt8uiPL29ECUstxSLVnPv6M4uXPJa7k+0lvj7XdB7aJ/LzexPAa/Z1+hsr2sO9An5qPnKM5Tp5zj9Xq2T7WBiDObYLxYZX5ez32jKfSYgv3cpIo5HnhKB3rZL3Alp6iJ2NFsDiB6pIUc2YQ3UU8wiMU90ifA83ORttzRDdLCuH1lYCHPk8rcVqeydgNrI4pRVrdIah3wm6hHc7YjSSnjIOhcl286iVtYgn10RUKxcs//ElgoGm0IkefKRy2WcDDL+10ZifpSWxRu0yrpwlxd0uHCAhrkOEnvaamn+0TSu/6s9VxoUyn9ZJhY7Jgnb6Z9Qxi4C+u2vXf6lOQvzl4AawnD9DW+w2L6hr2njGhvgjj2VLIHM/GIOV/OaYW97AiW0NBuEGDyBiuj8TxIUL7IuVj+QZVfyUzZHHL0c0Hy4jlQ+sh2nFzOAGWVZwEdAvLl9JCCs46iA9DHtBSrHxit7lytyspp7q8TYfE1lA0pIwkx20E3t+4CNdUQAr/IJaZJxhdfKAyW3UipP4LdRbweyYHZYFkoN0gEDMrzE0yB7XFNw5ddm/+o8KIuSUl44UVFcp2j0KPfuXadx7Pz1aa5HKpVUdc5CfJOjqgPJFn/MQU702YdUaV0qD+EHDOiVv313gUHdy9kpieQ3s2LDSh0qBkPdxLAdYXKLP24Mj3V+A2lyHU1WtLrIEVP37eCAFSYPf6Lz6TW4zrEBpHF4nwlE8M+0jQ/oB4lINxnkCa3YKYLFMiZ3dAmqGzVElesgymmB21xvdfrHgB1Z5OtQqYT8PPAw6llujXv6Pj9CqDGGS4U8UeW5GCFi/qyV6+hdg2IUsWtSzkbLJ5n8cfafEYeRBRgzK/B6qlTmoOrRl+bzmjVCJX29P+38KCpu7srnSQ+T0fR6t0OWyHGfC/39iMzATnhpiIXdnngVV9Cypgod5we44C2Rb4Or/nr5mdEidElIIthDiD7GHPNSeMXrdxs+ow76rh42DiY7x0L0SMRWyUEz0seL1JdBCdNn/7LuSn4CVpggqZD8anf9n+IUjrJtqQ+AvaogfuxM65byhGK4iVIijrogfBHb4nGywXxeEKe03JJ8nOWWN2ndyNhMW1dfNGraHvAt7DWL+/tp4qKCA89VFaZjwsqINANF1VVwh96SB6qT4tlKJjaPD3YpawT6Jfs+cg3pMj36FIPzHoNd/r+LwCBZ0WiA5xZiO0DX6WhwTfJVStsz4i9VXElCmWF2dpf5kTEC0T62Y1VCc++M1cTfwX34mdHPvdsm1Vi1qpqz4HTez8ateFukyj1FIN7++eYWoBJBoclhb3y/VUFwepORi84pz1fXUSSl8Fpg2U7NRyj+gcM5v/VAC1FGR4CJVpODIdROF7mCrLTbPzLn8Fv7EJHgHKNeU/sIT13+5V/UJSZPAxWcaUKhRWWuShSVb/1U13LjiWkHvmuH7SVLHbJDO5C5lA589rz4weTMd1OSymPuNB/xj2d2YrJUwqB3olsaxwm8w/bs2ot4GF4HFAdx3l0ESiR8jkBNAvr6vwRcXv+7nfXRpx2Mo5QU2YaunbqZxibmtNCQZBH8ZpQyUZOek4A5qDh6HW2VyJqKXeE8u1fbtOzB9xDYxgTrlVFhCw==
-clone_script:
-- ps: >-
- if(-not $env:appveyor_pull_request_number) {
- git lfs clone -q -n --branch=$env:appveyor_repo_branch https://github.com/$env:appveyor_repo_name.git $env:appveyor_build_folder
- git checkout -qf $env:appveyor_repo_commit
- } else {
- git lfs clone -q -n https://github.com/$env:appveyor_repo_name.git $env:appveyor_build_folder
- git fetch -q origin +refs/pull/$env:appveyor_pull_request_number/merge:
- git lfs fetch origin FETCH_HEAD
- git checkout -qf FETCH_HEAD
- }
-
- Set-Location $env:appveyor_build_folder
+ matrix:
+ - node_version: '8'
install:
- ps: >-
+ $full_build = Test-Path env:GHFU_KEY
+
+ $package = $full_build
+
git submodule sync
git submodule init
- $full_build = Test-Path env:GHFU_KEY
-
if ($full_build) {
+ $env:BUILD_TYPE="full"
$fileContent = "-----BEGIN RSA PRIVATE KEY-----`n"
$fileContent += $env:GHFU_KEY.Replace(' ', "`n")
$fileContent += "`n-----END RSA PRIVATE KEY-----`n"
Set-Content c:\users\appveyor\.ssh\id_rsa $fileContent
+ Install-Product node $env:node_version
} else {
+ $env:BUILD_TYPE="partial"
git submodule deinit script
$destdir = Join-Path $env:appveyor_build_folder 'lib'
$destfile = Join-Path $destdir 'deps.zip'
@@ -43,6 +36,24 @@ install:
nuget restore GitHub.Unity.sln
+ Set-Location $env:appveyor_build_folder
+
+ $version = Get-Content "$($env:appveyor_build_folder)\common\SolutionInfo.cs" | %{ $regex = "const string GitHubForUnityVersion = `"([^`"]*)`""; if ($_ -match $regex) { $matches[1] } }
+
+ $env:package_version="$($version).$($env:APPVEYOR_BUILD_NUMBER)"
+
+ Update-AppveyorBuild -Version $env:package_version
+
+ $message = "Building "
+
+ if ($package) { $message += "and packaging "}
+
+ if ($full_build) { $message += "(full build)" } else { $message += "(partial build)" }
+
+ $message += " version " + $env:package_version + " "
+
+ Write-Host $message
+
assembly_info:
patch: false
file: common\SolutionInfo.cs
@@ -53,13 +64,44 @@ build:
project: GitHub.Unity.sln
verbosity: minimal
test:
+ assemblies:
+ except:
+ - '**\*.TestRunner.dll'
+ - '**\*.TestUtils.dll'
categories:
except:
- DoNotRunOnAppVeyor
-artifacts:
-- path: unity\PackageProject
- type: zip
- name: github-for-unity-packageproject
-- path: build\*.log
-on_failure:
- - ps: Get-ChildItem build\*.log | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
+on_success:
+- ps: |
+ if ($package) {
+ $rootdir=$env:appveyor_build_folder
+ Set-Location $rootdir
+ $sourcedir="$rootdir\unity\PackageProject"
+ $packagename="github-for-unity-$($env:package_version)"
+ $packagefile="$rootdir\$($packagename).unitypackage"
+ $commitfile="$sourcedir\commit"
+ $zipfile="$rootdir\PackageProject-$($env:package_version).zip"
+
+ # generate mdb files
+ Write-Output "Generating mdb files"
+ Get-ChildItem -Recurse "$($sourcedir)\*.pdb" | foreach { $_.fullname.substring(0, $_.fullname.length - $_.extension.length) } | foreach { Write-Output "Generating $($_).mdb"; & 'lib\pdb2mdb.exe' "$($_).dll" }
+
+ # generate unitypackage
+ Write-Output "Generating $packagefile"
+ submodules\packaging\unitypackage\run.ps1 -PathToPackage:$sourcedir -OutputFolder:$rootdir -PackageName:$packagename
+
+ # save commit
+ Add-Content $commitfile $appveyor_repo_commit
+
+ Write-Output "Zipping $sourcedir to $zipfile"
+ 7z a $zipfile $sourcedir
+
+ Write-Output "Uploading $zipfile"
+ Push-AppveyorArtifact $zipfile -DeploymentName source
+ Push-AppveyorArtifact $packagefile -DeploymentName package
+ Push-AppveyorArtifact "$($packagefile).md5" -DeploymentName package
+ }
+on_finish:
+- ps: |
+ Set-Location $env:appveyor_build_folder
+ Get-ChildItem $env:appveyor_build_folder\build\*.log | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name -DeploymentName logs }
diff --git a/build.sh b/build.sh
index 2ebedc2c0..900c994f7 100755
--- a/build.sh
+++ b/build.sh
@@ -29,7 +29,7 @@ fi
if [ x"$OS" == x"Windows" ]; then
common/nuget restore GitHub.Unity.sln
else
- nuget restore GitHub.Unity.sln
+ mono common/nuget.exe restore GitHub.Unity.sln
fi
xbuild GitHub.Unity.sln /verbosity:minimal /property:Configuration=$Configuration /target:$Target || true
diff --git a/common/SolutionInfo.cs b/common/SolutionInfo.cs
index 93fe06d45..eb95db6a0 100644
--- a/common/SolutionInfo.cs
+++ b/common/SolutionInfo.cs
@@ -6,12 +6,12 @@
using System.Runtime.InteropServices;
[assembly: AssemblyProduct("GitHub for Unity")]
-[assembly: AssemblyVersion(System.AssemblyVersionInformation.Version)]
-[assembly: AssemblyFileVersion(System.AssemblyVersionInformation.Version)]
+[assembly: AssemblyVersion(System.AssemblyVersionInformation.VersionForAssembly)]
+[assembly: AssemblyFileVersion(System.AssemblyVersionInformation.VersionForAssembly)]
[assembly: AssemblyInformationalVersion(System.AssemblyVersionInformation.Version)]
[assembly: ComVisible(false)]
[assembly: AssemblyCompany("GitHub, Inc.")]
-[assembly: AssemblyCopyright("Copyright GitHub, Inc. 2017-2018")]
+[assembly: AssemblyCopyright("Copyright GitHub, Inc. 2016-2019")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -31,6 +31,10 @@
namespace System
{
internal static class AssemblyVersionInformation {
- internal const string Version = "0.31.6";
+ private const string GitHubForUnityVersion = "1.4.0";
+ internal const string VersionForAssembly = GitHubForUnityVersion;
+
+ // If this is an alpha, beta or other pre-release, mark it as such as shown below
+ internal const string Version = GitHubForUnityVersion; // GitHubForUnityVersion + "-beta1"
}
}
diff --git a/common/build.targets b/common/build.targets
index 450909d08..163a071a8 100644
--- a/common/build.targets
+++ b/common/build.targets
@@ -16,7 +16,11 @@
- Location of Unity dlls is not set. You'll need to install Unity in a known location (the default installation directory for your system), or copy UnityEngine.dll and UnityEditor.dll to the {0}lib folder
+ The location of Unity dlls is not set. You'll need to install Unity in a known location (the default installation directory for your system), or:
+ - Copy UnityEngine.dll and UnityEditor.dll to the {0}lib\Managed folder
+ - Copy UnityEngine.TestRunner.dll to the {0}lib\UnityExtensions/Unity/TestRunner folder
+ - Copy UnityEditor.TestRunner.dll to the {0}lib\UnityExtensions/Unity/TestRunner/Editor folder
+
To build the OctokitDebugging solution you need to have https://github.com/github-for-unity/octokit.net checked out in {0}
To build the OctokitDebugging solution you need to have https://github.com/github-for-unity/dotnet-httpclient35 checked out in {0}
diff --git a/common/packaging.targets b/common/packaging.targets
index 77f866e20..a5ddd41a5 100644
--- a/common/packaging.targets
+++ b/common/packaging.targets
@@ -7,8 +7,9 @@
@@ -22,16 +23,16 @@
+ Condition="!$([System.String]::Copy('%(Filename)').Contains('deleteme')) and !$([System.String]::Copy('%(Extension)').Contains('xml'))" />
-
+
-
+
diff --git a/common/properties.props b/common/properties.props
index f8b204df8..8316329ed 100644
--- a/common/properties.props
+++ b/common/properties.props
@@ -3,13 +3,14 @@
Internal
- ENABLE_METRICS
+ ENABLE_METRICS
+ $(BuildDefs);ENABLE_MONO
- $(SolutionDir)\script\lib\
- $(SolutionDir)\lib\
- C:\Program Files\Unity\Editor\Data\Managed\
+ $(SolutionDir)script\lib\
+ $(SolutionDir)lib\
+ C:\Program Files\Unity\Editor\Data\
C:\Program Files (x86)\Unity\Editor\Data\Managed\
- \Applications\Unity\Unity.app\Contents\Managed\
+ \Applications\Unity\Unity.app\Contents\
Debug
$(Configuration)
diff --git a/create-octorun-zip.sh b/create-octorun-zip.sh
new file mode 100755
index 000000000..4eb568d33
--- /dev/null
+++ b/create-octorun-zip.sh
@@ -0,0 +1,3 @@
+#!/bin/sh -eu
+DIR=$(pwd)
+submodules/packaging/octorun/run.sh --path $DIR/octorun --out $DIR/src/GitHub.Api/Resources --source $DIR/src/GitHub.Api/Installer
diff --git a/create-unitypackage.sh b/create-unitypackage.sh
new file mode 100755
index 000000000..333f7cb90
--- /dev/null
+++ b/create-unitypackage.sh
@@ -0,0 +1,9 @@
+#!/bin/sh -eu
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+version=$(sed -En 's,.*GitHubForUnityVersion = "(.*)".*,\1,p' common/SolutionInfo.cs)
+commitcount=$(git rev-list --count HEAD)
+commit=$(git log -n1 --pretty=format:%h)
+version="${version}.${commitcount}-${commit}"
+
+$DIR/submodules/packaging/unitypackage/run.sh --path $DIR/unity/PackageProject --out $DIR --file github-for-unity-$version
diff --git a/CREDITS.txt b/credits.txt
similarity index 88%
rename from CREDITS.txt
rename to credits.txt
index e7137d888..b8fb5d7e1 100644
--- a/CREDITS.txt
+++ b/credits.txt
@@ -1,31 +1,5 @@
===============================================================================
-Port of Mono's System.Net.Http (https://github.com/shana/dotnet-httpclient35)
-===============================================================================
-
-Copyright (c) 2001, 2002, 2003 Ximian, Inc and the individuals listed
-on the ChangeLog entries.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-===============================================================================
-Port of Axosoft's NSFW (https://github.com/StanleyGoldman/sfw)
+Port of Axosoft's NSFW (https://github.com/github-for-unity/sfw)
===============================================================================
The MIT License (MIT)
@@ -50,29 +24,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-===============================================================================
-Octokit.NET (https://github.com/editor-tools/octokit.net/tree/net3.5)
-===============================================================================
-
-Copyright (c) 2012 GitHub, Inc.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
===============================================================================
AsyncBridge (https://github.com/OmerMor/AsyncBridge)
===============================================================================
diff --git a/docs/contributing/how-to-build.md b/docs/contributing/how-to-build.md
index ed32b7e5d..13ef8c2fa 100644
--- a/docs/contributing/how-to-build.md
+++ b/docs/contributing/how-to-build.md
@@ -13,7 +13,7 @@ This repository is LFS-enabled. To clone it, you should use a git client that su
### MacOS
-- Mono 4.x required.
+- [Mono 4.x](https://download.mono-project.com/archive/4.8.1/macos-10-universal/) required. You can install it via brew with `brew tap shana/mono && brew install mono@4.8`
- Mono 5.x will not work
- `UnityEngine.dll` and `UnityEditor.dll`.
- If you've installed Unity in the default location of `/Applications/Unity`, the build will be able to reference these DLLs automatically. Otherwise, you'll need to copy these DLLs from `[Unity installation path]/Unity.app/Contents/Managed` into the `lib` directory in order for the build to work
@@ -35,12 +35,25 @@ git submodule deinit script
### Important pre-build steps
-To be able to authenticate in GitHub for Unity, you'll need to:
+The build needs to reference `UnityEngine.dll` and `UnityEditor.dll`. These DLLs are included with Unity. If you've installed Unity in the default location, the build will be able to find them automatically. If not, copy these DLLs from `[your Unity installation path]\Unity\Editor\Data\Managed` into the `lib` directory in order for the build to work.
+
+#### Developer OAuth app
+
+Because GitHub for Unity uses OAuth web application flow to interact with the GitHub API and perform actions on behalf of a user, it needs to be bundled with a Client ID and Secret.
+
+For external contributors, we have bundled a developer OAuth application in the source so that you can complete the sign in flow locally without needing to configure your own application.
+
+These are listed in `src/GitHub.Api/Application/ApplicationInfo.cs`
+
+DO NOT TRUST THIS CLIENT ID AND SECRET! THIS IS ONLY FOR TESTING PURPOSES!!
+
+The limitation with this developer application is that this will not work with GitHub Enterprise. You will see sign-in will fail on the OAuth callback due to the credentials not being present there.
+
+To provide your own Client ID and Client Secret:
- [Register a new developer application](https://github.com/settings/developers) in your profile.
- Copy [common/ApplicationInfo_Local.cs-example](../../common/ApplicationInfo_Local.cs-example) to `common/ApplicationInfo_Local.cs` and fill out the clientId/clientSecret fields for your application.
-The build needs to reference `UnityEngine.dll` and `UnityEditor.dll`. These DLLs are included with Unity. If you've installed Unity in the default location, the build will be able to find them automatically. If not, copy these DLLs from `[your Unity installation path]\Unity\Editor\Data\Managed` into the `lib` directory in order for the build to work.
### Visual Studio
@@ -56,21 +69,14 @@ Once you've built the solution for the first time, you can open `src/UnityExtens
The build also creates a Unity test project called `GitHubExtension` inside a directory called `github-unity-test` next to your local clone. For instance, if the repository is located at `c:\Projects\Unity` the test project will be at `c:\Projects\github-unity-test\GitHubExtension`. You can use this project to test binary builds of the extension in a clean environment (all needed DLLs will be copied to it every time you build).
-Note: some files might be locked by Unity if have one of the build output projects open when you compile from VS or the command line. This is expected and shouldn't cause issues with your builds.
+Note: some files might be locked by Unity if have one of the build output projects open when you compile from VS or the command line. This is expected and shouldn't cause issues with your builds.
## Solution organization
The `GitHub.Unity.sln` solution includes several projects:
-- dotnet-httpclient35 and octokit: external dependencies for threading and github api support, respectively. These are the submodules.
- packaging: empty projects with build rules that copy DLLs to various locations for testing
- Tests: unit and integration test projects
- GitHub.Logging: A logging helper library
- GitHub.Api: The core of the extension. This project is C#6 and includes async/await threading and other features that Unity cannot currently compile.
- GitHub.Unity: Unity-specific code. This project is compilable by Unity
-
-## Other solutions
-
-Octokit is normally included as a prebuilt DLL (in the `lib` directory), but when updating Octokit or debugging issues with the 3.5 backport, it can sometimes be handy to reference the project directly. The `GitHub.Unity.OctokitDebugging.sln` solution is a helper solution that includes Octokit and dotnet-httpclient35 as referenced projects instead of prebuilt dlls.
-
-This solution requires a clone of [https://github.com/github-for-unity/octokit.net](https://github.com/github-for-unity/octokit.net) and [https://github.com/github-for-unity/dotnet-httpclient35](https://github.com/github-for-unity/dotnet-httpclient35) to exist next to your GitHub for Unity checkout.
diff --git a/docs/readme.md b/docs/readme.md
index a95adb7e9..d73e8e840 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -28,7 +28,10 @@ Details about how the team is organizing and shipping GitHub for Unity:
- **[Roadmap](process/roadmap.md)** - how we plan for the future
- **[Release](process/release-process.md)** - how we review contributions
-## Technical
+## Using
-These documents contain more details about the internals of GitHub for Unity
-and how things work:
+[Quick Guide](using/quick-guide.md)
+
+These documents contain more details on how to use the GitHub for Unity plugin:
+- **[Installing and Updating the GitHub for Unity package](using/how-to-install-and-update.md)**
+- **[Getting Started with the GitHub for Unity package](using/getting-started.md)**
diff --git a/docs/using/authenticating-to-github.md b/docs/using/authenticating-to-github.md
new file mode 100644
index 000000000..203a3473d
--- /dev/null
+++ b/docs/using/authenticating-to-github.md
@@ -0,0 +1,37 @@
+# Authenticating to GitHub
+
+## How to sign in to GitHub
+
+1. Open the **GitHub** window by going to the top level **Window** menu and selecting **GitHub**, as shown below.
+
+
+
+1. Click the **Sign in** button at the top right of the window.
+
+
+
+1. In the **Authenticate** dialog, enter your username or email and password
+
+
+
+ If your account requires Two Factor Authentication, you will be prompted for your auth code.
+
+
+
+You will need to create a GitHub account before you can sign in, if you don't have one already.
+
+- For more information on creating a GitHub account, see "[Signing up for a new GitHub account](https://help.github.com/articles/signing-up-for-a-new-github-account/)".
+
+### Personal access tokens
+
+If the sign in operation above fails, you can manually create a personal access token and use it as your password.
+
+The scopes for the personal access token are: `user`, `repo`.
+- *user* scope: Grants access to the user profile data. We currently use this to display your avatar and check whether your plans lets you publish private repositories.
+- *repo* scope: Grants read/write access to code, commit statuses, invitations, collaborators, adding team memberships, and deployment statuses for public and private repositories and organizations. This is needed for all git network operations (push, pull, fetch), and for getting information about the repository you're currently working on.
+
+***Note:*** *Some older versions of the plugin ask for `gist` and `write:public_key`.*
+
+For more information on creating personal access tokens, see "[Creating a personal access token for the command line](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line).
+
+For more information on authenticating with SAML single sign-on, see "[About authentication with SAML single sign-on](https://help.github.com/articles/about-authentication-with-saml-single-sign-on)."
diff --git a/docs/using/getting-started.md b/docs/using/getting-started.md
new file mode 100644
index 000000000..4c5892a96
--- /dev/null
+++ b/docs/using/getting-started.md
@@ -0,0 +1,28 @@
+# Getting Started with GitHub for Unity
+There are often three states that you might be in when you are setting up your GitHub for Unity package with your Unity project:
+- [Setting up a brand new repository](#setting-up-a-new-repository): You do not currently have your Unity project in a repository and you want to publish it to one.
+- [Connecting to an existing respository](#connecting-to-an-existing-repository): Your Unity project is already in a repository, but you do not have the GitHub for Unity package installed within it yet.
+- [Opening a Unity project that already has the GitHub for Unity package](#connecting-to-an-existing-repository-that-already-has-the-github-for-unity-package): Your Unity project is already in a repository and it already has the GitHub for Unity package and you are trying to setup a new machine.
+
+# Setting up a New Repository
+If you have a Unity project (new or existing) that is not yet connected to any remote repository, you can use the GitHub for Unity package to quickly intialize the repository and publish to a repository.
+
+1. If your Unity project doesn't yet have the GitHub plugin installed, follow [these](https://github.com/github-for-unity/Unity/blob/master/docs/using/how-to-install-and-update.md) instructions for installing it.
+2. Click on the `Initialize a git repository for this project` button
+
+And you should see the GitHub spinner:
+
+3. The GitHub tab should reload with the following buttons:
+ - Publish: Publish this repository to GitHub (Creating a new GitHub repository)
+ - Account: Login to your GitHub account | Logout from your GitHub account or visit your profile on GitHub.com
+ - Changes: Local changes that are not yet committed, with a dialog to commit those changes
+ - History: A history of commits with title, time stamp, and commit author
+ - Branches: A list of local and remote branches with the ability to create new branches, switch branches, or checkout remote branches
+ - Settings: your git configuration (pulled from your local git credentials if they have been previously set), your repository configuration (you can manually put the URL to any remote repository here instead of using the Publish button to publish to GitHub), a list of locked files, your git installation details, and general settings to help us better help you if you get stuck
+
+# Cloning an Existing Repository
+GitHub for Unity does not have the functionality to clone projects (yet!).
+1. Clone the repository (either through command line or with GitHub Desktop https://desktop.github.com/).
+2. Open the project in Unity.
+3. Install GitHub for Unity if it is not already installed.
+4. The GitHub plugin should load with all functionality enabled.
\ No newline at end of file
diff --git a/docs/using/how-to-install-and-update.md b/docs/using/how-to-install-and-update.md
new file mode 100644
index 000000000..fb9dd93d8
--- /dev/null
+++ b/docs/using/how-to-install-and-update.md
@@ -0,0 +1,51 @@
+# Installation and Updating Guide
+[Installing from a Downloaded Package File](#installing-from-a-package-file)
+[Installing from the Unity Asset Store](#installing-from-the-asset-store)
+[Updating a Previously Installed Version](#updating-the-github-for-unity-package)
+
+_Note: If your Unity project already has the GitHub for Unity plugin installed, see our [Getting Started](https://github.com/github-for-unity/Unity/blob/master/docs/using/getting-started.md) docs before continuing_
+
+
+# Installing from a Package File
+You can download the latest release from the repository at [https://github.com/github-for-unity/Unity/releases](https://github.com/github-for-unity/Unity/releases) or from the website at [https://unity.github.com/](https://unity.github.com/).
+
+Once you've downloaded the package file, you can quickly install it within Unity.
+1. Open a new Unity Project
+
+2. Go to `Assets -> Import Package -> Custom Package`
+
+3. Find and open the GitHub for Unity Asset Package
+
+4. Click `Import` when the dialog pops up
+
+5. After the package finishes importing, go to `Window -> GitHub` to open the GitHub window
+
+6. You should see the GitHub window in your Unity project, with a button to initialize your repository
+
+
+# Installing from the Asset Store
+1. Open a new Unity Project
+
+2. Go to `Window -> Asset Store`
+
+3. Search for "GitHub" in the search bar
+
+4. Click on the GitHub for Unity Package
+
+5. Click `Download` then `Install`
+
+
+6. Click `Import` when the dialog pops up
+
+7. After the package finishes importing, go to `Window -> GitHub` to open the GitHub window
+
+8. You should see the GitHub window in your Unity project, with a button to initialize your repository
+
+
+# Updating the GitHub for Unity Package
+
+- If you are running Unity and wish to update GitHub for Unity (unless explicitly stated), be sure that the files in `x64` and `x86` are not selected.
+
+ 
+
+- Otherwise, it's best to stop Unity and delete GitHub for Unity from your project. Startup Unity and run the package installer like normal. Allowing it to restore everything.
diff --git a/docs/using/images/branches-initial-view.png b/docs/using/images/branches-initial-view.png
new file mode 100644
index 000000000..811bcf724
--- /dev/null
+++ b/docs/using/images/branches-initial-view.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:558821e67ea26955434da0485785e9240b6c15c44a3790b642fdcd37382e56a2
+size 59798
diff --git a/docs/using/images/changes-view.png b/docs/using/images/changes-view.png
new file mode 100644
index 000000000..23000f8ef
--- /dev/null
+++ b/docs/using/images/changes-view.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2ac4637a222a06e6ab57f23082b8e8b2f5cb9e35b9b99776c112ddcebb923dec
+size 61918
diff --git a/docs/using/images/confirm-pull-changes.png b/docs/using/images/confirm-pull-changes.png
new file mode 100644
index 000000000..722bbd639
--- /dev/null
+++ b/docs/using/images/confirm-pull-changes.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4b0e1aa1d8d86cb0264c9ca569a5c8e71b0aba73a2eb6611c06ec47670037d7d
+size 21478
diff --git a/docs/using/images/confirm-push-changes.png b/docs/using/images/confirm-push-changes.png
new file mode 100644
index 000000000..ff1ca1cd0
--- /dev/null
+++ b/docs/using/images/confirm-push-changes.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9e2e136027b03609f344bfde0c777d8e915544a5e9920f1268e4cc3abc5b1481
+size 18676
diff --git a/docs/using/images/confirm-revert.png b/docs/using/images/confirm-revert.png
new file mode 100644
index 000000000..cbf5d1566
--- /dev/null
+++ b/docs/using/images/confirm-revert.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ba990ce12403bfb53fb387ca3f3e2c447b21814ed6cd7e6f88d968463a5253a8
+size 21973
diff --git a/docs/using/images/create-new-branch-view.png b/docs/using/images/create-new-branch-view.png
new file mode 100644
index 000000000..34c69d1ea
--- /dev/null
+++ b/docs/using/images/create-new-branch-view.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f0239a51f611501dc63c828de206f35d412493871f65e712983666e67dbfc231
+size 62046
diff --git a/docs/using/images/delete-dialog.png b/docs/using/images/delete-dialog.png
new file mode 100644
index 000000000..574489f7c
--- /dev/null
+++ b/docs/using/images/delete-dialog.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:99d7cd8ef4957614cc844a08a632159157295414dc4675c1394f3209e30ffa58
+size 76315
diff --git a/docs/using/images/github-authenticate.png b/docs/using/images/github-authenticate.png
new file mode 100644
index 000000000..188121d97
--- /dev/null
+++ b/docs/using/images/github-authenticate.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:31ca0f7c4fc5c737a3db2da6eeb3deaa862d27e2cf3d1aa78fa14555b7cac750
+size 5927
diff --git a/docs/using/images/github-menu-item.png b/docs/using/images/github-menu-item.png
new file mode 100644
index 000000000..44f3f9458
--- /dev/null
+++ b/docs/using/images/github-menu-item.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c423766a70230c245f4bcdb1e33d9e76f267bf789876ecf9050016f9c8671735
+size 19651
diff --git a/docs/using/images/github-sign-in-button.png b/docs/using/images/github-sign-in-button.png
new file mode 100644
index 000000000..cb6132c24
--- /dev/null
+++ b/docs/using/images/github-sign-in-button.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:79bea09964bcf1964d4d1e1fb9712fe9fc67dc8ffe11f936534c9f2be63fd777
+size 9318
diff --git a/docs/using/images/github-two-factor.png b/docs/using/images/github-two-factor.png
new file mode 100644
index 000000000..7a3286616
--- /dev/null
+++ b/docs/using/images/github-two-factor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aba2174f780ea52a7cf7bbfd5144527bb7b29c3ce6a3200b6b6f8c69b2d6fb48
+size 9665
diff --git a/docs/using/images/locked-scene.png b/docs/using/images/locked-scene.png
new file mode 100644
index 000000000..7a839141d
--- /dev/null
+++ b/docs/using/images/locked-scene.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bbc246b18cbd173c19f392fe7ab85bffb8a97a7616ef99e7e37496e307924e49
+size 51983
diff --git a/docs/using/images/locks-view-right-click.png b/docs/using/images/locks-view-right-click.png
new file mode 100644
index 000000000..0ee14312c
--- /dev/null
+++ b/docs/using/images/locks-view-right-click.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:590dae09b3c029e48c3703d6c85a6774b062ebea46f11c2c062d97fedcb436df
+size 27251
diff --git a/docs/using/images/locks-view.png b/docs/using/images/locks-view.png
new file mode 100644
index 000000000..e8790d74e
--- /dev/null
+++ b/docs/using/images/locks-view.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:118f2b2e1bb82b598107eaa73db9a077374ff4f9601e47677eca7296353b1bc3
+size 23274
diff --git a/docs/using/images/name-branch.png b/docs/using/images/name-branch.png
new file mode 100644
index 000000000..9926c06db
--- /dev/null
+++ b/docs/using/images/name-branch.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:08232fa5cfd41a41c8091197ac27f433fdfa865c4996388dd0c731ac75176442
+size 59599
diff --git a/docs/using/images/new-branch-created.png b/docs/using/images/new-branch-created.png
new file mode 100644
index 000000000..2a37a9aeb
--- /dev/null
+++ b/docs/using/images/new-branch-created.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8543da247ee23364c15abec87e4e3de3e62b85a470e7d3d090ade18fb321072c
+size 64045
diff --git a/docs/using/images/post-commit-view.png b/docs/using/images/post-commit-view.png
new file mode 100644
index 000000000..81fc7d135
--- /dev/null
+++ b/docs/using/images/post-commit-view.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:91e26123236bdd3e692ca7d30ff0d6477311fe739d8008ba19fd543ee3506176
+size 21345
diff --git a/docs/using/images/post-push-history-view.png b/docs/using/images/post-push-history-view.png
new file mode 100644
index 000000000..0d2ed767a
--- /dev/null
+++ b/docs/using/images/post-push-history-view.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c0c2d5ccaddc88d3268e0d57f02229525240ecc71bded951e23171456560268b
+size 22485
diff --git a/docs/using/images/pull-view.png b/docs/using/images/pull-view.png
new file mode 100644
index 000000000..842ff9660
--- /dev/null
+++ b/docs/using/images/pull-view.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:feb83a46ebdd34ff46db3a38e8829f5adec5b10824bf69a8c56c9ba6fbd603ff
+size 25054
diff --git a/docs/using/images/push-view.png b/docs/using/images/push-view.png
new file mode 100644
index 000000000..42bef651a
--- /dev/null
+++ b/docs/using/images/push-view.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ab87bae0916af3681b7528891a359c77597fa3ea57f13eb6924f1cbcf2f2ab7d
+size 14581
diff --git a/docs/using/images/release-lock.png b/docs/using/images/release-lock.png
new file mode 100644
index 000000000..545b81c8a
--- /dev/null
+++ b/docs/using/images/release-lock.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0f6e05a714d270016d1f8ad4e018a895ed2b023fe59bd34a28df8d315c065649
+size 140913
diff --git a/docs/using/images/request-lock.png b/docs/using/images/request-lock.png
new file mode 100644
index 000000000..b877e1d8f
--- /dev/null
+++ b/docs/using/images/request-lock.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3f25877141bed2dcbdc4324edcbd802b6f216771e80de704f20dc99a3fdc718b
+size 136078
diff --git a/docs/using/images/revert-commit.png b/docs/using/images/revert-commit.png
new file mode 100644
index 000000000..daad75c8c
--- /dev/null
+++ b/docs/using/images/revert-commit.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b7711f428f3e04ea056c47b5d43109f3941761f4c286360d3616e0598d2d7b75
+size 26119
diff --git a/docs/using/images/revert.png b/docs/using/images/revert.png
new file mode 100644
index 000000000..326071a28
--- /dev/null
+++ b/docs/using/images/revert.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dea7b36eec8e2d99e1906b6cbc9cd22f52d2db09cc1a0cf34563585720b6649d
+size 25141
diff --git a/docs/using/images/success-pull-changes.png b/docs/using/images/success-pull-changes.png
new file mode 100644
index 000000000..a246da405
--- /dev/null
+++ b/docs/using/images/success-pull-changes.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e0407ab83ca311f319ff7feac0dac84b862935e2b50353ff82c067bdadf80e16
+size 16888
diff --git a/docs/using/images/success-push-changes.png b/docs/using/images/success-push-changes.png
new file mode 100644
index 000000000..8fba1f506
--- /dev/null
+++ b/docs/using/images/success-push-changes.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cfd290cb1e52b0acb5503642a3aaef164bf343735729325484e25d8b99293d7e
+size 13262
diff --git a/docs/using/images/switch-confirmation.png b/docs/using/images/switch-confirmation.png
new file mode 100644
index 000000000..92d623d6c
--- /dev/null
+++ b/docs/using/images/switch-confirmation.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4ed80f5cf2fab5fd256dd7cd093ad505c966edb95bdc5a15c3f1ace042bec7f6
+size 73947
diff --git a/docs/using/images/switch-or-delete.png b/docs/using/images/switch-or-delete.png
new file mode 100644
index 000000000..deab6badf
--- /dev/null
+++ b/docs/using/images/switch-or-delete.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8c0d171b2e8f7e212f2446b416235c6232c57c9b4927cfa41891bffa1940ae9d
+size 73828
diff --git a/docs/using/images/switched-branches.png b/docs/using/images/switched-branches.png
new file mode 100644
index 000000000..c185d25c5
--- /dev/null
+++ b/docs/using/images/switched-branches.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:542806a8dcc012cdfadadaf9cdab3cf4871bbbbee2c8d73834946df4ed249aec
+size 64281
diff --git a/docs/using/locking-files.md b/docs/using/locking-files.md
new file mode 100644
index 000000000..f90c8e7a3
--- /dev/null
+++ b/docs/using/locking-files.md
@@ -0,0 +1,30 @@
+# Locking files
+
+## Request locks
+
+From the Project tab, right-click on a file to open the context menu and select `Request Lock`.
+
+
+An additional way to lock a file is by selecting it and going to `Assets` -> `Request Lock`.
+
+## View locks
+
+After requesting a lock, a lock icon appears in the bottom right-hand corner of the file.
+
+
+A list of all locked files will appear in the **Locks** view in the GitHub tab.
+
+
+## Release locks
+
+There are three ways to release locks:
+
+1. From the Project tab, right-click on the locked file to open the context menu and select the option to `Release Lock`.
+
+
+2. From the GitHub tab under the **Locks** view, right-click to open the context menu and select to `Release Lock`.
+
+
+3. Select the file to unlock and go to select the menu option `Assets` -> `Release Lock`.
+
+Note: There are also two options for how to release a lock on a file. Always choose the `Release Lock` option first. The `Release Lock (forced)` option can be used to remove someone else's lock.
diff --git a/docs/using/managing-branches.md b/docs/using/managing-branches.md
new file mode 100644
index 000000000..13fa9c1bd
--- /dev/null
+++ b/docs/using/managing-branches.md
@@ -0,0 +1,38 @@
+# Managing branches
+
+Initial **Branches** view
+
+
+
+## Create branch
+
+1. From the **Branches** view, click on `master` under local branches to enable the `New Branch` button and be able to create a new branch from master.
+2. Click on `New Branch`.
+
+
+3. Enter a name for the branch and click `Create`.
+
+
+4. The new branch will be created from master.
+
+
+## Checkout branch
+
+1. Right-click on a local branch and select `Switch` or double-click on the branch to switch to it.
+
+
+2. A dialog will appear asking `Switch branch to 'branch name'?`. Select `Switch`.
+
+
+The branch will be checked out.
+
+
+## Delete branches
+
+1. Click on the branch name to be deleted and the `Delete` button becomes enabled.
+2. Right-click on a local branch and select `Delete` or click the `Delete` button above the Local branches list.
+
+
+3. A dialog appears asking `Are you sure you want to delete the branch: 'branch name'?`. Select `Delete`.
+
+The branch will be deleted.
diff --git a/docs/using/quick-guide.md b/docs/using/quick-guide.md
new file mode 100644
index 000000000..2011ee40f
--- /dev/null
+++ b/docs/using/quick-guide.md
@@ -0,0 +1,164 @@
+# Quick Guide
+
+## More resources
+
+These documents contain more details on how to use the GitHub for Unity plugin:
+- **[Installing and Updating the GitHub for Unity package](https://github.com/github-for-unity/Unity/blob/master/docs/using/how-to-install-and-update.md)**
+- **[Getting Started with the GitHub for Unity package](https://github.com/github-for-unity/Unity/blob/master/docs/using/getting-started.md)**
+- **[Authenticating to GitHub](https://github.com/github-for-unity/Unity/blob/master/docs/using/authenticating-to-github.md)**
+- **[Managing Branches](https://github.com/github-for-unity/Unity/blob/master/docs/using/managing-branches.md)**
+- **[Locking Files](https://github.com/github-for-unity/Unity/blob/master/docs/using/locking-files.md)**
+- **[Working with Changes](https://github.com/github-for-unity/Unity/blob/master/docs/using/working-with-changes.md)**
+- **[Using the Api](https://github.com/github-for-unity/Unity/blob/master/docs/using/using-the-api.md)**
+
+## Table of Contents
+
+[Installing GitHub for Unity](#installing-github-for-unity)
+
+- [Requirements](#requirements)
+ - [Git on macOS](#git-on-macos)
+ - [Git on Windows](#git-on-windows)
+- [Installation](#installation)
+- [Log files](#log-files)
+ - [Windows](#windows)
+ - [macOS](#macos)
+
+[Quick Guide to GitHub for Unity](#quick-guide-to-github-for-unity)
+
+- [Opening the GitHub window](#opening-the-github-window)
+- [Initialize Repository](#initialize-repository)
+- [Authentication](#authentication)
+- [Publish a new repository](#publish-a-new-repository)
+- [Commiting your work - Changes tab](#commiting-your-work---changes-tab)
+- [Pushing/pulling your work - History tab](#pushingpulling-your-work---history-tab)
+- [Branches tab](#branches-tab)
+- [Settings tab](#settings-tab)
+
+## Installing GitHub for Unity
+
+### Requirements
+
+- Unity 5.4 or higher
+ - There's currently a blocker issue opened for 5.3 support, so we know it doesn't run there. Personal edition is fine.
+- Git and Git LFS 2.x
+
+#### Git on macOS
+
+The current release has limited macOS support. macOS users will need to install the latest [Git](https://git-scm.com/downloads) and [Git LFS](https://git-lfs.github.com/) manually, and make sure these are on the path. You can configure the Git location in the `Settings` tab on the GitHub window.
+
+The easiest way of installing git and git lfs is to install [Homebrew](https://brew.sh/) and then do `brew install git git-lfs`.
+
+Make sure a Git user and email address are set in the `~/.gitconfig` file before you initialize a repository for the first time. You can set these values by opening your `~/.gitconfig` file and adding the following section, if it doesn't exist yet:
+
+```
+[user]
+ name = Your Name
+ email = Your Email
+```
+
+#### Git on Windows
+
+The GitHub for Unity extension ships with a bundle of Git and Git LFS, to ensure that you have the correct version. These will be installed into `%LOCALAPPDATA%\GitHubUnity` when the extension runs for the first time.
+
+Make sure a Git user and email address are set in the `%HOME%\.gitconfig` file before you initialize a repository for the first time. You can set these values by opening your `%HOME%\.gitconfig` file and adding the following section, if it doesn't exist yet:
+
+```
+[user]
+ name = Your Name
+ email = Your Email
+```
+
+Once the extension is installed, you can open a command line with the same Git and Git LFS version that the extension uses by going to `Window` -> `GitHub Command Line` in Unity.
+
+### Installation
+
+This extensions needs to be installed (and updated) for each Unity project that you want to version control.
+First step is to download the latest package from [the releases page](https://github.com/github-for-unity/Unity/releases); it will be saved as a file with the extension `.unitypackage`.
+To install it, open Unity, then open the project you want to version control, and then double click on the downloaded package.
+Alternatively, import the package by clicking `Assets`, `Import Package`, `Custom Package`, then select the downloaded package.
+
+#### Log files
+
+##### macOS
+
+The extension log file can be found at `~/Library/Logs/GitHubUnity/github-unity.log`
+
+##### Windows
+
+The extension log file can be found at `%LOCALAPPDATA%\GitHubUnity\github-unity.log`
+
+## I have a problem with GitHub for Unity
+
+First, please search the [open issues](https://github.com/github-for-unity/Unity/issues?q=is%3Aopen)
+and [closed issues](https://github.com/github-for-unity/Unity/issues?q=is%3Aclosed)
+to see if your issue hasn't already been reported (it may also be fixed).
+
+If you can't find an issue that matches what you're seeing, open a [new issue](https://github.com/github-for-unity/Unity/issues/new)
+and fill out the template to provide us with enough information to investigate
+further.
+
+## Quick Guide to GitHub for Unity
+
+### Opening the GitHub window
+
+You can access the GitHub window by going to `Windows` -> `GitHub`. The window opens by default next to the Inspector window.
+
+### Initialize Repository
+
+
+
+If the current Unity project is not in a Git repository, the GitHub for Unity extension will offer to initialize the repository for you. This will:
+
+- Initialize a git repository at the Unity project root via `git init`
+- Initialize git-lfs via `git lfs install`
+- Set up a `.gitignore` file at the Unity project root.
+- Set up a `.gitattributes` file at the Unity project root with a large list of known binary filetypes (images, audio, etc) that should be tracked by LFS
+- Configure the project to serialize meta files as text
+- Create an initial commit with the `.gitignore` and `.gitattributes` file.
+
+### Authentication
+
+To set up credentials in Git so you can push and pull, you can sign in to GitHub by going to `Window` -> `GitHub` -> `Account` -> `Sign in`. You only have to sign in successfully once, your credentials will remain on the system for all Git operations in Unity and outside of it. If you've already signed in once but the Account dropdown still says `Sign in`, ignore it, it's a bug.
+
+
+
+For more information on Authentication: - **[Authenticating to GitHub](https://github.com/github-for-unity/Unity/blob/master/docs/using/authenticating-to-github.md)**
+
+### Publish a new repository
+
+1. Go to [github.com](https://github.com) and create a new empty repository - do not add a license, readme or other files during the creation process.
+2. Copy the **https** URL shown in the creation page
+3. In Unity, go to `Windows` -> `GitHub` -> `Settings` and paste the url into the `Remote` textbox.
+4. Click `Save repository`.
+5. Go to the `History` tab and click `Push`.
+
+### Commiting your work - Changes tab
+
+You can see which files have been changed and commit them through the `Changes` tab. `.meta` files will show up in relation to their files on the tree, so you can select a file for comitting and automatically have their `.meta`
+
+
+
+For more information on working with changes: - **[Working with Changes](https://github.com/github-for-unity/Unity/blob/master/docs/using/working-with-changes.md#commit-changes)**
+
+### Pushing/pulling your work - History tab
+
+The history tab includes a `Push` button to push your work to the server. Make sure you have a remote url configured in the `Settings` tab so that you can push and pull your work.
+
+To receive updates from the server by clicking on the `Pull` button. You cannot pull if you have local changes, so commit your changes before pulling.
+
+
+
+
+For more information on working with changes: - **[Working with Changes](https://github.com/github-for-unity/Unity/blob/master/docs/using/working-with-changes.md#pulling-changes)**
+
+### Branches tab
+
+
+
+### Settings tab
+
+You can configure your user data in the `Settings` tab, along with the path to the Git installation.
+
+Locked files will appear in a list in the Settings tab. You can see who has locked a file and release file locks after you've pushed your work.
+
+
diff --git a/docs/using/using-the-api.md b/docs/using/using-the-api.md
new file mode 100644
index 000000000..ab75cab55
--- /dev/null
+++ b/docs/using/using-the-api.md
@@ -0,0 +1,75 @@
+# Using the API
+
+GitHub for Unity provides access to a git client to help users create their own tools to assist in their workflow.
+
+Users can separate the user interface from the API by removing `GitHub.Unity.dll`. All other libraries are required by the API.
+
+## Creating an instance of `GitClient`
+```cs
+var defaultEnvironment = new DefaultEnvironment();
+defaultEnvironment.Initialize(null, NPath.Default, NPath.Default, NPath.Default, Application.dataPath.ToNPath());
+
+var processEnvironment = new ProcessEnvironment(defaultEnvironment);
+var processManager = new ProcessManager(defaultEnvironment, processEnvironment, TaskManager.Instance.Token);
+
+var gitClient = new GitClient(defaultEnvironment, processManager, TaskManager.Instance.Token);
+```
+
+## Full Example
+This example creates a window that has a single button which commits all changes.
+```cs
+using System;
+using System.Globalization;
+using GitHub.Unity;
+using UnityEditor;
+using UnityEngine;
+
+public class CustomGitEditor : EditorWindow
+{
+ [MenuItem("Window/Custom Git")]
+ public static void ShowWindow()
+ {
+ EditorWindow.GetWindow(typeof(CustomGitEditor));
+ }
+
+ [NonSerialized] private GitClient gitClient;
+
+ public void OnEnable()
+ {
+ InitGitClient();
+ }
+
+ private void InitGitClient()
+ {
+ if (gitClient != null) return;
+
+ Debug.Log("Init GitClient");
+
+ var defaultEnvironment = new DefaultEnvironment();
+ defaultEnvironment.Initialize(null, NPath.Default, NPath.Default,
+ NPath.Default, Application.dataPath.ToNPath());
+
+ var processEnvironment = new ProcessEnvironment(defaultEnvironment);
+ var processManager = new ProcessManager(defaultEnvironment, processEnvironment, TaskManager.Instance.Token);
+
+ gitClient = new GitClient(defaultEnvironment, processManager, TaskManager.Instance.Token);
+ }
+
+ void OnGUI()
+ {
+ GUILayout.Label("Custom Git Window", EditorStyles.boldLabel);
+
+ if (GUILayout.Button("Commit Stuff"))
+ {
+ var message = DateTime.Now.ToString(CultureInfo.InvariantCulture);
+ var body = string.Empty;
+
+ gitClient.AddAll()
+ .Then(gitClient.Commit(message, body))
+ .Start();
+ }
+ }
+}
+```
+
+
diff --git a/docs/using/working-with-changes.md b/docs/using/working-with-changes.md
new file mode 100644
index 000000000..228e4bd3f
--- /dev/null
+++ b/docs/using/working-with-changes.md
@@ -0,0 +1,53 @@
+# Working with changes
+
+## Commit changes
+
+All changes made to a repository will show up under the **Changes** view.
+
+1. Select the changes to be committed. Can choose the All/None options, or select directories or files individually.
+2. Enter a Commit summary which describes the purpose of the commit. An optional Commit description can also be entered.
+3. Click the button `Commit to [branch name]`.
+
+
+The commit will not be shown under the **History** view. On the top bar the button `Push (1)` indicates that there is 1 commit to push.
+
+
+
+## Push changes to GitHub
+
+1. Click `Push` once ready to push a commit to GitHub.
+
+
+2. A dialog will appear asking `Would you like to push changes to remote 'branch name'?` Select `Push`.
+
+
+3. Another dialog will appear when the push to GitHub is complete saying `Branch pushed`. Select `ok`.
+
+
+## Revert changes
+
+1. From the **History** view, right-click on a commit in the commit list. A `Revert` option will appear.
+2. Click `Revert`.
+
+
+3. A dialog will appear asking `Are you sure you want to revert the following commit: "commit message"?`. Select `Revert`.
+
+
+4. A new commit appears titled `Revert "commit summary"` and the view indicates that there is 1 commit to push.
+
+
+5. Follow the steps to push the reverted commit to GitHub.
+
+## Pulling changes
+
+1. Click the `Fetch` button to get all the latest branches and tags for the repository. The `Pull` button will then show the number of commits to pull from GitHub.
+2. Click `Pull`.
+
+
+3. A dialog will appear asking `Would you like to pull changes from remote 'branch name'?`. Select `Pull`.
+
+
+4. Another dialog appears saying `Local branch is up to date with 'branch name'`. Select `ok`.
+
+
+
diff --git a/EULA.txt b/eula.txt
similarity index 100%
rename from EULA.txt
rename to eula.txt
diff --git a/generate-package.sh b/generate-package.sh
new file mode 100755
index 000000000..e9ef73a4a
--- /dev/null
+++ b/generate-package.sh
@@ -0,0 +1,91 @@
+#!/bin/sh -eu
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+URL="http://ghfvs-installer.github.com"
+GIT=0
+LFS=0
+GHU=0
+FILE=""
+VERSION=""
+RN=""
+MSG=""
+OS=""
+
+while [[ $# -gt 0 ]]
+do
+key="$1"
+
+case $key in
+ -h)
+ URL="$2"
+ shift
+ shift
+ ;;
+ -p)
+ FILE="--path $2"
+ shift
+ shift
+ ;;
+ -v)
+ VERSION="--version $2"
+ shift
+ shift
+ ;;
+ -r)
+ RN="--rn $2"
+ shift
+ shift
+ ;;
+ -m)
+ MSG="--msg $2"
+ shift
+ shift
+ ;;
+ -git)
+ GIT=1
+ shift # past value
+ ;;
+ -lfs)
+ LFS=1
+ shift # past value
+ ;;
+ -ghu)
+ GHU=1
+ shift # past value
+ ;;
+ -windows)
+ OS="windows"
+ shift # past value
+ ;;
+ -mac)
+ OS="mac"
+ shift # past value
+ ;;
+ -linux)
+ OS="linux"
+ shift # past value
+ ;;
+esac
+done
+
+if [ x"$GIT" = "x0" -a x"$LFS" = "x0" -a x"$GHU" = "x0" ]; then
+ echo "Usage: generate-package.sh [-git|-lfs|-ghu] [-windows|-mac|-linux only if -git or -lfs] [-v version] [-p path to file] [-h host url (optional)] [-r release notes file (optional)] [-m message file (optional)]"
+ exit 1
+fi
+
+if [ x"$GHU" = x"1" ]; then
+ URL="--url $URL/unity/releases"
+else
+ URL="--url $URL/unity/git/$OS"
+fi
+
+EXEC=
+if [ ! -e "/c/" ]; then
+ EXEC=mono
+fi
+
+if [ ! -e "build/CommandLine/CommandLine.exe" ]; then
+ >&2 xbuild /target:CommandLine "$DIR/GitHub.Unity.sln" /verbosity:minimal
+fi
+
+$EXEC build/CommandLine/CommandLine.exe --gen-package $VERSION $FILE $URL $RN $MSG
diff --git a/lib/.gitignore b/lib/.gitignore
index 1ea843a1c..9432a8bee 100644
--- a/lib/.gitignore
+++ b/lib/.gitignore
@@ -1 +1,2 @@
-Unity*
\ No newline at end of file
+Managed
+UnityExtensions
\ No newline at end of file
diff --git a/lib/ICSharpCode.NRefactory.dll b/lib/ICSharpCode.NRefactory.dll
deleted file mode 100644
index f11688c6c..000000000
--- a/lib/ICSharpCode.NRefactory.dll
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:2e28e35172498877c7e4a33253b0c5df172bce5655b2ab933a922d6777ae5e6d
-size 528384
diff --git a/lib/ICSharpCode.SharpZipLib.dll b/lib/ICSharpCode.SharpZipLib.dll
deleted file mode 100644
index 108abfd2a..000000000
--- a/lib/ICSharpCode.SharpZipLib.dll
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:fbace48694fbfff69ff99ff7aefbde67965f7723092df9c13ff537d2a319f410
-size 192000
diff --git a/src/tests/IntegrationTests/PlatformResources/linux/.placeholder b/lib/Managed/.gitignore
similarity index 100%
rename from src/tests/IntegrationTests/PlatformResources/linux/.placeholder
rename to lib/Managed/.gitignore
diff --git a/lib/Mono.Security.dll b/lib/Mono.Security.dll
deleted file mode 100644
index a7a97c141..000000000
--- a/lib/Mono.Security.dll
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:71ba4944619044e35abf7970e23e9c6b87acd2700dcae8d6655f39c53bcf40e1
-size 294912
diff --git a/lib/UnityExtensions/Unity/TestRunner/Editor/.gitignore b/lib/UnityExtensions/Unity/TestRunner/Editor/.gitignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/lib/pdb2mdb.exe b/lib/pdb2mdb.exe
new file mode 100644
index 000000000..72547bf3c
--- /dev/null
+++ b/lib/pdb2mdb.exe
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a72425f98bdb5923704946c4010485d471ce9e35611264f394637fb185424619
+size 369664
diff --git a/nuget.config b/nuget.config
index 1a5bd9cf3..bad252e01 100644
--- a/nuget.config
+++ b/nuget.config
@@ -1,7 +1,7 @@
-
+
diff --git a/octorun/bin/octorun-meta b/octorun/bin/octorun-meta
new file mode 100644
index 000000000..1d2d3a7c9
--- /dev/null
+++ b/octorun/bin/octorun-meta
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+require('../src/bin/app-meta.js');
diff --git a/octorun/bin/octorun-token b/octorun/bin/octorun-token
new file mode 100644
index 000000000..3ea7b1500
--- /dev/null
+++ b/octorun/bin/octorun-token
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+require('../src/bin/app-token.js');
diff --git a/octorun/src/api.js b/octorun/src/api.js
index fcaf96c50..f7a672e39 100644
--- a/octorun/src/api.js
+++ b/octorun/src/api.js
@@ -1,17 +1,17 @@
var config = require("./configuration");
var octokitWrapper = require("./octokit");
-function ApiWrapper() {
- this.octokit = octokitWrapper.createOctokit();
-
- if (!config.user || !config.token) {
- throw "user and/or token missing";
- }
-
+function ApiWrapper(host) {
if (!config.appName) {
throw "appName missing";
}
+ if (!config.token) {
+ throw "token missing";
+ }
+
+ this.octokit = octokitWrapper.createOctokit(config.appName, host);
+
this.octokit.authenticate({
type: "oauth",
token: config.token
diff --git a/octorun/src/authentication.js b/octorun/src/authentication.js
index 43a2de5bb..4147522cd 100644
--- a/octorun/src/authentication.js
+++ b/octorun/src/authentication.js
@@ -1,12 +1,11 @@
-var endOfLine = require('os').EOL;
var config = require("./configuration");
var octokitWrapper = require("./octokit");
var twoFactorRegex = new RegExp("must specify two-factor authentication otp code", "gi");
-var scopes = ["user", "repo", "gist", "write:public_key"];
+var scopes = ["user", "repo"];
-var handleAuthentication = function (username, password, onSuccess, onFailure, twoFactor) {
+var handleAuthentication = function (username, password, onSuccess, onFailure, twoFactor, host) {
if (!config.clientId || !config.clientSecret) {
throw "clientId and/or clientSecret missing";
}
@@ -15,7 +14,7 @@ var handleAuthentication = function (username, password, onSuccess, onFailure, t
throw "appName missing";
}
- var octokit = octokitWrapper.createOctokit();
+ var octokit = octokitWrapper.createOctokit(config.appName, host);
octokit.authenticate({
type: "basic",
@@ -27,7 +26,6 @@ var handleAuthentication = function (username, password, onSuccess, onFailure, t
if (twoFactor) {
headers = {
"X-GitHub-OTP": twoFactor,
- "user-agent": config.appName
};
}
diff --git a/octorun/src/bin/app-login.js b/octorun/src/bin/app-login.js
index f3484c31b..4577bac81 100644
--- a/octorun/src/bin/app-login.js
+++ b/octorun/src/bin/app-login.js
@@ -1,12 +1,12 @@
var commander = require("commander");
var package = require('../../package.json');
var authentication = require('../authentication');
-var endOfLine = require('os').EOL;
var output = require('../output');
commander
.version(package.version)
.option('-t, --twoFactor')
+ .option('-h, --host ')
.parse(process.argv);
var handleAuthentication = function (username, password, twoFactor) {
@@ -19,7 +19,7 @@ var handleAuthentication = function (username, password, twoFactor) {
}
}, function (error) {
output.error(error);
- }, twoFactor);
+ }, twoFactor, commander.host);
}
var encoding = 'utf-8';
diff --git a/octorun/src/bin/app-meta.js b/octorun/src/bin/app-meta.js
new file mode 100644
index 000000000..ecbc36e66
--- /dev/null
+++ b/octorun/src/bin/app-meta.js
@@ -0,0 +1,52 @@
+var commander = require('commander');
+var package = require('../../package.json');
+var output = require('../output');
+
+commander
+ .version(package.version)
+ .option('-h, --host ')
+ .parse(process.argv);
+
+var host = commander.host;
+var port = 443;
+var scheme = 'https';
+
+if (host) {
+ var https = require(scheme);
+ var options = {
+ protocol: scheme + ':',
+ hostname: host,
+ port: port,
+ path: '/api/v3/meta',
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ };
+
+ var req = https.request(options, function (res) {
+ var success = res.statusCode == 200;
+
+ if(!success) {
+ output.error(res.statusCode);
+ } else {
+ res.on('data', function (d) {
+ output.custom("success", d, true);
+ });
+
+ res.on('end', function (d) {
+ process.exit();
+ });
+ }
+ });
+
+ req.on('error', function (error) {
+ output.error(error);
+ });
+
+ req.end();
+}
+else {
+ commander.help();
+ process.exit(-1);
+}
\ No newline at end of file
diff --git a/octorun/src/bin/app-organizations.js b/octorun/src/bin/app-organizations.js
index 480289aa1..1a67e181f 100644
--- a/octorun/src/bin/app-organizations.js
+++ b/octorun/src/bin/app-organizations.js
@@ -1,16 +1,15 @@
var commander = require("commander");
var package = require('../../package.json');
var ApiWrapper = require('../api');
-var endOfLine = require('os').EOL;
var output = require('../output');
commander
.version(package.version)
+ .option('-h, --host ')
.parse(process.argv);
try {
-
- var apiWrapper = new ApiWrapper();
+ var apiWrapper = new ApiWrapper(commander.host);
apiWrapper.getOrgs(function (error, result) {
if (error) {
output.error(error);
diff --git a/octorun/src/bin/app-publish.js b/octorun/src/bin/app-publish.js
index 5fe602390..62305763e 100644
--- a/octorun/src/bin/app-publish.js
+++ b/octorun/src/bin/app-publish.js
@@ -1,7 +1,6 @@
var commander = require("commander");
var package = require('../../package.json')
var ApiWrapper = require('../api')
-var endOfLine = require('os').EOL;
var output = require('../output');
commander
@@ -10,6 +9,7 @@ commander
.option('-d, --description ')
.option('-o, --organization ')
.option('-p, --private')
+ .option('-h, --host ')
.parse(process.argv);
if(!commander.repository)
@@ -24,7 +24,7 @@ if (commander.private) {
}
try {
- var apiWrapper = new ApiWrapper();
+ var apiWrapper = new ApiWrapper(commander.host);
apiWrapper.publish(commander.repository, commander.description, private, commander.organization,
function (error, result) {
diff --git a/octorun/src/bin/app-token.js b/octorun/src/bin/app-token.js
new file mode 100644
index 000000000..5811be43e
--- /dev/null
+++ b/octorun/src/bin/app-token.js
@@ -0,0 +1,66 @@
+
+var commander = require('commander');
+var package = require('../../package.json');
+var output = require('../output');
+var config = require("../configuration");
+var querystring = require('querystring');
+
+commander
+ .version(package.version)
+ .option('-h, --host ')
+ .parse(process.argv);
+
+var host = commander.host;
+var port = 443;
+var scheme = 'https';
+
+var valid = host && config.clientId && config.clientSecret && config.token;
+if (valid) {
+ var https = require(scheme);
+
+ var postData = querystring.stringify({
+ client_id: config.clientId,
+ client_secret: config.clientSecret,
+ code: config.token
+ });
+
+ var options = {
+ protocol: scheme + ':',
+ hostname: host,
+ port: port,
+ path: '/login/oauth/access_token',
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Content-Length': postData.length
+ }
+ };
+
+ var req = https.request(options, function (res) {
+ var success = res.statusCode == 200;
+
+ if(!success) {
+ output.error(res.statusCode);
+ } else {
+ res.on('data', function (d) {
+ output.custom("success", d, true);
+ });
+
+ res.on('end', function (d) {
+ process.exit();
+ });
+ }
+ });
+
+ req.on('error', function (error) {
+ output.error(error);
+ });
+
+ req.write(postData);
+
+ req.end();
+}
+else {
+ commander.help();
+ process.exit(-1);
+}
\ No newline at end of file
diff --git a/octorun/src/bin/app-usage.js b/octorun/src/bin/app-usage.js
index 07bd3b91c..0fb32b691 100644
--- a/octorun/src/bin/app-usage.js
+++ b/octorun/src/bin/app-usage.js
@@ -1,8 +1,7 @@
-var commander = require("commander");
-var package = require('../../package.json')
-var endOfLine = require('os').EOL;
+var commander = require('commander');
+var package = require('../../package.json');
+var config = require('../configuration');
var fs = require('fs');
-var util = require('util');
var output = require('../output');
commander
diff --git a/octorun/src/bin/app-validate.js b/octorun/src/bin/app-validate.js
index 8ba643021..294fbcbdc 100644
--- a/octorun/src/bin/app-validate.js
+++ b/octorun/src/bin/app-validate.js
@@ -1,15 +1,15 @@
var commander = require("commander");
var package = require('../../package.json');
-var endOfLine = require('os').EOL;
var ApiWrapper = require('../api');
var output = require('../output');
commander
.version(package.version)
+ .option('-h, --host ')
.parse(process.argv);
try {
- var apiWrapper = new ApiWrapper();
+ var apiWrapper = new ApiWrapper(commander.host);
apiWrapper.verifyUser(function (error, result) {
if (error) {
diff --git a/octorun/src/bin/app.js b/octorun/src/bin/app.js
index e40d738b2..095292965 100644
--- a/octorun/src/bin/app.js
+++ b/octorun/src/bin/app.js
@@ -9,4 +9,6 @@ commander
.command('organizations', 'Get Organizations')
.command('publish', 'Publish')
.command('usage', 'Usage')
+ .command('token', 'Create OAuth Token')
+ .command('meta', 'Get Server Meta Data')
.parse(process.argv);
\ No newline at end of file
diff --git a/octorun/src/configuration.js b/octorun/src/configuration.js
index f9462acde..0fe3906fd 100644
--- a/octorun/src/configuration.js
+++ b/octorun/src/configuration.js
@@ -3,13 +3,11 @@ require("dotenv").config({silent: true});
var clientId = process.env.OCTOKIT_CLIENT_ID;
var clientSecret = process.env.OCTOKIT_CLIENT_SECRET;
var appName = process.env.OCTOKIT_USER_AGENT;
-var user = process.env.OCTORUN_USER;
var token = process.env.OCTORUN_TOKEN;
module.exports = {
clientId: clientId,
clientSecret: clientSecret,
appName: appName,
- user: user,
token: token
};
\ No newline at end of file
diff --git a/octorun/src/octokit.js b/octorun/src/octokit.js
index 1cf90b1ac..b0ab0a42f 100644
--- a/octorun/src/octokit.js
+++ b/octorun/src/octokit.js
@@ -1,19 +1,20 @@
var Octokit = require('octokit-rest-for-node-v0.12');
-var createOctokit = function () {
- return Octokit({
+var createOctokit = function (appName, host) {
+ var octokitConfiguration = {
timeout: 0,
requestMedia: 'application/vnd.github.v3+json',
headers: {
- 'user-agent': 'octokit/rest.js v1.2.3'
+ 'user-agent': appName
}
+ };
- // change for custom GitHub Enterprise URL
- //host: 'api.github.com',
- //pathPrefix: '',
- //protocol: 'https',
- //port: 443
- });
+ if (host) {
+ octokitConfiguration.host = host;
+ octokitConfiguration.pathPrefix = 'api/v3';
+ }
+
+ return Octokit(octokitConfiguration);
};
module.exports = { createOctokit: createOctokit };
\ No newline at end of file
diff --git a/octorun/version b/octorun/version
index de08fdd40..998379c47 100644
--- a/octorun/version
+++ b/octorun/version
@@ -1 +1 @@
-9fcd9faa
\ No newline at end of file
+902910f48
\ No newline at end of file
diff --git a/package.cmd b/package.cmd
index e25ddbd41..a47264d8f 100644
--- a/package.cmd
+++ b/package.cmd
@@ -47,7 +47,7 @@ if not exist "%Unity%" (
del /Q unity\PackageProject\Assets\Plugins\GitHub\Editor\*.pdb.meta
del /Q unity\PackageProject\Assets\Plugins\GitHub\Editor\*.xml
- for /f tokens^=^2^ usebackq^ delims^=^" %%G in (`find "const string Version" common\SolutionInfo.cs`) do call :Package %%G
+ for /f tokens^=^2^ usebackq^ delims^=^" %%G in (`find "const string GitHubForUnityVersion" common\SolutionInfo.cs`) do call :Package %%G
goto End
diff --git a/package.sh b/package.sh
index ca09bd5e5..8dc3c93dc 100755
--- a/package.sh
+++ b/package.sh
@@ -58,7 +58,7 @@ rm -f unity/PackageProject/Assets/Plugins/GitHub/Editor/*.pdb
rm -f unity/PackageProject/Assets/Plugins/GitHub/Editor/*.pdb.meta
rm -f unity/PackageProject/Assets/Plugins/GitHub/Editor/*.xml
-Version=`sed -En 's,.*Version = "(.*)".*,\1,p' common/SolutionInfo.cs`
+Version=`sed -En 's,.*GitHubForUnityVersion = "(.*)".*,\1,p' common/SolutionInfo.cs`
commitcount=`git rev-list --count HEAD`
commit=`git log -n1 --pretty=format:%h`
Version="${Version}.${commitcount}-${commit}"
diff --git a/run-test-webserver.sh b/run-test-webserver.sh
new file mode 100755
index 000000000..8ab846630
--- /dev/null
+++ b/run-test-webserver.sh
@@ -0,0 +1,16 @@
+#!/bin/sh -eu
+PORT="50000"
+if [ $# -eq 1 ]; then
+ PORT="$1"
+fi
+
+EXEC="mono "
+if [ -e "/c/" ]; then
+ EXEC=""
+fi
+
+if [ ! -e build/CommandLine/CommandLine.exe ]; then
+ >&2 xbuild /target:CommandLine GitHub.Unity.sln /verbosity:minimal
+fi
+
+$EXEC build/CommandLine/CommandLine.exe --web --port $PORT
diff --git a/script b/script
index 48d975141..d373977da 160000
--- a/script
+++ b/script
@@ -1 +1 @@
-Subproject commit 48d975141aae81c47fe64981518edc726ee520f0
+Subproject commit d373977da73bdf7f9170e778638c80e5b49ca3b3
diff --git a/src/.gitignore b/src/.gitignore
index 59f76e79c..a7abc4e4a 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -311,4 +311,6 @@ sysinfo.txt
# Builds
*.apk
-*.unitypackage
\ No newline at end of file
+*.unitypackage
+UnityExtension/**/manifest.json
+tests/IntegrationTests/IOTestsRepo/
\ No newline at end of file
diff --git a/src/GitHub.Api/Application/ApiClient.cs b/src/GitHub.Api/Application/ApiClient.cs
index e6a35e10d..97a26797a 100644
--- a/src/GitHub.Api/Application/ApiClient.cs
+++ b/src/GitHub.Api/Application/ApiClient.cs
@@ -1,215 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Threading.Tasks;
+using System.Net;
using GitHub.Logging;
using System.Runtime.Serialization;
using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using GitHub.Unity.Json;
namespace GitHub.Unity
{
- class ApiClient : IApiClient
+ public class ApiClient : IApiClient
{
private static readonly ILogging logger = LogHelper.GetLogger();
+ private static readonly Regex httpStatusErrorRegex = new Regex("(?<=[a-z])([A-Z])", RegexOptions.Compiled);
+ private static readonly Regex accessTokenRegex = new Regex("access_token=(.*?)&", RegexOptions.Compiled);
+
public HostAddress HostAddress { get; }
- public UriString OriginalUrl { get; }
private readonly IKeychain keychain;
private readonly IProcessManager processManager;
private readonly ITaskManager taskManager;
- private readonly NPath nodeJsExecutablePath;
- private readonly NPath octorunScriptPath;
private readonly ILoginManager loginManager;
+ private readonly IEnvironment environment;
+ private IKeychainAdapter keychainAdapter;
+ private Connection connection;
- public ApiClient(UriString hostUrl, IKeychain keychain, IProcessManager processManager, ITaskManager taskManager, NPath nodeJsExecutablePath, NPath octorunScriptPath)
+ public ApiClient(IKeychain keychain, IProcessManager processManager, ITaskManager taskManager,
+ IEnvironment environment, UriString host = null)
{
- Guard.ArgumentNotNull(hostUrl, nameof(hostUrl));
Guard.ArgumentNotNull(keychain, nameof(keychain));
- HostAddress = HostAddress.Create(hostUrl);
- OriginalUrl = hostUrl;
+ host = host == null
+ ? UriString.ToUriString(HostAddress.GitHubDotComHostAddress.WebUri)
+ : new UriString(host.ToRepositoryUri().GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped));
+
+ HostAddress = HostAddress.Create(host);
+
this.keychain = keychain;
this.processManager = processManager;
this.taskManager = taskManager;
- this.nodeJsExecutablePath = nodeJsExecutablePath;
- this.octorunScriptPath = octorunScriptPath;
- loginManager = new LoginManager(keychain, ApplicationInfo.ClientId, ApplicationInfo.ClientSecret,
- processManager: processManager,
- taskManager: taskManager,
- nodeJsExecutablePath: nodeJsExecutablePath,
- octorunScript: octorunScriptPath);
+ this.environment = environment;
+ loginManager = new LoginManager(keychain, processManager, taskManager, environment);
}
- public async Task Logout(UriString host)
+ public ITask Logout(UriString host)
{
- await LogoutInternal(host);
+ return loginManager.Logout(host);
}
- private async Task LogoutInternal(UriString host)
- {
- await loginManager.Logout(host);
- }
-
- public async Task CreateRepository(string name, string description, bool isPrivate, Action callback, string organization = null)
+ public void CreateRepository(string name, string description, bool isPrivate,
+ Action callback, string organization = null)
{
Guard.ArgumentNotNull(callback, "callback");
- try
- {
- var repository = await CreateRepositoryInternal(name, organization, description, isPrivate);
- callback(repository, null);
- }
- catch (Exception e)
- {
- callback(null, e);
- }
- }
-
- public async Task GetOrganizations(Action onSuccess, Action onError = null)
- {
- Guard.ArgumentNotNull(onSuccess, nameof(onSuccess));
- await GetOrganizationInternal(onSuccess, onError);
- }
-
- public async Task GetCurrentUser(Action onSuccess, Action onError = null)
- {
- Guard.ArgumentNotNull(onSuccess, nameof(onSuccess));
- try
- {
- var user = await GetCurrentUser();
- onSuccess(user);
- }
- catch (Exception e)
- {
- onError?.Invoke(e);
- }
- }
-
- public async Task Login(string username, string password, Action need2faCode, Action result)
- {
- Guard.ArgumentNotNull(need2faCode, "need2faCode");
- Guard.ArgumentNotNull(result, "result");
-
- LoginResultData res = null;
- try
- {
- res = await loginManager.Login(OriginalUrl, username, password);
- }
- catch (Exception ex)
- {
- logger.Warning(ex);
- result(false, ex.Message);
- return;
- }
-
- if (res.Code == LoginResultCodes.CodeRequired)
- {
- var resultCache = new LoginResult(res, result, need2faCode);
- need2faCode(resultCache);
- }
- else
- {
- result(res.Code == LoginResultCodes.Success, res.Message);
- }
- }
- public async Task ContinueLogin(LoginResult loginResult, string code)
- {
- LoginResultData result = null;
- try
- {
- result = await loginManager.ContinueLogin(loginResult.Data, code);
- }
- catch (Exception ex)
- {
- loginResult.Callback(false, ex.Message);
- return;
- }
- if (result.Code == LoginResultCodes.CodeFailed)
+ new FuncTask(taskManager.Token, () =>
{
- loginResult.TwoFACallback(new LoginResult(result, loginResult.Callback, loginResult.TwoFACallback));
- }
- loginResult.Callback(result.Code == LoginResultCodes.Success, result.Message);
- }
+ EnsureValidCredentials();
- public async Task LoginAsync(string username, string password, Func need2faCode)
- {
- Guard.ArgumentNotNull(need2faCode, "need2faCode");
+ var command = new StringBuilder("publish");
- LoginResultData res = null;
- try
- {
- res = await loginManager.Login(OriginalUrl, username, password);
- }
- catch (Exception)
- {
- return false;
- }
-
- if (res.Code == LoginResultCodes.CodeRequired)
- {
- var resultCache = new LoginResult(res, null, null);
- var code = need2faCode(resultCache);
- return await ContinueLoginAsync(resultCache, need2faCode, code);
- }
- else
- {
- return res.Code == LoginResultCodes.Success;
- }
- }
-
- public async Task ContinueLoginAsync(LoginResult loginResult, Func need2faCode, string code)
- {
- LoginResultData result = null;
- try
- {
- result = await loginManager.ContinueLogin(loginResult.Data, code);
- }
- catch (Exception)
- {
- return false;
- }
-
- if (result.Code == LoginResultCodes.CodeFailed)
- {
- var resultCache = new LoginResult(result, null, null);
- code = need2faCode(resultCache);
- if (String.IsNullOrEmpty(code))
- return false;
- return await ContinueLoginAsync(resultCache, need2faCode, code);
- }
- return result.Code == LoginResultCodes.Success;
- }
-
- private async Task GetCurrentUser()
- {
- //TODO: ONE_USER_LOGIN This assumes we only support one login
- var keychainConnection = keychain.Connections.FirstOrDefault();
- if (keychainConnection == null)
- throw new KeychainEmptyException();
-
- var keychainAdapter = await GetValidatedKeychainAdapter(keychainConnection);
-
- // we can't trust that the system keychain has the username filled out correctly.
- // if it doesn't, we need to grab the username from the server and check it
- // unfortunately this means that things will be slower when the keychain doesn't have all the info
- if (keychainConnection.User == null || keychainAdapter.Credential.Username != keychainConnection.Username)
- {
- keychainConnection.User = await GetValidatedGitHubUser(keychainConnection, keychainAdapter);
- }
- return keychainConnection.User;
- }
-
- private async Task CreateRepositoryInternal(string repositoryName, string organization, string description, bool isPrivate)
- {
- try
- {
- logger.Trace("Creating repository");
-
- var user = await GetCurrentUser();
- var keychainAdapter = keychain.Connect(OriginalUrl);
+ if (!HostAddress.IsGitHubDotCom())
+ {
+ command.Append(" -h ");
+ command.Append(HostAddress.ApiUri.Host);
+ }
- var command = new StringBuilder("publish -r \"");
- command.Append(repositoryName);
+ command.Append(" -r \"");
+ command.Append(name);
command.Append("\"");
if (!string.IsNullOrEmpty(description))
@@ -231,11 +90,12 @@ private async Task CreateRepositoryInternal(string repositoryN
command.Append(" -p");
}
- var octorunTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath, octorunScriptPath, command.ToString(),
- user: user.Login, userToken: keychainAdapter.Credential.Token)
+ var adapter = EnsureKeychainAdapter();
+
+ var octorunTask = new OctorunTask(taskManager.Token, environment, command.ToString(), adapter.Credential.Token)
.Configure(processManager);
- var ret = await octorunTask.StartAwait();
+ var ret = octorunTask.RunSynchronously();
if (ret.IsSuccess && ret.Output.Length == 2)
{
return new GitHubRepository
@@ -246,28 +106,102 @@ private async Task CreateRepositoryInternal(string repositoryN
}
throw new ApiClientException(ret.GetApiErrorMessage() ?? "Publish failed");
- }
- catch (Exception ex)
+ })
+ .FinallyInUI((success, ex, repository) =>
{
- logger.Error(ex, "Error Creating Repository");
- throw;
- }
+ if (success)
+ callback(repository, null);
+ else
+ {
+ logger.Error(ex, "Error creating repository");
+ callback(null, ex);
+ }
+ })
+ .Start();
}
- private async Task GetOrganizationInternal(Action onSuccess, Action onError = null)
+ public void GetEnterpriseServerMeta(Action onSuccess, Action onError = null)
{
- try
+ Guard.ArgumentNotNull(onSuccess, nameof(onSuccess));
+ new FuncTask(taskManager.Token, () =>
+ {
+ var octorunTask = new OctorunTask(taskManager.Token, environment, "meta -h " + HostAddress.ApiUri.Host)
+ .Configure(processManager);
+
+ var ret = octorunTask.RunSynchronously();
+ if (ret.IsSuccess)
+ {
+ var deserializeObject = SimpleJson.DeserializeObject>(ret.Output[0]);
+
+ return new GitHubHostMeta
+ {
+ InstalledVersion = (string)deserializeObject["installed_version"],
+ GithubServicesSha = (string)deserializeObject["github_services_sha"],
+ VerifiablePasswordAuthentication = (bool)deserializeObject["verifiable_password_authentication"]
+ };
+ }
+
+ var message = ret.GetApiErrorMessage();
+
+ logger.Trace("Message: {0}", message);
+
+ if (message != null)
+ {
+ if (message.Contains("ETIMEDOUT", StringComparison.InvariantCulture))
+ {
+ message = "Connection timed out.";
+ }
+ else if (message.Contains("ECONNREFUSED", StringComparison.InvariantCulture))
+ {
+ message = "Connection refused.";
+ }
+ else if (message.Contains("ENOTFOUND", StringComparison.InvariantCulture))
+ {
+ message = "Address not found.";
+ }
+ else
+ {
+ int httpStatusCode;
+ if (int.TryParse(message, out httpStatusCode))
+ {
+ var httpStatus = ((HttpStatusCode)httpStatusCode).ToString();
+ message = httpStatusErrorRegex.Replace(httpStatus, " $1");
+ }
+ }
+ }
+ else
+ {
+ message = "Error getting server meta";
+ }
+
+ throw new ApiClientException(message);
+ })
+ .FinallyInUI((success, ex, meta) =>
{
- logger.Trace("Getting Organizations");
+ if (success)
+ onSuccess(meta);
+ else
+ {
+ logger.Error(ex, "Error getting server meta");
+ onError?.Invoke(ex);
+ }
+ })
+ .Start();
+ }
- var user = await GetCurrentUser();
- var keychainAdapter = keychain.Connect(OriginalUrl);
+ public void GetOrganizations(Action onSuccess, Action onError = null)
+ {
+ Guard.ArgumentNotNull(onSuccess, nameof(onSuccess));
+ new FuncTask(taskManager.Token, () =>
+ {
+ var adapter = EnsureKeychainAdapter();
- var octorunTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath, octorunScriptPath, "organizations",
- user: user.Login, userToken: keychainAdapter.Credential.Token)
+ var command = HostAddress.IsGitHubDotCom() ? "organizations" : "organizations -h " + HostAddress.ApiUri.Host;
+ var octorunTask = new OctorunTask(taskManager.Token, environment,
+ command, adapter.Credential.Token)
.Configure(processManager);
- var ret = await octorunTask.StartAsAsync();
+ var ret = octorunTask.RunSynchronously();
if (ret.IsSuccess)
{
var organizations = new List();
@@ -279,57 +213,238 @@ private async Task GetOrganizationInternal(Action onSuccess, Act
Login = ret.Output[i + 1]
});
}
-
- onSuccess(organizations.ToArray());
- return;
+ return organizations.ToArray();
}
throw new ApiClientException(ret.GetApiErrorMessage() ?? "Error getting organizations");
- }
- catch (Exception ex)
+ })
+ .FinallyInUI((success, ex, orgs) =>
+ {
+ if (success)
+ onSuccess(orgs);
+ else
+ {
+ logger.Error(ex, "Error Getting Organizations");
+ onError?.Invoke(ex);
+ }
+ })
+ .Start();
+ }
+
+ private IKeychainAdapter EnsureKeychainAdapter()
+ {
+ var adapter = KeychainAdapter;
+ if (adapter.Credential == null)
{
- logger.Error(ex, "Error Getting Organizations");
- onError?.Invoke(ex);
+ throw new ApiClientException("No Credentials found");
}
+
+ return adapter;
+ }
+
+ public void GetCurrentUser(Action onSuccess, Action onError = null)
+ {
+ Guard.ArgumentNotNull(onSuccess, nameof(onSuccess));
+ new FuncTask(taskManager.Token, GetCurrentUser)
+ .FinallyInUI((success, ex, user) =>
+ {
+ if (success)
+ onSuccess(user);
+ else
+ onError?.Invoke(ex);
+ })
+ .Start();
+ }
+
+ public void LoginWithToken(string token, Action result)
+ {
+ Guard.ArgumentNotNull(token, "token");
+ Guard.ArgumentNotNull(result, "result");
+
+ new FuncTask(taskManager.Token,
+ () => loginManager.LoginWithToken(HostAddress.WebUri.Host, token))
+ .FinallyInUI((success, ex, res) =>
+ {
+ if (!success)
+ {
+ logger.Warning(ex);
+ result(false);
+ return;
+ }
+
+ result(res);
+ })
+ .Start();
}
- private async Task GetValidatedKeychainAdapter(Connection keychainConnection)
+ public void CreateOAuthToken(string code, Action result)
{
- var keychainAdapter = await keychain.Load(keychainConnection.Host);
- if (keychainAdapter == null)
- throw new KeychainEmptyException();
+ var command = "token -h " + HostAddress.WebUri.Host;
+ var octorunTask = new OctorunTask(taskManager.Token, environment, command, code)
+ .Configure(processManager);
+
+ octorunTask
+ .Then((b, octorunResult) =>
+ {
+ if (b && octorunResult.IsSuccess)
+ {
+ var first = octorunResult.Output.FirstOrDefault();
+ if (first == null)
+ {
+ result(false, "Error validating token.");
+ return;
+ }
- if (string.IsNullOrEmpty(keychainAdapter.Credential?.Username))
+ var match = accessTokenRegex.Match(first);
+ if (match.Success)
+ {
+ var token = match.Groups[1].Value;
+ LoginWithToken(token, b1 => result(b1, "Error validating token."));
+ }
+ else
+ {
+ result(false, octorunResult.Output.FirstOrDefault());
+ }
+ }
+ else
+ {
+ result(false, octorunResult.Output.FirstOrDefault());
+ }
+ })
+ .Catch(exception => result(false, exception.ToString()))
+ .Start();
+ }
+
+ public void Login(string username, string password, Action need2faCode, Action result)
+ {
+ Guard.ArgumentNotNull(need2faCode, "need2faCode");
+ Guard.ArgumentNotNull(result, "result");
+
+ new FuncTask(taskManager.Token,
+ () => loginManager.Login(HostAddress.WebUri.Host, username, password))
+ .FinallyInUI((success, ex, res) =>
+ {
+ if (!success)
+ {
+ logger.Warning(ex);
+ result(false, ex.Message);
+ return;
+ }
+
+ if (res.Code == LoginResultCodes.CodeRequired)
+ {
+ var resultCache = new LoginResult(res, result, need2faCode);
+ need2faCode(resultCache);
+ }
+ else
+ {
+ result(res.Code == LoginResultCodes.Success, res.Message);
+ }
+ })
+ .Start();
+ }
+
+ public void ContinueLogin(LoginResult loginResult, string code)
+ {
+ new FuncTask(taskManager.Token,
+ () => loginManager.ContinueLogin(loginResult.Data, code))
+ .FinallyInUI((success, ex, result) =>
+ {
+ if (!success)
+ {
+ loginResult.Callback(false, ex.Message);
+ return;
+ }
+ if (result.Code == LoginResultCodes.CodeFailed)
+ {
+ loginResult.TwoFACallback(new LoginResult(result, loginResult.Callback, loginResult.TwoFACallback));
+ }
+ loginResult.Callback(result.Code == LoginResultCodes.Success, result.Message);
+ })
+ .Start();
+ }
+
+ public void EnsureValidCredentials()
+ {
+ GetCurrentUser();
+ }
+
+ public GitHubUser GetCurrentUser()
+ {
+ // we can't trust that the system keychain has the username filled out correctly.
+ // if it doesn't, we need to grab the username from the server and check it
+ // unfortunately this means that things will be slower when the keychain doesn't have all the info
+ if (Connection.User == null || KeychainAdapter.Credential.Username != Connection.Username)
{
- logger.Warning("LoadKeychainInternal: Username is empty");
- throw new TokenUsernameMismatchException(keychainConnection.Username);
+ Connection.User = GetValidatedGitHubUser();
}
- if (keychainAdapter.Credential.Username != keychainConnection.Username)
+ return Connection.User;
+ }
+
+ private Connection Connection
+ {
+ get
{
- logger.Warning("LoadKeychainInternal: Token username does not match");
+ if (connection == null)
+ {
+ connection = keychain.Connections.FirstOrDefault(x => x.Host.ToUriString().Host == HostAddress.WebUri.Host);
+ }
+
+ return connection;
}
+ }
+
+ private IKeychainAdapter KeychainAdapter
+ {
+ get
+ {
+ if (keychainAdapter == null)
+ {
+ if (Connection == null)
+ throw new KeychainEmptyException();
+
+ var loadedKeychainAdapter = keychain.LoadFromSystem(Connection.Host);
+ if (loadedKeychainAdapter == null)
+ throw new KeychainEmptyException();
- return keychainAdapter;
+ if (string.IsNullOrEmpty(loadedKeychainAdapter.Credential?.Username))
+ {
+ logger.Warning("LoadKeychainInternal: Username is empty");
+ throw new TokenUsernameMismatchException(connection.Username);
+ }
+
+ if (loadedKeychainAdapter.Credential.Username != connection.Username)
+ {
+ logger.Warning("LoadKeychainInternal: Token username does not match");
+ }
+
+ keychainAdapter = loadedKeychainAdapter;
+ }
+
+ return keychainAdapter;
+ }
}
- private async Task GetValidatedGitHubUser(Connection keychainConnection, IKeychainAdapter keychainAdapter)
+ private GitHubUser GetValidatedGitHubUser()
{
try
{
- var octorunTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath, octorunScriptPath, "validate",
- user: keychainConnection.Username, userToken: keychainAdapter.Credential.Token)
+ var adapter = EnsureKeychainAdapter();
+
+ var command = HostAddress.IsGitHubDotCom() ? "validate" : "validate -h " + HostAddress.ApiUri.Host;
+ var octorunTask = new OctorunTask(taskManager.Token, environment, command, adapter.Credential.Token)
.Configure(processManager);
- var ret = await octorunTask.StartAsAsync();
+ var ret = octorunTask.RunSynchronously();
if (ret.IsSuccess)
{
var login = ret.Output[1];
- if (login != keychainConnection.Username)
+ if (!string.Equals(login, Connection.Username, StringComparison.InvariantCultureIgnoreCase))
{
logger.Trace("LoadKeychainInternal: Api username does not match");
- throw new TokenUsernameMismatchException(keychainConnection.Username, login);
+ throw new TokenUsernameMismatchException(Connection.Username, login);
}
return new GitHubUser
@@ -354,13 +469,20 @@ private async Task GetValidatedGitHubUser(Connection keychainConnect
}
}
- class GitHubUser
+ public class GitHubHostMeta
+ {
+ public bool VerifiablePasswordAuthentication { get; set; }
+ public string GithubServicesSha { get; set; }
+ public string InstalledVersion { get; set; }
+ }
+
+ public class GitHubUser
{
public string Name { get; set; }
public string Login { get; set; }
}
- class GitHubRepository
+ public class GitHubRepository
{
public string Name { get; set; }
public string CloneUrl { get; set; }
@@ -383,7 +505,7 @@ protected ApiClientException(SerializationInfo info, StreamingContext context) :
}
[Serializable]
- class TokenUsernameMismatchException : ApiClientException
+ public class TokenUsernameMismatchException : ApiClientException
{
public string CachedUsername { get; }
public string CurrentUsername { get; }
@@ -398,7 +520,7 @@ protected TokenUsernameMismatchException(SerializationInfo info, StreamingContex
}
[Serializable]
- class KeychainEmptyException : ApiClientException
+ public class KeychainEmptyException : ApiClientException
{
public KeychainEmptyException()
{
diff --git a/src/GitHub.Api/Application/ApplicationConfiguration.cs b/src/GitHub.Api/Application/ApplicationConfiguration.cs
index 40d4e7933..5c8358303 100644
--- a/src/GitHub.Api/Application/ApplicationConfiguration.cs
+++ b/src/GitHub.Api/Application/ApplicationConfiguration.cs
@@ -5,18 +5,8 @@ namespace GitHub.Unity
public static class ApplicationConfiguration
{
public const int DefaultWebTimeout = 3000;
-
- static ApplicationConfiguration()
- {
- var executingAssembly = typeof(ApplicationConfiguration).Assembly;
- AssemblyName = executingAssembly.GetName();
- }
-
- ///
- /// The currently executing assembly.
- ///
- public static AssemblyName AssemblyName { get; }
-
+ public const int DefaultGitTimeout = 5000;
public static int WebTimeout { get; set; } = DefaultWebTimeout;
+ public static int GitTimeout { get; set; } = DefaultGitTimeout;
}
}
diff --git a/src/GitHub.Api/Application/ApplicationInfo.cs b/src/GitHub.Api/Application/ApplicationInfo.cs
index f70bf4241..f15f7d482 100644
--- a/src/GitHub.Api/Application/ApplicationInfo.cs
+++ b/src/GitHub.Api/Application/ApplicationInfo.cs
@@ -6,15 +6,32 @@ static partial class ApplicationInfo
#if DEBUG
public const string ApplicationName = "GitHub for Unity Debug";
public const string ApplicationProvider = "GitHub";
+ public const string ApplicationSafeName = "GitHubUnity-dev";
#else
public const string ApplicationName = "GitHubUnity";
public const string ApplicationProvider = "GitHub";
-#endif
public const string ApplicationSafeName = "GitHubUnity";
+#endif
public const string ApplicationDescription = "GitHub for Unity";
+#if DEBUG
+/*
+ For external contributors, we have bundled a developer OAuth application
+ called `GitHub for Unity (dev)` so that you can complete the sign in flow
+ locally without needing to configure your own application.
+ This is for testing only and it is (obviously) public, proceed with caution.
+
+ For a release build, you should create a new oauth application on github.com,
+ copy the `common/ApplicationInfo_Local.cs-example`
+ template to `common/ApplicationInfo_Local.cs` and fill out the `myClientId` and
+ `myClientSecret` fields for your oauth app.
+ */
+ internal static string ClientId { get; private set; } = "924a97f36926f535e72c";
+ internal static string ClientSecret { get; private set; } = "b4fa550b7f8e38034c6b1339084fa125eebb6155";
+#else
internal static string ClientId { get; private set; } = "";
internal static string ClientSecret { get; private set; } = "";
+#endif
public static string Version { get { return System.AssemblyVersionInformation.Version; } }
diff --git a/src/GitHub.Api/Application/ApplicationManagerBase.cs b/src/GitHub.Api/Application/ApplicationManagerBase.cs
index 45ac09712..5d258087e 100644
--- a/src/GitHub.Api/Application/ApplicationManagerBase.cs
+++ b/src/GitHub.Api/Application/ApplicationManagerBase.cs
@@ -3,282 +3,376 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using GitHub.Logging;
+using static GitHub.Unity.GitInstaller;
namespace GitHub.Unity
{
- abstract class ApplicationManagerBase : IApplicationManager
+ public class ApplicationManagerBase : IApplicationManager
{
protected static ILogging Logger { get; } = LogHelper.GetLogger();
private RepositoryManager repositoryManager;
- private Progress progressReporter;
+ private ProgressReporter progressReporter = new ProgressReporter();
+ private Progress progress = new Progress(TaskBase.Default);
protected bool isBusy;
+ private bool firstRun;
+ protected bool FirstRun { get { return firstRun; } set { firstRun = value; } }
+ private Guid instanceId;
+ protected Guid InstanceId { get { return instanceId; } set { instanceId = value; } }
+
public event Action OnProgress
{
add { progressReporter.OnProgress += value; }
remove { progressReporter.OnProgress -= value; }
}
- public ApplicationManagerBase(SynchronizationContext synchronizationContext)
+ public ApplicationManagerBase(SynchronizationContext synchronizationContext, IEnvironment environment)
{
- progressReporter = new Progress();
+ UIScheduler = ThreadingHelper.GetUIScheduler(synchronizationContext);
+
SynchronizationContext = synchronizationContext;
- SynchronizationContext.SetSynchronizationContext(SynchronizationContext);
ThreadingHelper.SetUIThread();
- UIScheduler = TaskScheduler.FromCurrentSynchronizationContext();
ThreadingHelper.MainThreadScheduler = UIScheduler;
+
+ Environment = environment;
TaskManager = new TaskManager(UIScheduler);
+ Platform = new Platform(Environment);
+ ProcessManager = new ProcessManager(Environment, Platform.GitEnvironment, TaskManager.Token);
+ GitClient = new GitClient(Environment, ProcessManager, TaskManager.Token);
}
protected void Initialize()
{
- // accessing Environment triggers environment initialization if it hasn't happened yet
- Platform = new Platform(Environment);
-
- UserSettings = new UserSettings(Environment);
- LocalSettings = new LocalSettings(Environment);
- SystemSettings = new SystemSettings(Environment);
-
- UserSettings.Initialize();
- LocalSettings.Initialize();
- SystemSettings.Initialize();
-
LogHelper.TracingEnabled = UserSettings.Get(Constants.TraceLoggingKey, false);
- ProcessManager = new ProcessManager(Environment, Platform.GitEnvironment, CancellationToken);
+ ApplicationConfiguration.WebTimeout = UserSettings.Get(Constants.WebTimeoutKey, ApplicationConfiguration.WebTimeout);
+ ApplicationConfiguration.GitTimeout = UserSettings.Get(Constants.GitTimeoutKey, ApplicationConfiguration.GitTimeout);
Platform.Initialize(ProcessManager, TaskManager);
- GitClient = new GitClient(Environment, ProcessManager, TaskManager.Token);
+ progress.OnProgress += progressReporter.UpdateProgress;
+ UsageTracker = new UsageTracker(TaskManager, GitClient, ProcessManager, UserSettings, Environment, Platform.Keychain, InstanceId.ToString());
+
+#if ENABLE_METRICS
+ var metricsService = new MetricsService(ProcessManager,
+ TaskManager,
+ Platform.Keychain,
+ Environment);
+ UsageTracker.MetricsService = metricsService;
+#endif
}
- public void Run(bool firstRun)
+ public void Run()
{
- Logger.Trace("Run - CurrentDirectory {0}", NPath.CurrentDirectory);
isBusy = true;
+ progress.UpdateProgress(0, 100, "Initializing...");
- var endTask = new ActionTask(CancellationToken,
- (_, state) => InitializeEnvironment(state))
- { Affinity = TaskAffinity.UI };
-
- ITask setExistingEnvironmentPath;
- if (Environment.IsMac)
- {
- setExistingEnvironmentPath = new SimpleProcessTask(CancellationToken, "bash".ToNPath(), "-c \"/usr/libexec/path_helper\"")
- .Configure(ProcessManager, dontSetupGit: true)
- .Catch(e => true) // make sure this doesn't throw if the task fails
- .Then((success, path) => success ? path?.Split(new[] { "\"" }, StringSplitOptions.None)[1] : null);
- }
- else
+ if (firstRun)
{
- setExistingEnvironmentPath = new FuncTask(CancellationToken, () => null);
+ UsageTracker.IncrementNumberOfStartups();
}
- setExistingEnvironmentPath.OnEnd += (t, path, success, ex) =>
+ var thread = new Thread(() =>
{
- if (path != null)
+ GitInstallationState state = new GitInstallationState();
+ try
{
- Logger.Trace("Existing Environment Path Original:{0} Updated:{1}", Environment.Path, path);
- Environment.Path = path;
- }
- };
-
- var setupOctorun = new OctorunInstaller(Environment, TaskManager).SetupOctorunIfNeeded();
- var setOctorunEnvironment = new ActionTask(CancellationToken,
- (s, octorunPath) => Environment.OctorunScriptPath = octorunPath);
+ if (Environment.IsMac)
+ {
+ var getEnvPath = new SimpleProcessTask(TaskManager.Token, "bash".ToNPath(), "-c \"/usr/libexec/path_helper\"")
+ .Configure(ProcessManager, dontSetupGit: true)
+ .Catch(e => true); // make sure this doesn't throw if the task fails
+ var path = getEnvPath.RunSynchronously();
+ if (getEnvPath.Successful)
+ {
+ Logger.Trace("Existing Environment Path Original:{0} Updated:{1}", Environment.Path, path);
+ Environment.Path = path?.Split(new[] { "\"" }, StringSplitOptions.None)[1];
+ }
+ }
- var getGitFromSettings = new FuncTask(CancellationToken, () =>
- {
- var gitExecutablePath = SystemSettings.Get(Constants.GitInstallPathKey)?.ToNPath();
- if (gitExecutablePath.HasValue && gitExecutablePath.Value.FileExists()) // we have a git path
+ progress.UpdateProgress(20, 100, "Setting up octorun...");
+
+ Environment.OctorunScriptPath = new OctorunInstaller(Environment, TaskManager)
+ .SetupOctorunIfNeeded();
+
+ progress.UpdateProgress(50, 100, "Setting up git...");
+
+ state = Environment.GitInstallationState;
+ if (!state.GitIsValid && !state.GitLfsIsValid && FirstRun)
+ {
+ // importing old settings
+ NPath gitExecutablePath = Environment.SystemSettings.Get(Constants.GitInstallPathKey, NPath.Default);
+ if (gitExecutablePath.IsInitialized)
+ {
+ Environment.SystemSettings.Unset(Constants.GitInstallPathKey);
+ state.GitExecutablePath = gitExecutablePath;
+ state.GitInstallationPath = gitExecutablePath.Parent.Parent;
+ Environment.GitInstallationState = state;
+ }
+ }
+
+
+ var installer = new GitInstaller(Environment, ProcessManager, TaskManager.Token);
+ installer.Progress.OnProgress += progressReporter.UpdateProgress;
+ if (state.GitIsValid && state.GitLfsIsValid)
+ {
+ if (firstRun)
+ {
+ installer.ValidateGitVersion(state);
+ if (state.GitIsValid)
+ {
+ installer.ValidateGitLfsVersion(state);
+ }
+ }
+ }
+
+ if (!state.GitIsValid || !state.GitLfsIsValid)
+ {
+ state = installer.SetupGitIfNeeded();
+ }
+
+ SetupGit(state);
+
+ progress.UpdateProgress(80, 100, "Initializing repository...");
+
+ if (state.GitIsValid && state.GitLfsIsValid)
+ {
+ RestartRepository();
+ }
+
+ progress.UpdateProgress(100, 100, "Initialized");
+ }
+ catch (Exception ex)
{
- Logger.Trace("Using git install path from settings: {0}", gitExecutablePath);
- return gitExecutablePath.Value;
+ Logger.Error(ex, "A problem ocurred setting up Git");
+ progress.UpdateProgress(90, 100, "Initialization failed");
}
- return NPath.Default;
+
+ new ActionTask(TaskManager.Token, (s, gitIsValid) =>
+ {
+ InitializationComplete();
+ if (gitIsValid)
+ {
+ InitializeUI();
+ }
+ },
+ () => state.GitIsValid && state.GitLfsIsValid)
+ { Affinity = TaskAffinity.UI }
+ .Start();
});
+ thread.Start();
+ }
- getGitFromSettings.OnEnd += (t, path, _, __) =>
+ public void SetupGit(GitInstaller.GitInstallationState state)
+ {
+ if (!state.GitIsValid || !state.GitLfsIsValid)
{
- if (path.IsInitialized)
+ if (!state.GitExecutablePath.IsInitialized)
{
- var state = new GitInstaller.GitInstallationState {
- GitExecutablePath = path,
- GitIsValid = true
- };
- endTask.PreviousResult = state;
- endTask.Start();
- return;
+ Logger.Warning(Localization.GitNotFound);
}
- Logger.Trace("Using portable git");
-
- var setupGit = new GitInstaller(Environment, ProcessManager, TaskManager).SetupGitIfNeeded();
- t.Then(setupGit);
- setupGit.Finally((s, state) =>
+ else if (!state.GitLfsExecutablePath.IsInitialized)
+ {
+ Logger.Warning(Localization.GitLFSNotFound);
+ }
+ else if (state.GitVersion < Constants.MinimumGitVersion)
+ {
+ Logger.Warning(String.Format(Localization.GitVersionTooLow, state.GitExecutablePath, state.GitVersion, Constants.MinimumGitVersion));
+ }
+ else if (state.GitLfsVersion < Constants.MinimumGitLfsVersion)
{
- endTask.PreviousResult = state;
- endTask.Start();
- });
- setupGit.Progress(progressReporter.UpdateProgress);
- // append installer task to top chain
- };
-
- var setupChain = setExistingEnvironmentPath.Then(setupOctorun);
- setupChain.OnEnd += (t, path, _, __) =>
+ Logger.Warning(String.Format(Localization.GitLfsVersionTooLow, state.GitLfsExecutablePath, state.GitLfsVersion, Constants.MinimumGitLfsVersion));
+ }
+ return;
+ }
+
+ Environment.GitInstallationState = state;
+ Environment.User.Initialize(GitClient);
+
+ if (firstRun)
{
- t.GetEndOfChain().Then(setOctorunEnvironment).Then(getGitFromSettings);
- };
+ if (Environment.RepositoryPath.IsInitialized)
+ {
+ UpdateMergeSettings();
- setupChain.Start();
+ GitClient.LfsInstall()
+ .Catch(e =>
+ {
+ Logger.Error(e, "Error running lfs install");
+ return true;
+ })
+ .RunSynchronously();
+ }
+
+ if (Environment.IsWindows)
+ {
+ var credentialHelper = GitClient.GetConfig("credential.helper", GitConfigSource.Global)
+ .Catch(e =>
+ {
+ Logger.Error(e, "Error getting the credential helper");
+ return true;
+ }).RunSynchronously();
+
+ if (string.IsNullOrEmpty(credentialHelper))
+ {
+ Logger.Warning("No Windows CredentialHelper found: Setting to wincred");
+ GitClient.SetConfig("credential.helper", "wincred", GitConfigSource.Global)
+ .Catch(e =>
+ {
+ Logger.Error(e, "Error setting the credential helper");
+ return true;
+ })
+ .RunSynchronously();
+ }
+ }
+ }
}
- public ITask InitializeRepository()
+ public void InitializeRepository()
{
- //Logger.Trace("Running Repository Initialize");
+ isBusy = true;
+ progress.UpdateProgress(0, 100, "Initializing...");
+ var thread = new Thread(() =>
+ {
+ var success = true;
+ try
+ {
+ var targetPath = NPath.CurrentDirectory;
- var targetPath = NPath.CurrentDirectory;
+ var gitignore = targetPath.Combine(".gitignore");
+ var gitAttrs = targetPath.Combine(".gitattributes");
+ var assetsGitignore = targetPath.Combine("Assets", ".gitignore");
- var unityYamlMergeExec = Environment.UnityApplicationContents.Combine("Tools", "UnityYAMLMerge" + Environment.ExecutableExtension);
+ var filesForInitialCommit = new List { gitignore, gitAttrs, assetsGitignore };
- var yamlMergeCommand = Environment.IsWindows
- ? $@"'{unityYamlMergeExec}' merge -p ""$BASE"" ""$REMOTE"" ""$LOCAL"" ""$MERGED"""
- : $@"'{unityYamlMergeExec}' merge -p '$BASE' '$REMOTE' '$LOCAL' '$MERGED'";
+ GitClient.Init().RunSynchronously();
+ progress.UpdateProgress(10, 100, "Initializing...");
- var gitignore = targetPath.Combine(".gitignore");
- var gitAttrs = targetPath.Combine(".gitattributes");
- var assetsGitignore = targetPath.Combine("Assets", ".gitignore");
+ ConfigureMergeSettings();
+ progress.UpdateProgress(20, 100, "Initializing...");
- var filesForInitialCommit = new List { gitignore, gitAttrs, assetsGitignore };
+ GitClient.LfsInstall().RunSynchronously();
+ progress.UpdateProgress(30, 100, "Initializing...");
- var task =
- GitClient.Init()
- .Then(GitClient.SetConfig("merge.unityyamlmerge.cmd", yamlMergeCommand, GitConfigSource.Local))
- .Then(GitClient.SetConfig("merge.unityyamlmerge.trustExitCode", "false", GitConfigSource.Local))
- .Then(GitClient.LfsInstall())
- .ThenInUI(SetProjectToTextSerialization)
- .Then(new ActionTask(CancellationToken, _ => {
AssemblyResources.ToFile(ResourceType.Generic, ".gitignore", targetPath, Environment);
AssemblyResources.ToFile(ResourceType.Generic, ".gitattributes", targetPath, Environment);
-
assetsGitignore.CreateFile();
- }))
- .Then(GitClient.Add(filesForInitialCommit))
- .Then(GitClient.Commit("Initial commit", null))
- .Then(_ =>
- {
+ GitClient.Add(filesForInitialCommit).RunSynchronously();
+ progress.UpdateProgress(60, 100, "Initializing...");
+ GitClient.Commit("Initial commit", null).RunSynchronously();
+ progress.UpdateProgress(70, 100, "Initializing...");
Environment.InitializeRepository();
- RestartRepository();
- })
- .ThenInUI(InitializeUI);
- return task;
- }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "A problem ocurred initializing the repository");
+ progress.UpdateProgress(90, 100, "Failed to initialize repository");
+ success = false;
+ }
- public void RestartRepository()
- {
- if (Environment.RepositoryPath.IsInitialized)
- {
- repositoryManager = Unity.RepositoryManager.CreateInstance(Platform, TaskManager, GitClient, Environment.FileSystem, Environment.RepositoryPath);
- repositoryManager.Initialize();
- Environment.Repository.Initialize(repositoryManager, TaskManager);
- repositoryManager.Start();
- Environment.Repository.Start();
- Logger.Trace($"Got a repository? {(Environment.Repository != null ? Environment.Repository.LocalPath : "null")}");
- }
+ if (success)
+ {
+ progress.UpdateProgress(90, 100, "Initializing...");
+ RestartRepository();
+ TaskManager.RunInUI(InitializeUI);
+ UsageTracker.IncrementProjectsInitialized();
+ progress.UpdateProgress(100, 100, "Initialized");
+ }
+ isBusy = false;
+ });
+ thread.Start();
}
- protected void SetupMetrics(string unityVersion, bool firstRun)
+ private void ConfigureMergeSettings(string keyName = null)
{
- //Logger.Trace("Setup metrics");
+ var unityYamlMergeExec =
+ Environment.UnityApplicationContents.Combine("Tools", "UnityYAMLMerge" + Environment.ExecutableExtension);
- var usagePath = Environment.UserCachePath.Combine(Constants.UsageFile);
-
- string id = null;
- if (UserSettings.Exists(Constants.GuidKey))
- {
- id = UserSettings.Get(Constants.GuidKey);
- }
+ var yamlMergeCommand = $"'{unityYamlMergeExec}' merge -h -p --force %O %B %A %A";
- if (String.IsNullOrEmpty(id))
- {
- id = Guid.NewGuid().ToString();
- UserSettings.Set(Constants.GuidKey, id);
- }
+ keyName = keyName ?? "unityyamlmerge";
-#if ENABLE_METRICS
- var metricsService = new MetricsService(ProcessManager,
- TaskManager,
- Environment.FileSystem,
- Environment.NodeJsExecutablePath,
- Environment.OctorunScriptPath);
+ GitClient.SetConfig($"merge.{keyName}.name", "Unity SmartMerge (UnityYamlMerge)", GitConfigSource.Local).Catch(e => {
+ Logger.Error(e, "Error setting merge." + keyName + ".name");
+ return true;
+ }).RunSynchronously();
- UsageTracker = new UsageTracker(metricsService, UserSettings, usagePath, id, unityVersion);
+ GitClient.SetConfig($"merge.{keyName}.driver", yamlMergeCommand, GitConfigSource.Local).Catch(e => {
+ Logger.Error(e, "Error setting merge." + keyName + ".driver");
+ return true;
+ }).RunSynchronously();
- if (firstRun)
- {
- UsageTracker.IncrementLaunchCount();
- }
-#endif
+ GitClient.SetConfig($"merge.{keyName}.recursive", "binary", GitConfigSource.Local).Catch(e => {
+ Logger.Error(e, "Error setting merge." + keyName + ".recursive");
+ return true;
+ }).RunSynchronously();
}
- protected abstract void SetupMetrics();
- protected abstract void InitializeUI();
- protected abstract void SetProjectToTextSerialization();
-
- ///
- /// Initialize environment after finding where git is. This needs to run on the main thread
- ///
- ///
- ///
- private void InitializeEnvironment(GitInstaller.GitInstallationState installationState)
+ private void UpdateMergeSettings()
{
- isBusy = false;
- SetupMetrics();
-
- if (!installationState.GitIsValid)
+ var gitAttributesPath = Environment.RepositoryPath.Combine(".gitattributes");
+ if (gitAttributesPath.FileExists())
{
- return;
+ var readAllText = gitAttributesPath.ReadAllText();
+ var containsLegacyUnityYamlMergeError = readAllText.Contains("unityamlmerge");
+
+ if (containsLegacyUnityYamlMergeError)
+ {
+ ConfigureMergeSettings("unityamlmerge");
+ }
}
- var gitInstallDetails = new GitInstaller.GitInstallDetails(Environment.UserCachePath, Environment.IsWindows);
- var isCustomGitExec = installationState.GitExecutablePath != gitInstallDetails.GitExecutablePath;
+ GitClient.UnSetConfig("merge.unityyamlmerge.cmd", GitConfigSource.Local).Catch(e => {
+ Logger.Error(e, "Error removing merge.unityyamlmerge.cmd");
+ return true;
+ }).RunSynchronously();
- Environment.GitExecutablePath = installationState.GitExecutablePath;
- Environment.GitLfsExecutablePath = installationState.GitLfsExecutablePath;
+ GitClient.UnSetConfig("merge.unityyamlmerge.trustExitCode", GitConfigSource.Local).Catch(e => {
+ Logger.Error(e, "Error removing merge.unityyamlmerge.trustExitCode");
+ return true;
+ }).RunSynchronously();
- Environment.IsCustomGitExecutable = isCustomGitExec;
- Environment.User.Initialize(GitClient);
+ ConfigureMergeSettings();
+ }
+
+ public void RestartRepository()
+ {
+ if (!Environment.RepositoryPath.IsInitialized)
+ return;
- var afterGitSetup = new ActionTask(CancellationToken, RestartRepository)
- .ThenInUI(InitializeUI);
+ repositoryManager?.Dispose();
- ITask task = afterGitSetup;
- if (Environment.IsWindows)
- {
- var credHelperTask = GitClient.GetConfig("credential.helper", GitConfigSource.Global);
- credHelperTask.OnEnd += (thisTask, credentialHelper, success, exception) =>
- {
- if (!success || string.IsNullOrEmpty(credentialHelper))
- {
- Logger.Warning("No Windows CredentialHelper found: Setting to wincred");
- thisTask
- .Then(GitClient.SetConfig("credential.helper", "wincred", GitConfigSource.Global))
- .Then(afterGitSetup);
- }
- else
- thisTask.Then(afterGitSetup);
- };
- task = credHelperTask;
- }
- task.Start();
+ repositoryManager = Unity.RepositoryManager.CreateInstance(Platform, TaskManager, GitClient, Environment.RepositoryPath);
+ repositoryManager.Initialize();
+ Environment.Repository.Initialize(repositoryManager, TaskManager);
+ repositoryManager.Start();
+ Environment.Repository.Start();
+ Logger.Trace($"Got a repository? {(Environment.Repository != null ? Environment.Repository.LocalPath : "null")}");
}
+ protected virtual void InitializeUI() {}
+ protected virtual void InitializationComplete() {}
private bool disposed = false;
+ private IOAuthCallbackManager oAuthCallbackManager;
+
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (disposed) return;
disposed = true;
- if (TaskManager != null) TaskManager.Dispose();
- if (repositoryManager != null) repositoryManager.Dispose();
+ if (ProcessManager != null)
+ {
+ ProcessManager.Stop();
+ }
+ if (TaskManager != null)
+ {
+ TaskManager.Dispose();
+ TaskManager = null;
+ }
+ if (repositoryManager != null)
+ {
+ repositoryManager.Dispose();
+ repositoryManager = null;
+ }
}
}
@@ -287,18 +381,30 @@ public void Dispose()
Dispose(true);
}
- public abstract IEnvironment Environment { get; }
-
+ public IEnvironment Environment { get; private set; }
public IPlatform Platform { get; protected set; }
public virtual IProcessEnvironment GitEnvironment { get; set; }
public IProcessManager ProcessManager { get; protected set; }
- public CancellationToken CancellationToken { get { return TaskManager.Token; } }
public ITaskManager TaskManager { get; protected set; }
public IGitClient GitClient { get; protected set; }
- public ISettings LocalSettings { get; protected set; }
- public ISettings SystemSettings { get; protected set; }
- public ISettings UserSettings { get; protected set; }
+ public ISettings LocalSettings { get { return Environment.LocalSettings; } }
+ public ISettings SystemSettings { get { return Environment.SystemSettings; } }
+ public ISettings UserSettings { get { return Environment.UserSettings; } }
public IUsageTracker UsageTracker { get; protected set; }
+
+ public IOAuthCallbackManager OAuthCallbackManager
+ {
+ get
+ {
+ if (oAuthCallbackManager == null)
+ {
+ oAuthCallbackManager = new OAuthCallbackManager();
+ }
+
+ return oAuthCallbackManager;
+ }
+ }
+
public bool IsBusy { get { return isBusy; } }
protected TaskScheduler UIScheduler { get; private set; }
protected SynchronizationContext SynchronizationContext { get; private set; }
diff --git a/src/GitHub.Api/Application/IApiClient.cs b/src/GitHub.Api/Application/IApiClient.cs
index 0ab28ceaf..09c79611f 100644
--- a/src/GitHub.Api/Application/IApiClient.cs
+++ b/src/GitHub.Api/Application/IApiClient.cs
@@ -1,19 +1,19 @@
-using System.Threading.Tasks;
-using System;
+using System;
namespace GitHub.Unity
{
- interface IApiClient
+ public interface IApiClient
{
HostAddress HostAddress { get; }
- UriString OriginalUrl { get; }
- Task CreateRepository(string name, string description, bool isPrivate,
+ void CreateRepository(string name, string description, bool isPrivate,
Action callback, string organization = null);
- Task GetOrganizations(Action onSuccess, Action onError = null);
- Task Login(string username, string password, Action need2faCode, Action result);
- Task ContinueLogin(LoginResult loginResult, string code);
- Task LoginAsync(string username, string password, Func need2faCode);
- Task Logout(UriString host);
- Task GetCurrentUser(Action onSuccess, Action onError = null);
+ void GetOrganizations(Action onSuccess, Action onError = null);
+ void Login(string username, string password, Action need2faCode, Action result);
+ void ContinueLogin(LoginResult loginResult, string code);
+ void LoginWithToken(string token, Action result);
+ ITask Logout(UriString host);
+ void GetCurrentUser(Action onSuccess, Action onError = null);
+ void GetEnterpriseServerMeta(Action onSuccess, Action onError = null);
+ void CreateOAuthToken(string code, Action result);
}
}
diff --git a/src/GitHub.Api/Application/IApplicationManager.cs b/src/GitHub.Api/Application/IApplicationManager.cs
index 4642101ed..ab82a27d3 100644
--- a/src/GitHub.Api/Application/IApplicationManager.cs
+++ b/src/GitHub.Api/Application/IApplicationManager.cs
@@ -6,7 +6,6 @@ namespace GitHub.Unity
{
public interface IApplicationManager : IDisposable
{
- CancellationToken CancellationToken { get; }
IEnvironment Environment { get; }
IPlatform Platform { get; }
IProcessEnvironment GitEnvironment { get; }
@@ -17,10 +16,12 @@ public interface IApplicationManager : IDisposable
ITaskManager TaskManager { get; }
IGitClient GitClient { get; }
IUsageTracker UsageTracker { get; }
+ IOAuthCallbackManager OAuthCallbackManager { get; }
bool IsBusy { get; }
- void Run(bool firstRun);
- void RestartRepository();
- ITask InitializeRepository();
+ void Run();
+ void InitializeRepository();
event Action OnProgress;
+ void SetupGit(GitInstaller.GitInstallationState state);
+ void RestartRepository();
}
-}
\ No newline at end of file
+}
diff --git a/src/GitHub.Api/Application/Organization.cs b/src/GitHub.Api/Application/Organization.cs
index e78849dd6..8deea7d99 100644
--- a/src/GitHub.Api/Application/Organization.cs
+++ b/src/GitHub.Api/Application/Organization.cs
@@ -1,8 +1,8 @@
namespace GitHub.Unity
{
- class Organization
+ public class Organization
{
public string Name { get; set; }
public string Login { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/GitHub.Api/Authentication/Credential.cs b/src/GitHub.Api/Authentication/Credential.cs
index f7c74d3a5..86e76c458 100644
--- a/src/GitHub.Api/Authentication/Credential.cs
+++ b/src/GitHub.Api/Authentication/Credential.cs
@@ -16,9 +16,10 @@ public Credential(UriString host, string username, string token)
this.Token = token;
}
- public void UpdateToken(string token)
+ public void Update(string token, string username)
{
this.Token = token;
+ this.Username = username;
}
public UriString Host { get; private set; }
diff --git a/src/GitHub.Api/Authentication/ICredentialManager.cs b/src/GitHub.Api/Authentication/ICredentialManager.cs
index 28742a6c7..94aef5a97 100644
--- a/src/GitHub.Api/Authentication/ICredentialManager.cs
+++ b/src/GitHub.Api/Authentication/ICredentialManager.cs
@@ -8,15 +8,14 @@ public interface ICredential : IDisposable
UriString Host { get; }
string Username { get; }
string Token { get; }
- void UpdateToken(string token);
+ void Update(string token, string username);
}
public interface ICredentialManager
{
- Task Load(UriString host);
- Task Save(ICredential cred);
- Task Delete(UriString host);
+ ICredential Load(UriString host);
+ void Save(ICredential cred);
+ void Delete(UriString host);
bool HasCredentials();
- ICredential CachedCredentials { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/GitHub.Api/Authentication/IKeychain.cs b/src/GitHub.Api/Authentication/IKeychain.cs
index 4aa1bcb97..4a14e1e2f 100644
--- a/src/GitHub.Api/Authentication/IKeychain.cs
+++ b/src/GitHub.Api/Authentication/IKeychain.cs
@@ -1,23 +1,19 @@
using System;
using System.Collections.Generic;
-using System.Threading.Tasks;
namespace GitHub.Unity
{
public interface IKeychain
{
IKeychainAdapter Connect(UriString host);
- Task Load(UriString host);
- Task Clear(UriString host, bool deleteFromCredentialManager);
- Task Save(UriString host);
- void UpdateToken(UriString host, string token);
- void SetCredentials(ICredential credential);
+ IKeychainAdapter LoadFromSystem(UriString host);
+ void Clear(UriString host, bool deleteFromCredentialManager);
+ void SaveToSystem(UriString host);
void Initialize();
Connection[] Connections { get; }
IList Hosts { get; }
bool HasKeys { get; }
- void SetToken(UriString host, string token);
event Action ConnectionsChanged;
}
-}
\ No newline at end of file
+}
diff --git a/src/GitHub.Api/Authentication/ILoginManager.cs b/src/GitHub.Api/Authentication/ILoginManager.cs
index 32defff79..c78112c0e 100644
--- a/src/GitHub.Api/Authentication/ILoginManager.cs
+++ b/src/GitHub.Api/Authentication/ILoginManager.cs
@@ -8,23 +8,32 @@ namespace GitHub.Unity
interface ILoginManager
{
///
- /// Attempts to log into a GitHub server.
+ /// Attempts to log into a GitHub server with a username and password.
///
- ///
+ /// The host.
/// The username.
/// The password.
/// The logged in user.
///
/// The login authorization failed.
///
- Task Login(UriString host, string username, string password);
- Task ContinueLogin(LoginResultData loginResultData, string twofacode);
+ LoginResultData Login(UriString host, string username, string password);
+
+ LoginResultData ContinueLogin(LoginResultData loginResultData, string twofacode);
///
/// Logs out of GitHub server.
///
/// The address of the server.
///
- Task Logout(UriString hostAddress);
+ ITask Logout(UriString hostAddress);
+
+ ///
+ /// Attempts to log into a GitHub server with a token.
+ ///
+ /// The host.
+ /// The token.
+ ///
+ bool LoginWithToken(UriString host, string token);
}
-}
\ No newline at end of file
+}
diff --git a/src/GitHub.Api/Authentication/Keychain.cs b/src/GitHub.Api/Authentication/Keychain.cs
index 2b3c933e2..c71d45cbe 100644
--- a/src/GitHub.Api/Authentication/Keychain.cs
+++ b/src/GitHub.Api/Authentication/Keychain.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Threading.Tasks;
using GitHub.Logging;
namespace GitHub.Unity
@@ -68,7 +67,7 @@ public bool Equals(Connection other)
}
}
- class Keychain : IKeychain
+ public class Keychain : IKeychain
{
const string ConnectionFile = "connections.json";
@@ -95,32 +94,36 @@ public Keychain(IEnvironment environment, ICredentialManager credentialManager)
public IKeychainAdapter Connect(UriString host)
{
+ Guard.ArgumentNotNull(host, nameof(host));
return FindOrCreateAdapter(host);
}
- public async Task Load(UriString host)
+ public IKeychainAdapter LoadFromSystem(UriString host)
{
- var keychainAdapter = FindOrCreateAdapter(host);
- var connection = GetConnection(host);
+ Guard.ArgumentNotNull(host, nameof(host));
- //logger.Trace($@"Loading KeychainAdapter Host:""{host}"" Cached Username:""{cachedConnection.Username}""");
-
- var keychainItem = await credentialManager.Load(host);
- if (keychainItem == null)
+ var keychainAdapter = Connect(host) as KeychainAdapter;
+ var credential = credentialManager.Load(host);
+ if (credential == null)
{
logger.Warning("Cannot load host from Credential Manager; removing from cache");
- await Clear(host, false);
+ Clear(host, false);
keychainAdapter = null;
}
else
{
- if (keychainItem.Username != connection.Username)
+ keychainAdapter.Set(credential);
+ var connection = GetConnection(host);
+ if (connection.Username == null)
{
- logger.Warning("Keychain Username:\"{0}\" does not match cached Username:\"{1}\"; Hopefully it works", keychainItem.Username, connection.Username);
+ connection.Username = credential.Username;
+ SaveConnectionsToDisk();
}
- //logger.Trace("Loaded from Credential Manager Host:\"{0}\" Username:\"{1}\"", keychainItem.Host, keychainItem.Username);
- keychainAdapter.Set(keychainItem);
+ if (credential.Username != connection.Username)
+ {
+ logger.Warning("Keychain Username:\"{0}\" does not match cached Username:\"{1}\"; Hopefully it works", credential.Username, connection.Username);
+ }
}
return keychainAdapter;
}
@@ -138,56 +141,35 @@ private KeychainAdapter FindOrCreateAdapter(UriString host)
public void Initialize()
{
- //logger.Trace("Initialize");
LoadConnectionsFromDisk();
}
- public async Task Clear(UriString host, bool deleteFromCredentialManager)
+ public void Clear(UriString host, bool deleteFromCredentialManager)
{
- //logger.Trace("Clear Host:{0}", host);
- //clear octokit credentials
- await RemoveCredential(host, deleteFromCredentialManager);
- RemoveConnection(host);
- }
+ Guard.ArgumentNotNull(host, nameof(host));
- public async Task Save(UriString host)
- {
- //logger.Trace("Save: {0}", host);
- var keychainAdapter = await AddCredential(host);
- AddConnection(new Connection(host, keychainAdapter.Credential.Username));
- }
+ RemoveConnection(host);
- public void SetCredentials(ICredential credential)
- {
- //logger.Trace("SetCredentials Host:{0}", credential.Host);
- var keychainAdapter = GetKeychainAdapter(credential.Host);
- keychainAdapter.Set(credential);
+ //clear octokit credentials
+ RemoveCredential(host, deleteFromCredentialManager);
}
- public void SetToken(UriString host, string token)
+ public void SaveToSystem(UriString host)
{
- //logger.Trace("SetToken Host:{0}", host);
- var keychainAdapter = GetKeychainAdapter(host);
- keychainAdapter.UpdateToken(token);
- }
+ Guard.ArgumentNotNull(host, nameof(host));
- public void UpdateToken(UriString host, string token)
- {
- //logger.Trace("UpdateToken Host:{0}", host);
- var keychainAdapter = GetKeychainAdapter(host);
- var keychainItem = keychainAdapter.Credential;
- keychainItem.UpdateToken(token);
+ var keychainAdapter = AddCredential(host);
+ AddConnection(new Connection(host, keychainAdapter.Credential.Username));
}
private void LoadConnectionsFromDisk()
{
- //logger.Trace("ReadCacheFromDisk Path:{0}", cachePath.ToString());
if (cachePath.FileExists())
{
var json = cachePath.ReadAllText();
try
{
- var conns = SimpleJson.DeserializeObject(json);
+ var conns = json.FromJson();
UpdateConnections(conns);
}
catch (IOException ex)
@@ -205,10 +187,9 @@ private void LoadConnectionsFromDisk()
private void SaveConnectionsToDisk(bool raiseChangedEvent = true)
{
- //logger.Trace("WriteCacheToDisk Count:{0} Path:{1}", connectionCache.Count, cachePath.ToString());
try
{
- var json = SimpleJson.SerializeObject(connections.Values.ToArray());
+ var json = connections.Values.ToJson();
cachePath.WriteAllText(json);
}
catch (IOException ex)
@@ -234,7 +215,7 @@ private KeychainAdapter GetKeychainAdapter(UriString host)
return credentialAdapter;
}
- private async Task AddCredential(UriString host)
+ private KeychainAdapter AddCredential(UriString host)
{
var keychainAdapter = GetKeychainAdapter(host);
if (string.IsNullOrEmpty(keychainAdapter.Credential.Token))
@@ -243,12 +224,12 @@ private async Task AddCredential(UriString host)
}
// saves credential in git credential manager (host, username, token)
- await credentialManager.Delete(host);
- await credentialManager.Save(keychainAdapter.Credential);
+ credentialManager.Delete(host);
+ credentialManager.Save(keychainAdapter.Credential);
return keychainAdapter;
}
- private async Task RemoveCredential(UriString host, bool deleteFromCredentialManager)
+ private void RemoveCredential(UriString host, bool deleteFromCredentialManager)
{
KeychainAdapter k;
if (keychainAdapters.TryGetValue(host, out k))
@@ -259,18 +240,18 @@ private async Task RemoveCredential(UriString host, bool deleteFromCredentialMan
if (deleteFromCredentialManager)
{
- await credentialManager.Delete(host);
+ credentialManager.Delete(host);
}
}
private Connection GetConnection(UriString host)
{
if (!connections.ContainsKey(host))
- throw new ArgumentException($"{host} is not found", nameof(host));
+ return AddConnection(new Connection(host, null));
return connections[host];
}
- private void AddConnection(Connection connection)
+ private Connection AddConnection(Connection connection)
{
// create new connection in the connection cache for this host
if (connections.ContainsKey(connection.Host))
@@ -278,6 +259,7 @@ private void AddConnection(Connection connection)
else
connections.Add(connection.Host, connection);
SaveConnectionsToDisk();
+ return connection;
}
private void RemoveConnection(UriString host)
@@ -290,15 +272,6 @@ private void RemoveConnection(UriString host)
}
}
- private void RemoveAllConnections()
- {
- if (connections.Count > 0)
- {
- connections.Clear();
- SaveConnectionsToDisk();
- }
- }
-
private void UpdateConnections(Connection[] conns)
{
var updated = false;
@@ -326,4 +299,4 @@ private void UpdateConnections(Connection[] conns)
public IList Hosts => connections.Keys.ToArray();
public bool HasKeys => connections.Any();
}
-}
\ No newline at end of file
+}
diff --git a/src/GitHub.Api/Authentication/KeychainAdapter.cs b/src/GitHub.Api/Authentication/KeychainAdapter.cs
index 3fdde60b3..7a0c5d3aa 100644
--- a/src/GitHub.Api/Authentication/KeychainAdapter.cs
+++ b/src/GitHub.Api/Authentication/KeychainAdapter.cs
@@ -1,6 +1,6 @@
namespace GitHub.Unity
{
- class KeychainAdapter : IKeychainAdapter
+ public class KeychainAdapter : IKeychainAdapter
{
public ICredential Credential { get; private set; }
@@ -9,9 +9,9 @@ public void Set(ICredential credential)
Credential = credential;
}
- public void UpdateToken(string token)
+ public void Update(string token, string username)
{
- Credential.UpdateToken(token);
+ Credential.Update(token, username);
}
public void Clear()
@@ -23,5 +23,8 @@ public void Clear()
public interface IKeychainAdapter
{
ICredential Credential { get; }
+ void Set(ICredential credential);
+ void Update(string token, string username);
+ void Clear();
}
}
diff --git a/src/GitHub.Api/Authentication/LoginManager.cs b/src/GitHub.Api/Authentication/LoginManager.cs
index ce0a85ad2..6f892b659 100644
--- a/src/GitHub.Api/Authentication/LoginManager.cs
+++ b/src/GitHub.Api/Authentication/LoginManager.cs
@@ -1,7 +1,5 @@
using System;
-using System.Linq;
-using System.Net;
-using System.Threading.Tasks;
+using System.Text;
using GitHub.Logging;
namespace GitHub.Unity
@@ -18,49 +16,62 @@ public enum LoginResultCodes
///
/// Provides services for logging into a GitHub server.
///
- class LoginManager : ILoginManager
+ public class LoginManager : ILoginManager
{
private readonly ILogging logger = LogHelper.GetLogger();
private readonly IKeychain keychain;
- private readonly string clientId;
- private readonly string clientSecret;
private readonly IProcessManager processManager;
private readonly ITaskManager taskManager;
- private readonly NPath? nodeJsExecutablePath;
- private readonly NPath? octorunScript;
+ private readonly IEnvironment environment;
///
/// Initializes a new instance of the class.
///
///
- /// The application's client API ID.
- /// The application's client API secret.
///
///
///
///
public LoginManager(
- IKeychain keychain,
- string clientId,
- string clientSecret,
- IProcessManager processManager = null, ITaskManager taskManager = null, NPath? nodeJsExecutablePath = null, NPath? octorunScript = null)
+ IKeychain keychain, IProcessManager processManager, ITaskManager taskManager,
+ IEnvironment environment)
{
Guard.ArgumentNotNull(keychain, nameof(keychain));
- Guard.ArgumentNotNullOrWhiteSpace(clientId, nameof(clientId));
- Guard.ArgumentNotNullOrWhiteSpace(clientSecret, nameof(clientSecret));
this.keychain = keychain;
- this.clientId = clientId;
- this.clientSecret = clientSecret;
this.processManager = processManager;
this.taskManager = taskManager;
- this.nodeJsExecutablePath = nodeJsExecutablePath;
- this.octorunScript = octorunScript;
+ this.environment = environment;
+ }
+
+ public bool LoginWithToken(UriString host, string token)
+ {
+ Guard.ArgumentNotNull(host, nameof(host));
+ Guard.ArgumentNotNullOrWhiteSpace(token, nameof(token));
+
+ var keychainAdapter = keychain.Connect(host);
+ keychainAdapter.Set(new Credential(host, "[token]", token));
+
+ try
+ {
+ var username = RetrieveUsername(token, host);
+ keychainAdapter.Update(token, username);
+ keychain.SaveToSystem(host);
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ logger.Warning(e, "Login Exception");
+
+ keychain.Clear(host, false);
+ return false;
+ }
}
///
- public async Task Login(
+ public LoginResultData Login(
UriString host,
string username,
string password)
@@ -71,12 +82,12 @@ public async Task Login(
// Start by saving the username and password, these will be used by the `IGitHubClient`
// until an authorization token has been created and acquired:
- keychain.Connect(host);
- keychain.SetCredentials(new Credential(host, username, password));
+ var keychainAdapter = keychain.Connect(host);
+ keychainAdapter.Set(new Credential(host, username, password));
try
{
- var loginResultData = await TryLogin(host, username, password);
+ var loginResultData = TryLogin(host, username, password);
if (loginResultData.Code == LoginResultCodes.Success || loginResultData.Code == LoginResultCodes.CodeRequired)
{
if (string.IsNullOrEmpty(loginResultData.Token))
@@ -84,8 +95,14 @@ public async Task Login(
throw new InvalidOperationException("Returned token is null or empty");
}
- keychain.SetToken(host, loginResultData.Token);
- await keychain.Save(host);
+ keychainAdapter.Update(loginResultData.Token, username);
+
+ if (loginResultData.Code == LoginResultCodes.Success)
+ {
+ username = RetrieveUsername(loginResultData.Token, host);
+ keychainAdapter.Update(loginResultData.Token, username);
+ keychain.SaveToSystem(host);
+ }
return loginResultData;
}
@@ -96,21 +113,23 @@ public async Task Login(
{
logger.Warning(e, "Login Exception");
- await keychain.Clear(host, false);
+ keychain.Clear(host, false);
return new LoginResultData(LoginResultCodes.Failed, Localization.LoginFailed, host);
}
}
- public async Task ContinueLogin(LoginResultData loginResultData, string twofacode)
+ public LoginResultData ContinueLogin(LoginResultData loginResultData, string twofacode)
{
var host = loginResultData.Host;
var keychainAdapter = keychain.Connect(host);
+ if (keychainAdapter.Credential == null) {
+ return new LoginResultData(LoginResultCodes.Failed, Localization.LoginFailed, host);
+ }
var username = keychainAdapter.Credential.Username;
var password = keychainAdapter.Credential.Token;
try
{
- logger.Trace("2FA Continue");
- loginResultData = await TryLogin(host, username, password, twofacode);
+ loginResultData = TryLogin(host, username, password, twofacode);
if (loginResultData.Code == LoginResultCodes.Success)
{
@@ -119,8 +138,10 @@ public async Task ContinueLogin(LoginResultData loginResultData
throw new InvalidOperationException("Returned token is null or empty");
}
- keychain.SetToken(host, loginResultData.Token);
- await keychain.Save(host);
+ keychainAdapter.Update(loginResultData.Token, username);
+ username = RetrieveUsername(loginResultData.Token, host);
+ keychainAdapter.Update(loginResultData.Token, username);
+ keychain.SaveToSystem(host);
return loginResultData;
}
@@ -131,42 +152,42 @@ public async Task ContinueLogin(LoginResultData loginResultData
{
logger.Warning(e, "Login Exception");
- await keychain.Clear(host, false);
+ keychain.Clear(host, false);
return new LoginResultData(LoginResultCodes.Failed, Localization.LoginFailed, host);
}
}
///
- public async Task Logout(UriString hostAddress)
+ public ITask Logout(UriString hostAddress)
{
Guard.ArgumentNotNull(hostAddress, nameof(hostAddress));
-
- await new ActionTask(keychain.Clear(hostAddress, true)).StartAwait();
+ return taskManager.Run(() => keychain.Clear(hostAddress, true), "Signing out");
}
- private async Task TryLogin(
+ private LoginResultData TryLogin(
UriString host,
string username,
string password,
string code = null
)
{
- if (!nodeJsExecutablePath.HasValue)
+ var hasTwoFactorCode = code != null;
+
+ var command = new StringBuilder("login");
+
+ if (hasTwoFactorCode)
{
- throw new InvalidOperationException("nodeJsExecutablePath must be set");
+ command.Append(" --twoFactor");
}
- if (!octorunScript.HasValue)
+ if (!HostAddress.IsGitHubDotCom(host))
{
- throw new InvalidOperationException("octorunScript must be set");
+ command.Append(" -h ");
+ command.Append(host.Host);
}
- var hasTwoFactorCode = code != null;
-
- var arguments = hasTwoFactorCode ? "login --twoFactor" : "login";
- var loginTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath.Value, octorunScript.Value,
- arguments, ApplicationInfo.ClientId, ApplicationInfo.ClientSecret);
- loginTask.Configure(processManager, workingDirectory: octorunScript.Value.Parent.Parent, withInput: true);
+ var loginTask = new OctorunTask(taskManager.Token, environment, command.ToString());
+ loginTask.Configure(processManager, withInput: true);
loginTask.OnStartProcess += proc =>
{
proc.StandardInput.WriteLine(username);
@@ -178,7 +199,7 @@ private async Task TryLogin(
proc.StandardInput.Close();
};
- var ret = await loginTask.StartAwait();
+ var ret = loginTask.RunSynchronously();
if (ret.IsSuccess)
{
@@ -195,9 +216,24 @@ private async Task TryLogin(
return new LoginResultData(LoginResultCodes.Failed, ret.GetApiErrorMessage() ?? "Failed.", host);
}
+
+ private string RetrieveUsername(string token, UriString host)
+ {
+ var command = HostAddress.IsGitHubDotCom(host) ? "validate" : "validate -h " + host.Host;
+ var octorunTask = new OctorunTask(taskManager.Token, environment, command, token)
+ .Configure(processManager);
+
+ var validateResult = octorunTask.RunSynchronously();
+ if (!validateResult.IsSuccess)
+ {
+ throw new InvalidOperationException("Authentication validation failed");
+ }
+
+ return validateResult.Output[1];
+ }
}
- class LoginResultData
+ public class LoginResultData
{
public LoginResultCodes Code;
public string Message;
diff --git a/src/GitHub.Api/Authentication/OAuthCallbackManager.cs b/src/GitHub.Api/Authentication/OAuthCallbackManager.cs
new file mode 100644
index 000000000..b5b75b094
--- /dev/null
+++ b/src/GitHub.Api/Authentication/OAuthCallbackManager.cs
@@ -0,0 +1,106 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web;
+using GitHub.Logging;
+
+namespace GitHub.Unity
+{
+ public interface IOAuthCallbackManager
+ {
+ event Action OnCallback;
+ bool IsRunning { get; }
+ void Start();
+ void Stop();
+ }
+
+ public class OAuthCallbackManager : IOAuthCallbackManager
+ {
+ const int CallbackPort = 42424;
+ public static readonly Uri CallbackUrl = new Uri($"http://localhost:{CallbackPort}/callback");
+
+ private static readonly ILogging logger = LogHelper.GetLogger();
+ private static readonly object _lock = new object();
+
+
+ private readonly CancellationTokenSource cancelSource;
+
+ private HttpListener httpListener;
+ public bool IsRunning { get; private set; }
+
+ public event Action OnCallback;
+
+ public OAuthCallbackManager()
+ {
+ cancelSource = new CancellationTokenSource();
+ }
+
+ public void Start()
+ {
+ if (!IsRunning)
+ {
+ lock(_lock)
+ {
+ if (!IsRunning)
+ {
+ logger.Trace("Starting");
+
+ httpListener = new HttpListener();
+ httpListener.Prefixes.Add(CallbackUrl.AbsoluteUri + "/");
+ httpListener.Start();
+ Task.Factory.StartNew(Listen, cancelSource.Token);
+ IsRunning = true;
+ }
+ }
+ }
+ }
+
+ public void Stop()
+ {
+ logger.Trace("Stopping");
+ cancelSource.Cancel();
+ }
+
+ private void Listen()
+ {
+ try
+ {
+ using (httpListener)
+ {
+ using (cancelSource.Token.Register(httpListener.Stop))
+ {
+ while (true)
+ {
+ var context = httpListener.GetContext();
+ var queryParts = HttpUtility.ParseQueryString(context.Request.Url.Query);
+
+ var state = queryParts["state"];
+ var code = queryParts["code"];
+
+ logger.Trace("OnCallback: {0}", state);
+ if (OnCallback != null)
+ {
+ OnCallback(state, code);
+ }
+
+ context.Response.StatusCode = 200;
+ context.Response.Close();
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.Trace(ex.Message);
+ }
+ finally
+ {
+ IsRunning = false;
+ httpListener = null;
+ }
+ }
+ }
+}
diff --git a/src/GitHub.Api/Cache/CacheContainer.cs b/src/GitHub.Api/Cache/CacheContainer.cs
index f8de8c283..2c07ff867 100644
--- a/src/GitHub.Api/Cache/CacheContainer.cs
+++ b/src/GitHub.Api/Cache/CacheContainer.cs
@@ -30,9 +30,7 @@ public void InvalidateAll()
{
foreach (var cache in caches.Values)
{
- // force an invalidation if the cache is valid, otherwise it will do it on its own
- if (cache.Value.ValidateData())
- cache.Value.InvalidateData();
+ cache.Value.InvalidateData();
}
}
@@ -92,6 +90,7 @@ public void Dispose()
public IBranchCache BranchCache { get { return (IBranchCache)caches[CacheType.Branches].Value; } }
public IGitLogCache GitLogCache { get { return (IGitLogCache)caches[CacheType.GitLog].Value; } }
+ public IGitFileLogCache GitFileLogCache { get { return (IGitFileLogCache)caches[CacheType.GitFileLog].Value; } }
public IGitAheadBehindCache GitTrackingStatusCache { get { return (IGitAheadBehindCache)caches[CacheType.GitAheadBehind].Value; } }
public IGitStatusCache GitStatusEntriesCache { get { return (IGitStatusCache)caches[CacheType.GitStatus].Value; } }
public IGitLocksCache GitLocksCache { get { return (IGitLocksCache)caches[CacheType.GitLocks].Value; } }
@@ -164,7 +163,7 @@ public DateTimeOffset UpdatedTime
if (!updatedTimeValue.HasValue)
{
DateTimeOffset result;
- if (DateTimeOffset.TryParseExact(updatedTimeString, Constants.Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.None, out result))
+ if (DateTimeOffset.TryParseExact(updatedTimeString.ToEmptyIfNull(), Constants.Iso8601Formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out result))
{
updatedTimeValue = result;
}
diff --git a/src/GitHub.Api/Cache/CacheInterfaces.cs b/src/GitHub.Api/Cache/CacheInterfaces.cs
index 810db7774..ae815beac 100644
--- a/src/GitHub.Api/Cache/CacheInterfaces.cs
+++ b/src/GitHub.Api/Cache/CacheInterfaces.cs
@@ -9,6 +9,7 @@ public enum CacheType
RepositoryInfo,
Branches,
GitLog,
+ GitFileLog,
GitAheadBehind,
GitStatus,
GitLocks,
@@ -22,6 +23,7 @@ public interface ICacheContainer : IDisposable
IBranchCache BranchCache { get; }
IGitLogCache GitLogCache { get; }
+ IGitFileLogCache GitFileLogCache { get; }
IGitAheadBehindCache GitTrackingStatusCache { get; }
IGitStatusCache GitStatusEntriesCache { get; }
IGitLocksCache GitLocksCache { get; }
@@ -40,6 +42,7 @@ public interface IManagedCache
bool ValidateData();
void InvalidateData();
+ void ResetInvalidation();
DateTimeOffset LastUpdatedAt { get; }
CacheType CacheType { get; }
@@ -91,7 +94,7 @@ public interface IBranchCache : IManagedCache
ILocalConfigBranchDictionary LocalConfigBranches { get; }
IRemoteConfigBranchDictionary RemoteConfigBranches { get; }
IConfigRemoteDictionary ConfigRemotes { get; }
-
+
void SetRemotes(Dictionary remoteConfigs, Dictionary> configBranches, GitRemote[] gitRemotes, GitBranch[] gitBranches);
void SetLocals(Dictionary configBranches, GitBranch[] gitBranches);
}
@@ -102,6 +105,7 @@ public interface IRepositoryInfoCacheData
GitBranch? CurrentGitBranch { get; }
ConfigRemote? CurrentConfigRemote { get; }
ConfigBranch? CurrentConfigBranch { get; }
+ string CurrentHead { get; }
}
public interface IRepositoryInfoCache : IManagedCache, IRepositoryInfoCacheData, ICanUpdate
@@ -113,6 +117,11 @@ public interface IGitLogCache : IManagedCache
List Log { get; set; }
}
+ public interface IGitFileLogCache : IManagedCache
+ {
+ GitFileLog FileLog { get; set; }
+ }
+
public interface ICanUpdate
{
void UpdateData(T data);
diff --git a/src/GitHub.Api/Cache/CachingClasses.cs b/src/GitHub.Api/Cache/CachingClasses.cs
index 0f02368f6..a93f8c586 100644
--- a/src/GitHub.Api/Cache/CachingClasses.cs
+++ b/src/GitHub.Api/Cache/CachingClasses.cs
@@ -14,5 +14,6 @@ sealed class RepositoryInfoCacheData : IRepositoryInfoCacheData
public GitBranch? CurrentGitBranch { get; set; }
public ConfigRemote? CurrentConfigRemote { get; set; }
public ConfigBranch? CurrentConfigBranch { get; set; }
+ public string CurrentHead { get; set; }
}
}
diff --git a/src/GitHub.Api/Events/RepositoryWatcher.cs b/src/GitHub.Api/Events/RepositoryWatcher.cs
index 3ac349cd1..a9c852fc6 100644
--- a/src/GitHub.Api/Events/RepositoryWatcher.cs
+++ b/src/GitHub.Api/Events/RepositoryWatcher.cs
@@ -8,7 +8,7 @@
namespace GitHub.Unity
{
- interface IRepositoryWatcher : IDisposable
+ public interface IRepositoryWatcher : IDisposable
{
void Start();
void Stop();
@@ -23,13 +23,14 @@ interface IRepositoryWatcher : IDisposable
int CheckAndProcessEvents();
}
- class RepositoryWatcher : IRepositoryWatcher
+ public class RepositoryWatcher : IRepositoryWatcher
{
private readonly RepositoryPathConfiguration paths;
private readonly CancellationToken cancellationToken;
private readonly NPath[] ignoredPaths;
private readonly ManualResetEventSlim pauseEvent;
private NativeInterface nativeInterface;
+ private NativeInterface worktreeNativeInterface;
private bool running;
private int lastCountOfProcessedEvents = 0;
private bool processingEvents;
@@ -64,6 +65,11 @@ public void Initialize()
try
{
nativeInterface = new NativeInterface(pathsRepositoryPath);
+
+ if (paths.IsWorktree)
+ {
+ worktreeNativeInterface = new NativeInterface(paths.WorktreeDotGitPath);
+ }
}
catch (Exception ex)
{
@@ -81,6 +87,17 @@ public void Start()
Logger.Trace("Watching Path: \"{0}\"", paths.RepositoryPath.ToString());
+ if (paths.IsWorktree)
+ {
+ if (worktreeNativeInterface == null)
+ {
+ Logger.Warning("Worktree NativeInterface is null");
+ throw new InvalidOperationException("Worktree NativeInterface is null");
+ }
+
+ Logger.Trace("Watching Additional Path for Worktree: \"{0}\"", paths.WorktreeDotGitPath);
+ }
+
running = true;
pauseEvent.Reset();
Task.Factory.StartNew(WatcherLoop, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default);
@@ -89,12 +106,7 @@ public void Start()
public void Stop()
{
if (!running)
- {
- //Logger.Warning("Watcher already stopped");
return;
- }
-
- //Logger.Trace("Stopping watcher");
running = false;
pauseEvent.Set();
@@ -102,8 +114,6 @@ public void Stop()
private void WatcherLoop()
{
- //Logger.Trace("Starting watcher");
-
while (running)
{
if (cancellationToken.IsCancellationRequested)
@@ -136,9 +146,16 @@ public int CheckAndProcessEvents()
var fileEvents = nativeInterface.GetEvents();
if (fileEvents.Length > 0)
{
- //Logger.Trace("Handling {0} Events", fileEvents.Length);
processedEventCount = ProcessEvents(fileEvents);
- //Logger.Trace("Processed {0} Events", processedEventCount);
+ }
+
+ if (worktreeNativeInterface != null)
+ {
+ fileEvents = worktreeNativeInterface.GetEvents();
+ if (fileEvents.Length > 0)
+ {
+ processedEventCount = processedEventCount + ProcessEvents(fileEvents);
+ }
}
lastCountOfProcessedEvents = processedEventCount;
@@ -164,13 +181,11 @@ private int ProcessEvents(Event[] fileEvents)
break;
}
- //Logger.Trace(fileEvent.Describe());
-
var eventDirectory = new NPath(fileEvent.Directory);
var fileA = eventDirectory.Combine(fileEvent.FileA);
// handling events in .git/*
- if (fileA.IsChildOf(paths.DotGitPath))
+ if (fileA.IsChildOf(paths.DotGitPath) || (paths.WorktreeDotGitPath.IsInitialized && fileA.IsChildOf(paths.WorktreeDotGitPath)))
{
if (!events.Contains(EventType.ConfigChanged) && fileA.Equals(paths.DotGitConfig))
{
@@ -215,28 +230,24 @@ private int FireEvents(HashSet events)
int eventsProcessed = 0;
if (events.Contains(EventType.ConfigChanged))
{
- //Logger.Trace("ConfigChanged");
ConfigChanged?.Invoke();
eventsProcessed++;
}
if (events.Contains(EventType.HeadChanged))
{
- //Logger.Trace("HeadChanged");
HeadChanged?.Invoke();
eventsProcessed++;
}
if (events.Contains(EventType.LocalBranchesChanged))
{
- //Logger.Trace("LocalBranchesChanged");
LocalBranchesChanged?.Invoke();
eventsProcessed++;
}
if (events.Contains(EventType.RemoteBranchesChanged))
{
- //Logger.Trace("RemoteBranchesChanged");
RemoteBranchesChanged?.Invoke();
eventsProcessed++;
}
@@ -245,7 +256,6 @@ private int FireEvents(HashSet events)
{
if (!events.Contains(EventType.RepositoryChanged))
{
- //Logger.Trace("IndexChanged");
IndexChanged?.Invoke();
eventsProcessed++;
}
@@ -253,14 +263,12 @@ private int FireEvents(HashSet events)
if (events.Contains(EventType.RepositoryChanged))
{
- //Logger.Trace("RepositoryChanged");
RepositoryChanged?.Invoke();
eventsProcessed++;
}
if (events.Contains(EventType.RepositoryCommitted))
{
- //Logger.Trace("RepositoryCommitted");
RepositoryCommitted?.Invoke();
eventsProcessed++;
}
diff --git a/src/GitHub.Api/Extensions/EnvironmentExtensions.cs b/src/GitHub.Api/Extensions/EnvironmentExtensions.cs
index 6baff476c..ca69c7d81 100644
--- a/src/GitHub.Api/Extensions/EnvironmentExtensions.cs
+++ b/src/GitHub.Api/Extensions/EnvironmentExtensions.cs
@@ -1,4 +1,7 @@
using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
namespace GitHub.Unity
{
@@ -41,5 +44,15 @@ public static NPath GetAssetPath(this IEnvironment environment, NPath path)
return repositoryPath.Combine(path).MakeAbsolute().RelativeTo(projectPath);
}
+
+ public static IEnumerable ToNPathList(this string envPath, IEnvironment environment)
+ {
+ return envPath
+ .Split(Path.PathSeparator)
+ .Where(x => x != null)
+ .Select(x => environment.ExpandEnvironmentVariables(x.Trim('"', '\'')))
+ .Where(x => !String.IsNullOrEmpty(x))
+ .Select(x => x.ToNPath());
+ }
}
}
diff --git a/src/GitHub.Api/Extensions/ListExtensions.cs b/src/GitHub.Api/Extensions/ListExtensions.cs
index 839f58d78..bc20ed376 100644
--- a/src/GitHub.Api/Extensions/ListExtensions.cs
+++ b/src/GitHub.Api/Extensions/ListExtensions.cs
@@ -12,7 +12,7 @@ public static string Join(this IEnumerable list, string separator)
return null;
return String.Join(separator, list.Select(x => x?.ToString()).ToArray());
}
-
+
public static IEnumerable> Spool(this IEnumerable items, int spoolLength)
{
var currentSpoolLength = 0;
@@ -40,5 +40,15 @@ public static IEnumerable> Spool(this IEnumerable items, int
yield return currentList;
}
}
+
+ public static T[] Append(this T[] array, T item)
+ {
+ if (array == null)
+ return new T[] { item };
+ var ret = new T[array.Length];
+ array.CopyTo(ret, 0);
+ ret[ret.Length - 1] = item;
+ return ret;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/GitHub.Api/Extensions/StringExtensions.cs b/src/GitHub.Api/Extensions/StringExtensions.cs
index 0f6eae53d..ebb2c4351 100644
--- a/src/GitHub.Api/Extensions/StringExtensions.cs
+++ b/src/GitHub.Api/Extensions/StringExtensions.cs
@@ -7,6 +7,22 @@
namespace GitHub.Unity
{
+ static class DateTimeOffsetExtensions
+ {
+ private const string Today = "Today";
+ private const string Yesterday = "Yesterday";
+
+ public static string CreateRelativeTime(this DateTimeOffset @from, DateTimeOffset to)
+ {
+ return String.Format("{0}, {1:HH}:{1:mm}",
+ @from.DayOfYear == to.DayOfYear
+ ? Today
+ : @from.DayOfYear == to.DayOfYear - 1
+ ? Yesterday
+ : @from.ToString("d MMM yyyy"), @from);
+ }
+ }
+
static class StringExtensions
{
public static bool Contains(this string s, string expectedSubstring, StringComparison comparison)
@@ -24,6 +40,11 @@ public static string ToNullIfEmpty(this string s)
return String.IsNullOrEmpty(s) ? null : s;
}
+ public static string ToEmptyIfNull(this string s)
+ {
+ return String.IsNullOrEmpty(s) ? String.Empty : s;
+ }
+
public static bool StartsWith(this string s, char c)
{
if (String.IsNullOrEmpty(s)) return false;
@@ -82,24 +103,6 @@ public static string JoinForAppending(string separator, IEnumerable valu
: string.Empty;
}
- public static string RemoveSurroundingQuotes(this string s)
- {
- Guard.ArgumentNotNull(s, "string");
-
- if (s.Length < 2)
- return s;
-
- var quoteCharacters = new[] { '"', '\'' };
- char firstCharacter = s[0];
- if (!quoteCharacters.Contains(firstCharacter))
- return s;
-
- if (firstCharacter != s[s.Length - 1])
- return s;
-
- return s.Substring(1, s.Length - 2);
- }
-
public static string RightAfter(this string s, char search)
{
if (s == null) return null;
diff --git a/src/GitHub.Api/Git/FailureSeverity.cs b/src/GitHub.Api/Git/FailureSeverity.cs
index fdaa58345..3d34f95ef 100644
--- a/src/GitHub.Api/Git/FailureSeverity.cs
+++ b/src/GitHub.Api/Git/FailureSeverity.cs
@@ -1,8 +1,8 @@
namespace GitHub.Unity
{
- enum FailureSeverity
+ public enum FailureSeverity
{
Moderate,
Critical
};
-}
\ No newline at end of file
+}
diff --git a/src/GitHub.Api/Git/GitBranch.cs b/src/GitHub.Api/Git/GitBranch.cs
index 857212810..ecb6fede9 100644
--- a/src/GitHub.Api/Git/GitBranch.cs
+++ b/src/GitHub.Api/Git/GitBranch.cs
@@ -10,12 +10,12 @@ public struct GitBranch
public string name;
public string tracking;
- public GitBranch(string name, string tracking)
+ public GitBranch(string name, string tracking = null)
{
Guard.ArgumentNotNullOrWhiteSpace(name, "name");
this.name = name;
- this.tracking = tracking;
+ this.tracking = tracking ?? string.Empty;
}
public override int GetHashCode()
@@ -64,7 +64,7 @@ public bool Equals(GitBranch other)
public override string ToString()
{
- return $"{Name} Tracking? {Tracking}";
+ return $"{Name} Tracking? {Tracking ?? "[NULL]"}";
}
}
-}
\ No newline at end of file
+}
diff --git a/src/GitHub.Api/Git/GitClient.cs b/src/GitHub.Api/Git/GitClient.cs
index ebc337aaa..6aca3146b 100644
--- a/src/GitHub.Api/Git/GitClient.cs
+++ b/src/GitHub.Api/Git/GitClient.cs
@@ -1,101 +1,311 @@
using GitHub.Logging;
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading;
-using System.Threading.Tasks;
+using GitHub.Unity.Git.Tasks;
+using static GitHub.Unity.GitInstaller;
namespace GitHub.Unity
{
+ ///
+ /// Client that provides access to git functionality
+ ///
public interface IGitClient
{
- ITask ValidateGitInstall(NPath path, bool isCustomGit);
-
- ITask Init(IOutputProcessor processor = null);
-
- ITask LfsInstall(IOutputProcessor processor = null);
-
- ITask AheadBehindStatus(string gitRef, string otherRef,
- IOutputProcessor processor = null);
-
+ ///
+ /// Executes `git init` to initialize a git repo.
+ ///
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Init(IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git lfs install` to install LFS hooks.
+ ///
+ /// A custom output processor instance
+ /// String output of git command
+ ITask LfsInstall(IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git rev-list` to determine the ahead/behind status between two refs.
+ ///
+ /// Ref to compare
+ /// Ref to compare against
+ /// A custom output processor instance
+ /// output
+ ITask AheadBehindStatus(string gitRef, string otherRef, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git status` to determine the working directory status.
+ ///
+ /// A custom output processor instance
+ /// output
ITask Status(IOutputProcessor processor = null);
- ITask GetConfig(string key, GitConfigSource configSource,
- IOutputProcessor processor = null);
-
- ITask SetConfig(string key, string value, GitConfigSource configSource,
- IOutputProcessor processor = null);
-
+ ///
+ /// Executes `git config get` to get a configuration value.
+ ///
+ /// The configuration key to get
+ /// The config source (unspecified, local,user,global) to use
+ /// A custom output processor instance
+ /// String output of git command
+ ITask GetConfig(string key, GitConfigSource configSource, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git config set` to set a configuration value.
+ ///
+ /// The configuration key to set
+ /// The value to set
+ /// The config source (unspecified, local,user,global) to use
+ /// A custom output processor instance
+ /// String output of git command
+ ITask SetConfig(string key, string value, GitConfigSource configSource, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git config --unset` to remove a configuration value.
+ ///
+ /// The configuration key to remove
+ /// The config source (unspecified, local,user,global) to use
+ /// A custom output processor instance
+ /// String output of git command
+ ITask UnSetConfig(string key, GitConfigSource configSource, IOutputProcessor processor = null);
+
+ ///
+ /// Executes two `git config get` commands to get the git user and email.
+ ///
+ /// output
ITask GetConfigUserAndEmail();
- ITask> ListLocks(bool local,
- BaseOutputListProcessor processor = null);
-
- ITask Pull(string remote, string branch,
- IOutputProcessor processor = null);
-
- ITask Push(string remote, string branch,
- IOutputProcessor processor = null);
-
- ITask Revert(string changeset,
- IOutputProcessor processor = null);
-
- ITask Fetch(string remote,
- IOutputProcessor processor = null);
-
- ITask SwitchBranch(string branch,
- IOutputProcessor processor = null);
-
- ITask DeleteBranch(string branch, bool deleteUnmerged = false,
- IOutputProcessor processor = null);
-
- ITask CreateBranch(string branch, string baseBranch,
- IOutputProcessor processor = null);
-
- ITask RemoteAdd(string remote, string url,
- IOutputProcessor processor = null);
-
- ITask RemoteRemove(string remote,
- IOutputProcessor processor = null);
-
- ITask RemoteChange(string remote, string url,
- IOutputProcessor processor = null);
-
- ITask Commit(string message, string body,
- IOutputProcessor processor = null);
-
- ITask Add(IList files,
- IOutputProcessor processor = null);
-
+ ///
+ /// Executes `git lfs locks` to get a list of lfs locks from the git lfs server.
+ ///
+ ///
+ /// A custom output processor instance
+ /// of output
+ ITask> ListLocks(bool local, BaseOutputListProcessor processor = null);
+
+ ///
+ /// Executes `git pull` to perform a pull operation.
+ ///
+ /// The remote to pull from
+ /// The branch to pull
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Pull(string remote, string branch, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git push` to perform a push operation.
+ ///
+ /// The remote to push to
+ /// The branch to push
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Push(string remote, string branch, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git revert` to perform a revert operation.
+ ///
+ /// The changeset to revert
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Revert(string changeset, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git fetch` to perform a fetch operation.
+ ///
+ /// The remote to fetch from
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Fetch(string remote, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git checkout` to switch branches.
+ ///
+ /// The branch to checkout
+ /// A custom output processor instance
+ /// String output of git command
+ ITask SwitchBranch(string branch, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git branch -d` to delete a branch.
+ ///
+ /// The branch to delete
+ /// The flag to indicate the branch should be deleted even if not merged
+ /// A custom output processor instance
+ /// String output of git command
+ ITask DeleteBranch(string branch, bool deleteUnmerged = false, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git branch` to create a branch.
+ ///
+ /// The name of branch to create
+ /// The name of branch to create from
+ /// A custom output processor instance
+ /// String output of git command
+ ITask CreateBranch(string branch, string baseBranch, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git remote add` to add a git remote.
+ ///
+ /// The remote to add
+ /// The url of the remote
+ /// A custom output processor instance
+ /// String output of git command
+ ITask RemoteAdd(string remote, string url, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git remote rm` to remove a git remote.
+ ///
+ /// The remote to remove
+ /// A custom output processor instance
+ /// String output of git command
+ ITask RemoteRemove(string remote, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git remote set-url` to change the url of a git remote.
+ ///
+ /// The remote to change
+ /// The url to change to
+ /// A custom output processor instance
+ /// String output of git command
+ ITask RemoteChange(string remote, string url, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git commit` to perform a commit operation.
+ ///
+ /// The commit message summary
+ /// The commit message body
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Commit(string message, string body, IOutputProcessor processor = null);
+
+ ///
+ /// Executes at least one `git add` command to add the list of files to the git index.
+ ///
+ /// The file to add
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Add(IList files, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git add -A` to add all files to the git index.
+ ///
+ /// A custom output processor instance
+ /// String output of git command
ITask AddAll(IOutputProcessor processor = null);
- ITask Discard(IList files,
- IOutputProcessor processor = null);
-
+ ///
+ /// Executes at least one `git checkout` command to discard changes to the list of files.
+ ///
+ /// The files to discard
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Discard(IList files, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git checkout -- .` to discard all changes in the working directory.
+ ///
+ /// A custom output processor instance
+ /// String output of git command
ITask DiscardAll(IOutputProcessor processor = null);
- ITask Remove(IList files,
- IOutputProcessor processor = null);
-
- ITask AddAndCommit(IList files, string message, string body,
- IOutputProcessor processor = null);
-
- ITask Lock(string file,
- IOutputProcessor processor = null);
-
- ITask Unlock(string file, bool force,
- IOutputProcessor processor = null);
-
+ ///
+ /// Executes at least one `git checkout` command to checkout files at the given changeset
+ ///
+ /// The md5 of the changeset
+ /// The files to check out
+ /// A custom output processor instance
+ /// String output of git command
+ ITask CheckoutVersion(string changeset, IList files, IOutputProcessor processor = null);
+
+ ///
+ /// Executes at least one `git reset HEAD` command to remove files from the git index.
+ ///
+ /// The files to remove
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Remove(IList files, IOutputProcessor processor = null);
+
+ ///
+ /// Executes at least one `git add` command to add the list of files to the git index. Followed by a `git commit` command to commit the changes.
+ ///
+ /// The files to add and commit
+ /// The commit message summary
+ /// The commit message body
+ /// A custom output processor instance
+ /// String output of git command
+ ITask AddAndCommit(IList files, string message, string body, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git lfs lock` to lock a file.
+ ///
+ /// The file to lock
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Lock(NPath file, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git lfs unlock` to unlock a file.
+ ///
+ /// The file to unlock
+ /// If force should be used
+ /// A custom output processor instance
+ /// String output of git command
+ ITask Unlock(NPath file, bool force, IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git log` to get the history of the current branch.
+ ///
+ /// A custom output processor instance
+ /// of output
ITask> Log(BaseOutputListProcessor processor = null);
- ITask Version(IOutputProcessor processor = null);
-
- ITask LfsVersion(IOutputProcessor processor = null);
-
+ ///
+ /// Executes `git log -- ` to get the history of a specific file.
+ ///
+ ///
+ /// A custom output processor instance
+ /// of output
+ ITask> LogFile(string file, BaseOutputListProcessor processor = null);
+
+ ///
+ /// Executes `git --version` to get the git version.
+ ///
+ /// A custom output processor instance
+ /// output
+ ITask Version(IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git lfs version` to get the git lfs version.
+ ///
+ /// A custom output processor instance
+ /// output
+ ITask LfsVersion(IOutputProcessor processor = null);
+
+ ///
+ /// Executes `git count-objects` to get the size of the git repo in kilobytes.
+ ///
+ /// A custom output processor instance
+ /// output
+ ITask CountObjects(IOutputProcessor processor = null);
+
+ ///