Package bear.vcs

Source Code of bear.vcs.GitCLIPlugin$LogParser

/*
* Copyright (C) 2013 Andrey Chaschev.
*
* 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 bear.vcs;

import bear.console.AbstractConsole;
import bear.console.ConsoleCallback;
import bear.console.ConsoleCallbackResult;
import bear.core.GlobalContext;
import bear.core.SessionContext;
import bear.plugins.sh.CommandLine;
import bear.plugins.sh.ResultParser;
import bear.plugins.sh.Script;
import bear.plugins.sh.StubScript;
import bear.session.DynamicVariable;
import bear.session.Variables;
import bear.task.*;
import chaschev.lang.OpenStringBuilder;
import com.google.common.base.Splitter;
import com.google.common.collect.PeekingIterator;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static bear.session.Variables.equalTo;
import static bear.session.Variables.newVar;
import static chaschev.lang.LangUtils.elvis;
import static com.google.common.collect.Iterators.peekingIterator;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Collections.addAll;
import static org.apache.commons.lang3.StringUtils.substringAfter;
import static org.apache.commons.lang3.StringUtils.substringBefore;


/**
* @author Andrey Chaschev chaschev@gmail.com
*/
public class GitCLIPlugin extends VcsCLIPlugin<Task, TaskDef<Object, TaskResult<?>>> {
    public static final ResultParser<LsResult>  LS_PARSER = new ResultParser<LsResult>() {
        @Override
        public LsResult parse(String script, String commandOutput) {
            return new LsResult(commandOutput, convertLsOutput(commandOutput));
        }

        @Override
        public LsResult error(Exception e) {
            LsResult r = new LsResult();

            r.setException(e);

            return r;
        }
    };

    public static final LogParser LOG_PARSER = new LogParser();
    public static final Pattern HASH_REGEX = Pattern.compile("^[0-9a-f]{40}$");


    public final DynamicVariable<Integer> cloneDepth = newVar(-1);
    public final DynamicVariable<Boolean>
        enableSubmodules = newVar(false),
        submodulesRecursive = newVar(true),
        clean = equalTo(bear.clean)
    ;

    public final DynamicVariable<String> remote;

    public GitCLIPlugin(GlobalContext global) {
        super(global, new GitTaskDef());

        ((GitTaskDef) taskDefMixin).git = this;

        remote = Variables.<String>dynamic("remote user").setEqualTo(bear.vcsUsername);
    }

    @Override
    public GitCLIVCSSession newSession(SessionContext $, Task<Object, TaskResult<?>> parent) {
        return new GitCLIVCSSession(parent, taskDefMixin, $);
    }



    /*
    Useful Git commands:

        git --no-pager log  --pretty=oneline|fuller --since="1 years 5 months"
        git --no-pager log -10 --all --date-order  <-- last 10 commits
        git --no-pager show 39c0b36725  <-- commit id
        git --no-pager diff master^^
        git --no-pager diff master~3^~2
        git [-follow] log  -- application.properties

        git branch -r # list remote branches
          andrey/HEAD -> andrey/master
          andrey/master
        mkdir git-demo

     Work cycle:

        cd git-demo
        git init
        git add.
        git commit -m «initial commit»
        git branch new-feature
        git checkout new-feature
        git add.
        git commit -m «Done with the new feature»
        git checkout master
        git diff HEAD new-feature
        git merge new-feature
        git branch -d new-feature
        git log --since=«1 day»

        Tracking remote branches (http://stackoverflow.com/a/1590791/1851024) = origin


     */

    public class GitCLIVCSSession extends VCSSession {
        public GitCLIVCSSession(Task<Object, TaskResult<?>> parent, TaskDef def, SessionContext $) {
            super(parent, def, $);

            addDependency(new Dependency(taskDefMixin, "GIT", $, parent).addCommands("git --version"));
        }

        @Override
        public String head() {
            return elvis($(getBear().vcsBranchName), "HEAD");
        }

