List<URIish> getURIs(Project.NameKey project, String urlMatch) {
   List<URIish> r = Lists.newArrayListWithCapacity(config.getRemoteConfig().getURIs().size());
   for (URIish uri : config.getRemoteConfig().getURIs()) {
     if (matches(uri, urlMatch)) {
       String name = project.get();
       if (needsUrlEncoding(uri)) {
         name = encode(name);
       }
       String remoteNameStyle = config.getRemoteNameStyle();
       if (remoteNameStyle.equals("dash")) {
         name = name.replace("/", "-");
       } else if (remoteNameStyle.equals("underscore")) {
         name = name.replace("/", "_");
       } else if (remoteNameStyle.equals("basenameOnly")) {
         name = FilenameUtils.getBaseName(name);
       } else if (!remoteNameStyle.equals("slash")) {
         repLog.debug(
             String.format("Unknown remoteNameStyle: %s, falling back to slash", remoteNameStyle));
       }
       String replacedPath =
           ReplicationQueue.replaceName(uri.getPath(), name, isSingleProjectMatch());
       if (replacedPath != null) {
         uri = uri.setPath(replacedPath);
         r.add(uri);
       }
     }
   }
   return r;
 }
 boolean wouldPushRef(String ref) {
   if (!config.replicatePermissions() && RefNames.REFS_CONFIG.equals(ref)) {
     return false;
   }
   for (RefSpec s : config.getRemoteConfig().getPushRefSpecs()) {
     if (s.matchSource(ref)) {
       return true;
     }
   }
   return false;
 }
  void schedule(Project.NameKey project, String ref, URIish uri, ReplicationState state) {
    repLog.info("scheduling replication {}:{} => {}", project, ref, uri);
    if (!shouldReplicate(project, ref, state)) {
      return;
    }

    if (!config.replicatePermissions()) {
      PushOne e;
      synchronized (stateLock) {
        e = pending.get(uri);
      }
      if (e == null) {
        try (Repository git = gitManager.openRepository(project)) {
          try {
            Ref head = git.exactRef(Constants.HEAD);
            if (head != null
                && head.isSymbolic()
                && RefNames.REFS_CONFIG.equals(head.getLeaf().getName())) {
              return;
            }
          } catch (IOException err) {
            stateLog.error(String.format("cannot check type of project %s", project), err, state);
            return;
          }
        } catch (IOException err) {
          stateLog.error(String.format("source project %s not available", project), err, state);
          return;
        }
      }
    }

    synchronized (stateLock) {
      PushOne e = pending.get(uri);
      if (e == null) {
        e = opFactory.create(project, uri);
        pool.schedule(e, config.getDelay(), TimeUnit.SECONDS);
        pending.put(uri, e);
      }
      e.addRef(ref);
      state.increasePushTaskCount(project.get(), ref);
      e.addState(ref, state);
      repLog.info("scheduled {}:{} => {} to run after {}s", project, ref, e, config.getDelay());
    }
  }
  boolean wouldPushProject(Project.NameKey project) {
    if (!shouldReplicate(project)) {
      return false;
    }

    // by default push all projects
    List<String> projects = config.getProjects();
    if (projects.isEmpty()) {
      return true;
    }

    return (new ReplicationFilter(projects)).matches(project);
  }
 boolean isSingleProjectMatch() {
   List<String> projects = config.getProjects();
   boolean ret = (projects.size() == 1);
   if (ret) {
     String projectMatch = projects.get(0);
     if (ReplicationFilter.getPatternType(projectMatch)
         != ReplicationFilter.PatternType.EXACT_MATCH) {
       // projectMatch is either regular expression, or wild-card.
       //
       // Even though they might refer to a single project now, they need not
       // after new projects have been created. Hence, we do not treat them as
       // matching a single project.
       ret = false;
     }
   }
   return ret;
 }
 int getLockErrorMaxRetries() {
   return config.getLockErrorMaxRetries();
 }
 ImmutableList<String> getProjects() {
   return config.getProjects();
 }
 ImmutableList<String> getAuthGroupNames() {
   return config.getAuthGroupNames();
 }
 ImmutableList<String> getUrls() {
   return config.getUrls();
 }
 boolean isReplicateProjectDeletions() {
   return config.replicateProjectDeletions();
 }
 private boolean shouldReplicate(ProjectControl projectControl) {
   return projectControl.isReadable()
       && (!projectControl.isHidden() || config.replicateHiddenProjects());
 }
 boolean isCreateMissingRepos() {
   return config.createMissingRepos();
 }
  /**
   * It schedules again a PushOp instance.
   *
   * <p>If the reason for rescheduling is to avoid a collision with an in-flight push to the same
   * URI, we don't mark the operation as "retrying," and we schedule using the replication delay,
   * rather than the retry delay. Otherwise, the operation is marked as "retrying" and scheduled to
   * run following the minutes count determined by class attribute retryDelay.
   *
   * <p>In case the PushOp instance to be scheduled has same URI than one marked as "retrying," it
   * adds to the one pending the refs list of the parameter instance.
   *
   * <p>In case the PushOp instance to be scheduled has the same URI as one pending, but not marked
   * "retrying," it indicates the one pending should be canceled when it starts executing, removes
   * it from pending list, and adds its refs to the parameter instance. The parameter instance is
   * scheduled for retry.
   *
   * <p>Notice all operations to indicate a PushOp should be canceled, or it is retrying, or
   * remove/add it from/to pending Map should be protected by synchronizing on the stateLock object.
   *
   * @param pushOp The PushOp instance to be scheduled.
   */
  void reschedule(PushOne pushOp, RetryReason reason) {
    synchronized (stateLock) {
      URIish uri = pushOp.getURI();
      PushOne pendingPushOp = pending.get(uri);

      if (pendingPushOp != null) {
        // There is one PushOp instance already pending to same URI.

        if (pendingPushOp.isRetrying()) {
          // The one pending is one already retrying, so it should
          // maintain it and add to it the refs of the one passed
          // as parameter to the method.

          // This scenario would happen if a PushOp has started running
          // and then before it failed due transport exception, another
          // one to same URI started. The first one would fail and would
          // be rescheduled, being present in pending list. When the
          // second one fails, it will also be rescheduled and then,
          // here, find out replication to its URI is already pending
          // for retry (blocking).
          pendingPushOp.addRefs(pushOp.getRefs());
          pendingPushOp.addStates(pushOp.getStates());
          pushOp.removeStates();

        } else {
          // The one pending is one that is NOT retrying, it was just
          // scheduled believing no problem would happen. The one pending
          // should be canceled, and this is done by setting its canceled
          // flag, removing it from pending list, and adding its refs to
          // the pushOp instance that should then, later, in this method,
          // be scheduled for retry.

          // Notice that the PushOp found pending will start running and,
          // when notifying it is starting (with pending lock protection),
          // it will see it was canceled and then it will do nothing with
          // pending list and it will not execute its run implementation.
          pendingPushOp.canceledByReplication();
          pending.remove(uri);

          pushOp.addRefs(pendingPushOp.getRefs());
          pushOp.addStates(pendingPushOp.getStates());
          pendingPushOp.removeStates();
        }
      }

      if (pendingPushOp == null || !pendingPushOp.isRetrying()) {
        pending.put(uri, pushOp);
        switch (reason) {
          case COLLISION:
            pool.schedule(pushOp, config.getDelay(), TimeUnit.SECONDS);
            break;
          case TRANSPORT_ERROR:
          case REPOSITORY_MISSING:
          default:
            pushOp.setToRetry();
            pool.schedule(pushOp, config.getRetryDelay(), TimeUnit.MINUTES);
            break;
        }
      }
    }
  }
 String getRemoteConfigName() {
   return config.getRemoteConfig().getName();
 }
 boolean isReplicatePermissions() {
   return config.replicatePermissions();
 }
  protected Destination(
      Injector injector,
      DestinationConfiguration cfg,
      RemoteSiteUser.Factory replicationUserFactory,
      PluginUser pluginUser,
      GitRepositoryManager gitRepositoryManager,
      GroupBackend groupBackend,
      ReplicationStateListener stateLog,
      GroupIncludeCache groupIncludeCache) {
    config = cfg;
    gitManager = gitRepositoryManager;
    this.stateLog = stateLog;

    final CurrentUser remoteUser;
    if (!cfg.getAuthGroupNames().isEmpty()) {
      ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
      for (String name : cfg.getAuthGroupNames()) {
        GroupReference g = GroupBackends.findExactSuggestion(groupBackend, name);
        if (g != null) {
          builder.add(g.getUUID());
          addRecursiveParents(g.getUUID(), builder, groupIncludeCache);
        } else {
          repLog.warn(String.format("Group \"%s\" not recognized, removing from authGroup", name));
        }
      }
      remoteUser = replicationUserFactory.create(new ListGroupMembership(builder.build()));
    } else {
      remoteUser = pluginUser;
    }

    Injector child =
        injector.createChildInjector(
            new FactoryModule() {
              @Override
              protected void configure() {
                bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
                bind(PerThreadRequestScope.Propagator.class);
                bind(PerRequestProjectControlCache.class).in(RequestScoped.class);

                bind(Destination.class).toInstance(Destination.this);
                bind(RemoteConfig.class).toInstance(config.getRemoteConfig());
                install(new FactoryModuleBuilder().build(PushOne.Factory.class));
              }

              @Provides
              public PerThreadRequestScope.Scoper provideScoper(
                  final PerThreadRequestScope.Propagator propagator,
                  final Provider<RequestScopedReviewDbProvider> dbProvider) {
                final RequestContext requestContext =
                    new RequestContext() {
                      @Override
                      public CurrentUser getUser() {
                        return remoteUser;
                      }

                      @Override
                      public Provider<ReviewDb> getReviewDbProvider() {
                        return dbProvider.get();
                      }
                    };
                return new PerThreadRequestScope.Scoper() {
                  @Override
                  public <T> Callable<T> scope(Callable<T> callable) {
                    return propagator.scope(requestContext, callable);
                  }
                };
              }
            });

    projectControlFactory = child.getInstance(ProjectControl.Factory.class);
    opFactory = child.getInstance(PushOne.Factory.class);
    threadScoper = child.getInstance(PerThreadRequestScope.Scoper.class);
  }
 public void start(WorkQueue workQueue) {
   String poolName = "ReplicateTo-" + config.getRemoteConfig().getName();
   pool = workQueue.createQueue(config.getPoolThreads(), poolName);
 }