Development system providing methods for managing different versions of objects with a meta model6112024Abstract An "Object Cycle" versioning system having an Object Cycle Server is described. The server communicates over a wire or a network for providing versioning services to multiple clients. During a user session, a user invokes operation of the system from within the development environment of the particular client being used. From the perspective of the Object Cycle Server, each client is simply "a client" (without regard to proprietary nature) which desires to store "an object." The Object Cycle Server, in turn, maps the object into a meta model which serves as a container for facilitating version control. With the model, therefore, operations supported by the system for versioning will execute correctly even if the objects are stored in a format other than a relational database, such as an object-oriented database, a file server, or other storage system. The model separates out the name of an object from where the object itself is actually stored. As additional versions of the object are created, the number of object instances increases. Once an instance has been created, versioning activities can be undertaken, such as checking in, checking out, and the like--operations which are atomic at the object level. By separating out these areas of functionality into (conceptually) different nodes of a meta model, system performance is enhanced. Claims What is claimed is: Description COPYRIGHT NOTICE
__________________________________________________________________________
1:
CAMUS.sub.-- RC
2:
ciAddObject(
SESSIONHDL
sessionHdl
3:
, PiSZ piszName
4:
, PiSZ comments
5:
, cFORMAT format
6:
, cACCESSPOLICY
policy
7:
, PcBLOB pBlob
8:
, PiSZ optionalOwner
9:
, LONG flags
10:
)
11:
{
12:
CAMUS.sub.-- RC
rc = CAMUS.sub.-- SUCCESS;
13: cBLOB bufferedBlob = NULL.sub.-- BLOB;
14:
15: CamusTryExcept
16: {
17: /*
18: * Check if the blob has been buffered
19: */
20: if( BLOB.sub.-- IS.sub.-- BUFFERED(pBlob) )
21: {
22: rc = cGetBufferedBlob( sessionHdl, &bufferedBlob );
23:
24: if ( rc == CAMUS.sub.-- SUCCESS )
25: {
26: CAMUS.sub.-- ASSERT( bufferedBlob.size == pBlob->size );
27: pBlob->pContents = bufferedBlob.pContents;
28: }
29: }
30:
31: if ( rc == CAMUS.sub.-- SUCCESS )
32: {
33: rc = cAddObject(
sessionHdl
34: , piszName
35: , comments
36: , format
37: , policy
38: , pBlob
39: , optionalOwner
40: , flags
41: ) ;
42: }
43:
44: /*
45: * If our contents were really buffered here as opposed to
46: * having come over the wire, we need to free it ourselves
47: */
48: if ( bufferedBlob.pContents != NULL )
49: cFree( bufferedBlob.pContents );
50: }
51: CamusExcept( CamusHandleException )
52: {
53: // Post Error Msg to error Mgr ( File & Screen )
54: rc = camErrorRegisterMsgByKey(
(PCCB) sessionHdl
55: , cSERVER.sub.-- EXCEPTION
56: , NO.sub.-- PREV.sub.-- ERROR
57: , ERR.sub.-- INTERNAL
58: , CamusExceptionCode
59: , cpActionTypeToString( cADD )
60: ) ;
61: cpUnlockSession( sessionHdl );
62: }
63: CamusEndExcept
64:
65: return( rc );
66:
}
__________________________________________________________________________
(line numbers added for clarity of the following description)
As shown, the method call is invoked with eight parameters. The first parameter, sessionHdl, is a session handle. This parameter references general session information; it is initialized upon the client first connecting to the system. More particularly, the session handle references a connection block characterizing a particular connection. The connection block may be represented by a connection block data structure as follows.
______________________________________
typedef struct .sub.-- camusConnectionBlock
CAMUSID userID;
cUSERROLE userRole;
iUSERNAMEBUF userNameBuf;
CAMUSID currProjectID;
CAMUSID currProjectVID;
CAMUSID rootNodeID;
CAMUSID currNodeID;
CAMUSID projSystemNodeID;
PSZ pszLanguage;
cACTIONTYPE currAction;
time.sub.-- t lastActionTime;
CAMUS.sub.-- CRITICAL.sub.-- SECTION
userSync;
CCBFLAGS flags;
PIDARRAY pNodeIDArray;
} CCB, *PCCB;
______________________________________
As shown, the first data member of the connection block is userID--an identifier or ID for the user. The second data member, userRole, represents a role for the user. The user role data member represents an enumerated type. In an exemplary embodiment, the following types are defined.
______________________________________
typedef enum .sub.-- userRole
cUSER = 0x00001,
cADMINISTRATOR = 0x00011,
cINACTIVE = 0x00021,
cMAXUSERROLE = 0x00031
} cUSERROLE;
______________________________________
For instance, the user may assume the role of "administrator." A user without administrative privileges, on the other hand, is simply a "user." The third data member of the connection block is userNameBuf. This references a buffer storing a text string of the user's name (e.g., "Ken"). The next several data members store various IDs. The first one, currProjectID, stores an ID for the current project. The next data member, currProjectVID, stores a version ID for the project, for supporting multiple versions of a given project. The rootNodeID data member stores an ID for the root node, for identifying a particular root directory (disk subdirectory) for the project. In a corresponding manner, the currNodelD data member identifies a current object which exists within the project hierarchy. The projSystemNodeID data member stores an identifier for a system node, for storing system objects (e.g., labels or aliases for objects). The pszLanguage data member stores a string indicating a current language employed by the system. In a default setting, the system stores "English" for this data member. This information is then employed at runtime for appropriate handling of locale-specific information. The currAction data member stores information describing the current action undertaken by the user (e.g., AddObject). Time stamp information for user actions is stored by the lastActionTime data member. The userSync data member maintains synchronization information, for permitting the system to operate in a multi-user environment. The flaps data member stores miscellaneous flags or housekeeping data. Finally, the pNodeIDArray data member references an array storing node IDs, for indicating the current path in the project hierarchy or tree (from the root node to the current node). Returning to the description of the parameters passed to the ciAddObject method, the second parameter, piszName, stores a name for the object to be added (e.g., "myfile.cpp"). In a similar manner, the comments parameter stores a string comprising user-supplied comments for the object to be stored. The format parameter specifies a format, such as text or binary, for the object to be stored. These and other types may be defined as follows.
______________________________________
/*
* Object format
*/
typedef enum .sub.-- objectFormat
cTEXT = 0x0000,
cBINARY = 0x0001,
cMAX.sub.-- FORMAT
= 0x0002
} cFORMAT;
______________________________________
The policy data member describes an access policy for the object to be stored. For instance, a policy of read-only, writeable, or versioned may be defined as follows.
______________________________________
/*
* Object access policy
*
* READONLY
Once added, object may not be overwritten
* WRITEABLE
Object may be overwritten by cPutObject()
* VERSIONED
Object must be checked in and out to be modified
*/
typedef enum .sub.-- accessPolicy
cREADONLY = 0x0000,
cWRITEABLE = 0x0001,
cVERSIONED = 0x0002,
cMAX.sub.-- POLICY
= 0x0003
} cACCESSPOLICY;
______________________________________
The sixth parameter of ciAddObject, pBlob, comprises a pointer to a cBlob data structure defined as follows.
______________________________________
/* size is 8 */
typedef struct .sub.-- cBlob
cSIZE size;
/* [size.sub.-- is] [length.sub.-- is] */ PcCONTENTS pContents;
} cBLOB;
/* size is 4 */
typedef struct .sub.-- cBlob .sub.-- RPC.sub.-- FAR *PcBLOB;
______________________________________
The pBlob pointer, therefore, references a blob record storing a size for the object-to-be-stored together with the actual contents of that object. The optionalOwner data member comprises a text string specifying an owner for the object being added. By default (i.e., if another user is not specified), the current user is the owner. Finally, the flags parameter serves to store miscellaneous flags or housekeeping information. After initializing local variables at lines 12-13, the method checks whether the blob (actual object) has been buffered, at lines 17-29. In the event that the blob is correctly buffered, tested at line 31, the method calls on to the cAddObject method call, at lines 33-41. In essence, the ciAddObject call exists in a high-level layer. After it has validated parameter or argument information, it calls onto a lower-level layer, as illustrated by the call to cAddObject which resides in the next layer. The cAddObject method itself may be implemented as follows.
__________________________________________________________________________
1:
/*
2:
* cAddObject()
3:
*
4:
* Add an object in the current node and project.
5:
*
6:
*/
7:
CAMUSAPI cAddObject(
SESSIONHDL
sessionHdl
8:
, PiSZ piszName
9:
, PiSZ pComments
10:
, cFORMAT format
11:
, cACCESSPOLICY
policy
12:
, PcBLOB pBlob
13:
, PiSZ piszOptionalOwner
14:
, LONG flags
15:
)
16:
{
17: CAMUS.sub.-- RC
rc;
18: PCCB pccb;
19: CAMUSID
ownerID = NULL.sub.-- CAMUSID;
20:
21: // Log event
22: CAMUS.sub.-- MESSAGE( cpActionTypeToString( cADD ) );
23:
24: // Lock session
25: rc = cpLockSession( sessionHdl, &pccb, FALSE );
26: if ( rc != CAMUS.sub.-- SUCCESS )
27: {
28: return( rc );
29: }
30:
31: /*
32: * We have a valid session handle; validate arguments
33: */
34: while( TRUE )
35: {
36: /*
37: * Check action, blob name, blob, format,
38: * policy, and optional owner
39: */
40:
41: if ( !VALID.sub.-- ACTION( pccb, cADD ) )
42: {
43: rc = ACTION.sub.-- ERROR;
44: break;
45: }
46: if ( !VALID.sub.-- NAME( piszName ) )
47: {
48: rc = NAME.sub.-- ERROR( MAX.sub.-- NAMELEN );
49: break;
50: }
51: /*
Only simple names within the current node
52: are currently allowed */
53: if
( !VALID.sub.-- SIMPLE.sub.-- NAME( piszName ))
54: {
55: rc = camInfoRegisterMsgByKey(
pccb
56: , cARG.sub.-- BAD.sub.-- SIMPLE.sub.-- NAME
57: , NO.sub.-- PREV.sub.-- ERROR
58: , ERR.sub.-- ARG.sub.-- BAD.sub.-- CAMUS.sub.--
NAME
59: ) ;
60: break;
61: }
62: if ( !pBlob .vertline..vertline. ! (pBlob->pContents) )
63: {
64: rc = camInfoRegisterMsgByKey(
pccb
65: , cARG.sub.-- BAD.sub.-- BLOB
66: , NO.sub.-- PREV.sub.-- ERROR
67: , ERR.sub.-- ARG.sub.-- BAD.sub.-- ARGUMENT
68: ) ;
69: break;
70: }
71: if ( !VALID.sub.-- FORMAT( format ))
72: {
73: rc = camInfoRegisterMsgByKey(
pccb
74: , cARG.sub.-- BAD.sub.-- FORMAT
75: , NO.sub.-- PREV.sub.-- ERROR
76: , ERR.sub.-- ARG.sub.-- BAD.sub.-- ARGUMENT
77: ) ;
78: break;
79: }
80: if ( !VALID.sub.-- POLICY( policy ))
81: {
82: rc = camInfoRegisterMsgByKey(
pccb
83: , cARG.sub.-- BAD.sub.-- POLICY
84: , NO.sub.-- PREV.sub.-- ERROR
85: , ERR.sub.-- ARG.sub.-- BAD.sub.-- ARGUMENT
86: ) ;
87: break;
88: }
89: if ( piszOptionalOwner )
90: {
91: if ( VALID.sub.-- USER.sub.-- NAME( piszOptionalOwner ) )
92: {
93: rc = clGetUserIDByName( pccb,
94: piszOptionalOwner, &ownerID );
95: }
96: else
97: {
98: rc = camInfoRegisterMsgByKey(
pccb
99: , cARG.sub.-- BAD.sub.-- USER.sub.-- NAME
100: , rc
101: , ERR.sub.-- ARG.sub.-- BAD.sub.-- USER.sub.--
NAME
102: , MAX.sub.-- USER.sub.-- NAMELEN
103: ) ;
104: }
105:
106: if ( rc != CAMUS.sub.-- SUCCESS ) break;
107: }
108: // Call onto cpAddObject (workhorse method)
109: rc = cpAddObject(
pccb
110: , piszName
111: , pComments
112: , format
113: , policy
114: , cUSER.sub.-- DATA
115: , cACTIVE.sub.-- NAME
116: , pBlob
117: , NULL.sub.-- CAMUSID
118: , ownerID
119: , NULL.sub.-- CAMUSID );
120:
121: /*
122: * XXX turn around and check this blob out to the
123: * user (or owner?) if flag is set
124: */
125: break;
126: }
127:
128: cpUnlockSession( sessionHdl );
129: return( rc );
130:
}
__________________________________________________________________________
As shown, the parameters to the ciAddObject method call are passed to the cAddObject method call. After declaring local variables at lines 17-19, the method logs an "add" event, at lines 21-22. At lines 24-29, the method locks a session, for obtaining access to shared data structures. If a lock cannot be obtained successfully (tested at line 26), the method returns at line 28. Otherwise, the session handle has been validated and the method may now proceed to validate arguments. At lines 31-126, the method establishes a loop for checking the arguments; specifically, it validates the passed-in action, blob name, blob (data), format, policy, and optional owner. If an error occurs, a return or result condition variable, rc, is set to the value for the error code. After the arguments have been tested and validated, the method calls on to the cpAddObject method, at lines 109-119. The cpAddObject method is the workhorse routine for adding an object to the repository. In an exemplary embodiment, the method may be constructed as follows.
__________________________________________________________________________
1:
/*
2:
* cpAddObject()
3:
*
4:
*/
5:
CAMUSAPI cpAddObject(
PCCB pccb
6:
, PiSZ piszName
7:
, PiSZ piszComments
8:
, cFORMAT format
9:
, cACCESSPOLICY
policy
10:
, cOBJECTTYPE
type
11:
, cNAMESTATUS
nameStatus
12:
, PcBLOB pBlob
13:
, CAMUSID optionalNodeID
14:
, CAMUSID optionalOwnerID
15:
, CAMUSID optionalProjectVID
16:
)
17:
{
18: CAMUS.sub.-- RC
rc;
19: PDSCONN pDSConn;
20: CAMUSID entityID;
21: CAMUSID actionID;
22: VERSRECORD
versRec = { 0 };
23: CAMUSID ownerID = (optionalOwnerID == NULL.sub.-- CAMUSID)
24: ? pccb->userID
25: : optionalOwnerID;
26:
27:
if (CAMUS.sub.-- SUCCESS != (rc = dsGetConnection(
pccb
28:
, &pDSConn
29:
, ISOLATION.sub.-- 0 )))
30: {
31: return( rc );
32: }
33:
34: /*
35: * Ok, to add an entity, we do the following:
36: *
37: *
1. Add the entity record
38: *
2. Add the name record in the current node
39: *
3. Add an action record
40: *
4. Add the first version record; this process
41: *
will also update appropriate project currency
42: *
and base links within the version "tree"
43: *
5. If all that succeeds, we update the name cache
44: *
and then commit these db operations; otherwise
45: *
we rollback the whole mess.
46: */
47: while( TRUE )
48: {
49: rc = dbpAddEntity(
pccb
50: , pDSConn
51: , format
52: , type
53: , policy
54: , ownerID
55: , &entityID );
56:
57: if ( rc != CAMUS.sub.-- SUCCESS ) break;
58:
59: rc = dbpAddName(
pccb
60: , pDSConn
61: , piszName
62: , optionalNodeID
63: == NULL.sub.-- CAMUSID ? pccb->currNodeID
64: : optionalNodeID
65: , entityID
66: , nameStatus );
67:
68: if ( rc != CAMUS.sub.-- SUCCESS ) break;
69:
70: rc = dbpAddAction(
pccb
71: , pDSConn
72: , piszComments
73: , &actionID );
74:
75: if ( rc != CAMUS.sub.-- SUCCESS ) break;
76:
77: /*
78: * Add the first version of this guy into the
79: * appropriate project;
80: * by default, we start a trunk version with
81: * a revision of 0
82: */
83: versRec.entityID
= entityID;
84: versRec.actionID
= actionID;
85: versRec.ancestorVID
= NULL.sub.-- CAMUSID;
86: versRec.versDesc.major
= 1;
87: versRec.versDesc.distance
= 0;
88: versRec.versDesc.revision
= 0;
89:
90: rc = dbpAddVersion (
pccb
91: , pDSConn
92: , &versRec
93: , optionalProjectVID
94: , pBlob );
95:
96: if ( rc != CAMUS.sub.-- SUCCESS ) break;
97:
98: /*
99: * Update cache last!
100: */
101: rc = nsAddName(
pccb
102: , piszName
103: , entityID
104: , clEntityTypeToNameType( type )
105: , nameStatus
106: , optionalNodeID );
107: break;
108: }
109:
110: if ( rc == CAMUS.sub.-- SUCCESS )
111: {
112: CAMUS.sub.-- SUCCESS == dsCommitTran( pccb, pDSConn );
113: }
114: else
115: {
116: CAMUS.sub.-- SUCCESS == dsRollbackTran( pccb, pDSConn );
117: }
118:
119: CAMUS.sub.-- SUCCESS == dsReturnConnection( pDSConn );
120: return( rc );
121:
}
__________________________________________________________________________
The method is invoked with the previously-described parameters, together with additional parameters for specifying status and ID information. After declaring local variables, at lines 18-25, the method establishes a connection, at lines 27-29. If a connection cannot be successfully obtained (as tested at line 27), the method returns at line 31. If the method has not terminated, it is ready to add an entity to the repository. The specific method steps for adding the entity are as follows. At lines 49-55, the method adds an "entity" record to the repository, specifying, for instance, the format, type, policy, owner ID, and entity ID for the object. A name record for the current node is then added, at lines 59-66. An action record is added at lines 70-73. A version record is added at lines 90-94; completion of individual version fields is shown at lines 83-88. If the method has been successful in adding these records (i.e., entity record, name record, action record, and version record), then it updates the name cache (cache 835 from FIG. 8) at lines 98-108 and commits the transaction at line 112. Otherwise (i.e., the return code is not equal to "success" at line 110), the method rolls back the transaction, as shown at line 116. To conclude the method, the connection is dropped or returned at line 119, and the return or result code is returned to the caller, at line 120. The calls to add the various records (i.e., the dbp- calls at line 49, 59, 70, and 90) invoke the database primary layer (layer 850 in FIG. 8). The dbpAddEntity, which serves to add an entry in the entity table, may be constructed as follows.
__________________________________________________________________________
1:
/*
2:
* dbpAddEntity()
3:
*
4:
* Add an entry in the entity table
5:
*/
6:
CAMUSAPI dbpAddEntity(
PCCB pccb
7:
, PDSCONN pDSConn
8:
, cFORMAT format
9:
, cOBJECTTYPE
type
10:
, cACCESSPOLICY
policy
11:
, CAMUSID ownerID
12:
, CAMUSID *pEntityID
13:
)
14:
{
15: CHAR cmdBuf[ CMDBUFLEN ],
16: valuesBuf[ VALUESBUFLEN ];
17: LONG dupkeyRetries = 0;
18: CAMUS.sub.-- RC
rc;
19:
20: *pEntityID = newCamusID();
21:
22: // Prepare SQL statement
23:
24: sprintf(
valuesBuf
25: , "%ld, %d, %d, %d, %ld"
26: , *pEntityID
27: , format
28: , type
29: , policy
30: , ownerID
31: ) ;
32: sprintf(
cmdBuf
33: , INSERT.sub.-- CMD
34: , ENTITIES.sub.-- TABLE
35: , valuesBuf
36: ) ;
37:
38: while( dupkeyRetries++ < DUPKEY.sub.-- RETRY.sub.-- LIMIT )
39: {
40: // Execute SQL Statement
41: rc = dsExecuteCmd(
pccb
42: , pDSConn
43: , cmdBuf
44: , NULL
45: , NULL
46: , NULL
47: , NULL
48: ) ;
49:
50: if ( GET.sub.-- ERROR.sub.-- CODE( rc ) == ERR.sub.-- DS.sub.--
DUPKEY )
51: {
52: CAMUS.sub.-- TRACE("Duplicate key hit; retrying...");
53: CAMUS.sub.-- ASSERT( CAMUS.sub.-- SUCCESS == camErrorClear( pccb,
rc ));
54: (*pEntityID) += (CAMUSID) rand();
55: sprintf(
valuesBuf
56: , "%ld, %d, %d, %d, %ld"
57: , *pEntityID
58: , format
59: , type
60: , policy
61: , ownerID
62: ) ;
63: sprintf(
cmdBuf
64: , INSERT.sub.-- CMD
65: , ENTITIES.sub.-- TABLE
66: , valuesBuf
67: ) ;
68:
69: continue;
70: }
71:
72: break;
73: }
74:
75: return( rc );
76:
}
__________________________________________________________________________
After declaring local variables at lines 15-18, the method initializes an identifier or ID for the entity record, at line 20. Now, the method may undertake preparation of an appropriate SQL statement. At lines 24-31, the method constructs a string (valuesBuj) which stores the entity ID, format, type, policy, and owner ID for the entity to be added. Next, the method constructs a command string (cmdBuf) which adds to the above-constructed string a SQL command (here, "INSERT" ), together with the name of the table to insert into (here, "ENTITIES.sub.-- TABLE" ). After the SQL statement has been constructed, the method is now ready to submit the statement for execution. This is shown at lines 38-73. Specifically, the SQL statement (command buffer or cmdBuf) is passed, together with a (pointer to) connection descriptor, to the dsExecuteCmd routine, as shown at lines 41-48. This call performs the actual insertion of the entities record. Upon completion of the call, a return or result code (rc) is returned, for reporting the success of the operation. Error processing (e.g., attempting to insert a duplicate key value) is handled at lines 50-70. The method will generate a new entity ID for each repeat attempt. After successful completion of insertion of the record or after reaching a "retry limit," the method proceeds to line 75 to return the result code. Here, the method returns to the caller, the cpAddObject method. It, in turn, will return to its caller and so forth and so on until control is; ultimately returned to the ciAddObject method. After performing housekeeping and cleanup steps, the ciAddObject method concludes by returning a result code to its caller, for indicating the success of adding the object. 2. Checking Out an Object A request by a client to check out an object is first received by the communication layer 810, shown previously in FIG. 8. It, in turn, passes the request to the change control manager or module 825, for changing control. The change control manager 825 validates the arguments and then passes the request to the primary layer 830. After examining entity information about the object from cache 835, the primary layer 830 will invoke the database primary layer 850 for preparing an SQL statement which requests the object. The prepared SQL statement is, in turn, executed by the database integrity layer 860. This selects the appropriate object from the database table 870. As shown in FIG. 12, this operation--"Checkout Object" method 1200--may be implemented as a specific sequence of method calls for checking out an object: ciCheckOutObject 1210 calls ciCheckOutObject 1220 which, in turn, calls cpCheckOutObject 1230 which, in turn, calls ciCheckOutBlob 1240 which, in turn, calls ciReserveCurrent 1250. Implementation of these methods will now be described in further detail. As illustrated, the ciCheckOutObject method is the main entry point for checking out objects. In an exemplary embodiment, the method may be constructed as follows.
__________________________________________________________________________
1:
// Checkout Object
2:
3:
CAMUS.sub.-- RC
4:
ciCheckOutObject(
SESSIONHDL
sessionHdl
5:
, PiSZ piszName
6:
, PcBLOB pBlob
7:
, ULONG maxBytes
8:
)
9:
{
10: CAMUS.sub.-- RC
rc;
11:
12: CamusTryExcept
13: {
14: rc = cCheckOutObject(
sessionHdl
15: , piszName
16: , pBlob
17: ) ;
18:
19: /*
20: * Do we need to buffer this blob for the client ?
21: */
22: if ( rc == CAMUS.sub.-- SUCCESS && (BUFFER.sub.-- BLOB(pBlob,
maxBytes)) )
23: {
24: rc = cBufferBlob(
sessionHdl
25: , maxBytes
26: , pBlob->size
27: , pBlob->pContents
28: , cBUFFER.sub.-- FOR.sub.-- READ );
29:
30: /* Alert client that we've buffered the blob for him/her to
31: * come get in chunks by nulling the contents but leaving the
32: * size
33: */
34: pBlob->pContents = NULL;
35: }
36: }
37: CamusExcept( CamusHandleException )
38: {
39: rc = camErrorRegisterMsgByKey(
(PCCB) sessionHdl
40: , cSERVER.sub.-- EXCEPTION
41: , NO.sub.-- PREV.sub.-- ERROR
42: , ERR.sub.-- INTERNAL
43: , CamusExceptionCode
44: , cpActionTypeToString( cCHECKOUT
45: ) ;
46: cpUnlockSession( sessionHdl );
47: }
48: CamusEndExcept
49:
50: return( rc );
51:
}
__________________________________________________________________________
The method is invoked with four arguments or parameters. The first parameter, sessionHdl, is a session handle--that is, a data member referencing descriptor information for the current session. The next three parameters characterize the object to check out. The piszName parameter, for instance, stores a string comprising a text name for the object. The third parameter., pBlob, comprises a pointer to a memory location for receiving the object (which is to be received as a binary large object or blob). The final parameter, maxBytes, stores the size (byte count) for the object. After initializing a local variable at line 10, the method invokes the cCheckOutObject method, at lines 14-17. Here, cCheckOutObject is invoked with the above-described session handle (sessionHdl), name (piszName), and pointer to blob (pBlob). At line 22, the method tests whether it needs to buffer the blob for the client (such as a client limited to 64K memory blocks); this is done only if the call to check out the blob was successful. The specific call to buffer the blob, cBufferBlob, occurs at lines 24-28. By setting the contents of the blob to NULL, the method marks the blob as one which has been buffered for the client. At line 44, the method registers an action type of"checkout" by calling the method cpActionTypeToString. This method call is itself nested within an error registration method, camErrorRegisterMsgByKey, as set forth beginning at line 39. Upon completion of posting the action string, the method can unlock the session (shown at line 46) and return the result to its caller, as shown at line 50. The cCheckOutObject method call will now be described in further detail. In an exemplary embodiment, the method may be constructed as follows.
__________________________________________________________________________
1:
/*
2:
* cCheckOutObject ()
3:
*
4:
* Reserve and get a blob for the user
5:
*/
6:
CAMUSAPI cCheckOutObject(
SESSIONHDL
sessionHdl
7:
, PiSZ piszName
8:
, PcBLOB pBlob
9:
)
10:
{
11: CAMUS.sub.-- RC
rc;
12: PCCB pccb;
13:
14: // Post aa action msg
15: CAMUS.sub.-- MESSAGE( cpActionTypeToString( cCHECKOUT ) );
16:
17: rc = cpLockSession( sessionHdl, &pccb, FALSE );
18: if ( rc != CAMUS.sub.-- SUCCESS )
19: {
20: return( rc );
21: }
22:
23: /*
24: * We have a valid session handle; validate arguments
25: */
26: while( TRUE )
27: {
28: /*
29: * Check action, blob name
30: */
31: if ( !VALID.sub.-- ACTION( pccb, cCHECKOUT ) )
32: {
33: rc = ACTION.sub.-- ERROR;
34: break;
35: }
36: if ( !VALID.sub.-- FULLNAME( piszName ) )
37: {
38: rc = NAME.sub.-- ERROR( MAX.sub.-- FULL.sub.-- NAMELEN );
39: break;
40: }
41: if ( !pBlob )
42: {
43: rc = camInfoRegisterMsgByKey(
pccb
44: , cARG.sub.-- BAD.sub.-- BLOB
45: , NO.sub.-- PREV.sub.-- ERROR
46: , ERR.sub.-- ARG.sub.-- BAD.sub.-- ARGUMENT
47: ) ;
48: break;
49: }
50:
51: rc = cpCheckOutObject( pccb, piszName, pBlob );
52:
53: break;
54: }
55:
56: cpUnlockSession( sessionHdl );
57: return( rc );
58:
}
__________________________________________________________________________
After initializing local variables at lines 11-12, the method posts a "CHECKOUT" action string at line 15. Next, at line 17, the method asserts a session lock, for protecting shared data structures. In the event that a lock cannot be successfully asserted, the method returns at line 20. Otherwise, a valid session handle exists and the method may proceed to validate arguments, as indicated by lines 23-54. The steps for validation are as follows. At lines 28-35, the method validates the action (i.e., "CHECKOUT"). At lines 36-40, the method validates the passed-in name. At lines 41-49, the method validates the blob. If all of these validations had been successfully completed, the method may, in turn, invoke the cpCheckOutObject method call, as shown at line 51. The cpCheckOutObject method call serves to resolve the object name (into an identifiable object which may be checked out). After the call completes, the cCheckOutObject method unlocks the session at line 56 and then returns the result code at line 57. The cpCheckOutObject method itself may be constructed as follows.
__________________________________________________________________________
1:
/*
2:
* cpCheckOutObject()
3:
*
4:
* Resolve the name and invoke the camlow checkout function to
5:
* do the work
6:
*/
7:
CAMUSAPI cpCheckOutObject(
PCCB pccb
8:
, PiSZ piszName
9:
, PcBLOB
pBlob
10:
)
11:
{
12: CAMUS.sub.-- RC
rc;
13: CAMUSID
entityID;
14:
15: /*
16: * Resolve this name down to an entity ID
17: * Fills the entityID and gives us back.
18: */
19: if (CAMUS.sub.-- SUCCESS != (rc = nsResolveActiveName(
pccb
20: , piszName
21: , NULL.sub.-- CAMUSID
22: , &entityID )))
23: {
24: return( rc );
25: }
26:
27: return( clCheckOutBlob( pccb, entityID, pBlob ) );
28:
}
__________________________________________________________________________
The method essentially serves as a wrapper to another method, nsResolveActiveName, which is invoked at lines 19-22. Based on the passed-in name (piszName), the nsResolveActiveName method call returns an entity ID (by writing to the entity ID parameter which is passed by reference). If the name cannot be successfully resolved, the method will return an error code at line 24. Otherwise, the method proceeds to line 27 to call a lower-level routine, clCheckOutBlob. The clCheckOutBlob method itself may, in turn, be implemented as follows.
__________________________________________________________________________
1:
/*
2:
* clCheckOutBlob()
3:
*
4:
* This routine does most of the work in reserving and retrieving
5:
* a blob; first attempt to reserve it and then commit the
6:
* reservation if it succeeds; then go about the business of
7:
* getting the blob
8:
*/
9:
CAMUSAPI clCheckOutBlob(
PCCB pccb
10:
, CAMUSID
entityID
11:
, PcBLOB
pBlob
12:
)
13:
{
14: CAMUS.sub.-- RC
rc;
15: PDSCONN pDSConn;
16: ENTITYINFO
entityInfo = { 0 };
17: VERSRECORD
versRec = { 0 };
18:
19: /*
20: * This ISO level will leave a read lock on the current
21: * version link until we've committed or rolled back; we
22: * don't need the extra phantom protection (ISO 3 ) here,
23: * though. (Of course, realistically, the only processes
24: * that might bump that current version pointer would be
25: * checkin, which wouldn't get far anyway because this
26: * guy isn't checked out yet. So we're sort of inherently
27: * safe because of the co/ci protocol. Maybe we can put
28: * this back to ISO 1 if throughput becomes an issue...?
29: */
30: if (CAMUS.sub.-- SUCCESS != (rc = dsGetConnection(
pccb
31: , &pDSConn
32: , ISOLATION.sub.-- 2 )))
33: {
34: return( rc );
35: }
36:
37: /*
38: * To reserve a version, validate that the policy is
39: * VERSIONED (we should probably do this up in cpCheckOutBlob()
40: * but we don't have a ds connection there...although maybe
41: * we should.. ), then we have to add an action, determine
42: * the current version, then reserve it
43: */
44: while( TRUE )
45: {
46: /*
47: * Validate that the access policy is VERSIONED
48: */
49: if (CAMUS.sub.-- SUCCESS != (rc = dbpGetEntityInfo(
pccb
50: , pDSConn
51: , entityID
52: , &entityInfo
53: )))
54: break;
55:
56: if ( entityInfo.policy != cVERSIONED )
57: {
58: // Post msg that entity is not versioned
59: rc = camInfoRegisterMsgByKey(
pccb
60: , cBLOB.sub.-- NOT.sub.-- VERSIONED
61: , NO.sub.-- PREV.sub.-- ERROR
62: , ERR.sub.-- BLOB.sub.-- POLICY.sub.-- CONFLICT
63: ) ;
64: break;
65: }
66:
67: /*
68: * Attempt to reserve the current version; if this fails
69: * because someone else already has it reserved, the extended
70: * error info will have the name of that reserver
71: */
72: if ( CAMUS.sub.-- SUCCESS != ( rc = clReserveCurrent(
pccb
73: , pDSConn
74: , entityID
75: , &versRec )))
76: break;
77:
78: /*
79: * You know, we could commit after getting the reservation
80: * and decouple the retrieval from the reservation transaction.
81: * Then if the retrieval failed, the user would still have
82: * the blob reserved and would need only to get it.
83: */
84: rc = dbpGetBlob( pccb, pDSConn, versRec.versionID, pBlob );
85: break;
86: }
87:
88: if ( rc == CAMUS.sub.-- SUCCESS )
89: {
90: CAMUS.sub.-- ASSERT( CAMUS.sub.-- SUCCESS
91: == dsCommitTran( pccb, pDSConn ) );
92: }
93: else
94: {
95: CAMUS.sub.-- ASSERT(CAMUS.sub.-- SUCCESS == dsRollbackTran( pccb,
pDSConn ));
96: }
97:
98: CAMUS.sub.-- ASSERT( CAMUS.sub.-- SUCCESS == dsReturnConnection(
pDSConn ) );
99: return( rc );
100:
}
__________________________________________________________________________
This is a workhorse routine or method which performs most of the work in reserving and retrieving a blob. After initializing local variables at lines 14-17, the method "grabs" a connection at lines 30-32. If a connection cannot be successfully grabbed, the method returns an error code at line 34. Otherwise, the method proceeds to line 49 to validate the access policy. At this point, the method calls into the database primary layer, by invoking dbpGetEntityInfo, at lines 49-53. The entity information is returned to the address passed as the fourth parameter, (address of) entityInfo. The call to dbpGetEntityInfo, since it is a call into a database primary layer, is ultimately executed as an SQL statement against the database. Entity information itself is maintained in an entity record. Such a record may be constructed as follows.
______________________________________
/*
* Entity record
*/
typedef struct .sub.-- entityInfo
{
CAMUSID entityID;
cFORMAT format;
cOBJECTTYPE type;
cACCESSPOLICY
policy;
CAMUSID ownerID;
} ENTITYINFO, *PENTITYINFO;
______________________________________
As shown, the record stores five fields holding the following information. The first field stores an entityID. The second field stores a format (e.g., text, binary, and the like). The third field, type, stores an object type for the entity. The fourth field stores the access policy for the entity. The final field, ownerID, stores an identifier for the owner of the entity. Returning to the clCheckOutBlob method, at line 56 the method tests whether the access policy for the entity is "versioned." If the entity is not versioned, a message is posted to the system at lines 58-63. Otherwise, the method will proceed to line 72 to attempt to reserve the current version, by calling clReserveCurrent. If the method cannot reserve the entity, an error is returned. If, on the other hand, the method can reserve the entity, the method proceeds to line 84 to retrieve the blob; this is done by a call into the database primary layer, dbpGetBlob. At this point, the method breaks out of the "while" loop established between lines 44-86. Upon breaking out of this loop, the method tests the success of the operation of getting the blob. Specifically, the return code (rc) is tested at line 88. If the blob was successfully retrieved, the method commits the transaction at lines 90-91. Otherwise, the method rolls back the transaction at line 95. Finally, the method concludes by dropping the connection (line 98) and returning the result code (line 99). 3. Checking in an Object The methodology of checking in an object is similar to that illustrated for checking out an object. The following description will, therefore, focus on those method steps necessary for understanding the differences between checking an object out and checking an object in. For creating a new version of an object, the system in essence "checks in a blob." After performing argument validation (in a manner similar to that described above), the system invokes a clCheckInBlob method. This method is the workhorse method for checking in a blob which the user has reserved. In an exemplary embodiment, the method may be constructed as follows.
__________________________________________________________________________
1:
/*
2:
* clCheckInBlob()
3:
*
4:
* Check in blob that the user has reserved
5:
*/
6:
CAMUSAPI clCheckInBlob(
PCCB pccb
7:
, CAMUSID
entityID
8:
, PiSZ piszComments
9:
, PcBLOB
pBlob
10:
)
11:
{
12: CAMUS.sub.-- RC
rc;
13: PDSCONN pDSConn;
14: RESERVATIONINFO
reservation = { 0 };
15:
16: /*
17: * See discussion on checkout iso level; we could probably
18: * get by with ISO 1 here, too, and have better throughput,
19: * knowing that only these routines will be updating them,
20: * but it seems safer to leave read locks on the version and
21: * link rows involved in determining the latest version.
22: * Do we have a phantom row issue in calculating the next
23: * major number on the first branch check-in (see calcNextMajor,
24: * which uses a count(*) of the number of child rows. Wouldn't
25: * seem bad to perceived committed adds in that count(*); and
26: * the worst case would be a failure due to an integrity constraint
27: * violation: of two children with the same parent and the same
28: * major number. If we ever see an internal ds error like that
29: * we can bump this to ISO 3.
30: */
31:
if (CAMUS.sub.-- SUCCESS != (rc = dsGetConnection(
pccb
32:
, &pDSConn
33:
, ISOLATION.sub.-- 2 )))
34: {
35: return( rc );
36: }
37:
38: /*
39: * First get the reservation information and verify that
40: * this guy is reserved for this user. Then calculate
41: * the version info for the next revision, add the action
42: * write the next version, remove the reservation, and
43: * we're done -- commit or rollback
44: */
45: while( TRUE )
46: {
47: /* verify reservation */
48: if ( CAMUS.sub.-- SUCCESS != (rc = dbpGetReservation(
pccb
49: , pDSConn
50: , entityID
51: , CHECKOUT.sub.-- LOCK
52: , pccb->userID
53: , &reservation)))
54: break;
55:
56: if ( reservation.userID != pccb->userID )
57: {
58: rc = camInfoRegisterMsgByKey(
pccb
59: , cBLOB.sub.-- NOT.sub.-- CHECKED.sub.-- OUT
60: , NO.sub.-- PREV.sub.-- ERROR
61: , ERR.sub.-- BLOB.sub.-- NOT.sub.-- CHECKED.sub.--
OUT
62: ) ;
63: break;
64: }
65:
66: /*
67: * Write out the next version and action record with
68: * comments
69: */
70: if ( CAMUS.sub.-- SUCCESS != (rc = clWriteNextVersion(
pccb
71: , pDSConn
72: , entityID
73: , reservation.versionID
74: , piszComments
75: , pBlob )))
76: break;
77:
78: /*
79: * Finally, remove the reservation on the ancestor version
80: */
81: rc = dbpRemoveReservation(
pccb
82: , pDSConn
83: , &reservation
84: ) ;
85: break;
86: }
87:
88: if ( rc == CAMUS.sub.-- SUCCESS )
89: {
90: CAMUS.sub.-- ASSERT( CAMUS.sub.-- SUCCESS == dsCommitTran( pccb,
pDSConn ) );
91: }
92: else
93: {
94: CAMUS.sub.-- ASSERT( CAMUS.sub.-- SUCCESS == dsRollbackTran( pccb,
pDSConn )
);
95: }
96:
97: CAMUS.sub.-- ASSERT( CAMUS.sub.-- SUCCESS == dsReturnConnection(
pDSConn ) );
98: return( rc );
99:
}
__________________________________________________________________________
The method operates by creating a new entity--a new version--for a particular object. Accordingly, much of the housekeeping incurred by the "AddObject" method calls is avoided. The specific steps are as follows. After initializing local variables at lines 12-14, the method obtains a connection at line 31. If a connection cannot be obtained for some reason, an error code is returned at line 35. At lines 47-53, the method verifies that the object (as identified by the entity ID) is reserved. If the object is not reserved by the current user, then an error message is logged (lines 58-62). If an error condition has not arisen, the method may proceed to write out a new version of the object. At line 66-75, the method invokes clWriteNextVersion, for writing out a new version of the object. The new version includes a version ID for this new version, as well as any user-supplied comments. This action is followed by removing a reservation which was held on the ancestor version of the object, as indicated by lines 78-84. In the event that the version was successfully added (as indicated by the return code, rc), the method may proceed to commit the transaction, as shown at line 90. Otherwise, the transaction is rolled back, as indicated at line 94. Finally, the method drops the connection at line 97 and returns the result code (rc) at line 98. While the invention is described in some detail with specific reference to a single preferred embodiment and certain alternatives, there is no intent to limit the invention to that particular embodiment or those specific alternatives. Thus, the true scope of the present invention is not limited to any one of the foregoing exemplary embodiments but is instead defined by the appended claims. Appendix A: General Concepts and Tutorial of ObjectCycle.TM. (Commercial Embodiment) ObjectCycle.TM. Concepts A. ObjectCycle Overview ObjectCycle.TM. is a next generation, client/server object management facility for software version control and deployment. ObjectCycle is specifically targeted toward the platforms and network services available in a typical Microsoft Windows network and can run on any machine in such a network, providing services to multiple client applications at a time. ObjectCycle manages client/server communication through the Microsoft RPC services of a Microsoft Windows network, and stores its data in a RDBMS such as Sybase SQL Anywhere 5.0 via ODBC. The ObjectCycle Server is a multi-threaded 32-bit program geared for either Windows NT or Windows 95. It can be stopped and started as a service in Windows NT or as an icon on the Windows 95 or Windows NT desktop. Once the server is initialized, it may immediately begin handling requests from multiple clients (either locally or on the network). The ObjectCycle Server uses a relational database management system to sore its objects. The ObjectCycle Server uses the ODBC database access standard to communicate with the RDBMS. The RDBMS is used to reliably store and quickly access ObjectCycle objects. Sybase SQL, Anywhere 5.0 is a desktop RDBMS and is included with ObjectCycle. ObjectCycle Manager: The ObjectCycle Manager is an easy-to-use, graphical tool that comes in a 16 bit format for Windows 3.11 or a 32-bit format for Windows 95 and NT. ObjectCycle Manager is intended to be used by both ObjectCycle users and administrators to manage and manipulate data within the ObjectCycle Server. Development tools and ObjectCycle: ObjectCycle is specifically tuned to provide seamless version control for development tools on the desktop such as PowerBuilder 5.0. To find out more about desktop development tools the user can purchase, the user can see the ObjectCycle installation notes (readme.txt) in the ObjectCycle folder provided by ObjectCycle (Part No. HC0091; Powersoft Corp., Concord, Mass.). ObjectCycle Client Library: The ObjectCycle client library is a Windows DLL that comes in a 16-bit format for Windows 3.11 clients as well as a 32-bit format for Windows 95 and NT clients. The library is a light weight layer that exposes the ObjectCycle API and implements the client-side marshaling of memory provided by the MS RPC mechanism. B. Client/Server Concepts ObjectCycle is a network-based client/server facility. The server component is ObjectCycle Server; the client component is ObjectCycle Manager, or other clients such as PowerBuilder. Client/Server communications are managed by Microsoft RPC. ObjectCycle Server, which developers access from their own machines, stores and tracks the development history of objects. The data is stored in the Sybase SQL Anywhere 5.0 relational database that comes with the ObjectCycle Server. An administrator installs ObjectCycle Server on the network, and sets up projects and user access. With ObjectCycle Manager installed on the user's machine, the user logs on to the ObjectCycle Server and uses the ObjectCycle Manager to perform operations on objects under the user's control, and to track what is happening on the team project. To find out how ObjectCycle implements version control, see Version control concepts. To understand the different user roles, see User role concepts. C. Version Control Concepts Version control is a methodology used by development teams to control and track changes to code, ensure that the correct version of the code is deployed, and facilitate the deployment process. ObjectCycle makes all these tasks easy. The ObjectCycle Manager lets the user represent a real-world development project in a hierarchical graphical view that maps to the file system. From the ObjectCycle perspective, an ObjectCycle project is partitioned into folders containing ObjectCycle objects, which represent the application objects stored in the ObjectCycle Server. Once the user's project and user access have been set up in ObjectCycle Server, the user will perform versioning activities such as: Check out--borrow an object to edit; prevent others from accessing while it is being edited; Check in--return modified objects to create new versions; Get--copy one or multiple objects to the user's local directory; View reports--quickly view project activity status; Label--assign a name to one or more objects for group operations; and Create builds--use labels to restore older versions. To understand the different user roles in ObjectCycle, see User role concepts. The tutorials show how versioning activities are performed on a typical C code project. D. User Role Concepts User role concepts. ObjectCycle recognizes three user roles: User, Administrator, and Inactive. An administrator assigns user roles for each ObjectCycle project.
______________________________________
Role Description
______________________________________
Administrator
A user who prepares and maintains ObjectCycle for use
by the team on one or more projects.
User Developer who uses ObjectCycle Manager to check
objects in and out, get copies of objects, and other basic
operations.
Inactive A user who is only authorized to browse projects.
______________________________________
ObjectCycle gives the user flexibility in these roles so that the user can do his/her development work most effectively. For example, the user may need to be both a user and an administrator on a project. The user's team may want to have several administrators with different responsibilities. The tutorials show how users in different roles perform version control activities on a typical project. E. Project Set-up Setting up a project. This section is directed to the person who will perform administrator functions to set up projects and user access. For practice in doing administrator tasks on a sample project, see the Administrator tutorial. To prepare ObjectCycle for team development: 1. With ObjectCycle Server running, double-click the ObjectCycle Manager icon. The ObjectCycle Manager main window opens. 2. Select File.fwdarw.Open. The Open Project dialog displays. 3. Log on as administrator, using the following values:
______________________________________
Field Default (first log on)
______________________________________
User Name admin
Password camus
Project system
Server (Name of the user's ObjectCycle Server machine)
______________________________________
When the user clicks OK, the ObjectCycle Manager project browser window opens, displaying the default project `system`. The user can use this project, or create a new one for his/her team. Frequently-used menu command; are available from the toolbars. 4. Partition and populate the default project (using the File.fwdarw.New.fwdarw.Folder and File.fwdarw.New.fwdarw.Object commands) or create a new project (using the File.fwdarw.New.fwdarw.Project command). 5. Create user names, passwords and project access for each user on the project (see Configure.fwdarw.Users command). Tip: Be sure to enter a new password for each user as his/her future login. 6. Let the users know their log on information. Tutorial Help A. Tutorial Overview There are three tutorials in this section: one for the administrator, who sets up projects and user access, one for an administrator who is responsible for release management, and one for a user working on a typical project. (These roles are explained in user role concepts.) On any project, the user might have one or more of these roles. ObjectCycle administrators should do all three tutorials to get a good understanding of how to use ObjectCycle on a typical project. B. Administrator Tutorial This tutorial demonstrates how to create and partition a sample C code project and assign user access for the project. For this tutorial, the user must log on as an administrator. This tutorial has two sections: A. Create and partition the sample project B. Assign user access To create and partition the sample project: 1. Locate a shared disk to contain the baseline files on the machine on which ObjectCycle Server is installed. 2. Set up a directory tree on the shared disk, with a directory called ProjectA, and three subdirectories called Client, Server, and Tools, as in the example (using the C drive): c:.backslash.PROJECTA c:.backslash.PROJECTA.backslash.CLIENT c:.backslash.PROJECTA.backslash.SERVER c:.backslash.PROJECTA.backslash.TOOLS 3. With ObjectCycle Server running, double-click the ObjectCycle Manager icon. The ObjectCycle Manager main window opens. 4. Select File.fwdarw.Open and log on to the ObjectCycle Server as an administrator. If this is the user's first time logging on as an administrator, the following values are used. Otherwise one logs on with a password the user has set for himself/herself
______________________________________
Field Defaults
______________________________________
User Name admin
Password camus
Project system
Server (Name of the user's ObjectCycle Server machine)
______________________________________
When the user clicks OK, the ObjectCycle Manager project browser window opens, displaying the default project `system`. Take a moment to look at the menus and toolbar. Most commands the user will need are on the toolbars. The user will create the new project for his/her team called "projectA". 5. Select File.fwdarw.New.fwdarw.Project, enter the name "projectA" and a project description in the dialog, and click OK. A new project browser displays on top of the default system project, and now the user is ready to partition the project. (The browser window can contain multiple projects for each session.) 6. Select File.fwdarw.New.fwdarw.Folder (or click the New Folder icon), enter the folder name "Client" and click OK. With the root projectA selected, repeat this process to create the "Server" and "Tools" folders. The user has just created and partitioned a project. The next task is to assign user access to this project. To assign user access: 1. Select Configure.fwdarw.Users. "admin" appears s the user's username. Before the user creates other user profiles the user will change his/her password. 2. Highlight "Admin", click the Edit button and type a new password. Re-enter it in the Verify field. 3. Select "projectA" as the user's default project, and Admin as the user's role, and click OK. 4. Click Add on Configure Users Browser dialog to create two user profiles, one for a user called Mike (no password, default project=projectA, user role=user) and one for Suzanne (default project=projectA,user role=admin). Suzanne will be the release manager. Mike will be a developer. Note: The user can create a user without a password by not typing anything in the Password field. At this point, the user has created a project and assigned user privileges. Now the user can try the Release manager tutorial to see how Suzanne will populate the project and carry out release manager activities. C. Release Manager Tutorial This tutorial demonstrates how to populate the project namespace created in the Administrator tutorial, and how to deploy a release of the software. To do this tutorial, the user will log on as the administrator "Suzanne" (created in the Administrator tutorial). This tutorial includes the following sections: A. Create a Map Directory B. Populate ProjectA with objects C. Deploy a release of ProjectA code To create a Map Directory: 1. With the ObjectCycle Server running, double-click the ObjectCycle Manager icon. The ObjectCycle Manager main window opens. 2. Select File.fwdarw.Open and log on to the ObjectCycle Server with the following values:
______________________________________
Field Default values
______________________________________
User Name suzanne
Password (leave blank)
Project projectA
Server (Name of the user's ObjectCycle Server machine)
______________________________________
When the user clicks OK, the ObjectCycle Manager project browser window opens, displaying the default project `system`. Take a moment to look at the menus and toolbar. Most commands the user will need are on the toolbars. 3. Select Configure.fwdarw.Preferences, and on the Map Directory tab, enter the letter for the Map Directory (created in Step 2 of the Administrator tutorial). 4. Click Default. Clicking Defaults adds the Map Directory to the list. The user can edit any line if needed. 5. Click Create Directories. This creates the directories as indicated. 6. Select the File Type tab and click Defaults. This populates the default file types for the user's use. Note that each of them has an icon to make it easy to identify the file type (e.g., .c, .h, .def, .rc, .mak, .bat, .txt, .bmp). 7. When the user is done adding file types, he or she clicks OK to exit from Preferences. A SUZANNE.INI object appears in the browser object view. This object stores the user profile information. To populate ProjectA with objects: 1. Select the Server folder and select File.fwdarw.New.fwdarw.Object. 2. If necessary, click Browse and select an object for this folder. 3. In the New Object dialog, enter any comments to describe this object (e.g., "This module provides server transaction control services."). Notice that Object Name, File name, Format, and User values are entered for the user. The default access is "versioned." Click OK to exit the dialog. 4. Repeat the above steps so that the user has at least 2 versioned objects in each of the three folders. Now the user is ready to deploy a release of his/her software. To do this, the user will create a "Beta01" label and perform a Get operation on all objects belonging to this label. (In a real situation, first check with all users to verify that they have checked in their final code.) To deploy a release of ProjectA code: 1. Select the root ProjectA, and right-click to display the tree view popup menu. 2. Select Get from the popup menu. This displays the Get Objects dialog. This dialog lets the user Get objects from ObjectCycle to the file system and optionally label those objects. If the user selects; the Label option, a label object is automatically created in the Project Objects folder. The user can deploy all objects in the selected folder, or optionally include the subfolders. The user will use the Get Objects feature to create a label for all objects in the ObjectCycle project and deploy them to a new release, "Beta01". 3. Check the Include subfolders box. This tells ObjectCycle to apply the Get command to all objects in the project (since the user selected the root project in Step 1.) 4. Check the Label box and in the Label box, type the label name, "Beta01". Notice that if the user had other labels in the project the user could select them from the drop-down list. 5. Add a descriptive comment in the Comments box if the user chooses, and click OK when the dialog is complete. ObjectCycle labels all the objects in the user's project and performs a Get operation. Now, the user has just deployed a release of his/her software. To understand what the users are doing, see the User tutorial. D. User Tutorial The user tutorial demonstrates how a user performs typical version control activities on a project. This tutorial has three sections: A. Create a new version B. Restore an earlier version C. Version a deleted file Introduction A typical ObjectCycle user is a developer or author on a team who is creating versions of code and documentation. ObjectCycle assists the user in his/her work by providing mechanisms for: Preventing others from modifying an object (file) while the user is editing it; automatically incrementing version numbers when the user returns (check in) a modified object; tracking who on the team has checked out or checked in other modules the user and the team need to know about; getting copies of objects from ObjectCycle to the user's file system when the user needs to verify their contents or start versioning them; letting the user assign labels to related objects to identify and work with them as a group; and creating new releases and restoring earlier versions quickly whenever needed. There are only a few commands needed to do the above tasks. They are Check Out, Check In, Clear Checkout, Get and Label. This tutorial depends on the setup work done in the Administrator tutorial, in which an administrator created ProjectA in ObjectCycle and set up access for a user called Suzanne. Users can only log on to ObjectCycle and use it on a project when an administrator has done this initial setup. Once the user is working on a project, he/she can perform any operations on objects except moving, renaming, clearing the Checkout status, and destroying label objects, folder, and projects (only an administrator can do these operations). To prepare for this tutorial: 1. With the ObjectCycle Server running, double-click the ObjectCycle Manager icon. The ObjectCycle Manager main window opens. 2. Select File.fwdarw.Open and log on to the ObjectCycle Server with the following values:
______________________________________
Field Default values
______________________________________
User Name mike
Password (leave blank)
Project projectA
Server (Name of the user's ObjectCycle Server machine)
______________________________________
When the user clicks OK, the ObjectCycle Manager project browser window opens, displaying the default project `system`. Take a moment to look at the menus and toolbar. Most commands the user will need are on the toolbars. 3. Select Configure.fwdarw.Preferences, and on the Map Directory tab, enter the letter for the Map Directory (created in Step 2 of the Administrator tutorial). 4. Click Defaults. Clicking Defaults adds the Map Directory to the list. The user can edit any line if needed. 5. Click Create Directories and click OK. This creates the directories as indicated. 6. With a folder selected, select File.fwdarw.New.fwdarw.Object and add an object to the project. A. Creating a New Version To create a new version of an object, the user's main activities will be to: check out the ObjectCycle object so that the user can modify it, and check in the modified object to ObjectCycle to create a new version 1. With ProjectA in the user's browser window, open a folder containing an object. 2. Select an object, and select File.fwdarw.Check Out. A confirmation dialog appears with the file name indicated. 3. Click OK on the Check out Object to File dialog. Notice the lock icon that appears on the ObjectCycle object (shown in the project browser). No other user can modify this object while the user has it checked out to his/her file system. 4. Select View.fwdarw.Checkout Report and notice that ObjectCycle has recorded the information (object, user, version, date) so that the user's team members know which objects can view the ongoing checkout status. While the user has the object checked out, other users can view it by double-clicking it and selecting the Get command. At this point the user is ready to make modifications to his/her object. Let's assume the user has done his/her editing and is ready to create the new version. 5. Select the ObjectCycle object representing the modified file, and select File.fwdarw.Check in (or the Check In icon). 6. In the Check in Object dialog, add a comment about the user's changes, such as "Fixed bugs 45 and 56" and click OK. ObjectCycle unlocks the object and increments the version number. It removes the file from the user's local directory. As a final step, take a look at the information that ObjectCycle has stored about the brief history of the user's object. 7. Select the object, right-click and select Properties from the object view popup menu. Notice that the Description tab stores information about the object, the History tab shows the activities on it, and the Label tab provides label information. Now, the user has just created a version, using the Check Out and Check In commands. Along the way the user looked at the Checkout Report, and Object Properties. The user also used the popup menu as an alternative to the File menu. In a real situation, the user might need to complete this process by checking the object out again, to retain authorship over the new version. If the user wants to create a version consisting of multiple objects, try out Section C of the Release manager tutorial. B. Restoring an earlier version Sometimes the user needs to restore an earlier version of one or more objects. To restore an earlier version, the user's main activities will be to: check out the ObjectCycle object to establish control of it; get the modified object to the user's Map Directory and overwrite the user's existing (i.e., incorrect) file;and check in the modified object to ObjectCycle to restore the version. To do this section of the tutorial, the user must be logged on as the user Mike to ProjectA (as explained in Section A). 1. With ProjectA in the user's browser window, select an object, and select File.fwdarw.Check Out. Click OK on the Check out Object to File dialog. Next the user needs to copy the earlier version to his/her local directory, which currently has the incorrect or undesired version. 2. Select the ObjectCycle object and select File.fwdarw.Get. This copies the object to the user's directory. 3. When prompted, confirm that the user wants to overwrite his/her local (incorrect) file. At this point the user can make modifications, if needed, or simply check the object back in. Let's assume the user is ready to restore the correct version. 4. Select the ObjectCycle object and select File.fwdarw.Check in. 5. In the Check in Object dialog, add a comment about the user's changes, such as "Restored previous version to fix bug 78" and click OK. ObjectCycle unlocks the object and increments the version number. Now, the user has restored the correct version of his/her software. To do this on a group of objects, the user can select the folder(s) containing them and use the File.fwdarw.Get command to label and restore them as a group. C. Versioning a deleted file Sometimes the user needs to version an object which has been deleted from his/her directory. To create a version in this situation the user will do the following: get a copy of the ObjectCycle object to the user's local directory; Check Out the object to gain control of it and make any edits; and Check In the object. To do this section of the tutorial, the user must be logged on as the user Mike to ProjectA (as explained in Section A). 1. With ProjectA Din the user's browser window, double-click an object and select Get. The Open Object dialog lets the user choose to Get or Checkout an object. It runs the file the user is getting so that the user can view the contents to verify. Now the user can establish control of the object. 2. Select the ObjectCycle object and select File.fwdarw.Check Out. At this point the user can make modifications if needed or simply check the object back in. Let's assume the user is ready check it in. 3. Select the ObjectCycle object and select File.fwdarw.Check in. 4. In the Check in Object dialog, add a comment about the user's changes, such as "Versioned the deleted file" and click OK. ObjectCycle unlocks the object and increments the version number. Now, the user has restored, recovered and versioned a deleted file. A similar process would be followed if the user needed to take ownership of an object currently checked out by another user. The user would have the administrator clear the checkout status of the object and then follow the 4 steps above. ObjectCycle Manager A. ObjectCycle Manager Overview ObjectCycle Manager is the graphical utility for performing version control, deployment, and administration of users, projects, and objects in the ObjectCycle Server. After the initial setup, ObjectCycle Manager makes it easy for the user to manage his/her projects. With ObjectCycle Manager, the user can: create and destroy projects; add, rename, move, delete and destroy objects; easily view the status of objects and their attributes assign icons to identify types of objects visually; check out and check in versioned objects; get copies of objects to the user's file system to view; check out copies of objects to the user's file system; display the status of checked-out objects; assign labels to objects and filter version lists by this label; view version history; view and print reports on objects and versions; create a new release of the user's application; and restore earlier versions of objects. B. ObjectCycle Manager Main Window When the ObjectCycle manager starts up, the ObjectCycle Manager main window appears. This is an MDI (Multiple Document Interface) window. From the main window the user opens, closes or creates a new project, manipulates the window and frame toolbar, selects Help, or exits from ObjectCycle Manager. C. Project Browsers When the user opens or creates a new ObjectCycle project, the ObjectCycle Manager displays a project browser (the child window) to let the user view and manage folders, ObjectCycle objects, and label objects. ObjectCycle Manager displays separate project browsers for each session. The project's server name, project name, and user name appear on the title bar of the project browser. A top level folder showing the name of the user's project, and a read-only folder called Project Objects are created. The Project Objects folder contains the project objects maintained by ObjectCycle, such as label objects. The project browser has a tree view on the left, and an object view on the right.
______________________________________
The tree view displays . . .
The object view displays . . .
______________________________________
Project or folders containing
ObjectCycle objects and label objects
ObjectCycle objects
Click the plus or minus to
Left-click to select an object and to
expand or collapse access the
the object view popup menu
tree and select a folder.
Click a folder to display or
Right-click in a clear space in the object
refresh the display of objects in
view to display the object view popup
the object view menu
Right-click and hold on a folder
Click and hold on the object name to do
name to do a Rename
a Rename
Double-click an object to view or edit
the object
______________________________________
D. Command Access The user can access ObjectCycle Manager commands from the menus, from the toolbars, or from context-sensitive popup menus. The user can look up a command in Help by menu, toolbar, or popup menu in the Command Reference section. There are six menus: File, Edit, View, Configure, Window, and Help. Some commands are only enabled when the user has first selected an object or folder on which to perform an operation. Frequently-used commands are available by clicking icons on a toolbar. There are two toolbars for quick access to these commands, the frame toolbar, and the project browser toolbar. In addition, most menu commands are available on context-sensitive popup menus in the Windows 95 style by right-clicking the mouse. Command Reference A. Toolbars ObjectCycle Manager provides two toolbars for quick access to most ObjectCycle functions. The top toolbar is the frame toolbar; the configurable toolbar is the project browser toolbar. Select Window.fwdarw.Toolbars and click Show Text to familiarize the user with the convenience of the toolbars. The Toolbars dialog also lets the user move, show/hide the toolbar and display tips.
______________________________________
Frame toolbar Click this icon to . . .
______________________________________
Open Project Open an existing project
Close Close opened project
Exit Exit ObjectCycle Manager
Help Display ObjectCycle Manager Help
______________________________________
For any of the following commands that operate on an existing object or folder, select the object or folder name first:
______________________________________
Browser toolbar
Click this icon to . . .
______________________________________
New Folder
Create a new folder under current folder
New Object
Create a new object in currently selected folder
Check Out Check out an ObjectCycle object's data to a file.
Check In Check in an object from a file
Get Copy an object(s) to a file.
Put Update a writeable object
Label View, add or remove label(s)
Cut Cut an object that the user wants to paste to another
folder (cut/paste is the same as drag and drop or move)
Copy Copy a version of an object to the clipboard
Paste Paste a cut or copied version of an object to a selected
folder
Delete Delete selected folder, object or label object
Undo Delete
Remove status of Delete on a folder or object
Destroy Permanently remove deleted objects, folders, projects
Rename Rename selected folder or object
Prop | ||||||
