All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.hudson.mercurial.HudsonMercurialSCM Maven / Gradle / Ivy

There is a newer version: RELEASE230
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.netbeans.modules.hudson.mercurial;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.ini4j.Ini;
import org.ini4j.InvalidFileFormatException;
import org.netbeans.modules.hudson.api.ConnectionBuilder;
import org.netbeans.modules.hudson.api.HudsonJob;
import org.netbeans.modules.hudson.api.HudsonJobBuild;
import org.netbeans.modules.hudson.api.Utilities;
import static org.netbeans.modules.hudson.mercurial.Bundle.*;
import org.netbeans.modules.hudson.spi.HudsonJobChangeItem;
import org.netbeans.modules.hudson.spi.HudsonJobChangeItem.HudsonJobChangeFile.EditType;
import org.netbeans.modules.hudson.spi.HudsonSCM;
import org.netbeans.modules.hudson.spi.HudsonSCM.ConfigurationStatus;
import org.netbeans.modules.hudson.ui.api.HudsonSCMHelper;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.openide.windows.OutputListener;
import org.openide.xml.XMLUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * Permits use of Mercurial to create and view Hudson projects.
 */
@ServiceProvider(service=HudsonSCM.class, position=200)
public class HudsonMercurialSCM implements HudsonSCM {

    static final Logger LOG = Logger.getLogger(HudsonMercurialSCM.class.getName());

    @Messages({
        "# {0} - repository location",
        "warning.local_repo={0} will only be accessible from a Hudson server on the same machine.",
        "error.repo.in.parent=Cannot find repository metadata in the project folder. Please configure the build manually."
    })
    public Configuration forFolder(File folder) {
        // XXX could also permit projects as subdirs of Hg repos (lacking SPI currently)
        final FindDefaultPullResult result = findDefaultPull(folder);
        if (!result.found()) {
            return null;
        }
        final URI source = result.getDefaultPull();
        final String repo;
        if ("file".equals(source.getScheme())) { // NOI18N
            repo = org.openide.util.Utilities.toFile(source).getAbsolutePath();
        } else {
            repo = source.toString();
        }
        return new Configuration() {
            public void configure(Document doc) {
                Element root = doc.getDocumentElement();
                Element configXmlSCM = (Element) root.appendChild(doc.createElement("scm")); // NOI18N
                configXmlSCM.setAttribute("class", "hudson.plugins.mercurial.MercurialSCM"); // NOI18N
                configXmlSCM.appendChild(doc.createElement("source")).appendChild(doc.createTextNode(repo)); // NOI18N
                configXmlSCM.appendChild(doc.createElement("modules")).appendChild(doc.createTextNode("")); // NOI18N
                configXmlSCM.appendChild(doc.createElement("clean")).appendChild(doc.createTextNode("true")); // NOI18N
                HudsonSCMHelper.addTrigger(doc);
            }
            public ConfigurationStatus problems() {
                if (result.isFoundInParenFolder()) {
                    return ConfigurationStatus.withError(error_repo_in_parent());
                } else if (!source.isAbsolute() || "file".equals(source.getScheme())) { // NOI18N
                    return ConfigurationStatus.withWarning(warning_local_repo(repo));
                } else {
                    return null;
                }
            }
        };
    }

    /**
     * Find default pull URL in a (project) folder and its ancestor folders.
     */
    private FindDefaultPullResult findDefaultPull(File folder) {
        boolean inParent = false;
        URI defaultPull = null;
        File repoRoot = folder;
        while (repoRoot != null) {
            defaultPull = getDefaultPull(
                    org.openide.util.Utilities.toURI(repoRoot));
            if (defaultPull != null) {
                break;
            } else {
                repoRoot = repoRoot.getParentFile();
                inParent = true;
            }
        }
        return new FindDefaultPullResult(repoRoot, defaultPull, inParent);
    }

