summaryrefslogtreecommitdiff
path: root/plugins/svn4idea/src/org/jetbrains/idea/svn/ForNestedRootChecker.java
blob: e87c55dc447dbd57d036b585cfa653a057e0a36f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * 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 org.jetbrains.idea.svn;

import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.vcs.impl.VcsRootIterator;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.commandLine.SvnBindException;
import org.jetbrains.idea.svn.info.Info;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.wc.SVNRevision;

import java.io.File;
import java.util.LinkedList;
import java.util.List;

public class ForNestedRootChecker {

  @NotNull private final SvnVcs myVcs;
  @NotNull private final VcsRootIterator myRootIterator;

  public ForNestedRootChecker(@NotNull SvnVcs vcs) {
    myVcs = vcs;
    myRootIterator = new VcsRootIterator(vcs.getProject(), vcs);
  }

  public List<Node> getAllNestedWorkingCopies(@NotNull final VirtualFile root, final boolean goIntoNested) {
    final LinkedList<WorkItem> workItems = new LinkedList<WorkItem>();
    final LinkedList<Node> result = new LinkedList<Node>();

    workItems.add(new WorkItem(root));
    while (!workItems.isEmpty()) {
      final WorkItem item = workItems.removeFirst();
      checkCancelled();

      // check self
      final Node vcsElement = new VcsFileResolver(myVcs, item.file).resolve();
      // TODO: actually goIntoNested = false always => item.url will be always null when this line is reached
      if (vcsElement != null && (item.url == null || vcsElement.onUrl(item.url))) {
        result.add(vcsElement);
        if (!goIntoNested) {
          continue;
        }
      }

      // for next step
      final VirtualFile file = item.file;
      if (file.isDirectory() && (! SvnUtil.isAdminDirectory(file))) {
        // TODO: Only directory children should be checked.
        for (VirtualFile child : file.getChildren()) {
          checkCancelled();

          if (myRootIterator.acceptFolderUnderVcs(root, child)) {
            // TODO: actually goIntoNested = false always => we could reach this line only when vcsElement is null
            workItems.add(WorkItem.create(vcsElement, child));
          }
        }
      }
    }
    return result;
  }

  private void checkCancelled() {
    if (myVcs.getProject().isDisposed()) {
      throw new ProcessCanceledException();
    }
  }

  private static class WorkItem {

    @NotNull private final VirtualFile file;
    @Nullable private final SVNURL url;

    private WorkItem(@NotNull VirtualFile file) {
      this(file, null);
    }

    private WorkItem(@NotNull VirtualFile file, @Nullable SVNURL url) {
      this.file = file;
      this.url = url;
    }

    @NotNull
    private static WorkItem create(@Nullable Node node, @NotNull VirtualFile child) {
      return node == null ? new WorkItem(child) : new WorkItem(child, SvnUtil.append(node.getUrl(), child.getName()));
    }
  }

  private static class VcsFileResolver {

    @NotNull private final SvnVcs myVcs;
    @NotNull private final VirtualFile myFile;
    @NotNull private final File myIoFile;
    @Nullable private Info myInfo;
    @Nullable private SvnBindException myError;

    private VcsFileResolver(@NotNull SvnVcs vcs, @NotNull VirtualFile file) {
      myVcs = vcs;
      myFile = file;
      myIoFile = VfsUtilCore.virtualToIoFile(file);
    }

    @Nullable
    public Node resolve() {
      runInfo();

      return processInfo();
    }

    private void runInfo() {
      try {
        myInfo = myVcs.getFactory(myIoFile, false).createInfoClient().doInfo(myIoFile, SVNRevision.UNDEFINED);
      }
      catch (SvnBindException e) {
        myError = e;
      }
    }

    @Nullable
    private Node processInfo() {
      Node result = null;

      if (myError != null) {
        if (!SvnUtil.isUnversionedOrNotFound(myError)) {
          // error code does not indicate that myFile is unversioned or path is invalid => create result, but indicate error
          result = new Node(myFile, getFakeUrl(), getFakeUrl(), myError);
        }
      }
      else if (myInfo != null && myInfo.getRepositoryRootURL() != null && myInfo.getURL() != null) {
        result = new Node(myFile, myInfo.getURL(), myInfo.getRepositoryRootURL());
      }

      return result;
    }

    // TODO: This should be updated when fully removing SVNKit object model from code
    private SVNURL getFakeUrl() {
      // do not have constants like SVNURL.EMPTY - use fake url - it should be never used in any real logic
      try {
        return SVNURL.fromFile(myIoFile);
      }
      catch (SVNException e) {
        // should not occur
        throw SvnUtil.createIllegalArgument(e);
      }
    }
  }
}