/******************************************************************************* Copyright (c) 2011, Perforce Software, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /******************************************************************************* * Name : Changelist.cs * * Author : dbb * * Description : Class used to abstract a client in Perforce. * ******************************************************************************/ using System; using System.Collections.Generic; using System.Linq; using System.Text; public enum ChangeListType {None, Public, Restricted}; namespace Perforce.P4 { /// <summary> /// Shelved file information from a Describe -S command /// </summary> public class ShelvedFile { public DepotPath Path { get; set; } public int Revision { get; set; } public FileAction Action { get; set; } public FileType Type { get; set; } public long Size { get; set; } public string Digest { get; set; } } /// <summary> /// A changelist specification in a Perforce repository. /// </summary> public class Changelist { private bool _initialized = false; public void initialize(Connection connection) { if (connection == null) { P4Exception.Throw(ErrorSeverity.E_FAILED, "Changelist cannot be initialized"); return; } Connection = connection; if (connection._p4server == null) { // not connected to the server yet return; } if (Id < 0) { // new change list _initialized = true; return; } P4Command cmd = new P4Command(connection, "change", true, "-o", Id.ToString()); P4CommandResult results = cmd.Run(); if ((results.Success) && (results.TaggedOutput != null) && (results.TaggedOutput.Count > 0)) { FromChangeCmdTaggedOutput(results.TaggedOutput[0],string.Empty,false); _initialized = true; } else { P4Exception.Throw(results.ErrorList); } } internal Connection Connection { get; private set; } internal FormBase _baseForm; public int Id { get; internal set; } public string Description { get; set; } public string OwnerName { get; set; } public bool Pending { get; private set; } public DateTime ModifiedDate { get; private set; } public string ClientId { get; set; } public bool Shelved { get; set; } public IList<ShelvedFile> ShelvedFiles { get; set; } public Dictionary<string, string> Jobs { get; set; } public IList<FileMetaData> Files { get; set; } public FormSpec Spec { get; set; } private StringEnum<ChangeListType> _type; public ChangeListType Type { get { return _type; } set { _type = value; } } /// <summary> /// Create a new pending changelist /// </summary> public Changelist() { Pending = true; Id = -1; Type = ChangeListType.None; Files = new List<FileMetaData>(); } /// <summary> /// Create a new numbered changelist /// </summary> /// <param name="id"></param> /// <param name="pending"></param> public Changelist(int id, bool pending) { Id = id; Pending = pending; Type = ChangeListType.None; Files = new List<FileMetaData>(); } /// <summary> /// Fill in the fields for the changelist using the tagged output of a "change' command /// </summary> /// <param name="objectInfo">The tagged output of a "change' command</param> public void FromChangeCmdTaggedOutput(TaggedObject objectInfo, string offset, bool dst_mismatch) { FromChangeCmdTaggedOutput(objectInfo, false, offset, dst_mismatch); } /// <summary> /// Fill in the fields for the changelist using the tagged output of a "change' command /// </summary> /// <param name="objectInfo">The tagged output of a "change' command</param> public void FromChangeCmdTaggedOutput(TaggedObject objectInfo, bool GetShelved, string offset, bool dst_mismatch) { // need to check for tags starting with upper and lower case, it the 'change' command's // output the tags start with an uppercase character whereas with the 'changes' command // they start with a lower case character, i.e. "Change" vs "change" _baseForm = new FormBase(); _baseForm.SetValues(objectInfo); if (objectInfo.ContainsKey("Change")) { int v = -1; if (int.TryParse(objectInfo["Change"], out v)) { Id = v; } } else if (objectInfo.ContainsKey("change")) { int v = -1; if (int.TryParse(objectInfo["change"], out v)) { Id = v; } } if (objectInfo.ContainsKey("Date")) { DateTime v; DateTime.TryParse(objectInfo["Date"], out v); ModifiedDate = v; } else if (objectInfo.ContainsKey("time")) { long v; if (long.TryParse(objectInfo["time"], out v)) { DateTime UTC = FormBase.ConvertUnixTime(v); DateTime GMT = new DateTime(UTC.Year, UTC.Month, UTC.Day, UTC.Hour, UTC.Minute, UTC.Second, DateTimeKind.Unspecified); ModifiedDate = FormBase.ConvertFromUTC(GMT, offset, dst_mismatch); } } if (objectInfo.ContainsKey("Client")) ClientId = objectInfo["Client"]; else if (objectInfo.ContainsKey("client")) ClientId = objectInfo["client"]; if (objectInfo.ContainsKey("User")) OwnerName = objectInfo["User"]; else if (objectInfo.ContainsKey("user")) OwnerName = objectInfo["user"]; if (objectInfo.ContainsKey("Status")) { Pending = true; String v = objectInfo["Status"]; if (v == "submitted") Pending = false; } else if (objectInfo.ContainsKey("status")) { Pending = true; String v = objectInfo["status"]; if (v == "submitted") Pending = false; } if (objectInfo.ContainsKey("Description")) Description = objectInfo["Description"]; else if (objectInfo.ContainsKey("desc")) Description = objectInfo["desc"]; char[] array = {'\r', '\n'}; Description = Description.TrimEnd(array); Description = Description.Replace("\r", ""); Description = Description.Replace("\n", "\r\n"); if (objectInfo.ContainsKey("Type")) { _type = objectInfo["Type"]; } else if (objectInfo.ContainsKey("changeType")) { _type = objectInfo["changeType"]; } if (objectInfo.ContainsKey("shelved")) { Shelved = true; } int idx = 0; String key = "Jobs0"; if (objectInfo.ContainsKey(key)) { idx = 1; Jobs = new Dictionary<string, string>(); do { Jobs.Add(objectInfo[key], null); key = String.Format("Jobs{0}", idx++); } while (objectInfo.ContainsKey(key)); } else { key = "jobs0"; if (objectInfo.ContainsKey(key)) { idx = 1; Jobs = new Dictionary<string, string>(); do { Jobs.Add(objectInfo[key], null); key = String.Format("jobs{0}", idx++); } while (objectInfo.ContainsKey(key)) ; } } key = "Files0"; if (objectInfo.ContainsKey(key)) { idx = 1; Files = new List<FileMetaData>(); do { FileMetaData file = new FileMetaData(); file.DepotPath = new DepotPath(PathSpec.UnescapePath(objectInfo[key])); Files.Add(file); key = String.Format("Files{0}", idx++); } while (objectInfo.ContainsKey(key)); } else { key = "files0"; if (objectInfo.ContainsKey(key)) { idx = 1; Files = new List<FileMetaData>(); do { FileMetaData file = new FileMetaData(); file.DepotPath = new DepotPath(PathSpec.UnescapePath(objectInfo[key])); Files.Add(file); key = String.Format("files{0}", idx++); } while (objectInfo.ContainsKey(key)); } } if (GetShelved) { key = "depotFile0"; String actionKey = "action0"; String typeKey = "type0"; String revKey = "rev0"; String sizeKey = "fileSize0"; String digestKey = "digest0"; if (objectInfo.ContainsKey(key)) { ShelvedFiles = new List<ShelvedFile>(); idx = 1; do { ShelvedFile file = new ShelvedFile(); file.Path = new DepotPath(PathSpec.UnescapePath(objectInfo[key])); StringEnum<FileAction> action = objectInfo[actionKey]; file.Action = action; file.Type = new FileType(objectInfo[typeKey]); string revstr = objectInfo[revKey]; if (revstr == "none") { revstr = "0"; } int rev = Convert.ToInt32(revstr); file.Revision = rev; if (objectInfo.ContainsKey(sizeKey)) { long size = -1; long.TryParse(objectInfo[sizeKey], out size); file.Size = size; } if (objectInfo.ContainsKey(digestKey)) { file.Digest = objectInfo[digestKey]; } ShelvedFiles.Add(file); key = String.Format("depotFile{0}", idx); actionKey = String.Format("action{0}", idx); typeKey = String.Format("type{0}", idx); revKey = String.Format("rev{0}", idx); sizeKey = String.Format("fileSize{0}", idx); digestKey = String.Format("digest{0}", idx++); } while (objectInfo.ContainsKey(key)); } } else { key = "depotFile0"; String actionKey = "action0"; String typeKey = "type0"; String revKey = "rev0"; String sizeKey = "fileSize0"; String digestKey = "digest0"; if (objectInfo.ContainsKey(key)) { idx = 1; Files = new List<FileMetaData>(); do { FileMetaData file = new FileMetaData(); file.DepotPath = new DepotPath(PathSpec.UnescapePath(objectInfo[key])); StringEnum<FileAction> action = objectInfo[actionKey]; file.Action = action; file.Type = new FileType(objectInfo[typeKey]); string revstr = objectInfo[revKey]; int rev = Convert.ToInt32(revstr); file.HeadRev = rev; if (objectInfo.ContainsKey(sizeKey)) { long size = -1; long.TryParse(objectInfo[sizeKey], out size); file.FileSize = size; } if (objectInfo.ContainsKey(digestKey)) { file.Digest = objectInfo[digestKey]; } Files.Add(file); key = String.Format("depotFile{0}", idx); actionKey = String.Format("action{0}", idx); typeKey = String.Format("type{0}", idx); revKey = String.Format("rev{0}", idx); sizeKey = String.Format("fileSize{0}", idx); digestKey = String.Format("digest{0}", idx++); } while (objectInfo.ContainsKey(key)); } } key = "job0"; String statKey = "jobstat0"; if (objectInfo.ContainsKey(key)) { idx = 1; Jobs = new Dictionary<string, string>(); do { string jobStatus = string.Empty; string jobId = objectInfo[key]; if (objectInfo.ContainsKey(statKey)) { jobStatus = objectInfo[statKey]; } Jobs.Add(jobId, jobStatus); key = String.Format("job{0}", idx); statKey = String.Format("jobstat{0}", idx++); } while (objectInfo.ContainsKey(key)); } } /// <summary> /// Parse the fields from a changelist specification /// </summary> /// <param name="spec">Text of the changelist specification in server format</param> /// <returns></returns> public bool Parse(String spec) { _baseForm = new FormBase(); _baseForm.Parse(spec); // parse the values into the underlying dictionary if (_baseForm.ContainsKey("Change")) { int v = -1; int.TryParse((string)_baseForm["Change"], out v); Id = v; } if (_baseForm.ContainsKey("Date")) { DateTime v; DateTime.TryParse((string)_baseForm["Date"], out v); ModifiedDate = v; } if (_baseForm.ContainsKey("Client")) ClientId = (string)_baseForm["Client"]; if (_baseForm.ContainsKey("User")) OwnerName = (string)_baseForm["User"]; if (_baseForm.ContainsKey("Status")) { Pending = true; String v = (string)_baseForm["Status"]; if (v == "submitted") Pending = false; } if (_baseForm.ContainsKey("Type")) { _type = (string)_baseForm["Type"]; } if (_baseForm.ContainsKey("Description")) { if (_baseForm["Description"] is string) { Description = (string)_baseForm["Description"]; } else if (_baseForm["Description"] is IList<string>) { IList<string> strList = _baseForm["Description"] as IList<string>; Description = string.Empty; for (int idx = 0; idx < strList.Count; idx++) { if (idx > 0) Description += "\r\n"; Description += strList[idx]; } } } if (_baseForm.ContainsKey("Jobs") && (_baseForm["Jobs"] is IList<string>)) { Jobs = new Dictionary<string, string>(); foreach (string job in (IList<string>)_baseForm["Jobs"]) { if (job.Contains('#')) { string[] parts = job.Split('#'); Jobs[parts[0].Trim()] = null; } else { Jobs[job] = null; } } } if ((_baseForm.ContainsKey("Files")) && (_baseForm["Files"] is IList<string>)) { IList<string> files = (IList<string>)_baseForm["Files"]; foreach (string path in files) { FileMetaData file = new FileMetaData(); if (path.Contains('#')) { string[] parts = path.Split('#'); file.DepotPath = new DepotPath(parts[0].Trim()); } else { file.DepotPath = new DepotPath(path); } Files.Add(file); } } return true; } /// <summary> /// Format of a user specification used to save a user to the server /// </summary> private static String ChangelistSpecFormat = "Change:\t{0}\n" + "\n" + "Date:\t{1}\n" + "\n" + "Client:\t{2}\n" + "\n" + "User:\t{3}\n" + "\n" + "Status:\t{4}\n" + "\n" + "{5}" + "Description:\n" + "\t{6}\n" + "\n" + "Jobs:\n" + "{7}\n" + "\n" + "Files:\n{8}"; /// <summary> /// Convert to specification in server format /// </summary> /// <returns></returns> override public String ToString() { String descStr = String.Empty; if (Description != null) descStr = FormBase.FormatMultilineField(Description.ToString()); // only add the files field if it is a pending changelist // // # Jobs: What opened jobs are to be closed by this changelist. // # You may delete jobs from this list. (New changelists only.) // # Files: What opened files from the default changelist are to be added // # to this changelist. You may delete files from this list. // # (New changelists only.) String jobsStr = String.Empty; if (Jobs != null) { foreach (string jobId in Jobs.Keys) { jobsStr += string.Format("\t{0}\r\n", jobId); } } String filesStr = String.Empty; if (Files != null && Pending) { foreach (FileMetaData file in Files) { if (file != null && file.IsInDepot) { filesStr += String.Format("\t{0}\r\n", PathSpec.EscapePath(file.DepotPath.Path)); } } } String ChangeNumbeStr = "new"; if (Id != -1) ChangeNumbeStr = Id.ToString(); String restrictedStr = String.Empty; if (_type == ChangeListType.Restricted) restrictedStr = "Type:\trestricted\n\n"; if (_type == ChangeListType.Public) restrictedStr = "Type:\tpublic\n\n"; String statusStr = Pending ? "pending" : "submitted"; if (Id == -1) statusStr = "new"; String value = String.Format(ChangelistSpecFormat, ChangeNumbeStr, FormBase.FormatDateTime(ModifiedDate), ClientId, OwnerName, statusStr, restrictedStr, descStr, jobsStr, filesStr); return value; } /// <summary> /// Convert to a string for display /// </summary> /// <param name="includeTime">Include the time as well as the date</param> /// <returns></returns> public string ToString(bool includeTime) { String dateTime; if (includeTime) dateTime = String.Format("{0} {1}", ModifiedDate.ToShortDateString(), ModifiedDate.ToShortTimeString()); else dateTime = ModifiedDate.ToShortDateString(); String desc = String.Empty; if (Description != null) desc = FormBase.FormatMultilineField(Description.ToString()); return String.Format("Change {0} on {1} by {2}: {3}", Id, dateTime, OwnerName, desc); } /// <summary> /// /// </summary> /// <param name="options"></param> /// <param name="jobs"></param> /// <returns></returns> /// <remarks> /// <br/><b>p4 help fix</b> /// <br/> /// <br/> fix -- Mark jobs as being fixed by the specified changelist /// <br/> /// <br/> p4 fix [-d] [-s status] -c changelist# jobName ... /// <br/> /// <br/> 'p4 fix' marks each named job as being fixed by the changelist /// <br/> number specified with -c. The changelist can be pending or /// <br/> submitted and the jobs can open or closed (fixed by another /// <br/> changelist). /// <br/> /// <br/> If the changelist has already been submitted and the job is still /// <br/> open, then 'p4 fix' marks the job closed. If the changelist has not /// <br/> been submitted and the job is still open, the job is closed when the /// <br/> changelist is submitted. If the job is already closed, it remains /// <br/> closed. /// <br/> /// <br/> The -d flag deletes the specified fixes. This operation does not /// <br/> otherwise affect the specified changelist or jobs. /// <br/> /// <br/> The -s flag uses the specified status instead of the default defined /// <br/> in the job specification. This status is reported by 'p4 fixes'. /// <br/> The 'p4 fix' and 'p4 change' (of a submitted changelist) and 'p4 submit' /// <br/> (of a pending changelist) commands set the job's status to the fix's /// <br/> status for each job associated with the change. If the fix's status /// <br/> is 'same', the job's status is left unchanged. /// <br/> /// <br/> /// </remarks> public IList<Fix> FixJobs(Options options, params Job[] jobs) { if (_initialized == false) { throw new ApplicationException("Changelist connection is not initialized"); } IList<Fix> value = null; P4Command cmd = null; if ((jobs != null) && (jobs.Length > 0)) { cmd = new P4Command(Connection, "fix", true, Job.ToStrings(jobs)); } else { cmd = new P4Command(Connection, "fix", true); } if (options == null) { options = new Options(); } if (options.ContainsKey("-c") == false) { options["-c"] = this.Id.ToString(); } P4CommandResult results = cmd.Run(options); if (results.Success) { if ((results.TaggedOutput == null) || (results.TaggedOutput.Count <= 0)) { return null; } value = new List<Fix>(); foreach (TaggedObject obj in results.TaggedOutput) { Fix fix = Fix.FromFixCmdTaggedOutput(obj); value.Add(fix); } } else { P4Exception.Throw(results.ErrorList); } return value; } /// <summary> /// /// </summary> /// <param name="jobs"></param> /// <param name="options"></param> /// <returns></returns> public IList<Fix> FixJobs(IList<Job> jobs, Options options) { return FixJobs(options, jobs.ToArray()); } /// <summary> /// /// </summary> /// <param name="options"></param> /// <param name="jobs"></param> /// <returns></returns> /// <remarks> /// <br/><b>p4 help fix</b> /// <br/> /// <br/> fix -- Mark jobs as being fixed by the specified changelist /// <br/> /// <br/> p4 fix [-d] [-s status] -c changelist# jobName ... /// <br/> /// <br/> 'p4 fix' marks each named job as being fixed by the changelist /// <br/> number specified with -c. The changelist can be pending or /// <br/> submitted and the jobs can open or closed (fixed by another /// <br/> changelist). /// <br/> /// <br/> If the changelist has already been submitted and the job is still /// <br/> open, then 'p4 fix' marks the job closed. If the changelist has not /// <br/> been submitted and the job is still open, the job is closed when the /// <br/> changelist is submitted. If the job is already closed, it remains /// <br/> closed. /// <br/> /// <br/> The -d flag deletes the specified fixes. This operation does not /// <br/> otherwise affect the specified changelist or jobs. /// <br/> /// <br/> The -s flag uses the specified status instead of the default defined /// <br/> in the job specification. This status is reported by 'p4 fixes'. /// <br/> The 'p4 fix' and 'p4 change' (of a submitted changelist) and 'p4 submit' /// <br/> (of a pending changelist) commands set the job's status to the fix's /// <br/> status for each job associated with the change. If the fix's status /// <br/> is 'same', the job's status is left unchanged. /// <br/> /// <br/> /// </remarks> public IList<Fix> FixJobs(Options options, params string[] jobIds) { if (_initialized == false) { throw new ApplicationException("Changelist connection is not initialized"); } IList<Fix> value = null; P4Command cmd = null; if ((jobIds != null) && (jobIds.Length > 0)) { cmd = new P4Command(Connection, "fix", true, jobIds); } else { cmd = new P4Command(Connection, "fix", true); } if (options == null) { options = new Options(); } if (options.ContainsKey("-c") == false) { options["-c"] = this.Id.ToString(); } P4CommandResult results = cmd.Run(options); if (results.Success) { if ((results.TaggedOutput == null) || (results.TaggedOutput.Count <= 0)) { return null; } value = new List<Fix>(); foreach (TaggedObject obj in results.TaggedOutput) { Fix fix = Fix.FromFixCmdTaggedOutput(obj); value.Add(fix); } } else { P4Exception.Throw(results.ErrorList); } return value; } /// <summary> /// /// </summary> /// <param name="jobs"></param> /// <param name="options"></param> /// <returns></returns> public IList<Fix> FixJobs(IList<string> jobIds, Options options) { return FixJobs(options, jobIds.ToArray()); } public SubmitResults Submit(Options options) { if (_initialized == false) { throw new ApplicationException("Changelist connection is not initialized"); } if (options == null) { options = new Options(); } if (options.ContainsKey("-e") == false&& options.ContainsKey("-c") == false) { options["-c"] = this.Id.ToString(); } return Connection.Client.SubmitFiles(options, null); } } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 19284 | ava | "Forking branch Main of perforce-software-p4connect to ava-p4connect." | ||
//guest/perforce_software/p4connect/main/src/P4Bridge/p4api.net/Changelist.cs | |||||
#1 | 16209 | Norman Morse | Move entire source tree into "main" branch so workshop code will act correctly. | ||
//guest/perforce_software/p4connect/src/P4Bridge/p4api.net/Changelist.cs | |||||
#3 | 13692 | Norman Morse |
Fix FileSpec corruption issue Make sure that the logged in user is verified against files in the default change. Multiple users can share the same workspace and cause lots of confusion. Also changed the code in Perforce.P4.Changelist.ToString() to create new FileSpecs instead of doing implicite casts. This should fix the exception if the source FileMetaData is incomplete some how. |
||
#2 | 12135 | Norman Morse |
Integrate dev branch changes into main. This code is the basiis of the 2.7 BETA release which provides Unity 5 compatibility |
||
#1 | 10940 | Norman Morse |
Inital Workshop release of P4Connect. Released under BSD-2 license |