blob: a8eda9782b3f8ed77f26b3ebce74f29d973da52e [file] [log] [blame]
// Copyright (C) 2013 The Android Open Source Project
//
// 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.
package com.google.gerrit.server.project;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.google.inject.util.Providers;
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.kohsuke.args4j.Option;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class ListBranches implements RestReadView<ProjectResource> {
private final GitRepositoryManager repoManager;
private final DynamicMap<RestView<BranchResource>> branchViews;
private final WebLinks webLinks;
@Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "maximum number of branches to list")
private int limit;
@Option(name = "--start", aliases = {"-s"}, metaVar = "CNT", usage = "number of branches to skip")
private int start;
@Option(name = "--match", aliases = {"-m"}, metaVar = "MATCH", usage = "match branches substring")
private String matchSubstring;
@Option(name = "--regex", aliases = {"-r"}, metaVar = "REGEX", usage = "match branches regex")
private String matchRegex;
@Inject
public ListBranches(GitRepositoryManager repoManager,
DynamicMap<RestView<BranchResource>> branchViews,
WebLinks webLinks) {
this.repoManager = repoManager;
this.branchViews = branchViews;
this.webLinks = webLinks;
}
@Override
public List<BranchInfo> apply(ProjectResource rsrc)
throws ResourceNotFoundException, IOException, BadRequestException {
List<BranchInfo> branches = Lists.newArrayList();
BranchInfo headBranch = null;
BranchInfo configBranch = null;
final Set<String> targets = Sets.newHashSet();
final Repository db;
try {
db = repoManager.openRepository(rsrc.getNameKey());
} catch (RepositoryNotFoundException noGitRepository) {
throw new ResourceNotFoundException();
}
try {
List<Ref> refs =
new ArrayList<>(db.getRefDatabase().getRefs(Constants.R_HEADS)
.values());
try {
Ref head = db.getRef(Constants.HEAD);
if (head != null) {
refs.add(head);
}
} catch (IOException e) {
// Ignore the failure reading HEAD.
}
try {
Ref config = db.getRef(RefNames.REFS_CONFIG);
if (config != null) {
refs.add(config);
}
} catch (IOException e) {
// Ignore the failure reading refs/meta/config.
}
for (Ref ref : refs) {
if (ref.isSymbolic()) {
targets.add(ref.getTarget().getName());
}
}
for (Ref ref : refs) {
if (ref.isSymbolic()) {
// A symbolic reference to another branch, instead of
// showing the resolved value, show the name it references.
//
String target = ref.getTarget().getName();
RefControl targetRefControl = rsrc.getControl().controlForRef(target);
if (!targetRefControl.isVisible()) {
continue;
}
if (target.startsWith(Constants.R_HEADS)) {
target = target.substring(Constants.R_HEADS.length());
}
BranchInfo b = new BranchInfo(ref.getName(), target, false);
if (Constants.HEAD.equals(ref.getName())) {
headBranch = b;
} else {
b.setCanDelete(targetRefControl.canDelete());
branches.add(b);
}
continue;
}
final RefControl refControl = rsrc.getControl().controlForRef(ref.getName());
if (refControl.isVisible()) {
if (RefNames.REFS_CONFIG.equals(ref.getName())) {
configBranch = createBranchInfo(ref, refControl, targets);
} else {
branches.add(createBranchInfo(ref, refControl, targets));
}
}
}
} finally {
db.close();
}
Collections.sort(branches, new Comparator<BranchInfo>() {
@Override
public int compare(final BranchInfo a, final BranchInfo b) {
return a.ref.compareTo(b.ref);
}
});
if (configBranch != null) {
branches.add(0, configBranch);
}
if (headBranch != null) {
branches.add(0, headBranch);
}
List<BranchInfo> filteredBranches;
if ((matchSubstring != null && !matchSubstring.isEmpty())
|| (matchRegex != null && !matchRegex.isEmpty())) {
filteredBranches = filterBranches(branches);
} else {
filteredBranches = branches;
}
if (!filteredBranches.isEmpty()) {
int end = filteredBranches.size();
if (limit > 0 && start + limit < end) {
end = start + limit;
}
if (start <= end) {
filteredBranches = filteredBranches.subList(start, end);
} else {
filteredBranches = Collections.emptyList();
}
}
return filteredBranches;
}
private List<BranchInfo> filterBranches(List<BranchInfo> branches)
throws BadRequestException {
if (matchSubstring != null) {
return Lists.newArrayList(Iterables.filter(branches,
new Predicate<BranchInfo>() {
@Override
public boolean apply(BranchInfo in) {
if (!in.ref.startsWith(Constants.R_HEADS)){
return in.ref.toLowerCase(Locale.US).contains(
matchSubstring.toLowerCase(Locale.US));
} else {
return in.ref.substring(Constants.R_HEADS.length())
.toLowerCase(Locale.US)
.contains(matchSubstring.toLowerCase(Locale.US));
}
}
}));
} else if (matchRegex != null) {
if (matchRegex.startsWith("^")) {
matchRegex = matchRegex.substring(1);
if (matchRegex.endsWith("$") && !matchRegex.endsWith("\\$")) {
matchRegex = matchRegex.substring(0, matchRegex.length() - 1);
}
}
if (matchRegex.equals(".*")) {
return branches;
}
try {
final RunAutomaton a =
new RunAutomaton(new RegExp(matchRegex).toAutomaton());
return Lists.newArrayList(Iterables.filter(
branches, new Predicate<BranchInfo>() {
@Override
public boolean apply(BranchInfo in) {
if (!in.ref.startsWith(Constants.R_HEADS)){
return a.run(in.ref);
} else {
return a.run(in.ref.substring(Constants.R_HEADS.length()));
}
}
}));
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getMessage());
}
}
return branches;
}
private BranchInfo createBranchInfo(Ref ref, RefControl refControl,
Set<String> targets) {
BranchInfo info = new BranchInfo(ref.getName(),
ref.getObjectId() != null ? ref.getObjectId().name() : null,
!targets.contains(ref.getName()) && refControl.canDelete());
for (UiAction.Description d : UiActions.from(
branchViews,
new BranchResource(refControl.getProjectControl(), info),
Providers.of(refControl.getCurrentUser()))) {
if (info.actions == null) {
info.actions = new TreeMap<>();
}
info.actions.put(d.getId(), new ActionInfo(d));
}
FluentIterable<WebLinkInfo> links =
webLinks.getBranchLinks(
refControl.getProjectControl().getProject().getName(), ref.getName());
info.webLinks = links.isEmpty() ? null : links.toList();
return info;
}
public static class BranchInfo {
public String ref;
public String revision;
public Boolean canDelete;
public Map<String, ActionInfo> actions;
public List<WebLinkInfo> webLinks;
public BranchInfo(String ref, String revision, boolean canDelete) {
this.ref = ref;
this.revision = revision;
this.canDelete = canDelete;
}
void setCanDelete(boolean canDelete) {
this.canDelete = canDelete ? true : null;
}
}
}