
.10.dp.source-code.git.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core_2.10 Show documentation
Show all versions of core_2.10 Show documentation
Core drx utilities that have no other dependencies other than the core scala lib
The newest version!
/*
Copyright 2010 Aaron J. Radke
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// vim: set ts=3 sw=3 et:
package cc.drx
object Git{
private lazy val gitdirFiles:List[String] = "HEAD config objects refs".split(' ').toList
private def check(dir:File)(implicit ec:ExecutionContext):Option[Git] = if(gitdirFiles forall {f => (dir/f).exists}) Some(new Git(dir)) else None
def find(f:File)(implicit ec:ExecutionContext):Option[Git] = check(f) | check(f/".git") | check(f companion "git") | check(f/"../.git") | check(f/"../../.git")
def apply(f:File)(implicit ec:ExecutionContext):Git = find(f.canon) | new Git(f)
def apply()(implicit ec:ExecutionContext):Git = Git(File.cwd)
//TODO add clone capability and shallow clones
/*
def clone(f:File) = ???
def clone(url:URL) = ???
*/
//TODO use the following classes to make reprts and logs cleaner
/*
case class Rev(sha1:String)
*/
}
class Git private(val gitdir:File)(implicit ec:ExecutionContext){
override def toString = "Git("+gitdir.path+")"
//--type aliases
private type Result[A] = Future[List[A]]
// private val emptyResult = Future.successful(List.empty[String])
//TODO make this private again
private val BareTruePat = """(\s*)(bare\s*=\s*true\s*)""".r.ignoreCase
private val BareFalsePat = """(\s*)(bare\s*=\s*false\s*)""".r.ignoreCase
//--helper vals
val config = gitdir/'config
private lazy val configLines = config.in.lines.toList
//bare features
lazy val isBare:Boolean = configLines exists (BareTruePat matchesFully _)
def syncBareTo(dst:File,dryRun:Boolean = false):Git = {
Sync(gitdir, dst, dryRun)
Git(dst).makeBare
}
def makeBare:Git = {
def explicitlyNotBare = configLines exists (BareFalsePat matchesFully _)
if(isBare) println(Orange("Warn, this repo is already bare 'bare = true'"))
else if(!explicitlyNotBare) println(Red("Error, this can only make repo's bare that explictly set 'bare = false' in .git/config"))
else {
config.out.print{f =>
configLines.map{
case BareFalsePat(space,_) => space + "bare = true"
case line => line
} foreach f.println
}
}
Git(gitdir) //make a new one so the config is scanned again
}
private val gitCmd = List("git","--git-dir", gitdir.path)
//--helper defs
private def git(cmd:String, args:String*):Shell = Shell(gitCmd ++ cmd.split("\\s+") ++ args)
//--tests
/**check if a git repo has modified files */
def dirty:Future[Boolean] = if(isBare) Future.successful(false)
else git("diff-index --quiet HEAD --").exitCode.map{_ != 0}
//exit code for git diff-index (0 == no differences; 1 == differences
// http://stackoverflow.com/a/2659808/622016
//--commands
def autoCommit(msg:String="auto commit"):Result[String] =
for( dirt <- dirty; if(dirt); lines <- git("commit -a -m",msg).lines) yield lines
def commit(msg:String):Result[String] = git("commit -m", msg).lines
def push(args:String*):Result[String] = git("push", args:_*).lines
def pull(args:String*):Result[String] = git("push", args:_*).lines
//--drx-ified commands
private def asFiles(res:Result[String]):Result[File] = res map {_ map File.apply}
// def branch:Try[String] = for(lines <- git("rev-parse --abbrev-ref HEAD"); head <- lines.headOption) yield head //http://stackoverflow.com/a/12142066/622016
// def branch:Future[String] = git("rev-parse --abbrev-ref HEAD") map {_.headOption | ""} //http://stackoverflow.com/a/12142066/622016
def branch:Future[String] = for(lines <- git("rev-parse --abbrev-ref HEAD").lines; if lines.nonEmpty) yield lines.head //http://stackoverflow.com/a/12142066/622016
def staged:Result[File] = this asFiles git("diff --name-only --cached").lines
def diff:Result[File] = this asFiles git("diff --name-only").lines
def patch:Future[String] = git("diff").lines map {_ mkString "\n"}
def show(date:Date,file:File):Result[String] = git("show", "HEAD@{"+date.isoDay+"}:"+file.path).lines
// def status(args:String*):Result = git("status", args:_*)
// def log(args:String*):Result = git("log", args:_*)
def log(format:String):Result[String] = git("log",s"--format=$format","--decorate=full").lines //TODO maybe quotes are needed aounrd the format for the windows version
//TODO make the sha1 a sha1 type rather than a string
case class Ref(path:String) {
lazy val short = path strip "refs/tags/ refs/heads/ refs/remotes/".split(" ").toList
lazy val isTag = path startsWith "refs/tags/"
lazy val isRemote = path startsWith "refs/remotes/"
lazy val isLocal = path startsWith "refs/heads/"
lazy val isVersion = isTag && (
short.startsWith("v") || VersionPat.matchesFully(short)
)
lazy val isKrypton = isTag && KryptonPat.matchesFully(short)
def nice = (if(isVersion) Red else if(isTag) Orange else if(isLocal) Green else Blue) ansi short
private lazy val VersionPat = """v?[\d\.]+""".r
private lazy val KryptonPat = """@[0-9a-zA-Z]+""".r
}
case class Commit(id:String, author:String, email:String, date:Date, msg:String, refs:List[Ref]){
//TODO add link to a parent commit???
lazy val lastName = author.split("\\s+").lastOption.getOrElse(author)
lazy val shortId = id.take(10)
private def niceRefs = if(refs.isEmpty) "" else refs.map{_.nice}.mkString("(",", ",")")
def nice:String = f"$shortId ${lastName take 8}%8s ${date.toRelativeString}%10s ${niceRefs} ${msg take 80}"
}
@deprecated("use commmits instead","di4")
def log:Result[Commit] = commits
def head:Future[Commit] = logCommits("HEAD","-1").map{_.head}
def commits:Result[Commit] = logCommits()
def commits(lastN:Int):Result[Commit] = logCommits(s"-$lastN") //warning the "-100" count limit needs to happen before --no-walk
val tags:Result[(Ref,Commit)] = logCommitRefs("--no-walk", "--tags")
val refs:Result[(Ref,Commit)] = logCommitRefs()
def versions:Result[(Version,Commit)] = tags.map{ts =>
ts.collect{case (ref,c) if ref.isVersion =>
val r = ref.short
val ver = if(r.startsWith("v") || r.startsWith("@")) r.drop(1) else r
Version(ver) -> c
}.sortBy{_._1}
}//filter tags with a tag filter on version type
private def logCommitRefs(optFlags:String*):Result[(Ref,Commit)] =
logCommits(optFlags:_*).map{cs => //map the future commits
for(c <- cs; r <- c.refs) yield r -> c //unfold each ref (some commits may have multiple refs)
}
//http://blog.lost-theory.org/post/how-to-parse-git-log-output/
private def logCommits(optFlags:String*):Result[Commit] = {
val fieldSep = Data.Ansi.Field
val recordSep = Data.Ansi.Record
//name:an email:ae date:at msg:s d:ref list
val format = "H an ae at s d".split(' ').map('%'+_).mkString(fieldSep) + recordSep
val logCmd = git("log",s"--format=$format","--decorate=full") ++ optFlags //TODO maybe quotes are needed aounrd the format for the windows version
val logResult = logCmd.lines
logResult.map{ lines =>
val records = lines.mkString("").split(recordSep)
for(record <- records.toList; f = record split fieldSep; if f.size >= 5 ) yield {
val refs = if(f.size != 6) Nil else {
val stripValues = "HEAD ->,(,),tag:".split(',')
f(5).strip(stripValues).trim
.split("""\s*,\s*""").toList
.map(Ref.apply)
}
Commit(f(0), f(1), f(2), Date.fromSec(f(3).toLong), f(4), refs)
}
}
}
def krypton:Future[Version] = for(h <- head; ts <- tags) yield {
h.refs.find(_.isKrypton) match {
//--if the current head is tagged as a krypton version use this as the version
case Some(headRef) => Version(headRef.short drop 1)
case None =>
ts.map{_._1}.filter(_.isKrypton).map{_.short}.sorted.lastOption match {
//--find the latest tagged krypton version and increment the version based on the current date
case Some(short) => Version.kryptonNext(Version(short drop 1), Date.now)
//--the first release just uses the top level krypton date
case None => Version(Date.now.krypton take 1) //no version has been defined yet
}
}
}
def undo(file:File):Result[String] = git("checkout","--",file.path).lines
def sync(remote:String="origin", branch:String="master"):Result[String] =
for(_ <- autoCommit(); _ <- pull(remote,branch); res <- push(remote,branch) ) yield res
}