        public String origin() {
            return elvis($(remote), "origin");
        }

        @Override
        public String command() {
            return "git";
        }

        protected String verbose() {
            return $.var(getBear().verbose) ? "--verbose" : "";
        }

        @Override
        public VCSScript<?> checkout(String revision, String destination, Map<String, String> params) {
            String git = command();
            String remote = origin();

            List<String> args = new ArrayList<String>();

            if ($.isSet(bear.vcsBranchName)) {
                addAll(args, "-b", $(getBear().vcsBranchName));
            }

//            if (remoteIsNotOrigin(remote)) {
//                addAll(args, "-o", remote);
//            }

            if ($(cloneDepth) != -1) {
                addAll(args, "--depth", "" + $(cloneDepth));
            }

            final VCSScript script = new VCSScript($.sys, this);

            script
                .line()
                .stty().a(git).a("clone", verbose()).a(args)
                .a($(bear.repositoryURI), destination).build()
//                .line()
//                .stty()
//                .cd(destination)
//                .a(git, "checkout", "-b", "deploy", revision)
//                .build()
            ;

            syncSubmodules(git, script);

            return script;
        }

        private void syncSubmodules(String git, Script script) {
            if ($.isSet(enableSubmodules)) {
                script
                    .line().stty().a(git, "submodule", verbose(), "init").build()
                    .line().stty().a(git, "submodule", verbose(), "sync").build();

                if ($(submodulesRecursive)) {
                    script.line().addRaw("export GIT_RECURSIVE=$([ ! \"`#{git} --version`\" \\< \"git version 1.6.5\" ] && echo --recursive");
                } else {
                    script.line().stty().a(git, "submodule", verbose(), "update", "--init").build();
                }
            }
        }

        public VCSScript<?> sync(String revision, String destination, Map<String, String> params) {
            String git = command();
            String remote = origin();

            VCSScript<?> script = newVCSScript()
                .cd(destination);

            // Use git-config to setup a remote tracking branches. Could use
            // git-remote but it complains when a remote of the same name already
            // exists, git-config will just silently overwrite the setting every
            // time. This could cause weirdness in the remote cache if the url
            // changes between calls, but as long as the repositories are all
            // based from each other it should still work fine.

//            if (remoteIsNotOrigin(remote)) {
//                script
//                    .line().stty().a(git, "config", "remote." + remote + ".url", $(getBear().repositoryURI)).build()
//                    .line().stty().a(git, "config", "remote." + remote + ".fetch", "+refs/heads/*:refs/remotes/" + remote + "/*").build();
//            }

            return script
                .line().stty().a(git, "pull", verbose()).build();

            /*//since we're in a local branch already, just reset to specified revision rather than merge
            script
                .line().stty().a(git, "fetch", verbose(), remote).build()
                .line().stty().a(git, "fetch", "--tags", verbose(), remote).build()
                .line().stty().a(git, "reset", verbose(), "--hard", revision).build();

            syncSubmodules(git, script);

            // Make sure there's nothing else lying around in the repository (for
            // example, a submodule that has subsequently been removed).

            //todo think: in capistrano these commands chain

            if($(clean)){
                script.line().a("git", "clean", verbose()).addSplit("-d -x -f").build();
            }

            return script;*/
        }

        @Override
        public VCSScript<? extends BranchInfo> queryRevision(String revision) {
            return queryRevision(revision, emptyParams());
        }

