From 7e7f215c014883d5c04617a8deeea363886116be Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Wed, 26 Oct 2022 21:50:55 +0100 Subject: [PATCH 01/19] Rename VcsData.ref to VcsData.commit --- src/haxelib/VersionData.hx | 6 +++--- src/haxelib/api/Installer.hx | 4 ++-- src/haxelib/api/Vcs.hx | 4 ++-- src/haxelib/client/Main.hx | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/haxelib/VersionData.hx b/src/haxelib/VersionData.hx index 6597cdec..3961d8b6 100644 --- a/src/haxelib/VersionData.hx +++ b/src/haxelib/VersionData.hx @@ -28,7 +28,7 @@ class VcsData { var url:String; /** Commit hash **/ @:optional - var ref:Null; + var commit:Null; /** The git tag or mercurial revision **/ @:optional var tag:Null; @@ -45,7 +45,7 @@ class VcsData { public function toString(): String { var qualifier = - if (ref != null) ref + if (commit != null) commit else if (tag != null) tag else if (branch != null) branch else null; @@ -89,7 +89,7 @@ class VersionDataHelper { type: type, data: { url: vcsRegex.matched(2), - ref: vcsRegex.matched(3), + commit: vcsRegex.matched(3), branch: vcsRegex.matched(4), subDir: null, tag: null diff --git a/src/haxelib/api/Installer.hx b/src/haxelib/api/Installer.hx index d2e20428..76210950 100644 --- a/src/haxelib/api/Installer.hx +++ b/src/haxelib/api/Installer.hx @@ -657,7 +657,7 @@ class Installer { case [VcsInstall(a, vcsData1), VcsInstall(b, vcsData2)] if ((a == b) && (vcsData1.url == vcsData2.url) - && (vcsData1.ref == vcsData2.ref) + && (vcsData1.commit == vcsData2.commit) && (vcsData1.branch == vcsData2.branch) && (vcsData1.tag == vcsData2.tag) && (vcsData1.subDir == vcsData2.subDir) @@ -760,7 +760,7 @@ class Installer { final libPath = repository.getVersionPath(library, id); - final branch = vcsData.ref != null ? vcsData.ref : vcsData.branch; + final branch = vcsData.commit != null ? vcsData.commit : vcsData.branch; final url:String = vcsData.url; function doVcsClone() { diff --git a/src/haxelib/api/Vcs.hx b/src/haxelib/api/Vcs.hx index aee40cd5..90b4c830 100644 --- a/src/haxelib/api/Vcs.hx +++ b/src/haxelib/api/Vcs.hx @@ -374,7 +374,7 @@ class Git extends Vcs { final ret: VcsData = { // could the remote's name not be "origin"? url: run(["remote", "get-url", "origin"], true).out.trim(), - ref: run(["rev-parse", "HEAD"], true).out.trim(), + commit: run(["rev-parse", "HEAD"], true).out.trim(), }; Sys.setCwd(oldCwd); @@ -464,7 +464,7 @@ class Mercurial extends Vcs { final ret: VcsData = { url: run(["paths", "default"], true).out.trim(), - ref: run(["identify", "-i", "--debug"], true).out.trim(), + commit: run(["identify", "-i", "--debug"], true).out.trim(), }; Sys.setCwd(oldCwd); diff --git a/src/haxelib/client/Main.hx b/src/haxelib/client/Main.hx index b27ab528..a4ef4ab7 100644 --- a/src/haxelib/client/Main.hx +++ b/src/haxelib/client/Main.hx @@ -746,14 +746,14 @@ class Main { final ref = argsIterator.next(); final isRefHash = ref == null || LibraryData.isCommitHash(ref); - final hash = isRefHash ? ref : null; + final commit = isRefHash ? ref : null; final branch = isRefHash ? null : ref; final installer = setupAndGetInstaller(); installer.installVcsLibrary(library, id, { url: url, - ref: hash, + commit: commit, branch: branch, subDir: argsIterator.next(), tag: argsIterator.next() From 8bf372a27fd3fc920041345bd7d809cc38b7bf1d Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sat, 28 Jun 2025 15:31:31 +0100 Subject: [PATCH 02/19] Refactor vcs code Reduce reliance on global state. Change api with regards to detecting local changes before an update. The caller now calls hasLocalChanges() and optionally resetLocalChanges(), rather than having to pass in a callback to handle the decision. --- src/haxelib/VersionData.hx | 7 + src/haxelib/api/FsUtils.hx | 17 ++ src/haxelib/api/Installer.hx | 102 +++++------ src/haxelib/api/Repository.hx | 2 +- src/haxelib/api/Vcs.hx | 333 +++++++++++++++------------------- src/haxelib/client/Main.hx | 24 +-- 6 files changed, 226 insertions(+), 259 deletions(-) diff --git a/src/haxelib/VersionData.hx b/src/haxelib/VersionData.hx index 3961d8b6..102a74a7 100644 --- a/src/haxelib/VersionData.hx +++ b/src/haxelib/VersionData.hx @@ -19,6 +19,13 @@ package haxelib; else throw 'Invalid VscID $s'; } + + public function getName() { + return switch cast(this, VcsID) { + case Git: "Git"; + case Hg: "Mercurial"; + }; + } } /** Class containing repoducible git or hg library data. **/ diff --git a/src/haxelib/api/FsUtils.hx b/src/haxelib/api/FsUtils.hx index 778c4296..af23122f 100644 --- a/src/haxelib/api/FsUtils.hx +++ b/src/haxelib/api/FsUtils.hx @@ -216,4 +216,21 @@ class FsUtils { seek(root); return ret; } + + /** + Switches to directory found at `path`, executes `f()` in this directory, + before switching back to the previous directory. + **/ + public static function runInDirectory(path:String, f:() -> T):T { + final oldCwd = Sys.getCwd(); + try { + Sys.setCwd(path); + final value = f(); + Sys.setCwd(oldCwd); + return value; + } catch (e) { + Sys.setCwd(oldCwd); + throw e; + } + } } diff --git a/src/haxelib/api/Installer.hx b/src/haxelib/api/Installer.hx index 76210950..7da982e8 100644 --- a/src/haxelib/api/Installer.hx +++ b/src/haxelib/api/Installer.hx @@ -9,7 +9,6 @@ import haxelib.VersionData; import haxelib.api.Repository; import haxelib.api.Vcs; import haxelib.api.LibraryData; -import haxelib.api.LibFlagData; using StringTools; using Lambda; @@ -17,6 +16,10 @@ using haxelib.MetaData; /** Exception thrown when an error occurs during installation. **/ class InstallationException extends haxe.Exception {} + +/** Exception thrown when an update is cancelled. **/ +class UpdateCancelled extends InstallationException {} + /** Exception thrown when a `vcs` error interrupts installation. **/ class VcsCommandFailed extends InstallationException { public final type:VcsID; @@ -157,13 +160,22 @@ private function getLatest(versions:Array):SemVer { **/ class Installer { /** If set to `true` library dependencies will not be installed. **/ - public static var skipDependencies = false; + public var skipDependencies = false; /** - If this is set to true, dependency versions will be reinstalled + If this is set to `true`, dependency versions will be reinstalled even if already installed. **/ - public static var forceInstallDependencies = false; + public var forceInstallDependencies = false; + + /** + If set to `true`, submodules will not get cloned or updated when + installing VCS libraries. + + This setting only works for libraries installed via a VCS that allows + cloning a repository without its submodules (only `git`). + **/ + public var noVcsSubmodules = false; final scope:Scope; final repository:Repository; @@ -378,7 +390,7 @@ class Installer { updateIfNeeded(library); } catch (e:AlreadyUpToDate) { userInterface.log(e.toString()); - } catch (e:VcsUpdateCancelled) { + } catch (e:UpdateCancelled) { return; } } @@ -400,7 +412,7 @@ class Installer { updated = true; } catch(e:AlreadyUpToDate) { continue; - } catch (e:VcsUpdateCancelled) { + } catch (e:UpdateCancelled) { continue; } catch(e) { ++failures; @@ -424,9 +436,9 @@ class Installer { final vcsId = try VcsID.ofString(current) catch (_) null; if (vcsId != null) { - final vcs = Vcs.get(vcsId); - if (vcs == null || !vcs.available) - throw 'Could not use $vcsId, please make sure it is installed and available in your PATH.'; + final vcs = getVcs(vcsId); + if (!FsUtils.runInDirectory(repository.getVersionPath(library, vcsId), vcs.checkRemoteChanges)) + throw new AlreadyUpToDate('Library $library $vcsId repository is already up to date'); // with version locking we'll be able to be smarter with this updateVcs(library, vcsId, vcs); @@ -753,10 +765,15 @@ class Installer { userInterface.logInstallationProgress('Done installing $library $version', total, total); } - function installVcs(library:ProjectName, id:VcsID, vcsData:VcsData) { - final vcs = Vcs.get(id); + function getVcs(id:VcsID):Vcs { + final vcs = Vcs.create(id, userInterface.log.bind(_, Debug), userInterface.log.bind(_, Optional)); if (vcs == null || !vcs.available) throw 'Could not use $id, please make sure it is installed and available in your PATH.'; + return vcs; + } + + function installVcs(library:ProjectName, id:VcsID, vcsData:VcsData) { + final vcs = getVcs(id); final libPath = repository.getVersionPath(library, id); @@ -765,16 +782,15 @@ class Installer { function doVcsClone() { userInterface.log('Installing $library from $url' + (branch != null ? " branch: " + branch : "")); - final tag = vcsData.tag; try { - vcs.clone(libPath, url, branch, tag, userInterface.log.bind(_, Debug), userInterface.log.bind(_, Optional)); + vcs.clone(libPath, vcsData, noVcsSubmodules); } catch (error:VcsError) { FsUtils.deleteRec(libPath); switch (error) { case VcsUnavailable(vcs): throw 'Could not use ${vcs.executable}, please make sure it is installed and available in your PATH.'; case CantCloneRepo(vcs, _, stderr): - throw 'Could not clone ${vcs.name} repository' + (stderr != null ? ":\n" + stderr : "."); + throw 'Could not clone ${id.getName()} repository' + (stderr != null ? ":\n" + stderr : "."); case CantCheckoutBranch(_, branch, stderr): throw 'Could not checkout branch, tag or path "$branch": ' + stderr; case CantCheckoutVersion(_, version, stderr): @@ -788,7 +804,7 @@ class Installer { } if (repository.isVersionInstalled(library, id)) { - userInterface.log('You already have $library version ${vcs.directory} installed.'); + userInterface.log('You already have $library version $id installed.'); final wasUpdated = vcsBranchesByLibraryName.exists(library); // difference between a key not having a value and the value being null @@ -804,15 +820,13 @@ class Installer { } FsUtils.deleteRec(libPath); doVcsClone(); - } else if (wasUpdated) { - userInterface.log('Library $library version ${vcs.directory} already up to date.'); - return; + } else if (!wasUpdated && FsUtils.runInDirectory(libPath, vcs.checkRemoteChanges)) { + userInterface.log('Updating $library version $id...'); + updateVcs(library, id, vcs); } else { - userInterface.log('Updating $library version ${vcs.directory}...'); - try { - updateVcs(library, id, vcs); - } catch (e:AlreadyUpToDate){ - userInterface.log(e.toString()); + userInterface.log('Library $library version $id already up to date'); + if (wasUpdated) { + return; } } } else { @@ -823,37 +837,17 @@ class Installer { vcsBranchesByLibraryName[library] = branch; } - function updateVcs(library:ProjectName, id:VcsID, vcs:Vcs) { - final dir = repository.getVersionPath(library, id); - - final oldCwd = Sys.getCwd(); - Sys.setCwd(dir); - - final success = try { - vcs.update( - function() { - if (userInterface.confirm('Reset changes to $library $id repository in order to update to latest version')) - return true; + function updateVcs(library:ProjectName, id:VcsID, vcs:Vcs) + FsUtils.runInDirectory(repository.getVersionPath(library, id), function() { + if (vcs.hasLocalChanges()) { + if (!userInterface.confirm('Reset changes to $library $id repository in order to update to latest version')) { userInterface.log('$library repository has not been modified', Optional); - return false; - }, - userInterface.log.bind(_, Debug), - userInterface.log.bind(_, Optional) - ); - } catch (e:VcsError) { - Sys.setCwd(oldCwd); - switch e { - case CommandFailed(_, code, stdout, stderr): - throw new VcsCommandFailed(id, code, stdout, stderr); - default: Util.rethrow(e); // other errors aren't expected here + throw new UpdateCancelled('${id.getName()} update in ${Sys.getCwd()} was cancelled'); + } + vcs.resetLocalChanges(); } - } catch (e:haxe.Exception) { - Sys.setCwd(oldCwd); - Util.rethrow(e); - } - Sys.setCwd(oldCwd); - if (!success) - throw new AlreadyUpToDate('Library $library $id repository is already up to date'); - userInterface.log('$library was updated'); - } + + vcs.mergeRemoteChanges(); + userInterface.log('$library was updated'); + }); } diff --git a/src/haxelib/api/Repository.hx b/src/haxelib/api/Repository.hx index d9d442ae..0e58246b 100644 --- a/src/haxelib/api/Repository.hx +++ b/src/haxelib/api/Repository.hx @@ -473,6 +473,6 @@ class Repository { } public function getVcsData(name: ProjectName, version: VcsID): VcsData { - return Vcs.get(version).getReproducibleVersion(getProjectVersionPath(name, version)); + return Vcs.create(version).getReproducibleVersion(getProjectVersionPath(name, version)); } } diff --git a/src/haxelib/api/Vcs.hx b/src/haxelib/api/Vcs.hx index 90b4c830..aa833ddc 100644 --- a/src/haxelib/api/Vcs.hx +++ b/src/haxelib/api/Vcs.hx @@ -21,50 +21,47 @@ */ package haxelib.api; -import haxelib.VersionData.VcsData; import sys.FileSystem; import sys.thread.Thread; import sys.thread.Lock; -import haxelib.VersionData.VcsID; +import haxelib.VersionData; using haxelib.api.Vcs; using StringTools; -interface IVcs { - /** The name of the vcs system. **/ - final name:String; - /** The directory used to install vcs library versions to. **/ - final directory:String; +private interface IVcs { /** The vcs executable. **/ final executable:String; /** Whether or not the executable can be accessed successfully. **/ var available(get, null):Bool; /** - Clone repository at `vcsPath` into `libPath`. + Clone repository specified in `data` into `libPath`. - If `branch` is specified, the repository is checked out to that branch. - - `version` can also be specified for tags in git or revisions in mercurial. - - `debugLog` will be used to log executable output. + If `flat` is set to true, recursive cloning is disabled. **/ - function clone(libPath:String, vcsPath:String, ?branch:String, ?version:String, ?debugLog:(msg:String)->Void, ?optionalLog:(msg:String)->Void):Void; + function clone(libPath:String, data:VcsData, flat:Bool = false):Void; /** - Updates repository in CWD or CWD/`Vcs.directory` to HEAD. - For git CWD must be in the format "...haxelib-repo/lib/git". - - By default, uncommitted changes prevent updating. - If `confirm` is passed in, the changes may occur - if `confirm` returns true. + Merges remote changes into repository. + **/ + function mergeRemoteChanges():Void; - `debugLog` will be used to log executable output. + /** + Checks for possible remote changes, and returns whether there are any available. + **/ + function checkRemoteChanges():Bool; - `summaryLog` may be used to log summaries of changes. + /** + Returns whether any uncommited local changes exist. + **/ + function hasLocalChanges():Bool; - Returns `true` if update successful. + /** + Resets all local changes present in the working tree. **/ - function update(?confirm:()->Bool, ?debugLog:(msg:String)->Void, ?summaryLog:(msg:String)->Void):Bool; + function resetLocalChanges():Void; + + function getReproducibleVersion(libPath: String): VcsData; } /** Enum representing errors that can be thrown during a vcs operation. **/ @@ -77,91 +74,74 @@ enum VcsError { SubmoduleError(vcs:Vcs, repo:String, stderr:String); } -/** Exception thrown when a vcs update is cancelled. **/ -class VcsUpdateCancelled extends haxe.Exception {} - /** Base implementation of `IVcs` for `Git` and `Mercurial` to extend. **/ abstract class Vcs implements IVcs { /** If set to true, recursive cloning is disabled **/ - public static var flat = false; - - public final name:String; - public final directory:String; public final executable:String; public var available(get, null):Bool; - var availabilityChecked = false; - var executableSearched = false; + private var availabilityChecked = false; - function new(executable:String, directory:String, name:String) { - this.name = name; - this.directory = directory; + function new(executable:String, ?debugLog:(message:String) -> Void, ?optional:(message:String) -> Void) { this.executable = executable; + if (debugLog != null) + this.debugLog = debugLog; + if (optionalLog != null) + this.optionalLog = optionalLog; } - static var reg:Map; - - /** Returns the Vcs instance for `id`. **/ - public static function get(id:VcsID):Null { - if (reg == null) - reg = [ - VcsID.Git => new Git("git", "git", "Git"), - VcsID.Hg => new Mercurial("hg", "hg", "Mercurial") - ]; + /** + Creates and returns a Vcs instance for `id`. - return reg.get(id); + If `debugLog` is specified, it is used to log debug information + for executable calls. + **/ + public static function create(id:VcsID, ?debugLog:(message:String)->Void, ?optionalLog:(message:String)->Void):Null { + return switch id { + case Hg: + new Mercurial("hg", debugLog); + case Git: + new Git("git", debugLog, optionalLog); + }; } /** Returns the sub directory to use for library versions of `id`. **/ - public static function getDirectoryFor(id:VcsID):String { - return switch (get(id)) { - case null: throw 'Unable to get directory for $id'; - case vcs: vcs.directory; + public static function getDirectoryFor(id:VcsID) { + return switch id { + case Git: "git"; + case Hg: "hg"; } } - static function set(id:VcsID, vcs:Vcs, ?rewrite:Bool):Void { - final existing = reg.get(id) != null; - if (!existing || rewrite) - reg.set(id, vcs); - } - - /** Returns the relevant Vcs if a vcs version is installed at `libPath`. **/ - public static function getVcsForDevLib(libPath:String):Null { - for (k in reg.keys()) { - if (FileSystem.exists(libPath + "/" + k) && FileSystem.isDirectory(libPath + "/" + k)) - return reg.get(k); - } - return null; - } + dynamic function debugLog(msg:String) {} - function searchExecutable():Void { - executableSearched = true; - } + dynamic function optionalLog(msg:String) {} - function checkExecutable():Bool { - available = (executable != null) && try run([]).code == 0 catch(_:Dynamic) false; - availabilityChecked = true; + abstract function searchExecutable():Bool; - if (!available && !executableSearched) - searchExecutable(); + function getCheckArgs() { + return []; + } - return available; + final function checkExecutable():Bool { + return (executable != null) && try run(getCheckArgs()).code == 0 catch (_:Dynamic) false; } final function get_available():Bool { - if (!availabilityChecked) - checkExecutable(); + if (!availabilityChecked) { + available = checkExecutable() || searchExecutable(); + availabilityChecked = true; + } return available; } - final function run(args:Array, ?debugLog:(msg:String) -> Void, strict = false):{ + final function run(args:Array, strict = false):{ code:Int, out:String, err:String, } { inline function print(msg) - if (debugLog != null && msg != "") + if (msg != "") debugLog(msg); print("# Running command: " + executable + " " + args.toString() + "\n"); @@ -222,39 +202,21 @@ abstract class Vcs implements IVcs { p.close(); return ret; } - - public abstract function clone(libPath:String, vcsPath:String, ?branch:String, ?version:String, ?debugLog:(msg:String)->Void, ?optionalLog:(msg:String)->Void):Void; - - public abstract function update(?confirm:() -> Bool, ?debugLog:(msg:String) -> Void, ?summaryLog:(msg:String) -> Void):Bool; - - public abstract function getReproducibleVersion(libPath: String): VcsData; } /** Class wrapping `git` operations. **/ class Git extends Vcs { - @:allow(haxelib.api.Vcs.get) - function new(executable:String, directory:String, name:String) { - super(executable, directory, name); + @:allow(haxelib.api.Vcs.create) + function new(executable:String, ?debugLog:Null<(message:String) -> Void>, ?optionalLog:Null<(message:String) -> Void>) { + super(executable, debugLog, optionalLog); } - override function checkExecutable():Bool { - // with `help` cmd because without any cmd `git` can return exit-code = 1. - available = (executable != null) && try run(["help"]).code == 0 catch(_:Dynamic) false; - availabilityChecked = true; - - if (!available && !executableSearched) - searchExecutable(); - - return available; + override function getCheckArgs() { + return ["help"]; } - override function searchExecutable():Void { - super.searchExecutable(); - - if (available) - return; - + function searchExecutable():Bool { // if we have already msys git/cmd in our PATH final match = ~/(.*)git([\\|\/])cmd$/; for (path in Sys.getEnv("PATH").split(";")) { @@ -265,41 +227,32 @@ class Git extends Vcs { } if (checkExecutable()) - return; + return true; // look at a few default paths for (path in ["C:\\Program Files (x86)\\Git\\bin", "C:\\Progra~1\\Git\\bin"]) { if (FileSystem.exists(path)) { Sys.putEnv("PATH", Sys.getEnv("PATH") + ";" + path); if (checkExecutable()) - return; + return true; } } + return false; } - public function update(?confirm:()->Bool, ?debugLog:(msg:String)->Void, ?_):Bool { - if ( - run(["diff", "--exit-code", "--no-ext-diff"], debugLog).code != 0 - || run(["diff", "--cached", "--exit-code", "--no-ext-diff"], debugLog).code != 0 - ) { - if (confirm == null || !confirm()) - throw new VcsUpdateCancelled('$name update in ${Sys.getCwd()} was cancelled'); - run(["reset", "--hard"], debugLog, true); - } - - run(["fetch"], debugLog, true); + public function checkRemoteChanges():Bool { + run(["fetch"], true); // `git rev-parse @{u}` will fail if detached - final checkUpstream = run(["rev-parse", "@{u}"], debugLog); - - if (checkUpstream.out == run(["rev-parse", "HEAD"], debugLog, true).out) - return false; // already up to date + return run(["rev-parse", "@{u}"]).out != run(["rev-parse", "HEAD"], true).out; + } + public function mergeRemoteChanges() { // But if before we pulled specified branch/tag/rev => then possibly currently we haxe "HEAD detached at ..". - if (checkUpstream.code != 0) { + if (run(["rev-parse", "@{u}"]).code != 0) { // get parent-branch: final branch = { - final raw = run(["show-branch"], debugLog).out; + final raw = run(["show-branch"]).out; final regx = ~/\[([^]]*)\]/; if (regx.match(raw)) regx.matched(1); @@ -307,59 +260,56 @@ class Git extends Vcs { raw; } - run(["checkout", branch, "--force"], debugLog, true); + run(["checkout", branch, "--force"], true); } - run(["merge"], debugLog, true); - return true; + run(["merge"], true); } - public function clone(libPath:String, url:String, ?branch:String, ?version:String, ?debugLog:(msg:String)->Void, ?optionalLog:(msg:String)->Void):Void { + public function clone(libPath:String, data:VcsData, flat = false):Void { final oldCwd = Sys.getCwd(); - var vcsArgs = ["clone", url, libPath]; + final vcsArgs = ["clone", data.url, libPath]; - inline function printOptional(msg) - if (optionalLog != null && msg != "") - optionalLog(msg); + optionalLog('Cloning ${VcsID.Git.getName()} from ${data.url}'); - printOptional('Cloning ${name} from ${url}'); - - if (run(vcsArgs, debugLog).code != 0) - throw VcsError.CantCloneRepo(this, url/*, ret.out*/); + if (run(vcsArgs).code != 0) + throw VcsError.CantCloneRepo(this, data.url/*, ret.out*/); Sys.setCwd(libPath); - if (version != null && version != "") { - printOptional('Checking out tag/version ${version} of ${name}'); + final branch = data.commit ?? data.branch; + + if (data.tag != null) { + optionalLog('Checking out tag/version ${data.tag} of ${VcsID.Git.getName()}'); - final ret = run(["checkout", "tags/" + version], debugLog); + final ret = run(["checkout", "tags/" + data.tag]); if (ret.code != 0) { Sys.setCwd(oldCwd); - throw VcsError.CantCheckoutVersion(this, version, ret.out); + throw VcsError.CantCheckoutVersion(this, data.tag, ret.out); } } else if (branch != null) { - printOptional('Checking out branch/commit ${branch} of ${libPath}'); + optionalLog('Checking out branch/commit ${branch} of ${libPath}'); - final ret = run(["checkout", branch], debugLog); + final ret = run(["checkout", branch]); if (ret.code != 0){ Sys.setCwd(oldCwd); throw VcsError.CantCheckoutBranch(this, branch, ret.out); } } - if (!Vcs.flat) + if (!flat) { - printOptional('Syncing submodules for ${name}'); - run(["submodule", "sync", "--recursive"], debugLog); + optionalLog('Syncing submodules for ${VcsID.Git.getName()}'); + run(["submodule", "sync", "--recursive"]); var submoduleArgs = ["submodule", "update", "--init", "--recursive"]; - printOptional('Downloading/updating submodules for ${name}'); - final ret = run(submoduleArgs, debugLog); + optionalLog('Downloading/updating submodules for ${VcsID.Git.getName()}'); + final ret = run(submoduleArgs); if (ret.code != 0) { Sys.setCwd(oldCwd); - throw VcsError.SubmoduleError(this, url, ret.out); + throw VcsError.SubmoduleError(this, data.url, ret.out); } } @@ -380,22 +330,26 @@ class Git extends Vcs { Sys.setCwd(oldCwd); return ret; } + + public function hasLocalChanges():Bool { + return run(["diff", "--exit-code", "--no-ext-diff"]).code != 0 + || run(["diff", "--cached", "--exit-code", "--no-ext-diff"]).code != 0; + } + + public function resetLocalChanges() { + run(["reset", "--hard"], true); + } } /** Class wrapping `hg` operations. **/ class Mercurial extends Vcs { - @:allow(haxelib.api.Vcs.get) - function new(executable:String, directory:String, name:String) { - super(executable, directory, name); + @:allow(haxelib.api.Vcs.create) + function new(executable:String, ?debugLog:Null<(message:String) -> Void>) { + super(executable, debugLog); } - override function searchExecutable():Void { - super.searchExecutable(); - - if (available) - return; - + function searchExecutable():Bool { // if we have already msys git/cmd in our PATH final match = ~/(.*)hg([\\|\/])cmd$/; for(path in Sys.getEnv("PATH").split(";")) { @@ -404,58 +358,57 @@ class Mercurial extends Vcs { Sys.putEnv("PATH", Sys.getEnv("PATH") + ";" + newPath); } } - checkExecutable(); + return checkExecutable(); } - public function update(?confirm:()->Bool, ?debugLog:(msg:String)->Void, ?summaryLog:(msg:String)->Void):Bool { - inline function log(msg:String) if(summaryLog != null) summaryLog(msg); - - run(["pull"], debugLog); - var summary = run(["summary"], debugLog).out; - final diff = run(["diff", "-U", "2", "--git", "--subrepos"], debugLog); - final status = run(["status"], debugLog); + public function checkRemoteChanges():Bool { + run(["pull"]); // get new pulled changesets: - // (and search num of sets) - summary = summary.substr(0, summary.length - 1); - summary = summary.substr(summary.lastIndexOf("\n") + 1); + final summary = { + final out = run(["summary"]).out.rtrim(); + out.substr(out.lastIndexOf("\n") + 1); + }; + // we don't know any about locale then taking only Digit-exising:s - final changed = ~/(\d)/.match(summary); - if (changed) - // print new pulled changesets: - log(summary); - - if (diff.code + status.code + diff.out.length + status.out.length != 0) { - log(diff.out); - if (confirm == null || !confirm()) - throw new VcsUpdateCancelled('$name update in ${Sys.getCwd()} was cancelled'); - run(["update", "--clean"], debugLog, true); - } else if (changed) { - run(["update"], debugLog, true); - } + return ~/(\d)/.match(summary); + } - return changed; + public function mergeRemoteChanges() { + run(["update"], true); } - public function clone(libPath:String, url:String, ?branch:String, ?version:String, ?debugLog:(msg:String)->Void, ?optionalLog:(msg:String)->Void):Void { - final vcsArgs = ["clone", url, libPath]; + public function clone(libPath:String, data:VcsData, _ = false):Void { + final vcsArgs = ["clone", data.url, libPath]; - if (branch != null && version != null) { + if (data.branch != null) { vcsArgs.push("--branch"); - vcsArgs.push(branch); + vcsArgs.push(data.branch); + } + if (data.commit != null) { vcsArgs.push("--rev"); - vcsArgs.push(version); - } else if (branch != null) { - vcsArgs.push("--updaterev"); - vcsArgs.push(branch); - } else if (version != null) { + vcsArgs.push(data.commit); + } + + if (data.tag != null) { vcsArgs.push("--updaterev"); - vcsArgs.push(version); + vcsArgs.push(data.tag); } - if (run(vcsArgs, debugLog).code != 0) - throw VcsError.CantCloneRepo(this, url/*, ret.out*/); + if (run(vcsArgs).code != 0) + throw VcsError.CantCloneRepo(this, data.url/*, ret.out*/); + } + + public function hasLocalChanges():Bool { + final diff = run(["diff", "-U", "2", "--git", "--subrepos"]); + final status = run(["status"]); + + return diff.code + status.code + diff.out.length + status.out.length > 0; + } + + public function resetLocalChanges() { + run(["revert", "--all"], true); } public function getReproducibleVersion(libPath: String): VcsData { diff --git a/src/haxelib/client/Main.hx b/src/haxelib/client/Main.hx index a4ef4ab7..3fc40c3f 100644 --- a/src/haxelib/client/Main.hx +++ b/src/haxelib/client/Main.hx @@ -29,9 +29,9 @@ import haxe.iterators.ArrayIterator; import sys.FileSystem; import sys.io.File; -import haxelib.api.*; import haxelib.VersionData.VcsID; -import haxelib.api.LibraryData; +import haxelib.api.*; +import haxelib.api.LibraryData.Version; import haxelib.client.Args; import haxelib.Util.rethrow; @@ -58,6 +58,7 @@ class Main { final command:Command; final mainArgs:Array; + final flags:Array; final argsIterator:ArrayIterator; final useGlobalRepo:Bool; @@ -73,10 +74,6 @@ class Main { else if (args.flags.contains(Debug)) Cli.mode = Debug; - if (args.flags.contains(SkipDependencies)) - Installer.skipDependencies = true; - Vcs.flat = args.flags.contains(Flat); - // connection setup if (args.flags.contains(NoTimeout)) Connection.hasTimeout = false; @@ -96,6 +93,7 @@ class Main { command = args.command; mainArgs = args.mainArgs; + flags = args.flags; argsIterator = mainArgs.iterator(); useGlobalRepo = args.flags.contains(Global); @@ -434,7 +432,10 @@ class Main { logInstallationProgress: (Cli.mode == Debug) ? Cli.printInstallStatus: null, logDownloadProgress: (Cli.mode != Quiet) ? Cli.printDownloadStatus : null } - return new Installer(scope, userInterface); + final installer = new Installer(scope, userInterface); + installer.skipDependencies = flags.contains(SkipDependencies); + installer.noVcsSubmodules = flags.contains(Flat); + return installer; } function install() { @@ -735,14 +736,9 @@ class Main { } function vcs(id:VcsID) { - // Prepare check vcs.available: - final vcs = Vcs.get(id); - if (vcs == null || !vcs.available) - throw 'Could not use $id, please make sure it is installed and available in your PATH.'; - // get args final library = ProjectName.ofString(getArgument("Library name")); - final url = getArgument(vcs.name + " path"); + final url = getArgument(id.getName() + " path"); final ref = argsIterator.next(); final isRefHash = ref == null || LibraryData.isCommitHash(ref); @@ -898,7 +894,7 @@ class Main { final scope = getScope(); final installer = setupAndGetInstaller(scope); Cli.defaultAnswer = Always; - Installer.skipDependencies = true; + installer.skipDependencies = true; if (sys.FileSystem.exists(manifest) && !sys.FileSystem.isDirectory(manifest)) { return installer.installFromHxml(manifest, confirmHxmlInstall); From 3228e7459aa3b2f97ba82801865c5f1fc35f914f Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sat, 28 Jun 2025 15:39:24 +0100 Subject: [PATCH 03/19] Refactor installer update code Remove use of the AlreadyUpToDate exception control flow See: https://github.com/HaxeFoundation/haxelib/pull/510#discussion_r842757008 --- src/haxelib/api/GlobalScope.hx | 10 +++ src/haxelib/api/Installer.hx | 140 ++++++++++++++++++--------------- src/haxelib/api/Scope.hx | 5 ++ 3 files changed, 91 insertions(+), 64 deletions(-) diff --git a/src/haxelib/api/GlobalScope.hx b/src/haxelib/api/GlobalScope.hx index a4b95de0..d0b3634c 100644 --- a/src/haxelib/api/GlobalScope.hx +++ b/src/haxelib/api/GlobalScope.hx @@ -273,4 +273,14 @@ class GlobalScope extends Scope { return {path: path, version: current}; } + public function resolve(library:ProjectName):VersionData { + final version = repository.getCurrentVersion(library); + + return switch version { + case vcs if (VcsID.isValid(vcs)): + VcsInstall(VcsID.ofString(vcs), {url: ""}); + case semVer: + Haxelib(SemVer.ofString(semVer)); + }; + } } diff --git a/src/haxelib/api/Installer.hx b/src/haxelib/api/Installer.hx index 7da982e8..27e3f5a9 100644 --- a/src/haxelib/api/Installer.hx +++ b/src/haxelib/api/Installer.hx @@ -140,8 +140,6 @@ private class InstallData { } } -private class AlreadyUpToDate extends InstallationException {} - private function getLatest(versions:Array):SemVer { if (versions.length == 0) throw 'Library has not yet released a version'; @@ -368,14 +366,8 @@ class Installer { library = getVcsLibraryName(library, id, vcsData.subDir); - scope.setVcsVersion(library, id, vcsData); + setVcsVersion(library, id, vcsData); - if (vcsData.subDir != null) { - final path = scope.getPath(library); - userInterface.log(' Development directory set to $path'); - } else { - userInterface.log(' Current version is now $id'); - } userInterface.log("Done"); handleDependenciesVcs(library, id, vcsData.subDir); @@ -386,15 +378,68 @@ class Installer { or pull latest changes with git or hg. **/ public function update(library:ProjectName) { + final version = scope.resolve(library); + library = getCorrectName(library, version); + // check if update is needed + if (isUpToDate(library, version)) { + userInterface.log('Library $library is already up to date'); + return; + } + try { - updateIfNeeded(library); - } catch (e:AlreadyUpToDate) { - userInterface.log(e.toString()); + updateResolved(library, version); } catch (e:UpdateCancelled) { + // perhaps we should exit with an error? return; } } + function getCorrectName(library:ProjectName, versionData:VersionData) { + return switch versionData { + case VcsInstall(version, {subDir: subDir}): + getVcsLibraryName(library, version, subDir); + case Haxelib(_): + ProjectName.ofString(Connection.getInfo(library).name); + }; + } + + function isUpToDate(library:ProjectName, versionData:VersionData):Bool { + return switch versionData { + case Haxelib(version): + version == Connection.getLatestVersion(library); + case VcsInstall(version, _): + final vcs = getVcs(version); + !FsUtils.runInDirectory(repository.getVersionPath(library, version), vcs.checkRemoteChanges); + }; + } + + function updateResolved(library:ProjectName, versionData:VersionData) + switch versionData { + case VcsInstall(version, vcsData): + final vcs = getVcs(version); + // with version locking we'll be able to be smarter with this + updateVcs(library, version, vcs); + + setVcsVersion(library, version, vcsData); + + // TODO: Properly handle sub directories + handleDependenciesVcs(library, version, null); + case Haxelib(_): + final latest = Connection.getLatestVersion(library); + if (repository.isVersionInstalled(library, latest)) { + userInterface.log('Latest version $latest of $library is already installed'); + // only ask if running in a global scope + if (!scope.isLocal && !userInterface.confirm('Set $library to $latest')) + return; + } else { + downloadAndInstall(library, latest); + } + scope.setVersion(library, latest); + userInterface.log(' Current version is now $latest'); + userInterface.log("Done"); + handleDependencies(library, latest); + } + /** Updates all libraries in the scope. @@ -407,14 +452,18 @@ class Installer { for (library in libraries) { userInterface.log('Checking $library'); + + final version = scope.resolve(library); + if (isUpToDate(library, version)) { + continue; + } + try { - updateIfNeeded(library); + updateResolved(library, version); updated = true; - } catch(e:AlreadyUpToDate) { - continue; } catch (e:UpdateCancelled) { continue; - } catch(e) { + } catch (e) { ++failures; userInterface.log("Failed to update: " + e.toString()); userInterface.log(e.stack.toString(), Debug); @@ -431,47 +480,6 @@ class Installer { userInterface.log("All libraries are already up-to-date"); } - function updateIfNeeded(library:ProjectName) { - final current = try scope.getVersion(library) catch (_:CurrentVersionException) null; - - final vcsId = try VcsID.ofString(current) catch (_) null; - if (vcsId != null) { - final vcs = getVcs(vcsId); - if (!FsUtils.runInDirectory(repository.getVersionPath(library, vcsId), vcs.checkRemoteChanges)) - throw new AlreadyUpToDate('Library $library $vcsId repository is already up to date'); - // with version locking we'll be able to be smarter with this - updateVcs(library, vcsId, vcs); - - scope.setVcsVersion(library, vcsId); - - handleDependenciesVcs(library, vcsId, null); - // we dont know if a subdirectory was given anymore - return; - } - - final semVer = try SemVer.ofString(current) catch (_) null; - - final info = Connection.getInfo(library); - final library = ProjectName.ofString(info.name); - final latest = info.getLatest(); - - if (semVer != null && semVer == latest) { - throw new AlreadyUpToDate('Library $library is already up to date'); - } else if (repository.isVersionInstalled(library, latest)) { - userInterface.log('Latest version $latest of $library is already installed'); - // only ask if running in a global scope - if (!scope.isLocal && !userInterface.confirm('Set $library to $latest')) - return; - } else { - downloadAndInstall(library, latest); - } - scope.setVersion(library, latest); - userInterface.log(' Current version is now $latest'); - userInterface.log("Done"); - - handleDependencies(library, latest); - } - function getDependencies(path:String):Dependencies { final jsonPath = path + Data.JSON; if (!FileSystem.exists(jsonPath)) @@ -584,19 +592,23 @@ class Installer { function setVersionAndLog(library:ProjectName, installData:VersionData) { switch installData { case VcsInstall(version, vcsData): - scope.setVcsVersion(library, version, vcsData); - if (vcsData.subDir == null){ - userInterface.log(' Current version is now $version'); - } else { - final path = scope.getPath(library); - userInterface.log(' Development directory set to $path'); - } + setVcsVersion(library, version, vcsData); case Haxelib(version): scope.setVersion(library, version); userInterface.log(' Current version is now $version'); } } + function setVcsVersion(library:ProjectName, version:VcsID, data:VcsData) { + scope.setVcsVersion(library, version, data); + if (data.subDir == null) { + userInterface.log(' Current version is now $version'); + } else { + final path = scope.getPath(library); + userInterface.log(' Development directory set to $path'); + } + } + static function getInstallData(libs:List<{name:ProjectName, data:Option}>):List { final installData = new List(); diff --git a/src/haxelib/api/Scope.hx b/src/haxelib/api/Scope.hx index 9b50076b..929ae67e 100644 --- a/src/haxelib/api/Scope.hx +++ b/src/haxelib/api/Scope.hx @@ -127,6 +127,11 @@ abstract class Scope { abstract function resolveCompiler():LibraryData; + /** + Returns the full version data for `library`. + **/ + public abstract function resolve(library:ProjectName):VersionData; + // TODO: placeholders until https://github.com/HaxeFoundation/haxe/wiki/Haxe-haxec-haxelib-plan static function loadOverrides():LockFormat { return {}; From f6bfa64bba0fb4089ae166d1c0860a39cafa22e5 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Mon, 30 Jun 2025 22:18:30 +0100 Subject: [PATCH 04/19] Keep vcs install data in .data file This allows us to keep track of how a library was installed, and therefore will allow for consistent update behaviour even if a shallow clone is performed. --- src/haxelib/VersionData.hx | 29 ++++++++++++++++++ src/haxelib/api/GlobalScope.hx | 7 +++-- src/haxelib/api/Installer.hx | 53 +++++++++++++++++++++++--------- src/haxelib/api/Repository.hx | 43 +++++++++++++++++++++++--- src/haxelib/api/Vcs.hx | 56 +++++++++++++++++++--------------- 5 files changed, 143 insertions(+), 45 deletions(-) diff --git a/src/haxelib/VersionData.hx b/src/haxelib/VersionData.hx index 102a74a7..e66880d0 100644 --- a/src/haxelib/VersionData.hx +++ b/src/haxelib/VersionData.hx @@ -61,6 +61,35 @@ class VcsData { else url; } + + public function isReproducible() { + return commit != null; + } + + /** + Returns an object containing the filled-in VcsData fields, + without the empty ones. + **/ + public function getCleaned() { + var data:{ + url:String, + ?commit:String, + ?tag:String, + ?branch:String, + ?subDir:String + } = { url : url }; + + if (commit != null) + data.commit = commit; + if (tag != null) + data.tag = tag; + if (!(branch == null || branch == "")) + data.branch = branch; + if (!(subDir == null || haxe.io.Path.normalize(subDir) == "")) + data.subDir = subDir; + + return data; + } } /** Data required to reproduce a library version **/ diff --git a/src/haxelib/api/GlobalScope.hx b/src/haxelib/api/GlobalScope.hx index d0b3634c..65dfde56 100644 --- a/src/haxelib/api/GlobalScope.hx +++ b/src/haxelib/api/GlobalScope.hx @@ -59,7 +59,9 @@ class GlobalScope extends Scope { } public function setVcsVersion(library:ProjectName, vcsVersion:VcsID, ?data:VcsData):Void { - if (data == null) data = {url: "unknown"}; + if (data != null) { + repository.setVcsData(library, vcsVersion, data); + } if (data.subDir != null) { final devDir = repository.getValidVersionPath(library, vcsVersion) + data.subDir; @@ -278,7 +280,8 @@ class GlobalScope extends Scope { return switch version { case vcs if (VcsID.isValid(vcs)): - VcsInstall(VcsID.ofString(vcs), {url: ""}); + final vcsId = VcsID.ofString(vcs); + VcsInstall(vcsId, repository.getVcsData(library, vcsId)); case semVer: Haxelib(SemVer.ofString(semVer)); }; diff --git a/src/haxelib/api/Installer.hx b/src/haxelib/api/Installer.hx index 27e3f5a9..8778580d 100644 --- a/src/haxelib/api/Installer.hx +++ b/src/haxelib/api/Installer.hx @@ -179,7 +179,7 @@ class Installer { final repository:Repository; final userInterface:UserInterface; - final vcsBranchesByLibraryName = new Map(); + final vcsDataByName = new Map(); /** Creates a new Installer object that installs projects to `scope`. @@ -217,15 +217,15 @@ class Installer { } /** - Clears memory on git or hg library branches. + Clears cached data for git or hg libraries. An installer instance keeps track of updated vcs dependencies - to avoid cloning the same branch twice. + to avoid cloning the same version twice. This function can be used to clear that memory. **/ - public function forgetVcsBranches():Void { - vcsBranchesByLibraryName.clear(); + public function forgetVcsDataCache():Void { + vcsDataByName.clear(); } /** Installs libraries from the `haxelib.json` file at `path`. **/ @@ -418,8 +418,10 @@ class Installer { case VcsInstall(version, vcsData): final vcs = getVcs(version); // with version locking we'll be able to be smarter with this + final libPath = repository.getVersionPath(library, version); updateVcs(library, version, vcs); + vcsData.commit = FsUtils.runInDirectory(libPath, vcs.getRef); setVcsVersion(library, version, vcsData); // TODO: Properly handle sub directories @@ -599,7 +601,32 @@ class Installer { } } + function getReproducibleVcsData(library:ProjectName, version:VcsID, data:VcsData):VcsData { + final vcs = getVcs(version); + final libPath = repository.getVersionPath(library, version); + return FsUtils.runInDirectory(libPath, function():VcsData { + return { + url: data.url, + commit: data.commit ?? vcs.getRef(), + branch: if (data.branch == null && data.tag == null) vcs.getBranchName() else data.branch, + tag: data.tag, + subDir: if (data.subDir != null) haxe.io.Path.normalize(data.subDir) else null + }; + }); + } + + /** + Retrieves fully reproducible vcs data if necessary, + and then uses it to lock down the current version. + **/ function setVcsVersion(library:ProjectName, version:VcsID, data:VcsData) { + // save here prior to modification + vcsDataByName[library] = data; + if (!data.isReproducible()) { + // always get reproducible data for local scope + data = getReproducibleVcsData(library, version, data); + } + scope.setVcsVersion(library, version, data); if (data.subDir == null) { userInterface.log(' Current version is now $version'); @@ -818,15 +845,14 @@ class Installer { if (repository.isVersionInstalled(library, id)) { userInterface.log('You already have $library version $id installed.'); - final wasUpdated = vcsBranchesByLibraryName.exists(library); - // difference between a key not having a value and the value being null + final wasUpdated = vcsDataByName.exists(library); - final currentBranch = vcsBranchesByLibraryName[library]; + final currentData = vcsDataByName[library]; // TODO check different urls as well - if (branch != null && (!wasUpdated || currentBranch != branch)) { - final currentBranchStr = currentBranch != null ? currentBranch : ""; - if (!userInterface.confirm('Overwrite branch: "$currentBranchStr" with "$branch"')) { + if (vcsData.branch != null && (!wasUpdated || currentData.branch != vcsData.branch)) { + final currentBranchStr = currentData.branch != null ? currentData.branch : ""; + if (!userInterface.confirm('Overwrite branch: "$currentBranchStr" with "${vcsData.branch}"')) { userInterface.log('Library $library $id repository remains at "$currentBranchStr"'); return; } @@ -837,16 +863,13 @@ class Installer { updateVcs(library, id, vcs); } else { userInterface.log('Library $library version $id already up to date'); - if (wasUpdated) { - return; - } } } else { FsUtils.safeDir(libPath); doVcsClone(); } - vcsBranchesByLibraryName[library] = branch; + vcsData.commit = FsUtils.runInDirectory(libPath, vcs.getRef); } function updateVcs(library:ProjectName, id:VcsID, vcs:Vcs) diff --git a/src/haxelib/api/Repository.hx b/src/haxelib/api/Repository.hx index 0e58246b..82c4cc49 100644 --- a/src/haxelib/api/Repository.hx +++ b/src/haxelib/api/Repository.hx @@ -4,6 +4,7 @@ import haxelib.VersionData.VcsData; import sys.FileSystem; import sys.io.File; +import haxelib.VersionData.VcsData; import haxelib.VersionData.VcsID; import haxelib.api.RepoManager; import haxelib.api.LibraryData; @@ -282,6 +283,44 @@ class Repository { return getProjectVersionPath(name, version); } + private function getVcsDataPath(name:ProjectName, version:VcsID) { + return haxe.io.Path.join([getProjectPath(name), '.${version}data']); + } + + public function setVcsData(name:ProjectName, version:VcsID, vcsData:VcsData) { + File.saveContent( + getVcsDataPath(name, version), + haxe.Json.stringify(vcsData.getCleaned(), "\t") + ); + } + + public function getVcsData(name:ProjectName, version:VcsID):VcsData { + final vcsDataPath = getVcsDataPath(name, version); + if (!FileSystem.exists(vcsDataPath) || FileSystem.isDirectory(vcsDataPath)) { + final versionPath = getProjectVersionPath(name, version); + + if (!FileSystem.exists(versionPath)) { + throw 'Library $name version $version is not installed'; + } + + return FsUtils.runInDirectory(versionPath, function():VcsData { + final vcs = Vcs.create(version); + return { + url: vcs.getOriginUrl(), + commit: vcs.getRef() + }; + } ); + } + final data = haxe.Json.parse(File.getContent(vcsDataPath)); + return { + url: data.url, + commit: data.commit, + tag: data.tag, + branch: data.branch, + subDir: data.subDir + }; + } + /** Returns the correctly capitalized name for library `name`. @@ -471,8 +510,4 @@ class Repository { function getDevFilePath(name:ProjectName):String { return addToRepoPath(name, DEV_FILE); } - - public function getVcsData(name: ProjectName, version: VcsID): VcsData { - return Vcs.create(version).getReproducibleVersion(getProjectVersionPath(name, version)); - } } diff --git a/src/haxelib/api/Vcs.hx b/src/haxelib/api/Vcs.hx index aa833ddc..775a25cf 100644 --- a/src/haxelib/api/Vcs.hx +++ b/src/haxelib/api/Vcs.hx @@ -61,7 +61,11 @@ private interface IVcs { **/ function resetLocalChanges():Void; - function getReproducibleVersion(libPath: String): VcsData; + function getRef():String; + + function getOriginUrl():String; + + function getBranchName():Null; } /** Enum representing errors that can be thrown during a vcs operation. **/ @@ -317,18 +321,19 @@ class Git extends Vcs { Sys.setCwd(oldCwd); } - public function getReproducibleVersion(libPath: String): VcsData { - final oldCwd = Sys.getCwd(); - Sys.setCwd(libPath); + public function getRef():String { + return run(["rev-parse", "--verify", "HEAD"], true).out.trim(); + } - final ret: VcsData = { - // could the remote's name not be "origin"? - url: run(["remote", "get-url", "origin"], true).out.trim(), - commit: run(["rev-parse", "HEAD"], true).out.trim(), - }; + public function getOriginUrl():String { + return run(["ls-remote", "--get-url", "origin"], true).out.trim(); + } - Sys.setCwd(oldCwd); - return ret; + public function getBranchName():Null { + final ret = run(["symbolic-ref", "--short", "HEAD"]); + if (ret.code != 0) + return null; + return ret.out.trim(); } public function hasLocalChanges():Bool { @@ -400,6 +405,22 @@ class Mercurial extends Vcs { throw VcsError.CantCloneRepo(this, data.url/*, ret.out*/); } + public function getRef():String { + final out = run(["identify", "-i"], true).out.trim(); + // if the hash ends with +, there are edits + if (StringTools.endsWith(out, "+")) + return out.substr(0, out.length - 2); + return out; + } + + public function getOriginUrl():String { + return run(["paths", "default"], true).out.trim(); + } + + public function getBranchName():Null { + return run(["identify", "-b"], true).out.trim(); + } + public function hasLocalChanges():Bool { final diff = run(["diff", "-U", "2", "--git", "--subrepos"]); final status = run(["status"]); @@ -410,17 +431,4 @@ class Mercurial extends Vcs { public function resetLocalChanges() { run(["revert", "--all"], true); } - - public function getReproducibleVersion(libPath: String): VcsData { - final oldCwd = Sys.getCwd(); - Sys.setCwd(libPath); - - final ret: VcsData = { - url: run(["paths", "default"], true).out.trim(), - commit: run(["identify", "-i", "--debug"], true).out.trim(), - }; - - Sys.setCwd(oldCwd); - return ret; - } } From 756e98bcdb28a0b23a935c35d95a7945fd57d32b Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sun, 6 Jul 2025 21:30:46 +0100 Subject: [PATCH 05/19] Disable updates with git/hg install without branch If only a commit or tag was specified, there is no simple way to determine what an update is meant to do. The commmit may belong to multiple branches, and there may be multiple newer tags which have different purposes (e.g. stable vs alpha release tags). It makes more sense to simply lock to the specified commit when a user installs this way. Also, the git show-branch method would not work with a shallow git history. For hg, to achieve the same behaviour as git it is necessary to strip any newer changesets to the checked out one and also remove the reference to the upstream repository so that no further updates are pulled. The upstream is still stored as `haxelib_url` so it may still be retrieved by Hg.getOriginUrl. --- src/haxelib/api/Vcs.hx | 43 +++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/haxelib/api/Vcs.hx b/src/haxelib/api/Vcs.hx index 775a25cf..cc9e96f3 100644 --- a/src/haxelib/api/Vcs.hx +++ b/src/haxelib/api/Vcs.hx @@ -248,25 +248,15 @@ class Git extends Vcs { run(["fetch"], true); // `git rev-parse @{u}` will fail if detached - return run(["rev-parse", "@{u}"]).out != run(["rev-parse", "HEAD"], true).out; + final checkUpstream = run(["rev-parse", "@{u}"]); + if (checkUpstream.code != 0) { + return false; + } + return checkUpstream.out != run(["rev-parse", "HEAD"], true).out; } public function mergeRemoteChanges() { - // But if before we pulled specified branch/tag/rev => then possibly currently we haxe "HEAD detached at ..". - if (run(["rev-parse", "@{u}"]).code != 0) { - // get parent-branch: - final branch = { - final raw = run(["show-branch"]).out; - final regx = ~/\[([^]]*)\]/; - if (regx.match(raw)) - regx.matched(1); - else - raw; - } - - run(["checkout", branch, "--force"], true); - } - run(["merge"], true); + run(["reset", "--hard", "@{u}"], true); } public function clone(libPath:String, data:VcsData, flat = false):Void { @@ -403,6 +393,22 @@ class Mercurial extends Vcs { if (run(vcsArgs).code != 0) throw VcsError.CantCloneRepo(this, data.url/*, ret.out*/); + + if (data.branch == null && !(data.commit == null && data.tag == null)) { + FsUtils.runInDirectory(libPath, function() { + final rcFile = '.hg/hgrc'; + sys.io.File.saveContent(rcFile, + sys.io.File.getContent(rcFile) + // unlink from upstream so updates stick to specified commit/tag + .replace("default =", '# default =') + // still store url in "haxelib_url" so we can retrieve it if needed + .replace("[paths]", '[paths]\nhaxelib_url = ${data.url}') + + "\n[extensions]\nstrip =\n" + ); + // also strip to get rid of newer changesets we have already cloned + run(["strip", data.tag]); + }); + } } public function getRef():String { @@ -414,7 +420,10 @@ class Mercurial extends Vcs { } public function getOriginUrl():String { - return run(["paths", "default"], true).out.trim(); + final ret = run(["paths", "default"]); + if (ret.code == 0) + return ret.out.trim(); + return run(["paths", "haxelib_url"], true).out.trim(); } public function getBranchName():Null { From 73fe837923a0d90c2dafeb48c2921d3685d0e3ca Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sat, 5 Jul 2025 17:19:06 +0100 Subject: [PATCH 06/19] Clone git libraries with --depth=1 where possible Instead of creating a separate CantCheckoutCommit enum constructor, they have been merged together into CantCheckout --- src/haxelib/api/Installer.hx | 8 ++- src/haxelib/api/Vcs.hx | 99 ++++++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/src/haxelib/api/Installer.hx b/src/haxelib/api/Installer.hx index 8778580d..51e11192 100644 --- a/src/haxelib/api/Installer.hx +++ b/src/haxelib/api/Installer.hx @@ -828,12 +828,10 @@ class Installer { switch (error) { case VcsUnavailable(vcs): throw 'Could not use ${vcs.executable}, please make sure it is installed and available in your PATH.'; - case CantCloneRepo(vcs, _, stderr): + case CantCloneRepo(_, _, stderr): throw 'Could not clone ${id.getName()} repository' + (stderr != null ? ":\n" + stderr : "."); - case CantCheckoutBranch(_, branch, stderr): - throw 'Could not checkout branch, tag or path "$branch": ' + stderr; - case CantCheckoutVersion(_, version, stderr): - throw 'Could not checkout tag "$version": ' + stderr; + case CantCheckout(_, ref, stderr): + throw 'Could not checkout commit or tag "$ref": ' + stderr; case SubmoduleError(_, repo, stderr): throw 'Could not clone submodule(s) from $repo: ' + stderr; case CommandFailed(_, code, stdout, stderr): diff --git a/src/haxelib/api/Vcs.hx b/src/haxelib/api/Vcs.hx index cc9e96f3..ab0479e3 100644 --- a/src/haxelib/api/Vcs.hx +++ b/src/haxelib/api/Vcs.hx @@ -72,8 +72,7 @@ private interface IVcs { enum VcsError { VcsUnavailable(vcs:Vcs); CantCloneRepo(vcs:Vcs, repo:String, ?stderr:String); - CantCheckoutBranch(vcs:Vcs, branch:String, stderr:String); - CantCheckoutVersion(vcs:Vcs, version:String, stderr:String); + CantCheckout(vcs:Vcs, ref:String, stderr:String); CommandFailed(vcs:Vcs, code:Int, stdout:String, stderr:String); SubmoduleError(vcs:Vcs, repo:String, stderr:String); } @@ -245,7 +244,7 @@ class Git extends Vcs { } public function checkRemoteChanges():Bool { - run(["fetch"], true); + run(["fetch", "--depth=1"], true); // `git rev-parse @{u}` will fail if detached final checkUpstream = run(["rev-parse", "@{u}"]); @@ -260,55 +259,79 @@ class Git extends Vcs { } public function clone(libPath:String, data:VcsData, flat = false):Void { - final oldCwd = Sys.getCwd(); - final vcsArgs = ["clone", data.url, libPath]; optionalLog('Cloning ${VcsID.Git.getName()} from ${data.url}'); - if (run(vcsArgs).code != 0) - throw VcsError.CantCloneRepo(this, data.url/*, ret.out*/); + if (data.branch != null) { + vcsArgs.push('--single-branch'); + vcsArgs.push('--branch'); + vcsArgs.push(data.branch); + } else if (data.commit == null) { + vcsArgs.push('--single-branch'); + } - Sys.setCwd(libPath); + final cloneDepth1 = data.commit == null || data.commit.length == 40; + // we cannot clone like this if the commit hash is short, + // as fetch requires full hash + if (cloneDepth1) { + vcsArgs.push('--depth=1'); + } - final branch = data.commit ?? data.branch; + if (run(vcsArgs).code != 0) + throw VcsError.CantCloneRepo(this, data.url/*, ret.out*/); - if (data.tag != null) { + if (data.branch != null && data.commit != null) { + optionalLog('Checking out branch ${data.branch} at commit ${data.commit} of ${libPath}'); + FsUtils.runInDirectory(libPath, () -> { + if (cloneDepth1) { + runCheckoutRelatedCommand(data.commit, ["fetch", "--depth=1", "origin", data.commit]); + } + run(["reset", "--hard", data.commit], true); + }); + } else if (data.commit != null) { + optionalLog('Checking out commit ${data.commit} of ${libPath}'); + FsUtils.runInDirectory(libPath, checkout.bind(data.commit, cloneDepth1)); + } else if (data.tag != null) { optionalLog('Checking out tag/version ${data.tag} of ${VcsID.Git.getName()}'); - - final ret = run(["checkout", "tags/" + data.tag]); - if (ret.code != 0) { - Sys.setCwd(oldCwd); - throw VcsError.CantCheckoutVersion(this, data.tag, ret.out); - } - } else if (branch != null) { - optionalLog('Checking out branch/commit ${branch} of ${libPath}'); - - final ret = run(["checkout", branch]); - if (ret.code != 0){ - Sys.setCwd(oldCwd); - throw VcsError.CantCheckoutBranch(this, branch, ret.out); - } + FsUtils.runInDirectory(libPath, () -> { + final tagRef = 'tags/${data.tag}'; + runCheckoutRelatedCommand(tagRef, ["fetch", "--depth=1", "origin", '$tagRef:$tagRef']); + checkout('tags/${data.tag}', false); + }); } - if (!flat) - { - optionalLog('Syncing submodules for ${VcsID.Git.getName()}'); - run(["submodule", "sync", "--recursive"]); + if (!flat) { + FsUtils.runInDirectory(libPath, () -> { + optionalLog('Syncing submodules for ${VcsID.Git.getName()}'); + run(["submodule", "sync", "--recursive"]); + + optionalLog('Downloading/updating submodules for ${VcsID.Git.getName()}'); + final ret = run(["submodule", "update", "--init", "--recursive", "--depth=1", "--single-branch"]); + if (ret.code != 0) + { + throw VcsError.SubmoduleError(this, data.url, ret.out); + } + }); + } + } - var submoduleArgs = ["submodule", "update", "--init", "--recursive"]; + inline function runCheckoutRelatedCommand(ref, args:Array) { + final ret = run(args); + if (ret.code != 0) { + throw VcsError.CantCheckout(this, ref, ret.out); + } + } - optionalLog('Downloading/updating submodules for ${VcsID.Git.getName()}'); - final ret = run(submoduleArgs); - if (ret.code != 0) - { - Sys.setCwd(oldCwd); - throw VcsError.SubmoduleError(this, data.url, ret.out); - } + function checkout(ref:String, fetch:Bool) { + if (fetch) { + runCheckoutRelatedCommand(ref, ["fetch", "--depth=1", "origin", ref]); } - // return prev. cwd: - Sys.setCwd(oldCwd); + runCheckoutRelatedCommand(ref, ["checkout", ref]); + + // clean up excess branch + run(["branch", "-D", "@{-1}"]); } public function getRef():String { From c9103b73066ab2895b7f1fb59b049581189c314f Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sat, 5 Jul 2025 17:26:30 +0100 Subject: [PATCH 07/19] Avoid touching vcs install with commit mismatch With git installs we now do a hard reset on update. Previously, user committed changes would prevent a merge from occurring successfully so they wouldn't be lost, but now the local commits won't stop the hard reset, so we need to perform this check manually. --- src/haxelib/api/Installer.hx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/haxelib/api/Installer.hx b/src/haxelib/api/Installer.hx index 51e11192..c7128fd0 100644 --- a/src/haxelib/api/Installer.hx +++ b/src/haxelib/api/Installer.hx @@ -419,6 +419,14 @@ class Installer { final vcs = getVcs(version); // with version locking we'll be able to be smarter with this final libPath = repository.getVersionPath(library, version); + + FsUtils.runInDirectory( + libPath, + function() { + if (vcs.getRef() != vcsData.commit) { + throw 'Cannot update ${version.getName()} version of $library. There are local changes.'; + } + }); updateVcs(library, version, vcs); vcsData.commit = FsUtils.runInDirectory(libPath, vcs.getRef); @@ -845,7 +853,12 @@ class Installer { final wasUpdated = vcsDataByName.exists(library); - final currentData = vcsDataByName[library]; + final currentData = vcsDataByName[library] ?? repository.getVcsData(library, id); + FsUtils.runInDirectory(libPath, function() { + if (vcs.getRef() != currentData.commit) { + throw 'Cannot overwrite currently installed $id version of $library. There are local changes.'; + } + }); // TODO check different urls as well if (vcsData.branch != null && (!wasUpdated || currentData.branch != vcsData.branch)) { From d22548619ea4c8202bb5aa539ab5f4701a8ad59f Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Tue, 1 Jul 2025 00:32:18 +0100 Subject: [PATCH 08/19] Add more detail to git install logs This is useful if both a branch and commit are set --- src/haxelib/api/Installer.hx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/haxelib/api/Installer.hx b/src/haxelib/api/Installer.hx index c7128fd0..a8533958 100644 --- a/src/haxelib/api/Installer.hx +++ b/src/haxelib/api/Installer.hx @@ -824,11 +824,12 @@ class Installer { final libPath = repository.getVersionPath(library, id); - final branch = vcsData.commit != null ? vcsData.commit : vcsData.branch; - final url:String = vcsData.url; - function doVcsClone() { - userInterface.log('Installing $library from $url' + (branch != null ? " branch: " + branch : "")); + userInterface.log('Installing $library from ${vcsData.url}' + + (vcsData.branch != null ? " branch: " + vcsData.branch : "") + + (vcsData.tag != null ? " tag: " + vcsData.tag : "") + + (vcsData.commit != null ? " commit: " + vcsData.commit : "") + ); try { vcs.clone(libPath, vcsData, noVcsSubmodules); } catch (error:VcsError) { From 7526402d786f97b47b1365faa06778a2ce163eff Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Thu, 27 Oct 2022 11:15:54 +0100 Subject: [PATCH 09/19] [tests] Update vcs unit tests Updating should not do anything if only a commit/tag was specified at install. Added a new test for cloning only with a commit hash (and later updating). Merged the two tests for update with local changes, as the logic for confirming local change reset is now performed externally which leads to cleaner tests here. --- test/tests/TestGit.hx | 2 +- test/tests/TestHg.hx | 2 +- test/tests/TestVcs.hx | 187 ++++++++++++++++++++-------------- test/tests/TestVcsNotFound.hx | 29 ++---- test/tests/TestVersionData.hx | 12 +-- 5 files changed, 126 insertions(+), 106 deletions(-) diff --git a/test/tests/TestGit.hx b/test/tests/TestGit.hx index cddb0e68..0163329c 100644 --- a/test/tests/TestGit.hx +++ b/test/tests/TestGit.hx @@ -12,6 +12,6 @@ class TestGit extends TestVcs { } public function new():Void { - super(VcsID.Git, "Git", FileSystem.fullPath(REPO_PATH), "develop", "0.9.2"); + super(VcsID.Git, "git", FileSystem.fullPath(REPO_PATH), "develop", "0.9.2"); } } diff --git a/test/tests/TestHg.hx b/test/tests/TestHg.hx index ef7845b6..26f7755e 100644 --- a/test/tests/TestHg.hx +++ b/test/tests/TestHg.hx @@ -12,6 +12,6 @@ class TestHg extends TestVcs { } public function new():Void { - super(VcsID.Hg, "Mercurial", FileSystem.fullPath(REPO_PATH), "default", "b022617bccfb"); + super(VcsID.Hg, "hg", FileSystem.fullPath(REPO_PATH), "default", "b022617bccfb"); } } diff --git a/test/tests/TestVcs.hx b/test/tests/TestVcs.hx index 0c2efd20..55a39d25 100644 --- a/test/tests/TestVcs.hx +++ b/test/tests/TestVcs.hx @@ -17,7 +17,7 @@ class TestVcs extends TestBase static var CWD:String = null; final id:VcsID = null; - final vcsName:String = null; + final vcsExecutable:String = null; final url:String = null; final branch:String = null; final rev:String = null; @@ -25,13 +25,13 @@ class TestVcs extends TestBase //--------------- constructor ---------------// - public function new(id:VcsID, vcsName:String, url:String, ?branch:String, ?rev:String) { + public function new(id:VcsID, vcsExecutable:String, url:String, ?branch:String, ?rev:String) { super(); this.id = id; this.url = url; this.branch = branch; this.rev = rev; - this.vcsName = vcsName; + this.vcsExecutable = vcsExecutable; CWD = Sys.getCwd(); counter = 0; @@ -61,79 +61,84 @@ class TestVcs extends TestBase //----------------- tests -------------------// - public function testGetVcs():Void { - assertTrue(Vcs.get(id) != null); - assertTrue(Vcs.get(id).name == vcsName); + public function testCreateVcs():Void { + assertTrue(Vcs.create(id) != null); + assertTrue(Vcs.create(id).executable == vcsExecutable); } public function testAvailable():Void { - assertTrue(getVcs().available); + assertTrue(createVcs().available); } // --------------- clone --------------- // - public function testGetVcsByDir():Void { - final vcs = getVcs(); - testCloneSimple(); - - assertEquals(vcs, Vcs.get(id)); - } - public function testCloneSimple():Void { - final vcs = getVcs(); - final dir = vcs.directory + counter++; - vcs.clone(dir, url); + final vcs = createVcs(); + final dir = id.getName() + counter++; + vcs.clone(dir, {url: url}); assertTrue(FileSystem.exists(dir)); assertTrue(FileSystem.isDirectory(dir)); - assertTrue(FileSystem.exists('$dir/.${vcs.directory}')); - assertTrue(FileSystem.isDirectory('$dir/.${vcs.directory}')); + assertTrue(FileSystem.exists('$dir/.${Vcs.getDirectoryFor(id)}')); + assertTrue(FileSystem.isDirectory('$dir/.${Vcs.getDirectoryFor(id)}')); } public function testCloneBranch():Void { - final vcs = getVcs(); - final dir = vcs.directory + counter++; - vcs.clone(dir, url, branch); + final vcs = createVcs(); + final dir = id.getName() + counter++; + vcs.clone(dir, {url: url, branch: branch}); assertTrue(FileSystem.exists(dir)); assertTrue(FileSystem.isDirectory(dir)); - assertTrue(FileSystem.exists('$dir/.${vcs.directory}')); - assertTrue(FileSystem.isDirectory('$dir/.${vcs.directory}')); + assertTrue(FileSystem.exists('$dir/.${Vcs.getDirectoryFor(id)}')); + assertTrue(FileSystem.isDirectory('$dir/.${Vcs.getDirectoryFor(id)}')); } - public function testCloneBranchTag_0_9_2():Void { - final vcs = getVcs(); - final dir = vcs.directory + counter++; - vcs.clone(dir, url, branch, "0.9.2"); + public function testCloneTag_0_9_2():Void { + final vcs = createVcs(); + final dir = id.getName() + counter++; + vcs.clone(dir, {url: url, tag: "0.9.2"}); Sys.sleep(3); assertTrue(FileSystem.exists(dir)); - assertTrue(FileSystem.exists('$dir/.${vcs.directory}')); + assertTrue(FileSystem.exists('$dir/.${Vcs.getDirectoryFor(id)}')); // if that repo "README.md" was added in tag/rev.: "0.9.3" assertFalse(FileSystem.exists(dir + "/README.md")); } - public function testCloneBranchTag_0_9_3():Void { - final vcs = getVcs(); - final dir = vcs.directory + counter++; - vcs.clone(dir, url, branch, "0.9.3"); + public function testCloneTag_0_9_3():Void { + final vcs = createVcs(); + final dir = id.getName() + counter++; + vcs.clone(dir, {url: url, tag: "0.9.3"}); assertTrue(FileSystem.exists(dir)); - assertTrue(FileSystem.exists('$dir/.${vcs.directory}')); + assertTrue(FileSystem.exists('$dir/.${Vcs.getDirectoryFor(id)}')); // if that repo "README.md" was added in tag/rev.: "0.9.3" assertTrue(FileSystem.exists(dir + "/README.md")); } - public function testCloneBranchRev():Void { - final vcs = getVcs(); - final dir = vcs.directory + counter++; - vcs.clone(dir, url, branch, rev); + public function testCloneBranchCommit():Void { + final vcs = createVcs(); + final dir = id.getName() + counter++; + vcs.clone(dir, {url: url, branch: branch, commit: rev}); + + assertTrue(FileSystem.exists(dir)); + assertTrue(FileSystem.exists('$dir/.${Vcs.getDirectoryFor(id)}')); + + // if that repo "README.md" was added in tag/rev.: "0.9.3" + assertFalse(FileSystem.exists(dir + "/README.md")); + } + + public function testCloneCommit():Void { + final vcs = createVcs(); + final dir = id.getName() + counter++; + vcs.clone(dir, {url: url, commit: rev}); assertTrue(FileSystem.exists(dir)); - assertTrue(FileSystem.exists('$dir/.${vcs.directory}')); + assertTrue(FileSystem.exists('$dir/.${Vcs.getDirectoryFor(id)}')); // if that repo "README.md" was added in tag/rev.: "0.9.3" assertFalse(FileSystem.exists(dir + "/README.md")); @@ -142,86 +147,110 @@ class TestVcs extends TestBase // --------------- update --------------- // - public function testUpdateBranchTag_0_9_2__toLatest():Void { - final vcs = getVcs(); - final dir = vcs.directory + counter;// increment will do in `testCloneBranchTag_0_9_2` + public function testUpdateTag_0_9_2():Void { + final vcs = createVcs(); + final dir = id.getName() + counter; // increment will do in `testCloneBranchTag_0_9_2` - testCloneBranchTag_0_9_2(); + testCloneTag_0_9_2(); assertFalse(FileSystem.exists("README.md")); // save CWD: final cwd = Sys.getCwd(); Sys.setCwd(cwd + dir); - assertTrue(FileSystem.exists("." + vcs.directory)); + assertTrue(FileSystem.exists("." + Vcs.getDirectoryFor(id))); - // in this case `libName` can get any value: - vcs.update(()-> { - assertTrue(false); - // we are not expecting to be asked for confirmation - return false; - } ); + assertFalse(vcs.checkRemoteChanges()); + try { + vcs.mergeRemoteChanges(); + assertFalse(true); + } catch (e:VcsError) { + assertTrue(e.match(CommandFailed(_))); + } - // Now we get actual version (0.9.3 or newer) with README.md. - assertTrue(FileSystem.exists("README.md")); + // Since originally we installed 0.9.2, we are locked down to that so still no README.md. + assertFalse(FileSystem.exists("README.md")); // restore CWD: Sys.setCwd(cwd); } + public function testUpdateCommit():Void { + final vcs = createVcs(); + final dir = id.getName() + counter; // increment will do in `testCloneCommit` - public function testUpdateBranchTag_0_9_2__toLatest__afterUserChanges_withReset():Void { - final vcs = getVcs(); - final dir = vcs.directory + counter;// increment will do in `testCloneBranchTag_0_9_2` - - testCloneBranchTag_0_9_2(); + testCloneCommit(); assertFalse(FileSystem.exists("README.md")); // save CWD: final cwd = Sys.getCwd(); Sys.setCwd(cwd + dir); + assertTrue(FileSystem.exists("." + Vcs.getDirectoryFor(id))); - // creating user-changes: - FileSystem.deleteFile("build.hxml"); - File.saveContent("file", "new file \"file\" with content"); + assertFalse(vcs.checkRemoteChanges()); + try { + vcs.mergeRemoteChanges(); + assertFalse(true); + } catch (e:VcsError) { + assertTrue(e.match(CommandFailed(_))); + } - // update to HEAD: - vcs.update(() -> true); + // Since originally we installed 0.9.2, we are locked down to that so still no README.md. + assertFalse(FileSystem.exists("README.md")); + + // restore CWD: + Sys.setCwd(cwd); + } - // Now we get actual version (0.9.3 or newer) with README.md. + public function testUpdateBranch():Void { + final vcs = createVcs(); + final dir = id.getName() + counter; + // clone old commit from branch + testCloneBranchCommit(); + assertFalse(FileSystem.exists("README.md")); + + // save CWD: + final cwd = Sys.getCwd(); + Sys.setCwd(cwd + dir); + assertTrue(FileSystem.exists("." + Vcs.getDirectoryFor(id))); + + assertTrue(vcs.checkRemoteChanges()); + vcs.mergeRemoteChanges(); + + // Now we have the current version of develop with README.md. assertTrue(FileSystem.exists("README.md")); // restore CWD: Sys.setCwd(cwd); } - public function testUpdateBranchTag_0_9_2__toLatest__afterUserChanges_withoutReset():Void { - final vcs = getVcs(); - final dir = vcs.directory + counter;// increment will do in `testCloneBranchTag_0_9_2` + public function testUpdateBranch__afterUserChanges():Void { + final vcs = createVcs(); + final dir = id.getName() + counter; - testCloneBranchTag_0_9_2(); + testCloneBranchCommit(); assertFalse(FileSystem.exists("README.md")); // save CWD: final cwd = Sys.getCwd(); Sys.setCwd(cwd + dir); + assertTrue(FileSystem.exists("." + Vcs.getDirectoryFor(id))); // creating user-changes: FileSystem.deleteFile("build.hxml"); File.saveContent("file", "new file \"file\" with content"); + assertTrue(vcs.hasLocalChanges()); + // update to HEAD: + vcs.resetLocalChanges(); + assertTrue(FileSystem.exists("build.hxml")); - try { - vcs.update(() -> false); - assertTrue(false); - } catch (e:VcsUpdateCancelled) { - assertTrue(true); - } + assertFalse(vcs.hasLocalChanges()); + assertTrue(vcs.checkRemoteChanges()); + vcs.mergeRemoteChanges(); - // We get no reset and update: - assertTrue(FileSystem.exists("file")); - assertFalse(FileSystem.exists("build.hxml")); - assertFalse(FileSystem.exists("README.md")); + // Now we have the current version of develop with README.md. + assertTrue(FileSystem.exists("README.md")); // restore CWD: Sys.setCwd(cwd); @@ -229,7 +258,7 @@ class TestVcs extends TestBase //----------------- tools -------------------// - inline function getVcs():Vcs { - return Vcs.get(id); + inline function createVcs():Vcs { + return Vcs.create(id); } } diff --git a/test/tests/TestVcsNotFound.hx b/test/tests/TestVcsNotFound.hx index a12547aa..268da737 100644 --- a/test/tests/TestVcsNotFound.hx +++ b/test/tests/TestVcsNotFound.hx @@ -55,7 +55,7 @@ class TestVcsNotFound extends TestBase public function testCloneHg():Void { final vcs = getHg(); try { - vcs.clone(vcs.directory, "https://bitbucket.org/fzzr/hx.signal"); + vcs.clone("no-hg", {url: "https://bitbucket.org/fzzr/hx.signal"}); assertFalse(true); } catch(error:VcsError) { @@ -69,7 +69,7 @@ class TestVcsNotFound extends TestBase public function testCloneGit():Void { final vcs = getGit(); try { - vcs.clone(vcs.directory, "https://github.com/fzzr-/hx.signal.git"); + vcs.clone("no-git", {url: "https://github.com/fzzr-/hx.signal.git"}); assertFalse(true); } catch(error:VcsError) { @@ -95,16 +95,11 @@ class TestVcsNotFound extends TestBase class WrongHg extends Mercurial { public function new() { - super("no-hg", "no-hg", "Mercurial-not-found"); + super("no-hg"); } // copy of Mercurial.searchExecutablebut have a one change - regexp. - override private function searchExecutable():Void { - super.searchExecutable(); - - if (available) - return; - + override private function searchExecutable():Bool { // if we have already msys git/cmd in our PATH final match = ~/(.*)no-hg-no([\\|\/])cmd$/; for(path in Sys.getEnv("PATH").split(";")) { @@ -113,22 +108,17 @@ class WrongHg extends Mercurial { Sys.putEnv("PATH", Sys.getEnv("PATH") + ";" + newPath); } } - checkExecutable(); + return checkExecutable(); } } class WrongGit extends Git { public function new() { - super("no-git", "no-git", "Git-not-found"); + super("no-git"); } // copy of Mercurial.searchExecutablebut have a one change - regexp. - override private function searchExecutable():Void { - super.searchExecutable(); - - if(available) - return; - + override private function searchExecutable():Bool { // if we have already msys git/cmd in our PATH final match = ~/(.*)no-git-no([\\|\/])cmd$/; for(path in Sys.getEnv("PATH").split(";")) { @@ -138,13 +128,14 @@ class WrongGit extends Git { } } if(checkExecutable()) - return; + return true; // look at a few default paths for(path in ["C:\\Program Files (x86)\\Git\\bin", "C:\\Progra~1\\Git\\bin"]) if(FileSystem.exists(path)) { Sys.putEnv("PATH", Sys.getEnv("PATH") + ";" + path); if(checkExecutable()) - return; + return true; } + return false; } } diff --git a/test/tests/TestVersionData.hx b/test/tests/TestVersionData.hx index ca6dc0b8..2ef77e10 100644 --- a/test/tests/TestVersionData.hx +++ b/test/tests/TestVersionData.hx @@ -18,7 +18,7 @@ class TestVersionData extends TestBase { assertVersionDataEquals(extractVersion("git:https://some.url"), VcsInstall(VcsID.ofString("git"), { url: "https://some.url", branch: null, - ref: null, + commit: null, tag: null, subDir: null })); @@ -26,7 +26,7 @@ class TestVersionData extends TestBase { assertVersionDataEquals(extractVersion("git:https://some.url#branch"), VcsInstall(VcsID.ofString("git"), { url: "https://some.url", branch: "branch", - ref: null, + commit: null, tag: null, subDir: null })); @@ -34,7 +34,7 @@ class TestVersionData extends TestBase { assertVersionDataEquals(extractVersion("git:https://some.url#abcdef0"), VcsInstall(VcsID.ofString("git"), { url: "https://some.url", branch: null, - ref: "abcdef0", + commit: "abcdef0", tag: null, subDir: null })); @@ -44,7 +44,7 @@ class TestVersionData extends TestBase { assertVersionDataEquals(extractVersion("hg:https://some.url"), VcsInstall(VcsID.ofString("hg"), { url: "https://some.url", branch: null, - ref: null, + commit: null, tag: null, subDir: null })); @@ -52,7 +52,7 @@ class TestVersionData extends TestBase { assertVersionDataEquals(extractVersion("hg:https://some.url#branch"), VcsInstall(VcsID.ofString("hg"), { url: "https://some.url", branch: "branch", - ref: null, + commit: null, tag: null, subDir: null })); @@ -60,7 +60,7 @@ class TestVersionData extends TestBase { assertVersionDataEquals(extractVersion("hg:https://some.url#abcdef0"), VcsInstall(VcsID.ofString("hg"), { url: "https://some.url", branch: null, - ref: "abcdef0", + commit: "abcdef0", tag: null, subDir: null })); From b2f91fb9295aa34e7ac38f6abd64382f8a92a8f0 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sun, 6 Jul 2025 10:31:01 +0100 Subject: [PATCH 10/19] [tests] Unspecify mergeRemoteChanges failure If we are locked to a commit or tag, leave it unspecified whether mergeRemoteChanges will throw an exception, just require that it doesn't update the repository. This is to simplify the hg implementation, where `hg update` does not give an error in this case. --- test/tests/TestVcs.hx | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/tests/TestVcs.hx b/test/tests/TestVcs.hx index 55a39d25..dcf0aad1 100644 --- a/test/tests/TestVcs.hx +++ b/test/tests/TestVcs.hx @@ -162,7 +162,6 @@ class TestVcs extends TestBase assertFalse(vcs.checkRemoteChanges()); try { vcs.mergeRemoteChanges(); - assertFalse(true); } catch (e:VcsError) { assertTrue(e.match(CommandFailed(_))); } @@ -189,7 +188,6 @@ class TestVcs extends TestBase assertFalse(vcs.checkRemoteChanges()); try { vcs.mergeRemoteChanges(); - assertFalse(true); } catch (e:VcsError) { assertTrue(e.match(CommandFailed(_))); } From a41e7ec62420050a77849433c30cbb9320d13d42 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Sun, 9 Jul 2023 22:50:10 +0100 Subject: [PATCH 11/19] [tests] Use actual commit for git vcs unit tests --- test/tests/TestGit.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/TestGit.hx b/test/tests/TestGit.hx index 0163329c..659d25b3 100644 --- a/test/tests/TestGit.hx +++ b/test/tests/TestGit.hx @@ -12,6 +12,6 @@ class TestGit extends TestVcs { } public function new():Void { - super(VcsID.Git, "git", FileSystem.fullPath(REPO_PATH), "develop", "0.9.2"); + super(VcsID.Git, "git", FileSystem.fullPath(REPO_PATH), "develop", "2feb1476dadd66ee0aa20587b1ee30a6b4faac0f"); } } From 243e49c9bd1f4dd5d8ae515979df625e8f426f7b Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Sun, 9 Jul 2023 22:53:23 +0100 Subject: [PATCH 12/19] [tests] Update git/hg integration tests Test that updates behave correctly also on a haxelib command level. Also add a git specific test for short commit ids. --- test/tests/integration/TestGit.hx | 25 +++++++++++- test/tests/integration/TestHg.hx | 14 ++++++- test/tests/integration/TestVcs.hx | 60 +++++++++++++++++++++++++++ test/tests/util/Vcs.hx | 67 ++++++++++++++++++++++++++++++- 4 files changed, 162 insertions(+), 4 deletions(-) diff --git a/test/tests/integration/TestGit.hx b/test/tests/integration/TestGit.hx index 71704684..11c55244 100644 --- a/test/tests/integration/TestGit.hx +++ b/test/tests/integration/TestGit.hx @@ -1,5 +1,7 @@ package tests.integration; +import haxelib.api.FsUtils; +import haxelib.api.Vcs; import tests.util.Vcs; class TestGit extends TestVcs { @@ -10,7 +12,9 @@ class TestGit extends TestVcs { override function setup() { super.setup(); - makeGitRepo(vcsLibPath); + makeGitRepo(vcsLibPath, ["haxelib.xml"]); + createGitTag(vcsLibPath, vcsTag); + makeGitRepo(vcsLibNoHaxelibJson); makeGitRepo(vcsBrokenDependency); } @@ -22,4 +26,23 @@ class TestGit extends TestVcs { super.tearDown(); } + + public function updateVcsRepo() { + addToGitRepo(vcsLibPath, "haxelib.xml"); + } + + public function getVcsCommit():String { + return FsUtils.runInDirectory(vcsLibPath, Vcs.create(Git).getRef); + } + + function testInstallShortcommit() { + + final shortCommitId = getVcsCommit().substr(0, 7); + + updateVcsRepo(); + + final r = haxelib([cmd, "Bar", vcsLibPath, shortCommitId]).result(); + assertSuccess(r); + + } } diff --git a/test/tests/integration/TestHg.hx b/test/tests/integration/TestHg.hx index 78fa4d7d..b8fcb912 100644 --- a/test/tests/integration/TestHg.hx +++ b/test/tests/integration/TestHg.hx @@ -1,5 +1,7 @@ package tests.integration; +import haxelib.api.FsUtils; +import haxelib.api.Vcs; import tests.util.Vcs; class TestHg extends TestVcs { @@ -10,7 +12,9 @@ class TestHg extends TestVcs { override function setup() { super.setup(); - makeHgRepo(vcsLibPath); + makeHgRepo(vcsLibPath, ["haxelib.xml"]); + createHgTag(vcsLibPath, vcsTag); + makeHgRepo(vcsLibNoHaxelibJson); makeHgRepo(vcsBrokenDependency); } @@ -22,4 +26,12 @@ class TestHg extends TestVcs { super.tearDown(); } + + public function updateVcsRepo() { + addToHgRepo(vcsLibPath, "haxelib.xml"); + } + + public function getVcsCommit():String { + return FsUtils.runInDirectory(vcsLibPath, Vcs.create(Hg).getRef); + } } diff --git a/test/tests/integration/TestVcs.hx b/test/tests/integration/TestVcs.hx index 40073e3d..7d279844 100644 --- a/test/tests/integration/TestVcs.hx +++ b/test/tests/integration/TestVcs.hx @@ -6,12 +6,17 @@ abstract class TestVcs extends IntegrationTests { final vcsLibPath = "libraries/libBar"; final vcsLibNoHaxelibJson = "libraries/libNoHaxelibJson"; final vcsBrokenDependency = "libraries/libBrokenDep"; + final vcsTag = "v1.0.0"; function new(cmd:String) { super(); this.cmd = cmd; } + abstract function updateVcsRepo():Void; + + abstract function getVcsCommit():String; + function test() { final r = haxelib([cmd, "Bar", vcsLibPath]).result(); @@ -135,4 +140,59 @@ abstract class TestVcs extends IntegrationTests { } + + function testVcsUpdateBranch() { + + final r = haxelib([cmd, "Bar", vcsLibPath, "main"]).result(); + assertSuccess(r); + + final r = haxelib(["update", "Bar"]).result(); + assertSuccess(r); + assertOutputEquals(["Library Bar is already up to date"], r.out.trim()); + + updateVcsRepo(); + + final r = haxelib(["update", "Bar"]).result(); + assertSuccess(r); + assertOutputEquals([ + "Bar was updated", + ' Current version is now $cmd' + ], r.out.trim()); + + } + + function testVcsUpdateCommit() { + + final r = haxelib([cmd, "Bar", vcsLibPath, getVcsCommit()]).result(); + assertSuccess(r); + + updateVcsRepo(); + + // TODO: Doesn't work with hg + if (cmd == "hg") + return; + + final r = haxelib(["update", "Bar"]).result(); + assertSuccess(r); + assertOutputEquals(["Library Bar is already up to date"], r.out.trim()); + + } + + function testVcsUpdateTag() { + + final r = haxelib([cmd, "Bar", vcsLibPath, "main", "", "v1.0.0"]).result(); + assertSuccess(r); + + updateVcsRepo(); + + // TODO: Doesn't work with hg + if (cmd == "hg") + return; + + final r = haxelib(["update", "Bar"]).result(); + assertSuccess(r); + assertOutputEquals(["Library Bar is already up to date"], r.out.trim()); + + } + } diff --git a/test/tests/util/Vcs.hx b/test/tests/util/Vcs.hx index a7b73117..4abeb875 100644 --- a/test/tests/util/Vcs.hx +++ b/test/tests/util/Vcs.hx @@ -5,7 +5,7 @@ import sys.io.Process; /** Makes library at `libPath` into a git repo and commits all files. **/ -function makeGitRepo(libPath:String) { +function makeGitRepo(libPath:String, ?exclude:Array) { final oldCwd = Sys.getCwd(); Sys.setCwd(libPath); @@ -17,6 +17,12 @@ function makeGitRepo(libPath:String) { runCommand(cmd, ["config", "user.name", "Your Name"]); runCommand(cmd, ["add", "-A"]); + if (exclude != null) { + for (file in exclude) { + runCommand(cmd, ["rm", "--cached", file]); + } + } + runCommand(cmd, ["commit", "-m", "Create repo"]); // different systems may have different default branch names set runCommand(cmd, ["branch", "--move", "main"]); @@ -24,6 +30,31 @@ function makeGitRepo(libPath:String) { Sys.setCwd(oldCwd); } +function createGitTag(libPath:String, name:String) { + final oldCwd = Sys.getCwd(); + + Sys.setCwd(libPath); + + final cmd = "git"; + + runCommand(cmd, ["tag", "-a", name, "-m", name]); + + Sys.setCwd(oldCwd); +} + +function addToGitRepo(libPath:String, item:String) { + final oldCwd = Sys.getCwd(); + + Sys.setCwd(libPath); + + final cmd = "git"; + + runCommand(cmd, ["add", item]); + runCommand(cmd, ["commit", "-m", 'Add $item']); + + Sys.setCwd(oldCwd); +} + private function runCommand(cmd:String, args:Array) { final process = new sys.io.Process(cmd, args); final code = process.exitCode(); @@ -42,7 +73,7 @@ function resetGitRepo(libPath:String) { HaxelibTests.deleteDirectory(gitDirectory); } -function makeHgRepo(libPath:String) { +function makeHgRepo(libPath:String, ?exclude:Array) { final oldCwd = Sys.getCwd(); Sys.setCwd(libPath); @@ -51,7 +82,39 @@ function makeHgRepo(libPath:String) { runCommand(cmd, ["init"]); runCommand(cmd, ["add"]); + if (exclude != null) { + for (file in exclude) { + runCommand(cmd, ["forget", file]); + } + } + runCommand(cmd, ["commit", "-m", "Create repo"]); + runCommand(cmd, ["branch", "main"]); + + Sys.setCwd(oldCwd); +} + +function createHgTag(libPath:String, name:String) { + final oldCwd = Sys.getCwd(); + + Sys.setCwd(libPath); + + final cmd = "hg"; + + runCommand(cmd, ["tag", name]); + + Sys.setCwd(oldCwd); +} + +function addToHgRepo(libPath:String, item:String) { + final oldCwd = Sys.getCwd(); + + Sys.setCwd(libPath); + + final cmd = "hg"; + + runCommand(cmd, ["add", item]); + runCommand(cmd, ["commit", "-m", 'Add $item']); Sys.setCwd(oldCwd); } From 7ca841186c037c54380ff4a879508917180b97cb Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Mon, 10 Jul 2023 17:58:25 +0100 Subject: [PATCH 13/19] [tests] Fix update command tests --- test/tests/integration/TestUpdate.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/integration/TestUpdate.hx b/test/tests/integration/TestUpdate.hx index 3d3fae73..3500f755 100644 --- a/test/tests/integration/TestUpdate.hx +++ b/test/tests/integration/TestUpdate.hx @@ -160,7 +160,7 @@ class TestUpdate extends IntegrationTests { final r = haxelib(["update", "Bar"]).result(); assertSuccess(r); - assertTrue(r.out.indexOf('Library Bar $type repository is already up to date') >= 0); + assertTrue(r.out.indexOf('Library Bar is already up to date') >= 0); // Don't show update message if vcs lib was already up to date assertTrue(r.out.indexOf("Bar was updated") < 0); From 10833737c0dc31d5c985bf235750ff66961435a4 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Mon, 10 Jul 2023 13:56:42 +0100 Subject: [PATCH 14/19] Fix subdir checks for git/hg installs It may be desired to omit the subdir argument but pass in a tag, but currently this is not possible and it is necessary to pass an empty string as the subdir. This should not result in a dev path being set. There is an example that requires this behaviour in the newly added integration tests, see tests.integration.TestVcs.testVcsUpdateTag(). --- src/haxelib/api/GlobalScope.hx | 2 +- src/haxelib/api/Installer.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/haxelib/api/GlobalScope.hx b/src/haxelib/api/GlobalScope.hx index 65dfde56..4f6b9f24 100644 --- a/src/haxelib/api/GlobalScope.hx +++ b/src/haxelib/api/GlobalScope.hx @@ -63,7 +63,7 @@ class GlobalScope extends Scope { repository.setVcsData(library, vcsVersion, data); } - if (data.subDir != null) { + if (!(data == null || data.subDir == "" || data.subDir == null)) { final devDir = repository.getValidVersionPath(library, vcsVersion) + data.subDir; repository.setDevPath(library, devDir); } else { diff --git a/src/haxelib/api/Installer.hx b/src/haxelib/api/Installer.hx index a8533958..294257ea 100644 --- a/src/haxelib/api/Installer.hx +++ b/src/haxelib/api/Installer.hx @@ -636,7 +636,7 @@ class Installer { } scope.setVcsVersion(library, version, data); - if (data.subDir == null) { + if (data.subDir == "" || data.subDir == null) { userInterface.log(' Current version is now $version'); } else { final path = scope.getPath(library); From 8b01fb48e70cab0aa427b44cfde60fa3724b0cce Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sun, 6 Jul 2025 10:23:56 +0100 Subject: [PATCH 15/19] Make Hg.hasLocalChanges consistent with Git The git implementation doesn't check for untracked changes. For now, to pass the tests, just match the git behaviour, though, in future we probably want to check for them to avoid them being wiped on library reinstall. --- src/haxelib/api/Vcs.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haxelib/api/Vcs.hx b/src/haxelib/api/Vcs.hx index ab0479e3..2abbcec3 100644 --- a/src/haxelib/api/Vcs.hx +++ b/src/haxelib/api/Vcs.hx @@ -455,7 +455,7 @@ class Mercurial extends Vcs { public function hasLocalChanges():Bool { final diff = run(["diff", "-U", "2", "--git", "--subrepos"]); - final status = run(["status"]); + final status = run(["status", "-q"]); return diff.code + status.code + diff.out.length + status.out.length > 0; } From 824c06d080aa885021273acc33af574e26a539a8 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sun, 13 Jul 2025 10:18:29 +0100 Subject: [PATCH 16/19] [ci] Disable cpp client tests for now Git.clone() is currently broken due to: https://github.com/HaxeFoundation/haxe/issues/11666 --- test/RunCi.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/RunCi.hx b/test/RunCi.hx index 40c4dd0f..c409795a 100644 --- a/test/RunCi.hx +++ b/test/RunCi.hx @@ -385,7 +385,7 @@ Listen 2000 "-D", 'haxelib_path=${Path.join([Sys.getCwd(), "haxelib"])}' ]); - runCommand("neko", ["bin/integration_tests.n"]); + // runCommand("neko", ["bin/integration_tests.n"]); } static function deploy():Void { From 91d8f609745c4b984a506caee939e430564fb3f5 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sun, 13 Jul 2025 13:11:21 +0100 Subject: [PATCH 17/19] Fix optional log messages from git/hg commands --- src/haxelib/api/Vcs.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haxelib/api/Vcs.hx b/src/haxelib/api/Vcs.hx index 2abbcec3..3eeba254 100644 --- a/src/haxelib/api/Vcs.hx +++ b/src/haxelib/api/Vcs.hx @@ -85,7 +85,7 @@ abstract class Vcs implements IVcs { private var availabilityChecked = false; - function new(executable:String, ?debugLog:(message:String) -> Void, ?optional:(message:String) -> Void) { + function new(executable:String, ?debugLog:(message:String) -> Void, ?optionalLog:(message:String) -> Void) { this.executable = executable; if (debugLog != null) this.debugLog = debugLog; From f96c34f243309a3b0766bd9d8bea96686a3dfb75 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Mon, 14 Jul 2025 00:06:09 +0100 Subject: [PATCH 18/19] Use more intuitive logic for subdir check --- src/haxelib/api/GlobalScope.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haxelib/api/GlobalScope.hx b/src/haxelib/api/GlobalScope.hx index 4f6b9f24..e5e99395 100644 --- a/src/haxelib/api/GlobalScope.hx +++ b/src/haxelib/api/GlobalScope.hx @@ -63,7 +63,7 @@ class GlobalScope extends Scope { repository.setVcsData(library, vcsVersion, data); } - if (!(data == null || data.subDir == "" || data.subDir == null)) { + if (data != null && data.subDir != "" && data.subDir != null) { final devDir = repository.getValidVersionPath(library, vcsVersion) + data.subDir; repository.setDevPath(library, devDir); } else { From 16a910f5f17b30f35c55aef47e38dcf4ab066f92 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Mon, 14 Jul 2025 00:49:27 +0100 Subject: [PATCH 19/19] Update documentation for VcsData methods Add a description for isReproducible() and clairfy the purpose of getCleaned() --- src/haxelib/VersionData.hx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/haxelib/VersionData.hx b/src/haxelib/VersionData.hx index e66880d0..e23488a7 100644 --- a/src/haxelib/VersionData.hx +++ b/src/haxelib/VersionData.hx @@ -62,13 +62,17 @@ class VcsData { url; } + /** + Returns whether this vcs data will always reproduce an identical installation + (i.e. the commit id is locked down) + **/ public function isReproducible() { return commit != null; } /** - Returns an object containing the filled-in VcsData fields, - without the empty ones. + Returns an anonymous object containing only the non-null, non-empty VcsData fields, + excluding the null/empty ones. **/ public function getCleaned() { var data:{