(refs #2)Add forked count and repository tree view.

This commit is contained in:
takezoe
2013-07-25 20:47:35 +09:00
parent 88caff38f0
commit e15bd77789
9 changed files with 129 additions and 28 deletions

View File

@@ -1,6 +1,7 @@
ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_USER_NAME VARCHAR(100); ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_USER_NAME VARCHAR(100);
ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_REPOSITORY_NAME VARCHAR(100); ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_REPOSITORY_NAME VARCHAR(100);
ALTER TABLE REPOSITORY ADD COLUMN PARENT_USER_NAME VARCHAR(100);
ALTER TABLE REPOSITORY ADD COLUMN PARENT_REPOSITORY_NAME VARCHAR(100);
CREATE TABLE PULL_REQUEST( CREATE TABLE PULL_REQUEST(
USER_NAME VARCHAR(100) NOT NULL, USER_NAME VARCHAR(100) NOT NULL,

View File

@@ -116,8 +116,20 @@ trait CreateRepositoryControllerBase extends ControllerBase {
if(getRepository(loginUserName, repository.name, baseUrl).isEmpty){ if(getRepository(loginUserName, repository.name, baseUrl).isEmpty){
// Insert to the database at first // Insert to the database at first
// TODO Is private repository cloneable? // TODO Is private repository cloneable?
createRepository(repository.name, loginUserName, repository.repository.description,
repository.repository.isPrivate, Some(repository.name), Some(repository.owner)) val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
createRepository(
repositoryName = repository.name,
userName = loginUserName,
description = repository.repository.description,
isPrivate = repository.repository.isPrivate,
originRepositoryName = Some(originRepositoryName),
originUserName = Some(originUserName),
parentRepositoryName = Some(repository.name),
parentUserName = Some(repository.owner)
)
// Insert default labels // Insert default labels
insertDefaultLabels(loginUserName, repository.name) insertDefaultLabels(loginUserName, repository.name)

View File

@@ -202,6 +202,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
BadRequest BadRequest
} }
}) })
get("/:owner/:repository/network/members")(referrersOnly { repository =>
repo.html.forked(
getForkedRepositoryTree(
repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name)),
repository)
})
/** /**
* Provides HTML of the file list. * Provides HTML of the file list.

View File

@@ -11,7 +11,9 @@ object Repositories extends Table[Repository]("REPOSITORY") with BasicTemplate {
def lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE") def lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE")
def originUserName = column[String]("ORIGIN_USER_NAME") def originUserName = column[String]("ORIGIN_USER_NAME")
def originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME") def originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
def * = userName ~ repositoryName ~ isPrivate ~ description.? ~ defaultBranch ~ registeredDate ~ updatedDate ~ lastActivityDate ~ originUserName.? ~ originRepositoryName.? <> (Repository, Repository.unapply _) def parentUserName = column[String]("PARENT_USER_NAME")
def parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
def * = userName ~ repositoryName ~ isPrivate ~ description.? ~ defaultBranch ~ registeredDate ~ updatedDate ~ lastActivityDate ~ originUserName.? ~ originRepositoryName.? ~ parentUserName.? ~ parentRepositoryName.? <> (Repository, Repository.unapply _)
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository) def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
} }
@@ -26,5 +28,7 @@ case class Repository(
updatedDate: java.util.Date, updatedDate: java.util.Date,
lastActivityDate: java.util.Date, lastActivityDate: java.util.Date,
originUserName: Option[String], originUserName: Option[String],
originRepositoryName: Option[String] originRepositoryName: Option[String],
parentUserName: Option[String],
parentRepositoryName: Option[String]
) )

View File

@@ -19,7 +19,8 @@ trait RepositoryService { self: AccountService =>
* @param originUserName specify for the forked repository. (default is None) * @param originUserName specify for the forked repository. (default is None)
*/ */
def createRepository(repositoryName: String, userName: String, description: Option[String], isPrivate: Boolean, def createRepository(repositoryName: String, userName: String, description: Option[String], isPrivate: Boolean,
originRepositoryName: Option[String] = None, originUserName: Option[String] = None): Unit = { originRepositoryName: Option[String] = None, originUserName: Option[String] = None,
parentRepositoryName: Option[String] = None, parentUserName: Option[String] = None): Unit = {
Repositories insert Repositories insert
Repository( Repository(
userName = userName, userName = userName,
@@ -31,8 +32,10 @@ trait RepositoryService { self: AccountService =>
updatedDate = currentDate, updatedDate = currentDate,
lastActivityDate = currentDate, lastActivityDate = currentDate,
originUserName = originUserName, originUserName = originUserName,
originRepositoryName = originRepositoryName) originRepositoryName = originRepositoryName,
parentUserName = parentUserName,
parentRepositoryName = parentRepositoryName)
IssueId insert (userName, repositoryName, 0) IssueId insert (userName, repositoryName, 0)
} }
@@ -87,7 +90,13 @@ trait RepositoryService { self: AccountService =>
} }
q1.union(q2).filter(visibleFor(_, loginUserName)).sortBy(_.lastActivityDate desc).list map { repository => q1.union(q2).filter(visibleFor(_, loginUserName)).sortBy(_.lastActivityDate desc).list map { repository =>
new RepositoryInfo(JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), repository) new RepositoryInfo(
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
repository,
getForkedCount(
repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName)
))
} }
} }
@@ -101,7 +110,13 @@ trait RepositoryService { self: AccountService =>
*/ */
def getRepository(userName: String, repositoryName: String, baseUrl: String): Option[RepositoryInfo] = { def getRepository(userName: String, repositoryName: String, baseUrl: String): Option[RepositoryInfo] = {
(Query(Repositories) filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository => (Query(Repositories) filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository =>
new RepositoryInfo(JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), repository) new RepositoryInfo(
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
repository,
getForkedCount(
repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName)
))
} }
} }
@@ -115,7 +130,13 @@ trait RepositoryService { self: AccountService =>
def getAccessibleRepositories(account: Option[Account], baseUrl: String): List[RepositoryInfo] = { def getAccessibleRepositories(account: Option[Account], baseUrl: String): List[RepositoryInfo] = {
def newRepositoryInfo(repository: Repository): RepositoryInfo = { def newRepositoryInfo(repository: Repository): RepositoryInfo = {
new RepositoryInfo(JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), repository) new RepositoryInfo(
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
repository,
getForkedCount(
repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName)
))
} }
(account match { (account match {
@@ -194,28 +215,39 @@ trait RepositoryService { self: AccountService =>
} }
} }
// def getBaseRepositories(userName: String, repositoryName: String, repositories: List[String] = Nil): List[String] = { // TODO It must be _.length instead of map (_.issueId) list).length.
// Query(Repositories).filter { t => // But it does not work on Slick 1.0.1 (worked on Slick 1.0.0).
// (t.originUserName is userName.bind) && (t.originRepositoryName is repositoryName.bind) // https://github.com/slick/slick/issues/170
// }.map(_.userName).list match { private def getForkedCount(userName: String, repositoryName: String): Int =
// case Nil => repositories.sorted Query(Repositories).filter { t =>
// case list => list.map { x => (t.originUserName is userName.bind) && (t.originRepositoryName is repositoryName.bind)
// getBaseRepositories(x, repositoryName, x :: repositories) }.list.length
// }.flatten
// }
// } def getForkedRepositoryTree(userName: String, repositoryName: String): RepositoryTreeNode = {
RepositoryTreeNode(userName, repositoryName,
Query(Repositories).filter { t =>
(t.parentUserName is userName.bind) && (t.parentRepositoryName is repositoryName.bind)
}.map { t =>
t.userName ~ t.repositoryName
}.list.map { case (userName, repositoryName) =>
getForkedRepositoryTree(userName, repositoryName)
}
)
}
} }
object RepositoryService { object RepositoryService {
case class RepositoryInfo(owner: String, name: String, url: String, repository: Repository, case class RepositoryInfo(owner: String, name: String, url: String, repository: Repository,
commitCount: Int, branchList: List[String], tags: List[util.JGitUtil.TagInfo]){ commitCount: Int, forkedCount: Int, branchList: List[String], tags: List[util.JGitUtil.TagInfo]){
def this(repo: JGitUtil.RepositoryInfo, model: Repository) = { def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int) = {
this(repo.owner, repo.name, repo.url, model, repo.commitCount, repo.branchList, repo.tags) this(repo.owner, repo.name, repo.url, model, repo.commitCount, forkedCount, repo.branchList, repo.tags)
} }
} }
case class RepositoryTreeNode(owner: String, name: String, children: List[RepositoryTreeNode])
} }