        @Override
        public VCSScript<? extends BranchInfo> queryRevision(String revision, Map<String, String> params) {
            if (revision.startsWith("origin/")) {
                throw new IllegalArgumentException(String.format(
                    "Deploying remote branches is not supported.  Specify the remote branch as a local branch for the git repository you're deploying from (ie: '%s' rather than '%s').",
                    revision.replaceAll("origin/", ""),
                    revision)
                );
            }

            if (validRevision(revision)) {
                return newQueryRevisionResult(revision);
            }

            // this will break the logic a bit
            // queries remote repo to get latest commit revision
            // same as git --no-pager log -1 --all --date-order for local repo
            String newRevision = null;

            final LsResult lsResult = lsRemote(revision).timeoutSec(10).run();

            for (String s : lsResult.getFiles()) {
                final String rev = substringBefore(s, "\t");
                final String ref = substringAfter(s, "\t");

                if (ref.replaceFirst("refs/.*?/", "").trim().equals(revision)) {
                    newRevision = rev;
                }
            }

            if (validRevision(newRevision)) {
                return newQueryRevisionResult(newRevision);
            }

            //If sha is not found on remote, try expanding from local repository

            newRevision = $.sys.sendCommand(commandPrefix("rev-parse", emptyParams())
                .cd($(getBear().vcsBranchLocalPath))
                .a("--revs-only", origin() + "/" + revision)
                .timeoutSec(10)).output.trim();

            if (!validRevision(newRevision)) {
                return newQueryRevisionResult(newRevision);
            }

            throw new RuntimeException(String.format(
                "Unable to resolve revision for '%s' on repository '%s'.", revision, $(getBear().repositoryURI)));
        }

        @Override
        public ConsoleCallback passwordCallback() {
            final String password = $(getBear().vcsPassword);

            return new ConsoleCallback() {
                @Override
                @Nonnull
                public ConsoleCallbackResult progress(AbstractConsole.Terminal console, String buffer, String wholeText) {
                    if (buffer.matches(".*\\bpassword.*:.*")) {
                        console.println(password);
                    } else if (buffer.contains("(yes/no)")) {
                        // git is asking whether or not to connect
                        console.println("yes");
                    } else if (buffer.contains("passphrase")) {
                        // git is asking for the passphrase for the user's key
                        throw new UnsupportedOperationException("user prompt not yet supported!");
                    } else if (buffer.contains("accept (t)emporarily")) {
                        // git is asking whether to accept the certificate
                        console.println("t");
                    }

                    return ConsoleCallbackResult.CONTINUE;
                }
            };
        }


        private VCSScript<? extends BranchInfo> newQueryRevisionResult(String revision) {
            return new StubScript<BranchInfo>($.sys, this, new BranchInfo(null, revision, null));
        }

        @Override
        public VCSScript<?> export(String revision, String destination, Map<String, String> params) {
            VCSScript<?> checkout = checkout(revision, destination, emptyParams());
            checkout.add($.sys.rm(destination + "/.git").asLine());
            return checkout;
        }

        @Override
        public VCSScript<?> diff(String rFrom, String rTo, Map<String, String> params) {
            throw new UnsupportedOperationException("todo: diff");
        }

        @Override
        public VCSScript<?> log(String rFrom, String rTo, Map<String, String> params) {
            throw new UnsupportedOperationException("todo: log");
        }

        @Override
        public VCSScript<VcsLogInfo> logLastN(int n) {
            return newPlainScript("git --no-pager log -" + n + " --all --date-order", GitCLIPlugin.LOG_PARSER);
        }

        public VCSScript<LsResult> ls(String path, Map<String, String> params) {
            //readability on top
            //noinspection unchecked
            return newVCSScript(commandPrefix("ls", params, LsResult.class)
                .a(path)).setParser(LS_PARSER);
        }

        public VCSScript<LsResult> lsRemote(String revision) {
            //noinspection unchecked
            return newVCSScript(commandPrefix("ls-remote", emptyParams(), LsResult.class)
                .a($(getBear().repositoryURI), revision)).setParser(LS_PARSER);
        }

        private CommandLine<CommandLineResult<?>, VCSScript<CommandLineResult<?>>> commandPrefix(String cmd, Map<String, String> params) {
            return (CommandLine)commandPrefix(cmd, params, CommandLineResult.class);
        }

