September 23, 2008

Attach to Defect on Promote

Surround SCM

Works with TestTrack 2008 and 2009

Works with Surround SCM 2008 and 2009

A popular feature request is the ability to attach to a TestTrack defect when a file is promoted. While this is something we periodically review when we consider enhancements for future relases, this can be done using triggers in the meantime. Similar examples have been posted on how to attach an SCC file from Perforce and Subversion to TestTrack. This example takes the same approach, only modified to retrieve the Surround SCM environment variables. [toc]

The Trigger

The trigger in Surround SCM must be setup to run after a promote action. The Precondition must be set to be the branch you will be promoting to (the parent branch). The action the trigger will fire is a script that uses the TestTrack SOAP based SDK and updates the specific defect.

Use Case

While some TestTrack information may be hard coded in the script, like the Project name, the user to login via SOAP, etc..., at least one piece of information must be passed when the promote takes place, the defect number. In the example script below, a token is used to parse the defect number out of the comments. The format of the token is: <:#:> Where # is where the defect number would go. The parsing logic in the example script will only work if a single defect number is passed. It can accept multiple digits, but not multiple digits separated by a comma, semicolon, etc... Some examples of what the token may look like:
<:23:>
<:6:>
<:45123:>
Some examples of what the token may not look like:
<:23,34,45:>
<:234fdf:>
<:23;74;78:>
NOTE: If you want to be able to pass multiple defect numbers, change the parse logic to account for this.

The Script

The following are examples of what the script may look like. Feel free to add your own version of the script to this article.

C# Example

This is the easiest of all three examples. This script can only handle one defect number being passed.
using System;
using System.Collections.Generic;
using System.Text;

namespace AttachOnPromote
{
    class Program
    {
        static void Main(string[] args)
        {
            string s_DNum = "";
            long i_DNum = 0;

            string scmComments = System.Environment.GetEnvironmentVariable("SSCM_COMMENT");
            string scmBranch = System.Environment.GetEnvironmentVariable("SSCM_BRANCH");
            string scmVersion = System.Environment.GetEnvironmentVariable("SSCM_FILEVERSION");
            string scmRepo = System.Environment.GetEnvironmentVariable("SSCM_REPOSITORY");
            string scmFile = System.Environment.GetEnvironmentVariable("SSCM_FILE");
            string scmDate = System.Environment.GetEnvironmentVariable("SSCM_DATE");

            string e_Message = ""; ;

            if ((scmComments != "") && (scmComments != null))
            {
                if ((scmComments.Contains("<:")) && (scmComments.Contains(":>")))
                {

                    scmComments = scmComments.Remove(0, scmComments.IndexOf("<:") + 2);
                    s_DNum = scmComments.Substring(0, scmComments.IndexOf(":>"));

                    bool isInt = false;
                    try
                    {
                        i_DNum = Convert.ToInt64(s_DNum);
                        //if no exception, then we set the boolean to true
                        isInt = true;
                    }
                    catch (Exception d)
                    {
                        //if we get an exception, we set the boolean to false
                        isInt = false;
                    }
                    if (!((s_DNum == "") || (s_DNum == null) || (isInt == false)))
                    {

                        long m_Cookie = 0;
                        long m_DNum = i_DNum;

                        string m_SCCFilePath = scmBranch + "::" + scmRepo + "/" + scmFile;

                        string m_User = "Administrator";
                        string m_Password = "password";
                        string m_Url = "http://localhost/scripts/ttsoapcgi.exe";
                        string m_ProjectName = "Sample Project";

                        ttsoapcgi m_ttsoapcgi = new ttsoapcgi();
                        m_ttsoapcgi.Url = m_Url;

                        CProject m_Project = new CProject();
                                                CProject[] m_Projects = m_ttsoapcgi.getProjectList(m_User, m_Password);
                        for (int i = 0; i < m_Projects.Length; i++)
                        {
                            if (m_Projects[i].database.name == m_ProjectName)
                            {
                                m_Project = m_Projects[i];
                            }
                        }

                        try
                        {
                            m_Cookie = m_ttsoapcgi.ProjectLogon(m_Project, m_User, m_Password);
                        }
                        catch (Exception fcc)
                        {
                            e_Message = fcc.Message;
                        }
                        if ( e_Message != "")
                        {

                        }
                        else
                        {
                            CDefect m_Defect = m_ttsoapcgi.editDefect(m_Cookie, m_DNum, "", false);
                            CSCCFileRecord m_SCCFile = new CSCCFileRecord();
                            m_SCCFile.mstrFileName = m_SCCFilePath;
                            m_SCCFile.mstrFixedRevision = scmVersion;
                            m_SCCFile.mdateFixedTimestamp = Convert.ToDateTime(scmDate);
                            if (m_Defect.pSCCFileList == null)
                            {
                                m_Defect.pSCCFileList = new CSCCFileRecord[1];
                                m_Defect.pSCCFileList.SetValue(m_SCCFile, m_Defect.pSCCFileList.Length -1);
                            }
                            else
                            {
                                m_Defect.pSCCFileList.SetValue(m_SCCFile, m_Defect.pSCCFileList.Length);
                            }
                            m_ttsoapcgi.saveDefect(m_Cookie, m_Defect);

                            m_ttsoapcgi.DatabaseLogoff(m_Cookie);
                        }

                    }
                }

            }

        }
    }
}

