2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.vcs.changes;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.Comparing;
21 import com.intellij.openapi.util.Pair;
22 import com.intellij.openapi.vcs.AbstractVcs;
23 import com.intellij.openapi.vcs.FilePath;
24 import com.intellij.openapi.vcs.FileStatus;
25 import com.intellij.openapi.vcs.VcsKey;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.util.IncorrectOperationException;
28 import com.intellij.util.containers.MultiMap;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
35 /** should work under _external_ lock
36 * just logic here: do modifications to group of change lists
38 public class ChangeListWorker implements ChangeListsWriteOperations {
39 private final static Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.ChangeListWorker");
41 private final Project myProject;
42 private final Map<String, LocalChangeList> myMap;
43 // in fact, a kind of local change
44 private final DeletedFilesHolder myLocallyDeleted;
45 private final SwitchedFileHolder mySwitchedHolder;
46 private LocalChangeList myDefault;
48 private ChangeListsIndexes myIdx;
49 private final ChangesDelta myDelta;
51 public ChangeListWorker(final Project project, final PlusMinus<Pair<String, AbstractVcs>> deltaListener) {
53 myMap = new HashMap<String, LocalChangeList>();
54 myIdx = new ChangeListsIndexes();
55 myLocallyDeleted = new DeletedFilesHolder();
56 mySwitchedHolder = new SwitchedFileHolder(project, FileHolder.HolderType.SWITCHED);
58 myDelta = new ChangesDelta(project, deltaListener);
61 private ChangeListWorker(final ChangeListWorker worker) {
62 myProject = worker.myProject;
63 myMap = new HashMap<String, LocalChangeList>();
64 myIdx = new ChangeListsIndexes(worker.myIdx);
65 myLocallyDeleted = worker.myLocallyDeleted.copy();
66 mySwitchedHolder = (SwitchedFileHolder) worker.mySwitchedHolder.copy();
67 myDelta = worker.myDelta;
69 LocalChangeList defaultList = null;
70 for (LocalChangeList changeList : worker.myMap.values()) {
71 final LocalChangeList copy = changeList.copy();
73 final String changeListName = copy.getName();
74 myMap.put(changeListName, copy);
75 if (copy.isDefault()) {
79 if (defaultList == null) {
80 LOG.info("default list not found when copy");
81 defaultList = myMap.get(worker.getDefaultListName());
84 if (defaultList == null) {
85 LOG.info("default list not found when copy in original object too");
86 if (! myMap.isEmpty()) {
87 defaultList = myMap.values().iterator().next();
89 // can be when there's no vcs configured
90 ///LOG.error("no changelists at all");
93 myDefault = defaultList;
96 public void takeData(@NotNull final ChangeListWorker worker) {
98 myMap.putAll(worker.myMap);
99 myDefault = worker.myDefault;
101 myDelta.step(myIdx, worker.myIdx);
102 myIdx = new ChangeListsIndexes(worker.myIdx);
104 myLocallyDeleted.takeFrom(worker.myLocallyDeleted);
105 mySwitchedHolder.takeFrom(worker.mySwitchedHolder);
108 public ChangeListWorker copy() {
109 return new ChangeListWorker(this);
112 public boolean findListByName(@NotNull final String name) {
113 return myMap.containsKey(name);
117 public LocalChangeList getCopyByName(final String name) {
118 return myMap.get(name);
122 public LocalChangeList getChangeList(String id) {
123 for (LocalChangeList changeList : myMap.values()) {
124 if (changeList.getId().equals(id)) {
125 return changeList.copy();
132 * @return if list with name exists, return previous default list name or null of there wasn't previous
135 public String setDefault(final String name) {
136 final LocalChangeList newDefault = myMap.get(name);
137 if (newDefault == null) {
140 String previousName = null;
141 if (myDefault != null) {
142 ((LocalChangeListImpl) myDefault).setDefault(false);
143 correctChangeListEditHandler(myDefault);
144 previousName = myDefault.getName();
147 ((LocalChangeListImpl) newDefault).setDefault(true);
148 myDefault = newDefault;
153 public boolean setReadOnly(final String name, final boolean value) {
154 final LocalChangeList list = myMap.get(name);
156 list.setReadOnly(value);
161 public LocalChangeList addChangeList(@NotNull final String name, @Nullable final String description) {
162 return addChangeList(null, name, description, false);
165 LocalChangeList addChangeList(String id, @NotNull final String name, @Nullable final String description, final boolean inUpdate) {
166 final boolean contains = myMap.containsKey(name);
167 LOG.assertTrue(! contains, "Attempt to create duplicate changelist " + name);
168 final LocalChangeListImpl newList = (LocalChangeListImpl) LocalChangeList.createEmptyChangeList(myProject, name);
170 if (description != null) {
171 newList.setCommentImpl(description);
176 myMap.put(name, newList);
178 // scope is not important: nothing had been added jet, nothing to move to "old state" members
179 newList.startProcessingChanges(myProject, null);
181 return newList.copy();
184 public boolean addChangeToList(@NotNull final String name, final Change change, final VcsKey vcsKey) {
185 final LocalChangeList changeList = myMap.get(name);
186 if (changeList != null) {
187 ((LocalChangeListImpl) changeList).addChange(change);
188 myIdx.changeAdded(change, vcsKey);
189 correctChangeListEditHandler(changeList);
191 return changeList != null;
194 public void addChangeToCorrespondingList(final Change change, final VcsKey vcsKey) {
195 assert myDefault != null;
196 for (LocalChangeList list : myMap.values()) {
197 if (list.isDefault()) continue;
198 if (((LocalChangeListImpl) list).processChange(change)) {
199 myIdx.changeAdded(change, vcsKey);
200 correctChangeListEditHandler(list);
204 ((LocalChangeListImpl) myDefault).processChange(change);
205 myIdx.changeAdded(change, vcsKey);
206 correctChangeListEditHandler(myDefault);
209 public boolean removeChangeList(@NotNull String name) {
210 final LocalChangeList list = myMap.get(name);
214 if (list.isDefault()) {
215 throw new RuntimeException(new IncorrectOperationException("Cannot remove default changelist"));
217 final String listName = list.getName();
219 for (Change change : list.getChanges()) {
220 ((LocalChangeListImpl) myDefault).addChange(change);
223 myMap.remove(listName);
228 for (LocalChangeList list : myMap.values()) {
229 correctChangeListEditHandler(list);
233 // since currently it is only for P4, "composite" edit handler is not created - it does not make sence
234 private void correctChangeListEditHandler(final LocalChangeList list) {
238 public MultiMap<LocalChangeList, Change> moveChangesTo(final String name, final Change[] changes) {
239 final LocalChangeListImpl changeList = (LocalChangeListImpl) myMap.get(name);
240 if (changeList != null) {
241 final MultiMap<LocalChangeList, Change> result = new MultiMap<LocalChangeList, Change>();
242 for (LocalChangeList list : myMap.values()) {
243 if (list.equals(changeList)) continue;
244 for (Change change : changes) {
245 final Change removedChange = ((LocalChangeListImpl)list).removeChange(change);
246 if (removedChange != null) {
247 correctChangeListEditHandler(list);
249 changeList.addChange(removedChange);
250 result.putValue(list, removedChange);
254 correctChangeListEditHandler(changeList);
260 public boolean editName(@NotNull final String fromName, @NotNull final String toName) {
261 if (fromName.equals(toName)) return false;
262 final LocalChangeList list = myMap.get(fromName);
263 final boolean canEdit = list != null && (!list.isReadOnly());
265 final LocalChangeListImpl listImpl = (LocalChangeListImpl) list;
266 listImpl.setNameImpl(toName);
267 myMap.remove(fromName);
268 myMap.put(toName, list);
269 final ChangeListEditHandler editHandler = listImpl.getEditHandler();
270 if (editHandler != null) {
271 listImpl.setCommentImpl(editHandler.changeCommentOnChangeName(toName, listImpl.getComment()));
278 public String editComment(@NotNull final String fromName, final String newComment) {
279 final LocalChangeList list = myMap.get(fromName);
281 final String oldComment = list.getComment();
282 if (! Comparing.equal(oldComment, newComment)) {
283 final LocalChangeListImpl listImpl = (LocalChangeListImpl) list;
284 listImpl.setCommentImpl(newComment);
285 final ChangeListEditHandler editHandler = listImpl.getEditHandler();
286 if (editHandler != null) {
287 listImpl.setNameImpl(editHandler.changeNameOnChangeComment(listImpl.getName(), listImpl.getComment()));
288 if (! fromName.equals(listImpl.getName())) {
289 myMap.remove(fromName);
290 myMap.put(listImpl.getName(), list);
299 public boolean isEmpty() {
300 return myMap.isEmpty();
304 public LocalChangeList getDefaultListCopy() {
305 return myDefault == null ? null : myDefault.copy();
308 public boolean isDefaultList(LocalChangeList list) {
309 return myDefault != null && list.getId().equals(myDefault.getId());
312 public Project getProject() {
316 // called NOT under ChangeListManagerImpl lock
317 public void notifyStartProcessingChanges(final VcsAppendableDirtyScope scope) {
318 final Collection<Change> oldChanges = new ArrayList<Change>();
319 for (LocalChangeList list : myMap.values()) {
320 final Collection<Change> affectedChanges = ((LocalChangeListImpl)list).startProcessingChanges(myProject, scope);
321 if (! affectedChanges.isEmpty()) {
322 oldChanges.addAll(affectedChanges);
325 for (Change change : oldChanges) {
326 myIdx.changeRemoved(change);
328 // scope should be modified for correct moves tracking
329 correctScopeForMoves(scope, oldChanges);
331 myLocallyDeleted.cleanScope(scope);
332 mySwitchedHolder.cleanScope(scope);
335 private void correctScopeForMoves(final VcsAppendableDirtyScope scope, final Collection<Change> changes) {
336 if (scope == null) return;
337 for (Change change : changes) {
338 if (change.isMoved() || change.isRenamed()) {
339 scope.addDirtyFile(change.getBeforeRevision().getFile());
340 scope.addDirtyFile(change.getAfterRevision().getFile());
345 public void notifyDoneProcessingChanges(final ChangeListListener dispatcher) {
346 List<ChangeList> changedLists = new ArrayList<ChangeList>();
347 final Map<LocalChangeListImpl, List<Change>> removedChanges = new HashMap<LocalChangeListImpl, List<Change>>();
348 for (LocalChangeList list : myMap.values()) {
349 final List<Change> removed = new ArrayList<Change>();
350 final LocalChangeListImpl listImpl = (LocalChangeListImpl)list;
351 if (listImpl.doneProcessingChanges(removed)) {
352 changedLists.add(list);
354 if (! removed.isEmpty()) {
355 removedChanges.put(listImpl, removed);
358 for (Map.Entry<LocalChangeListImpl, List<Change>> entry : removedChanges.entrySet()) {
359 dispatcher.changesRemoved(entry.getValue(), entry.getKey());
361 for(ChangeList changeList: changedLists) {
362 dispatcher.changeListChanged(changeList);
364 mySwitchedHolder.calculateChildren();
367 public List<LocalChangeList> getListsCopy() {
368 final List<LocalChangeList> result = new ArrayList<LocalChangeList>();
369 for (LocalChangeList list : myMap.values()) {
370 result.add(list.copy());
375 public String getDefaultListName() {
376 return myDefault == null ? null : myDefault.getName();
379 public List<File> getAffectedPaths() {
380 return myIdx.getAffectedPaths();
384 public List<VirtualFile> getAffectedFiles() {
385 final List<VirtualFile> result = new ArrayList<VirtualFile>();
386 for (LocalChangeList list : myMap.values()) {
387 for (Change change : list.getChanges()) {
388 final ContentRevision before = change.getBeforeRevision();
389 final ContentRevision after = change.getAfterRevision();
390 if (before != null) {
391 final VirtualFile file = before.getFile().getVirtualFile();
397 final VirtualFile file = after.getFile().getVirtualFile();
407 public LocalChangeList getListCopy(@NotNull final VirtualFile file) {
408 for (LocalChangeList list : myMap.values()) {
409 for (Change change : list.getChanges()) {
410 if (change.getAfterRevision() != null &&
411 Comparing.equal(change.getAfterRevision().getFile().getVirtualFile(), file)) {
414 if (change.getBeforeRevision() != null &&
415 Comparing.equal(change.getBeforeRevision().getFile().getVirtualFile(), file)) {
424 public Change getChangeForPath(final FilePath file) {
425 for (LocalChangeList list : myMap.values()) {
426 for (Change change : list.getChanges()) {
427 final ContentRevision afterRevision = change.getAfterRevision();
428 if (afterRevision != null && afterRevision.getFile().equals(file)) {
431 final ContentRevision beforeRevision = change.getBeforeRevision();
432 if (beforeRevision != null && beforeRevision.getFile().equals(file)) {
440 public FileStatus getStatus(final VirtualFile file) {
441 return myIdx.getStatus(file);
444 public DeletedFilesHolder getLocallyDeleted() {
445 return myLocallyDeleted.copy();
448 public SwitchedFileHolder getSwitchedHolder() {
449 return mySwitchedHolder.copy();
452 public void addSwitched(final VirtualFile file, @NotNull String branchName, final boolean recursive) {
453 mySwitchedHolder.addFile(file, branchName, recursive);
456 public void removeSwitched(final VirtualFile file) {
457 mySwitchedHolder.removeFile(file);
460 public String getBranchForFile(final VirtualFile file) {
461 return mySwitchedHolder.getBranchForFile(file);
464 public boolean isSwitched(final VirtualFile file) {
465 return mySwitchedHolder.containsFile(file);
468 public void addLocallyDeleted(final LocallyDeletedChange change) {
469 myLocallyDeleted.addFile(change);
472 public boolean isContainedInLocallyDeleted(final FilePath filePath) {
473 return myLocallyDeleted.isContainedInLocallyDeleted(filePath);
476 private abstract class ExternalVsInternalChangesIntersection {
477 protected final Collection<Change> myInChanges;
478 protected final Map<Pair<String, String>, LocalChangeList> myInternalMap;
479 protected final LocalChangeList myDefaultCopy;
480 protected final Map<String, LocalChangeList> myIncludedListsCopies;
482 protected ExternalVsInternalChangesIntersection(final Collection<Change> inChanges) {
483 myInChanges = inChanges;
484 myInternalMap = new HashMap<Pair<String, String>, LocalChangeList>();
485 myDefaultCopy = myDefault.copy();
486 myIncludedListsCopies = new HashMap<String, LocalChangeList>();
489 private Pair<String, String> keyForChange(final Change change) {
490 final FilePath beforePath = ChangesUtil.getBeforePath(change);
491 final String beforeKey = beforePath == null ? null : beforePath.getIOFile().getAbsolutePath();
492 final FilePath afterPath = ChangesUtil.getAfterPath(change);
493 final String afterKey = afterPath == null ? null : afterPath.getIOFile().getAbsolutePath();
494 return new Pair<String, String>(beforeKey, afterKey);
497 private void preparation() {
498 for (LocalChangeList list : myMap.values()) {
499 final Collection<Change> managerChanges = list.getChanges();
500 final LocalChangeList copy = list.copy();
501 for (Change change : managerChanges) {
502 myInternalMap.put(keyForChange(change), copy);
507 protected abstract void processInChange(final Pair<String, String> key, final Change change);
512 for (Change change : myInChanges) {
513 final Pair<String, String> key = keyForChange(change);
514 processInChange(key, change);
518 public Map<String, LocalChangeList> getIncludedListsCopies() {
519 return myIncludedListsCopies;
523 private class GatherChangesVsListsInfo extends ExternalVsInternalChangesIntersection {
524 private final Map<String, List<Change>> myListToChangesMap;
526 private GatherChangesVsListsInfo(final Collection<Change> inChanges) {
528 myListToChangesMap = new HashMap<String, List<Change>>();
531 protected void processInChange(Pair<String, String> key, Change change) {
532 LocalChangeList tmpList = myInternalMap.get(key);
533 if (tmpList == null) {
534 tmpList = myDefaultCopy;
536 final String tmpName = tmpList.getName();
537 List<Change> list = myListToChangesMap.get(tmpName);
539 list = new ArrayList<Change>();
540 myListToChangesMap.put(tmpName, list);
541 myIncludedListsCopies.put(tmpName, tmpList);
546 public Map<String, List<Change>> getListToChangesMap() {
547 return myListToChangesMap;
551 private class GatherListsFilterValidChanges extends ExternalVsInternalChangesIntersection {
552 private final List<Change> myValidChanges;
554 private GatherListsFilterValidChanges(final Collection<Change> inChanges) {
556 myValidChanges = new ArrayList<Change>();
559 protected void processInChange(Pair<String, String> key, Change change) {
560 final LocalChangeList list = myInternalMap.get(key);
562 myIncludedListsCopies.put(list.getName(), list);
563 myValidChanges.add(change);
567 public List<Change> getValidChanges() {
568 return myValidChanges;
573 public Map<String, List<Change>> listsForChanges(final Collection<Change> changes, final Map<String, LocalChangeList> lists) {
574 final GatherChangesVsListsInfo info = new GatherChangesVsListsInfo(changes);
576 lists.putAll(info.getIncludedListsCopies());
577 return info.getListToChangesMap();
581 public Collection<LocalChangeList> getInvolvedListsFilterChanges(final Collection<Change> changes, final List<Change> validChanges) {
582 final GatherListsFilterValidChanges worker = new GatherListsFilterValidChanges(changes);
584 validChanges.addAll(worker.getValidChanges());
585 return worker.getIncludedListsCopies().values();
589 public LocalChangeList listForChange(final Change change) {
590 for (LocalChangeList list : myMap.values()) {
591 if (list.getChanges().contains(change)) return list.copy();
597 public String listNameIfOnlyOne(final @Nullable Change[] changes) {
598 if (changes == null || changes.length == 0) {
602 final Change first = changes[0];
604 for (LocalChangeList list : myMap.values()) {
605 final Collection<Change> listChanges = list.getChanges();
606 if (listChanges.contains(first)) {
607 // must contain all other
608 for (int i = 1; i < changes.length; i++) {
609 final Change change = changes[i];
610 if (! listChanges.contains(change)) {
614 return list.getName();
621 public Collection<Change> getChangesIn(final FilePath dirPath) {
622 List<Change> changes = new ArrayList<Change>();
623 for (ChangeList list : myMap.values()) {
624 for (Change change : list.getChanges()) {
625 final ContentRevision afterRevision = change.getAfterRevision();
626 if (afterRevision != null && afterRevision.getFile().isUnder(dirPath, false)) {
631 final ContentRevision beforeRevision = change.getBeforeRevision();
632 if (beforeRevision != null && beforeRevision.getFile().isUnder(dirPath, false)) {
640 ChangeListManagerGate createSelfGate() {
641 return new MyGate(this);
644 private static class MyGate implements ChangeListManagerGate {
645 private final ChangeListWorker myWorker;
647 private MyGate(final ChangeListWorker worker) {
651 public List<LocalChangeList> getListsCopy() {
652 return myWorker.getListsCopy();
656 public LocalChangeList findChangeList(final String name) {
657 return myWorker.getCopyByName(name);
660 public LocalChangeList addChangeList(final String name, final String comment) {
661 return myWorker.addChangeList(null, name, comment, true);
664 public LocalChangeList findOrCreateList(final String name, final String comment) {
665 LocalChangeList list = myWorker.getCopyByName(name);
667 list = addChangeList(name, comment);
672 public void editComment(final String name, final String comment) {
673 myWorker.editComment(name, comment);
676 public void editName(String oldName, String newName) {
677 myWorker.editName(oldName, newName);
680 // todo usage allowed only when..
681 public void moveChanges(String toList, Collection<Change> changes) {
682 myWorker.moveChangesTo(toList, changes.toArray(new Change[changes.size()]));
685 public void deleteIfEmpty(String name) {
686 final LocalChangeList list = myWorker.getCopyByName(name);
687 if ((list != null) && (list.getChanges().isEmpty()) && (! list.isDefault())) {
688 myWorker.removeChangeList(name);