package com.perforce.svn.change; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.perforce.common.ConverterException; import com.perforce.common.asset.ContentType; import com.perforce.common.asset.TranslateCharsetType; import com.perforce.common.client.Connection; import com.perforce.common.client.ConnectionFactory; import com.perforce.common.client.P4Factory; import com.perforce.common.depot.DepotImport; import com.perforce.common.node.NodeAttributes; import com.perforce.common.node.PathMapTranslator; import com.perforce.common.process.ChangeInfo; import com.perforce.common.process.ProcessFactory; import com.perforce.config.CFG; import com.perforce.config.CaseSensitivity; import com.perforce.config.Config; import com.perforce.config.ConfigException; import com.perforce.p4java.client.IClient; import com.perforce.p4java.core.ChangelistStatus; import com.perforce.p4java.core.IChangelist; import com.perforce.p4java.core.file.FileSpecBuilder; import com.perforce.p4java.core.file.FileSpecOpStatus; import com.perforce.p4java.core.file.IFileSpec; import com.perforce.p4java.impl.generic.core.Changelist; import com.perforce.p4java.server.IOptionsServer; import com.perforce.svn.history.Action; import com.perforce.svn.history.ChangeAction; import com.perforce.svn.parser.Content; import com.perforce.svn.parser.Property; import com.perforce.svn.process.MergeInfo; import com.perforce.svn.process.MergeSource; import com.perforce.svn.query.QueryPerforce; public class ChangeImport implements ChangeInterface { private static Logger logger = LoggerFactory.getLogger(ChangeImport.class); private IClient iclient; private IChangelist ichangelist; private long change; private DepotImport depot; private ChangeInfo changeInfo; private List<MergeInfo> mergeInfoList = new ArrayList<MergeInfo>(); private MergeSource mergeSource; public ChangeImport(long c, ChangeInfo i, DepotImport d) throws Exception { changeInfo = i; depot = d; String description = i.getDescription(); Connection p4 = ConnectionFactory.getConnection(); iclient = p4.getIclient(); IChangelist implChangelist = new Changelist(); implChangelist.setClientId((String) Config.get(CFG.P4_CLIENT)); implChangelist.setDescription(description); implChangelist.setDate(changeInfo.getDate()); ichangelist = iclient.createChangelist(implChangelist); change = ichangelist.getId(); } @Override public void setCounter(String key, String value) throws Exception { Connection p4 = ConnectionFactory.getConnection(); IOptionsServer iserver = p4.getIserver(); iserver.setCounter(key, value, false); } @Override public long submit() throws Exception { long change = 0; ichangelist.refresh(); // check for empty changelist if (ichangelist.getFiles(true).isEmpty()) { delete(); // client won't submit empty change :-( } // submit change else { if (logger.isDebugEnabled()) { StringBuffer msg = new StringBuffer(); msg.append(" submitting changelist "); msg.append(ichangelist.getId()); msg.append(" by user "); msg.append(changeInfo.getUser()); logger.debug(msg.toString()); } // call submit and check returned spec List<IFileSpec> submitted = ichangelist.submit(null); String ignore = "Submitted as change"; P4Factory.validateFileSpecs(submitted, ignore); change = findSubmittedChange(submitted); // Clean up workspace cleanWorkspace(); ichangelist.setDate(changeInfo.getDate()); ichangelist.setUsername(changeInfo.getUser()); ichangelist.update(true); // check if there was a problem if (ichangelist.getStatus() == ChangelistStatus.PENDING) { throw new ConverterException("Could not submit changelist!"); } } if (logger.isTraceEnabled()) { logger.trace("done.\n\n"); } return change; } private long findSubmittedChange(List<IFileSpec> submitted) { long change = 0; for (IFileSpec spec : submitted) { if (spec.getOpStatus() != FileSpecOpStatus.VALID) { String msg = spec.getStatusMessage(); String cng = "Submitted as change "; if (msg.startsWith(cng)) { try { String id = msg.substring(cng.length()); change = Long.parseLong(id); } catch (NumberFormatException e) { change = -1; } } } } return change; } /** * Remove (unsync) files from clients local workspace * * @throws Exception */ private void cleanWorkspace() throws Exception { List<IFileSpec> fileSpecs; String path = PathMapTranslator.translate(null) + "...#none"; fileSpecs = FileSpecBuilder.makeFileSpecList(path); List<IFileSpec> syncMsg = iclient.sync(fileSpecs, null); P4Factory.validateFileSpecs(syncMsg, "file(s) up-to-date."); } /** * Delete pending changelist */ @Override public void delete() throws Exception { if (getNumberOfRevisions() > 0) throw new ConverterException("Changelist contains files"); // logging if (logger.isDebugEnabled()) { StringBuffer msg = new StringBuffer(); msg.append("deleting changelist "); msg.append(ichangelist.getId()); msg.append(" by user "); msg.append(ichangelist.getUsername()); logger.debug(msg.toString()); } IOptionsServer iserver = depot.getIServer(); iserver.setUserName(ichangelist.getUsername()); iserver.deletePendingChangelist(ichangelist.getId()); } public void addPath(Action nodeAction, String toPath, ArrayList<MergeSource> fromList, Property property, boolean subBlock) throws Exception { // Path syntax translation String depotToPath = PathMapTranslator.translate(toPath); // Deal with null paths and don't add extra '/' if (toPath == null) depotToPath = "//" + depot.getName(); // get a RevisionImport helper RevisionImport rev = new RevisionImport(iclient, ichangelist, depot, changeInfo.getDate()); switch (nodeAction) { case ADD: case EDIT: // find properties and store if required createDirProperty(toPath, property); break; case UPDATE: // remove directory or file rev.deletePath(depotToPath); removeAction(rev, depotToPath, false); addPath(Action.EDIT, toPath, fromList, property, subBlock); break; case COPY: // remove directory or file rev.deletePath(depotToPath); removeAction(rev, depotToPath, false); addPath(Action.BRANCH, toPath, fromList, property, subBlock); break; case REMOVE: // remove directory or file rev.deletePath(depotToPath); removeAction(rev, depotToPath, false); break; case BRANCH: for (MergeSource from : fromList) { String fromPath = from.getFromPath(); long fromChange = from.getEndFromChange(); String depotFromPath = PathMapTranslator.translate(fromPath); // Deal with null paths and don't add extra '/' if (fromPath == null || fromPath.isEmpty()) depotFromPath = "//" + depot.getName(); if (pathMatch(depotToPath, depotFromPath)) { rev.rollBackBranch(depotToPath, depotFromPath, fromChange); } else { rev.branchPath(depotFromPath, fromChange, depotToPath); } } // find properties and store if required createDirProperty(toPath, property); break; case LABEL: return; default: throw new ConverterException("Node-action(" + nodeAction + ")"); } } private void createDirProperty(String toPath, Property property) throws Exception { if (!(Boolean) Config.get(CFG.SVN_PROP_ENABLED) || property == null) return; // Property file path String propPath = toPath; if (!toPath.isEmpty()) { propPath += "/"; } propPath += Config.get(CFG.SVN_PROP_NAME); // Create attributes content and node Content content = new Content(); NodeAttributes attributes = new NodeAttributes(property); content.setAttributes(attributes); // Query perforce for last action QueryPerforce query = (QueryPerforce) ProcessFactory.getQuery(depot); ChangeAction lastProp = query.findLastAction(propPath, change); int headRev = query.findHeadRevision(propPath, change); // Determine necessary action Action act = null; if (!property.isEmpty()) { if ((lastProp != null) && (lastProp.getAction() != Action.REMOVE)) act = Action.EDIT; else act = Action.ADD; } else { if ((lastProp != null) && (lastProp.getAction() != Action.REMOVE)) act = Action.REMOVE; } if (act != null) { // Write header to content headRev += 1; // add as pending revision is not added on content.getAttributes().setHeader(propPath, change, headRev); addRevision(act, propPath, null, content, false, false); } } public void addRevision(Action nodeAction, String toPath, ArrayList<MergeSource> fromList, Content content, boolean subBlock, boolean pendingBlock) throws Exception { // Path syntax translation String depotToPath = PathMapTranslator.translate(toPath); // Get last 'to' action QueryPerforce query = (QueryPerforce) ProcessFactory.getQuery(depot); ChangeAction lastTo = query.findLastAction(toPath, change - 1); // get a RevisionImport helper RevisionImport rev = new RevisionImport(iclient, ichangelist, depot, changeInfo.getDate()); if (logger.isTraceEnabled()) { logger.trace("addRevision (" + nodeAction + ")"); } // down grade content type for non unicode servers from utf8 to text or // utf32 to binary if ((Boolean) Config.get(CFG.P4_UNICODE) == false) { if ((content.getType() == ContentType.UTF_32BE) || (content.getType() == ContentType.UTF_32LE)) { if (logger.isInfoEnabled()) { logger.info("... Non-unicode server, downgrading utf32 file to binary"); } content.setType(ContentType.P4_BINARY); } if (content.getType().getP4Type() == TranslateCharsetType.UTF8) { if (logger.isInfoEnabled()) { logger.info("... Non-unicode server, downgrading file to text"); } content.setType(ContentType.P4_TEXT); } } // Determine action and down grade if required switch (nodeAction) { case ADD: case EDIT: if (rev.isFile(depotToPath)) { // revert any pending actions (typically deletes) if (rev.openedFile(depotToPath, Action.REMOVE)) { rev.revertFile(depotToPath); if (logger.isDebugEnabled()) { logger.debug("reverting file: " + depotToPath); } } rev.editFile(depotToPath, content); } else { rev.addFile(depotToPath, content); } break; case UPDATE: // remove directory or file rev.deletePath(depotToPath); removeAction(rev, depotToPath, false); addRevision(Action.EDIT, toPath, fromList, content, subBlock, pendingBlock); break; case COPY: // remove directory or file rev.deletePath(depotToPath); removeAction(rev, depotToPath, false); addRevision(Action.BRANCH, toPath, fromList, content, subBlock, pendingBlock); break; case REMOVE: removeAction(rev, depotToPath, true); break; case BRANCH: case MERGE: // revert any pending actions (typically deletes) if (rev.isOpened(depotToPath)) { rev.revertFile(depotToPath); if (logger.isDebugEnabled()) { logger.debug("reverting file: " + depotToPath); } } for (MergeSource from : fromList) { String fromPath = from.getFromPath(); long startFromChange = from.getStartFromChange(); long endFromChange = from.getEndFromChange(); String depotFromPath = PathMapTranslator.translate(fromPath); if (pathMatch(depotToPath, depotFromPath)) { // roll back action rev.rollBackFile(depotToPath, content, depotFromPath, endFromChange); continue; } // Normal branch (no target or deleted target) if (lastTo == null || lastTo.getAction() == Action.REMOVE) { rev.branchFile(depotToPath, content, depotFromPath, endFromChange); } else { rev.integFile(depotToPath, content, depotFromPath, startFromChange, endFromChange, from.getMergeAction()); } } break; case LABEL: return; default: throw new ConverterException("Node-action(" + nodeAction + ")"); } } private void removeAction(RevisionImport rev, String depotToPath, boolean remove) throws Exception { if (rev.isOpened(depotToPath)) { if (!remove) { if (logger.isTraceEnabled()) { logger.trace("addRevision (reverting): " + depotToPath); } rev.revertFile(depotToPath); } else { rev.revertFile(depotToPath); rev.deleteFile(depotToPath); } } else { rev.deleteFile(depotToPath); } } @Override public int getNumberOfRevisions() throws Exception { List<IFileSpec> files = ichangelist.getFiles(true); return files.size(); } @Override public String getUser() { return changeInfo.getUser(); } @Override public long getChange() { return change; } @Override public void close() throws Exception { setCounter("p4-convert.svn.version", (String) Config.get(CFG.VERSION)); ConnectionFactory.close(); } @Override public long getSvnRevision() { return changeInfo.getScmChange(); } private boolean pathMatch(String toPath, String fromPath) throws ConfigException { boolean match = false; CaseSensitivity mode = (CaseSensitivity) Config.get(CFG.P4_CASE); // For case insensitive platforms ignore case when down grading if (mode == CaseSensitivity.NONE) { match = toPath.equals(fromPath); } else { match = toPath.equalsIgnoreCase(fromPath); } return match; } @Override public void setMergeInfo(MergeInfo m) { mergeInfoList.add(m); } @Override public List<MergeInfo> getMergeInfoList() { return mergeInfoList; } @Override public void setMergeSource(MergeSource m) { mergeSource = m; } @Override public MergeSource getMergeSource() { return mergeSource; } @Override public boolean isPendingRevision(String toPath) throws Exception { RevisionImport rev = new RevisionImport(iclient, ichangelist, depot, changeInfo.getDate()); String depotToPath = PathMapTranslator.translate(toPath); return rev.isOpened(depotToPath); } @Override public Action getPendingAction(String toPath) throws Exception { RevisionImport rev = new RevisionImport(iclient, ichangelist, depot, changeInfo.getDate()); String depotToPath = PathMapTranslator.translate(toPath); return rev.getOpenedAction(depotToPath); } @Override public ChangeInfo getChangeInfo() { return changeInfo; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 12446 | Paul Allen | Branching using p4convert-rusty | ||
//guest/perforce_software/p4convert/src/com/perforce/svn/change/ChangeImport.java | |||||
#8 | 11263 | Paul Allen | Refactor the enum Action outside of ChangeAction. | ||
#7 | 11071 | Paul Allen |
Path map translator for CVS and SVN paths. (undoc) To use create a path.map file with regex and group match. Only the first matching entry is used. The regex and group match are seperated by ', ' (or in regex terms ',\s+'). Lines starting with '#' are ignored. For example, 'trunk' is renamed to 'main', but other entries are left as-is. # path.map trunk/(.*), //import/main/{1} (.*), //import/{1} Note: if no file is found the default 'depot' and 'subPath' options are used to generate the map, preserving the original behaviour. CVS paths will always stat with the 'branch' name. 'main' for 1.1 and the symbol for other branches. # path.map main/projA/(.*), //import/projA/MAIN/{1} release_(.*)/projA/(.*), //import/projA/REL{1}/{2} (.*)/projA/(.*), //import/projA/TAG-{1}/{2} (.*), //import/unexpected/{1} Node: adding a catch all is a good idea. |
||
#6 | 11066 | Paul Allen |
Code refactor: Moved 'node' package from 'svn' to 'common'. |
||
#5 | 11041 | Paul Allen | SVN: Fixes to support changelist numbering and AUTOMATIC labels in Import mode (front door). | ||
#4 | 11013 | Paul Allen | SVN: Expose ChangeInfo object outside of ChangeInterface. | ||
#3 | 10983 | Paul Allen |
SVN: Updated change number mapping. Use the submitted change number not the pending number, for the changeMap. |
||
#2 | 10882 | Paul Allen |
CVS non branched revisions. Address issue when adding files to a branch that have no connection to main. CVS identifies these as 'dead' revisions on main with a branch. - replaced CVS test case 047 |
||
#1 | 9807 | Paul Allen | Initial import of p4-convert (from change 894340) |