Java Example

Almost as easy as C#, except you have to a little more work to build array of SCC files, since there isn't a SetValue() method you can call. The testtrack_Interface imported in this example was obtained from here.
import testtrack_Interface.*;
import java.util.*;
import java.text.*;
import java.io.*;
/**
 *
 * @author cremerf
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here

        //Let's define our vars
       //Surround SCM env vars

        String scmComments = System.getenv("SSCM_COMMENT");

        if ((scmComments.contains("<:"))&&(scmComments.contains(":>")))
        {
            String scmBranch = System.getenv("SSCM_BRANCH");
            String scmRepository = System.getenv("SSCM_Repository");
            String scmFile = System.getenv("SSCM_FILE");
            String scmVersion = System.getenv("SSCM_FILEVERSION");
            String strDNum = parseComments(scmComments);
            String strDate = System.getenv("SSCM_DATE");

            Calendar cal=Calendar.getInstance();
            try
            {
                  DateFormat formatter ;
                  Date date ;
                  formatter = new SimpleDateFormat("dd-mm-yy");
                  date = (Date)formatter.parse(strDate);
                  cal.setTime(date);

            }
            catch (ParseException e)
            {

                try
                 {
                     FileWriter outFile = new FileWriter("c:\errpromote.txt");
                     PrintWriter out = new PrintWriter(outFile);
                     out.println("The following error was encountered:");
                     out.println(e.getMessage());

                     out.close();
                  }
                  catch (IOException ef)
                  {
                     e.printStackTrace();

                  }
            }   

            //build the file including the full path
            String sccFile = scmBranch + "::" + scmRepository + "/" + scmFile;

            //TestTrack vars
            String ttUser = "Administrator";
            String ttPassword = "password";
            String ttURL = "http://localhost/scripts/ttsoapcgi.exe";
            String ttProject = "Sample Project";
            long ttCookie =0;
            long DNum = Integer.parseInt(strDNum);

            //Now we can try to log in

            try
            { // This code block invokes the Ttsoapcgi:projectLogon operation on web service
                Ttsoapcgi ttsoapcgi = new Ttsoapcgi_Impl();
                //the _ttsoapcgi is what we'll use to make all the calls
                TtsoapcgiPortType _ttsoapcgi = ttsoapcgi.getTtsoapcgi();

                CProject ttProj = new CProject();

                CProject[] ttProjs = _ttsoapcgi.getProjectList(ttUser,ttPassword);

                for (int i = 0; i <ttProjs.length;i++)
                {
                    if (ttProjs[i].getDatabase().getName().equals(ttProject))
                    {
                        ttProj = ttProjs[i];
                    }
                }

                ttCookie = _ttsoapcgi.projectLogon(ttProj, ttUser, ttPassword);
                if (ttCookie > 0)
                {
                    CDefect theDefect = _ttsoapcgi.editDefect(ttCookie, DNum , "", false);
                    CSCCFileRecord[] ttSCCFiles = theDefect.getPSCCFileList();
                    int fileCount = 0;
                    if (ttSCCFiles != null)
                    {
                        fileCount= ttSCCFiles.length;

                         //check to see if file exists
                         boolean sccFileExists = false;
                         for (int i = 0;i < fileCount; i++)
                         {
                             if (ttSCCFiles[i].getMstrFileName().equals(sccFile))
                             {
                                 ttSCCFiles[i].setMstrFixedRevision(ttSCCFiles[i].getMstrFixedRevision() + "," + scmVersion);
                                 sccFileExists = true;
                             }
                         }
                         if (sccFileExists == false)
                         {
                             CSCCFileRecord[] ttNSCCFiles = new CSCCFileRecord[fileCount + 1];
                             for (int i = 0; i < fileCount; i++)
                             {
                                 ttNSCCFiles[i] = new CSCCFileRecord();
                                 ttNSCCFiles[i].setMdateFixedTimestamp(ttSCCFiles[i].getMdateFixedTimestamp());
                                 ttNSCCFiles[i].setMstrFileName(ttSCCFiles[i].getMstrFileName());
                                 ttNSCCFiles[i].setMstrFixedRevision(ttSCCFiles[i].getMstrFixedRevision());

                             }
                             ttNSCCFiles[fileCount] = new CSCCFileRecord();
                             ttNSCCFiles[fileCount].setMstrFileName(sccFile);
                             ttNSCCFiles[fileCount].setMstrFixedRevision(scmVersion);
                             ttNSCCFiles[fileCount].setMdateFixedTimestamp(cal);

                             theDefect.setPSCCFileList(ttNSCCFiles);

                         }
                    }
                    else
                    {
                       CSCCFileRecord[] ttNSCCFiles = new CSCCFileRecord[fileCount + 1];
                       ttNSCCFiles[fileCount] = new CSCCFileRecord();
                       ttNSCCFiles[fileCount].setMstrFileName(sccFile);
                       ttNSCCFiles[fileCount].setMstrFixedRevision(scmVersion);
                       ttNSCCFiles[fileCount].setMdateFixedTimestamp(cal);

                       theDefect.setPSCCFileList(ttNSCCFiles);  

                    }
                    _ttsoapcgi.saveDefect(ttCookie, theDefect);
                    _ttsoapcgi.databaseLogoff(ttCookie);

                }
             }

             catch(Exception ex)
             {
                 java.util.logging.Logger.getLogger(testtrack_Interface.Ttsoapcgi.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
                 try
                 {
                     FileWriter outFile = new FileWriter("c:\errorpromote.txt");
                     PrintWriter out = new PrintWriter(outFile);
                     out.println("The following error was encountered:");
                     out.println(ex.getMessage());

                     out.close();
                  }
                  catch (IOException e)
                  {
                     e.printStackTrace();

                  }
             }

        }

Python Example

By far the most difficult, since you have to build the XML envelope from scratch. This script, however, can handle more than one defect number being passed. This script is based on this one.
import getopt
import httplib
import os
import StringIO
import sys
import time
import xml
from xml.dom import DOMImplementation, DocumentType
from xml.dom.ext import PrettyPrint
import xml.dom.minidom

class Usage(Exception):
    def __init__(self, msg):
        self.msg = msg

c_soapaddr = "127.0.0.1"
c_soapport = 80
c_soapcgi = "http://127.0.0.1/scripts/ttsoapcgi.exe"
c_project = "Sample Project"

m_comments = ""

def parseComments(p_comments):

   #First, make sure we find the token
   istart = p_comments.find("<:")
   #if token found
   if istart >= 0:
      #find end of token
      iend = p_comments.find(":>", istart)
      #extract from the comments the token
      ttpcomments = p_comments[istart+2:iend]
      icomma1=istart
      icomma2=0

      defectnum = ""
      defectList = []
      defectCount = 0

      while icomma2 >= 0 :
         icomma2 = ttpcomments.find(",", icomma1)
         #icomma2 will be -1 once it does not find a comma in the remmainning portion,
         #so we know that is the last item in the token
         if icomma2 == -1:
            defectnum = ttpcomments[icomma1:len(ttpcomments)]
            defectList[defectCount:0] = [defectnum]
         else:
            defectnum = ttpcomments[icomma1:icomma2]
            defectList[defectCount:0] = [defectnum]
            defectCount = defectCount + 1
            #reset the starting position
            icomma1 = icomma2+1

   return defectList

def login(p_user, p_pwd):
   dom = DOMImplementation.getDOMImplementation ()
   typ = xml.dom.DocumentType.DocumentType ("Envelope", [], [], "", "")
   # this is not the namespace required by soap 1.2
   # but the service requires this namespace (from error message)
   doc = dom.createDocument ("http://schemas.xmlsoap.org/soap/envelope/", "Envelope", typ)

   env = doc.getElementsByTagName ("Envelope")[0]
   bdy = doc.createElement ("Body")
   env.appendChild (bdy)

   msg = doc.createElementNS ("urn:testtrack-interface", "DatabaseLogon")
   bdy.appendChild (msg)

   eleProject = doc.createElement("dbname")
   eleProject.setAttributeNode( doc.createAttribute("xsi:type"))
   eleProject.setAttribute("xsi:type", "xsd:string")
   msg.appendChild(eleProject)
   eleProject.appendChild(doc.createTextNode(str(c_project)))

   eleUser = doc.createElement("username")
   eleUser.setAttributeNode( doc.createAttribute("xsi:type"))
   eleUser.setAttribute("xsi:type", "xsd:string")
   msg.appendChild(eleUser)
   eleUser.appendChild(doc.createTextNode(str(p_user)))

   elePwd = doc.createElement("password")
   elePwd.setAttributeNode( doc.createAttribute("xsi:type"))
   elePwd.setAttribute("xsi:type", "xsd:string")
   msg.appendChild(elePwd)
   elePwd.appendChild(doc.createTextNode(str(p_pwd)))

   # Let's lock the defect for editing!
   buffer = StringIO.StringIO()
   PrettyPrint(doc, buffer)
   request = buffer.getvalue()
   #print request #DEBUG

   httpsvr = httplib.HTTPConnection(c_soapaddr, c_soapport)
   httpsvr.putrequest("POST", c_soapcgi)
   httpsvr.putheader("Content-Type", "text/xml")
   httpsvr.putheader("Content-Length", str(len(request)))
   httpsvr.endheaders()
   httpsvr.send(request)

   response = httpsvr.getresponse()
   session = response.read()
   print "DatabaseLogon", response.status, response.reason

   return session[session.find("<Cookie>")+8:session.find("</Cookie>")]

def logout(p_session):
   dom = DOMImplementation.getDOMImplementation ()
   typ = xml.dom.DocumentType.DocumentType ("Envelope", [], [], "", "")
   # this is not the namespace required by soap 1.2
   # but the service requires this namespace (from error message)
   doc = dom.createDocument ("http://schemas.xmlsoap.org/soap/envelope/", "Envelope", typ)

   env = doc.getElementsByTagName ("Envelope")[0]
   bdy = doc.createElement ("Body")
   env.appendChild (bdy)

   msg = doc.createElementNS ("urn:testtrack-interface", "DatabaseLogoff")
   bdy.appendChild (msg)

   eleCookie = doc.createElement("cookie")
   eleCookie.setAttributeNode( doc.createAttribute("xsi:type"))
   eleCookie.setAttribute("xsi:type", "xsd:int")
   msg.appendChild(eleCookie)
   eleCookie.appendChild(doc.createTextNode(str(p_session)))

   # Let's lock the defect for editing!
   buffer = StringIO.StringIO()
   PrettyPrint(doc, buffer)
   request = buffer.getvalue()

   httpsvr = httplib.HTTPConnection(c_soapaddr, c_soapport)
   httpsvr.putrequest("POST", c_soapcgi)
   httpsvr.putheader("Content-Type", "text/xml")
   httpsvr.putheader("Content-Length", str(len(request)))
   httpsvr.endheaders()
   httpsvr.send(request)

   response = httpsvr.getresponse()
   print "DatabaseLogoff", response.status, response.reason

def updateDefect(p_session, p_defectnumber, p_revnum, p_file, p_fixdate):
   dom = DOMImplementation.getDOMImplementation ()
   typ = xml.dom.DocumentType.DocumentType ("Envelope", [], [], "", "")
   # this is not the namespace required by soap 1.2
   # but the service requires this namespace (from error message)
   doc = dom.createDocument ("http://schemas.xmlsoap.org/soap/envelope/", "Envelope", typ)

   env = doc.getElementsByTagName ("Envelope")[0]
   bdy = doc.createElement ("Body")
   env.appendChild (bdy)

   msg = doc.createElementNS ("urn:testtrack-interface", "editDefect")
   bdy.appendChild (msg)

   eleCookie = doc.createElement("cookie")
   eleCookie.setAttributeNode( doc.createAttribute("xsi:type"))
   eleCookie.setAttribute("xsi:type", "xsd:int")
   msg.appendChild(eleCookie)
   eleCookie.appendChild(doc.createTextNode(str(p_session)))

   eleNum = doc.createElement("defectNumber")
   eleNum.setAttributeNode( doc.createAttribute("xsi:type"))
   eleNum.setAttribute("xsi:type", "xsd:int")
   msg.appendChild(eleNum)
   eleNum.appendChild(doc.createTextNode(str(p_defectnumber)))

   eleSummary = doc.createElement("summary")
   eleSummary.setAttributeNode( doc.createAttribute("xsi:type"))
   eleSummary.setAttribute("xsi:type", "xsd:string")
   msg.appendChild(eleSummary)
   eleSummary.appendChild(doc.createTextNode(""))

   eleDnldAtt = doc.createElement("bDownloadAttachments")
   eleDnldAtt.setAttributeNode( doc.createAttribute("xsi:type"))
   eleDnldAtt.setAttribute("xsi:type", "xsd:boolean")
   msg.appendChild(eleDnldAtt)
   eleDnldAtt.appendChild(doc.createTextNode("false"))

   # Let's lock the defect for editing!
   buffer = StringIO.StringIO()
   PrettyPrint(doc, buffer)
   request = buffer.getvalue()

   httpsvr = httplib.HTTPConnection(c_soapaddr, c_soapport)
   httpsvr.putrequest("POST", c_soapcgi)
   httpsvr.putheader("Content-Type", "text/xml")
   httpsvr.putheader("Content-Length", str(len(request)))
   httpsvr.endheaders()
   httpsvr.send(request)

   response = httpsvr.getresponse()
   respDefect = response.read()

   # Replace 'editDefectRequest' with 'saveDefect' and we're ready to use this block to save changes.
   dom = xml.dom.minidom.parseString(respDefect.replace("editDefectResponse", "saveDefect"))

   eleSaveDefect = dom.getElementsByTagName("ttns:saveDefect")
   if (len(eleSaveDefect) == 0):
      return # defect wasn't found!

   eleCookie = dom.createElement("cookie")
   eleCookie.setAttributeNode( dom.createAttribute("xsi:type"))
   eleCookie.setAttribute("xsi:type", "xsd:int")
   eleCookie.appendChild(dom.createTextNode(str(p_session)))
   eleSaveDefect[0].appendChild(eleCookie)

   eleDefect = dom.getElementsByTagName("pDefect")[0]

   # first, go through and fix all of the item tags to have all of the required child elements for events.
   # The response doesn't include 'value' child elements for date fields w/o a value set.
   eleEvents = dom.getElementsByTagName("eventlist")
   if (len(eleEvents) > 0):
      eleEvents = eleEvents[0]
      records = eleEvents.getElementsByTagName("fieldlist") # there are multiple item elements and we only care about custom fields.
      iRecCount = len(records)
      for item in records:
         recItems = item.getElementsByTagName("item")
         for itemField in recItems:
            if (itemField.getAttribute("xsi:type") == "ttns:CDateField"): # only handle DateField types
               eleValue = itemField.getElementsByTagName("value")
               if (len(eleValue) == 0):
                  eleValue = dom.createElement("value")
                  itemField.appendChild( eleValue)
                  eleValue.appendChild( dom.createTextNode("0001-01-01")) # element has to have a closing bracket, or TTP can't handle it.
               else:
                  eleTextNode = eleValue[0].childNodes
                  if (len(eleTextNode) == 0):
                     eleValue[0].appendChild( dom.createTextNode("0001-01-01")) # element has to have a closing bracket, or TTP can't handle it.

   # second, go through and fix all of the item tags to have all of the required child elements for custom fields.
   # The response doesn't include 'value' child elements for date fields w/o a value set.
   eleCustom = dom.getElementsByTagName("customFieldList")
   if (len(eleCustom) > 0):
      eleCustom = eleCustom[0]
      records = eleCustom.getElementsByTagName("item")
      iRecCount = len(records)
      for item in records:
         if (item.getAttribute("xsi:type") == "ttns:CDateField"): # only handle DateField types
            eleValue = item.getElementsByTagName("value")
            if (len(eleValue) == 0):
               eleValue = dom.createElement("value")
               item.appendChild( eleValue)
               eleValue.appendChild( dom.createTextNode("0001-01-01")) # element has to have a closing bracket, or TTP can't handle it.
            else:
               eleTextNode = eleValue[0].childNodes
               if (len(eleTextNode) == 0):
                  eleValue[0].appendChild( dom.createTextNode("0001-01-01")) # element has to have a closing bracket, or TTP can't handle it.

   eleSCCI = dom.getElementsByTagName("pSCCFileList")
   # If the defect has no SCC files attached, then the response will not include the pSCCIFileList element so create it here.
   if (len(eleSCCI) == 0):
      eleSCCI = dom.createElement("pSCCFileList")
      eleDefect.appendChild(eleSCCI)
      eleSCCI.setAttributeNode( dom.createAttribute("xsi:type"))
      eleSCCI.setAttribute("xsi:type", "SOAP-ENC:Array")
   else:
      eleSCCI = eleSCCI[0]

   # Now, go through and fix all of item tags to have all of the required child elements. The
   # response doesn't include child elements of item if the value is blank ie. m-strFixedRevision
   # will not have an element for an item where it has no value. This won't work on the save, so fix it here.
   records = eleSCCI.getElementsByTagName("item")
   iRecCount = len(records)
   for item in records:
      eleFRev = item.getElementsByTagName("m-strFixedRevision")
      if (len(eleFRev) == 0):
         eleFRev = dom.createElement("m-strFixedRevision")
         item.appendChild( eleFRev)
         eleFRev.setAttributeNode( dom.createAttribute("xsi:type"))
         eleFRev.setAttribute("xsi:type", "xsd:string")
         eleFRev.appendChild( dom.createTextNode(""))

      eleFDate = item.getElementsByTagName("m-dateFixedTimestamp")
      if (len(eleFDate) == 0):
         eleFDate = dom.createElement("m-dateFixedTimestamp")
         item.appendChild( eleFDate)
         eleFDate.setAttributeNode( dom.createAttribute("xsi:type"))
         eleFDate.setAttribute("xsi:type", "xsd:dateTime")
         eleFDate.appendChild( dom.createTextNode(""))

   # Ok! The saveDefect request is now valid. All we have to do now is update it based on the current file.

   records = eleSCCI.getElementsByTagName("item")
   fFound = 0
   if (len(records) > 0):
      for item in records:
         eleFName = item.getElementsByTagName("m-strFileName")
         if (len(eleFName) > 0):
            if (eleFName[0].firstChild.nodeValue == p_file):
               # file is already attached, so just add to the rev and make sure all of the elements are present
               fFound = 1
               eleFRev = item.getElementsByTagName("m-strFixedRevision")
               if (len(eleFRev) == 0):
                  eleFRev = dom.createElement("m-strFixedRevision")
                  item.appendChild( eleFRev)
                  eleFRev.setAttributeNode( dom.createAttribute("xsi:type"))
                  eleFRev.setAttribute("xsi:type", "xsd:string")
                  eleFRev.appendChild( dom.createTextNode(str(p_revnum)))
               else:
                  if (len(eleFRev[0].firstChild.nodeValue) > 0):
                     eleFRev[0].firstChild.nodeValue = eleFRev[0].firstChild.nodeValue + ", " + str(p_revnum)
                  else:
                     eleFRev[0].firstChild.nodeValue = str(p_revnum)

   if fFound == 0: # File not already attached to defect, so let's add it here.
      iRecCount = iRecCount + 1
      eleItem = dom.createElement("item")
      eleSCCI.appendChild( eleItem)
      eleItem.setAttributeNode( dom.createAttribute("xsi:type"))
      eleItem.setAttribute("xsi:type", "ttns:CSCCFileRecord")

      eleRecId = dom.createElement("recordid")
      eleItem.appendChild( eleRecId)
      eleRecId.setAttributeNode( dom.createAttribute("xsi:type"))
      eleRecId.setAttribute("xsi:type", "xsd:int")
      eleRecId.appendChild( dom.createTextNode("0"))

      eleFName = dom.createElement("m-strFileName")
      eleItem.appendChild( eleFName)
      eleFName.setAttributeNode( dom.createAttribute("xsi:type"))
      eleFName.setAttribute("xsi:type", "xsd:string")
      eleFName.appendChild( dom.createTextNode(p_file))

      eleFRev = dom.createElement("m-strFixedRevision")
      eleItem.appendChild( eleFRev)
      eleFRev.setAttributeNode( dom.createAttribute("xsi:type"))
      eleFRev.setAttribute("xsi:type", "xsd:string")
      eleFRev.appendChild( dom.createTextNode(str(p_revnum)))

      eleFDate = dom.createElement("m-dateFixedTimestamp")
      eleItem.appendChild( eleFDate)
      eleFDate.setAttributeNode( dom.createAttribute("xsi:type"))
      eleFDate.setAttribute("xsi:type", "xsd:dateTime")
      eleFDate.appendChild( dom.createTextNode((p_fixdate)))

   # Update the pSCCIFileList element's type attribute to correctly reflect how many files are attached.
   eleSCCI.setAttributeNode( dom.createAttribute("SOAP-ENC:arrayType"))
   eleSCCI.setAttribute( "SOAP-ENC:arrayType", "ttns:CSCCFileRecord[" + str(iRecCount) + "]")

   # Let's save the defect!
   buffer = StringIO.StringIO ()
   PrettyPrint (dom, buffer)
   request = buffer.getvalue ()
   #print request #DEBUG

   httpsvr = httplib.HTTPConnection(c_soapaddr, c_soapport)
   httpsvr.putrequest("POST", c_soapcgi)
   httpsvr.putheader("Content-Type", "text/xml")
   httpsvr.putheader("Content-Length", str(len(request)))
   httpsvr.endheaders()
   httpsvr.send(request)

   resp = httpsvr.getresponse()
   print "saveDefect", resp.status, resp.reason

def main(argv=None):
   if argv is None:
      argv = sys.argv
   try:
      try:
         log = file('C:defectlist.log', 'w')
         log.write("starting...")
         m_branch = os.environ.get("SSCM_BRANCH")
         m_repo = os.environ.get("SSCM_REPOSITORY")
         m_file = os.environ.get("SSCM_FILE")
         m_version = os.environ.get("SSCM_FILEVERSION")
         m_date = os.environ.get("SSCM_DATE")
         m_comments = os.environ.get("SSCM_COMMENT")

         log.write(m_branch + "n")
         log.write(m_repo + "n")
         log.write(m_file + "n")
         log.write(m_version + "n")
         log.write(m_date + "n")
         log.write(m_comments + "n")

         sccFile = m_branch + "::" + m_repo + m_file

         opts, args = getopt.getopt(argv[1:], "h", ["help"])
         m_defects = parseComments(m_comments)

         for defect in m_defects:
            log.write(defect + "n")
            try:
               session = login("Administrator", "password")
               if session > 0:
                  updateDefect(session, defect, m_version, sccFile,m_date)
                  logout(session)
            except getopt.error, msg:
               raise Usage(msg)      

      except getopt.error, msg:
         raise Usage(msg)
         # more code, unchanged
   except Usage, err:
      print >>sys.stderr, err.msg
      print >>sys.stderr, "for help use --help"
      return 2

if __name__ == "__main__":
   sys.exit(main())