    public String translateWorkspacePath(HudsonJob job, String workspacePath, File localRoot) {
        // XXX find repo at or above localRoot, assume workspacePath is repo-relative
        // XXX check whether job's repo matches that of repo, by looking at e.g. head of 00changelog.i
        return null; // XXX
    }

    public @Override List parseChangeSet(final HudsonJobBuild build) {
        final Element changeSet;
        try {
            changeSet = XMLUtil.findElement(new ConnectionBuilder().job(build.getJob()).url(build.getUrl() + "api/xml?tree=changeSet[kind,items[author[fullName],msg,merge,node,addedPaths,modifiedPaths,deletedPaths]]").parseXML().getDocumentElement(), "changeSet", null);
        } catch (IOException x) {
            LOG.log(Level.WARNING, "could not parse changelog for {0}: {1}", new Object[] {build, x});
            return Collections.emptyList();
        }
        if (!"hg".equals(Utilities.xpath("kind", changeSet))) { // NOI18N
            return null;
        }
        URI repo = getDefaultPull(URI.create(build.getJob().getUrl() + "ws/"), build.getJob()); // NOI18N
        if (repo == null) {
            LOG.log(Level.FINE, "No known repo location for {0}", build.getJob());
        }
        if (repo != null && !"http".equals(repo.getScheme()) && !"https".equals(repo.getScheme())) { // NOI18N
            LOG.log(Level.FINE, "Need hgweb to show changes from {0}", repo);
            repo = null;
        }
        final URI _repo = repo;
        class HgItem implements HudsonJobChangeItem {
            final Element itemXML;
            HgItem(Element xml) {
                this.itemXML = xml;
            }
            public String getUser() {
                return Utilities.xpath("author/fullName", itemXML); // NOI18N
            }
            public String getMessage() {
                return Utilities.xpath("msg", itemXML); // NOI18N
            }
            public Collection getFiles() {
                if ("true".equals(Utilities.xpath("merge", itemXML))) { // NOI18N
                    return Collections.emptySet();
                }
                final String node = Utilities.xpath("node", itemXML); // NOI18N
                class HgFile implements HudsonJobChangeFile {
                    final String path;
                    final EditType editType;
                    HgFile(String path, EditType editType) {
                        this.path = path;
                        this.editType = editType;
                    }
                    public String getName() {
                        return path;
                    }
                    public EditType getEditType() {
                        return editType;
                    }
                    public OutputListener hyperlink() {
                        return _repo != null ? new MercurialHyperlink(_repo, node, this) : null;
                    }
                }
                List files = new ArrayList();
                NodeList nl = itemXML.getElementsByTagName("addedPath"); // NOI18N
                for (int i = 0; i < nl.getLength(); i++) {
                    files.add(new HgFile(Utilities.xpath("text()", (Element) nl.item(i)), EditType.add)); // NOI18N
                }
                nl = itemXML.getElementsByTagName("modifiedPath"); // NOI18N
                for (int i = 0; i < nl.getLength(); i++) {
                    files.add(new HgFile(Utilities.xpath("text()", (Element) nl.item(i)), EditType.edit)); // NOI18N
                }
                nl = itemXML.getElementsByTagName("deletedPath"); // NOI18N
                for (int i = 0; i < nl.getLength(); i++) {
                    files.add(new HgFile(Utilities.xpath("text()", (Element) nl.item(i)), EditType.delete)); // NOI18N
                }
                return files;
            }
        }
        List items = new ArrayList();
        NodeList nl = changeSet.getElementsByTagName("item"); // NOI18N
        for (int i = 0; i < nl.getLength(); i++) {
            items.add(new HgItem((Element) nl.item(i)));
        }
        return items;
    }

