diff --git a/README.md b/README.md index e56432c..f3b36db 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,16 @@ Release Notes -------- +### 1.4 - 31 Jul 2013 +- Group management. +- Repository search for code and issues. +- Display user related issues on the dashboard. +- Display participants avatar of issues on the issue page. +- Performance improvement for repository viewer. +- Alert by milestone due date. +- H2 database administration console. +- Fixed some bugs. + ### 1.3 - 18 Jul 2013 - Batch updating for issues. - Display assigned user on issue list. diff --git a/gitbucket.erd b/gitbucket.erd index 7b63794..74f5fb4 100644 --- a/gitbucket.erd +++ b/gitbucket.erd @@ -23,8 +23,8 @@ -1 -1 - 37 - 36 + 33 + 18 @@ -51,8 +51,8 @@ -1 -1 - 751 - 47 + 723 + 138 @@ -79,8 +79,8 @@ -1 -1 - 882 - 239 + 1182 + 339 @@ -108,8 +108,8 @@ -1 -1 - 940 - 615 + 1301 + 836 @@ -138,8 +138,8 @@ -1 -1 - 420 - 758 + 684 + 858 @@ -167,8 +167,8 @@ -1 -1 - 307 - 356 + 293 + 478 @@ -210,8 +210,8 @@ -1 -1 - 641 - 569 + 875 + 677 @@ -283,9 +283,14 @@ - MILESTONE_NAME - Milestone Name - + TITLE + Title + + VARCHAR + 文字列 + true + 12 + 100 true false @@ -293,6 +298,49 @@ false + + DESCRIPTION + Description + + TEXT + 文字列 + true + 2005 + + + false + false + + false + + + + DUE_DATE + Due Date + + TIMESTAMP + 日時 + false + 93 + + 10 + false + false + + false + + + + CLOSED_DATE + Closed Date + + 10 + false + false + + false + + @@ -350,6 +398,36 @@ + + + + + + 2 + + + + + + + ISSUE_FK_2 + + + + + ASSIGNED_USER_NAME + Assinged User Name + + 100 + false + false + + false + + + + + @@ -375,8 +453,8 @@ -1 -1 - 26 - 660 + 18 + 776 @@ -462,6 +540,22 @@ true + + ACTION + Action + + VARCHAR + 文字列 + true + 12 + + 20 + true + false + Expand to VARCHAR(20) from VARCHAR(10) in 1.3 + false + + CONTENT @@ -498,7 +592,7 @@ UPDATED_DATE Updated Date - + 10 true false @@ -572,10 +666,11 @@ false + TITLE Title - + true false @@ -586,7 +681,7 @@ CONTENT Content - + true false @@ -597,7 +692,7 @@ REGISTERED_DATE Registered Date - + 10 true false @@ -608,7 +703,7 @@ UPDATED_DATE Updated Date - + 10 true false @@ -801,8 +896,8 @@ -1 -1 - 388 - 166 + 481 + 361 @@ -862,6 +957,250 @@ + + + + + + 2 + + + + + + + + + + 2 + + + + + + -1 + -1 + 1199 + 25 + + + + + + + + + 2 + + + + + + + ACTIVITY_FK_2 + + + + + ACTIVITY_USER_NAME + Activity User Name + + 100 + true + false + + false + + + + + + + + + + ACTIVITY + Activity + Since 1.2 + + + ACTIVITY_ID + Activity ID + + INT + 整数 + false + 4 + + 10 + true + true + + true + + + + USER_NAME + User Name + + 100 + true + false + + false + + + + REPOSITORY_NAME + Repository Name + + 100 + true + false + + false + + + + + ACTIVITY_TYPE + Activity Type + + 100 + true + false + + false + + + + MESSAGE + Message + + + true + false + + false + + + + ADDITIONAL_INFO + Additional Information + + + false + false + + false + + + + ACTIVITY_DATE + Activity Date + + 10 + true + false + + false + + + + + + 255 + 255 + 206 + + + + + ACTIVITY_FK_1 + + + + + + + + 2 + + + + + + + + + + 2 + + + + + + -1 + -1 + 1451 + 577 + + + + + + + + COMMIT_LOG + Commit Log + Since 1.2 + + + USER_NAME + User Name + + 100 + true + true + + false + + + + REPOSITORY_NAME + Repository Name + + 100 + true + true + + false + + + + COMMIT_ID + Commit ID + + 40 + true + true + + false + + + + + + 255 + 255 + 206 + + + + + COMMIT_LOG_FK_1 + + @@ -1062,6 +1401,100 @@ + + + + + + + + 2 + + + + + + + + + + 2 + + + + + + -1 + -1 + 432 + 240 + + + + + + + + + 2 + + + + + + + GROUP_MEMBER_FK_2 + + + + + + + GROUP_MEMBER + Group Member + Since 1.4 + + + GROUP_NAME + Group Name + + 100 + true + true + + false + + + + USER_NAME + User Name + + 100 + true + true + + false + + + + + + 255 + 255 + 206 + + + + + GROUP_MEMBER_FK_1 + + + + + + + + @@ -1089,8 +1522,8 @@ PASSWORD Password - - 20 + + 40 true false @@ -1098,18 +1531,18 @@ - USER_TYPE - User Type + ADMINISTRATOR + Administrator - INT - 整数 + BOOLEAN + 真偽値 false - 4 + 16 10 true false - 0:Normal 1:Administrator + false 0 @@ -1157,6 +1590,33 @@ false + + IMAGE + Image + + 100 + false + false + Since 1.3 + false + + + + GROUP_ACCOUNT + Group Account + + BOOLEAN + 真偽値 + false + 16 + + 10 + true + false + Since 1.4 + false + FALSE + @@ -1184,6 +1644,91 @@ + + + + + + + + + 2 + + + + + + -1 + -1 + 410 + 860 + + + + + + ISSUE_OUTLINE_VIEW + Issue Outline View + Since 1.4 + + + USER_NAME + User Name + + 100 + false + false + + false + + + + REPOSITORY_NAME + Repository Name + + 100 + false + false + + false + + + + ISSUE_ID + Issue ID + + INT + 整数 + false + 4 + + 10 + false + false + + false + + + + COMMENT_COUNT + Comment Count + + 10 + false + false + + false + + + + + + 210 + 232 + 249 + + + H2 diff --git a/project/plugins.sbt b/project/plugins.sbt index 3249832..e75b1d5 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.2") -addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.2.0") +addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.1") addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.2.0") diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala index acf066f..48b649b 100644 --- a/src/main/scala/app/AccountController.scala +++ b/src/main/scala/app/AccountController.scala @@ -58,7 +58,7 @@ case _ => _root_.account.html.repositories(account, if(account.isGroupAccount) Nil else getGroupsByUserName(userName), - getVisibleRepositories(userName, baseUrl, context.loginAccount.map(_.userName))) + getVisibleRepositories(context.loginAccount, baseUrl, Some(userName))) } } getOrElse NotFound } diff --git a/src/main/scala/app/DashboardController.scala b/src/main/scala/app/DashboardController.scala index 02e1668..7186d40 100644 --- a/src/main/scala/app/DashboardController.scala +++ b/src/main/scala/app/DashboardController.scala @@ -32,18 +32,26 @@ else IssueSearchCondition(request) session.put(sessionKey, condition) - - val repositories = getAccessibleRepositories(context.loginAccount, baseUrl) + + val userName = context.loginAccount.get.userName + val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name) + val filterUser = Map(filter -> userName) + val page = IssueSearchCondition.page(request) // dashboard.html.issues( - issues.html.listparts(Nil, 0, 0, 0, condition), - 0, - 0, - 0, - repositories, + issues.html.listparts( + searchIssue(condition, filterUser, (page - 1) * IssueLimit, IssueLimit, repositories: _*), + page, + countIssue(condition.copy(state = "open"), filterUser, repositories: _*), + countIssue(condition.copy(state = "closed"), filterUser, repositories: _*), + condition), + countIssue(condition, Map.empty, repositories: _*), + countIssue(condition, Map("assigned" -> userName), repositories: _*), + countIssue(condition, Map("created_by" -> userName), repositories: _*), + countIssueGroupByRepository(condition, filterUser, repositories: _*), condition, filter) } -} \ No newline at end of file +} diff --git a/src/main/scala/app/IndexController.scala b/src/main/scala/app/IndexController.scala index 5612f9b..f827492 100644 --- a/src/main/scala/app/IndexController.scala +++ b/src/main/scala/app/IndexController.scala @@ -16,19 +16,22 @@ val loginAccount = context.loginAccount html.index(getRecentActivities(), - getAccessibleRepositories(loginAccount, baseUrl), + getVisibleRepositories(loginAccount, baseUrl), loadSystemSettings(), - loginAccount.map{ account => getRepositoryNamesOfUser(account.userName) }.getOrElse(Nil) + loginAccount.map{ account => getUserRepositories(account.userName, baseUrl) }.getOrElse(Nil) ) } /** * JSON API for collaborator completion. + * + * TODO Move to other controller? */ - // TODO Move to other controller? get("/_user/proposals")(usersOnly { contentType = formats("json") - org.json4s.jackson.Serialization.write(Map("options" -> getAllUsers.filter(!_.isGroupAccount).map(_.userName).toArray)) + org.json4s.jackson.Serialization.write( + Map("options" -> getAllUsers.filter(!_.isGroupAccount).map(_.userName).toArray) + ) }) diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index 0c8b35f..af8da84 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -301,16 +301,10 @@ private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = { val owner = repository.owner val repoName = repository.name - val userName = if(filter != "all") Some(params("userName")) else None + val filterUser = Map(filter -> params.getOrElse("userName", "")) + val page = IssueSearchCondition.page(request) val sessionKey = s"${owner}/${repoName}/issues" - val page = try { - val i = params.getOrElse("page", "1").toInt - if(i <= 0) 1 else i - } catch { - case e: NumberFormatException => 1 - } - // retrieve search condition val condition = if(request.getQueryString == null){ session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition] @@ -319,17 +313,17 @@ session.put(sessionKey, condition) issues.html.list( - searchIssue(owner, repoName, condition, filter, userName, (page - 1) * IssueLimit, IssueLimit), + searchIssue(condition, filterUser, (page - 1) * IssueLimit, IssueLimit, owner -> repoName), page, (getCollaborators(owner, repoName) :+ owner).sorted, getMilestones(owner, repoName), getLabels(owner, repoName), - countIssue(owner, repoName, condition.copy(state = "open"), filter, userName), - countIssue(owner, repoName, condition.copy(state = "closed"), filter, userName), - countIssue(owner, repoName, condition, "all", None), - context.loginAccount.map(x => countIssue(owner, repoName, condition, "assigned", Some(x.userName))), - context.loginAccount.map(x => countIssue(owner, repoName, condition, "created_by", Some(x.userName))), - countIssueGroupByLabels(owner, repoName, condition, filter, userName), + countIssue(condition.copy(state = "open"), filterUser, owner -> repoName), + countIssue(condition.copy(state = "closed"), filterUser, owner -> repoName), + countIssue(condition, Map.empty, owner -> repoName), + context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), owner -> repoName)), + context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), owner -> repoName)), + countIssueGroupByLabels(owner, repoName, condition, filterUser), condition, filter, repository, diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 6c3b1d7..f5e7dc7 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -38,48 +38,28 @@ }) /** - * Displays the file list of the repository root and the specified branch. - */ - get("/:owner/:repository/tree/:id")(referrersOnly { - fileList(_, params("id")) - }) - - /** * Displays the file list of the specified path and branch. */ - get("/:owner/:repository/tree/:id/*")(referrersOnly { - fileList(_, params("id"), multiParams("splat").head) - }) - - /** - * Displays the commit list of the specified branch. - */ - get("/:owner/:repository/commits/:branch")(referrersOnly { repository => - val branchName = params("branch") - val page = params.getOrElse("page", "1").toInt - JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git => - JGitUtil.getCommitLog(git, branchName, page, 30) match { - case Right((logs, hasNext)) => - repo.html.commits(Nil, branchName, repository, logs.splitWith{ (commit1, commit2) => - view.helpers.date(commit1.time) == view.helpers.date(commit2.time) - }, page, hasNext) - case Left(_) => NotFound - } + get("/:owner/:repository/tree/*")(referrersOnly { repository => + val (id, path) = splitPath(repository, multiParams("splat").head) + if(path.isEmpty){ + fileList(repository, id) + } else { + fileList(repository, id, path) } }) /** * Displays the commit list of the specified resource. */ - get("/:owner/:repository/commits/:branch/*")(referrersOnly { repository => - val branchName = params("branch") - val path = multiParams("splat").head //.replaceFirst("^tree/.+?/", "") - val page = params.getOrElse("page", "1").toInt + get("/:owner/:repository/commits/*")(referrersOnly { repository => + val (branchName, path) = splitPath(repository, multiParams("splat").head) + val page = params.getOrElse("page", "1").toInt JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git => JGitUtil.getCommitLog(git, branchName, page, 30, path) match { case Right((logs, hasNext)) => - repo.html.commits(path.split("/").toList, branchName, repository, + repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository, logs.splitWith{ (commit1, commit2) => view.helpers.date(commit1.time) == view.helpers.date(commit2.time) }, page, hasNext) @@ -91,10 +71,9 @@ /** * Displays the file content of the specified branch or commit. */ - get("/:owner/:repository/blob/:id/*")(referrersOnly { repository => - val id = params("id") // branch name or commit id - val raw = params.get("raw").getOrElse("false").toBoolean - val path = multiParams("splat").head //.replaceFirst("^tree/.+?/", "") + get("/:owner/:repository/blob/*")(referrersOnly { repository => + val (id, path) = splitPath(repository, multiParams("splat").head) + val raw = params.get("raw").getOrElse("false").toBoolean JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git => val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) @@ -211,6 +190,16 @@ repository) }) + private def splitPath(repository: service.RepositoryService.RepositoryInfo, path: String): (String, String) = { + val id = repository.branchList.collectFirst { + case branch if(path == branch || path.startsWith(branch + "/")) => branch + } orElse repository.tags.collectFirst { + case tag if(path == tag.name || path.startsWith(tag.name + "/")) => tag.name + } orElse Some(path) get + + (id, path.substring(id.length).replaceFirst("^/", "")) + } + /** * Provides HTML of the file list. * diff --git a/src/main/scala/app/SearchController.scala b/src/main/scala/app/SearchController.scala index 38bf35a..7a89006 100644 --- a/src/main/scala/app/SearchController.scala +++ b/src/main/scala/app/SearchController.scala @@ -22,7 +22,7 @@ case class SearchForm(query: String, owner: String, repository: String) post("/search", searchForm){ form => - redirect(s"${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}") + redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}") } get("/:owner/:repository/search")(referrersOnly { repository => diff --git a/src/main/scala/service/IssuesService.scala b/src/main/scala/service/IssuesService.scala index ac1ca2b..543c795 100644 --- a/src/main/scala/service/IssuesService.scala +++ b/src/main/scala/service/IssuesService.scala @@ -42,18 +42,16 @@ /** * Returns the count of the search result against issues. * - * @param owner the repository owner - * @param repository the repository name * @param condition the search condition - * @param filter the filter type ("all", "assigned" or "created_by") - * @param userName the filter user name required for "assigned" and "created_by" + * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) + * @param repos Tuple of the repository owner and the repository name * @return the count of the search result */ - def countIssue(owner: String, repository: String, condition: IssueSearchCondition, filter: String, userName: Option[String]): Int = { + def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], repos: (String, String)*): Int = { // TODO It must be _.length instead of map (_.issueId) list).length. // But it does not work on Slick 1.0.1 (worked on Slick 1.0.0). // https://github.com/slick/slick/issues/170 - (searchIssueQuery(owner, repository, condition, filter, userName) map (_.issueId) list).length + (searchIssueQuery(repos, condition, filterUser) map (_.issueId) list).length } /** * Returns the Map which contains issue count for each labels. @@ -61,14 +59,13 @@ * @param owner the repository owner * @param repository the repository name * @param condition the search condition - * @param filter the filter type ("all", "assigned" or "created_by") - * @param userName the filter user name required for "assigned" and "created_by" - * @return the Map which contains issue count for each labels (key is label name, value is issue count), + * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) + * @return the Map which contains issue count for each labels (key is label name, value is issue count) */ def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition, - filter: String, userName: Option[String]): Map[String, Int] = { + filterUser: Map[String, String]): Map[String, Int] = { - searchIssueQuery(owner, repository, condition.copy(labels = Set.empty), filter, userName) + searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser) .innerJoin(IssueLabels).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } @@ -83,24 +80,43 @@ } .toMap } + /** + * Returns list which contains issue count for each repository. + * If the issue does not exist, its repository is not included in the result. + * + * @param condition the search condition + * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) + * @param repos Tuple of the repository owner and the repository name + * @return list which contains issue count for each repository + */ + def countIssueGroupByRepository(condition: IssueSearchCondition, filterUser: Map[String, String], + repos: (String, String)*): List[(String, String, Int)] = { + searchIssueQuery(repos, condition.copy(repo = None), filterUser) + .groupBy { t => + t.userName ~ t.repositoryName + } + .map { case (repo, t) => + repo ~ t.length + } + .filter (_._3 > 0.bind) + .list + } /** * Returns the search result against issues. * - * @param owner the repository owner - * @param repository the repository name * @param condition the search condition - * @param filter the filter type ("all", "assigned" or "created_by") - * @param userName the filter user name required for "assigned" and "created_by" + * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) * @param offset the offset for pagination * @param limit the limit for pagination + * @param repos Tuple of the repository owner and the repository name * @return the search result (list of tuples which contain issue, labels and comment count) */ - def searchIssue(owner: String, repository: String, condition: IssueSearchCondition, - filter: String, userName: Option[String], offset: Int, limit: Int): List[(Issue, List[Label], Int)] = { + def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], + offset: Int, limit: Int, repos: (String, String)*): List[(Issue, List[Label], Int)] = { // get issues and comment count and labels - searchIssueQuery(owner, repository, condition, filter, userName) + searchIssueQuery(repos, condition, filterUser) .innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } .leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) } .leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) } @@ -122,7 +138,11 @@ } .drop(offset).take(limit) .list - .splitWith(_._1.issueId == _._1.issueId) + .splitWith { (c1, c2) => + c1._1.userName == c2._1.userName && + c1._1.repositoryName == c2._1.repositoryName && + c1._1.issueId == c2._1.issueId + } .map { issues => issues.head match { case (issue, commentCount, _,_,_) => (issue, @@ -136,14 +156,18 @@ /** * Assembles query for conditional issue searching. */ - private def searchIssueQuery(owner: String, repository: String, condition: IssueSearchCondition, filter: String, userName: Option[String]) = + private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, filterUser: Map[String, String]) = Query(Issues) filter { t1 => - (t1.byRepository(owner, repository)) && + condition.repo + .map { _.split('/') match { case array => Seq(array(0) -> array(1)) } } + .getOrElse (repos) + .map { case (owner, repository) => t1.byRepository(owner, repository) } + .foldLeft[Column[Boolean]](false) ( _ || _ ) && (t1.closed is (condition.state == "closed").bind) && (t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) && (t1.milestoneId isNull, condition.milestoneId == Some(None)) && - (t1.assignedUserName is userName.get.bind, filter == "assigned") && - (t1.openedUserName is userName.get.bind, filter == "created_by") && + (t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) && + (t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) && (IssueLabels filter { t2 => (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.labelId in @@ -329,6 +353,13 @@ param(request, "state", Seq("open", "closed")).getOrElse("open"), param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"), param(request, "direction", Seq("asc", "desc")).getOrElse("desc")) + + def page(request: HttpServletRequest) = try { + val i = param(request, "page").getOrElse("1").toInt + if(i <= 0) 1 else i + } catch { + case e: NumberFormatException => 1 + } } } diff --git a/src/main/scala/service/RepositoryService.scala b/src/main/scala/service/RepositoryService.scala index ff9d66b..b03a0b5 100644 --- a/src/main/scala/service/RepositoryService.scala +++ b/src/main/scala/service/RepositoryService.scala @@ -62,45 +62,6 @@ Query(Repositories) filter(_.userName is userName.bind) map (_.repositoryName) list /** - * Returns the list of specified user's repositories information. - * - * @param userName the user name - * @param baseUrl the base url of this application - * @param loginUserName the logged in user name - * @return the list of repository information which is sorted in descending order of lastActivityDate. - */ - def getVisibleRepositories(userName: String, baseUrl: String, loginUserName: Option[String]): List[RepositoryInfo] = { - val q1 = Repositories - .filter { t => t.userName is userName.bind } - .map { r => r } - - val q2 = Collaborators - .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) - .filter{ case (t1, t2) => t1.collaboratorName is userName.bind} - .map { case (t1, t2) => t2 } - - def visibleFor(t: Repositories.type, loginUserName: Option[String]) = { - loginUserName match { - case Some(x) => (t.isPrivate is false.bind) || ( - (t.isPrivate is true.bind) && ((t.userName is x.bind) || (Collaborators.filter { c => - c.byRepository(t.userName, t.repositoryName) && (c.collaboratorName is x.bind) - }.exists))) - case None => (t.isPrivate is false.bind) - } - } - - q1.union(q2).filter(visibleFor(_, loginUserName)).sortBy(_.lastActivityDate desc).list map { repository => - new RepositoryInfo( - JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), - repository, - getForkedCount( - repository.originUserName.getOrElse(repository.userName), - repository.originRepositoryName.getOrElse(repository.repositoryName) - )) - } - } - - /** * Returns the specified repository information. * * @param userName the user name of the repository owner @@ -120,16 +81,11 @@ } } - /** - * Returns the list of accessible repositories information for the specified account user. - * - * @param account the account - * @param baseUrl the base url of this application - * @return the repository informations which is sorted in descending order of lastActivityDate. - */ - def getAccessibleRepositories(account: Option[Account], baseUrl: String): List[RepositoryInfo] = { - - def newRepositoryInfo(repository: Repository): RepositoryInfo = { + def getUserRepositories(userName: String, baseUrl: String): List[RepositoryInfo] = { + Query(Repositories).filter { t1 => + (t1.userName is userName.bind) || + (Query(Collaborators).filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName is userName.bind)} exists) + }.sortBy(_.lastActivityDate desc).list.map{ repository => new RepositoryInfo( JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), repository, @@ -138,18 +94,39 @@ repository.originRepositoryName.getOrElse(repository.repositoryName) )) } + } - (account match { + /** + * Returns the list of visible repositories for the specified user. + * If repositoryUserName is given then filters results by repository owner. + * + * @param loginAccount the logged in account + * @param baseUrl the base url of this application + * @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user) + * @return the repository information which is sorted in descending order of lastActivityDate. + */ + def getVisibleRepositories(loginAccount: Option[Account], baseUrl: String, repositoryUserName: Option[String] = None): List[RepositoryInfo] = { + (loginAccount match { // for Administrators case Some(x) if(x.isAdmin) => Query(Repositories) // for Normal Users case Some(x) if(!x.isAdmin) => Query(Repositories) filter { t => (t.isPrivate is false.bind) || - (Query(Collaborators).filter(t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName is x.userName.bind)) exists) + (Query(Collaborators).filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName is x.userName.bind)} exists) } // for Guests case None => Query(Repositories) filter(_.isPrivate is false.bind) - }).sortBy(_.lastActivityDate desc).list.map(newRepositoryInfo _) + }).filter { t => + repositoryUserName.map { userName => t.userName is userName.bind } getOrElse ConstColumn.TRUE + }.sortBy(_.lastActivityDate desc).list.map{ repository => + new RepositoryInfo( + JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), + repository, + getForkedCount( + repository.originUserName.getOrElse(repository.userName), + repository.originRepositoryName.getOrElse(repository.repositoryName) + )) + } } /** diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index 2c44e85..b647d53 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -133,15 +133,18 @@ } /** - * Returns RevCommit from the commit id. + * Returns RevCommit from the commit or tag id. * * @param git the Git object - * @param commitId the ObjectId of the commit - * @return the RevCommit for the specified commit + * @param objectId the ObjectId of the commit or tag + * @return the RevCommit for the specified commit or tag */ - def getRevCommitFromId(git: Git, commitId: ObjectId): RevCommit = { + def getRevCommitFromId(git: Git, objectId: ObjectId): RevCommit = { val revWalk = new RevWalk(git.getRepository) - val revCommit = revWalk.parseCommit(commitId) + val revCommit = revWalk.parseAny(objectId) match { + case r: RevTag => revWalk.parseCommit(r.getObject) + case _ => revWalk.parseCommit(objectId) + } revWalk.dispose revCommit } @@ -349,51 +352,11 @@ * @return the list of latest commit */ def getLatestCommitFromPaths(git: Git, paths: List[String], revision: String): Map[String, RevCommit] = { - - val map = new scala.collection.mutable.HashMap[String, RevCommit] - - val revWalk = new RevWalk(git.getRepository) - revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(revision))) - //revWalk.sort(RevSort.REVERSE); - val i = revWalk.iterator - - while(i.hasNext && map.size != paths.length){ - val commit = i.next - if(commit.getParentCount == 0){ - // Initial commit - val treeWalk = new TreeWalk(git.getRepository) - treeWalk.reset() - treeWalk.setRecursive(true) - treeWalk.addTree(commit.getTree) - while (treeWalk.next) { - paths.foreach { path => - if(treeWalk.getPathString.startsWith(path) && !map.contains(path)){ - map.put(path, commit) - } - } - } - treeWalk.release - } else { - (0 to commit.getParentCount - 1).foreach { i => - val parent = revWalk.parseCommit(commit.getParent(i).getId()) - val df = new DiffFormatter(DisabledOutputStream.INSTANCE) - df.setRepository(git.getRepository) - df.setDiffComparator(RawTextComparator.DEFAULT) - df.setDetectRenames(true) - val diffs = df.scan(parent.getTree(), commit.getTree) - diffs.asScala.foreach { diff => - paths.foreach { path => - if(diff.getChangeType != ChangeType.DELETE && diff.getNewPath.startsWith(path) && !map.contains(path)){ - map.put(path, commit) - } - } - } - } - } - - revWalk.release - } - map.toMap + val start = getRevCommitFromId(git, git.getRepository.resolve(revision)) + paths.map { path => + val commit = git.log.add(start).addPath(path).setMaxCount(1).call.iterator.next + (path, commit) + }.toMap } /** diff --git a/src/main/scala/view/AvatarImageProvider.scala b/src/main/scala/view/AvatarImageProvider.scala index bd1703d..28cf7f3 100644 --- a/src/main/scala/view/AvatarImageProvider.scala +++ b/src/main/scala/view/AvatarImageProvider.scala @@ -12,8 +12,13 @@ */ protected def getAvatarImageHtml(userName: String, size: Int, mailAddress: String = "", tooltip: Boolean = false)(implicit context: app.Context): Html = { - val src = getAccountByUserName(userName).collect { case account if(account.image.isEmpty) => - s"""http://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress)}?s=${size}""" + + val src = getAccountByUserName(userName).map { account => + if(account.image.isEmpty){ + s"""http://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress)}?s=${size}""" + } else { + s"""${context.path}/${userName}/_avatar""" + } } getOrElse { if(mailAddress.nonEmpty){ s"""http://www.gravatar.com/avatar/${StringUtil.md5(mailAddress)}?s=${size}""" diff --git a/src/main/twirl/dashboard/issues.scala.html b/src/main/twirl/dashboard/issues.scala.html index b1839ab..9ebdede 100644 --- a/src/main/twirl/dashboard/issues.scala.html +++ b/src/main/twirl/dashboard/issues.scala.html @@ -2,7 +2,7 @@ allCount: Int, assignedCount: Int, createdByCount: Int, - repositories: List[service.RepositoryService.RepositoryInfo], + repositories: List[(String, String, Int)], condition: service.IssuesService.IssueSearchCondition, filter: String)(implicit context: app.Context) @import context._ @@ -13,19 +13,19 @@

} - Files + Files Commits - Tags@if(repository.tags.length > 0){ @repository.tags.length} + Tags@if(repository.tags.length > 0){ @repository.tags.length}
  • diff --git a/src/main/twirl/settings/options.scala.html b/src/main/twirl/settings/options.scala.html index 59708ce..d386ac3 100644 --- a/src/main/twirl/settings/options.scala.html +++ b/src/main/twirl/settings/options.scala.html @@ -13,8 +13,7 @@ -
    -
    +
    -