0001: /*
0002: * ====================================================================
0003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
0004: *
0005: * This software is licensed as described in the file COPYING, which
0006: * you should have received as part of this distribution. The terms
0007: * are also available at http://svnkit.com/license.html
0008: * If newer versions of this license are posted there, you may use a
0009: * newer version instead, at your option.
0010: * ====================================================================
0011: */
0012: package org.tmatesoft.svn.core.internal.wc;
0013:
0014: import java.io.File;
0015: import java.util.ArrayList;
0016: import java.util.Arrays;
0017: import java.util.Collection;
0018: import java.util.Collections;
0019: import java.util.HashMap;
0020: import java.util.HashSet;
0021: import java.util.Iterator;
0022: import java.util.List;
0023: import java.util.Map;
0024: import java.util.StringTokenizer;
0025: import java.util.TreeMap;
0026:
0027: import org.tmatesoft.svn.core.SVNCancelException;
0028: import org.tmatesoft.svn.core.SVNErrorCode;
0029: import org.tmatesoft.svn.core.SVNErrorMessage;
0030: import org.tmatesoft.svn.core.SVNException;
0031: import org.tmatesoft.svn.core.SVNNodeKind;
0032: import org.tmatesoft.svn.core.SVNProperty;
0033: import org.tmatesoft.svn.core.SVNURL;
0034: import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
0035: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
0036: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
0037: import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
0038: import org.tmatesoft.svn.core.internal.wc.admin.SVNVersionedProperties;
0039: import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
0040: import org.tmatesoft.svn.core.io.ISVNEditor;
0041: import org.tmatesoft.svn.core.wc.ISVNCommitParameters;
0042: import org.tmatesoft.svn.core.wc.ISVNEventHandler;
0043: import org.tmatesoft.svn.core.wc.SVNCommitItem;
0044: import org.tmatesoft.svn.core.wc.SVNEvent;
0045: import org.tmatesoft.svn.core.wc.SVNRevision;
0046: import org.tmatesoft.svn.core.wc.SVNStatus;
0047: import org.tmatesoft.svn.core.wc.SVNStatusClient;
0048: import org.tmatesoft.svn.core.wc.SVNStatusType;
0049: import org.tmatesoft.svn.core.wc.SVNWCUtil;
0050:
0051: /**
0052: * @version 1.1.1
0053: * @author TMate Software Ltd.
0054: */
0055: public class SVNCommitUtil {
0056:
0057: public static void driveCommitEditor(ISVNCommitPathHandler handler,
0058: Collection paths, ISVNEditor editor, long revision)
0059: throws SVNException {
0060: if (paths == null || paths.isEmpty() || handler == null
0061: || editor == null) {
0062: return;
0063: }
0064: String[] pathsArray = (String[]) paths.toArray(new String[paths
0065: .size()]);
0066: Arrays.sort(pathsArray, SVNPathUtil.PATH_COMPARATOR);
0067: int index = 0;
0068: String lastPath = null;
0069: if ("".equals(pathsArray[index])) {
0070: handler.handleCommitPath("", editor);
0071: lastPath = pathsArray[index];
0072: index++;
0073: } else {
0074: editor.openRoot(revision);
0075: }
0076: for (; index < pathsArray.length; index++) {
0077: String commitPath = pathsArray[index];
0078: String commonAncestor = lastPath == null
0079: || "".equals(lastPath) ? "" : SVNPathUtil
0080: .getCommonPathAncestor(commitPath, lastPath);
0081: if (lastPath != null) {
0082: while (!lastPath.equals(commonAncestor)) {
0083: editor.closeDir();
0084: if (lastPath.lastIndexOf('/') >= 0) {
0085: lastPath = lastPath.substring(0, lastPath
0086: .lastIndexOf('/'));
0087: } else {
0088: lastPath = "";
0089: }
0090: }
0091: }
0092: String relativeCommitPath = commitPath
0093: .substring(commonAncestor.length());
0094: if (relativeCommitPath.startsWith("/")) {
0095: relativeCommitPath = relativeCommitPath.substring(1);
0096: }
0097:
0098: for (StringTokenizer tokens = new StringTokenizer(
0099: relativeCommitPath, "/"); tokens.hasMoreTokens();) {
0100: String token = tokens.nextToken();
0101: commonAncestor = "".equals(commonAncestor) ? token
0102: : commonAncestor + "/" + token;
0103: if (!commonAncestor.equals(commitPath)) {
0104: editor.openDir(commonAncestor, revision);
0105: } else {
0106: break;
0107: }
0108: }
0109: boolean closeDir = handler.handleCommitPath(commitPath,
0110: editor);
0111: if (closeDir) {
0112: lastPath = commitPath;
0113: } else {
0114: if (index + 1 < pathsArray.length) {
0115: lastPath = SVNPathUtil.removeTail(commitPath);
0116: } else {
0117: lastPath = commitPath;
0118: }
0119: }
0120: }
0121: while (lastPath != null && !"".equals(lastPath)) {
0122: editor.closeDir();
0123: lastPath = lastPath.lastIndexOf('/') >= 0 ? lastPath
0124: .substring(0, lastPath.lastIndexOf('/')) : "";
0125: }
0126: }
0127:
0128: public static SVNWCAccess createCommitWCAccess(File[] paths,
0129: boolean recursive, boolean force, Collection relativePaths,
0130: final SVNStatusClient statusClient) throws SVNException {
0131: String[] validatedPaths = new String[paths.length];
0132: for (int i = 0; i < paths.length; i++) {
0133: statusClient.checkCancelled();
0134: File file = paths[i];
0135: validatedPaths[i] = SVNPathUtil.validateFilePath(file
0136: .getAbsolutePath());
0137: }
0138: String rootPath = SVNPathUtil.condencePaths(validatedPaths,
0139: relativePaths, recursive);
0140: if (rootPath == null) {
0141: return null;
0142: }
0143: File baseDir = new File(rootPath).getAbsoluteFile();
0144: rootPath = baseDir.getAbsolutePath().replace(
0145: File.separatorChar, '/');
0146: Collection dirsToLock = new HashSet(); // relative paths to lock.
0147: Collection dirsToLockRecursively = new HashSet();
0148: boolean lockAll = false;
0149: if (relativePaths.isEmpty()) {
0150: statusClient.checkCancelled();
0151: String target = getTargetName(baseDir);
0152: if (!"".equals(target)) {
0153: // we will have to lock target as well, not only base dir.
0154: SVNFileType targetType = SVNFileType.getType(new File(
0155: rootPath));
0156: relativePaths.add(target);
0157: if (targetType == SVNFileType.DIRECTORY) {
0158: // lock recursively if forced and copied...
0159: if (recursive
0160: || (force && isRecursiveCommitForced(baseDir))) {
0161: // dir is copied, include children
0162: dirsToLockRecursively.add(target);
0163: } else {
0164: dirsToLock.add(target);
0165: }
0166: }
0167: baseDir = baseDir.getParentFile();
0168: } else {
0169: lockAll = true;
0170: }
0171: } else {
0172: baseDir = adjustRelativePaths(baseDir, relativePaths);
0173: // there are multiple paths.
0174: for (Iterator targets = relativePaths.iterator(); targets
0175: .hasNext();) {
0176: statusClient.checkCancelled();
0177: String targetPath = (String) targets.next();
0178: File targetFile = new File(baseDir, targetPath);
0179: SVNFileType targetKind = SVNFileType
0180: .getType(targetFile);
0181: if (targetKind == SVNFileType.DIRECTORY) {
0182: if (recursive
0183: || (force && isRecursiveCommitForced(targetFile))) {
0184: dirsToLockRecursively.add(targetPath);
0185: } else if (!targetFile.equals(baseDir)) {
0186: dirsToLock.add(targetPath);
0187: }
0188: }
0189: if (!targetFile.equals(baseDir)) {
0190: targetFile = targetFile.getParentFile();
0191: targetPath = SVNPathUtil.removeTail(targetPath);
0192: while (targetFile != null
0193: && !targetFile.equals(baseDir)
0194: && !dirsToLock.contains(targetPath)) {
0195: dirsToLock.add(targetPath);
0196: targetPath = SVNPathUtil.removeTail(targetPath);
0197: targetFile = targetFile.getParentFile();
0198: }
0199: }
0200: }
0201: }
0202: SVNWCAccess baseAccess = SVNWCAccess
0203: .newInstance(new ISVNEventHandler() {
0204: public void handleEvent(SVNEvent event,
0205: double progress) throws SVNException {
0206: }
0207:
0208: public void checkCancelled()
0209: throws SVNCancelException {
0210: statusClient.checkCancelled();
0211: }
0212: });
0213: baseAccess.setOptions(statusClient.getOptions());
0214: try {
0215: baseAccess.open(baseDir, true,
0216: lockAll ? SVNWCAccess.INFINITE_DEPTH : 0);
0217: statusClient.checkCancelled();
0218: dirsToLock = new ArrayList(dirsToLock);
0219: dirsToLockRecursively = new ArrayList(dirsToLockRecursively);
0220: Collections.sort((List) dirsToLock,
0221: SVNPathUtil.PATH_COMPARATOR);
0222: Collections.sort((List) dirsToLockRecursively,
0223: SVNPathUtil.PATH_COMPARATOR);
0224: if (!lockAll) {
0225: List uniqueDirsToLockRecursively = new ArrayList();
0226: uniqueDirsToLockRecursively
0227: .addAll(dirsToLockRecursively);
0228: for (Iterator ps = uniqueDirsToLockRecursively
0229: .iterator(); ps.hasNext();) {
0230: String pathToLock = (String) ps.next();
0231: for (Iterator existing = dirsToLockRecursively
0232: .iterator(); existing.hasNext();) {
0233: String existingPath = (String) existing.next();
0234: if (pathToLock.startsWith(existingPath + "/")) {
0235: // child of other path
0236: ps.remove();
0237: break;
0238: }
0239: }
0240:
0241: }
0242: Collections.sort(uniqueDirsToLockRecursively,
0243: SVNPathUtil.PATH_COMPARATOR);
0244: dirsToLockRecursively = uniqueDirsToLockRecursively;
0245: removeRedundantPaths(dirsToLockRecursively, dirsToLock);
0246: for (Iterator nonRecusivePaths = dirsToLock.iterator(); nonRecusivePaths
0247: .hasNext();) {
0248: statusClient.checkCancelled();
0249: String path = (String) nonRecusivePaths.next();
0250: File pathFile = new File(baseDir, path);
0251: baseAccess.open(pathFile, true, 0);
0252: }
0253: for (Iterator recusivePaths = dirsToLockRecursively
0254: .iterator(); recusivePaths.hasNext();) {
0255: statusClient.checkCancelled();
0256: String path = (String) recusivePaths.next();
0257: File pathFile = new File(baseDir, path);
0258: baseAccess.open(pathFile, true,
0259: SVNWCAccess.INFINITE_DEPTH);
0260: }
0261: }
0262: for (int i = 0; i < paths.length; i++) {
0263: statusClient.checkCancelled();
0264: File path = new File(SVNPathUtil
0265: .validateFilePath(paths[i].getAbsolutePath()));
0266: path = path.getAbsoluteFile();
0267: try {
0268: baseAccess.probeRetrieve(path);
0269: } catch (SVNException e) {
0270: SVNErrorMessage err = e
0271: .getErrorMessage()
0272: .wrap(
0273: "Are all the targets part of the same working copy?");
0274: SVNErrorManager.error(err);
0275: }
0276: if (!recursive && !force) {
0277: if (SVNFileType.getType(path) == SVNFileType.DIRECTORY) {
0278: // TODO replace with direct SVNStatusEditor call.
0279: SVNStatus status = statusClient.doStatus(path,
0280: false);
0281: if (status != null
0282: && (status.getContentsStatus() == SVNStatusType.STATUS_DELETED || status
0283: .getContentsStatus() == SVNStatusType.STATUS_REPLACED)) {
0284: SVNErrorMessage err = SVNErrorMessage
0285: .create(
0286: SVNErrorCode.UNSUPPORTED_FEATURE,
0287: "Cannot non-recursively commit a directory deletion");
0288: SVNErrorManager.error(err);
0289: }
0290: }
0291: }
0292: }
0293: // if commit is non-recursive and forced, remove those child dirs
0294: // that were not explicitly added but are explicitly copied. ufff.
0295: if (!recursive && force) {
0296: SVNAdminArea[] lockedDirs = baseAccess.getAdminAreas();
0297: for (int i = 0; i < lockedDirs.length; i++) {
0298: statusClient.checkCancelled();
0299: SVNAdminArea dir = lockedDirs[i];
0300: if (dir == null) {
0301: // could be null for missing, but known dir.
0302: continue;
0303: }
0304: SVNEntry rootEntry = baseAccess.getEntry(dir
0305: .getRoot(), true);
0306: if (rootEntry.getCopyFromURL() != null) {
0307: File dirRoot = dir.getRoot();
0308: boolean keep = false;
0309: for (int j = 0; j < paths.length; j++) {
0310: if (dirRoot.equals(paths[j])) {
0311: keep = true;
0312: break;
0313: }
0314: }
0315: if (!keep) {
0316: baseAccess.closeAdminArea(dir.getRoot());
0317: }
0318: }
0319: }
0320: }
0321: } catch (SVNException e) {
0322: baseAccess.close();
0323: throw e;
0324: }
0325: baseAccess.setAnchor(baseDir);
0326: return baseAccess;
0327: }
0328:
0329: public static SVNWCAccess[] createCommitWCAccess2(File[] paths,
0330: boolean recursive, boolean force, Map relativePathsMap,
0331: SVNStatusClient statusClient) throws SVNException {
0332: Map rootsMap = new HashMap(); // wc root file -> paths to be committed (paths).
0333: Map localRootsCache = new HashMap();
0334: for (int i = 0; i < paths.length; i++) {
0335: statusClient.checkCancelled();
0336: File path = paths[i];
0337: File rootPath = path;
0338: if (rootPath.isFile()) {
0339: rootPath = rootPath.getParentFile();
0340: }
0341: File wcRoot = localRootsCache.containsKey(rootPath) ? (File) localRootsCache
0342: .get(rootPath)
0343: : SVNWCUtil.getWorkingCopyRoot(rootPath, true);
0344: localRootsCache.put(path, wcRoot);
0345: if (!rootsMap.containsKey(wcRoot)) {
0346: rootsMap.put(wcRoot, new ArrayList());
0347: }
0348: Collection wcPaths = (Collection) rootsMap.get(wcRoot);
0349: wcPaths.add(path);
0350: }
0351: Collection result = new ArrayList();
0352: try {
0353: for (Iterator roots = rootsMap.keySet().iterator(); roots
0354: .hasNext();) {
0355: statusClient.checkCancelled();
0356: File root = (File) roots.next();
0357: Collection filesList = (Collection) rootsMap.get(root);
0358: File[] filesArray = (File[]) filesList
0359: .toArray(new File[filesList.size()]);
0360: Collection relativePaths = new ArrayList();
0361: SVNWCAccess wcAccess = createCommitWCAccess(filesArray,
0362: recursive, force, relativePaths, statusClient);
0363: relativePathsMap.put(wcAccess, relativePaths);
0364: result.add(wcAccess);
0365: }
0366: } catch (SVNException e) {
0367: for (Iterator wcAccesses = result.iterator(); wcAccesses
0368: .hasNext();) {
0369: SVNWCAccess wcAccess = (SVNWCAccess) wcAccesses.next();
0370: wcAccess.close();
0371: }
0372: throw e;
0373: }
0374: return (SVNWCAccess[]) result.toArray(new SVNWCAccess[result
0375: .size()]);
0376: }
0377:
0378: public static SVNCommitItem[] harvestCommitables(
0379: SVNWCAccess baseAccess, Collection paths, Map lockTokens,
0380: boolean justLocked, boolean recursive, boolean force,
0381: ISVNCommitParameters params) throws SVNException {
0382: Map commitables = new TreeMap();
0383: Collection danglers = new HashSet();
0384: Iterator targets = paths.iterator();
0385:
0386: boolean isRecursionForced = false;
0387:
0388: do {
0389: baseAccess.checkCancelled();
0390: String target = targets.hasNext() ? (String) targets.next()
0391: : "";
0392: // get entry for target
0393: File targetFile = new File(baseAccess.getAnchor(), target);
0394: String targetName = "".equals(target) ? "" : SVNPathUtil
0395: .tail(target);
0396: String parentPath = SVNPathUtil.removeTail(target);
0397: SVNAdminArea dir = baseAccess.probeRetrieve(targetFile);
0398: SVNEntry entry = baseAccess.getEntry(targetFile, false);
0399: String url = null;
0400: if (entry == null) {
0401: SVNErrorMessage err = SVNErrorMessage.create(
0402: SVNErrorCode.ENTRY_NOT_FOUND,
0403: "''{0}'' is not under version control",
0404: targetFile);
0405: SVNErrorManager.error(err);
0406: } else if (entry.getURL() == null) {
0407: SVNErrorMessage err = SVNErrorMessage.create(
0408: SVNErrorCode.ENTRY_MISSING_URL,
0409: "''{0}'' has no URL", targetFile);
0410: SVNErrorManager.error(err);
0411: } else {
0412: url = entry.getURL();
0413: }
0414: SVNEntry parentEntry = null;
0415: if (entry != null
0416: && (entry.isScheduledForAddition() || entry
0417: .isScheduledForReplacement())) {
0418: // get parent (for file or dir-> get ""), otherwise open parent
0419: // dir and get "".
0420: try {
0421: baseAccess.retrieve(targetFile.getParentFile());
0422: } catch (SVNException e) {
0423: if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
0424: baseAccess.open(targetFile.getParentFile(),
0425: true, 0);
0426: } else {
0427: throw e;
0428: }
0429: }
0430: parentEntry = baseAccess.getEntry(targetFile
0431: .getParentFile(), false);
0432: if (parentEntry == null) {
0433: SVNErrorMessage err = SVNErrorMessage
0434: .create(
0435: SVNErrorCode.WC_CORRUPT,
0436: "''{0}'' is scheduled for addition within unversioned parent",
0437: targetFile);
0438: SVNErrorManager.error(err);
0439: } else if (parentEntry.isScheduledForAddition()
0440: || parentEntry.isScheduledForReplacement()) {
0441: danglers.add(targetFile.getParentFile());
0442: }
0443: }
0444: boolean recurse = recursive;
0445: if (entry != null && entry.isCopied()
0446: && entry.getSchedule() == null) {
0447: // if commit is forced => we could collect this entry, assuming
0448: // that its parent is already included into commit
0449: // it will be later removed from commit anyway.
0450: if (!force) {
0451: SVNErrorMessage err = SVNErrorMessage
0452: .create(
0453: SVNErrorCode.ILLEGAL_TARGET,
0454: "Entry for ''{0}''"
0455: + " is marked as 'copied' but is not itself scheduled\n"
0456: + "for addition. Perhaps you're committing a target that is\n"
0457: + "inside an unversioned (or not-yet-versioned) directory?",
0458: targetFile);
0459: SVNErrorManager.error(err);
0460: } else {
0461: // just do not process this item as in case of recursive
0462: // commit.
0463: continue;
0464: }
0465: } else if (entry != null && entry.isCopied()
0466: && entry.isScheduledForAddition()) {
0467: if (force) {
0468: isRecursionForced = !recursive;
0469: recurse = true;
0470: }
0471: } else if (entry != null && entry.isScheduledForDeletion()
0472: && force && !recursive) {
0473: // if parent is also deleted -> skip this entry
0474: if (!"".equals(targetName)) {
0475: parentEntry = dir.getEntry("", false);
0476: } else {
0477: File parentFile = targetFile.getParentFile();
0478: try {
0479: baseAccess.retrieve(parentFile);
0480: } catch (SVNException e) {
0481: if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
0482: baseAccess.open(targetFile.getParentFile(),
0483: true, 0);
0484: } else {
0485: throw e;
0486: }
0487: }
0488: parentEntry = baseAccess
0489: .getEntry(parentFile, false);
0490: }
0491: if (parentEntry != null
0492: && parentEntry.isScheduledForDeletion()
0493: && paths.contains(parentPath)) {
0494: continue;
0495: }
0496: // this recursion is not considered as "forced", all children should be
0497: // deleted anyway.
0498: recurse = true;
0499: }
0500: // String relativePath = entry.getKind() == SVNNodeKind.DIR ? target : SVNPathUtil.removeTail(target);
0501: harvestCommitables(commitables, dir, targetFile,
0502: parentEntry, entry, url, null, false, false,
0503: justLocked, lockTokens, recurse, isRecursionForced,
0504: params);
0505: } while (targets.hasNext());
0506:
0507: for (Iterator ds = danglers.iterator(); ds.hasNext();) {
0508: baseAccess.checkCancelled();
0509: File file = (File) ds.next();
0510: if (!commitables.containsKey(file)) {
0511: SVNErrorMessage err = SVNErrorMessage
0512: .create(
0513: SVNErrorCode.ILLEGAL_TARGET,
0514: "''{0}'' is not under version control\n"
0515: + "and is not part of the commit, \n"
0516: + "yet its child is part of the commit",
0517: file);
0518: SVNErrorManager.error(err);
0519: }
0520: }
0521: if (isRecursionForced) {
0522: // if commit is non-recursive and forced and there are elements included into commit
0523: // that not only 'copied' but also has local mods (modified or deleted), remove those items?
0524: // or not?
0525: for (Iterator items = commitables.values().iterator(); items
0526: .hasNext();) {
0527: baseAccess.checkCancelled();
0528: SVNCommitItem item = (SVNCommitItem) items.next();
0529: if (item.isDeleted()) {
0530: // to detect deleted copied items.
0531: File file = item.getFile();
0532: if (item.getKind() == SVNNodeKind.DIR) {
0533: if (!file.exists()) {
0534: continue;
0535: }
0536: } else {
0537: String name = SVNPathUtil.tail(item.getPath());
0538: SVNAdminArea dir = baseAccess.retrieve(item
0539: .getFile().getParentFile());
0540: if (!dir.getBaseFile(name, false).exists()) {
0541: continue;
0542: }
0543: }
0544: }
0545: if (item.isContentsModified() || item.isDeleted()
0546: || item.isPropertiesModified()) {
0547: // if item was not explicitly included into commit, then just make it 'added'
0548: // but do not remove that are marked as 'deleted'
0549: String itemPath = item.getPath();
0550: if (!paths.contains(itemPath)) {
0551: items.remove();
0552: }
0553: }
0554: }
0555: }
0556: return (SVNCommitItem[]) commitables.values().toArray(
0557: new SVNCommitItem[commitables.values().size()]);
0558: }
0559:
0560: public static String translateCommitables(SVNCommitItem[] items,
0561: Map decodedPaths) throws SVNException {
0562: Map itemsMap = new TreeMap();
0563: for (int i = 0; i < items.length; i++) {
0564: SVNCommitItem item = items[i];
0565: if (itemsMap.containsKey(item.getURL().toString())) {
0566: SVNCommitItem oldItem = (SVNCommitItem) itemsMap
0567: .get(item.getURL().toString());
0568: SVNErrorMessage err = SVNErrorMessage
0569: .create(
0570: SVNErrorCode.CLIENT_DUPLICATE_COMMIT_URL,
0571: "Cannot commit both ''{0}'' and ''{1}'' as they refer to the same URL",
0572: new Object[] { item.getFile(),
0573: oldItem.getFile() });
0574: SVNErrorManager.error(err);
0575: }
0576: itemsMap.put(item.getURL().toString(), item);
0577: }
0578:
0579: Iterator urls = itemsMap.keySet().iterator();
0580: String baseURL = (String) urls.next();
0581: while (urls.hasNext()) {
0582: String url = (String) urls.next();
0583: baseURL = SVNPathUtil.getCommonURLAncestor(baseURL, url);
0584: }
0585: if (itemsMap.containsKey(baseURL)) {
0586: SVNCommitItem root = (SVNCommitItem) itemsMap.get(baseURL);
0587: if (root.getKind() != SVNNodeKind.DIR) {
0588: baseURL = SVNPathUtil.removeTail(baseURL);
0589: } else if (root.getKind() == SVNNodeKind.DIR
0590: && (root.isAdded() || root.isDeleted()
0591: || root.isCopied() || root.isLocked())) {
0592: baseURL = SVNPathUtil.removeTail(baseURL);
0593: }
0594: }
0595: urls = itemsMap.keySet().iterator();
0596: while (urls.hasNext()) {
0597: String url = (String) urls.next();
0598: SVNCommitItem item = (SVNCommitItem) itemsMap.get(url);
0599: String realPath = url.equals(baseURL) ? "" : url
0600: .substring(baseURL.length() + 1);
0601: decodedPaths.put(SVNEncodingUtil.uriDecode(realPath), item);
0602: }
0603: return baseURL;
0604: }
0605:
0606: public static Map translateLockTokens(Map lockTokens, String baseURL) {
0607: Map translatedLocks = new TreeMap();
0608: for (Iterator urls = lockTokens.keySet().iterator(); urls
0609: .hasNext();) {
0610: String url = (String) urls.next();
0611: String token = (String) lockTokens.get(url);
0612: url = url.equals(baseURL) ? "" : url.substring(baseURL
0613: .length() + 1);
0614: translatedLocks.put(SVNEncodingUtil.uriDecode(url), token);
0615: }
0616: lockTokens.clear();
0617: lockTokens.putAll(translatedLocks);
0618: return lockTokens;
0619: }
0620:
0621: public static void harvestCommitables(Map commitables,
0622: SVNAdminArea dir, File path, SVNEntry parentEntry,
0623: SVNEntry entry, String url, String copyFromURL,
0624: boolean copyMode, boolean addsOnly, boolean justLocked,
0625: Map lockTokens, boolean recursive, boolean forcedRecursion,
0626: ISVNCommitParameters params) throws SVNException {
0627: if (commitables.containsKey(path)) {
0628: return;
0629: }
0630: if (dir != null && dir.getWCAccess() != null) {
0631: dir.getWCAccess().checkCancelled();
0632: }
0633: long cfRevision = entry.getCopyFromRevision();
0634: String cfURL = null;
0635: if (entry.getKind() != SVNNodeKind.DIR
0636: && entry.getKind() != SVNNodeKind.FILE) {
0637: SVNErrorMessage err = SVNErrorMessage.create(
0638: SVNErrorCode.NODE_UNKNOWN_KIND,
0639: "Unknown entry kind for ''{0}''", path);
0640: SVNErrorManager.error(err);
0641: }
0642: SVNFileType fileType = SVNFileType.getType(path);
0643: if (fileType == SVNFileType.UNKNOWN) {
0644: SVNErrorMessage err = SVNErrorMessage.create(
0645: SVNErrorCode.NODE_UNKNOWN_KIND,
0646: "Unknown entry kind for ''{0}''", path);
0647: SVNErrorManager.error(err);
0648: }
0649: String specialPropertyValue = dir
0650: .getProperties(entry.getName()).getPropertyValue(
0651: SVNProperty.SPECIAL);
0652: boolean specialFile = fileType == SVNFileType.SYMLINK;
0653: if (SVNFileType.isSymlinkSupportEnabled()) {
0654: if (((specialPropertyValue == null && specialFile) || (!SVNFileUtil.isWindows
0655: && specialPropertyValue != null && !specialFile))
0656: && fileType != SVNFileType.NONE) {
0657: SVNErrorMessage err = SVNErrorMessage
0658: .create(
0659: SVNErrorCode.NODE_UNEXPECTED_KIND,
0660: "Entry ''{0}'' has unexpectedly changed special status",
0661: path);
0662: SVNErrorManager.error(err);
0663: }
0664: }
0665: boolean propConflicts;
0666: boolean textConflicts = false;
0667: SVNAdminArea entries = null;
0668: if (entry.getKind() == SVNNodeKind.DIR) {
0669: SVNAdminArea childDir = null;
0670: try {
0671: childDir = dir.getWCAccess().retrieve(
0672: dir.getFile(entry.getName()));
0673: } catch (SVNException e) {
0674: if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
0675: childDir = null;
0676: } else {
0677: throw e;
0678: }
0679: }
0680: if (childDir != null && childDir.entries(true) != null) {
0681: entries = childDir;
0682: if (entries.getEntry("", false) != null) {
0683: entry = entries.getEntry("", false);
0684: dir = childDir;
0685: }
0686: }
0687: propConflicts = dir.hasPropConflict(entry.getName());
0688: } else {
0689: propConflicts = dir.hasPropConflict(entry.getName());
0690: textConflicts = dir.hasTextConflict(entry.getName());
0691: }
0692: if (propConflicts || textConflicts) {
0693: SVNErrorMessage err = SVNErrorMessage.create(
0694: SVNErrorCode.WC_FOUND_CONFLICT,
0695: "Aborting commit: ''{0}'' remains in conflict",
0696: path);
0697: SVNErrorManager.error(err);
0698: }
0699: if (entry.getURL() != null && !copyMode) {
0700: url = entry.getURL();
0701: }
0702: boolean commitDeletion = !addsOnly
0703: && ((entry.isDeleted() && entry.getSchedule() == null)
0704: || entry.isScheduledForDeletion() || entry
0705: .isScheduledForReplacement());
0706: if (!addsOnly && !commitDeletion
0707: && fileType == SVNFileType.NONE && params != null) {
0708: ISVNCommitParameters.Action action = entry.getKind() == SVNNodeKind.DIR ? params
0709: .onMissingDirectory(path)
0710: : params.onMissingFile(path);
0711: if (action == ISVNCommitParameters.ERROR) {
0712: SVNErrorMessage err = SVNErrorMessage.create(
0713: SVNErrorCode.WC_NOT_LOCKED,
0714: "Working copy file ''{0}'' is missing", path);
0715: SVNErrorManager.error(err);
0716: } else if (action == ISVNCommitParameters.DELETE) {
0717: commitDeletion = true;
0718: entry.scheduleForDeletion();
0719: dir.saveEntries(false);
0720: }
0721: }
0722: boolean commitAddition = false;
0723: boolean commitCopy = false;
0724: if (entry.isScheduledForAddition()
0725: || entry.isScheduledForReplacement()) {
0726: commitAddition = true;
0727: if (entry.getCopyFromURL() != null) {
0728: cfURL = entry.getCopyFromURL();
0729: addsOnly = false;
0730: commitCopy = true;
0731: } else {
0732: addsOnly = true;
0733: }
0734: }
0735: if ((entry.isCopied() || copyMode) && !entry.isDeleted()
0736: && entry.getSchedule() == null) {
0737: long parentRevision = entry.getRevision() - 1;
0738: boolean switched = false;
0739: if (entry != null && parentEntry != null) {
0740: switched = !entry.getURL().equals(
0741: SVNPathUtil.append(parentEntry.getURL(),
0742: SVNEncodingUtil.uriEncode(path
0743: .getName())));
0744: }
0745: if (!switched && !dir.getWCAccess().isWCRoot(path)) {
0746: if (parentEntry != null) {
0747: parentRevision = parentEntry.getRevision();
0748: }
0749:
0750: } else if (!copyMode) {
0751: SVNErrorMessage err = SVNErrorMessage
0752: .create(
0753: SVNErrorCode.WC_CORRUPT,
0754: "Did not expect ''{0}'' to be a working copy root",
0755: path);
0756: SVNErrorManager.error(err);
0757: }
0758: if (parentRevision != entry.getRevision()) {
0759: commitAddition = true;
0760: commitCopy = true;
0761: addsOnly = false;
0762: cfRevision = entry.getRevision();
0763: if (copyMode) {
0764: cfURL = entry.getURL();
0765: } else if (copyFromURL != null) {
0766: cfURL = copyFromURL;
0767: } else {
0768: SVNErrorMessage err = SVNErrorMessage
0769: .create(
0770: SVNErrorCode.BAD_URL,
0771: "Commit item ''{0}'' has copy flag but no copyfrom URL",
0772: path);
0773: SVNErrorManager.error(err);
0774: }
0775: }
0776: }
0777: boolean textModified = false;
0778: boolean propsModified = false;
0779: boolean commitLock;
0780:
0781: if (commitAddition) {
0782: SVNVersionedProperties props = dir.getProperties(entry
0783: .getName());
0784: SVNVersionedProperties baseProps = dir
0785: .getBaseProperties(entry.getName());
0786: Map propDiff = null;
0787: if (entry.isScheduledForReplacement()) {
0788: propDiff = props.asMap();
0789: } else {
0790: propDiff = baseProps.compareTo(props).asMap();
0791: }
0792: boolean eolChanged = textModified = propDiff != null
0793: && propDiff.containsKey(SVNProperty.EOL_STYLE);
0794: if (entry.getKind() == SVNNodeKind.FILE) {
0795: if (commitCopy) {
0796: textModified = propDiff != null
0797: && propDiff
0798: .containsKey(SVNProperty.EOL_STYLE);
0799: if (!textModified) {
0800: textModified = dir.hasTextModifications(entry
0801: .getName(), eolChanged);
0802: }
0803: } else {
0804: textModified = true;
0805: }
0806: }
0807: propsModified = propDiff != null && !propDiff.isEmpty();
0808: } else if (!commitDeletion) {
0809: SVNVersionedProperties props = dir.getProperties(entry
0810: .getName());
0811: SVNVersionedProperties baseProps = dir
0812: .getBaseProperties(entry.getName());
0813: Map propDiff = baseProps.compareTo(props).asMap();
0814: boolean eolChanged = textModified = propDiff != null
0815: && propDiff.containsKey(SVNProperty.EOL_STYLE);
0816: propsModified = propDiff != null && !propDiff.isEmpty();
0817: if (entry.getKind() == SVNNodeKind.FILE) {
0818: textModified = dir.hasTextModifications(
0819: entry.getName(), eolChanged);
0820: }
0821: }
0822:
0823: commitLock = entry.getLockToken() != null
0824: && (justLocked || textModified || propsModified
0825: || commitDeletion || commitAddition || commitCopy);
0826:
0827: if (commitAddition || commitDeletion || textModified
0828: || propsModified || commitCopy || commitLock) {
0829: SVNCommitItem item = new SVNCommitItem(path, SVNURL
0830: .parseURIEncoded(url), cfURL != null ? SVNURL
0831: .parseURIEncoded(cfURL) : null, entry.getKind(),
0832: SVNRevision.create(entry.getRevision()),
0833: SVNRevision.create(cfRevision), commitAddition,
0834: commitDeletion, propsModified, textModified,
0835: commitCopy, commitLock);
0836: String itemPath = dir.getRelativePath(dir.getWCAccess()
0837: .retrieve(dir.getWCAccess().getAnchor()));
0838: if ("".equals(itemPath)) {
0839: itemPath += entry.getName();
0840: } else if (!"".equals(entry.getName())) {
0841: itemPath += "/" + entry.getName();
0842: }
0843: item.setPath(itemPath);
0844: commitables.put(path, item);
0845: if (lockTokens != null && entry.getLockToken() != null) {
0846: lockTokens.put(url, entry.getLockToken());
0847: }
0848: }
0849: if (entries != null && recursive
0850: && (commitAddition || !commitDeletion)) {
0851: // recurse.
0852: for (Iterator ents = entries.entries(copyMode); ents
0853: .hasNext();) {
0854: if (dir != null && dir.getWCAccess() != null) {
0855: dir.getWCAccess().checkCancelled();
0856: }
0857: SVNEntry currentEntry = (SVNEntry) ents.next();
0858: if ("".equals(currentEntry.getName())) {
0859: continue;
0860: }
0861: // if recursion is forced and entry is explicitly copied, skip it.
0862: if (forcedRecursion && currentEntry.isCopied()
0863: && currentEntry.getCopyFromURL() != null) {
0864: continue;
0865: }
0866: String currentCFURL = cfURL != null ? cfURL
0867: : copyFromURL;
0868: if (currentCFURL != null) {
0869: currentCFURL = SVNPathUtil.append(currentCFURL,
0870: SVNEncodingUtil.uriEncode(currentEntry
0871: .getName()));
0872: }
0873: String currentURL = currentEntry.getURL();
0874: if (copyMode || entry.getURL() == null) {
0875: currentURL = SVNPathUtil.append(url,
0876: SVNEncodingUtil.uriEncode(currentEntry
0877: .getName()));
0878: }
0879: File currentFile = dir.getFile(currentEntry.getName());
0880: SVNAdminArea childDir;
0881: if (currentEntry.getKind() == SVNNodeKind.DIR) {
0882: try {
0883: childDir = dir.getWCAccess().retrieve(
0884: dir.getFile(currentEntry.getName()));
0885: } catch (SVNException e) {
0886: if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
0887: childDir = null;
0888: } else {
0889: throw e;
0890: }
0891: }
0892: if (childDir == null) {
0893: SVNFileType currentType = SVNFileType
0894: .getType(currentFile);
0895: if (currentType == SVNFileType.NONE
0896: && currentEntry
0897: .isScheduledForDeletion()) {
0898: SVNCommitItem item = new SVNCommitItem(
0899: currentFile,
0900: SVNURL.parseURIEncoded(currentURL),
0901: null, currentEntry.getKind(),
0902: SVNRevision.UNDEFINED,
0903: SVNRevision.UNDEFINED, false, true,
0904: false, false, false, false);
0905: String dirPath = dir.getRelativePath(dir
0906: .getWCAccess().retrieve(
0907: dir.getWCAccess()
0908: .getAnchor()));
0909: item.setPath(SVNPathUtil.append(dirPath,
0910: currentEntry.getName()));
0911: commitables.put(currentFile, item);
0912: continue;
0913: } else if (currentType != SVNFileType.NONE) {
0914: // directory is not missing, but obstructed,
0915: // or no special params are specified.
0916: SVNErrorMessage err = SVNErrorMessage
0917: .create(
0918: SVNErrorCode.WC_NOT_LOCKED,
0919: "Working copy ''{0}'' is missing or not locked",
0920: currentFile);
0921: SVNErrorManager.error(err);
0922: } else {
0923: ISVNCommitParameters.Action action = params != null ? params
0924: .onMissingDirectory(dir
0925: .getFile(currentEntry
0926: .getName()))
0927: : ISVNCommitParameters.ERROR;
0928: if (action == ISVNCommitParameters.DELETE) {
0929: SVNCommitItem item = new SVNCommitItem(
0930: currentFile,
0931: SVNURL
0932: .parseURIEncoded(currentURL),
0933: null, currentEntry.getKind(),
0934: SVNRevision.UNDEFINED,
0935: SVNRevision.UNDEFINED, false,
0936: true, false, false, false,
0937: false);
0938: String dirPath = dir
0939: .getRelativePath(dir
0940: .getWCAccess()
0941: .retrieve(
0942: dir
0943: .getWCAccess()
0944: .getAnchor()));
0945: item.setPath(SVNPathUtil
0946: .append(dirPath, currentEntry
0947: .getName()));
0948: commitables.put(currentFile, item);
0949: currentEntry.scheduleForDeletion();
0950: entries.saveEntries(false);
0951: continue;
0952: } else if (action != ISVNCommitParameters.SKIP) {
0953: SVNErrorMessage err = SVNErrorMessage
0954: .create(
0955: SVNErrorCode.WC_NOT_LOCKED,
0956: "Working copy ''{0}'' is missing or not locked",
0957: currentFile);
0958: SVNErrorManager.error(err);
0959: }
0960: }
0961: }
0962: }
0963: harvestCommitables(commitables, dir, currentFile,
0964: entry, currentEntry, currentURL, currentCFURL,
0965: copyMode, addsOnly, justLocked, lockTokens,
0966: true, forcedRecursion, params);
0967:
0968: }
0969: }
0970: if (lockTokens != null && entry.getKind() == SVNNodeKind.DIR
0971: && commitDeletion) {
0972: // harvest lock tokens for deleted items.
0973: collectLocks(dir, lockTokens);
0974: }
0975: }
0976:
0977: private static void collectLocks(SVNAdminArea adminArea,
0978: Map lockTokens) throws SVNException {
0979: for (Iterator ents = adminArea.entries(false); ents.hasNext();) {
0980: SVNEntry entry = (SVNEntry) ents.next();
0981: if (entry.getURL() != null && entry.getLockToken() != null) {
0982: lockTokens.put(entry.getURL(), entry.getLockToken());
0983: }
0984: if (!adminArea.getThisDirName().equals(entry.getName())
0985: && entry.isDirectory()) {
0986:
0987: SVNAdminArea child;
0988: try {
0989: child = adminArea.getWCAccess().retrieve(
0990: adminArea.getFile(entry.getName()));
0991: } catch (SVNException e) {
0992: if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
0993: child = null;
0994: } else {
0995: throw e;
0996: }
0997: }
0998: if (child != null) {
0999: collectLocks(child, lockTokens);
1000: }
1001: }
1002: }
1003: adminArea.closeEntries();
1004: }
1005:
1006: private static void removeRedundantPaths(
1007: Collection dirsToLockRecursively, Collection dirsToLock) {
1008: for (Iterator paths = dirsToLock.iterator(); paths.hasNext();) {
1009: String path = (String) paths.next();
1010: if (dirsToLockRecursively.contains(path)) {
1011: paths.remove();
1012: } else {
1013: for (Iterator recPaths = dirsToLockRecursively
1014: .iterator(); recPaths.hasNext();) {
1015: String existingPath = (String) recPaths.next();
1016: if (path.startsWith(existingPath + "/")) {
1017: paths.remove();
1018: break;
1019: }
1020: }
1021: }
1022: }
1023: }
1024:
1025: private static File adjustRelativePaths(File rootFile,
1026: Collection relativePaths) throws SVNException {
1027: if (relativePaths.contains("")) {
1028: String targetName = getTargetName(rootFile);
1029: if (!"".equals(targetName)
1030: && rootFile.getParentFile() != null) {
1031: // there is a versioned parent.
1032: rootFile = rootFile.getParentFile();
1033: List result = new ArrayList();
1034: for (Iterator paths = relativePaths.iterator(); paths
1035: .hasNext();) {
1036: String path = (String) paths.next();
1037: path = "".equals(path) ? targetName : SVNPathUtil
1038: .append(targetName, path);
1039: if (!result.contains(path)) {
1040: result.add(path);
1041: }
1042: }
1043: relativePaths.clear();
1044: Collections.sort(result);
1045: relativePaths.addAll(result);
1046: }
1047: }
1048: return rootFile;
1049: }
1050:
1051: private static String getTargetName(File file) throws SVNException {
1052: SVNWCAccess wcAccess = SVNWCAccess.newInstance(null);
1053: try {
1054: wcAccess.probeOpen(file, false, 0);
1055: SVNFileType fileType = SVNFileType.getType(file);
1056: if ((fileType == SVNFileType.FILE || fileType == SVNFileType.SYMLINK)
1057: || !wcAccess.isWCRoot(file)) {
1058: return file.getName();
1059: }
1060: } finally {
1061: wcAccess.close();
1062: }
1063: return "";
1064: }
1065:
1066: private static boolean isRecursiveCommitForced(File directory)
1067: throws SVNException {
1068: SVNWCAccess wcAccess = SVNWCAccess.newInstance(null);
1069: try {
1070: wcAccess.open(directory, false, 0);
1071: SVNEntry targetEntry = wcAccess.getEntry(directory, false);
1072: if (targetEntry != null) {
1073: return targetEntry.isCopied()
1074: || targetEntry.isScheduledForDeletion()
1075: || targetEntry.isScheduledForReplacement();
1076: }
1077: } finally {
1078: wcAccess.close();
1079: }
1080: return false;
1081: }
1082: }
|