using System; using System.Collections.Generic; using Newtonsoft.Json; using System.IO; using System.Linq; using LibGit2Sharp; using System.Diagnostics; namespace repomgr { public class RepoMgr { public RepoMgr(string buildpath) { _buildpath = Path.GetFullPath(buildpath); _pkgpath = Path.Combine(_buildpath, "pkg"); } public readonly string _buildpath; public readonly string _pkgpath; public Repository _repo; public void Init(string repopath, string reponame) { _repo = new Repository(repopath, reponame); if (!Directory.Exists(repopath)) Directory.CreateDirectory(repopath); WriteIndex(); Console.WriteLine("Initialized."); } public void Add(string package) { Package pkg; if (Directory.Exists(Path.Combine(_pkgpath, package))) { if (_repo.Packages.Find(p => p.Name.Equals(package)) != null) { Console.WriteLine("Package already exists."); return; } if (!CheckPackage(package)) { throw new Exception("Package directory exists but is invalid (PKGBUILD or .git missing)"); } pkg = new Package(package); } else { if (_repo.Packages.Find(p => p.Name.Equals(package)) != null) _repo.Packages.RemoveAll(p => p.Name.Equals(package)); try { var refcount = LibGit2Sharp.Repository.ListRemoteReferences($"https://aur.archlinux.org/{package}.git").Count(); if (refcount < 1) { throw new Exception("git clone failed: Package doesn't exist in remote"); } LibGit2Sharp.Repository.Clone($"https://aur.archlinux.org/{package}.git", Path.Combine(_pkgpath, package)); } catch (Exception e) { throw new Exception($"Error during clone: {e}"); } pkg = new Package(package); } Console.WriteLine($"Adding package {package}..."); _repo.Packages.Add(pkg); WriteIndex(); } private void Build(Package package, bool force = false) { if (UpdateAvailable(package)) UpdatePackage(package); if (File.ReadAllText(Path.Combine(_pkgpath, package.Name, "PKGBUILD")).Contains("pkgver()")) Shell.Exec("makepkg -os --noconfirm", Path.Combine(_pkgpath, package.Name)); package.CurrentVersion = Shell.ExecR("source PKGBUILD; echo \"$pkgver-$pkgrel\"", Path.Combine(_pkgpath, package.Name)); WriteIndex(); if (force) Shell.Exec("makepkg -Ccsf --sign --noconfirm 2>&1| tee buildlog.txt", Path.Combine(_pkgpath, package.Name)); else if (package.CurrentVersion != package.RepoVersion) Shell.Exec("makepkg -Ccs --sign --noconfirm 2>&1| tee buildlog.txt", Path.Combine(_pkgpath, package.Name)); else return; var resultingPackages = Directory .GetFiles(Path.Combine(_pkgpath, package.Name), "*.pkg.tar*"); if (resultingPackages.Length < 1) { package.LastBuildSucceeded = false; throw new Exception("makepkg didn't build any package"); } package.LastBuildSucceeded = true; if (package.PkgFiles.Any()) { foreach (var pkgFile in package.PkgFiles) if (File.Exists(Path.Combine(_repo.Path, pkgFile))) File.Delete(Path.Combine(_repo.Path, pkgFile)); package.PkgFiles.Clear(); } foreach (var resultingPackage in resultingPackages.Where(p => p.EndsWith(".sig"))) { File.Copy(resultingPackage, Path.Combine(_repo.Path, Path.GetFileName(resultingPackage)), true); File.Delete(resultingPackage); package.PkgFiles.Add(Path.GetFileName(resultingPackage)); } foreach (var resultingPackage in resultingPackages.Where(p => !p.EndsWith(".sig"))) { File.Copy(resultingPackage, Path.Combine(_repo.Path, Path.GetFileName(resultingPackage)), true); File.Delete(resultingPackage); package.PkgFiles.Add(Path.GetFileName(resultingPackage)); Shell.Exec($"repo-add --remove --sign {_repo.Name}.db.tar.gz {Path.GetFileName(resultingPackage)}", _repo.Path); } package.RepoVersion = package.CurrentVersion; WriteIndex(); } private void Remove(Package package) { var packageDir = Path.Combine(_pkgpath, package.Name); if (Directory.Exists(packageDir)) Directory.Delete(packageDir, true); Shell.Exec($"repo-remove --sign {_repo.Name}.db.tar.gz {package.Name}", _repo.Path); if (package.PkgFiles.Any()) { foreach (var pkgFile in package.PkgFiles) if (File.Exists(Path.Combine(_repo.Path, pkgFile))) File.Delete(Path.Combine(_repo.Path, pkgFile)); package.PkgFiles.Clear(); } _repo.Packages.Remove(package); WriteIndex(); } public void List() { Console.WriteLine(Shell.Yellow($"{Shell.Bold(_repo.Name)} ({_repo.Packages.Count} packages):")); foreach (var package in _repo.Packages.OrderBy(p => p.Name)) { var line = $"{Shell.Bold(package.Name)}"; if (package.RepoVersion != package.CurrentVersion) line += $" ({Shell.Red(package.RepoVersion)} {Shell.Gray("->")} {Shell.Yellow(package.CurrentVersion)})"; else line += $" ({Shell.Green(package.RepoVersion)})"; if (package.LastBuildSucceeded) line += $" [{Shell.Green(Shell.Bold("BUILD PASSING"))}]"; else line += $" [{Shell.Red(Shell.Bold("BUILD FAILING"))}]"; Console.WriteLine(line); } } // util stuff private Package GetPackage(string package) { var pkg = _repo.Packages.FirstOrDefault(p => p.Name.Equals(package)); if (pkg != null) return pkg; throw new Exception("Package not found."); } // git fetch, returns true if differences between origin/master and master found private bool UpdateAvailable(Package package) { var repo = new LibGit2Sharp.Repository(Path.Combine(_pkgpath, package.Name)); var remote = repo.Network.Remotes["origin"]; var refSpecs = remote.FetchRefSpecs.Select(x => x.Specification); Commands.Fetch(repo, remote.Name, refSpecs, null, null); return repo.Diff.Compare(repo.Branches["origin/master"].Tip.Tree, DiffTargets.Index | DiffTargets.WorkingDirectory).Any(); } // resets master to origin/master private void UpdatePackage(Package package) { var repo = new LibGit2Sharp.Repository(Path.Combine(_pkgpath, package.Name)); var originMaster = repo.Branches["origin/master"]; repo.Reset(ResetMode.Hard, originMaster.Tip); } public void ReadIndex() { try { _repo = JsonConvert.DeserializeObject( File.ReadAllText(Path.Combine(_buildpath, "repomgr.index.json"))); } catch (IOException) { throw new Exception("configuration file not found or wrong permissions. Did you run repomgr init?"); } catch (JsonException) { throw new Exception("configuration corrupt. Please check manually or re-init repo."); } } private void WriteIndex() { try { var json = JsonConvert.SerializeObject(_repo); if (!Directory.Exists(_buildpath)) Directory.CreateDirectory(_buildpath); File.WriteAllText(Path.Combine(_buildpath, "repomgr.index.json"), json); } catch (IOException) { throw new Exception("Unable to write configuratoin. Check permissions."); } } private bool CheckPackage(string package) { return Directory.Exists(Path.Combine(_pkgpath, package, ".git")) && File.Exists(Path.Combine(_pkgpath, package, "PKGBUILD")); } public void Build(string package, bool force = false) { if (!CheckPackage(package)) return; var iPackage = GetPackage(package); try { Build(iPackage, force); } catch (Exception) { iPackage.LastBuildSucceeded = false; throw; } } public void BuildAll() { foreach (var package in _repo.Packages) { if (!CheckPackage(package.Name)) return; try { Build(package); } catch (Exception) { package.LastBuildSucceeded = false; } } } public void Remove(string package) { if (CheckPackage(package)) Remove(GetPackage(package)); } } public class Repository { public Repository(string path, string name) { Path = path; Name = name; Packages = new List(); } public readonly string Path; public readonly string Name; public readonly List Packages; } public class Package { public Package(string name) { Name = name; } public readonly string Name; public string CurrentVersion = "never-updated"; public string RepoVersion = "nA"; public bool LastBuildSucceeded = true; public readonly List PkgFiles = new List(); } internal static class Shell { public static void Exec(string cmd, string workingDirectory) { var escapedArgs = cmd.Replace("\"", "\\\""); var process = new Process() { StartInfo = new ProcessStartInfo { FileName = "/bin/bash", Arguments = $"-c \"{escapedArgs}\"", WorkingDirectory = workingDirectory, RedirectStandardOutput = false, RedirectStandardError = false, UseShellExecute = false, CreateNoWindow = true, } }; process.Start(); process.WaitForExit(); } public static string ExecR(string cmd, string workingDirectory) { var escapedArgs = cmd.Replace("\"", "\\\""); var process = new Process() { StartInfo = new ProcessStartInfo { FileName = "/bin/bash", Arguments = $"-c \"{escapedArgs}\"", WorkingDirectory = workingDirectory, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, } }; process.Start(); var stdout = process.StandardOutput.ReadToEnd(); process.WaitForExit(); return stdout.Trim(); } public static string Bold(string s) { return $"\x1B[1m{s}\x1B[21m"; } public static string Red(string s) { return $"\x1b[31m{s}\x1b[39m"; } public static string Yellow(string s) { return $"\x1b[33m{s}\x1b[39m"; } public static string Green(string s) { return $"\x1b[32m{s}\x1b[39m"; } public static string Gray(string s) { return $"\x1b[90m{s}\x1b[39m"; } } }