View File

@@ -16,7 +16,7 @@
} }
</div> </div>
@if(repository.repository.originUserName.isDefined){ @if(repository.repository.originUserName.isDefined){
<div class="small muted">forked from <a href="@path/@repository.repository.originUserName/@repository.repository.originRepositoryName">@repository.repository.originUserName/@repository.repository.originRepositoryName</a></div> <div class="small muted">forked from <a href="@path/@repository.repository.parentUserName/@repository.repository.parentRepositoryName">@repository.repository.parentUserName/@repository.repository.parentRepositoryName</a></div>
} }
@if(repository.repository.description.isDefined){ @if(repository.repository.description.isDefined){
<div>@repository.repository.description</div> <div>@repository.repository.description</div>

View File

@@ -2,7 +2,10 @@
@import context._ @import context._
@import view.helpers._ @import view.helpers._
<div class="pull-right"> <div class="pull-right">
<input type="button" id="fork" class="btn" value="Fork" style="margin-bottom: 10px;"/> <div class="input-prepend">
<input type="button" id="fork" class="btn" value="Fork" style="margin-bottom: 10px;"/>
<span class="add-on"><a href="@url(repository)/network/members">@repository.forkedCount</a></span>
</div>
</div> </div>
<div class="head"> <div class="head">
<a href="@url(repository.owner)">@repository.owner</a> / <a href="@url(repository)">@repository.name</a> <a href="@url(repository.owner)">@repository.owner</a> / <a href="@url(repository)">@repository.name</a>
@@ -12,7 +15,7 @@
@defining(repository.repository){ x => @defining(repository.repository){ x =>
@if(repository.repository.originRepositoryName.isDefined){ @if(repository.repository.originRepositoryName.isDefined){
<div class="forked"> <div class="forked">
forked from <a href="@path/@x.originUserName/@x.originRepositoryName">@x.originUserName/@x.originRepositoryName</a> forked from <a href="@path/@x.parentUserName/@x.parentRepositoryName">@x.parentUserName/@x.parentRepositoryName</a>
</div> </div>
} }
} }

View File

@@ -0,0 +1,28 @@
@(members: service.RepositoryService.RepositoryTreeNode,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._
@import view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
<ul class="nav nav-tabs">
<li class="active"><a href="@url(repository)/network/members">Members</a></li>
</ul>
<h3>Members of the @repository.name Network</h3>
<ul>
@renderTree(members)
</ul>
}
@renderTree(node: service.RepositoryService.RepositoryTreeNode) = {
<li>
<div style="font-size: 120%; margin-bottom: 8px;">
@avatar(node.owner, 20) <a href="@url(node.owner)">@node.owner</a> / <a href="@path/@node.owner/@node.name">@node.name</a>
</div>
@if(node.children.nonEmpty){
<ul>
@node.children.map { child =>
@renderTree(child)
}
</ul>
}
</li>
}

View File

@@ -70,6 +70,19 @@ table.global-nav th a:link, table.global-nav th a:hover, table.global-nav th a:v
text-decoration: none; text-decoration: none;
} }
div.input-prepend span.add-on {
background-color: white;
-webkit-border-radius: 0 4px 4px 0;
-moz-border-radius: 0 4px 4px 0;
border-radius: 0 4px 4px 0;
}
/*
div.input-prepend span.add-on a {
color: #333;
}
*/
/* ======================================================================== */ /* ======================================================================== */
/* General Styles */ /* General Styles */
/* ======================================================================== */ /* ======================================================================== */