net.morimekta.idltool.cmd.RemoteStatus.java Source code

Java tutorial

Introduction

Here is the source code for net.morimekta.idltool.cmd.RemoteStatus.java

Source

/*
 * Copyright 2017 (c) Stein Eldar Johnsen
 *
 * 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 net.morimekta.idltool.cmd;

import net.morimekta.console.args.ArgumentParser;
import net.morimekta.console.chr.Color;
import net.morimekta.diff.Change;
import net.morimekta.diff.DiffLines;
import net.morimekta.diff.Operation;
import net.morimekta.idltool.IdlTool;
import net.morimekta.idltool.IdlUtils;
import net.morimekta.idltool.meta.Meta;
import net.morimekta.idltool.meta.Remote;
import net.morimekta.util.Strings;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.errors.GitAPIException;

import javax.annotation.Nonnull;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import static net.morimekta.util.io.IOUtils.readString;

/**
 * Interactively manage branches.
 */
public class RemoteStatus extends Command {
    private final boolean diff;

    public RemoteStatus(ArgumentParser parent, boolean diff) {
        super(parent);
        this.diff = diff;
    }

    @Override
    public ArgumentParser makeParser() {
        if (diff) {
            return new ArgumentParser(getParent(), "diff", "Shows file diff between local idl files and remote.");
        } else {
            return new ArgumentParser(getParent(), "status",
                    "Shows status diff between local idl files and remote.");
        }
    }

    @Override
    public void execute(IdlTool idlTool) throws IOException, GitAPIException {
        // Part 1: Check for localRemoteName in repository.
        File localIDL = idlTool.getLocalIdl();
        boolean first = true;

        File localRemoteDir = new File(localIDL, idlTool.getLocalRemoteName());

        if (Files.exists(localRemoteDir.toPath())) {

            Meta publishedMeta = idlTool.getRepositoryMeta(idlTool.getIdl().getUpstreamRepository());
            if (publishedMeta == null) {
                throw new IllegalStateException("Upstream repository does not have valid meta.json");
            }

            Remote publishedRemote = publishedMeta.getRemotes().get(idlTool.getLocalRemoteName());
            if (publishedRemote == null) {
                // assume nothing is published.
                publishedRemote = Remote.builder().build();
            }
            File publishedDir = new File(idlTool.getRepositoryGitCacheIdl(idlTool.getIdl().getUpstreamRepository()),
                    idlTool.getLocalRemoteName());

            Map<String, String> localSha1sums = IdlUtils.buildSha1Sums(localRemoteDir.toPath());
            first = showStatus(true, "local " + idlTool.getLocalRemoteName(), localRemoteDir, localSha1sums,
                    publishedDir, publishedRemote.getShasums());
        }

        Meta localMeta = idlTool.getLocalMeta();
        for (Map.Entry<String, Remote> remoteEntry : localMeta.getRemotes().entrySet()) {
            String remoteName = remoteEntry.getKey();
            if (idlTool.getLocalRemoteName().equals(remoteName)) {
                continue;
            }

            String repository = idlTool.getRemoteRepository(remoteName);
            if (repository == null) {
                System.out.println("No repository for remote " + remoteName);
                continue;
            }
            Remote remoteRemote = idlTool.getRepositoryMeta(repository).getRemotes().get(remoteName);
            if (remoteRemote == null) {
                throw new IllegalStateException("No remote remote");
            }

            File localDir = new File(localIDL, remoteName);
            Map<String, String> localSha1Sums = IdlUtils.buildSha1Sums(localDir.toPath());

            File publishedDir = new File(idlTool.getRepositoryGitCacheIdl(repository), remoteName);
            Map<String, String> publishedSha1Sums = remoteRemote.getShasums();

            first = showStatus(first, "remote " + remoteName, publishedDir, publishedSha1Sums, localDir,
                    localSha1Sums);
        }
    }

