/* * Copyright 2013 gitblit.com. * * 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.gitblit.service; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceiveCommand.Type; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.TrackingRefUpdate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.git.ReceiveCommandEvent; import com.gitblit.manager.IRepositoryManager; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; import com.gitblit.tickets.BranchTicketService; import com.gitblit.utils.JGitUtils; /** * The Mirror service handles periodic fetching of mirrored repositories. * * @author James Moger * */ public class MirrorService implements Runnable { private final Logger logger = LoggerFactory.getLogger(MirrorService.class); private final Set<String> repairAttempted = Collections.synchronizedSet(new HashSet<String>()); private final IStoredSettings settings; private final IRepositoryManager repositoryManager; private AtomicBoolean running = new AtomicBoolean(false); private AtomicBoolean forceClose = new AtomicBoolean(false); private final UserModel gitblitUser; public MirrorService( IStoredSettings settings, IRepositoryManager repositoryManager) { this.settings = settings; this.repositoryManager = repositoryManager; this.gitblitUser = new UserModel("gitblit"); this.gitblitUser.displayName = "Gitblit"; } public boolean isReady() { return settings.getBoolean(Keys.git.enableMirroring, false); } public boolean isRunning() { return running.get(); } public void close() { forceClose.set(true); } @Override public void run() { if (!isReady()) { return; } running.set(true); for (String repositoryName : repositoryManager.getRepositoryList()) { if (forceClose.get()) { break; } if (repositoryManager.isCollectingGarbage(repositoryName)) { logger.debug("mirror is skipping {} garbagecollection", repositoryName); continue; } RepositoryModel model = null; Repository repository = null; try { model = repositoryManager.getRepositoryModel(repositoryName); if (!model.isMirror && !model.isBare) { // repository must be a valid bare git mirror logger.debug("mirror is skipping {} !mirror !bare", repositoryName); continue; } repository = repositoryManager.getRepository(repositoryName); if (repository == null) { logger.warn(MessageFormat.format("MirrorExecutor is missing repository {0}?!?", repositoryName)); continue; } // automatically repair (some) invalid fetch ref specs if (!repairAttempted.contains(repositoryName)) { repairAttempted.add(repositoryName); JGitUtils.repairFetchSpecs(repository); } // find the first mirror remote - there should only be one StoredConfig rc = repository.getConfig(); RemoteConfig mirror = null; List<RemoteConfig> configs = RemoteConfig.getAllRemoteConfigs(rc); for (RemoteConfig config : configs) { if (config.isMirror()) { mirror = config; break; } } if (mirror == null) { // repository does not have a mirror remote logger.debug("mirror is skipping {} no mirror remote found", repositoryName); continue; } logger.debug("checking {} remote {} for ref updates", repositoryName, mirror.getName()); final boolean testing = false; Git git = new Git(repository); FetchResult result = git.fetch().setRemote(mirror.getName()).setDryRun(testing).call(); Collection<TrackingRefUpdate> refUpdates = result.getTrackingRefUpdates(); if (refUpdates.size() > 0) { ReceiveCommand ticketBranchCmd = null; for (TrackingRefUpdate ru : refUpdates) { StringBuilder sb = new StringBuilder(); sb.append("updated mirror "); sb.append(repositoryName); sb.append(" "); sb.append(ru.getRemoteName()); sb.append(" -> "); sb.append(ru.getLocalName()); if (ru.getResult() == Result.FORCED) { sb.append(" (forced)"); } sb.append(" "); sb.append(ru.getOldObjectId() == null ? "" : ru.getOldObjectId().abbreviate(7).name()); sb.append(".."); sb.append(ru.getNewObjectId() == null ? "" : ru.getNewObjectId().abbreviate(7).name()); logger.info(sb.toString()); if (BranchTicketService.BRANCH.equals(ru.getLocalName())) { ReceiveCommand.Type type = null; switch (ru.getResult()) { case NEW: type = Type.CREATE; break; case FAST_FORWARD: type = Type.UPDATE; break; case FORCED: type = Type.UPDATE_NONFASTFORWARD; break; default: type = null; break; } if (type != null) { ticketBranchCmd = new ReceiveCommand(ru.getOldObjectId(), ru.getNewObjectId(), ru.getLocalName(), type); } } } if (ticketBranchCmd != null) { repository.fireEvent(new ReceiveCommandEvent(model, ticketBranchCmd)); } } } catch (Exception e) { logger.error("Error updating mirror " + repositoryName, e); } finally { // cleanup if (repository != null) { repository.close(); } } } running.set(false); } }