mirror of
https://github.com/beefytech/Beef.git
synced 2025-07-04 23:36:00 +02:00
Initial package management support
This commit is contained in:
parent
78138f5c5a
commit
4870c6fdd8
19 changed files with 2520 additions and 205 deletions
326
IDE/src/util/GitManager.bf
Normal file
326
IDE/src/util/GitManager.bf
Normal file
|
@ -0,0 +1,326 @@
|
|||
#pragma warning disable 168
|
||||
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
|
||||
namespace IDE.util;
|
||||
|
||||
class GitManager
|
||||
{
|
||||
public enum Error
|
||||
{
|
||||
Unknown
|
||||
}
|
||||
|
||||
public class GitInstance : RefCounted
|
||||
{
|
||||
public class TagInfo
|
||||
{
|
||||
public String mHash ~ delete _;
|
||||
public String mTag ~ delete _;
|
||||
}
|
||||
|
||||
public GitManager mGitManager;
|
||||
public bool mFailed;
|
||||
public bool mDone;
|
||||
public bool mStarted;
|
||||
public bool mRemoved;
|
||||
|
||||
public String mArgs ~ delete _;
|
||||
public String mPath ~ delete _;
|
||||
public float mProgress;
|
||||
public float mProgressRecv;
|
||||
public float mProgressDeltas;
|
||||
public float mProgressFiles;
|
||||
|
||||
public Stopwatch mStopwatch = new .()..Start() ~ delete _;
|
||||
|
||||
public SpawnedProcess mProcess ~ delete _;
|
||||
public Monitor mMonitor = new .() ~ delete _;
|
||||
public List<String> mDeferredOutput = new .() ~ DeleteContainerAndItems!(_);
|
||||
public List<TagInfo> mTagInfos = new .() ~ DeleteContainerAndItems!(_);
|
||||
|
||||
public Thread mOutputThread ~ delete _;
|
||||
public Thread mErrorThread ~ delete _;
|
||||
|
||||
public this(GitManager gitManager)
|
||||
{
|
||||
mGitManager = gitManager;
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
IDEUtils.SafeKill(mProcess);
|
||||
mOutputThread?.Join();
|
||||
mErrorThread?.Join();
|
||||
|
||||
if (!mRemoved)
|
||||
mGitManager.mGitInstances.Remove(this);
|
||||
}
|
||||
|
||||
public void Init(StringView args, StringView path)
|
||||
{
|
||||
mArgs = new .(args);
|
||||
if (path != default)
|
||||
mPath = new .(path);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (mStarted)
|
||||
return;
|
||||
mStarted = true;
|
||||
|
||||
ProcessStartInfo psi = scope ProcessStartInfo();
|
||||
|
||||
String gitPath = scope .();
|
||||
#if BF_PLATFORM_WINDOWS
|
||||
Path.GetAbsolutePath(gApp.mInstallDir, "git/cmd/git.exe", gitPath);
|
||||
if (!File.Exists(gitPath))
|
||||
gitPath.Clear();
|
||||
|
||||
if (gitPath.IsEmpty)
|
||||
{
|
||||
Path.GetAbsolutePath(gApp.mInstallDir, "../../bin/git/cmd/git.exe", gitPath);
|
||||
if (!File.Exists(gitPath))
|
||||
gitPath.Clear();
|
||||
}
|
||||
|
||||
if (gitPath.IsEmpty)
|
||||
{
|
||||
Path.GetAbsolutePath(gApp.mInstallDir, "../../../bin/git/cmd/git.exe", gitPath);
|
||||
if (!File.Exists(gitPath))
|
||||
gitPath.Clear();
|
||||
}
|
||||
#endif
|
||||
if (gitPath.IsEmpty)
|
||||
gitPath.Set("git");
|
||||
|
||||
psi.SetFileName(gitPath);
|
||||
psi.SetArguments(mArgs);
|
||||
if (mPath != null)
|
||||
psi.SetWorkingDirectory(mPath);
|
||||
psi.UseShellExecute = false;
|
||||
psi.RedirectStandardError = true;
|
||||
psi.RedirectStandardOutput = true;
|
||||
psi.CreateNoWindow = true;
|
||||
|
||||
mProcess = new SpawnedProcess();
|
||||
if (mProcess.Start(psi) case .Err)
|
||||
{
|
||||
gApp.OutputErrorLine("Failed to execute Git");
|
||||
mFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
mOutputThread = new Thread(new => ReadOutputThread);
|
||||
mOutputThread.Start(false);
|
||||
|
||||
mErrorThread = new Thread(new => ReadErrorThread);
|
||||
mErrorThread.Start(false);
|
||||
}
|
||||
|
||||
public void ReadOutputThread()
|
||||
{
|
||||
FileStream fileStream = scope FileStream();
|
||||
if (mProcess.AttachStandardOutput(fileStream) case .Err)
|
||||
return;
|
||||
StreamReader streamReader = scope StreamReader(fileStream, null, false, 4096);
|
||||
|
||||
int count = 0;
|
||||
while (true)
|
||||
{
|
||||
count++;
|
||||
var buffer = scope String();
|
||||
if (streamReader.ReadLine(buffer) case .Err)
|
||||
break;
|
||||
using (mMonitor.Enter())
|
||||
{
|
||||
mDeferredOutput.Add(new .(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadErrorThread()
|
||||
{
|
||||
FileStream fileStream = scope FileStream();
|
||||
if (mProcess.AttachStandardError(fileStream) case .Err)
|
||||
return;
|
||||
StreamReader streamReader = scope StreamReader(fileStream, null, false, 4096);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var buffer = scope String();
|
||||
if (streamReader.ReadLine(buffer) case .Err)
|
||||
break;
|
||||
|
||||
using (mMonitor.Enter())
|
||||
{
|
||||
//mDeferredOutput.Add(new $"{mStopwatch.ElapsedMilliseconds / 1000.0:0.0}: {buffer}");
|
||||
mDeferredOutput.Add(new .(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
using (mMonitor.Enter())
|
||||
{
|
||||
while (!mDeferredOutput.IsEmpty)
|
||||
{
|
||||
var line = mDeferredOutput.PopFront();
|
||||
defer delete line;
|
||||
//Debug.WriteLine($"GIT: {line}");
|
||||
|
||||
if (line.StartsWith("Cloning into "))
|
||||
{
|
||||
// May be starting a submodule
|
||||
mProgressRecv = 0;
|
||||
mProgressDeltas = 0;
|
||||
mProgressFiles = 0;
|
||||
}
|
||||
|
||||
if (line.StartsWith("remote: Counting objects"))
|
||||
{
|
||||
mProgressRecv = 0.001f;
|
||||
}
|
||||
|
||||
if (line.StartsWith("Receiving objects: "))
|
||||
{
|
||||
var pctStr = line.Substring("Receiving objects: ".Length, 3)..Trim();
|
||||
mProgressRecv = float.Parse(pctStr).GetValueOrDefault() / 100.0f;
|
||||
}
|
||||
|
||||
if (line.StartsWith("Resolving deltas: "))
|
||||
{
|
||||
var pctStr = line.Substring("Resolving deltas: ".Length, 3)..Trim();
|
||||
mProgressDeltas = float.Parse(pctStr).GetValueOrDefault() / 100.0f;
|
||||
mProgressRecv = 1.0f;
|
||||
}
|
||||
|
||||
if (line.StartsWith("Updating files: "))
|
||||
{
|
||||
var pctStr = line.Substring("Updating files: ".Length, 3)..Trim();
|
||||
mProgressFiles = float.Parse(pctStr).GetValueOrDefault() / 100.0f;
|
||||
mProgressRecv = 1.0f;
|
||||
mProgressDeltas = 1.0f;
|
||||
}
|
||||
|
||||
StringView version = default;
|
||||
|
||||
int refTagIdx = line.IndexOf("\trefs/tags/");
|
||||
if (refTagIdx == 40)
|
||||
version = line.Substring(40 + "\trefs/tags/".Length);
|
||||
|
||||
if ((line.Length == 45) && (line.EndsWith("HEAD")))
|
||||
version = "HEAD";
|
||||
|
||||
if (!version.IsEmpty)
|
||||
{
|
||||
TagInfo tagInfo = new .();
|
||||
tagInfo.mHash = new .(line, 0, 40);
|
||||
tagInfo.mTag = new .(version);
|
||||
mTagInfos.Add(tagInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float pct = 0;
|
||||
if (mProgressRecv > 0)
|
||||
pct = 0.1f + (mProgressRecv * 0.3f) + (mProgressDeltas * 0.4f) + (mProgressFiles * 0.2f);
|
||||
|
||||
if (pct > mProgress)
|
||||
{
|
||||
mProgress = pct;
|
||||
//Debug.WriteLine($"Completed Pct: {pct}");
|
||||
}
|
||||
|
||||
if (mProcess.WaitFor(0))
|
||||
{
|
||||
if (mProcess.ExitCode != 0)
|
||||
mFailed = true;
|
||||
mDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
if (!mProcess.WaitFor(0))
|
||||
{
|
||||
//Debug.WriteLine($"GitManager Cancel {mProcess.ProcessId}");
|
||||
IDEUtils.SafeKill(mProcess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public const int sMaxActiveGitInstances = 4;
|
||||
|
||||
public List<GitInstance> mGitInstances = new .() ~
|
||||
{
|
||||
for (var gitInstance in _)
|
||||
gitInstance.ReleaseRef();
|
||||
delete _;
|
||||
};
|
||||
|
||||
public void Init()
|
||||
{
|
||||
//StartGit("-v");
|
||||
|
||||
//Repository repository = Clone("https://github.com/llvm/llvm-project", "c:/temp/__LLVM");
|
||||
|
||||
//Repository repository = Clone("https://github.com/Starpelly/raylib-beef", "c:/temp/__RAYLIB");
|
||||
/*while (true)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
Debug.WriteLine($"Repository {repository.mStatus} {repository.GetCompletedPct()}");
|
||||
}*/
|
||||
}
|
||||
|
||||
public GitInstance StartGit(StringView cmd, StringView path = default)
|
||||
{
|
||||
//Debug.WriteLine($"GIT STARTING: {cmd} in {path}");
|
||||
|
||||
GitInstance gitInst = new .(this);
|
||||
gitInst.Init(cmd, path);
|
||||
mGitInstances.Add(gitInst);
|
||||
return gitInst;
|
||||
}
|
||||
|
||||
public GitInstance Clone(StringView url, StringView path)
|
||||
{
|
||||
return StartGit(scope $"clone -v --progress --recurse-submodules {url} \"{path}\"");
|
||||
}
|
||||
|
||||
public GitInstance Checkout(StringView path, StringView hash)
|
||||
{
|
||||
return StartGit(scope $"checkout -b BeefManaged {hash}", path);
|
||||
}
|
||||
|
||||
public GitInstance GetTags(StringView url)
|
||||
{
|
||||
return StartGit(scope $"ls-remote {url}");
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
for (var gitInstance in mGitInstances)
|
||||
{
|
||||
if (@gitInstance.Index >= sMaxActiveGitInstances)
|
||||
break;
|
||||
|
||||
if (!gitInstance.mStarted)
|
||||
gitInstance.Start();
|
||||
gitInstance.Update();
|
||||
|
||||
if (gitInstance.mDone)
|
||||
{
|
||||
@gitInstance.Remove();
|
||||
gitInstance.mRemoved = true;
|
||||
gitInstance.ReleaseRef();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +1,397 @@
|
|||
#pragma warning disable 168
|
||||
|
||||
using System;
|
||||
using IDE.Util;
|
||||
|
||||
#if BF_PLATFORM_WINDOWS
|
||||
using static Git.GitApi;
|
||||
#define SUPPORT_GIT
|
||||
#endif
|
||||
using System.Collections;
|
||||
using System.Security.Cryptography;
|
||||
using System.IO;
|
||||
using Beefy.utils;
|
||||
using System.Threading;
|
||||
|
||||
namespace IDE.util
|
||||
{
|
||||
class PackMan
|
||||
{
|
||||
class GitHelper
|
||||
public class WorkItem
|
||||
{
|
||||
static bool sInitialized;
|
||||
|
||||
public this()
|
||||
public enum Kind
|
||||
{
|
||||
if (!sInitialized)
|
||||
{
|
||||
#if SUPPORT_GIT
|
||||
#unwarn
|
||||
var result = git_libgit2_init();
|
||||
sInitialized = true;
|
||||
#endif
|
||||
}
|
||||
None,
|
||||
FindVersion,
|
||||
Clone,
|
||||
Checkout
|
||||
}
|
||||
|
||||
public Kind mKind;
|
||||
public String mProjectName ~ delete _;
|
||||
public String mURL ~ delete _;
|
||||
public List<String> mConstraints ~ DeleteContainerAndItems!(_);
|
||||
public String mTag ~ delete _;
|
||||
public String mHash ~ delete _;
|
||||
public String mPath ~ delete _;
|
||||
public GitManager.GitInstance mGitInstance ~ _?.ReleaseRef();
|
||||
|
||||
public ~this()
|
||||
{
|
||||
mGitInstance?.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public List<WorkItem> mWorkItems = new .() ~ DeleteContainerAndItems!(_);
|
||||
public bool mInitialized;
|
||||
public String mManagedPath ~ delete _;
|
||||
public bool mFailed;
|
||||
|
||||
public void Fail(StringView error)
|
||||
{
|
||||
gApp.OutputErrorLine(error);
|
||||
|
||||
if (!mFailed)
|
||||
{
|
||||
mFailed = true;
|
||||
gApp.[Friend]FlushDeferredLoadProjects();
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckInit()
|
||||
{
|
||||
if (mInitialized)
|
||||
return true;
|
||||
|
||||
if (gApp.mBeefConfig.mManagedLibPath.IsEmpty)
|
||||
return false;
|
||||
|
||||
mManagedPath = new .(gApp.mBeefConfig.mManagedLibPath);
|
||||
mInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void GetPath(StringView url, StringView hash, String outPath)
|
||||
{
|
||||
//var urlHash = SHA256.Hash(url.ToRawData()).ToString(.. scope .());
|
||||
//outPath.AppendF($"{mManagedPath}/{urlHash}/{hash}");
|
||||
outPath.AppendF($"{mManagedPath}/{hash}");
|
||||
}
|
||||
|
||||
public bool CheckLock(StringView projectName, String outPath)
|
||||
{
|
||||
if (!CheckInit())
|
||||
return false;
|
||||
|
||||
if (gApp.mWantUpdateVersionLocks != null)
|
||||
{
|
||||
if ((gApp.mWantUpdateVersionLocks.IsEmpty) || (gApp.mWantUpdateVersionLocks.ContainsAlt(projectName)))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gApp.mWorkspace.mProjectLockMap.TryGetAlt(projectName, ?, var lock))
|
||||
return false;
|
||||
|
||||
switch (lock)
|
||||
{
|
||||
case .Git(let url, let tag, let hash):
|
||||
var path = GetPath(url, hash, .. scope .());
|
||||
var managedFilePath = scope $"{path}/BeefManaged.toml";
|
||||
if (File.Exists(managedFilePath))
|
||||
{
|
||||
outPath.Append(path);
|
||||
outPath.Append("/BeefProj.toml");
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CloneCompleted(StringView projectName, StringView url, StringView tag, StringView hash, StringView path)
|
||||
{
|
||||
gApp.mWorkspace.SetLock(projectName, .Git(new .(url), new .(tag), new .(hash)));
|
||||
|
||||
StructuredData sd = scope .();
|
||||
sd.CreateNew();
|
||||
sd.Add("FileVersion", 1);
|
||||
sd.Add("Version", tag);
|
||||
sd.Add("GitURL", url);
|
||||
sd.Add("GitTag", tag);
|
||||
sd.Add("GitHash", hash);
|
||||
var tomlText = sd.ToTOML(.. scope .());
|
||||
var managedFilePath = scope $"{path}/BeefManaged.toml";
|
||||
File.WriteAllText(managedFilePath, tomlText).IgnoreError();
|
||||
}
|
||||
|
||||
public void GetWithHash(StringView projectName, StringView url, StringView tag, StringView hash)
|
||||
{
|
||||
if (!CheckInit())
|
||||
return;
|
||||
|
||||
String destPath = GetPath(url, hash, .. scope .());
|
||||
var urlPath = Path.GetDirectoryPath(destPath, .. scope .());
|
||||
Directory.CreateDirectory(urlPath).IgnoreError();
|
||||
if (Directory.Exists(destPath))
|
||||
{
|
||||
var managedFilePath = scope $"{destPath}/BeefManaged.toml";
|
||||
if (File.Exists(managedFilePath))
|
||||
{
|
||||
if (gApp.mVerbosity >= .Normal)
|
||||
{
|
||||
if (tag.IsEmpty)
|
||||
gApp.OutputLine($"Git selecting library '{projectName}' at {hash.Substring(0, 7)}");
|
||||
else
|
||||
gApp.OutputLine($"Git selecting library '{projectName}' tag '{tag}' at {hash.Substring(0, 7)}");
|
||||
}
|
||||
|
||||
CloneCompleted(projectName, url, tag, hash, destPath);
|
||||
ProjectReady(projectName, destPath);
|
||||
return;
|
||||
}
|
||||
|
||||
String tempDir = new $"{destPath}__{(int32)Internal.GetTickCountMicro():X}";
|
||||
|
||||
//if (Directory.DelTree(destPath) case .Err)
|
||||
if (Directory.Move(destPath, tempDir) case .Err)
|
||||
{
|
||||
delete tempDir;
|
||||
Fail(scope $"Failed to remove directory '{destPath}'");
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadPool.QueueUserWorkItem(new () =>
|
||||
{
|
||||
Directory.DelTree(tempDir);
|
||||
}
|
||||
~
|
||||
{
|
||||
delete tempDir;
|
||||
});
|
||||
}
|
||||
|
||||
if (gApp.mVerbosity >= .Normal)
|
||||
{
|
||||
if (tag.IsEmpty)
|
||||
gApp.OutputLine($"Git cloning library '{projectName}' at {hash.Substring(0, 7)}...");
|
||||
else
|
||||
gApp.OutputLine($"Git cloning library '{projectName}' tag '{tag}' at {hash.Substring(0, 7)}");
|
||||
}
|
||||
|
||||
WorkItem workItem = new .();
|
||||
workItem.mKind = .Clone;
|
||||
workItem.mProjectName = new .(projectName);
|
||||
workItem.mURL = new .(url);
|
||||
workItem.mTag = new .(tag);
|
||||
workItem.mHash = new .(hash);
|
||||
workItem.mPath = new .(destPath);
|
||||
mWorkItems.Add(workItem);
|
||||
}
|
||||
|
||||
public void GetWithVersion(StringView projectName, StringView url, SemVer semVer)
|
||||
{
|
||||
if (!CheckInit())
|
||||
return;
|
||||
|
||||
bool ignoreLock = false;
|
||||
if (gApp.mWantUpdateVersionLocks != null)
|
||||
{
|
||||
if ((gApp.mWantUpdateVersionLocks.IsEmpty) || (gApp.mWantUpdateVersionLocks.ContainsAlt(projectName)))
|
||||
ignoreLock = true;
|
||||
}
|
||||
|
||||
if ((!ignoreLock) && (gApp.mWorkspace.mProjectLockMap.TryGetAlt(projectName, ?, var lock)))
|
||||
{
|
||||
switch (lock)
|
||||
{
|
||||
case .Git(let checkURL, let tag, let hash):
|
||||
if (checkURL == url)
|
||||
GetWithHash(projectName, url, tag, hash);
|
||||
return;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if (gApp.mVerbosity >= .Normal)
|
||||
gApp.OutputLine($"Git retrieving version list for '{projectName}'");
|
||||
|
||||
WorkItem workItem = new .();
|
||||
workItem.mKind = .FindVersion;
|
||||
workItem.mProjectName = new .(projectName);
|
||||
workItem.mURL = new .(url);
|
||||
if (semVer != null)
|
||||
workItem.mConstraints = new .() { new String(semVer.mVersion) };
|
||||
mWorkItems.Add(workItem);
|
||||
}
|
||||
|
||||
public void UpdateGitConstraint(StringView url, SemVer semVer)
|
||||
{
|
||||
for (var workItem in mWorkItems)
|
||||
{
|
||||
if ((workItem.mKind == .FindVersion) && (workItem.mURL == url))
|
||||
{
|
||||
if (workItem.mConstraints == null)
|
||||
workItem.mConstraints = new .();
|
||||
workItem.mConstraints.Add(new String(semVer.mVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Checkout(StringView projectName, StringView url, StringView path, StringView tag, StringView hash)
|
||||
{
|
||||
if (!CheckInit())
|
||||
return;
|
||||
|
||||
WorkItem workItem = new .();
|
||||
workItem.mKind = .Checkout;
|
||||
workItem.mProjectName = new .(projectName);
|
||||
workItem.mURL = new .(url);
|
||||
workItem.mTag = new .(tag);
|
||||
workItem.mHash = new .(hash);
|
||||
workItem.mPath = new .(path);
|
||||
mWorkItems.Add(workItem);
|
||||
}
|
||||
|
||||
public void ProjectReady(StringView projectName, StringView path)
|
||||
{
|
||||
if (var project = gApp.mWorkspace.FindProject(projectName))
|
||||
{
|
||||
String projectPath = scope $"{path}/BeefProj.toml";
|
||||
|
||||
project.mProjectPath.Set(projectPath);
|
||||
gApp.RetryProjectLoad(project, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
bool executingGit = false;
|
||||
|
||||
// First handle active git items
|
||||
for (var workItem in mWorkItems)
|
||||
{
|
||||
if (workItem.mGitInstance == null)
|
||||
continue;
|
||||
|
||||
if (!workItem.mGitInstance.mDone)
|
||||
{
|
||||
executingGit = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!workItem.mGitInstance.mFailed)
|
||||
{
|
||||
switch (workItem.mKind)
|
||||
{
|
||||
case .FindVersion:
|
||||
gApp.CompilerLog("");
|
||||
|
||||
StringView bestTag = default;
|
||||
StringView bestHash = default;
|
||||
|
||||
for (var tag in workItem.mGitInstance.mTagInfos)
|
||||
{
|
||||
if ((tag.mTag == "HEAD") && (workItem.mConstraints == null))
|
||||
bestHash = tag.mHash;
|
||||
else if (workItem.mConstraints != null)
|
||||
{
|
||||
bool hasMatch = false;
|
||||
for (var constraint in workItem.mConstraints)
|
||||
{
|
||||
if (SemVer.IsVersionMatch(tag.mTag, constraint))
|
||||
{
|
||||
hasMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMatch)
|
||||
{
|
||||
if ((bestTag.IsEmpty) || (SemVer.Compare(tag.mTag, bestTag) > 0))
|
||||
{
|
||||
bestTag = tag.mTag;
|
||||
bestHash = tag.mHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestHash != default)
|
||||
{
|
||||
GetWithHash(workItem.mProjectName, workItem.mURL, bestTag, bestHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
String constraints = scope .();
|
||||
for (var constraint in workItem.mConstraints)
|
||||
{
|
||||
if (!constraints.IsEmpty)
|
||||
constraints.Append(", ");
|
||||
constraints.Append('\'');
|
||||
constraints.Append(constraint);
|
||||
constraints.Append('\'');
|
||||
}
|
||||
|
||||
Fail(scope $"Failed to locate version for '{workItem.mProjectName}' with constraints '{constraints}'");
|
||||
}
|
||||
case .Clone:
|
||||
Checkout(workItem.mProjectName, workItem.mURL, workItem.mPath, workItem.mTag, workItem.mHash);
|
||||
case .Checkout:
|
||||
CloneCompleted(workItem.mProjectName, workItem.mURL, workItem.mTag, workItem.mHash, workItem.mPath);
|
||||
ProjectReady(workItem.mProjectName, workItem.mPath);
|
||||
|
||||
if (gApp.mVerbosity >= .Normal)
|
||||
gApp.OutputLine($"Git cloning library '{workItem.mProjectName}' done.");
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@workItem.Remove();
|
||||
delete workItem;
|
||||
}
|
||||
|
||||
if (!executingGit)
|
||||
{
|
||||
// First handle active git items
|
||||
for (var workItem in mWorkItems)
|
||||
{
|
||||
if (workItem.mGitInstance != null)
|
||||
continue;
|
||||
|
||||
switch (workItem.mKind)
|
||||
{
|
||||
case .FindVersion:
|
||||
workItem.mGitInstance = gApp.mGitManager.GetTags(workItem.mURL)..AddRef();
|
||||
case .Checkout:
|
||||
workItem.mGitInstance = gApp.mGitManager.Checkout(workItem.mPath, workItem.mHash)..AddRef();
|
||||
case .Clone:
|
||||
workItem.mGitInstance = gApp.mGitManager.Clone(workItem.mURL, workItem.mPath)..AddRef();
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GetHashFromFilePath(StringView filePath, String path)
|
||||
{
|
||||
if (mManagedPath == null)
|
||||
return;
|
||||
|
||||
if (!filePath.StartsWith(mManagedPath))
|
||||
return;
|
||||
|
||||
StringView hashPart = filePath.Substring(mManagedPath.Length);
|
||||
if (hashPart.Length < 42)
|
||||
return;
|
||||
|
||||
hashPart.RemoveFromStart(1);
|
||||
hashPart.Length = 40;
|
||||
path.Append(hashPart);
|
||||
}
|
||||
|
||||
public void CancelAll()
|
||||
{
|
||||
if (mWorkItems.IsEmpty)
|
||||
return;
|
||||
|
||||
Fail("Aborted project transfer");
|
||||
mWorkItems.ClearAndDeleteItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,51 @@ using System;
|
|||
|
||||
namespace IDE.Util
|
||||
{
|
||||
[Reflect]
|
||||
class SemVer
|
||||
{
|
||||
public struct Parts
|
||||
{
|
||||
public enum Kind
|
||||
{
|
||||
case Empty;
|
||||
case Num(int32 val);
|
||||
case Wild;
|
||||
|
||||
public int32 NumOrDefault
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this case .Num(let val))
|
||||
return val;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Kind[3] mPart;
|
||||
public StringView mPreRelease;
|
||||
|
||||
public Kind Major => mPart[0];
|
||||
public Kind Minor => mPart[1];
|
||||
public Kind Patch => mPart[2];
|
||||
}
|
||||
|
||||
enum CompareKind
|
||||
{
|
||||
Caret, // Default
|
||||
Tilde,
|
||||
Equal,
|
||||
Gt,
|
||||
Gte,
|
||||
Lt,
|
||||
Lte
|
||||
}
|
||||
|
||||
public String mVersion ~ delete _;
|
||||
|
||||
public bool IsEmpty => String.IsNullOrEmpty(mVersion);
|
||||
|
||||
public this()
|
||||
{
|
||||
|
||||
|
@ -27,5 +68,240 @@ namespace IDE.Util
|
|||
mVersion = new String(ver);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<Parts> GetParts(StringView version)
|
||||
{
|
||||
int startIdx = 0;
|
||||
int partIdx = 0;
|
||||
|
||||
if (version.IsEmpty)
|
||||
return .Err;
|
||||
|
||||
if (version.StartsWith("V", .OrdinalIgnoreCase))
|
||||
startIdx++;
|
||||
|
||||
Parts parts = .();
|
||||
|
||||
Result<void> SetPart(Parts.Kind kind)
|
||||
{
|
||||
if (partIdx >= 3)
|
||||
return .Err;
|
||||
parts.mPart[partIdx] = kind;
|
||||
partIdx++;
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
Result<void> FlushPart(int i)
|
||||
{
|
||||
StringView partStr = version.Substring(startIdx, i - startIdx);
|
||||
if (!partStr.IsEmpty)
|
||||
{
|
||||
int32 partNum = Try!(int32.Parse(partStr));
|
||||
Try!(SetPart(.Num(partNum)));
|
||||
}
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
for (int i in startIdx ..< version.Length)
|
||||
{
|
||||
char8 c = version[i];
|
||||
if (c.IsWhiteSpace)
|
||||
return .Err;
|
||||
|
||||
if (c == '.')
|
||||
{
|
||||
Try!(FlushPart(i));
|
||||
startIdx = i + 1;
|
||||
continue;
|
||||
}
|
||||
else if (c.IsNumber)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (c == '-')
|
||||
{
|
||||
if (partIdx == 0)
|
||||
return .Err;
|
||||
parts.mPreRelease = version.Substring(i);
|
||||
return .Ok(parts);
|
||||
}
|
||||
else if (c == '*')
|
||||
{
|
||||
Try!(SetPart(.Wild));
|
||||
continue;
|
||||
}
|
||||
|
||||
return .Err;
|
||||
}
|
||||
Try!(FlushPart(version.Length));
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public Result<Parts> GetParts()
|
||||
{
|
||||
return GetParts(mVersion);
|
||||
}
|
||||
|
||||
public static bool IsVersionMatch(StringView fullVersion, StringView wildcard)
|
||||
{
|
||||
int commaPos = wildcard.IndexOf(',');
|
||||
if (commaPos != -1)
|
||||
return IsVersionMatch(fullVersion, wildcard.Substring(0, commaPos)..Trim()) && IsVersionMatch(fullVersion, wildcard.Substring(commaPos + 1)..Trim());
|
||||
|
||||
var wildcard;
|
||||
|
||||
wildcard.Trim();
|
||||
CompareKind compareKind = .Caret;
|
||||
if (wildcard.StartsWith('^'))
|
||||
{
|
||||
compareKind = .Caret;
|
||||
wildcard.RemoveFromStart(1);
|
||||
}
|
||||
else if (wildcard.StartsWith('~'))
|
||||
{
|
||||
compareKind = .Tilde;
|
||||
wildcard.RemoveFromStart(1);
|
||||
}
|
||||
else if (wildcard.StartsWith('='))
|
||||
{
|
||||
compareKind = .Equal;
|
||||
wildcard.RemoveFromStart(1);
|
||||
}
|
||||
else if (wildcard.StartsWith('>'))
|
||||
{
|
||||
compareKind = .Gt;
|
||||
wildcard.RemoveFromStart(1);
|
||||
if (wildcard.StartsWith('='))
|
||||
{
|
||||
compareKind = .Gte;
|
||||
wildcard.RemoveFromStart(1);
|
||||
}
|
||||
}
|
||||
else if (wildcard.StartsWith('<'))
|
||||
{
|
||||
compareKind = .Lt;
|
||||
wildcard.RemoveFromStart(1);
|
||||
if (wildcard.StartsWith('='))
|
||||
{
|
||||
compareKind = .Lte;
|
||||
wildcard.RemoveFromStart(1);
|
||||
}
|
||||
}
|
||||
wildcard.Trim();
|
||||
|
||||
// Does we include equality?
|
||||
if ((compareKind != .Gt) && (compareKind != .Lt))
|
||||
{
|
||||
if (fullVersion == wildcard)
|
||||
return true;
|
||||
}
|
||||
|
||||
Parts full;
|
||||
if (!(GetParts(fullVersion) case .Ok(out full)))
|
||||
return false;
|
||||
Parts wild;
|
||||
if (!(GetParts(wildcard) case .Ok(out wild)))
|
||||
return false;
|
||||
|
||||
// Don't allow a general wildcard to match a pre-prelease
|
||||
if ((!full.mPreRelease.IsEmpty) && (full.mPreRelease != wild.mPreRelease))
|
||||
return false;
|
||||
|
||||
for (int partIdx < 3)
|
||||
{
|
||||
if (wild.mPart[partIdx] case .Wild)
|
||||
return true;
|
||||
int comp = full.mPart[partIdx].NumOrDefault <=> wild.mPart[partIdx].NumOrDefault;
|
||||
switch (compareKind)
|
||||
{
|
||||
case .Caret:
|
||||
if ((full.mPart[partIdx].NumOrDefault > 0) || (wild.mPart[partIdx].NumOrDefault > 0))
|
||||
{
|
||||
if (comp != 0)
|
||||
return false;
|
||||
// First number matches, now make sure we are at least a high enough version on the other numbers
|
||||
compareKind = .Gte;
|
||||
}
|
||||
case .Tilde:
|
||||
if (wild.mPart[partIdx] case .Empty)
|
||||
return true;
|
||||
if (partIdx == 2)
|
||||
{
|
||||
if (comp < 0)
|
||||
return false;
|
||||
}
|
||||
else if (comp != 0)
|
||||
return false;
|
||||
case .Equal:
|
||||
if (wild.mPart[partIdx] case .Empty)
|
||||
return true;
|
||||
if (comp != 0)
|
||||
return false;
|
||||
case .Gt:
|
||||
if (comp > 0)
|
||||
return true;
|
||||
if (partIdx == 2)
|
||||
return false;
|
||||
if (comp < 0)
|
||||
return false;
|
||||
case .Gte:
|
||||
if (comp < 0)
|
||||
return false;
|
||||
case .Lt:
|
||||
if (comp < 0)
|
||||
return true;
|
||||
if (partIdx == 2)
|
||||
return false;
|
||||
if (comp > 0)
|
||||
return false;
|
||||
case .Lte:
|
||||
if (comp > 0)
|
||||
return false;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsVersionMatch(SemVer fullVersion, SemVer wildcard) => IsVersionMatch(fullVersion.mVersion, wildcard.mVersion);
|
||||
|
||||
public static Result<int> Compare(StringView lhs, StringView rhs)
|
||||
{
|
||||
Parts lhsParts;
|
||||
if (!(GetParts(lhs) case .Ok(out lhsParts)))
|
||||
return .Err;
|
||||
Parts rhsParts;
|
||||
if (!(GetParts(rhs) case .Ok(out rhsParts)))
|
||||
return .Err;
|
||||
|
||||
int comp = 0;
|
||||
for (int partIdx < 3)
|
||||
{
|
||||
comp = lhsParts.mPart[partIdx].NumOrDefault <=> rhsParts.mPart[partIdx].NumOrDefault;
|
||||
if (comp != 0)
|
||||
return comp;
|
||||
}
|
||||
|
||||
// Don't allow a general wildcard to match a pre-prelease
|
||||
if ((!lhsParts.mPreRelease.IsEmpty) || (!rhsParts.mPreRelease.IsEmpty))
|
||||
{
|
||||
if (lhsParts.mPreRelease.IsEmpty)
|
||||
return 1;
|
||||
if (rhsParts.mPreRelease.IsEmpty)
|
||||
return -1;
|
||||
return lhsParts.mPreRelease <=> rhsParts.mPreRelease;
|
||||
}
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
public override void ToString(String strBuffer)
|
||||
{
|
||||
strBuffer.Append(mVersion);
|
||||
}
|
||||
|
||||
public static int operator<=>(Self lhs, Self rhs) => (lhs?.mVersion ?? "") <=> (rhs?.mVersion ?? "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ namespace IDE.Util
|
|||
case .Path(let path):
|
||||
return .Path(new String(path));
|
||||
case .Git(let url, let ver):
|
||||
if (ver == null)
|
||||
return .Git(new String(url), null);
|
||||
return .Git(new String(url), new SemVer(ver));
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +115,7 @@ namespace IDE.Util
|
|||
using (data.CreateObject(name))
|
||||
{
|
||||
data.Add("Git", path);
|
||||
if (ver != null)
|
||||
if ((ver != null) && (!ver.mVersion.IsEmpty))
|
||||
data.Add("Version", ver.mVersion);
|
||||
}
|
||||
case .SemVer(var ver):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue