• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Camelcade / Perl5-IDEA / #525521533

16 May 2025 01:46PM UTC coverage: 82.199% (-0.04%) from 82.237%
#525521533

push

github

hurricup
Bump org.eclipse.jgit:org.eclipse.jgit

Bumps [org.eclipse.jgit:org.eclipse.jgit](https://github.com/eclipse-jgit/jgit) from 7.2.0.202503040940-r to 7.2.1.202505142326-r.
- [Commits](https://github.com/eclipse-jgit/jgit/compare/v7.2.0.202503040940-r...v7.2.1.202505142326-r)

---
updated-dependencies:
- dependency-name: org.eclipse.jgit:org.eclipse.jgit
  dependency-version: 7.2.1.202505142326-r
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

30879 of 37566 relevant lines covered (82.2%)

0.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.94
/mojo/core/src/main/java/com/perl5/lang/mojolicious/model/MojoProjectManager.java
1
/*
2
 * Copyright 2015-2024 Alexandr Evstigneev
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.perl5.lang.mojolicious.model;
18

19
import com.intellij.ide.projectView.ProjectView;
20
import com.intellij.openapi.Disposable;
21
import com.intellij.openapi.application.ApplicationManager;
22
import com.intellij.openapi.application.ReadAction;
23
import com.intellij.openapi.diagnostic.Logger;
24
import com.intellij.openapi.progress.ProcessCanceledException;
25
import com.intellij.openapi.progress.ProgressManager;
26
import com.intellij.openapi.project.DumbService;
27
import com.intellij.openapi.project.Project;
28
import com.intellij.openapi.roots.ProjectFileIndex;
29
import com.intellij.openapi.vfs.VfsUtilCore;
30
import com.intellij.openapi.vfs.VirtualFile;
31
import com.intellij.psi.search.GlobalSearchScope;
32
import com.intellij.psi.util.PsiUtilCore;
33
import com.intellij.util.concurrency.Semaphore;
34
import com.intellij.util.containers.ContainerUtil;
35
import com.intellij.util.messages.MessageBusConnection;
36
import com.intellij.util.ui.update.MergingUpdateQueue;
37
import com.intellij.util.ui.update.Update;
38
import com.perl5.lang.mojolicious.MojoUtil;
39
import com.perl5.lang.mojolicious.idea.modules.MojoTemplateMarkSourceRootAction;
40
import com.perl5.lang.perl.idea.actions.PerlMarkLibrarySourceRootAction;
41
import com.perl5.lang.perl.psi.PerlNamespaceDefinitionElement;
42
import com.perl5.lang.perl.util.PerlPackageUtil;
43
import org.jetbrains.annotations.Contract;
44
import org.jetbrains.annotations.NotNull;
45
import org.jetbrains.annotations.Nullable;
46
import org.jetbrains.annotations.TestOnly;
47

48
import java.util.*;
49
import java.util.concurrent.atomic.AtomicBoolean;
50
import java.util.function.BooleanSupplier;
51

52
import static com.perl5.lang.mojolicious.model.MojoProjectListener.MOJO_PROJECT_TOPIC;
53

54
public class MojoProjectManager implements Disposable {
55
  static final Logger LOG = Logger.getInstance(MojoProjectManager.class);
1✔
56
  private final @NotNull Project myProject;
57
  private final @NotNull MergingUpdateQueue myUpdateQueue;
58
  private final @NotNull AtomicBoolean myUpdatingModel = new AtomicBoolean(false);
1✔
59
  private volatile @NotNull Model myModel = new Model(Collections.emptySet());
1✔
60
  private volatile Semaphore myTestSemaphore;
61

62
  public MojoProjectManager(@NotNull Project project) {
1✔
63
    myProject = project;
1✔
64
    myUpdateQueue = new MergingUpdateQueue("mojo model updating queue", 500, true, null, this, null, false);
1✔
65
    MessageBusConnection connection = myProject.getMessageBus().connect(this);
1✔
66
    connection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
1✔
67
      @Override
68
      public void exitDumbMode() {
69
        LOG.debug("Exiting dumb mode");
1✔
70
        scheduleUpdate();
1✔
71
      }
1✔
72
    });
73

74
    connection.subscribe(MOJO_PROJECT_TOPIC, new MojoProjectListener() {
1✔
75
      @Override
76
      public void applicationCreated(@NotNull MojoApp mojoApp) {
77
        ApplicationManager.getApplication().invokeLater(() -> {
1✔
78
          if (myProject.isDisposed() || !mojoApp.isValid()) {
1✔
79
            return;
1✔
80
          }
81
          VirtualFile appRoot = mojoApp.getRoot();
×
82
          new PerlMarkLibrarySourceRootAction().markRoot(myProject, appRoot.findChild(PerlPackageUtil.DEFAULT_LIB_DIR));
×
83
          new MojoTemplateMarkSourceRootAction().markRoot(myProject, appRoot.findChild(MojoUtil.DEFAULT_TEMPLATES_DIR_NAME));
×
84
        });
×
85
      }
1✔
86

87
      @Override
88
      public void pluginCreated(@NotNull MojoPlugin mojoPlugin) {
89
        ApplicationManager.getApplication().invokeLater(() -> {
1✔
90
          if (myProject.isDisposed() || !mojoPlugin.isValid()) {
1✔
91
            return;
1✔
92
          }
93
          new PerlMarkLibrarySourceRootAction().markRoot(myProject, mojoPlugin.getRoot().findChild(PerlPackageUtil.DEFAULT_LIB_DIR));
×
94
        });
×
95
      }
1✔
96
    });
97
  }
1✔
98

99
  @Override
100
  public void dispose() {
101
    // nothing to dispose
102
  }
1✔
103

104
  /**
105
   * @return true iff mojo is available for the project: perl is enabled and mojo installed
106
   */
107
  public boolean isMojoAvailable() {
108
    return MojoUtil.isMojoAvailable(myProject);
×
109
  }
110

111
  @Contract("null->null")
112
  public @Nullable MojoProject getMojoProject(@Nullable VirtualFile root) {
113
    return myModel.myProjectRoots.get(root);
×
114
  }
115

116
  public @NotNull List<MojoProject> getMojoProjects() {
117
    return Collections.unmodifiableList(new ArrayList<>(myModel.myProjectRoots.values()));
1✔
118
  }
119

120
  /**
121
   * Queues model update
122
   */
123
  void scheduleUpdate() {
124
    LOG.debug("Scheduling update");
1✔
125
    myUpdateQueue.queue(Update.create(this, this::updateModel));
1✔
126
  }
1✔
127

128
  /**
129
   * Attempts to update a model in read action with smart mode. If update fails, re-schedules it
130
   */
131
  private void updateModel() {
132
    LOG.debug("Attempting to update");
1✔
133
    if (!myUpdatingModel.getAndSet(true)) {
1✔
134
      try {
135
        //noinspection deprecation
136
        var isFinished = ReadAction.nonBlocking(() -> {
1✔
137
          if (myProject.isDisposed()) {
1✔
138
            LOG.warn("Project is disposed");
1✔
139
            return true;
1✔
140
          }
141
          if (DumbService.getInstance(myProject).isDumb()) {
1✔
142
            LOG.debug("Dumb mode, rescheduling");
1✔
143
            scheduleUpdate();
1✔
144
            return false;
1✔
145
          }
146
          else {
147
            LOG.debug("Performing model update");
1✔
148
            doUpdateModel();
1✔
149
            return true;
1✔
150
          }
151
        }).executeSynchronously();
1✔
152
        if (myTestSemaphore != null && isFinished) {
1✔
153
          myTestSemaphore.up();
1✔
154
          myTestSemaphore = null;
1✔
155
        }
156
      }
157
      catch (ProcessCanceledException e) {
×
158
        LOG.debug("Update was cancelled, rescheduling");
×
159
        scheduleUpdate();
×
160
      }
161
      finally {
162
        myUpdatingModel.set(false);
1✔
163
      }
1✔
164
    }
165
    else {
166
      LOG.debug("Another update in progress, rescheduling");
×
167
      scheduleUpdate();
×
168
    }
169
  }
1✔
170

171
  /**
172
   * Performs an update. This method invoked with guaranteed conditions: smart mode, read action and project is alive
173
   */
174
  private void doUpdateModel() {
175
    LOG.debug("Updating model");
1✔
176
    Set<MojoProject> newProjects = findAllProjects();
1✔
177
    Set<MojoProject> oldProjects = myModel.getProjects();
1✔
178
    if (oldProjects.equals(newProjects)) {
1✔
179
      LOG.debug("Model was not changed");
1✔
180
      return;
1✔
181
    }
182

183
    LOG.debug("Current projects: ", newProjects);
1✔
184
    LOG.debug("Old projects: ", oldProjects);
1✔
185
    MojoProjectListener projectListener = myProject.getMessageBus().syncPublisher(MOJO_PROJECT_TOPIC);
1✔
186
    Collection<MojoProject> removedProjects = ContainerUtil.subtract(oldProjects, newProjects);
1✔
187
    if (!removedProjects.isEmpty()) {
1✔
188
      LOG.debug("Projects removed: ", removedProjects);
1✔
189
    }
190
    removedProjects.forEach(projectListener::projectDeleted);
1✔
191
    myModel = new Model(newProjects);
1✔
192
    Collection<MojoProject> createdProjects = ContainerUtil.subtract(newProjects, oldProjects);
1✔
193
    if (!createdProjects.isEmpty()) {
1✔
194
      LOG.debug("Projects created: ", createdProjects);
1✔
195
    }
196
    createdProjects.forEach(projectListener::projectCreated);
1✔
197
    LOG.debug("Model updated");
1✔
198
    ApplicationManager.getApplication().invokeLater(() -> {
1✔
199
      if (!myProject.isDisposed() && !ApplicationManager.getApplication().isUnitTestMode()) {
1✔
200
        ProjectView.getInstance(myProject).refresh();
×
201
      }
202
    });
1✔
203
  }
1✔
204

205
  /**
206
   * @return a set of mojo entities in project
207
   */
208
  private @NotNull Set<MojoProject> findAllProjects() {
209
    if (!MojoUtil.isMojoAvailable(myProject)) {
1✔
210
      LOG.debug("Mojo is not available in project");
1✔
211
      return Collections.emptySet();
1✔
212
    }
213
    HashSet<MojoProject> result = new HashSet<>();
1✔
214
    List<PerlNamespaceDefinitionElement> applicationClasses = PerlPackageUtil.getChildNamespaces(
1✔
215
      myProject, MojoUtil.MOJO_PACKAGE_NAME, GlobalSearchScope.projectScope(myProject));
1✔
216
    for (PerlNamespaceDefinitionElement namespace : applicationClasses) {
1✔
217
      LOG.debug("Got application class: " + namespace);
1✔
218
      ProgressManager.checkCanceled();
1✔
219
      VirtualFile root = findLibContainer(namespace);
1✔
220
      if (root != null) {
1✔
221
        LOG.debug("App root: " + root);
1✔
222
        result.add(new MojoApp(root));
1✔
223
      }
224
      else {
225
        LOG.debug("No app root");
×
226
      }
227
    }
1✔
228

229
    List<PerlNamespaceDefinitionElement> pluginClasses = PerlPackageUtil.getChildNamespaces(
1✔
230
      myProject, MojoUtil.MOJO_PLUGIN_PACKAGE_NAME, GlobalSearchScope.projectScope(myProject));
1✔
231
    for (PerlNamespaceDefinitionElement namespace : pluginClasses) {
1✔
232
      LOG.debug("Got plugin class: " + namespace);
1✔
233
      ProgressManager.checkCanceled();
1✔
234
      VirtualFile root = findLibContainer(namespace);
1✔
235
      if (root != null) {
1✔
236
        LOG.debug("Plugin root: " + root);
1✔
237
        result.add(new MojoPlugin(root));
1✔
238
      }
239
      else {
240
        LOG.debug("No plugin root");
×
241
      }
242
    }
1✔
243
    return result;
1✔
244
  }
245

246
  /**
247
   * @return a directory in project, containing a {@code lib} directory, containing current class or null
248
   */
249
  private @Nullable VirtualFile findLibContainer(@NotNull PerlNamespaceDefinitionElement namespaceDefinition) {
250
    VirtualFile namespaceFile = PsiUtilCore.getVirtualFile(namespaceDefinition);
1✔
251
    if (namespaceFile == null) {
1✔
252
      LOG.debug("Namespace without a virtual file");
×
253
      return null;
×
254
    }
255
    VirtualFile libDirectory = VfsUtilCore.findContainingDirectory(namespaceFile, PerlPackageUtil.DEFAULT_LIB_DIR);
1✔
256
    if (libDirectory == null) {
1✔
257
      LOG.debug("No containing lib dir found");
×
258
      return null;
×
259
    }
260
    VirtualFile root = libDirectory.getParent();
1✔
261
    if (root != null && ProjectFileIndex.getInstance(myProject).isInContent(root)) {
1✔
262
      return root;
1✔
263
    }
264
    LOG.debug("No root or not in content: " + root);
×
265
    return null;
×
266
  }
267

268
  public static @NotNull MojoProjectManager getInstance(@NotNull Project project) {
269
    return project.getService(MojoProjectManager.class);
1✔
270
  }
271

272
  private static class Model {
273
    private final @NotNull Map<VirtualFile, MojoProject> myProjectRoots;
274

275
    private Model(@NotNull Set<? extends MojoProject> mojoProjects) {
1✔
276
      if (mojoProjects.isEmpty()) {
1✔
277
        myProjectRoots = Collections.emptyMap();
1✔
278
        return;
1✔
279
      }
280
      Map<VirtualFile, MojoProject> map = new HashMap<>();
1✔
281
      mojoProjects.forEach(it -> map.put(it.getRoot(), it));
1✔
282
      myProjectRoots = Collections.unmodifiableMap(map);
1✔
283
    }
1✔
284

285
    private Set<MojoProject> getProjects() {
286
      return myProjectRoots.isEmpty() ? Collections.emptySet() : new HashSet<>(myProjectRoots.values());
1✔
287
    }
288
  }
289

290
  @TestOnly
291
  public BooleanSupplier updateInTestMode() {
292
    var semaphore = new Semaphore(1);
1✔
293
    myTestSemaphore = semaphore;
1✔
294
    updateModel();
1✔
295
    return semaphore::isUp;
1✔
296
  }
297
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc