blob: f7c2f7f1d8f2471d0060f6a8ee1bdbef859cdbf2 [file] [log] [blame]
/*
* Licensed under the Apache License,Version2.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 org.apache.jenkins.gitpubsub;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import jenkins.scm.api.SCMFile;
import org.eclipse.jgit.lib.Constants;
import org.jsoup.HttpStatusException;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import static org.apache.jenkins.gitpubsub.ASFGitSCMFileSystem.fetchDocument;
import static org.apache.jenkins.gitpubsub.ASFGitSCMNavigator.RFC_2822;
/**
* A {@link SCMFile} that is backed by a GitWeb server hosted on {@code apache.org}.
*/
public class ASFGitSCMFile extends SCMFile {
/**
* The Git URL from which the project and gitweb server can be derived.
*/
private final String remote;
/**
* The ref or hash that the file is being accessed for.
*/
private final String refOrHash;
/**
* Root constructor.
* @param remote The Git URL from which the project and gitweb server can be derived.
* @param refOrHash The ref or hash that the file is being accessed for.
*/
ASFGitSCMFile(String remote, String refOrHash) {
this.remote = remote;
this.refOrHash = refOrHash;
}
/**
* Child constructor.
* @param parent the parent file.
* @param name the name of the child.
*/
ASFGitSCMFile(@NonNull ASFGitSCMFile parent, String name) {
super(parent, name);
this.remote = parent.remote;
this.refOrHash = parent.refOrHash;
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
protected SCMFile newChild(@NonNull String name, boolean assumeIsDirectory) {
return new ASFGitSCMFile(this, name);
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public Iterable<SCMFile> children() throws IOException, InterruptedException {
String treeUrl = ASFGitSCMFileSystem.buildTemplateWithRemote("{+server}{?p}{;a,hb,f}", remote)
.set("a", "tree")
.set("hb", refOrHash)
.set("f", isRoot() ? null : getPath())
.expand();
Document doc = fetchDocument(treeUrl);
Elements elements = doc.select("table.tree tr td.list a");
List<SCMFile> result = new ArrayList<>();
for (Element element : elements) {
String name = element.text();
if (".".equals(name) || "..".equals(name)) {
continue;
}
Element mode = element.parent().previousElementSibling().previousElementSibling();
if (mode.text().startsWith("d")) {
result.add(newChild(element.text(), true));
} else {
result.add(newChild(element.text(), false));
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public long lastModified() throws IOException, InterruptedException {
if (isRoot()) {
if (refOrHash.startsWith(Constants.R_TAGS)) {
String tagUrl = ASFGitSCMFileSystem.buildTemplateWithRemote("{+server}{?p}{;a,h}", remote)
.set("a", "tag")
.set("h", refOrHash)
.expand();
Document doc;
try {
doc = fetchDocument(tagUrl);
} catch (HttpStatusException e) {
if (e.getStatusCode() == 404) {
// must be a lightweight tag
doc = null;
} else {
return 0L;
}
}
if (doc != null) {
Elements elements = doc.select("table.object_header tr td span.datetime");
try {
return new SimpleDateFormat(RFC_2822).parse(elements.get(0).text())
.getTime();
} catch (ParseException | IndexOutOfBoundsException e) {
return 0L;
}
}
}
String commitUrl = ASFGitSCMFileSystem.buildTemplateWithRemote("{+server}{?p}{;a,h}", remote)
.set("a", "commit")
.set("h", refOrHash)
.expand();
Document doc = fetchDocument(commitUrl);
Elements elements = doc.select("table.object_header tr td span.datetime");
try {
return new SimpleDateFormat(RFC_2822).parse(elements.get(1).text()).getTime();
} catch (ParseException | IndexOutOfBoundsException e) {
return 0L;
}
}
String historyUrl = ASFGitSCMFileSystem.buildTemplateWithRemote("{+server}{?p}{;a,hb,f}", remote)
.set("a", "history")
.set("hb", refOrHash)
.set("f", getPath())
.expand();
Document doc = fetchDocument(historyUrl);
Elements elements = doc.select("table.history tr td a.subject");
if (elements.isEmpty()) {
return 0L;
}
Matcher href = ASFGitSCMFileSystem.URL_EXTRACT_H.matcher(elements.get(0).attr("href"));
if (!href.matches()) {
return 0L;
}
String commitUrl = ASFGitSCMFileSystem.buildTemplateWithRemote("{+server}{?p}{;a,h}", remote)
.set("a", "commit")
.set("h", href.group(1))
.expand();
doc = fetchDocument(commitUrl);
elements = doc.select("table.object_header tr td span.datetime");
try {
return new SimpleDateFormat(RFC_2822).parse(elements.get(1).text()).getTime();
} catch (ParseException | IndexOutOfBoundsException e) {
return 0L;
}
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
protected Type type() throws IOException, InterruptedException {
String path = getPath();
int lastSlash = path.lastIndexOf('/');
String treeUrl = ASFGitSCMFileSystem.buildTemplateWithRemote("{+server}{?p}{;a,hb,f}", remote)
.set("a", "tree")
.set("hb", refOrHash)
.set("f", lastSlash == -1 ? null : path.substring(0, lastSlash))
.expand();
Document doc = fetchDocument(treeUrl);
Elements elements = doc.select("table.tree tr td.list a");
for (Element element : elements) {
if (element.text().equals(getName())) {
Element mode = element.parent().previousElementSibling().previousElementSibling();
if (mode.text().startsWith("d")) {
return Type.DIRECTORY;
} else if (mode.text().startsWith("-")) {
return Type.REGULAR_FILE;
}
}
}
return Type.NONEXISTENT;
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public InputStream content() throws IOException, InterruptedException {
String blobUrl = ASFGitSCMFileSystem.buildTemplateWithRemote("{+server}{?p}{;a,f,hb}", remote)
.set("a", "blob_plain")
.set("hb", refOrHash)
.set("f", getPath())
.expand();
return new URL(blobUrl).openStream();
}
}