    /**
     * Show standard status for a remote.
     *
     * @param first          If this is the first remote to print diffs.
     * @param sourceSha1sums The remote file to sha1sum map.
     * @param targetSha1sums The local file to sha1sum map.
     * @return If the next remote is the first to print diffs.
     */
    private boolean showStatus(boolean first, @Nonnull String remoteName, @Nonnull File sourceDirectory,
            @Nonnull Map<String, String> sourceSha1sums, @Nonnull File targetDirectory,
            @Nonnull Map<String, String> targetSha1sums) throws IOException {
        Set<String> removedFiles = new TreeSet<>(targetSha1sums.keySet());
        removedFiles.removeAll(sourceSha1sums.keySet());

        Set<String> addedFiles = new TreeSet<>(sourceSha1sums.keySet());
        addedFiles.removeAll(targetSha1sums.keySet());

        Set<String> updatedFiles = new TreeSet<>(sourceSha1sums.keySet());
        updatedFiles.removeAll(addedFiles);

        updatedFiles = updatedFiles.stream().filter(f -> !targetSha1sums.get(f).equals(sourceSha1sums.get(f)))
                .collect(Collectors.toSet());

        Set<String> allFiles = new TreeSet<>();
        allFiles.addAll(removedFiles);
        allFiles.addAll(addedFiles);
        allFiles.addAll(updatedFiles);

        if (allFiles.size() == 0) {
            return first;
        }

        int longestName = allFiles.stream().mapToInt(String::length).max().orElse(0);
        int diffSeparatorLength = Math.max(72, longestName + 6 + remoteName.length());

        if (!first) {
            System.out.println();
        }
        System.out.println(String.format("%sUpdates on %s%s", Color.BOLD, remoteName, Color.CLEAR));
        for (String file : allFiles) {
            String paddedFile = StringUtils.rightPad(file, longestName);
            File sourceFile = new File(sourceDirectory, file);
            File targetFile = new File(targetDirectory, file);

            if (diff) {
                System.out.println();
                System.out.println(Color.DIM + Strings.times("#", diffSeparatorLength) + Color.CLEAR);
                paddedFile = remoteName + "/" + paddedFile;
            }

            if (removedFiles.contains(file)) {
                System.out.println(String.format("  %s%s%s (%sD%s)%s", Color.YELLOW, paddedFile, Color.CLEAR,
                        Color.RED, Color.CLEAR, getDiffStats(sourceFile, targetFile)));
            } else if (addedFiles.contains(file)) {
                System.out.println(String.format("  %s%s%s (%sA%s)%s", Color.YELLOW, paddedFile, Color.CLEAR,
                        Color.GREEN, Color.CLEAR, getDiffStats(sourceFile, targetFile)));
            } else {
                System.out.println(String.format("  %s%s%s    %s", Color.YELLOW, paddedFile, Color.CLEAR,
                        getDiffStats(sourceFile, targetFile)));
            }
            if (diff) {
                System.out.println(Color.DIM + Strings.times("-", diffSeparatorLength) + Color.CLEAR);
                printDiffLines(sourceFile, targetFile);
                System.out.println(Color.DIM + Strings.times("#", diffSeparatorLength) + Color.CLEAR);
            }
        }

        return false;
    }

    /**
     * Get line diff string, so that it shows what updates are done if target
     * is overwritten by source file.
     *
     * @param source Source file.
     * @param target Target file.
     * @return Diff line or empty.
     * @throws IOException If file reading failed.
     */
    private String getDiffStats(File source, File target) throws IOException {
        if (!source.exists()) {
            // no source file, all lines are removed.
            return String.format("  (%s+%s%s)", Color.RED, countLines(target), Color.CLEAR);
        }
        if (!target.exists()) {
            // no target file, all lines are added.
            return String.format("  (%s+%s%s)", Color.GREEN, countLines(source), Color.CLEAR);
        }

        // What to do about target to get to source content.
        DiffLines diffLines = new DiffLines(readFile(target), readFile(source));

        long inserts = diffLines.getChangeList().stream().filter(c -> c.operation == Operation.INSERT).count();
        long deletes = diffLines.getChangeList().stream().filter(c -> c.operation == Operation.DELETE).count();

        if (inserts == 0) {
            return String.format("  (%s-%s%s)", Color.RED, deletes, Color.CLEAR);
        } else if (deletes == 0) {
            return String.format("  (%s+%s%s)", Color.GREEN, inserts, Color.CLEAR);
        }

        return String.format("  (%s+%s%s,%s-%s%s)", Color.GREEN, inserts, Color.CLEAR, Color.RED, deletes,
                Color.CLEAR);
    }

