All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.change.Files Maven / Gradle / Ivy

// Copyright (C) 2013 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.change;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.CacheControl;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountPatchReview;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.TimeUnit;

@Singleton
public class Files implements ChildCollection {
  private final DynamicMap> views;
  private final Provider list;

  @Inject
  Files(DynamicMap> views, Provider list) {
    this.views = views;
    this.list = list;
  }

  @Override
  public DynamicMap> views() {
    return views;
  }

  @Override
  public RestView list() throws AuthException {
    return list.get();
  }

  @Override
  public FileResource parse(RevisionResource rev, IdString id)
      throws ResourceNotFoundException, OrmException, AuthException {
    return new FileResource(rev, id.get());
  }

  public static final class ListFiles implements RestReadView {
    private static final Logger log = LoggerFactory.getLogger(ListFiles.class);

    @Option(name = "--base", metaVar = "revision-id")
    String base;

    @Option(name = "--reviewed")
    boolean reviewed;

    private final Provider db;
    private final Provider self;
    private final FileInfoJson fileInfoJson;
    private final Revisions revisions;
    private final GitRepositoryManager gitManager;
    private final PatchListCache patchListCache;

    @Inject
    ListFiles(Provider db,
        Provider self,
        FileInfoJson fileInfoJson,
        Revisions revisions,
        GitRepositoryManager gitManager,
        PatchListCache patchListCache) {
      this.db = db;
      this.self = self;
      this.fileInfoJson = fileInfoJson;
      this.revisions = revisions;
      this.gitManager = gitManager;
      this.patchListCache = patchListCache;
    }

    public ListFiles setReviewed(boolean r) {
      this.reviewed = r;
      return this;
    }

    @Override
    public Response apply(RevisionResource resource) throws AuthException,
        BadRequestException, ResourceNotFoundException, OrmException {
      if (base != null && reviewed) {
        throw new BadRequestException("cannot combine base and reviewed");
      } else if (reviewed) {
        return Response.ok(reviewed(resource));
      }

      PatchSet basePatchSet = null;
      if (base != null) {
        RevisionResource baseResource = revisions.parse(
            resource.getChangeResource(), IdString.fromDecoded(base));
        basePatchSet = baseResource.getPatchSet();
      }
      try {
        Response> r = Response.ok(fileInfoJson.toFileInfoMap(
            resource.getChange(),
            resource.getPatchSet(),
            basePatchSet));
        if (resource.isCacheable()) {
          r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
        }
        return r;
      } catch (PatchListNotAvailableException e) {
        throw new ResourceNotFoundException(e.getMessage());
      }
    }

    private List reviewed(RevisionResource resource)
        throws AuthException, OrmException {
      CurrentUser user = self.get();
      if (!(user.isIdentifiedUser())) {
        throw new AuthException("Authentication required");
      }

      Account.Id userId = ((IdentifiedUser) user).getAccountId();
      List r = scan(userId, resource.getPatchSet().getId());

      if (r.isEmpty() && 1 < resource.getPatchSet().getPatchSetId()) {
        for (Integer id : reverseSortPatchSets(resource)) {
          PatchSet.Id old = new PatchSet.Id(resource.getChange().getId(), id);
          List o = scan(userId, old);
          if (!o.isEmpty()) {
            try {
              r = copy(Sets.newHashSet(o), old, resource, userId);
            } catch (IOException | PatchListNotAvailableException e) {
              log.warn("Cannot copy patch review flags", e);
            }
            break;
          }
        }
      }

      return r;
    }

    private List scan(Account.Id userId, PatchSet.Id psId)
        throws OrmException {
      List r = Lists.newArrayList();
      for (AccountPatchReview w : db.get().accountPatchReviews()
          .byReviewer(userId, psId)) {
        r.add(w.getKey().getPatchKey().getFileName());
      }
      return r;
    }

    private List reverseSortPatchSets(
        RevisionResource resource) throws OrmException {
      SortedSet ids = Sets.newTreeSet();
      for (PatchSet p : db.get().patchSets()
          .byChange(resource.getChange().getId())) {
        if (p.getPatchSetId() < resource.getPatchSet().getPatchSetId()) {
          ids.add(p.getPatchSetId());
        }
      }

      List r = Lists.newArrayList(ids);
      Collections.reverse(r);
      return r;
    }

    private List copy(Set paths, PatchSet.Id old,
        RevisionResource resource, Account.Id userId) throws IOException,
        PatchListNotAvailableException, OrmException {
      Repository git =
          gitManager.openRepository(resource.getChange().getProject());
      try {
        ObjectReader reader = git.newObjectReader();
        try {
          PatchList oldList = patchListCache.get(
              resource.getChange(),
              db.get().patchSets().get(old));

          PatchList curList = patchListCache.get(
              resource.getChange(),
              resource.getPatchSet());

          int sz = paths.size();
          List inserts = Lists.newArrayListWithCapacity(sz);
          List pathList = Lists.newArrayListWithCapacity(sz);

          RevWalk rw = new RevWalk(reader);
          TreeWalk tw = new TreeWalk(reader);
          tw.setFilter(PathFilterGroup.createFromStrings(paths));
          tw.setRecursive(true);
          int o = tw.addTree(rw.parseCommit(oldList.getNewId()).getTree());
          int c = tw.addTree(rw.parseCommit(curList.getNewId()).getTree());

          int op = -1;
          if (oldList.getOldId() != null) {
            op = tw.addTree(rw.parseTree(oldList.getOldId()));
          }

          int cp = -1;
          if (curList.getOldId() != null) {
            cp = tw.addTree(rw.parseTree(curList.getOldId()));
          }

          while (tw.next()) {
            String path = tw.getPathString();
            if (tw.getRawMode(o) != 0 && tw.getRawMode(c) != 0
                && tw.idEqual(o, c)
                && paths.contains(path)) {
              // File exists in previously reviewed oldList and in curList.
              // File content is identical.
              inserts.add(new AccountPatchReview(
                  new Patch.Key(
                      resource.getPatchSet().getId(),
                      path),
                    userId));
              pathList.add(path);
            } else if (op >= 0 && cp >= 0
                && tw.getRawMode(o) == 0 && tw.getRawMode(c) == 0
                && tw.getRawMode(op) != 0 && tw.getRawMode(cp) != 0
                && tw.idEqual(op, cp)
                && paths.contains(path)) {
              // File was deleted in previously reviewed oldList and curList.
              // File exists in ancestor of oldList and curList.
              // File content is identical in ancestors.
              inserts.add(new AccountPatchReview(
                  new Patch.Key(
                      resource.getPatchSet().getId(),
                      path),
                    userId));
              pathList.add(path);
            }
          }
          db.get().accountPatchReviews().insert(inserts);
          return pathList;
        } finally {
          reader.release();
        }
      } finally {
        git.close();
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy