Skip to content

Instantly share code, notes, and snippets.

@davedelong
Last active January 30, 2017 12:43
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save davedelong/fdf103445a9180350d9d to your computer and use it in GitHub Desktop.
Save davedelong/fdf103445a9180350d9d to your computer and use it in GitHub Desktop.
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")
}
@carrchr
Copy link

carrchr commented Nov 16, 2014

Nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment