code: package com.beijunyi.parallelgit.filesystem.io;
  
  import java.io.IOException;
  import java.util.*;
  import javax.annotation.Nonnull;
  
  import com.beijunyi.parallelgit.filesystem.GitFileSystem;
  import com.beijunyi.parallelgit.utils.io.GitFileEntry;
  import org.eclipse.jgit.lib.FileMode;
  
  import static com.beijunyi.parallelgit.filesystem.utils.GfsPathUtils.*;
  
  public class GfsChangesCollector {
  
    private static final GfsChange DELETE_NODE = new DeleteNode();
    private static final GfsChange PREPARE_DIRECTORY = new MakeDirectory();
  
    private final Map<String, GfsChange> changes = new HashMap<>();
    private final Map<String, Set<String>> changedDirs = new HashMap<>();
  
    private final Queue<DirectoryNode> dirs = new LinkedList<>();
    private final Queue<String> paths = new LinkedList<>();
  
    public boolean isEmpty() {
      return changes.isEmpty();
    }
  
    public void addChange(String path, GfsChange change) {
      if(changes.containsKey(path))
        throw new IllegalStateException();
      changes.put(path, change);
      addChangedDirectory(path);
    }
  
    public void addChange(String path, GitFileEntry entry) {
      GfsChange change;
      if(entry.isMissing())
        change = DELETE_NODE;
      else if(entry.isVirtualSubtree())
        change = PREPARE_DIRECTORY;
      else
        change = new UpdateNode(entry);
      addChange(path, change);
    }
  
    public void addChange(String path, byte[] bytes, FileMode mode) {
      addChange(path, new UpdateFile(bytes, mode));
    }
  
    public void applyTo(GitFileSystem gfs) throws IOException {
      dirs.add(gfs.getFileStore().getRoot());
      paths.add("/");
      while(!dirs.isEmpty()) {
        DirectoryNode dir = dirs.poll();
        String path = paths.poll();
        applyChangesToDir(dir, path);
      }
    }
  
    private void addChangedDirectory(String path) {
      if(isRoot(path))
        return;
      int parentLength = path.lastIndexOf('/');
      if(parentLength >= 0) {
        String parent = path.substring(0, parentLength == 0 ? 1 : parentLength);
        String filename = path.substring(parentLength + 1);
        Set<String> siblings = childrenOf(parent);
        if(siblings.add(filename))
          addChangedDirectory(parent);
      }
    }
  
    @Nonnull
    private Set<String> childrenOf(String parent) {
      Set<String> ret = changedDirs.get(parent);
      if(ret == null) {
        ret = new HashSet<>();
        changedDirs.put(parent, ret);
      }
      return ret;
    }
  
    private void applyChangesToDir(DirectoryNode dir, String path) throws IOException {
      String prefix = addTrailingSlash(path);
      for(String childName : childrenOf(path)) {
        String childPath = prefix + childName;
        GfsChange change = changes.get(childPath);
        if(change != null)
          change.applyTo(dir, childName);
        if(changedDirs.containsKey(childPath))
          addSubDirectoryToQueue(childPath, childName, dir);
      }
    }
  
    private void addSubDirectoryToQueue(String childPath, String childName, DirectoryNode dir) throws IOException {
      DirectoryNode child = (DirectoryNode) dir.getChild(childName);
      if(child == null) {
        child = DirectoryNode.newDirectory(dir);
        dir.addChild(childName, child, false);
      }
      dirs.add(child);
      paths.add(childPath);
    }
  
  }
  