        private <T extends CommandLineResult<?>> CommandLine<T, VCSScript<T>> commandPrefix(String cmd, Map<String, String> params, Class<T> tClass) {
            return $.newCommandLine()
                .stty()
                .a(command(), cmd).p(params);
        }
    }

    @Override
    public InstallationTaskDef<InstallationTask> getInstall() {
        return new InstallationTaskDef<InstallationTask>(new SingleTaskSupplier<Object, TaskResult<?>>() {
            @Override
            public InstallationTask<InstallationTaskDef> createNewSession(SessionContext $, Task<Object, TaskResult<?>> parent, TaskDef<Object, TaskResult<?>> def) {
                return new InstallationTask<InstallationTaskDef>(parent, (InstallationTaskDef) def, $) {
                    @Override
                    protected TaskResult<?> exec(SessionRunner runner) {
                        return $.sys.getPackageManager().installPackage("git");
                    }

                    @Override
                    public Dependency asInstalledDependency() {
                        return new Dependency("git dep", $)
                            .addCommands("git --version");
                    }
                };
            }
        });
    }

    private static boolean validRevision(String revision) {
        return revision != null && HASH_REGEX.matcher(revision).matches();
    }

    private static boolean remoteIsNotOrigin(String remote) {
        return !remote.equals("origin");
    }

    static class GitTaskDef extends TaskDef<Object, TaskResult<?>> {
        private GitCLIPlugin git;

        GitTaskDef() {
            super(new NamedSupplier<Object, TaskResult<?>>("git.session", new SingleTaskSupplier<Object, TaskResult<?>>() {
                @Override
                public Task<Object, TaskResult<?>> createNewSession(SessionContext $, Task<Object, TaskResult<?>> parent, TaskDef<Object, TaskResult<?>> def) {
                    return def.singleTaskSupplier().createNewSession($, parent, def);
                }
            }));
        }
    }

    private static List<String> convertLsOutput(String s) {
        return newArrayList(s.split("\n"));
    }

    static class LogParser implements ResultParser<VcsLogInfo> {
        public static final DateTimeFormatter GIT_DATE_FORMAT = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss yyyy Z");

        @Override
        public VcsLogInfo parse(String script, String s) {
            List<VcsLogInfo.LogEntry> entries = new ArrayList<VcsLogInfo.LogEntry>();

            OpenStringBuilder comment = new OpenStringBuilder(256);

            for (PeekingIterator<String> it = peekingIterator(Splitter.on("\n").trimResults().split(s).iterator()); it.hasNext(); ) {
                String line;

                String revision = null;

                for(line = it.next();it.hasNext();line=it.next()){
                    if(line.startsWith("commit")) {
                        revision = substringAfter(line, " ").trim();
                        Matcher matcher = HASH_REGEX.matcher(revision);
                        if(matcher.matches()){
                            break;
                        }
                    }
                }

                if(revision == null) break;

                while(it.hasNext() && !it.peek().contains("Author: ")) it.next();
                String author = substringAfter(it.next(), "Author: ");
                String stringDate = substringAfter(it.next(), "Date: ").trim();
                DateTime date = DateTime.parse(stringDate, GIT_DATE_FORMAT);

                it.next(); //empty line

                comment.setLength(0);

                while (it.hasNext()) {
                    line = it.peek();
                    String possibleRevision = substringAfter(line, " ");

                    if (HASH_REGEX.matcher(possibleRevision).matches()) {
                        break;
                    }

                    comment.append(it.next()).append("\n");
                }

                comment.trim();

                entries.add(new VcsLogInfo.LogEntry(date, author, comment.toString(), revision));
            }

            return new VcsLogInfo(s, entries);

        }

        @Override
        public VcsLogInfo error(Exception e) {
            VcsLogInfo r = new VcsLogInfo();

            r.setException(e);

            return r;
        }
    }


}
TOP

Related Classes of bear.vcs.GitCLIPlugin$LogParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.