Initial version (0.1.0).
This commit is contained in:
commit
fed862e9d5
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
nimcache/
|
||||
git_pull_all
|
||||
*.sw?
|
151
git_pull_all.nim
Normal file
151
git_pull_all.nim
Normal 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
13
git_pull_all.nimble
Normal 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"
|
Loading…
x
Reference in New Issue
Block a user