/// <summary> /// Whether the driver support the specified uri. /// </summary> /// <param name="io">The input/output instance.</param> /// <param name="config">The config instance.</param> /// <param name="uri">Thr specified uri.</param> /// <param name="deep">Whether is deep checker.</param> /// <returns>True if the driver is supported.</returns> public static bool IsSupport(IIO io, Config config, string uri, bool deep = false) { if (Regex.IsMatch(uri, @"(^git://|\.git/?$|git(?:olite)?@|//git\.|//github\.com/|//gitlib\.com/)", RegexOptions.IgnoreCase)) { return(true); } bool ProcessCheck(string command, string cwd) { var process = new BucketProcessExecutor(io); return(process.Execute(command, cwd) == 0); } if (FileSystemLocal.IsLocalPath(uri)) { uri = FileSystemLocal.GetPlatformPath(uri); if (!Directory.Exists(uri)) { return(false); } return(ProcessCheck("git tag", uri)); } if (!deep) { return(false); } return(ProcessCheck($"git ls-remote --heads {ProcessExecutor.Escape(uri)}", null)); }
/// <summary> /// Fetch the specified reference or synchronize the git repository. /// </summary> /// <param name="uri">The specified git repository. can use ssh.</param> /// <param name="reference">The specified git reference.</param> /// <param name="path">The specified saved path. this path will be based on the file system.</param> /// <returns>True if fetched. false is sync mirror.</returns> public bool FetchReferenceOrSyncMirror(string uri, string reference, string path = null) { var parsedPath = string.IsNullOrEmpty(path) ? GetRepositoryNameFromUri(uri) : path; if (fileSystem is IReportPath report) { parsedPath = report.ApplyRootPath(parsedPath); } if (string.IsNullOrEmpty(parsedPath)) { throw new FileSystemException("Clone path is invalid, cannot be empty."); } if (fileSystem.Exists(parsedPath, FileSystemOptions.Directory) && process.Execute("git rev-parse --git-dir", out string output, parsedPath) == 0 && output.Trim() == ".") { var escapedReference = ProcessExecutor.Escape($"{reference}^{{commit}}"); if (process.Execute($"git rev-parse --quiet --verify {escapedReference}", parsedPath) == 0) { return(true); } } SyncMirror(uri, path); return(false); }
/// <summary> /// Generate compatibility files under unix. /// </summary> /// <returns>Returns the compatible content.</returns> protected virtual string GenerateUnixProxyCode(string binPath, string link) { binPath = BaseFileSystem.GetRelativePath(link, binPath); var binDirPath = ProcessExecutor.Escape(Path.GetDirectoryName(binPath)); var binFile = Path.GetFileName(binPath); var proxyCode = @"#!/usr/bin/env sh dir=$(cd ""${0%[/\\]*}"" > /dev/null; cd %binDirPath% && pwd) if [ -d /proc/cygdrive ]; then case $(which dotnet) in $(readlink -n /proc/cygdrive)/*) dir=$(cygpath -m ""$dir""); ;; esac fi ""${dir}/%binFile%"" ""$@"" "; proxyCode = proxyCode.Replace("%binDirPath%", binDirPath); proxyCode = proxyCode.Replace("%binFile%", binFile); proxyCode = proxyCode.Replace("\r\n", "\n"); return proxyCode; }
/// <summary> /// Generate compatibility files under windows. /// </summary> /// <param name="binPath">The package's bin file.</param> /// <param name="link">The link file.</param> /// <returns>Returns the compatible content.</returns> protected virtual string GenerateWindowsProxyCode(string binPath, string link) { var caller = DetermineBinaryCaller(binPath, fileSystem); binPath = BaseFileSystem.GetRelativePath(link, binPath); return "@ECHO OFF\r\n" + "setlocal DISABLEDELAYEDEXPANSION\r\n" + $"SET BIN_TARGET=%~dp0/{ProcessExecutor.Escape(binPath).Trim('\'', '"')}\r\n" + $"{caller} \"%BIN_TARGET%\" %*\r\n"; }
/// <inheritdoc /> protected override string GetCommitLogs(string fromReference, string toReference, string cwd) { cwd = NormalizePath(cwd); var escapedFrom = ProcessExecutor.Escape(fromReference); var escapedTo = ProcessExecutor.Escape(toReference); var command = $"git log {escapedFrom}..{escapedTo} --pretty=format:\"%h - %an: %s\""; if (Process.Execute(command, out string stdout, out string stderr, cwd) != 0) { throw new RuntimeException($"Failed to execute {command}{Environment.NewLine}{Environment.NewLine}{stderr}"); } return(stdout.Trim()); }
/// <summary> /// Synchronize the specified git repository to the specified directory. /// </summary> /// <param name="uri">The specified git repository. can use ssh.</param> /// <param name="path">The specified saved path. this path will be based on the file system.</param> /// <returns>True if successful to sync mirror.</returns> public bool SyncMirror(string uri, string path = null) { path = string.IsNullOrEmpty(path) ? GetRepositoryNameFromUri(uri) : path; if (fileSystem is IReportPath report) { path = report.ApplyRootPath(path); } if (string.IsNullOrEmpty(path)) { throw new FileSystemException("Clone path is invalid, cannot be empty."); } if (fileSystem.Exists(path, FileSystemOptions.Directory) && process.Execute("git rev-parse --git-dir", out string output, path) == 0 && output.Trim() == ".") { string Upgrate(string protoUri) { return($"git remote set-url origin {protoUri} && git remote update --prune origin"); } try { ExecuteCommand(Upgrate, uri, path); return(true); } #pragma warning disable CA1031 catch (System.Exception) #pragma warning restore CA1031 { return(false); } } fileSystem.Delete(path); string Cloneable(string protoUri) { return($"git clone --mirror {ProcessExecutor.Escape(protoUri)} {ProcessExecutor.Escape(path)}"); } ExecuteCommand(Cloneable, uri, path, true); return(true); }
/// <inheritdoc /> protected override void DoUpdate(IPackage initial, IPackage target, string cwd, string uri) { Git.CleanEnvironment(); cwd = NormalizePath(cwd); if (!HasMetadataRepository(cwd)) { throw new RuntimeException($"The .git directory is missing from \"{cwd}\""); } var updateOriginUri = false; if (Process.Execute("git remote -v", out string stdout, cwd) == 0) { var originMatch = Regex.Match(stdout, @"^origin\s+(?<uri>\S+?)\r?", RegexOptions.Multiline); var bucketMatch = Regex.Match(stdout, @"^bucket\s+(?<uri>\S+?)\r?", RegexOptions.Multiline); if (originMatch.Success && bucketMatch.Success && originMatch.Groups["uri"].Value == bucketMatch.Groups["uri"].Value && bucketMatch.Groups["uri"].Value != target.GetSourceUri()) { updateOriginUri = true; } } var reference = target.GetSourceReference(); IO.WriteError($" Checking out {GetShortHash(reference)}"); string CommandCallable(string authUri) { var escapedAuthUri = ProcessExecutor.Escape(authUri); var escapedReference = ProcessExecutor.Escape($"{reference}^{{commit}}"); return($"git remote set-url bucket {escapedAuthUri} && git rev-parse --quiet --verify {escapedReference} || (git fetch bucket && git fetch --tags bucket)"); } git.ExecuteCommand(CommandCallable, uri, cwd); ExecuteUpdate(cwd, reference, target); if (updateOriginUri) { UpdateOriginUri(target.GetSourceUri(), cwd); } }
/// <summary> /// Set the origin push uri. /// </summary> protected virtual void SetPushUri(string uri, string cwd) { // set push url for github projects. var match = Regex.Match(uri, $"^(?:https?|git)://{Git.GetGithubDomainsRegex(Config)}/(?<username>[^/]+)/(?<repository>[^/]+?)(?:\\.git)?$"); if (!match.Success) { return; } string[] protocols = Config.Get(Settings.GithubProtocols); var pushUri = $"git@{match.Groups["domain"].Value}:{match.Groups["username"].Value}/{match.Groups["repository"].Value}.git"; if (!Array.Exists(protocols, protocol => protocol == "ssh")) { pushUri = $"https://{match.Groups["domain"].Value}/{match.Groups["username"].Value}/{match.Groups["repository"].Value}.git"; } Process.Execute($"git remote set-url --push origin {ProcessExecutor.Escape(pushUri)}", cwd); }
private DateTime?GetPackageTimeFromSource(IPackage package) { var installedPath = Path.Combine(Environment.CurrentDirectory, installationManager.GetInstalledPath(package)); var sourceType = package.GetSourceType(); DateTime?ret = null; if (string.IsNullOrEmpty(installedPath) || !Array.Exists(new[] { "git" }, (item) => item == sourceType)) { return(null); } var sourceReference = package.GetSourceReference() ?? package.GetDistReference(); DateTime?GetGitDateTime() { Git.CleanEnvironment(); if (process.Execute( $"git log -n1 --pretty=%aD {ProcessExecutor.Escape(sourceReference)}", out string output, installedPath) == 0 && DateTime.TryParse(output.Trim(), out DateTime dateTime)) { return(dateTime); } return(null); } switch (sourceReference) { case "git": ret = GetGitDateTime(); break; default: break; } return(ret); }
/// <summary> /// extract <paramref name="file"/> to <paramref name="extractPath"/> with unzip command. /// </summary> protected internal virtual void ExtractWithUnzipCommand(string file, string extractPath, bool isFallback = false) { // When called after a ZipArchive failed, perhaps // there is some files to overwrite var overwrite = isFallback ? "-o " : string.Empty; var command = $"unzip -qq {overwrite}{ProcessExecutor.Escape(file)} -d {ProcessExecutor.Escape(extractPath)}"; FileSystemLocal.EnsureDirectory(extractPath); SException processException; try { if (process.Execute(command, out _, out string stderr) == 0) { return; } throw new RuntimeException( $"Failed to execute {command} {Environment.NewLine}{Environment.NewLine} {stderr}"); } #pragma warning disable CA1031 catch (SException ex) #pragma warning restore CA1031 { processException = ex; } if (isFallback) { ExceptionDispatchInfo.Capture(processException).Throw(); } io.WriteError($" {processException.Message}"); io.WriteError($" The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)"); io.WriteError($" Unzip with unzip command failed, falling back to {nameof(ZipArchive)}."); ExtractWithZipArchive(file, extractPath, true); }
/// <inheritdoc /> public override void Delete(string path = null) { AssertReadonly(); var location = ApplyRootPath(path); void DeleteFile(string fileLocation) { if (!File.Exists(fileLocation)) { return; } File.SetAttributes(fileLocation, FileAttributes.Normal); File.Delete(fileLocation); } void DeleteDirectory(string directoryLocation) { if (!Directory.Exists(directoryLocation)) { return; } var files = Directory.GetFiles(directoryLocation); var directories = Directory.GetDirectories(directoryLocation); Array.ForEach(files, DeleteFile); Array.ForEach(directories, DeleteDirectory); Directory.Delete(directoryLocation, false); } try { DeleteFile(location); DeleteDirectory(location); } #pragma warning disable CA1031 catch (SException) #pragma warning restore CA1031 { // Retry after a bit on windows since it tends // to be touchy with mass removals. if (Platform.IsWindows) { Thread.Sleep(350); } try { DeleteFile(location); DeleteDirectory(location); } catch (SException) { string command; if (Platform.IsWindows) { command = $"rmdir /S /Q {ProcessExecutor.Escape(location)}"; } else { command = $"rm -rf {ProcessExecutor.Escape(location)}"; } if (process.Execute(command) != 0) { throw; } } } }
/// <summary> /// Update the origin uri. /// </summary> protected virtual void UpdateOriginUri(string uri, string cwd) { Process.Execute($"git remote set-url origin {ProcessExecutor.Escape(uri)}", cwd); SetPushUri(uri, cwd); }
/// <summary> /// Updates the given path to the given commit ref. /// </summary> /// <param name="cwd">The given path.</param> /// <param name="reference">Checkout to specified reference(commit ref, tag, branch).</param> /// <param name="branch">The name of the branch to use when checking out.</param> /// <returns>If a string is returned, it is the commit reference that was checked out if the original could not be found.</returns> protected virtual string UpdateToCommit(string cwd, string reference, string branch, DateTime?releaseDate) { Process.Execute("git branch -r", out string branches, cwd); bool IsGitHash(string hash) { return(Regex.IsMatch(hash, "^[a-f0-9]{40}$")); } bool IsBranchesHasRemote(string remote) { return(Regex.IsMatch(branches, $"^\\s+bucket/{Regex.Escape(remote)}\r?$", RegexOptions.Multiline)); } string command; var force = hasDiscardedChanges || hasStashedChanges ? "-f " : string.Empty; branch = Regex.Replace(branch ?? string.Empty, @"(?:^dev-|(?:\.x)?-dev$)", string.Empty, RegexOptions.IgnoreCase); // check whether non-commitish are branches or tags, and fetch branches // with the remote name. Use branch(in the context may be the version) // as the new branch name. if (!IsGitHash(reference) && !string.IsNullOrEmpty(branches) && IsBranchesHasRemote(reference)) { var escapedBranch = ProcessExecutor.Escape(branch); var escapedReference = ProcessExecutor.Escape($"bucket/{reference}"); command = $"git checkout {force}-B {escapedBranch} {escapedReference} -- && git reset --hard {escapedReference} --"; if (Process.Execute(command, cwd) == 0) { return(null); } } // try to checkout branch by name and then reset it so it's on the proper branch name. if (IsGitHash(reference)) { // add 'v' in front of the branch if it was stripped when generating the pretty name. if (!IsBranchesHasRemote(branch) && IsBranchesHasRemote($"v{branch}")) { branch = $"v{branch}"; } var escapedBranch = ProcessExecutor.Escape(branch); command = $"git checkout {escapedBranch} --"; var fallbackCommand = $"git checkout {force}-B {escapedBranch} {ProcessExecutor.Escape($"bucket/{branch}")} --"; if (Process.Execute(command, cwd) == 0 || Process.Execute(fallbackCommand, cwd) == 0) { command = $"git reset --hard {ProcessExecutor.Escape(reference)} --"; if (Process.Execute(command, cwd) == 0) { return(null); } } } // This uses the "--" sequence to separate branch from file parameters. // // Otherwise git tries the branch name as well as file name. // If the non-existent branch is actually the name of a file, the file // is checked out. var escapedGitReference = ProcessExecutor.Escape(reference); command = $"git checkout {force}{escapedGitReference} -- && git reset --hard {escapedGitReference} --"; if (Process.Execute(command, out _, out string stderr, cwd) == 0) { return(null); } // reference was not found (prints "fatal: reference is not a tree: $ref"). if (stderr.Contains(reference)) { IO.WriteError($" <warning>{reference} is gone (history was rewritten?)</warning>"); } stderr = BucketProcessExecutor.FilterSensitive($"Failed to execute \"{command}\" {Environment.NewLine}{Environment.NewLine}{stderr}"); throw new RuntimeException(stderr); }
/// <inheritdoc /> protected override void DoInstall(IPackage package, string cwd, string uri) { Git.CleanEnvironment(); cwd = NormalizePath(cwd); var cachePath = Config.Get(Settings.CacheVcsDir) + $"/{CacheFileSystem.FormatCacheFolder(uri)}/"; var reference = package.GetSourceReference(); var flag = Platform.IsWindows ? "/D " : string.Empty; // --dissociate option is only available since git 2.3.0-rc0 var gitVersion = git.GetVersion(); var message = $"Cloning {GetShortHash(reference)}"; var command = $"git clone --no-checkout %uri% %path% && cd {flag}%path% && git remote add bucket %uri% && git fetch bucket"; if (!string.IsNullOrEmpty(gitVersion) && Comparator.GreaterThanOrEqual(gitVersion, "2.3.0-rc0") && CacheFileSystem.IsUsable(cachePath)) { IO.WriteError(string.Empty, true, Verbosities.Debug); IO.WriteError($" Cloning to cache at {ProcessExecutor.Escape(cachePath)}", true, Verbosities.Debug); try { git.FetchReferenceOrSyncMirror(uri, reference, cachePath); if (FileSystem.Exists(cachePath, FileSystemOptions.Directory)) { command = "git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath%" + $" && cd {flag}%path%" + " && git remote set-url origin %uri% && git remote add bucket %uri%"; message = $"Cloning {GetShortHash(reference)} from cache."; } } catch (RuntimeException) { // ignore runtime exception because this is an optimization solution. } } IO.WriteError(message); string CommandCallable(string authUri) { var template = command; template = template.Replace("%uri%", ProcessExecutor.Escape(authUri)); template = template.Replace("%path%", ProcessExecutor.Escape(cwd)); template = template.Replace("%cachePath%", ProcessExecutor.Escape(cachePath)); return(template); } git.ExecuteCommand(CommandCallable, uri, cwd, true); if (uri != package.GetSourceUri()) { UpdateOriginUri(package.GetSourceUri(), cwd); } else { SetPushUri(uri, cwd); } ExecuteUpdate(cwd, reference, package); }