com.google.gerrit.server.git.ReceiveCommitsAdvertiseRefsHook.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gerrit.server.git.ReceiveCommitsAdvertiseRefsHook.java

Source

// Copyright (C) 2010 The Android Open Source Project
//
// 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 com.google.gerrit.server.git;

import static org.eclipse.jgit.lib.RefDatabase.ALL;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.util.MagicBranch;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;

import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.AdvertiseRefsHook;
import org.eclipse.jgit.transport.BaseReceivePack;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
import org.eclipse.jgit.transport.UploadPack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Map;
import java.util.Set;

/** Exposes only the non refs/changes/ reference names. */
public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
    private static final Logger log = LoggerFactory.getLogger(ReceiveCommitsAdvertiseRefsHook.class);

    private final ReviewDb db;
    private final Provider<InternalChangeQuery> queryProvider;
    private final Project.NameKey projectName;

    public ReceiveCommitsAdvertiseRefsHook(ReviewDb db, Provider<InternalChangeQuery> queryProvider,
            Project.NameKey projectName) {
        this.db = db;
        this.queryProvider = queryProvider;
        this.projectName = projectName;
    }

    @Override
    public void advertiseRefs(UploadPack us) {
        throw new UnsupportedOperationException("ReceiveCommitsAdvertiseRefsHook cannot be used for UploadPack");
    }

    @Override
    public void advertiseRefs(BaseReceivePack rp) throws ServiceMayNotContinueException {
        Map<String, Ref> oldRefs = rp.getAdvertisedRefs();
        if (oldRefs == null) {
            try {
                oldRefs = rp.getRepository().getRefDatabase().getRefs(ALL);
            } catch (ServiceMayNotContinueException e) {
                throw e;
            } catch (IOException e) {
                ServiceMayNotContinueException ex = new ServiceMayNotContinueException();
                ex.initCause(e);
                throw ex;
            }
        }
        Map<String, Ref> r = Maps.newHashMapWithExpectedSize(oldRefs.size());
        for (Map.Entry<String, Ref> e : oldRefs.entrySet()) {
            String name = e.getKey();
            if (!skip(name)) {
                r.put(name, e.getValue());
            }
        }
        rp.setAdvertisedRefs(r, advertiseHistory(r.values(), rp));
    }

    private Set<ObjectId> advertiseHistory(Iterable<Ref> sending, BaseReceivePack rp) {
        Set<ObjectId> toInclude = Sets.newHashSet();

        // Advertise some recent open changes, in case a commit is based one.
        final int limit = 32;
        try {
            Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(limit);
            for (ChangeData cd : queryProvider.get().enforceVisibility(true).setLimit(limit)
                    .byProjectOpen(projectName)) {
                PatchSet.Id id = cd.change().currentPatchSetId();
                if (id != null) {
                    toGet.add(id);
                }
            }
            for (PatchSet ps : db.patchSets().get(toGet)) {
                if (ps.getRevision() != null && ps.getRevision().get() != null) {
                    toInclude.add(ObjectId.fromString(ps.getRevision().get()));
                }
            }
        } catch (OrmException err) {
            log.error("Cannot list open changes of " + projectName, err);
        }

        // Size of an additional ".have" line.
        final int haveLineLen = 4 + Constants.OBJECT_ID_STRING_LENGTH + 1 + 5 + 1;

        // Maximum number of bytes to "waste" in the advertisement with
        // a peek at this repository's current reachable history.
        final int maxExtraSize = 8192;

        // Number of recent commits to advertise immediately, hoping to
        // show a client a nearby merge base.
        final int base = 64;

        // Number of commits to skip once base has already been shown.
        final int step = 16;

        // Total number of commits to extract from the history.
        final int max = maxExtraSize / haveLineLen;

        // Scan history until the advertisement is full.
        Set<ObjectId> alreadySending = Sets.newHashSet();
        RevWalk rw = rp.getRevWalk();
        for (Ref ref : sending) {
            try {
                if (ref.getObjectId() != null) {
                    alreadySending.add(ref.getObjectId());
                    rw.markStart(rw.parseCommit(ref.getObjectId()));
                }
            } catch (IOException badCommit) {
                continue;
            }
        }

        int stepCnt = 0;
        RevCommit c;
        try {
            while ((c = rw.next()) != null && toInclude.size() < max) {
                if (alreadySending.contains(c) || toInclude.contains(c) || c.getParentCount() > 1) {
                    // Do nothing
                } else if (toInclude.size() < base) {
                    toInclude.add(c);
                } else {
                    stepCnt = ++stepCnt % step;
                    if (stepCnt == 0) {
                        toInclude.add(c);
                    }
                }
            }
        } catch (IOException err) {
            log.error("Error trying to advertise history on " + projectName, err);
        }
        rw.reset();
        return toInclude;
    }

    private static boolean skip(String name) {
        return name.startsWith(RefNames.REFS_CHANGES) || name.startsWith(RefNames.REFS_CACHE_AUTOMERGE)
                || MagicBranch.isMagicBranch(name);
    }
}