package hudson.plugins.accurev; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.Run; import hudson.plugins.accurev.parsers.output.ParseOutputToFile; import hudson.plugins.accurev.parsers.xml.ParseUpdate; import hudson.scm.ChangeLogParser; import hudson.scm.ChangeLogSet; import hudson.scm.RepositoryBrowser; import org.xml.sax.SAXException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.logging.Logger; /** * Parses a change log that was recorded by {@link ParseOutputToFile}. */ public class ParseChangeLog extends ChangeLogParser { private static final Logger logger = Logger.getLogger(AccurevSCM.class.getName()); private static final long MILLIS_PER_SECOND = 1000L; /** * Converts an Accurev timestamp into a {@link Date} * * @param transactionTime The accurev timestamp. * @return A {@link Date} set to the time for the accurev timestamp. */ public static Date convertAccurevTimestamp(String transactionTime) { if (transactionTime == null) { return null; } try { final long time = Long.parseLong(transactionTime); final long date = time * MILLIS_PER_SECOND; return new Date(date); } catch (NumberFormatException e) { return null; } } /** * {@inheritDoc} * * @param build build * @param browser Repository browser * @param changelogFile change log file * @return ChangeLogSet with AccurevTransactions * @throws IOException failing IO * @throws SAXException failing XML SAX exception */ public ChangeLogSet<AccurevTransaction> parse(Run build, RepositoryBrowser<?> browser, File changelogFile) throws IOException, SAXException { UpdateLog updateLog = new UpdateLog(); List<AccurevTransaction> transactions = parse(changelogFile, updateLog); transactions = filterTransactions(transactions, updateLog); return new AccurevChangeLogSet(build, transactions); } private List<AccurevTransaction> filterTransactions(List<AccurevTransaction> transactions, UpdateLog updateLog) { List<AccurevTransaction> filteredTransactions; if (updateLog.hasUpdate()) { filteredTransactions = new ArrayList<>(); if (updateLog.hasChanges()) { List<String> changesFiles = updateLog.changedFiles; List<String> filteredFiles = new ArrayList<>(changesFiles); for (AccurevTransaction transaction : transactions) { List<String> rawPaths = transaction.getAffectedRawPaths(); boolean includeTransaction = true; for (String rawPath : rawPaths) { if (!changesFiles.contains(rawPath)) { includeTransaction = false; break; } } if (includeTransaction) { filteredTransactions.add(transaction); filteredFiles.removeAll(rawPaths); } } if (!filteredFiles.isEmpty()) { AccurevTransaction extraFiles = new AccurevTransaction(); extraFiles.setDate(new Date()); extraFiles.setAction("promote"); extraFiles.setId("upstream"); extraFiles.setMsg("Upstream changes"); extraFiles.setUser("upstream"); filteredFiles.forEach(extraFiles::addAffectedPath); filteredTransactions.add(extraFiles); } } } else { // No Update log dont filter filteredTransactions = transactions; } filteredTransactions.removeIf(t -> t.getAction().equalsIgnoreCase("dispatch")); return filteredTransactions; } private List<AccurevTransaction> parse(File changelogFile, UpdateLog updateLog) throws IOException { List<AccurevTransaction> transactions = new ArrayList<>(); try { XmlPullParser parser = XmlParserFactory.newParser(); try (BufferedReader br = Files.newBufferedReader(changelogFile.toPath())) { parser.setInput(br); transactions.addAll(parseTransactions(parser, changelogFile, updateLog)); } finally { parser.setInput(null); } } catch (XmlPullParserException e) { throw new IOException(e); } return transactions; } // TODO: Reduce complexity @SuppressFBWarnings("SF_SWITCH_NO_DEFAULT") private List<AccurevTransaction> parseTransactions(XmlPullParser parser, File changeLogFile, UpdateLog updateLog) throws IOException, XmlPullParserException { List<AccurevTransaction> transactions = new ArrayList<>(); AccurevTransaction currentTransaction = null; boolean inComment = false; boolean inIssueNum = false; boolean inVersion = false; String path = ""; String realVersion = ""; String issueNum; String affectedPathInfo; boolean inConsolidatedChangeLog = false; boolean inUpdateLog = false; boolean inDepot = false; boolean inWebuiURL = false; String depotName = ""; String webuiURL = ""; while (true) { switch (parser.next()) { case XmlPullParser.START_DOCUMENT: break; case XmlPullParser.END_DOCUMENT: return transactions; case XmlPullParser.START_TAG: final String tagName = parser.getName(); if ("transaction".equalsIgnoreCase(tagName)) { currentTransaction = new AccurevTransaction(); transactions.add(currentTransaction); currentTransaction.setId(parser.getAttributeValue("", "id")); currentTransaction.setUser(parser.getAttributeValue("", "user")); currentTransaction.setDate(ParseChangeLog.convertAccurevTimestamp(parser.getAttributeValue("", "time"))); currentTransaction.setAction(parser.getAttributeValue("", "type")); if (webuiURL != null && !webuiURL.isEmpty()) { currentTransaction.setWebuiURLforTrans(webuiURL + "/WebGui.jsp?tran_number=" + parser.getAttributeValue("", "id") + "&depot=" + depotName + "&view=trans_hist"); } } else if ("version".equalsIgnoreCase(tagName) && currentTransaction != null) { path = parser.getAttributeValue("", "path"); if (path != null) { path = path.replace("\\", "/"); if (path.startsWith("/./")) { path = path.substring(3); } // currentTransaction.addAffectedPath(path); } inVersion = true; realVersion = parser.getAttributeValue("", "real"); // currentTransaction.addFileRevision("Version - "+realVersion); } else if ("issueNum".equalsIgnoreCase(tagName) && currentTransaction != null) { inIssueNum = true; } else if ("comment".equalsIgnoreCase(tagName) && currentTransaction != null) { inComment = true; } else if ("ChangeLog".equalsIgnoreCase(tagName)) { inConsolidatedChangeLog = true; } else if ("UpdateLog".equalsIgnoreCase(tagName)) { inUpdateLog = true; } else if ("depot".equalsIgnoreCase(tagName)) { inDepot = true; } else if ("webuiURL".equalsIgnoreCase(tagName)) { inWebuiURL = true; } break; case XmlPullParser.END_TAG: final String endTagName = parser.getName(); if ("issueNum".equalsIgnoreCase(endTagName) && inVersion && inIssueNum && currentTransaction != null) { affectedPathInfo = path + " --- " + "Version - " + realVersion; currentTransaction.addAffectedPath(affectedPathInfo); currentTransaction.addAffectedRawPath(path); inIssueNum = false; inVersion = false; } else if ("version".equalsIgnoreCase(endTagName) && inVersion && currentTransaction != null) { affectedPathInfo = path + " --- " + "Version - " + realVersion; currentTransaction.addAffectedPath(affectedPathInfo); currentTransaction.addAffectedRawPath(path); inVersion = false; } else if ("comment".equalsIgnoreCase(endTagName)) { inComment = false; } else if ("ChangeLog".equalsIgnoreCase(endTagName)) { inConsolidatedChangeLog = false; } else if ("UpdateLog".equalsIgnoreCase(endTagName)) { inUpdateLog = false; } else if ("depot".equalsIgnoreCase(endTagName)) { inDepot = false; } else if ("webuiURL".equalsIgnoreCase(endTagName)) { inWebuiURL = false; } break; case XmlPullParser.TEXT: if (inComment && currentTransaction != null) { currentTransaction.setMsg(parser.getText()); } else if (inVersion && inIssueNum && currentTransaction != null) { issueNum = parser.getText(); currentTransaction.setIssueNum(issueNum); if (webuiURL != null && !webuiURL.isEmpty()) { currentTransaction.setWebuiURLforIssue(webuiURL + "/WebGui.jsp?depot=" + depotName + "&issueNum=" + issueNum + "&view=issue"); } } else if (inDepot) { depotName = parser.getText(); } else if (inWebuiURL) { webuiURL = parser.getText(); } if (inConsolidatedChangeLog) { File subChangeLog = new File(changeLogFile.getParent(), parser.getText()); transactions.addAll(parse(subChangeLog, updateLog)); } if (inUpdateLog) { File updateLogFile = new File(changeLogFile.getParent(), parser.getText()); parseUpdate(updateLogFile, updateLog); } break; } } } private void parseUpdate(File updateLogFile, UpdateLog updateLog) throws IOException { ParseUpdate parseUpdate = new ParseUpdate(); List<String> updatedFiles = new ArrayList<>(); updateLog.changedFiles = updatedFiles; try { try { XmlPullParser parser = XmlParserFactory.newParser(); try (BufferedReader br = Files.newBufferedReader(updateLogFile.toPath())) { parser.setInput(br); parseUpdate.parse(parser, updatedFiles); } finally { parser.setInput(null); } } catch (XmlPullParserException e) { throw new IOException(e); } } catch (AccurevLauncher.UnhandledAccurevCommandOutput ex) { throw new IOException(ex); } } private static class UpdateLog { private List<String> changedFiles; public boolean hasUpdate() { return changedFiles != null; } public boolean hasChanges() { return hasUpdate() && !changedFiles.isEmpty(); } } }