    /**
     * Get line diff string, so that it shows what updates are done if target
     * is overwritten by source file.
     *
     * @param source Source file.
     * @param target Target file.
     * @return Diff line or empty.
     * @throws IOException If file reading failed.
     */
    private void printDiffLines(File source, File target) throws IOException {
        if (!source.exists()) {
            // no source file, all lines are removed.
            for (String line : readLines(target)) {
                System.out.println(Color.RED + "- " + line + Color.CLEAR);
            }
        } else if (!target.exists()) {
            // no target file, all lines are added.
            for (String line : readLines(source)) {
                System.out.println(Color.GREEN + "+ " + line + Color.CLEAR);
            }
        } else {
            // What to do about target to get to source content.
            DiffLines diffLines = new DiffLines(readFile(target), readFile(source));

            int skippedLines = 0;
            int sourceLineNo = 0;
            int targetLineNo = 0;

            ArrayList<Change> changes = new ArrayList<>(diffLines.getChangeList());
            for (int i = 0; i < changes.size(); ++i) {
                Change ch = changes.get(i);
                switch (ch.operation) {
                case EQUAL: {
                    ++sourceLineNo;
                    ++targetLineNo;
                    {
                        // print if line (-1..-3) before is not equal.
                        if (i > 0) {
                            Change bef = changes.get(i - 1);
                            if (bef.operation != Operation.EQUAL) {
                                System.out.println(" " + ch.text);
                                continue;
                            }
                        }
                        if (i > 1) {
                            Change bef = changes.get(i - 2);
                            if (bef.operation != Operation.EQUAL) {
                                System.out.println(" " + ch.text);
                                continue;
                            }
                        }
                        if (i > 2) {
                            Change bef = changes.get(i - 3);
                            if (bef.operation != Operation.EQUAL) {
                                System.out.println(" " + ch.text);
                                continue;
                            }
                        }
                    }

                    {
                        // never skip 1 line.
                        if (i > 3 && i < (changes.size() - 4)) {
                            Change bef = changes.get(i - 4);
                            Change aft = changes.get(i + 4);
                            if (bef.operation != Operation.EQUAL && aft.operation != Operation.EQUAL) {
                                System.out.println(" " + ch.text);
                                continue;
                            }
                        }
                    }

                    {
                        // print if line (+1..+3) after is not equal.
                        if (i < (changes.size() - 3)) {
                            Change aft = changes.get(i + 3);
                            if (aft.operation != Operation.EQUAL) {
                                if (skippedLines > 0) {
                                    System.out.println(Color.CYAN + "@@ -" + sourceLineNo + " +" + targetLineNo
                                            + " @@" + Color.CLEAR + Color.DIM + " -- (skipped " + skippedLines
                                            + " lines)" + Color.CLEAR);
                                }

                                System.out.println(" " + ch.text);
                                skippedLines = 0;
                                continue;
                            }
                        }
                        if (i < (changes.size() - 2)) {
                            Change aft = changes.get(i + 2);
                            if (aft.operation != Operation.EQUAL) {
                                System.out.println(" " + ch.text);
                                continue;
                            }
                        }
                        if (i < (changes.size() - 1)) {
                            Change aft = changes.get(i + 1);
                            if (aft.operation != Operation.EQUAL) {
                                System.out.println(" " + ch.text);
                                continue;
                            }
                        }
                    }
                    ++skippedLines;
                    break;
                }
                case DELETE: {
                    ++sourceLineNo;
                    skippedLines = 0;
                    System.out.println(Color.RED + "-" + ch.text + Color.CLEAR);
                    break;
                }
                case INSERT: {
                    ++targetLineNo;
                    skippedLines = 0;
                    System.out.println(Color.GREEN + "+" + ch.text + Color.CLEAR);
                    break;
                }
                }
            }
        }
    }

    private int countLines(File file) throws IOException {
        return readLines(file).length;
    }

    private String[] readLines(File file) throws IOException {
        // remove the last line if empty, e.g. trailing newline vs no trailing newline.
        return readFile(file).replaceAll("\r?\n$", "").split("\r?\n");
    }

    private String readFile(File file) throws IOException {
        try (FileInputStream fis = new FileInputStream(file.getCanonicalFile().getAbsoluteFile())) {
            return readString(new BufferedReader(new InputStreamReader(fis)));
        }
    }
}