org.uberfire.java.nio.fs.jgit.util.commands.RefTreeUpdateCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.uberfire.java.nio.fs.jgit.util.commands.RefTreeUpdateCommand.java

Source

/*
 * Copyright 2016 Red Hat, Inc. and/or its affiliates.
 *
 * 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 org.uberfire.java.nio.fs.jgit.util.commands;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.ketch.Proposal;
import org.eclipse.jgit.internal.storage.reftree.Command;
import org.eclipse.jgit.internal.storage.reftree.RefTree;
import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.uberfire.java.nio.fs.jgit.util.Git;
import org.uberfire.java.nio.fs.jgit.util.exceptions.GitException;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.eclipse.jgit.internal.ketch.Proposal.State.QUEUED;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.MASTER;
import static org.eclipse.jgit.lib.Ref.Storage.NETWORK;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;

public class RefTreeUpdateCommand {

    private final Git git;
    private final String name;
    private final RevCommit commit;

    public RefTreeUpdateCommand(final Git git, final String branchName, final RevCommit commit) {
        this.git = git;
        this.name = branchName;
        this.commit = commit;
    }

    public void execute() throws java.io.IOException, ConcurrentRefUpdateException {
        update(git.getRepository(), Constants.R_HEADS + name, commit);
        //this `initialization` aims to be temporary
        // -> without this cgit can't find master when cloning repos
        if (name.equals(MASTER) && !git.isHEADInitialized()) {
            synchronized (git.getRepository()) {
                symRef(git, HEAD, Constants.R_HEADS + name);
                git.setHeadAsInitialized();
            }
        }
    }

    private void symRef(final Git git, final String name, final String dst) throws java.io.IOException {
        commit(git.getRepository(), null, (reader, tree) -> {
            Ref old = tree.exactRef(reader, name);
            Ref newx = tree.exactRef(reader, dst);
            final Command n;
            if (newx != null) {
                n = new Command(old, new SymbolicRef(name, newx));
            } else {
                n = new Command(old, new SymbolicRef(name, new ObjectIdRef.Unpeeled(Ref.Storage.NEW, dst, null)));
            }
            return tree.apply(Collections.singleton(n));
        });
    }

    private void update(final Repository _repo, final String _name, final RevCommit _commit) throws IOException {
        commit(_repo, _commit, (reader, refTree) -> {
            final Ref old = refTree.exactRef(reader, _name);
            final List<Command> n = new ArrayList<>(1);
            try (RevWalk rw = new RevWalk(_repo)) {
                n.add(new Command(old, toRef(rw, _commit, _name, true)));
                if (git.isKetchEnabled()) {
                    proposeKetch(n, _commit);
                }
            } catch (final IOException | InterruptedException e) {
                String msg = JGitText.get().transactionAborted;
                for (Command cmd : n) {
                    if (cmd.getResult() == NOT_ATTEMPTED) {
                        cmd.setResult(REJECTED_OTHER_REASON, msg);
                    }
                }
                throw new GitException("Error");
                //log.error(msg, e);
            }
            return refTree.apply(n);
        });
    }

    private void proposeKetch(final List<Command> n, final RevCommit _commit)
            throws IOException, InterruptedException {
        final Proposal proposal = new Proposal(n).setAuthor(_commit.getAuthorIdent()).setMessage("push");
        git.getKetchLeader().queueProposal(proposal);
        if (proposal.isDone()) {
            // This failed fast, e.g. conflict or bad precondition.
            throw new GitException("Error");
        }
        if (proposal.getState() == QUEUED) {
            waitForQueue(proposal);
        }
        if (!proposal.isDone()) {
            waitForPropose(proposal);
        }
    }

    private void waitForQueue(final Proposal proposal) throws InterruptedException {
        while (!proposal.awaitStateChange(QUEUED, 250, MILLISECONDS)) {
            System.out.println("waiting queue...");
        }
        switch (proposal.getState()) {
        case RUNNING:
        default:
            break;

        case EXECUTED:
            break;

        case ABORTED:
            break;
        }
    }

    private void waitForPropose(final Proposal proposal) throws InterruptedException {
        while (!proposal.await(250, MILLISECONDS)) {
            System.out.println("waiting propose...");
        }
    }

    private static Ref toRef(final RevWalk rw, final ObjectId id, final String name, final boolean mustExist)
            throws IOException {
        if (ObjectId.zeroId().equals(id)) {
            return null;
        }

        try {
            RevObject o = rw.parseAny(id);
            if (o instanceof RevTag) {
                RevObject p = rw.peel(o);
                return new ObjectIdRef.PeeledTag(NETWORK, name, id, p.copy());
            }
            return new ObjectIdRef.PeeledNonTag(NETWORK, name, id);
        } catch (MissingObjectException e) {
            if (mustExist) {
                throw e;
            }
            return new ObjectIdRef.Unpeeled(NETWORK, name, id);
        }
    }

    interface BiFunction {

        boolean apply(final ObjectReader reader, final RefTree refTree) throws IOException;
    }

    private void commit(final Repository repo, final RevCommit original, final BiFunction fun) throws IOException {
        try (final ObjectReader reader = repo.newObjectReader();
                final ObjectInserter inserter = repo.newObjectInserter();
                final RevWalk rw = new RevWalk(reader)) {

            final RefTreeDatabase refdb = (RefTreeDatabase) repo.getRefDatabase();
            final RefDatabase bootstrap = refdb.getBootstrap();
            final RefUpdate refUpdate = bootstrap.newUpdate(refdb.getTxnCommitted(), false);

            final CommitBuilder cb = new CommitBuilder();
            final Ref ref = bootstrap.exactRef(refdb.getTxnCommitted());
            final RefTree tree;
            if (ref != null && ref.getObjectId() != null) {
                tree = RefTree.read(reader, rw.parseTree(ref.getObjectId()));
                cb.setParentId(ref.getObjectId());
                refUpdate.setExpectedOldObjectId(ref.getObjectId());
            } else {
                tree = RefTree.newEmptyTree();
                refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
            }

            if (fun.apply(reader, tree)) {
                final Ref ref2 = bootstrap.exactRef(refdb.getTxnCommitted());
                if (ref2 == null || ref2.getObjectId().equals(ref != null ? ref.getObjectId() : null)) {
                    cb.setTreeId(tree.writeTree(inserter));
                    if (original != null) {
                        cb.setAuthor(original.getAuthorIdent());
                        cb.setCommitter(original.getAuthorIdent());
                    } else {
                        final PersonIdent personIdent = new PersonIdent("user", "user@example.com");
                        cb.setAuthor(personIdent);
                        cb.setCommitter(personIdent);
                    }
                    refUpdate.setNewObjectId(inserter.insert(cb));
                    inserter.flush();
                    final RefUpdate.Result result = refUpdate.update(rw);
                    switch (result) {
                    case NEW:
                    case FAST_FORWARD:
                        break;
                    default:
                        throw new RuntimeException(
                                repo.getDirectory() + " -> " + result.toString() + " : " + refUpdate.getName());
                    }
                    final File commited = new File(repo.getDirectory(), refdb.getTxnCommitted());
                    final File accepted = new File(repo.getDirectory(), refdb.getTxnNamespace() + "accepted");
                    Files.copy(commited.toPath(), accepted.toPath(), StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }
}