    /**
     * Try to find the default pull location for a possible Hg repository.
     * @param repository the repository location (checkout root)
     * @return its pull location as an absolute URI ending in a slash,
     *         or null in case it could not be determined
     */
    static URI getDefaultPull(URI repository) {
        return getDefaultPull(repository, null);
    }
    private static URI getDefaultPull(URI repository, HudsonJob job) {
        assert repository.toString().endsWith("/");
        URI hgrc = repository.resolve(".hg/hgrc"); // NOI18N
        String defaultPull = null;
        ClassLoader l = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(HudsonMercurialSCM.class.getClassLoader()); // #141364
        try {
            ConnectionBuilder cb = new ConnectionBuilder();
            if (job != null) {
                cb = cb.job(job);
            }
            InputStream is = cb.url(hgrc.toURL()).connection().getInputStream();
            try {
                Ini ini = new Ini(is);
                Ini.Section section = ini.get("paths"); // NOI18N
                if (section != null) {
                    defaultPull = section.get("default-pull"); // NOI18N
                    if (defaultPull == null) {
                        defaultPull = section.get("default"); // NOI18N
                    }
                }
            } finally {
                is.close();
            }
        } catch (InvalidFileFormatException x) {
            LOG.log(Level.FINE, "{0} was malformed, perhaps no workspace: {1}", new Object[] {hgrc, x});
            return null;
        } catch (FileNotFoundException x) {
            try {
                ConnectionBuilder cb = new ConnectionBuilder();
                if (job != null) {
                    cb = cb.job(job);
                }
                cb.url(repository.resolve(".hg/requires").toURL()).connection();
            } catch (IOException x2) {
                LOG.log(Level.FINE, "{0} is not an Hg repo", repository);
                return null;
            }
            LOG.log(Level.FINE, "{0} not found", hgrc);
            return repository;
        } catch (Exception x) {
            LOG.log(Level.WARNING, "Could not parse " + hgrc, x);
            return null;
        } finally {
            Thread.currentThread().setContextClassLoader(l);
        }
        if (defaultPull == null) {
            LOG.log(Level.FINE, "{0} does not specify paths.default or default-pull", hgrc);
            return repository;
        }
        if (!defaultPull.endsWith("/")) { // NOI18N
            defaultPull += "/"; // NOI18N
        }
        if (defaultPull.startsWith("/") || defaultPull.startsWith("\\")) { // NOI18N
            LOG.log(Level.FINE, "{0} looks like a local file location", defaultPull);
            return org.openide.util.Utilities.toURI(new File(defaultPull));
        } else {
            String defaultPullNoPassword = defaultPull.replaceFirst("//[^/]+(:[^/]+)?@", "//");
            try {
                return repository.resolve(stringToURI(defaultPullNoPassword));
            } catch (URISyntaxException x) {
                LOG.log(Level.FINE, "{0} is not a valid URI", defaultPullNoPassword);
                return null;
            }
        }
    }

    /**
     * Convert string to URI. Ensure that drive letters in paths like
     * C:/something are not used as protocol part of the URI.
     */
    private static URI stringToURI(String s) throws URISyntaxException {
        String withProtocol = org.openide.util.Utilities.isWindows()
                && s.matches("\\w:/[^/].*") //NOI18N
                ? "file:/" + s //NOI18N
                : s;
        return new URI(withProtocol);
    }

    /**
     * Result of finding the default pull. Instances of this class are created
     * by method {@link #findDefaultPull(java.io.File)}
     */
    private static class FindDefaultPullResult {

        private final File localRepoFolder;
        private final URI defaultPull;
        private final boolean foundInParenFolder;

        public FindDefaultPullResult(File localRepoFolder, URI defaultPull,
                boolean foundInParenFolder) {
            this.localRepoFolder = localRepoFolder;
            this.defaultPull = defaultPull;
            this.foundInParenFolder = foundInParenFolder;
        }

        public File getLocalRepoFolder() {
            return localRepoFolder;
        }

        public URI getDefaultPull() {
            return defaultPull;
        }

        /**
         * @return False if the folder is root of the repository, true if the
         * folder is a subfolder of the repository root.
         */
        public boolean isFoundInParenFolder() {
            return foundInParenFolder;
        }

        /**
         * @return True if default pull was found, false otherwise.
         */
        public boolean found() {
            return defaultPull != null;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy