Java tutorial
/*********************************************************************************************** * Copyright (c) Microsoft Corporation All rights reserved. * * MIT License: * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. ***********************************************************************************************/ package com.microsoft.gittf.core.tasks; import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.LogCommand; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import com.microsoft.gittf.core.GitTFConstants; import com.microsoft.gittf.core.Messages; import com.microsoft.gittf.core.OutputConstants; import com.microsoft.gittf.core.config.ChangesetCommitMap; import com.microsoft.gittf.core.config.ChangesetCommitMapUtil; import com.microsoft.gittf.core.config.ChangesetCommitMapUtil.ChangesetCommitDetails; import com.microsoft.gittf.core.config.GitTFConfiguration; import com.microsoft.gittf.core.identity.TfsUserMap; import com.microsoft.gittf.core.identity.UserMap; import com.microsoft.gittf.core.interfaces.WorkspaceService; import com.microsoft.gittf.core.tasks.framework.NullTaskProgressMonitor; import com.microsoft.gittf.core.tasks.framework.TaskExecutor; import com.microsoft.gittf.core.tasks.framework.TaskProgressDisplay; import com.microsoft.gittf.core.tasks.framework.TaskProgressMonitor; import com.microsoft.gittf.core.tasks.framework.TaskStatus; import com.microsoft.gittf.core.tasks.pendDiff.PendDifferenceTask; import com.microsoft.gittf.core.tasks.pendDiff.RenameMode; import com.microsoft.gittf.core.util.Check; import com.microsoft.gittf.core.util.CommitUtil; import com.microsoft.gittf.core.util.CommitWalker; import com.microsoft.gittf.core.util.CommitWalker.CommitDelta; import com.microsoft.gittf.core.util.DateUtil; import com.microsoft.gittf.core.util.ObjectIdUtil; import com.microsoft.tfs.core.clients.versioncontrol.VersionControlClient; import com.microsoft.tfs.core.clients.versioncontrol.path.ServerPath; import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Changeset; import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.DeletedState; import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Item; import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.ItemType; import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.RecursionType; import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.WorkItemCheckinInfo; import com.microsoft.tfs.core.clients.versioncontrol.specs.version.LatestVersionSpec; import com.microsoft.tfs.core.clients.workitem.CheckinWorkItemAction; import com.microsoft.tfs.core.clients.workitem.WorkItem; import com.microsoft.tfs.core.clients.workitem.WorkItemClient; import com.microsoft.tfs.util.FileHelpers; /** * The CheckinHeadCommitTask checks in all the changes between HEAD in the * specified repository and the last downloaded/checked in changeset to TFS * */ public class CheckinHeadCommitTask extends WorkspaceTask { /** * Return code meaning that there was nothing to checkin */ public static final int ALREADY_UP_TO_DATE = 1; private static final Log log = LogFactory.getLog(CheckinHeadCommitTask.class); private boolean deep = false; private boolean mentions = false; private AbbreviatedObjectId[] squashCommitIDs = new AbbreviatedObjectId[0]; private WorkItemCheckinInfo[] workItems; private boolean lock = true; private boolean overrideGatedCheckin; private boolean autoSquashMultipleParents; private boolean preview = false; private String comment = null; private String buildDefinition = null; private boolean includeMetaDataInComment = false; private RenameMode renameMode = RenameMode.JUSTFILES; private boolean keepAuthor = false; private String userMapPath = GitTFConstants.GIT_TF_DEFAULT_USER_MAP; private final WorkItemClient witClient; /** * Constructor * * @param repository * the repository to checkin. The repository needs to be configured * to work with Git tf * @param versionControlClient * the version client object to use */ public CheckinHeadCommitTask(final Repository repository, final VersionControlClient versionControlClient, final WorkItemClient witClient) { super(repository, versionControlClient, GitTFConfiguration.loadFrom(repository).getServerPath()); this.witClient = witClient; } /** * Sets the deep option. The default is false. * * @param deep */ public void setDeep(final boolean deep) { this.deep = deep; } /** * Sets the mentions option. The default is false. * * @param mentions */ public void setMentions(final boolean mentions) { this.mentions = mentions; } /** * Sets the commit ids that should be squashed. * * @param squashCommitIDs */ public void setSquashCommitIDs(AbbreviatedObjectId[] squashCommitIDs) { this.squashCommitIDs = (squashCommitIDs == null) ? new AbbreviatedObjectId[0] : squashCommitIDs; } /** * Sets the work item checkin info to associate/resolve work items * * @param workItems */ public void setWorkItemCheckinInfo(WorkItemCheckinInfo[] workItems) { this.workItems = workItems; } /** * Sets whether gated check in build should be overriden or not * * @param overrideGatedCheckin */ public void setOverrideGatedCheckin(boolean overrideGatedCheckin) { this.overrideGatedCheckin = overrideGatedCheckin; } /** * Sets whether we should take a lock on the root folder or not when * checking in. This option is only used in deep checkin and is ignored in * shallow checkin. The default is true. * * @param lock */ public void setLock(boolean lock) { this.lock = lock; } /** * Sets whether the task should automatically figure out a path between the * HEAD commit and the last downloaded/checked in commit. This option is * ignored when checking in shallow mode. The default is false. * * @param autoSquashMultipleParents */ public void setAutoSquash(boolean autoSquashMultipleParents) { this.autoSquashMultipleParents = autoSquashMultipleParents; } /** * Sets whether the task should run only in preview mode * * @param preview */ public void setPreview(boolean preview) { this.preview = preview; } /** * Sets the checkin comment that should be used when creating the changeset * in TFS * * @param comment */ public void setComment(String comment) { this.comment = comment; } /** * Sets the build definition name that should be used if there are multiple * gated build definitions available. * * @param buildDefinition */ public void setBuildDefinition(String buildDefinition) { this.buildDefinition = buildDefinition; } /** * Sets whether the task should include meta data of the commit in the * checkin comment or not. The default is false. * * @param includeMetaDataInComment */ public void setIncludeMetaDataInComment(boolean includeMetaDataInComment) { this.includeMetaDataInComment = includeMetaDataInComment; } /** * Sets the rename mode to use when comparing the commits. The default is * ALL. * * @param renameMode */ public void setRenameMode(RenameMode renameMode) { this.renameMode = renameMode; } /** * Sets whether the task should use commit authors as changeset owners * * @param keepAuthor */ public void setKeepAuthor(final boolean keepAuthor) { this.keepAuthor = keepAuthor; } /** * Sets an absolute or relative path to the user map file * * @param userMapPath */ public void setUserMapPath(final String userMapPath) { this.userMapPath = userMapPath; } @Override public TaskStatus run(final TaskProgressMonitor progressMonitor) { progressMonitor.beginTask(Messages.formatString("CheckinHeadCommitTask.CheckingInToPathFormat", //$NON-NLS-1$ preview ? Messages.getString("CheckinHeadCommitTask.Preview") : "", //$NON-NLS-1$ //$NON-NLS-2$ serverPath), 1, TaskProgressDisplay.DISPLAY_PROGRESS.combine(TaskProgressDisplay.DISPLAY_SUBTASK_DETAIL)); WorkspaceInfo workspaceData = null; UserMap userMap = null; try { /* Create the temporary workspace */ WorkspaceService workspace = null; File workingFolder = null; log.debug("Creating temporary workspace"); workspaceData = createWorkspace(progressMonitor.newSubTask(1), preview); workspace = workspaceData.getWorkspace(); workingFolder = workspaceData.getWorkingFolder(); log.debug("Workspace " + workspace.getName() + " created for the folder " + workingFolder.getAbsolutePath()); int expectedChangesetNumber = -1; /* In deep mode we should always lock the workspace */ if (lock && deep) { log.debug("Locking TFS resource"); final TaskStatus lockStatus = new TaskExecutor(progressMonitor.newSubTask(1)) .execute(new LockTask(workspace, serverPath)); if (!lockStatus.isOK()) { return lockStatus; } } /* * if we are not locking we should attempt to detect if other users * sneaked in a checkin while this checkin is being processed */ else if (!deep) { log.debug("No lock requested. Checking the latest change set."); Changeset[] latestChangesets = versionControlClient.queryHistory(ServerPath.ROOT, LatestVersionSpec.INSTANCE, 0, RecursionType.FULL, null, null, null, 1, false, false, false, false); Check.notNull(latestChangesets, "latestChangesets"); //$NON-NLS-1$ expectedChangesetNumber = latestChangesets[0].getChangesetID() + 1; log.debug("Expected change set number = " + expectedChangesetNumber); } log.debug("Obtaining the HEAD commit in the master barnch."); /* Get the HEAD commit id */ final ObjectId headCommitID = CommitUtil.getMasterHeadCommitID(repository); /* * Retrieve the last bridged changeset and the latest changeset on * the server */ log.debug("Loading change set/commit map"); final ChangesetCommitMap commitMap = new ChangesetCommitMap(repository); final ChangesetCommitDetails lastBridgedChangeset = ChangesetCommitMapUtil .getLastBridgedChangeset(commitMap); final ChangesetCommitDetails latestChangeset = ChangesetCommitMapUtil.getLatestChangeset(commitMap, versionControlClient, serverPath); /* * This is a repository that has been configured and never checked * in to tfs before. We need to validate that the path in tfs either * does not exist or is empty */ if (lastBridgedChangeset == null || lastBridgedChangeset.getChangesetID() < 0) { log.debug( "Firts checking for the new repository. Check that the root folder is ampty or does notexist."); Item[] items = versionControlClient.getItems(serverPath, LatestVersionSpec.INSTANCE, RecursionType.ONE_LEVEL, DeletedState.NON_DELETED, ItemType.ANY).getItems(); if (items != null && items.length > 0) { /* The folder can exist but has to be empty */ if (!(items.length == 1 && ServerPath.equals(items[0].getServerItem(), serverPath))) { return new TaskStatus(TaskStatus.ERROR, Messages.formatString( "CheckinHeadCommitTask.CannotCheckinToANonEmptyFolderFormat", serverPath)); //$NON-NLS-1$ } } } /* * There is a changeset for this path on the server, but it does not * have a corresponding commit in the map. The user must merge with * the latest changeset. */ else if (latestChangeset != null && latestChangeset.getCommitID() == null) { return new TaskStatus(TaskStatus.ERROR, Messages.formatString("CheckinHeadCommitTask.NotFastForwardFormat", //$NON-NLS-1$ Integer.toString(latestChangeset.getChangesetID()))); } /* * The server path does not exist, but we have previously downloaded * some items from it, thus it has been deleted. We cannot proceed. */ else if (latestChangeset == null && lastBridgedChangeset != null) { return new TaskStatus(TaskStatus.ERROR, Messages.formatString("CheckinHeadCommitTask.ServerPathDoesNotExistFormat", //$NON-NLS-1$ serverPath)); } /* * The current HEAD is the latest changeset on the TFS server. * Nothing to do. */ else if (latestChangeset != null && latestChangeset.getCommitID().equals(headCommitID)) { return new TaskStatus(TaskStatus.OK, CheckinHeadCommitTask.ALREADY_UP_TO_DATE); } log.debug("Examining the repository"); progressMonitor.setDetail(Messages.getString("CheckinHeadCommitTask.ExaminingRepository")); //$NON-NLS-1$ log.debug("Building the list of commit sequence we need to checkin"); /* Build the list of commit sequence we need to checkin */ List<CommitDelta> commitsToCheckin = getCommitsToCheckin( latestChangeset != null ? latestChangeset.getCommitID() : null, headCommitID); progressMonitor.setDetail(null); int lastChangesetID = -1; ObjectId lastCommitID = null; boolean anyThingCheckedIn = false; boolean otherUserCheckinDetected = false; log.debug("Number of commits to checkin: " + commitsToCheckin.size()); progressMonitor.setWork(commitsToCheckin.size() * 2); TaskStatus userMapErrorStatus = null; if (keepAuthor) { log.debug("Loading the user map."); userMap = new TfsUserMap(versionControlClient.getConnection(), userMapPath, commitsToCheckin); progressMonitor.setDetail(Messages.getString("CheckinHeadCommitTask.MappingAuthors")); //$NON-NLS-1$ userMap.load(); userMap.check(progressMonitor); userMap.addGitUsers(); userMap.searchTfsUsers(progressMonitor.newSubTask(1)); if (userMap.isChanged() || !userMap.isOK()) { userMap.save(); final String userMapChangedMessage = Messages.formatString( "CheckinHeadCommitTask.UserMapChangedMessageFormat", //$NON-NLS-1$ userMap.getUserMapFile().getPath()); progressMonitor.displayMessage(userMapChangedMessage); if (!userMap.isOK()) { final String incompleteUserMapError = Messages.formatString( "CheckinHeadCommitTask.IncompleteUserMapFormat", //$NON-NLS-1$ userMap.getUserMapFile().getPath()); userMapErrorStatus = new TaskStatus(TaskStatus.ERROR, incompleteUserMapError); progressMonitor.worked(0); if (!preview) { return userMapErrorStatus; } } } log.debug("The user map is loaded."); progressMonitor.setDetail(null); } log.debug("Processing commit deltas."); /* * Loop the list of commit sequence and checkin the difference one * by one */ for (int i = 0; i < commitsToCheckin.size(); i++) { CommitDelta commitDelta = commitsToCheckin.get(i); log.debug("Committing delta " + i + ": from " + (commitDelta.getFromCommit() == null ? "initial commit" : commitDelta.getFromCommit().getName()) + " to " + commitDelta.getToCommit().getName()); progressMonitor.setDetail(Messages.formatString("CheckinHeadCommitTask.CommitFormat", //$NON-NLS-1$ ObjectIdUtil.abbreviate(repository, commitDelta.getToCommit()))); boolean isLastCommit = (i == (commitsToCheckin.size() - 1)); /* Save space: clean working folder after each checkin */ if (i > 0) { log.debug("Cleaning the working folder" + workingFolder.getAbsolutePath()); cleanWorkingFolder(workingFolder); } /* Pend the differences between the two commits */ final PendDifferenceTask pendTask = new PendDifferenceTask(repository, commitDelta.getFromCommit(), commitDelta.getToCommit(), workspace, serverPath, workingFolder); pendTask.setRenameMode(renameMode); pendTask.validate(); /* If this is preview mode, display the commit details HEADER */ if (preview) { if (i == 0) { progressMonitor .displayMessage(Messages.getString("CheckinHeadCommitTask.CheckedInPreview")); //$NON-NLS-1$ progressMonitor.displayMessage(""); //$NON-NLS-1$ } ObjectId fromCommit = commitDelta.getFromCommit(); ObjectId toCommit = commitDelta.getToCommit(); if (fromCommit == null || fromCommit == ObjectId.zeroId() || commitsToCheckin.size() != 1) { progressMonitor.displayMessage( Messages.formatString("CheckinHeadCommitTask.CheckedInPreviewSingleCommitFormat", //$NON-NLS-1$ i + 1, ObjectIdUtil.abbreviate(repository, toCommit))); } else { progressMonitor.displayMessage(Messages.formatString( "CheckinHeadCommitTask.CheckedInPreviewDifferenceCommitsFormat", //$NON-NLS-1$ i + 1, ObjectIdUtil.abbreviate(repository, toCommit), ObjectIdUtil.abbreviate(repository, fromCommit))); } String checkinComment = comment == null ? buildCommitComment(commitDelta) : comment; progressMonitor.displayMessage(""); //$NON-NLS-1$ progressMonitor.displayMessage(indentString(checkinComment)); progressMonitor.displayMessage( Messages.getString("CheckinHeadCommitTask.CheckedInPreviewTableHeader")); //$NON-NLS-1$ progressMonitor.displayMessage( "---------------------------------------------------------------------"); //$NON-NLS-1$ } log.debug("Pend the differences between the two commits."); final TaskStatus pendStatus = new TaskExecutor(progressMonitor.newSubTask(1)).execute(pendTask); if (!pendStatus.isOK()) { return pendStatus; } if (pendStatus.getCode() == PendDifferenceTask.NOTHING_TO_PEND) { continue; } anyThingCheckedIn = true; /* If this is preview mode, display the commit details FOOTER */ if (preview) { progressMonitor.displayMessage( "---------------------------------------------------------------------"); //$NON-NLS-1$ progressMonitor.displayMessage(""); //$NON-NLS-1$ } /* else perform the actual checkin */ else { log.debug("Checking in."); progressMonitor.setDetail(Messages.getString("CheckinHeadCommitTask.CheckingIn")); //$NON-NLS-1$ log.debug("Building the change set comment."); String commitComment = buildCommitComment(commitDelta); /* * Perform the checkin using the checkin pending changes * task */ final CheckinPendingChangesTask checkinTask = new CheckinPendingChangesTask(repository, commitDelta.getToCommit(), comment == null ? commitComment : comment, versionControlClient, workspace, pendTask.getPendingChanges()); checkinTask.setWorkItemCheckinInfo( getWorkItems(progressMonitor.newSubTask(1), commitComment, isLastCommit)); checkinTask.setOverrideGatedCheckin(overrideGatedCheckin); checkinTask.setBuildDefinition(buildDefinition); checkinTask.setExpectedChangesetNumber(expectedChangesetNumber); checkinTask.setUserMap(userMap); log.debug("Staring the check-in task."); final TaskStatus checkinStatus = new TaskExecutor(progressMonitor.newSubTask(1)) .execute(checkinTask); log.debug("The check-in task ended with the status code: " + checkinStatus.getCode()); if (!checkinStatus.isOK()) { return checkinStatus; } lastChangesetID = checkinTask.getChangesetID(); lastCommitID = commitDelta.getToCommit(); otherUserCheckinDetected = checkinStatus .getCode() == CheckinPendingChangesTask.CHANGESET_NUMBER_NOT_AS_EXPECTED; log.debug(" lastChangesetID: " + lastChangesetID); log.debug(" lastCommitID: " + lastCommitID); log.debug(" otherUserCheckinDetected: " + otherUserCheckinDetected); expectedChangesetNumber = -1; progressMonitor .displayVerbose(Messages.formatString("CheckinHeadCommitTask.CheckedInChangesetFormat", //$NON-NLS-1$ ObjectIdUtil.abbreviate(repository, lastCommitID), Integer.toString(checkinTask.getChangesetID()))); } } log.debug("Cleaning up the workspace."); /* Clean up the workspace */ final TaskProgressMonitor cleanupMonitor = progressMonitor.newSubTask(1); cleanupWorkspace(cleanupMonitor, workspaceData); workspaceData = null; if (userMapErrorStatus != null) { return userMapErrorStatus; } progressMonitor.endTask(); /* Display checkin results for the user */ if (!preview) { // There was nothing detected to checkin. if (!anyThingCheckedIn) { return new TaskStatus(TaskStatus.OK, CheckinHeadCommitTask.ALREADY_UP_TO_DATE); } if (commitsToCheckin.size() == 1) { progressMonitor.displayMessage(Messages.formatString("CheckinHeadCommitTask.CheckedInFormat", //$NON-NLS-1$ ObjectIdUtil.abbreviate(repository, lastCommitID), Integer.toString(lastChangesetID))); } else { progressMonitor .displayMessage(Messages.formatString("CheckinHeadCommitTask.CheckedInMultipleFormat", //$NON-NLS-1$ Integer.toString(commitsToCheckin.size()), Integer.toString(lastChangesetID))); } if (otherUserCheckinDetected) { progressMonitor .displayWarning(Messages.getString("CheckinHeadCommitTask.OtherUserCheckinDetected")); //$NON-NLS-1$ } } return TaskStatus.OK_STATUS; } catch (Exception e) { return new TaskStatus(TaskStatus.ERROR, e); } finally { if (workspaceData != null) { cleanupWorkspace(new NullTaskProgressMonitor(), workspaceData); } } } /** * Builds the commit comment to use when checking in * * @param commitDelta * @return */ private String buildCommitComment(CommitDelta commitDelta) { /* In deep mode the task will use the ToCommit message */ if (deep) { /* * If the meta data flag was set to true, then build the meta data * string */ if (includeMetaDataInComment) { return buildCommitComment(commitDelta.getToCommit()); } /* Otherwise just use the full message */ else { return commitDelta.getToCommit().getFullMessage(); } } /* * In Shallow mode use the log command to identify all the commit * included in the delta */ try { /* * TODO: Need to replace this code to use better logic to figure out * the included commits topologically and not chronologically */ LogCommand logCommand = new Git(repository).log(); logCommand.addRange(commitDelta.getFromCommit().getId(), commitDelta.getToCommit().getId()); logCommand.setMaxCount(OutputConstants.DEFAULT_MAXCOMMENTROLLUP + 1); Iterable<RevCommit> commits = logCommand.call(); int commitCounter = 0; StringBuilder comment = new StringBuilder(); comment.append(Messages.formatString("CheckinHeadCommitTask.ShallowCheckinRollupFormat", //$NON-NLS-1$ ObjectIdUtil.abbreviate(repository, commitDelta.getToCommit().getId()), ObjectIdUtil.abbreviate(repository, commitDelta.getFromCommit().getId())) + OutputConstants.NEW_LINE); comment.append(OutputConstants.NEW_LINE); for (RevCommit commit : commits) { commitCounter++; if (commitCounter > OutputConstants.DEFAULT_MAXCOMMENTROLLUP) { comment.append(Messages.formatString( "CheckinHeadCommitTask.ShallowCheckinCommentDisplayTruncatedFormat", //$NON-NLS-1$ OutputConstants.DEFAULT_MAXCOMMENTROLLUP, ObjectIdUtil.abbreviate(repository, commit.getId()), ObjectIdUtil.abbreviate(repository, commitDelta.getFromCommit().getId()))); break; } comment.append(buildCommitComment(commit) + OutputConstants.NEW_LINE); } if (commitCounter == 1) { return buildCommitComment(commitDelta.getToCommit()); } return comment.toString(); } catch (Exception e) { // if we fail execute the log command we default to the destination // commit full message return buildCommitComment(commitDelta.getToCommit()); } } /** * Builds the comment for a single commit * * @param commit * @return */ private String buildCommitComment(RevCommit commit) { if (includeMetaDataInComment) { StringBuilder comment = new StringBuilder(); comment.append(Messages.formatString("CheckinHeadCommitTask.ShallowCheckinCommentFormat", //$NON-NLS-1$ ObjectIdUtil.abbreviate(repository, commit.getId()), DateUtil.formatDate(new Date(((long) commit.getCommitTime()) * 1000))) + OutputConstants.NEW_LINE); comment.append(Messages.formatString("CheckinHeadCommitTask.ShallowCheckinCommentAuthorFormat", //$NON-NLS-1$ commit.getAuthorIdent().getName(), commit.getAuthorIdent().getEmailAddress()) + OutputConstants.NEW_LINE); comment.append(Messages.formatString("CheckinHeadCommitTask.ShallowCheckinCommentCommitterFormat", //$NON-NLS-1$ commit.getCommitterIdent().getName(), commit.getCommitterIdent().getEmailAddress()) + OutputConstants.NEW_LINE); comment.append( "-----------------------------------------------------------------" + OutputConstants.NEW_LINE); //$NON-NLS-1$ comment.append(indentString(commit.getFullMessage())); comment.append(OutputConstants.NEW_LINE); return comment.toString(); } else { return commit.getFullMessage(); } } private String indentString(String input) { String[] lines = input.split(OutputConstants.NEW_LINE); StringBuilder sb = new StringBuilder(); for (String line : lines) { sb.append(MessageFormat.format(" {0}{1}", line, OutputConstants.NEW_LINE)); //$NON-NLS-1$ } return sb.toString(); } /** * Gets the sequence of commit differences that need to be checked in * * @param sourceCommitID * @param headCommitID * @return * @throws Exception */ private List<CommitDelta> getCommitsToCheckin(final ObjectId sourceCommitID, final ObjectId headCommitID) throws Exception { log.debug("Detecting commit deltas."); Check.notNull(headCommitID, "headCommitID"); //$NON-NLS-1$ List<CommitDelta> commitsToCheckin; log.debug("Walking thru commit tree."); /* * In the case of shallow commit, we do not care if the user provided * ids to squash or not since we are not preserving history anyways we * select any path we find and that would be ok */ if (autoSquashMultipleParents || !deep) { commitsToCheckin = CommitWalker.getAutoSquashedCommitList(repository, sourceCommitID, headCommitID); } else { commitsToCheckin = CommitWalker.getCommitList(repository, sourceCommitID, headCommitID, squashCommitIDs); } int depth = deep ? Integer.MAX_VALUE : GitTFConstants.GIT_TF_SHALLOW_DEPTH; log.debug("Commit s to check-in number: " + commitsToCheckin.size()); /* Prune the list of commits down to their depth. */ if (commitsToCheckin.size() > depth) { log.debug("Prune commits to the depth: " + depth); List<CommitDelta> prunedCommits = new ArrayList<CommitDelta>(); RevCommit lastToCommit = null; RevCommit lastFromCommit = null; for (int i = 0; i < depth - 1; i++) { CommitDelta delta = commitsToCheckin.get(commitsToCheckin.size() - 1 - i); prunedCommits.add(delta); lastToCommit = delta.getFromCommit(); } lastFromCommit = commitsToCheckin.get(0).getFromCommit(); if (lastToCommit == null) { lastToCommit = commitsToCheckin.get(commitsToCheckin.size() - 1).getToCommit(); } Check.notNull(lastToCommit, "lastToCommit"); //$NON-NLS-1$ prunedCommits.add(new CommitDelta(lastFromCommit, lastToCommit)); commitsToCheckin = prunedCommits; } log.debug("Detection commit deltas finished."); return commitsToCheckin; } private void cleanWorkingFolder(final File workingFolder) { try { FileHelpers.deleteDirectory(workingFolder); workingFolder.mkdirs(); } catch (Exception e) { /* Not fatal */ log.warn(MessageFormat.format("Could not clean up temporary directory {0}", //$NON-NLS-1$ workingFolder.getAbsolutePath()), e); } } private void cleanupWorkspace(final TaskProgressMonitor progressMonitor, final WorkspaceInfo workspaceData) { if (workspaceData == null) { return; } progressMonitor.beginTask(Messages.getString("CheckinHeadCommitTask.DeletingWorkspace"), //$NON-NLS-1$ TaskProgressMonitor.INDETERMINATE, TaskProgressDisplay.DISPLAY_PROGRESS); final WorkspaceService workspace = workspaceData.getWorkspace(); if (workspaceData.getWorkspace() != null && lock && deep) { final TaskStatus unlockStatus = new TaskExecutor(progressMonitor.newSubTask(1)) .execute(new UnlockTask(workspace, serverPath)); if (!unlockStatus.isOK()) { log.warn(MessageFormat.format("Could not unlock {0}: {1}", serverPath, unlockStatus.getMessage())); //$NON-NLS-1$ } } disposeWorkspace(progressMonitor.newSubTask(1)); progressMonitor.endTask(); } /** * Parses the comments of the commits to list the mentioned work items */ private WorkItemCheckinInfo[] getWorkItems(final TaskProgressMonitor progressMonitor, final String commitComment, final boolean isLastCommit) throws Exception { List<WorkItemCheckinInfo> workItemsCheckinInfo = new ArrayList<WorkItemCheckinInfo>(); if (mentions) { final String REGEX = "(\\s|^)#\\d+(\\s|$)(#\\d+(\\s|$))*"; //$NON-NLS-1$ if (commitComment != null && commitComment.length() > 0) { final Pattern pattern = Pattern.compile(REGEX); // get a matcher object final Matcher patternMatcher = pattern.matcher(commitComment); while (patternMatcher.find()) { final String workItemIDREGEX = "#\\d+"; //$NON-NLS-1$ final Pattern workItemIDPattern = Pattern.compile(workItemIDREGEX); final String workItemIDString = commitComment.substring(patternMatcher.start(), patternMatcher.end()); final Matcher workItemIDMatcher = workItemIDPattern.matcher(workItemIDString); while (workItemIDMatcher.find()) { final WorkItem workitem = getWorkItem(progressMonitor, workItemIDString.substring(workItemIDMatcher.start(), workItemIDMatcher.end())); if (workitem != null) { final WorkItemCheckinInfo workItemCheckinInfo = new WorkItemCheckinInfo(workitem, CheckinWorkItemAction.ASSOCIATE); if (!workItemsCheckinInfo.contains(workItemCheckinInfo)) { workItemsCheckinInfo.add(workItemCheckinInfo); } } } } } } if (isLastCommit) { // If there were no work items in the comments if (workItemsCheckinInfo.isEmpty()) { return workItems; } for (final WorkItemCheckinInfo workItem : workItems) { if (!workItemsCheckinInfo.contains(workItem)) { workItemsCheckinInfo.add(workItem); } } } return workItemsCheckinInfo.toArray(new WorkItemCheckinInfo[workItemsCheckinInfo.size()]); } private WorkItem getWorkItem(final TaskProgressMonitor progressMonitor, final String mentionsString) throws Exception { final int id; try { id = Integer.parseInt(mentionsString.replace("#", "")); //$NON-NLS-1$//$NON-NLS-2$ if (id <= 0) { progressMonitor.displayWarning( Messages.formatString("CheckinHeadCommitTask.WorkItemInvalidFormat", mentionsString)); //$NON-NLS-1$ return null; } } catch (final NumberFormatException e) { progressMonitor.displayWarning( Messages.formatString("CheckinHeadCommitTask.WorkItemInvalidFormat", mentionsString)); //$NON-NLS-1$ return null; } final WorkItem workItem = witClient.getWorkItemByID(id); if (workItem == null) { progressMonitor .displayWarning(Messages.formatString("CheckinHeadCommitTask.WorkItemDoesNotExistFormat", id)); //$NON-NLS-1$ } return workItem; } }