A headless Swift program that keeps a directory of git repositories up-to-date
#!/usr/bin/swift | |
import Foundation | |
func scanForRepositories(directory: NSURL, root: NSURL) { | |
let fileManager = NSFileManager.defaultManager() | |
let options: NSDirectoryEnumerationOptions = .SkipsSubdirectoryDescendants | .SkipsPackageDescendants | |
if let contents = fileManager.contentsOfDirectoryAtURL(directory, includingPropertiesForKeys: [NSURLIsDirectoryKey], options: options, error: nil) { | |
let urls = contents as Array<NSURL> | |
urls.filter { | |
var value: AnyObject? | |
$0.getResourceValue(&value, forKey: NSURLIsDirectoryKey, error: nil) | |
if let number = value as? NSNumber { | |
return number.boolValue | |
} | |
return false | |
}.map { | |
updateDirectory($0, root) | |
} | |
} | |
} | |
func updateDirectory(url: NSURL, root: NSURL) { | |
if let git = Git(repository: url) { | |
let relative = url.relativePathToURL(root) | |
println(enboxen(" \(url.lastPathComponent) (\(relative)) ")) | |
git.update() | |
println("\n") | |
} else { | |
scanForRepositories(url, root) | |
} | |
} | |
struct Git { | |
private let repo: NSURL | |
init?(repository: NSURL) { | |
let gitURL = repository.URLByAppendingPathComponent(".git", isDirectory: true) | |
let fileManager = NSFileManager.defaultManager() | |
var isDirectory: ObjCBool = false | |
let gitPath = gitURL.path! | |
if fileManager.fileExistsAtPath(gitPath, isDirectory: &isDirectory) && isDirectory { | |
repo = repository | |
} else { | |
return nil | |
} | |
} | |
func update() { | |
if remotes.count == 0 { | |
// having no remotes is a good indication it's an svn clone | |
svnRebase() | |
} else { | |
pull() | |
} | |
} | |
var remotes: Array<String> { | |
let r = exec(["remote", "-v"]) | |
if countElements(r) == 0 { return [] } | |
return r.componentsSeparatedByString("\n") | |
} | |
private func pull() { | |
exec(["pull", "origin", "master"]) | |
} | |
private func svnRebase() { | |
exec(["svn", "rebase"]) | |
} | |
private func exec(args: Array<String>) -> String { | |
let task = NSTask() | |
task.launchPath = "/usr/bin/git" | |
task.arguments = args | |
task.currentDirectoryPath = repo.path! | |
let pipe = NSPipe() | |
task.standardOutput = pipe | |
task.launch() | |
let data = pipe.fileHandleForReading.readDataToEndOfFile() | |
let output: String = NSString(data: data, encoding: NSUTF8StringEncoding) ?? "" | |
return output.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) | |
} | |
} | |
func *(lhs: String, rhs: Int) -> String { | |
var s = "" | |
for _ in 0 ..< rhs { | |
s.extend(lhs) | |
} | |
return s | |
} | |
func *(lhs: Int, rhs: String) -> String { | |
return rhs * lhs | |
} | |
protocol Box { | |
var TL: String { get } | |
var TR: String { get } | |
var BL: String { get } | |
var BR: String { get } | |
var H: String { get } | |
var V: String { get } | |
} | |
struct SingleLineBox: Box { | |
let TL = "┌" | |
let TR = "┐" | |
let BL = "└" | |
let BR = "┘" | |
let H = "─" | |
let V = "│" | |
} | |
struct ThickLineBox: Box { | |
let TL = "┏" | |
let TR = "┓" | |
let BL = "┗" | |
let BR = "┛" | |
let H = "━" | |
let V = "┃" | |
} | |
struct DoubleLineBox: Box { | |
let TL = "╔" | |
let TR = "╗" | |
let BL = "╚" | |
let BR = "╝" | |
let H = "═" | |
let V = "║" | |
} | |
func enboxen(string: String, box: Box = SingleLineBox()) -> String { | |
let length = countElements(string) | |
let topLine = box.TL + (box.H * length) + box.TR | |
let middle = box.V + string + box.V | |
let bottomLine = box.BL + (box.H * length) + box.BR | |
return join("\n", [topLine, middle, bottomLine]) | |
} | |
extension NSURL { | |
func relativePathToURL(other: NSURL) -> String { | |
let myComponents = pathComponents as Array<String> | |
let baseComponents = other.pathComponents as Array<String> | |
var commonIndex: Int = -1 | |
for var index = 0; index < baseComponents.count; index++ { | |
if baseComponents[index] != myComponents[index] { break } | |
commonIndex++ | |
} | |
if commonIndex == baseComponents.count - 1 { | |
// in base directory | |
let slice = myComponents[commonIndex+1 ..< myComponents.count] | |
return "./" + String.pathWithComponents(Array(slice)) | |
} else if commonIndex >= 0 { | |
// outside of base directory | |
let numParents = baseComponents.count - 1 - commonIndex | |
let goUp = "../" * numParents | |
let slice = myComponents[commonIndex+1 ..< myComponents.count] | |
return goUp + String.pathWithComponents(Array(slice)) | |
} | |
return absoluteString! | |
} | |
} | |
if Process.arguments.count > 1 { | |
let arguments = Process.arguments[1..<Process.arguments.count] | |
for argument in arguments { | |
if let directory = NSURL(fileURLWithPath: argument) { | |
println(enboxen(" Scanning \(argument) ", box: DoubleLineBox())) | |
scanForRepositories(directory, directory) | |
} else { | |
fatalError("\"\(argument)\" is not a valid path") | |
} | |
} | |
} else if let currentDirectory = NSURL(fileURLWithPath: NSFileManager.defaultManager().currentDirectoryPath) { | |
scanForRepositories(currentDirectory, currentDirectory) | |
} else { | |
fatalError("Unable to determine current directory") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
carrchr commentedNov 16, 2014
Nice!