package com.perforce.p4simulink; /** * Copyright (C) 2014 Perforce Software. All rights reserved. * * Please see LICENSE.txt in top-level folder of this distribution. */ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Paths; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JOptionPane; import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.mathworks.cmlink.api.AdapterSupportedFeature; import com.mathworks.cmlink.api.ApplicationInteractor; import com.mathworks.cmlink.api.ConfigurationManagementException; import com.mathworks.cmlink.api.FileState; import com.mathworks.cmlink.api.IntegerRevision; import com.mathworks.cmlink.api.LocalStatus; import com.mathworks.cmlink.api.Revision; import com.mathworks.cmlink.api.customization.CoreAction; import com.mathworks.cmlink.api.customization.CustomizationWidgetFactory; import com.mathworks.cmlink.api.version.r14a.CMAdapter; import com.perforce.p4java.core.ChangelistStatus; import com.perforce.p4java.core.IChangelist; import com.perforce.p4java.core.IChangelistSummary; import com.perforce.p4java.core.ILabelSummary; import com.perforce.p4java.core.file.FileAction; import com.perforce.p4java.core.file.FileSpecBuilder; import com.perforce.p4java.core.file.FileSpecOpStatus; import com.perforce.p4java.core.file.IExtendedFileSpec; import com.perforce.p4java.core.file.IFileRevisionData; import com.perforce.p4java.core.file.IFileSpec; import com.perforce.p4java.exception.P4JavaException; import com.perforce.p4java.impl.generic.core.Changelist; import com.perforce.p4java.impl.generic.core.file.FileSpec; import com.perforce.p4java.impl.mapbased.server.Server; import com.perforce.p4java.option.client.AddFilesOptions; import com.perforce.p4java.option.client.DeleteFilesOptions; import com.perforce.p4java.option.client.EditFilesOptions; import com.perforce.p4java.option.client.GetDiffFilesOptions; import com.perforce.p4java.option.client.ReconcileFilesOptions; import com.perforce.p4java.option.client.ReopenFilesOptions; import com.perforce.p4java.option.client.ResolveFilesAutoOptions; import com.perforce.p4java.option.client.RevertFilesOptions; import com.perforce.p4java.option.client.SyncOptions; import com.perforce.p4java.option.server.GetChangelistsOptions; import com.perforce.p4java.option.server.GetExtendedFilesOptions; import com.perforce.p4java.option.server.GetFileContentsOptions; import com.perforce.p4java.option.server.GetRevisionHistoryOptions; import com.perforce.p4java.option.server.MoveFileOptions; import com.perforce.p4java.option.server.OpenedFilesOptions; import com.perforce.p4java.option.server.TagFilesOptions; import com.perforce.p4java.server.IOptionsServer; import com.perforce.p4simulink.connection.Config; import com.perforce.p4simulink.ui.P4ChangelistSelector; import com.perforce.p4simulink.ui.P4IgnoreDialogue; import com.perforce.p4simulink.util.Files; import com.perforce.p4simulink.util.Logging; import com.perforce.p4simulink.workspace.P4FileState; public class P4Adapter extends P4Interactor implements CMAdapter { private static final Logger log = LogManager.getLogger(P4Adapter.class); private final Collection<AdapterSupportedFeature> supportedFeatures; private IChangelist currentChangelist; public P4Adapter(ApplicationInteractor applicationInteractor, Config config) throws P4CMException { super(applicationInteractor, config); currentChangelist = null; supportedFeatures = EnumSet.of( AdapterSupportedFeature.FOLDERS_NOT_STORED, AdapterSupportedFeature.ALTERNATE_UPDATE, AdapterSupportedFeature.LOCK, AdapterSupportedFeature.GET_REVISION, AdapterSupportedFeature.IS_LATEST, AdapterSupportedFeature.LATEST_REVISION, AdapterSupportedFeature.RESOLVE, AdapterSupportedFeature.TAG, AdapterSupportedFeature.MOVE, AdapterSupportedFeature.GET_CONFLICT_REVISION); try { getClient(); } catch (Exception e) { // ignore } } @Override public void buildCustomActions(CustomizationWidgetFactory widgetFactory) { // include buttons defined in the interactor super.buildCustomActions(widgetFactory); // create button which will allow the user to enter their password and // re-login widgetFactory.createActionWidget("Re-connect to Perforce", null, new CoreAction() { @Override public void execute() throws ConfigurationManagementException { showPasswordDialogue(""); if (!login()) { throw new ConfigurationManagementException( "Error logging in to Perforce. Please check your connection settings and password."); } } @Override public String getDescription() { return "Re-connect to Perforce"; } @Override public boolean canCancel() { return true; } }); // create a button which will allow the user to manage .p4ignore widgetFactory.createActionWidget("Manage .p4ignore", null, new CoreAction() { @Override public void execute() throws ConfigurationManagementException { File ignoreFile = new File(sandboxRoot .getAbsolutePath() + File.separator + ".p4ignore"); String contents = ""; if (ignoreFile.exists() && ignoreFile.canRead()) { try { byte[] fileContents = java.nio.file.Files .readAllBytes(Paths.get(ignoreFile .getAbsolutePath())); contents = new String(fileContents, "utf-8"); } catch (IOException e) { contents = ""; } } P4IgnoreDialogue pane = new P4IgnoreDialogue(contents, ignoreFile); int option = JOptionPane.showConfirmDialog( applicationInteractor.getParentFrame(), pane.getFields(), "Edit .p4ignore", JOptionPane.OK_CANCEL_OPTION); if (option == JOptionPane.CANCEL_OPTION) { // user cancelled the operation, so don't do // anything return; } if (pane.save() == P4IgnoreDialogue.UNSAVED) { // couldn't save the file for whatever reason... throw new P4CMException( "Couldn't save P4IGNORE file: " + ignoreFile.getAbsolutePath()); } } @Override public String getDescription() { return "Manage .p4ignore"; } @Override public boolean canCancel() { return true; } }); // create a button which will allow the user to select the current // changelist widgetFactory.createActionWidget("Current Changelist", null, new CoreAction() { @Override public void execute() throws ConfigurationManagementException { // grab the list of current changes Vector<String> changelists = new Vector<>(); IChangelist current = hasCurrentChangelist() ? getCurrentChangelist() : null; // no client or server connection, so we bail if (client == null || server == null || sandboxRoot == null) { throw new P4CMException( "No client connection. Please try re-connecting to Perforce."); } try { GetChangelistsOptions options = new GetChangelistsOptions(); options.setClientName(client.getName()) .setType(IChangelist.Type.PENDING) .setUserName(config.getUser()); List<IChangelistSummary> serverList = server .getChangelists(toSpec(sandboxRoot), options); for (IChangelistSummary changelist : serverList) { changelists.add("" + changelist.getId()); } } catch (P4JavaException e) { } P4ChangelistSelector pane = new P4ChangelistSelector( changelists, "" + (current == null ? "New" : current .getId()), server); int option = JOptionPane.showConfirmDialog( applicationInteractor.getParentFrame(), pane.getFields(), "Select Current Changelist", JOptionPane.OK_CANCEL_OPTION); if (option == JOptionPane.CANCEL_OPTION) { // user cancelled the operation, so don't do // anything return; } if (pane.selectedNew()) { currentChangelist = buildChangelist(); } else { String selected = pane.getChangelistId(); try { currentChangelist = server .getChangelist(Integer .parseInt(selected)); } catch (Exception e) { // ? } } } @Override public String getDescription() { return "Current Changelist"; } @Override public boolean canCancel() { return true; } }); } public boolean login() throws ConfigurationManagementException { return login(""); } public boolean login(String password) throws ConfigurationManagementException { if (!isReady()) { connect(); } try { if (server.getServerInfo().isUnicodeEnabled()) { server.setCharsetName("utf8"); } switch (config.getAuthType()) { case PASSWORD: String status = server.getLoginStatus(); if (!status.contains("not necessary")) { String pass = (password != null && !password.trim() .isEmpty()) ? password : config.getPassword(); try { server.login(pass); } catch (P4JavaException e) { // exceptions are thrown when the password is invalid, // so swallow it } } break; case TICKETPATH: server.setTicketsFilePath(config.getTicketPath()); break; default: log.error("Unknown Authorization Type: " + config.getAuthType()); return false; } String status = server.getLoginStatus(); if (status.contains("not necessary")) { return true; } if (status.contains("ticket expires in")) { return true; } // If there is a broker or something else that swallows the message if (status.isEmpty()) { return true; } // if we got here, we could not log in return false; } catch (P4JavaException e) { log.error(e.getLocalizedMessage()); } return false; } @Override public boolean isFeatureSupported( AdapterSupportedFeature adapterSupportedFeature) { return supportedFeatures.contains(adapterSupportedFeature); } @Override public String getRepositorySpecifier(File sandboxDirectory) throws ConfigurationManagementException { return depotPath; } /** * Moves file to file2 within the depot. This is used in renaming operations * within Simulink. * * @param file * Source file * @param file2 * Destination file * @throws ConfigurationManagementException */ @Override public void moveFile(File file, File file2) throws ConfigurationManagementException { log.debug("moveFile() {} {}", file.toString(), file2.toString()); IFileSpec spec = new FileSpec(file.getAbsolutePath()); IFileSpec spec2 = new FileSpec(file2.getAbsolutePath()); try { IChangelist changelist = getCurrentChangelist(); // ensure file is opened for add or edit if (spec.getAction() != FileAction.ADD && spec.getAction() != FileAction.EDIT) { List<IFileSpec> files = new ArrayList<>(); files.add(spec); List<IFileSpec> edited = client.editFiles(files, new EditFilesOptions().setChangelistId(changelist .getId())); Files.validateFileSpecs(edited); } List<IFileSpec> moved = getServer().moveFile(spec, spec2, new MoveFileOptions().setChangelistId(changelist.getId())); Files.validateFileSpecs(moved); } catch (Exception e) { if (e instanceof P4JavaException) { Logging.logException(log, new P4JavaException(e), true); } } } /** * Submits the current changelist with the given description. * * @param description * Description for this commit. * @throws ConfigurationManagementException */ public void submitCurrent(String description) throws ConfigurationManagementException { if (!hasCurrentChangelist()) { return; } try { IChangelist changelist = getCurrentChangelist(); if (description != null) { changelist.setDescription(description); changelist.update(); } List<IFileSpec> submitted = changelist.submit(null); // detect if there are merges still pending boolean mergesPending = false; for (IFileSpec spec : submitted) { if (spec.getOpStatus() == FileSpecOpStatus.INFO) { String message = spec.getStatusMessage(); if (message.contains(" - must resolve ")) { mergesPending = true; break; } } } // resolve pending merges, if present, and attempt to re-submit if (mergesPending) { ResolveFilesAutoOptions resolveOptions = new ResolveFilesAutoOptions(); resolveOptions.setChangelistId(changelist.getId()); resolveOptions.setAcceptYours(true); resolveOptions.setForceResolve(true); client.resolveFilesAuto(changelist.getFiles(false), resolveOptions); submitted = changelist.submit(null); } Files.validateFileSpecs(submitted, "Submitted as change", "No files to submit"); resetCurrentChangelist(); } catch (P4JavaException e) { Logging.logException(log, e); } catch (ConfigurationManagementException e) { Logging.logException(log, e, true); } catch (Exception e) { Logging.logException(log, new ConfigurationManagementException(e), true); } } /** * Submits the current changelist with an empty description. * * @throws ConfigurationManagementException */ public void submitCurrent() throws ConfigurationManagementException { submitCurrent(""); } /** * Adds a list of files to the depot. Note that this is called when Simulink * wants to add files to the project. The files are not submitted to the * depot until checkin is called. * * @param files * list of files to add to the depot * @throws ConfigurationManagementException */ @Override public void add(Collection<File> files) throws ConfigurationManagementException { log.debug("add()"); log.debug(files); List<IFileSpec> specs = toSpec(files); try { IChangelist changelist = getCurrentChangelist(); if (!specs.isEmpty()) { AddFilesOptions opts = new AddFilesOptions(); opts.setChangelistId(changelist.getId()); List<IFileSpec> added = client.addFiles(specs, opts); Files.validateFileSpecs(added, "empty, assuming text", "can't add existing file"); changelist.setDescription("Opening files for add."); } } catch (P4JavaException e) { Logging.logException(log, e, true); } catch (Exception e) { Logging.logException(log, new ConfigurationManagementException(e), true); } } /** * Convenience method that opens files in Perforce for edit. * * @param files * Files to edit * @throws ConfigurationManagementException */ private void edit(Collection<File> files) throws ConfigurationManagementException { log.debug("edit()"); log.debug(files); List<IFileSpec> specs = toSpec(files); try { IChangelist changelist = getCurrentChangelist(); if (!specs.isEmpty()) { EditFilesOptions opts = new EditFilesOptions(); opts.setChangelistId(changelist.getId()); List<IFileSpec> edited = client.editFiles(specs, opts); Files.validateFileSpecs(edited, "empty, assuming text", " - currently opened for edit", " - file(s) not on client."); // files may already be open in a previous changelist - move // them to this one client.reopenFiles(specs, new ReopenFilesOptions() .setChangelistId(changelist.getId())); changelist.setDescription("Opening files for edit."); } } catch (P4JavaException e) { Logging.logException(log, e, true); } catch (Exception e) { Logging.logException(log, new ConfigurationManagementException(e), true); } } /** * Removes files from source control. Note that it does not delete the files * from disk, it only removes the file from source control and the project * from within Simulink. * * @param files * List of files to remove from source control. * @throws ConfigurationManagementException */ @Override public void remove(Collection<File> files) throws ConfigurationManagementException { log.debug("remove()"); log.debug(files); List<IFileSpec> specs = toSpec(files); try { IChangelist changelist = getCurrentChangelist(); DeleteFilesOptions options = new DeleteFilesOptions(); options.setChangelistId(changelist.getId()); options.setBypassClientDelete(true); List<IFileSpec> deleted = client.deleteFiles(specs, options); Files.validateFileSpecs(deleted, "files(s) not on client", "clobber writable"); } catch (P4JavaException e) { Logging.logException(log, e, true); } catch (Exception e) { Logging.logException(log, new ConfigurationManagementException(e), true); } } /** * Convenience method for checking out a list of IFileSpec objects, instead * of java.io.File objects. * * @param specs * List of IFileSpec objects to sync * @throws ConfigurationManagementException */ public void checkoutBySpec(List<IFileSpec> specs) throws ConfigurationManagementException { try { // basic sync SyncOptions options = new SyncOptions(); List<IFileSpec> synced = client.sync(specs, options); // ignore cases where we've already got the latest, and/or there are // no files to check out Files.validateFileSpecs(synced, "file(s) up-to-date.", " - no such file(s).", "- file(s) not on client."); // now check for any missing files and force sync them List<IFileSpec> missing = client.getDiffFiles(specs, new GetDiffFilesOptions().setUnopenedMissing(true)); if (!missing.isEmpty()) { options = new SyncOptions().setForceUpdate(true); synced = client.sync(specs, options); // ignore cases where we've already got the latest, and/or there // are no files to check out Files.validateFileSpecs(synced, "file(s) up-to-date.", " - no such file(s).", "- file(s) not on client."); } } catch (P4JavaException e) { Logging.logException(log, e, true); } catch (Exception e) { Logging.logException(log, new ConfigurationManagementException(e), true); } } /** * 'checkout' operation, essentially a p4 'sync -f' operation. This is only * called in Simulink when a project is opened from source control. * * @param files * Files to check out of source control (sync and open for edit). * @throws ConfigurationManagementException */ @Override public void checkout(Collection<File> files) throws ConfigurationManagementException { log.debug("checkout()"); log.debug(files); try { // sync the latest files down checkoutBySpec(toSpec(files)); // mark everything for edit edit(files); } catch (ConfigurationManagementException e) { Logging.logException(log, e, true); } } /** * Changelist management for the current changelist. * * @return true if we currently have a changelist we're working on, false * otherwise */ private boolean hasCurrentChangelist() { return currentChangelist != null && currentChangelist.getStatus() != ChangelistStatus.SUBMITTED; } /** * Gets the current changelist if one is already set. If not, it creates one * and returns that. * * @return current changelist * @throws ConfigurationManagementException */ private IChangelist getCurrentChangelist() throws ConfigurationManagementException { if (!hasCurrentChangelist()) { currentChangelist = buildChangelist(); } return currentChangelist; } private void resetCurrentChangelist() { currentChangelist = null; } /** * Reverts any local changes via p4 'revert'. * * @param files * List of files to revert. * @throws ConfigurationManagementException */ @Override public void uncheckout(Collection<File> files) throws ConfigurationManagementException { log.debug("uncheckout()"); log.debug(files); List<IFileSpec> specs = toSpec(files); try { IChangelist changelist = getCurrentChangelist(); RevertFilesOptions options = new RevertFilesOptions(); options.setChangelistId(changelist.getId()); List<IFileSpec> reverted = client.revertFiles(specs, options); Files.validateFileSpecs(reverted); } catch (P4JavaException e) { Logging.logException(log, e, true); } catch (Exception e) { Logging.logException(log, new ConfigurationManagementException(e), true); } } /** * Method for use in tests. Performs a forced sync (sync -f) followed by a * 'have' to get the list of local files. Note that this refreshes the * entire depot locally. * * @return List of specs for all files contained in the depot. * @throws ConfigurationManagementException */ public List<IFileSpec> getDepotFiles() throws ConfigurationManagementException { return getDepotFiles(""); } /** * Method for use in tests. Performs a forced sync (sync -f) followed by a * 'have' to get the list of local files. Note: This should NOT be called * outside of tests. * * @param path * Depot path from which files should be retrieved. * @return List of specs for all files contained in given path. * @throws ConfigurationManagementException */ public List<IFileSpec> getDepotFiles(String path) throws ConfigurationManagementException { List<IFileSpec> specs = new ArrayList<>(); if (path == null || path.isEmpty()) { path = "//..."; } try { specs = FileSpecBuilder.makeFileSpecList(path); client.sync(specs, new SyncOptions().setForceUpdate(true)); return client.haveList(specs); } catch (P4JavaException e) { Logging.logException(log, e, true); } return specs; } /** * Performs a sync and returns the list of files from the depot. * * @param files * List of files for which we want to sync and return information * about * @return List of specs for files which are in the depot * @throws ConfigurationManagementException */ public List<IFileSpec> getDepotFiles(Collection<File> files) throws ConfigurationManagementException { List<IFileSpec> specs = toSpec(files); try { client.sync(specs, new SyncOptions().setForceUpdate(true)); return client.haveList(specs); } catch (P4JavaException e) { Logging.logException(log, e, true); } return specs; } /** * Convenience method for building a new changelist. * * @return New changelist * @throws ConfigurationManagementException */ private IChangelist buildChangelist() throws ConfigurationManagementException { return buildChangelist("P4 Simulink."); } /** * Convenience method for building a new changelist. * * @param description * Description for the new changelist * @return New changelist * @throws ConfigurationManagementException */ private IChangelist buildChangelist(String description) throws ConfigurationManagementException { try { IOptionsServer server = getServer(); Changelist changelistImpl = new Changelist(IChangelist.UNKNOWN, client.getName(), config.getUser(), ChangelistStatus.NEW, new Date(), description, false, (Server) server); IChangelist changelist = client.createChangelist(changelistImpl); if (changelist == null) { throw new ConfigurationManagementException( "Failed to create changelist."); } changelist.setDescription(description); return changelist; } catch (P4JavaException e) { Logging.logException(log, e, true); } return null; } /** * Checks in the list of files. This amounts to calling a Perforce 'submit' * on the given files,with the given description. * * @param files * Files to submit to Perforce * @param description * Description for this commit. * @throws com.mathworks.cmlink.api.ConfigurationManagementException */ @Override public void checkin(Collection<File> files, String description) throws ConfigurationManagementException { log.debug("checkin() - Collection Files: {} {}", description, files.toString()); try { // no files to submit, so don't do any work Map<File, FileState> states = getFileState(files); if (states.isEmpty()) { return; } // get a new changelist to perform our submit on description = description != null ? description : "Submitting files from Simulink."; IChangelist changelist = buildChangelist(description); changelist.setDate(new Date()); changelist.setUsername(config.getUser()); // files are already open in the current changelist, so move them // over List<File> toReopen = new ArrayList<>(); List<File> toAdd = new ArrayList<>(); List<File> toDelete = new ArrayList<>(); List<File> toEdit = new ArrayList<>(); Map<File, FileState> opened = getOpenedState(toSpec(files)); for (File file : states.keySet()) { FileState state = states.get(file); LocalStatus status = state == null ? null : state .getLocalStatus(); if (status != null && opened.containsKey(file)) { toReopen.add(file); } else { if (status == LocalStatus.ADDED) { toAdd.add(file); } else if (status == LocalStatus.DELETED) { toDelete.add(file); } else if (status == LocalStatus.MODIFIED) { toEdit.add(file); } else if (status == LocalStatus.CONFLICTED) { throw new P4CMException("Resolve conflict(s) on " + file.getAbsolutePath() + " before submitting."); } } } if (!toReopen.isEmpty()) { List<IFileSpec> reopened = client.reopenFiles(toSpec(toReopen), new ReopenFilesOptions().setChangelistId(changelist .getId())); Files.validateFileSpecs(reopened, "empty, assuming text", "can't add existing file"); } if (!toAdd.isEmpty()) { client.addFiles(toSpec(toAdd), new AddFilesOptions() .setChangelistId(changelist.getId())); } if (!toEdit.isEmpty()) { client.editFiles(toSpec(toEdit), new EditFilesOptions() .setChangelistId(changelist.getId())); } if (!toDelete.isEmpty()) { client.deleteFiles(toSpec(toDelete), new DeleteFilesOptions() .setChangelistId(changelist.getId())); } changelist.update(); List<IFileSpec> submitted = changelist.submit(null); Files.validateFileSpecs(submitted, "Submitted as change"); // check if there was a problem (e.g. conflicted files) if (changelist.getStatus() == ChangelistStatus.PENDING) { throw new ConfigurationManagementException( "Could not submit changelist!"); } } catch (P4JavaException e) { Logging.logException(log, e, true); e.printStackTrace(); } catch (ConfigurationManagementException e) { Logging.logException(log, e, true); e.printStackTrace(); } catch (Exception e) { Logging.logException(log, new ConfigurationManagementException(e), true); e.printStackTrace(); } } /** * Check in a single file with the given description. * * @param file * File to submit to Perforce. * @param description * Description for this commit. * @throws ConfigurationManagementException */ @Override public void checkin(File file, String description) throws ConfigurationManagementException { log.debug("checkin() - Single File: {} {}", description, file.toString()); try { checkin(Collections.singletonList(file), description); } catch (ConfigurationManagementException e) { Logging.logException(log, e, true); } } /** * Get the latest revisions of all files in the list. Performs a Perforce * sync. * * @param files * files for which we want the latest version * @throws ConfigurationManagementException */ @Override public void getLatest(Collection<File> files) throws ConfigurationManagementException { log.debug("getLatest()"); log.debug(files); checkoutBySpec(toSpec(files)); } /** * Performs a sync on the entire workspace. * * @throws ConfigurationManagementException */ public void getLatest() throws ConfigurationManagementException { log.debug("getLatest() [all files]"); checkoutBySpec(toSpec(Collections.singletonList(new File(client .getRoot())))); } /** * Determines whether the user's workspace contains the latest version of * files from the depot. * * @param files * list of files to check * @return Mapping of file to boolean where true means the workspace has the * latest version, false if not * @throws ConfigurationManagementException */ @Override public Map<File, Boolean> isLatest(Collection<File> files) throws ConfigurationManagementException { log.debug("isLatest()"); log.debug(files); // @todo: determine if this is truly necessary - with large numbers of // files, I was getting exceptions if (files.size() > 20) { files = Collections.singletonList(new File(client.getRoot())); } Map<File, Boolean> isLatest = new HashMap<>(); // no work if no files were asked for if (files.size() == 0) { return isLatest; } // get local file state, marking any modified/missing or out of date // files as not latest Map<File, FileState> states = getFileState(files); for (File file : states.keySet()) { FileState state = states.get(file); isLatest.put(file, !state.getRevision().getStringRepresentation() .contains("/") && state.getLocalStatus() == LocalStatus.UNMODIFIED); } return isLatest; } /** * Get the latest version of the file from the depot. * * @param file * File for which we want the latest version. * @throws ConfigurationManagementException */ @Override public void update(File file) throws ConfigurationManagementException { log.debug("update()"); log.debug(file.getAbsoluteFile()); getLatest(Collections.singletonList(file)); } /** * Check out a list of files with specific revisions for each. * * @param fileRevisionMap * List of files and their specified revisions to be synced. * @throws ConfigurationManagementException */ @Override public void getRevision(Map<File, Revision> fileRevisionMap) throws ConfigurationManagementException { log.debug("getRevision()"); log.debug(fileRevisionMap); try { List<IFileSpec> files = new ArrayList<>(); for (File file : fileRevisionMap.keySet()) { String revision = fileRevisionMap.get(file) .getStringRepresentation(); // fix up filenames that might pooch revision specifications String filename = file.getAbsolutePath(); if (filename.contains("#") || filename.contains("@") || filename.contains("%")) { filename = filename.replace("%", "%25"); filename = filename.replace("@", "%40").replace("#", "%23"); } List<IFileSpec> spec = FileSpecBuilder .makeFileSpecList(filename); if (!spec.isEmpty()) { IFileSpec fileSpec = spec.get(0); int revisionNumber = Integer.parseInt(revision); fileSpec.setEndRevision(revisionNumber); fileSpec.setStartToRev(revisionNumber); fileSpec.setWorkRev(revisionNumber); fileSpec.setStartFromRev(revisionNumber); fileSpec.setEndFromRev(revisionNumber); fileSpec.setStartToRev(revisionNumber); fileSpec.setEndToRev(revisionNumber); fileSpec.setStartRevision(revisionNumber); fileSpec.setBaseRev(revisionNumber); files.add(fileSpec); } } List<IFileSpec> synced = client.sync(files, new SyncOptions().setForceUpdate(true)); Files.validateFileSpecs(synced, "file(s) up-to-date."); } catch (P4JavaException e) { Logging.logException(log, e, true); } catch (Exception e) { Logging.logException(log, new ConfigurationManagementException(e), true); } } /** * From a collection of files, look in the workspace and determine a file's * state. This means running: "p4 have", "p4 reconcile" and "p4 opened" to * determine a file's state. * * @param files * List of files for which we want the current state * @return List of files and their associated state * @throws ConfigurationManagementException */ @Override public Map<File, FileState> getFileState(Collection<File> files) throws ConfigurationManagementException { log.debug("getFileState - {}", files.toString()); Map<File, FileState> fileState = new HashMap<>(); // @todo: determine if this is truly necessary - with large numbers of // files, I was getting exceptions if (files.size() > 20) { files = Collections.singletonList(new File(client.getRoot())); } try { List<IFileSpec> specs = toSpec(files); fileState.putAll(getHaveState(specs)); fileState.putAll(getWorkspaceState(specs)); fileState.putAll(getOpenedState(specs)); } catch (P4JavaException e) { Logging.logException(log, new ConfigurationManagementException(e), true); } return fileState; } /** * Determine whether a file is not under source control, or whether it * should be ignored. * * @param spec * File spec to check * @return A FileState object that will either denote the spec as ignored or * not under source control * @throws P4JavaException * @throws ConfigurationManagementException */ private P4FileState getIgnoreState(IFileSpec spec) throws P4JavaException, ConfigurationManagementException { IChangelist changelist = getCurrentChangelist(); AddFilesOptions opts = new AddFilesOptions(); opts.setChangelistId(changelist.getId()); opts.setNoUpdate(true); List<IFileSpec> added = client.addFiles(Arrays.asList(spec), opts); for (IFileSpec fileSpec : added) { FileSpecOpStatus status = fileSpec.getOpStatus(); if (status != FileSpecOpStatus.VALID) { String msg = fileSpec.getStatusMessage(); if (msg.contains("ignored file can't be added")) { int rev = fileSpec.getEndRevision(); return new P4FileState(LocalStatus.IGNORED, rev); } } } return new P4FileState(LocalStatus.NOT_UNDER_CM, 0); } /** * Returns a map of file states of the files most recently synced to the * current workspace. * * @param specs * list of files (specs) file state is being requests * @return map of files to file states * @throws ConfigurationManagementException * @throws P4JavaException */ private Map<File, FileState> getHaveState(List<IFileSpec> specs) throws ConfigurationManagementException, P4JavaException { Map<File, FileState> fileState = new HashMap<>(); getClient(); // p4 sync -n to get files that have been modified in the depot by other // users SyncOptions options = new SyncOptions(); options.setForceUpdate(false); options.setNoUpdate(true); List<IFileSpec> synced = client.sync(specs, options); Map<File, Integer> updatedFiles = new HashMap<>(); for (IFileSpec spec : synced) { FileAction action = spec.getAction(); if (action != null && action == FileAction.UPDATED) { String localPath = depotToLocal(spec); updatedFiles.put(new File(localPath), spec.getEndRevision()); } else if (action == null && spec.getStatusMessage().contains(" - must resolve ")) { // conflict found! parse out the conflicting revision and depot // path Pattern pattern = Pattern .compile("(.+?) - must resolve #(\\d+) before submitting"); String message = spec.getStatusMessage(); Matcher matcher = pattern.matcher(message); while (matcher.find()) { int conflictRevision = -1 * Integer.parseInt(matcher.group(2)); String localPath = depotToLocal(new FileSpec( matcher.group(1))); if (localPath != null) { updatedFiles.put(new File(localPath), conflictRevision); } } } } List<IFileSpec> haveList = getHaveList(specs); for (IFileSpec spec : haveList) { String localPath = spec.getLocalPathString(); int revision = spec.getEndRevision(); if (localPath == null || revision == -1) { continue; } File localFile = new File(localPath); if (updatedFiles.containsKey(localFile) && revision != updatedFiles.get(localFile)) { // have and head revisions differ int headRevision = updatedFiles.get(localFile); if (headRevision < 0) { // use negative numbers to denote conflict version fileState.put(localFile, new P4FileState(LocalStatus.CONFLICTED, revision, Math.abs(headRevision))); } else { // file is unmodified, but a newer version exists in the // depot fileState.put(localFile, new P4FileState(LocalStatus.UNMODIFIED, revision, updatedFiles.get(localFile))); } } else { // we've got the current revision, and it is unmodified fileState.put(localFile, new P4FileState( LocalStatus.UNMODIFIED, revision)); } } return fileState; } /** * Faster implementation of IClient.haveList. * * @param specs * @return * @throws P4JavaException */ private List<IFileSpec> getHaveList(List<IFileSpec> specs) throws P4JavaException { List<IFileSpec> list = new ArrayList<>(); for (IFileSpec spec : specs) { if (spec == null || spec.toString().isEmpty()) { continue; } Map<String, Object> results[] = server.execInputStringMapCmd( "have", new String[] { spec.toString() }, null); for (Map<String, Object> result : results) { Set<String> keys = result.keySet(); // keyset contains: haveRev, path (local path), depotFile (depot // path) and clientFile (client path) if (keys.contains("haveRev") && keys.contains("path") && keys.contains("depotFile") && keys.contains("clientFile")) { IFileSpec fileSpec = new FileSpec(); fileSpec.setLocalPath((String) result.get("path")); fileSpec.setEndRevision(Integer.parseInt((String) result .get("haveRev"))); fileSpec.setClientPath((String) result.get("clientFile")); fileSpec.setDepotPath((String) result.get("depotFile")); list.add(fileSpec); } } } return list; } private Map<File, FileState> getWorkspaceState(List<IFileSpec> specs) throws ConfigurationManagementException, P4JavaException { Map<File, FileState> fileState = new HashMap<>(); IChangelist changelist = getCurrentChangelist(); // run: p4 reconcile -n -f -c <pending change> ReconcileFilesOptions opts = new ReconcileFilesOptions(); opts.setChangelistId(changelist.getId()); opts.setNoUpdate(true); opts.setUseWildcards(true); List<IFileSpec> status = client.reconcileFiles(specs, opts); try { Files.validateFileSpecs(status, " - no file(s) to reconcile"); } catch (Exception e) { Logging.logException(log, new ConfigurationManagementException(e), true); } List<File> toEdit = new ArrayList<>(); for (IFileSpec s : status) { FileAction action = s.getAction(); if (action == null) continue; int rev = s.getEndRevision(); String localPath = depotToLocal(s); if (localPath == null) { continue; } File file = new File(localPath); switch (action) { case ADD: case MOVE_ADD: fileState.put(file, getIgnoreState(s)); break; case EDIT: toEdit.add(file); fileState.put(file, new P4FileState(LocalStatus.MODIFIED, rev)); break; case DELETE: case MOVE_DELETE: fileState.put(file, new P4FileState(LocalStatus.DELETED, rev)); break; default: break; } } if (!toEdit.isEmpty()) { edit(toEdit); } return fileState; } private Map<File, FileState> getOpenedState(List<IFileSpec> specs) throws ConfigurationManagementException, P4JavaException { Map<File, FileState> fileState = new HashMap<>(); IChangelist changelist = getCurrentChangelist(); // Run: p4 opened -c <pending change> OpenedFilesOptions opts = new OpenedFilesOptions(); opts.setChangelistId(changelist.getId()); List<IFileSpec> opened = client.openedFiles(specs, opts); List<File> toEdit = new ArrayList<>(); for (IFileSpec open : opened) { FileAction action = open.getAction(); if (action == null) continue; int rev = open.getEndRevision(); String localPath = depotToLocal(open); File file = new File(localPath); switch (action) { case ADD: case MOVE_ADD: fileState.put(file, new P4FileState(LocalStatus.ADDED, rev)); break; case EDIT: toEdit.add(file); fileState.put(file, new P4FileState(LocalStatus.MODIFIED, rev)); break; case DELETE: case MOVE_DELETE: fileState.put(file, new P4FileState(LocalStatus.DELETED, rev)); break; default: fileState.put(file, new P4FileState(LocalStatus.UNMODIFIED, rev)); break; } } if (!toEdit.isEmpty()) { edit(toEdit); } return fileState; } private String depotToLocal(IFileSpec fileSpec) throws ConfigurationManagementException { String depotPath = fileSpec.getDepotPathString(); if (depotPath == null) { depotPath = fileSpec.getOriginalPathString(); } if (depotPath == null) { return null; } try { List<IFileSpec> dSpec = FileSpecBuilder.makeFileSpecList(depotPath); List<IFileSpec> lSpec = client.where(dSpec); return lSpec.get(0).getLocalPathString(); } catch (P4JavaException e) { Logging.logException(log, e, true); } return null; } @Override public Map<File, FileState> getStateForAllKnownFilesRecursively(File file) throws ConfigurationManagementException { log.debug("getStateForAllKnownFilesRecursively()"); log.debug(file); return getFileState(Collections.singletonList(file)); } /** * Get a copy of the specified files at the specified revisions from the * repository and name them with the specified destination file name. The * adapter can assume that revisions and destinations are not null and are * the same size as files. */ @Override public void export(Map<File, Revision> fileRevisionMap, Map<File, File> fileFileMap) throws ConfigurationManagementException { log.debug("export() {} {}", fileRevisionMap.toString(), fileFileMap.toString()); try { for (File file : fileRevisionMap.keySet()) { Revision revision = fileRevisionMap.get(file); File newFile = fileFileMap.get(file); // use p4 'print' to get the specified revision GetFileContentsOptions options = new GetFileContentsOptions(); options.setNoHeaderLine(true); List<IFileSpec> specs = toSpec(file); for (IFileSpec spec : specs) { Integer revisionNumber = Integer.parseInt(revision .getStringRepresentation()); spec.setEndRevision(revisionNumber); spec.setStartToRev(revisionNumber); spec.setWorkRev(revisionNumber); spec.setStartFromRev(revisionNumber); spec.setEndFromRev(revisionNumber); spec.setStartToRev(revisionNumber); spec.setEndToRev(revisionNumber); spec.setStartRevision(revisionNumber); spec.setBaseRev(revisionNumber); } // stream the contents to the new file location InputStream stream = server.getFileContents(specs, options); FileOutputStream fos = new FileOutputStream(newFile); IOUtils.copy(stream, fos); stream.close(); fos.close(); } } catch (IOException e) { Logging.logException(log, new ConfigurationManagementException(e), true); } catch (P4JavaException e) { Logging.logException(log, e, true); } } @Override public Revision getRevisionCausingConflict(File file) throws ConfigurationManagementException { log.debug("getRevisionCausingConflict()"); log.debug(file); try { FileState fileState = getFileState(Collections.singletonList(file)) .get(file); return fileState.getRevision(); } catch (ConfigurationManagementException e) { Logging.logException(log, e, true); } return null; } @Override public void addTagRecursively(File directory, String tagName, String comment) throws ConfigurationManagementException { log.debug("addTagRecursively() {} {}", tagName, comment); try { addTag(Collections.singletonList(directory), tagName, comment); } catch (ConfigurationManagementException e) { Logging.logException(log, e, true); } } @Override public void addTag(Collection<File> files, String tagName, String comment) throws ConfigurationManagementException { log.debug("addTag() {} {} {}", tagName, comment, files.toString()); try { List<IFileSpec> specs = toSpec(files); IOptionsServer server = getServer(); if (server != null && !specs.isEmpty()) { server.tagFiles(specs, tagName, new TagFilesOptions()); ArrayList<File> f = new ArrayList<>(); f.addAll(files); log.debug("TAGS: " + getTags(f.get(0))); } } catch (P4JavaException e) { Logging.logException(log, e, true); } } @Override public boolean doTagsNeedComments() { log.debug("doTagsNeedComments()"); return false; } @Override public Collection<String> getTags(File file) throws ConfigurationManagementException { log.debug("getTags()"); log.debug(file); Collection<String> tags = new ArrayList<>(); try { List<ILabelSummary> labels = getServer().getLabels(null, 1, null, toSpec(file)); for (ILabelSummary label : labels) { tags.add(label.getName()); } } catch (P4JavaException e) { Logging.logException(log, e, true); } return tags; } @Override public Collection<Revision> listRevisions(File file) throws ConfigurationManagementException { log.debug("listRevisions()"); log.debug(file); Collection<Revision> revisions = new ArrayList<>(); try { List<IFileSpec> spec = toSpec(file); IOptionsServer server = getServer(); if (server == null) { return revisions; } Map<IFileSpec, List<IFileRevisionData>> history = server .getRevisionHistory(spec, new GetRevisionHistoryOptions()); if (history.size() > 0) { IFileSpec ourSpec = history.keySet().iterator().next(); List<IFileRevisionData> revisionList = history .containsKey(ourSpec) ? history.get(ourSpec) : new ArrayList<IFileRevisionData>(); for (IFileRevisionData revision : revisionList) { DateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); Map<String, String> revisionInfo = new HashMap<>(); revisionInfo.put("User", revision.getUserName()); revisionInfo.put("Description", revision.getDescription()); revisionInfo.put("Date", format.format(revision.getDate())); Revision r = new IntegerRevision(revision.getRevision(), revisionInfo); revisions.add(r); } } } catch (P4JavaException e) { Logging.logException(log, e, true); } return revisions; } @Override public Map<File, Boolean> isStored(Collection<File> files) throws ConfigurationManagementException { log.debug("isStored()"); log.debug(files); Map<File, Boolean> stored = new HashMap<>(); try { List<IFileSpec> specs = toSpec(files); List<IExtendedFileSpec> extended = getServer().getExtendedFiles( specs, new GetExtendedFilesOptions()); for (IExtendedFileSpec spec : extended) { if (spec.getClientPathString() == null) { continue; } stored.put(new File(spec.getClientPathString()), spec.isMapped()); } } catch (P4JavaException e) { Logging.logException(log, e, true); } return stored; } @Override public void removeTag(Collection<File> files, String tagName, String comment) throws ConfigurationManagementException { log.debug("removeTag() {} {}", tagName, comment); try { List<IFileSpec> specs = toSpec(files); TagFilesOptions options = new TagFilesOptions(); options.setDelete(true); getServer().tagFiles(specs, tagName, options); } catch (P4JavaException e) { Logging.logException(log, e, true); } } @Override public void removeTag(String tagName, String comment, File file) throws ConfigurationManagementException { log.debug("removeTag() {} {}", tagName, comment); try { List<IFileSpec> specs = toSpec(file); TagFilesOptions options = new TagFilesOptions(); options.setDelete(true); getServer().tagFiles(specs, tagName, options); } catch (P4JavaException e) { Logging.logException(log, e, true); } } /** * According to the SVN example, this function simply resolves conflicts by * accepting the working file. Since this seems to be what Simulink expects, * this is what I implemented. * * @param file * File on which we are resolving the conflict. * @throws ConfigurationManagementException */ @Override public void resolveConflict(File file) throws ConfigurationManagementException { log.debug("resolveConflict()"); log.debug(file); List<IFileSpec> spec = toSpec(file); // accept the working file try { IChangelist changelist = getCurrentChangelist(); ResolveFilesAutoOptions options = new ResolveFilesAutoOptions(); options.setForceResolve(true).setAcceptYours(true) .setChangelistId(changelist.getId()); List<IFileSpec> files = client.resolveFilesAuto(spec, options); files.isEmpty(); } catch (P4JavaException e) { Logging.logException(log, e, true); } } @Override public boolean canCommitEmpty() throws ConfigurationManagementException { log.debug("canCommitEmpty()"); return false; } /** * Forbidden filenames from: * http://www.perforce.com/perforce/r12.2/manuals/cmdref/o.fspecs.html * * @return List of strings representing illegal filename formats. */ @Override public Collection<String> getForbiddenFileNames() { Collection<String> names = new HashSet<>(); // Perforce-specific forbidden patterns names.add(".*@.*"); names.add(".*#.*"); names.add(".*\\*.*"); names.add(".*\\.\\.\\..*"); names.add(".*%.*"); names.add(".*\\/.*"); // don't store the config names.add(".p4config"); names.add(".p4rc"); names.add(".p4ignore"); return names; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#7 | 11966 | Paul Allen | Clean up isModified method. | ||
#6 | 11944 | Paul Allen | Added [Get Revisions] button to sync files | ||
#5 | 11930 | Paul Allen |
Fix remove file operation. MatLab opened a project file for edit, then tries to delete it. Added a reopen and revert in uncheckout() method to help clean up before a delete. |
||
#4 | 11927 | Paul Allen |
Detect local edits (isModified) and add to pending change. Fix revert (uncheckout) if file is open in another change. |
||
#3 | 11925 | Paul Allen | Select pending changelist from table. | ||
#2 | 11899 | Paul Allen | Inprogress development - moved work to branch | ||
#1 | 11898 | Paul Allen |
Populate -o //guest/perforce_software/p4sl/main/... //guest/paul_allen/p4sl/.... |
||
//guest/perforce_software/p4sl/main/src/main/java/com/perforce/p4simulink/P4Adapter.java | |||||
#27 | 11672 | Paul Allen |
Tidy up formatting and imports. No functional change. |
||
#26 | 11663 | Paul Allen |
Fixing issues reported by Tim Hosey at Mathworks: fixed check project button by making getFileState function use the client root instead of a list of files for large file sets. Also investigated error messaging formats and determined that they and the "could not create new file" error are both controlled by MATLAB and not P4Simulink. All calls to "p4 reconcile" will strip the "No file(s) to reconcile." message as well. Transferred from p4://perforce.perforce.com:1666@991730 |
||
#25 | 11662 | Paul Allen |
Fix NPE in changelist selector, remove reference to "Simulink project" from error messages. Transferred from p4://perforce.perforce.com:1666@988672 |
||
#24 | 11661 | Paul Allen |
Finishing up unit tests. Transferred from p4://perforce.perforce.com:1666@985194 |
||
#23 | 11659 | Paul Allen |
Performance improvements, continued: improved performance of getFileState() by replacing the client haveList call with a direct server command. Also improved isLatest by removing fstat calls and replacing with simpler calls to diff and sync. Transferred from p4://perforce.perforce.com:1666@983506 |
||
#22 | 11657 | Paul Allen |
Additional changes from Scania meeting: * Current changelist pane: added description * Started investigation into performance Transferred from p4://perforce.perforce.com:1666@980859 |
||
#21 | 11656 | Paul Allen |
Additional functionality from Scania meeting, including current changelist selector (still needs descriptions added) as well as .p4ignore editor. Transferred from p4://perforce.perforce.com:1666@978388 |
||
#20 | 11653 | Paul Allen |
Fixes for R2014b of MATLAB/Simulink, including: * No longer prompting for connection information in isDirSandboxForThisAdapter * Fixed up commit logic, so it no longer uses the current changelist * Added back logic to walk the directory tree to find a .p4config file, rather than needing one in the sandbox directory * Added new buttons for re-connecting to p4d, and editing .p4config * Investigated conflict issues - fixed some, better logic coming * Removed .p4ignore generation - wrong spot for it Transferred from p4://perforce.perforce.com:1666@976888 |
||
#19 | 11652 | Paul Allen |
Added boilerplate to code, fixed build issues (NPEs on plugin load), removed Mathworks classes from plugin jar, fixed delete vs missing issue on remove() call. Transferred from p4://perforce.perforce.com:1666@970498 |
||
#18 | 11651 | Paul Allen |
Finalizing alpha code changes - adding jar for release. Transferred from p4://perforce.perforce.com:1666@962486 |
||
#17 | 11650 | Paul Allen |
Adding README.txt, added revision number changes for files that have changed in the depot (have/head style) and added build version and type to build process as well as version string within the plugin. Transferred from p4://perforce.perforce.com:1666@959461 |
||
#16 | 11649 | Paul Allen |
Fixes to file management that Paul found during his walkthrough. Transferred from p4://perforce.perforce.com:1666@958829 |
||
#15 | 11648 | Paul Allen |
Final changes after demo with PM, including removal of depot path from the connection dialogue, and fixing of the p4 print issue. Transferred from p4://perforce.perforce.com:1666@958518 |
||
#14 | 11647 | Paul Allen |
New project creation fixes. Transferred from p4://perforce.perforce.com:1666@958305 |
||
#13 | 11645 | Paul Allen |
Refactoring and additional work on UI: Removed Connection class and went with global server/client objects. Implemented panes for connection, depot path and passwords. Passwords are only requested when required. Transferred from p4://perforce.perforce.com:1666@957149 |
||
#12 | 11644 | Paul Allen |
Paul's changes: Refactor getAddress and getAddressForConfig to getPort and getP4JavaURI. There were a few cases when the wrong value was written + getPort was not working. Password had a null password object (now initilised) P4PaswordPane needs some work - must talk to Perfoce to see if it needs a pass or is already logged in. There are still sync -f (this is very bad) Think I miss understood the checkout method after finding and reading the JavaDoc. You were right to start. Looks like a p4 edit. Would like to set a break point and find out when it is used. Elliot's changes: * Changed export() to use p4 'print' * Connect/login now looks for P4TICKETS env var * Config no longer stores P4PASSWD or P4TICKETS * Removed P4TICKET functionality (was only for P4Jenkins) * Ensured we're using a global IServer object * checkout() now uses editFiles() (opens all files for edit) * Changed all "sync -f" to plain "sync" * Removed final client update with force flag * Removed error dialogues regarding login issues * Investigated use of P4IGNORE - I'll need to write my own filter, no big deal Transferred from p4://perforce.perforce.com:1666@955247 |
||
#11 | 11643 | Paul Allen |
Unshelved from pending changelist '953729': Paul: Added: - getWorkspaceState() -- run a p4 status - getOpenedState() -- run p4 opened - getIgnoreState() -- run p4 add -n Modified: - add() -- seemed to call reopen? - P4FileState -- simple constructor for state Elliot: * global client and changelist object within the adapter * removed repeated client updates and changelist refreshes * removed 'reopen' in add * added calls to 'edit' where appropriate file states are found * made 'checkout' a pure sync, with no edits * commented a bunch of the @Override methods * added a call to haveList in getFileState so we can see versions of files in the project * changed the connection pane to look like p4v's * refactored a bit of the dialogue code, added calls to prompt for password (like p4v) * changed all log.trace() calls to log.debug() so the log file messiness can be controlled more easily Functionality that now works properly: * open project from source control, with properly-configured .p4config file * add new project to source control, with properly-configured .p4config file * add * edit * commit * revert * compare reversions (opens Simulink diff tool) Things to work on: * dialogues (particularily the password one, but the connection one is a bit dodge-y) * remove from project (possibly always making the Simulink project meta-data editable will fix this - it *almost* works) - the current issue is Simulink needs to edit the meta-data, but Perforce has made the files read-only, so Simulink throws up on itself Transferred from p4://perforce.perforce.com:1666@954622 |
||
#10 | 11642 | Paul Allen |
More fixes from discussion with Paul and after looking at other integrations work. Transferred from p4://perforce.perforce.com:1666@953553 |
||
#9 | 11641 | Paul Allen |
Fixes from chat with Paul. Transferred from p4://perforce.perforce.com:1666@953389 |
||
#8 | 11640 | Paul Allen |
Fixes to project integrity checks within Simulink. Transferred from p4://perforce.perforce.com:1666@952945 |
||
#7 | 11639 | Paul Allen |
Integrate code from the P4Jenkins plugin, add ability to authenticate with tickets or ticket file. Worked through some issues with getting current file state. Transferred from p4://perforce.perforce.com:1666@952289 |
||
#6 | 11638 | Paul Allen |
General cleanup, fixed some logging issues, fixed getFileState. Transferred from p4://perforce.perforce.com:1666@950492 |
||
#5 | 11637 | Paul Allen |
Fix up isLatest, uncheckout, add back code that searched for a .p4rc file within the sandbox directory first, then looks in ~/.p4simulink/ and finally ~/. Transferred from p4://perforce.perforce.com:1666@950121 |
||
#4 | 11636 | Paul Allen |
Updates to integrate with Simulink, merging into one directory. Added auto submits to add, remove, etc since this is what Simulink expects. Transferred from p4://perforce.perforce.com:1666@947024 |
||
#3 | 11635 | Paul Allen |
Updated gradle build to create a "fat jar", fixed some issues with the interfaces when it runs in Simulink itself. Added connection.configuration pane, switched the adapter to use a current changelist which gives a more svn-ish feel (which the Adapter is expecting). Added some more unit tests. General re-factoring. Transferred from p4://perforce.perforce.com:1666@946607 |
||
#2 | 11634 | Paul Allen |
Debugging actual adapter functionality and starting to integrate with Simulink. Transferred from p4://perforce.perforce.com:1666@938961 |
||
#1 | 11632 | Paul Allen |
Added commons-exec library, server startup/shutdown for unit tests, fixed a bunch of NPEs. Transferred from p4://perforce.perforce.com:1666@937331 |