Initial version (0.1.0).

This commit is contained in:
Jonathan Bernard 2017-08-15 16:45:09 -05:00
commit fed862e9d5
3 changed files with 167 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
nimcache/
git_pull_all
*.sw?

151
git_pull_all.nim Normal file
View File

@ -0,0 +1,151 @@
import cliutils, docopt, os, ospaths, osproc, sequtils, strutils, terminal
type
OutputBuffer = ref object
outBuf*, errBuf*: seq[string]
let env = loadEnv()
var outputHandler: HandleProcMsgCB = nil
var verbose = false
var successRepos: seq[string] = @[]
var failRepos: seq[tuple[name, err: string]] = @[]
proc makeBufferLogger(b: OutputBuffer): HandleProcMsgCB =
result = proc(outMsg, errMsg: TaintedString, cmd: string): void {.closure.} =
let prefix = if cmd != nil: cmd & ": " else: ""
if outMsg != nil: b.outBuf.add(prefix & outMsg)
if errMsg != nil: b.errBuf.add(prefix & errMsg)
proc clear(buf: OutputBuffer): void =
buf.outBuf = @[]
buf.errBuf = @[]
proc `$`(buf: OutputBuffer): string =
buf.outBuf.join("\n") & "\n" & buf.errBuf.join("\n")
proc git(repoDir: string, args: openArray[string]): bool =
let res = exec("git", repoDir, args, env, {poUsePath}, outputHandler)
return res.exitCode == 0
proc isGitRepo(dir: string): bool =
if not existsDir(dir): return false
return git(dir, ["rev-parse", "--git-dir"])
proc findGitRepos(rootDir: string): seq[string] =
return toSeq(rootDir.walkDir())
.filterIt(it.kind == pcDir and isGitRepo(it.path))
.mapIt(it.path.expandFilename)
proc writeColoredOutput(b: OutputBuffer): void =
stdout.writeLine(b.outBuf.join("\n"))
stdout.setForegroundColor(fgRed, false)
stdout.writeLine(b.errBuf.join("\n"))
stdout.resetAttributes()
proc writeErrMsg(msg: string): void =
stdout.setForegroundColor(fgRed, false)
stdout.writeLine(msg)
stdout.resetAttributes()
when isMainModule:
try:
let doc = """
Usage:
git_pull_all [options] [<root>...]
Options:
-h --help Print this usage information and exit.
-v --verbose Enable verbose logging.
-V --version Print version
-r --remote <remote> Pull from <remote> (defaults to "origin")
-b --branch <branch> Pull the <branch> branch (defaults to "master")
"""
let args = docopt(doc, version = "git_pull_all 0.1.0")
let rootDirs: seq[string] =
if args["<root>"]: args["<root>"].mapIt(it)
else: @["."]
let remote =
if args["--remote"]: $args["<remote>"]
else: "origin"
let branch =
if args["--branch"]: $args["<branch>"]
else: "master"
# Get a list of repos -> absolute directory paths.
#let repos = rootDirs.map(proc (dir: string): openArray[string] =
let repos = rootDirs.mapIt(findGitRepos(it)).concat()
let cmdOutput = OutputBuffer(outBuf: @[], errBuf: @[])
outputHandler = makeBufferLogger(cmdOutput)
if args["--verbose"]:
verbose = true
outputHandler = combineProcMsgHandlers(outputHandler,
makeProcMsgHandler(stdout, stderr))
# Foreach repo:
for repoDir in repos:
let repoName = repoDir.extractFilename
stdout.setForegroundColor(fgCyan, true)
stdout.write("Pulling ")
stdout.setForegroundColor(fgYellow, false)
stdout.write(repoName & "... ")
stdout.resetAttributes()
if verbose: stdout.writeLine("")
let failRepo = proc(reason: string): void =
failRepos.add((repoName, reason))
writeErrMsg(reason)
# Is the repo clean? No -> fail to pull this repo
cmdOutput.clear()
if not git(repoDir, ["status"]) or
cmdOutput.outBuf.join("\n").find("working tree clean") < 0:
failRepo("working directory is not clean")
continue
# Are we on the correct branch?
cmdOutput.clear()
if not git(repoDir, ["branch"]):
failRepo("could not get current branch")
continue
let branches = cmdOutput.outBuf.filterIt(it.find("* ") > 0)
let currentBranch =
if branches.len == 1: branches[0][7..^1]
else: nil
# not on correct branch, switch branch
if currentBranch != branch:
if not git(repoDir, ["checkout", branch]):
failRepo("could not check out " & branch & " branch")
continue
# pull --ff-only
if not git(repoDir, ["pull", "--ff-only", remote, branch]):
failRepo("unable to ffwd branch")
continue
if cmdOutput.outBuf.anyIt(it.find("Already up-to-date") > 0):
stdout.setForegroundColor(fgBlack, true)
stdout.writeLine("already up-to-date")
else:
stdout.setForegroundColor(fgGreen, true)
stdout.writeLine("UPDATED")
stdout.resetAttributes()
successRepos.add(repoName)
# restore original branch
if currentBranch != branch:
if not git(repoDir, ["checkout", currentBranch]):
stdout.setForegroundColor(fgWhite, true)
stdout.writeLine("WARNING: unable to checkout original branch (" & currentBranch & ")")
stdout.resetAttributes()
except:
stderr.writeLine "git_pull_all: " & getCurrentExceptionMsg()

13
git_pull_all.nimble Normal file
View File

@ -0,0 +1,13 @@
# Package
version = "0.1.0"
author = "Jonathan Bernard"
description = "Small CLI utility to pull multiple git repos."
license = "MIT"
bin = @["git_pull_all"]
# Dependencies
requires @["nim >= 0.16.1", "docopt >= 0.6.4"]
requires "https://git.jdb-labs.com/jdb/nim-cli-utils.git"