/**
 * Setup for a DIFF on a single item.
 *
 * We return a varray of "DiffSteps".
 * 
 */
void SG_wc_tx__diff__setup(SG_context * pCtx,
						   SG_wc_tx * pWcTx,
						   const SG_rev_spec * pRevSpec,
						   const char * pszInput,
						   SG_uint32 depth,
						   SG_bool bNoIgnores,
						   SG_bool bNoTSC,
						   SG_bool bNoSort,
						   SG_bool bInteractive,
						   const char * pszTool,
						   SG_varray ** ppvaDiffSteps)
{
	SG_varray * pvaStatus = NULL;
	SG_varray * pvaDiffSteps = NULL;
	SG_uint32 nrRevSpecs = 0;

	SG_NULLARGCHECK_RETURN( pWcTx );
	// pszInput is optional
	// pszTool is optional
	SG_NULLARGCHECK_RETURN( ppvaDiffSteps );

	if (pRevSpec)
		SG_ERR_CHECK(  SG_rev_spec__count(pCtx, pRevSpec, &nrRevSpecs)  );

	if (nrRevSpecs > 0)
		SG_ERR_CHECK(  SG_wc_tx__status1(pCtx, pWcTx, pRevSpec,
										 pszInput, depth,
										 SG_FALSE,	// bListUnchanged
										 bNoIgnores,
										 bNoTSC,
										 SG_FALSE,	// bListSparse
										 SG_FALSE,	// bListReserved
										 bNoSort,
										 &pvaStatus)  );
	else
		SG_ERR_CHECK(  SG_wc_tx__status(pCtx, pWcTx,
										pszInput, depth,
										SG_FALSE,	// bListUnchanged
										bNoIgnores,
										bNoTSC,
										SG_FALSE,	// bListSparse
										SG_FALSE,	// bListReserved
										bNoSort,
										&pvaStatus, NULL)  );

	if (pvaStatus)
		SG_ERR_CHECK(  sg_wc_tx__diff__setup(pCtx, pWcTx, pvaStatus,
											 bInteractive, pszTool,
											 &pvaDiffSteps)  );

	SG_RETURN_AND_NULL(pvaDiffSteps, ppvaDiffSteps);

fail:
	SG_VARRAY_NULLFREE(pCtx, pvaStatus);
}
void sg_sync_client__http__request_fragball(
    SG_context* pCtx,
    SG_sync_client* pSyncClient,
    SG_vhash* pvhRequest,
    SG_bool bProgressIfPossible,
    const SG_pathname* pStagingPathname,
    char** ppszFragballName)
{
    char* pszFragballName = NULL;
    SG_pathname* pPathFragball = NULL;
    SG_string* pstrRequest = NULL;
    char* pszUrl = NULL;

    SG_NULLARGCHECK_RETURN(pSyncClient);

    SG_ERR_CHECK(  SG_allocN(pCtx, SG_TID_MAX_BUFFER_LENGTH, pszFragballName)  );
    SG_ERR_CHECK(  SG_tid__generate(pCtx, pszFragballName, SG_TID_MAX_BUFFER_LENGTH)  );


    SG_ERR_CHECK(  _get_sync_url(pCtx, pSyncClient->psz_remote_repo_spec, SYNC_URL_SUFFIX FRAGBALL_URL_SUFFIX, NULL, NULL, &pszUrl)  );


    if (pvhRequest)
    {
        SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pstrRequest)  );
        SG_ERR_CHECK(  SG_vhash__to_json(pCtx, pvhRequest, pstrRequest)  );
    }

    SG_ERR_CHECK(  SG_pathname__alloc__pathname_sz(pCtx, &pPathFragball, pStagingPathname, (const char*)pszFragballName)  );
    SG_ERR_CHECK(  do_url(
                       pCtx,
                       pszUrl,
                       "POST",
                       pstrRequest ? SG_string__sz(pstrRequest) : NULL,
                       pSyncClient->psz_username,
                       pSyncClient->psz_password,
                       NULL,
                       pPathFragball,
                       bProgressIfPossible
                   )  );


    SG_RETURN_AND_NULL(pszFragballName, ppszFragballName);

    /* fall through */
fail:
    SG_NULLFREE(pCtx, pszFragballName);
    SG_PATHNAME_NULLFREE(pCtx, pPathFragball);
    SG_NULLFREE(pCtx, pszUrl);
    SG_STRING_NULLFREE(pCtx, pstrRequest);
}
예제 #3
0
/**
 * Creates a staging area and returns an initialized instance data structure.
 */
static void _pull_init(SG_context* pCtx, 
					   SG_client* pClient,
					   const char* pszPullIntoRepoDescriptorName,
					   sg_pull_instance_data** ppMe)
{
	char* pszThisRepoId = NULL;
	char* pszThisHashMethod = NULL;
	char* pszOtherRepoId = NULL;
	char* pszOtherHashMethod = NULL;

	sg_pull_instance_data* pMe = NULL;
	
	SG_repo* pPullIntoRepo = NULL;

	SG_NULLARGCHECK_RETURN(pszPullIntoRepoDescriptorName);
	SG_NULLARGCHECK_RETURN(ppMe);

	SG_ERR_CHECK(  SG_client__get_repo_info(pCtx, pClient, &pszOtherRepoId, NULL, &pszOtherHashMethod)  );

	SG_ERR_CHECK(  SG_repo__open_repo_instance(pCtx, pszPullIntoRepoDescriptorName, &pPullIntoRepo)  );

	/* TODO This will care about dagnums once we're using the user dag. */
	SG_ERR_CHECK(  SG_repo__get_repo_id(pCtx, pPullIntoRepo, &pszThisRepoId)  );
	if (strcmp(pszThisRepoId, pszOtherRepoId) != 0)
		SG_ERR_THROW(SG_ERR_REPO_ID_MISMATCH);

	/* TODO check admin id when appropriate */

	SG_ERR_CHECK(  SG_repo__get_hash_method(pCtx, pPullIntoRepo, &pszThisHashMethod)  );
	if (strcmp(pszThisHashMethod, pszOtherHashMethod) != 0)
		SG_ERR_THROW(SG_ERR_REPO_HASH_METHOD_MISMATCH);

	// alloc instance data, store pull id in it (which identifies the staging area)
	SG_ERR_CHECK(  SG_alloc1(pCtx, pMe)  );
	SG_ERR_CHECK(  SG_staging__create(pCtx, pszPullIntoRepoDescriptorName, &pMe->pszPullId, &pMe->pStaging)  );
	pMe->pPullIntoRepo = pPullIntoRepo;
	pPullIntoRepo = NULL;

	SG_RETURN_AND_NULL(pMe, ppMe);

	/* fall through */
fail:
	SG_NULLFREE(pCtx, pszThisRepoId);
	SG_NULLFREE(pCtx, pszThisHashMethod);
	SG_NULLFREE(pCtx, pszOtherRepoId);
	SG_NULLFREE(pCtx, pszOtherHashMethod);
	SG_NULLFREE(pCtx, pPullIntoRepo);
	_NULLFREE_INSTANCE_DATA(pCtx, pMe);
}
/**
 * Pick thru the computed VARRAY build a subset VARRAY
 * containing just the dirty files.  That is, for an interactive
 * diff, we only show dirty files.  (In batch/patch mode, we show
 * everything.)
 *
 * We assume that the varray looks like:
 *
 * varray := [ { "status" : { "flags" : <int>,
 *                            ... },
 *               "path"   : <repo-path>,
 *               ... },
 *             ... ];
 *
 * Both a Canonical STATUS (pvaStatus) and a "DiffStep" (pvaDiffStep)
 * match this pattern.
 *
 * Return NULL if there aren't any.
 *
 */
static void _get_dirty_files(SG_context * pCtx,
                             const SG_varray * pva,
                             SG_varray ** ppvaDirtyFiles)
{
    SG_varray * pvaDirtyFiles = NULL;
    SG_uint32 k, nrItems;

    *ppvaDirtyFiles = NULL;

    if (!pva)
        return;
    SG_ERR_CHECK(  SG_varray__count(pCtx, pva, &nrItems)  );
    if (nrItems == 0)
        return;

    for (k=0; k<nrItems; k++)
    {
        SG_vhash * pvhItem_k;			// we do not own this
        SG_vhash * pvhItemStatus_k;		// we do not own this
        SG_int64 i64;
        SG_wc_status_flags statusFlags;

        SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pva, k, &pvhItem_k)  );
        SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhItem_k, "status", &pvhItemStatus_k)  );
        SG_ERR_CHECK(  SG_vhash__get__int64(pCtx, pvhItemStatus_k, "flags", &i64)  );
        statusFlags = (SG_wc_status_flags)i64;

        if ((statusFlags & SG_WC_STATUS_FLAGS__T__FILE) == 0)
            continue;
        if ((statusFlags & (SG_WC_STATUS_FLAGS__C__NON_DIR_MODIFIED
                            |SG_WC_STATUS_FLAGS__S__ADDED
                            |SG_WC_STATUS_FLAGS__S__DELETED
                            |SG_WC_STATUS_FLAGS__S__MERGE_CREATED
                            |SG_WC_STATUS_FLAGS__S__UPDATE_CREATED)) == 0)
            continue;

        if (!pvaDirtyFiles)
            SG_ERR_CHECK(  SG_VARRAY__ALLOC(pCtx, &pvaDirtyFiles)  );
        SG_ERR_CHECK(  SG_varray__appendcopy__vhash(pCtx, pvaDirtyFiles, pvhItem_k, NULL)  );
    }

    SG_RETURN_AND_NULL( pvaDirtyFiles, ppvaDirtyFiles );

fail:
    SG_VARRAY_NULLFREE(pCtx, pvaDirtyFiles);
}
void sg_sync_client__http__pull_clone(
    SG_context* pCtx,
    SG_sync_client* pSyncClient,
    SG_vhash* pvh_clone_request,
    const SG_pathname* pStagingPathname,
    char** ppszFragballName)
{
    SG_vhash* pvhRequest = NULL;
    char* pszFragballName = NULL;
    SG_pathname* pPathFragball = NULL;
    SG_string* pstrRequest = NULL;
    char* pszUrl = NULL;

    SG_ERR_CHECK(  SG_log__push_operation(pCtx, "Requesting repository from server", SG_LOG__FLAG__NONE)  );

    SG_NULLARGCHECK_RETURN(pSyncClient);

    SG_ERR_CHECK(  SG_allocN(pCtx, SG_TID_MAX_BUFFER_LENGTH, pszFragballName)  );
    SG_ERR_CHECK(  SG_tid__generate(pCtx, pszFragballName, SG_TID_MAX_BUFFER_LENGTH)  );

    SG_ERR_CHECK(  _get_sync_url(pCtx, pSyncClient->psz_remote_repo_spec, SYNC_URL_SUFFIX FRAGBALL_URL_SUFFIX, NULL, NULL, &pszUrl)  );


    SG_ERR_CHECK(  SG_VHASH__ALLOC(pCtx, &pvhRequest)  );
    SG_ERR_CHECK(  SG_vhash__add__null(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__CLONE)  );
    if (pvh_clone_request)
    {
        SG_ERR_CHECK(  SG_vhash__addcopy__vhash(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__CLONE_REQUEST, pvh_clone_request)  );
    }
    SG_ERR_CHECK(  SG_STRING__ALLOC__RESERVE(pCtx, &pstrRequest, 50)  );
    SG_ERR_CHECK(  SG_vhash__to_json(pCtx, pvhRequest, pstrRequest)  );

    SG_ERR_CHECK(  SG_pathname__alloc__pathname_sz(pCtx, &pPathFragball, pStagingPathname, (const char*)pszFragballName)  );
    SG_ERR_CHECK(  do_url(pCtx, pszUrl, "POST", SG_string__sz(pstrRequest), pSyncClient->psz_username, pSyncClient->psz_password, NULL, pPathFragball, SG_TRUE)  );

    SG_RETURN_AND_NULL(pszFragballName, ppszFragballName);

    /* fall through */
fail:
    SG_log__pop_operation(pCtx);
    SG_NULLFREE(pCtx, pszFragballName);
    SG_VHASH_NULLFREE(pCtx, pvhRequest);
    SG_PATHNAME_NULLFREE(pCtx, pPathFragball);
    SG_NULLFREE(pCtx, pszUrl);
    SG_STRING_NULLFREE(pCtx, pstrRequest);
}
void SG_sync_remote__push_begin(
	SG_context* pCtx,
	const char** ppszPushId
	)
{
	char* psz_staging_area_name = NULL;

	SG_NULLARGCHECK_RETURN(ppszPushId);

	SG_ERR_CHECK(  SG_staging__create(pCtx, &psz_staging_area_name, NULL)  );

	SG_RETURN_AND_NULL(psz_staging_area_name, ppszPushId);

	/* fallthru */

fail:
	SG_NULLFREE(pCtx, psz_staging_area_name);
}
void sg_wc_db__gid__get_gid_from_alias3(SG_context * pCtx,
										sg_wc_db * pDb,
										SG_uint64 uiAliasGid,
										char ** ppszGid)
{
	char * pszGid = NULL;
	SG_bool bIsTmp;

	SG_ERR_CHECK(  sg_wc_db__gid__get_gid_from_alias2(pCtx, pDb, uiAliasGid, &pszGid, &bIsTmp)  );
	if (bIsTmp)
	{
		SG_ASSERT( (pszGid[0] == 'g') );
		pszGid[0] = 't';
	}

	SG_RETURN_AND_NULL(pszGid, ppszGid);

fail:
	SG_NULLFREE(pCtx, pszGid);
}
예제 #8
0
void SG_server__push_begin(
	SG_context* pCtx,
	SG_server * pServer,
	const char* psz_repo_descriptor_name,
	const char** ppszPushId
	)
{
	char* psz_staging_area_name = NULL;

	SG_NULLARGCHECK_RETURN(pServer);
	SG_NULLARGCHECK_RETURN(ppszPushId);

	SG_ERR_CHECK(  SG_staging__create(pCtx, psz_repo_descriptor_name, &psz_staging_area_name, NULL)  );

	SG_RETURN_AND_NULL(psz_staging_area_name, ppszPushId);

	/* fallthru */

fail:
	SG_NULLFREE(pCtx, psz_staging_area_name);
}
예제 #9
0
void GetProfileSettings(
	SG_context* pCtx, 
	const char* szProfile,
	char** ppszDest, 
	char** ppszSrc, 
	SG_int64* plSyncIntervalMinutes,
	LPWSTR* ppwszErr)
{
	static const LPWSTR ERR_NO_DEST = L"No destination repository is configured.";
	static const LPWSTR ERR_NO_SRC = L"No source repository is configured.";

	char* pszDest = NULL;
	char* pszSrc = NULL;
	SG_variant* pvMinutes = NULL;
	SG_string* pstrConfigPath = NULL;
	SG_uint64 lMinutes;
	LPWSTR pwszErr = NULL;

	/* It's kind of icky, but we copy these error strings so the balloon code that displays them doesn't have to
	   be very smart and can always free the message string.  (Generic unhandled errors aren't constant, so it has
	   to free those.) I'm not sure making the balloon code more complexso this can be simpler is a net win, so I'm 
	   leaving this be. But it is icky. */

	SG_ERR_CHECK(  SG_string__alloc__format(pCtx, &pstrConfigPath, "%s/%s/%s", CONFIG_ROOT, szProfile, CONFIG_DEST)  );
	SG_ERR_CHECK(  SG_localsettings__get__sz(pCtx, SG_string__sz(pstrConfigPath), NULL, &pszDest, NULL)  );
	SG_ERR_CHECK(  SG_string__clear(pCtx, pstrConfigPath)  );
	if (!pszDest || !pszDest[0])
	{
		size_t len_in_chars = 0;
		if (FAILED(  StringCchLength(ERR_NO_DEST, STRSAFE_MAX_CCH, &len_in_chars)  ))
			SG_ERR_THROW(SG_ERR_GETLASTERROR(GetLastError()));
		pwszErr = (LPWSTR)malloc(sizeof(wchar_t) * (len_in_chars + 1));
		if (FAILED(  StringCchCopy(pwszErr, len_in_chars + 1, ERR_NO_DEST)  ))
			SG_ERR_THROW(SG_ERR_GETLASTERROR(GetLastError()));
	}

	SG_ERR_CHECK(  SG_string__append__format(pCtx, pstrConfigPath, "%s/%s/%s", CONFIG_ROOT, szProfile, CONFIG_SRC)  );
	SG_ERR_CHECK(  SG_localsettings__get__sz(pCtx, SG_string__sz(pstrConfigPath), NULL, &pszSrc, NULL)  );
	SG_ERR_CHECK(  SG_string__clear(pCtx, pstrConfigPath)  );
	if (!pwszErr)
	{
		if (!pszSrc || !pszSrc[0])
		{
			size_t len_in_chars = 0;
			if (FAILED(  StringCchLength(ERR_NO_SRC, STRSAFE_MAX_CCH, &len_in_chars)  ))
				SG_ERR_THROW(SG_ERR_GETLASTERROR(GetLastError()));
			pwszErr = (LPWSTR)malloc(sizeof(wchar_t) * (len_in_chars + 1));
			if (FAILED(  StringCchCopy(pwszErr, len_in_chars + 1, ERR_NO_SRC)  ))
				SG_ERR_THROW(SG_ERR_GETLASTERROR(GetLastError()));
		}
	}

	SG_ERR_CHECK(  SG_string__append__format(pCtx, pstrConfigPath, "%s/%s/%s", CONFIG_ROOT, szProfile, CONFIG_INTERVAL)  );
	SG_ERR_CHECK(  SG_localsettings__get__variant(pCtx, SG_string__sz(pstrConfigPath), NULL, &pvMinutes, NULL)  );

	if (!pvMinutes)
		lMinutes = DEFAULT_SYNC_EVERY_N_MINUTES;
	else
		SG_ERR_CHECK(  SG_variant__get__uint64(pCtx, pvMinutes, &lMinutes)  );

	if (plSyncIntervalMinutes)
		*plSyncIntervalMinutes = lMinutes;
	SG_RETURN_AND_NULL(pszDest, ppszDest);
	SG_RETURN_AND_NULL(pszSrc, ppszSrc);

	SG_RETURN_AND_NULL(pwszErr, ppwszErr);

	/* common cleanup */
fail:
	SG_NULLFREE(pCtx, pszDest);
	SG_NULLFREE(pCtx, pszSrc);
	SG_VARIANT_NULLFREE(pCtx, pvMinutes);
	SG_STRING_NULLFREE(pCtx, pstrConfigPath);

	if (pwszErr)
		free(pwszErr);
}
void sg_sync_client__http__push_begin(
    SG_context* pCtx,
    SG_sync_client * pSyncClient,
    SG_pathname** pFragballDirPathname,
    SG_sync_client_push_handle** ppPush)
{
    char* pszUrl = NULL;
    sg_sync_client_http_push_handle* pPush = NULL;
    SG_vhash* pvhResponse = NULL;
    SG_pathname* pPathUserTemp = NULL;
    SG_pathname* pPathFragballDir = NULL;
    char bufTid[SG_TID_MAX_BUFFER_LENGTH];
    SG_string* pstr = NULL;

    SG_NULLARGCHECK_RETURN(pSyncClient);
    SG_NULLARGCHECK_RETURN(pFragballDirPathname);
    SG_NULLARGCHECK_RETURN(ppPush);

    // Get the URL we're going to post to
    SG_ERR_CHECK(  _get_sync_url(pCtx, pSyncClient->psz_remote_repo_spec, SYNC_URL_SUFFIX JSON_URL_SUFFIX, NULL, NULL, &pszUrl)  );

    SG_ERR_CHECK(  do_url(pCtx, pszUrl, "POST", NULL, pSyncClient->psz_username, pSyncClient->psz_password, &pstr, NULL, SG_TRUE)  );
    SG_ERR_CHECK(  SG_vhash__alloc__from_json__sz(pCtx, &pvhResponse, SG_string__sz(pstr))  );
    SG_STRING_NULLFREE(pCtx, pstr);

    // Alloc a push handle.  Stuff the push ID we received into it.
    {
        const char* pszRef = NULL;
        SG_ERR_CHECK(  SG_alloc(pCtx, 1, sizeof(sg_sync_client_http_push_handle), &pPush)  );
        SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvhResponse, PUSH_ID_KEY, &pszRef)  );
        SG_ERR_CHECK(  SG_strdup(pCtx, pszRef, &pPush->pszPushId)  );
    }

    // Create a temporary local directory for stashing fragballs before shipping them over the network.
    SG_ERR_CHECK(  SG_PATHNAME__ALLOC__USER_TEMP_DIRECTORY(pCtx, &pPathUserTemp)  );
    SG_ERR_CHECK(  SG_tid__generate(pCtx, bufTid, SG_TID_MAX_BUFFER_LENGTH)  );
    SG_ERR_CHECK(  SG_PATHNAME__ALLOC__PATHNAME_SZ(pCtx, &pPathFragballDir, pPathUserTemp, bufTid)  );
    SG_ERR_CHECK(  SG_fsobj__mkdir__pathname(pCtx, pPathFragballDir)  );

    // Tell caller where to stash fragballs for this push.
    SG_RETURN_AND_NULL(pPathFragballDir, pFragballDirPathname);

    // Return the new push handle.
    *ppPush = (SG_sync_client_push_handle*)pPush;
    pPush = NULL;

    /* fall through */
fail:
    SG_STRING_NULLFREE(pCtx, pstr);
    if(SG_context__err_equals(pCtx, SG_ERR_SERVER_HTTP_ERROR))
    {
        const char * szInfo = NULL;
        if(SG_IS_OK(SG_context__err_get_description(pCtx, &szInfo)) && strcmp(szInfo, "405")==0)
            SG_ERR_RESET_THROW(SG_ERR_SERVER_DOESNT_ACCEPT_PUSHES);
    }
    _NULLFREE_PUSH_HANDLE(pCtx, pPush);
    SG_NULLFREE(pCtx, pszUrl);
    SG_PATHNAME_NULLFREE(pCtx, pPathUserTemp);
    SG_PATHNAME_NULLFREE(pCtx, pPathFragballDir);
    SG_VHASH_NULLFREE(pCtx, pvhResponse);
}
void sg_vc_hooks__lookup_by_interface__single_result(
        SG_context* pCtx, 
        SG_repo* pRepo, 
        const char* psz_interface, 
        SG_vhash** ppvh_latest_version
        )
{
	//This version will return only the hook with the largest version.
	//If multiple versions of the hook are defined,
	//all old versions will be deleted.
	SG_varray* pva_hooks = NULL;
	SG_vhash* pvh_latest_hook = NULL;
	SG_zingtx* pztx = NULL;
	char* psz_hid_cs_leaf = NULL;
	SG_dagnode* pdn = NULL;
	SG_changeset* pcs = NULL;

	SG_ERR_CHECK(  SG_vc_hooks__lookup_by_interface(
                pCtx, 
                pRepo, 
                psz_interface,
                &pva_hooks
                )  );

	if (!pva_hooks)
    {
        SG_ERR_THROW2(  SG_ERR_VC_HOOK_MISSING,
                (pCtx, "%s", psz_interface)
                );
    }
    else
    {
        SG_uint32 count = 0;

        SG_ERR_CHECK(  SG_varray__count(pCtx, pva_hooks, &count)  );
        if (0 == count)
        {
            SG_ERR_THROW2(  SG_ERR_VC_HOOK_MISSING,
                    (pCtx, "%s", psz_interface)
                    );
        }
        else
        {
            if (count > 1)
            {
				SG_uint32 i  = 0;
				SG_int32 nVersion = 0;
				SG_int32 nHighestVersion = -1;
				SG_int32 nAmbiguousVersion = -2;
				const char * hidRecToSave = NULL;
				const char * hidrec = NULL;
				SG_vhash * pvh_current_hook = NULL;

                //There are multiple versions installed for this hook.
				//delete the lesser numbered versions.

				for (i=0; i < count; i++)
				{
					SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pva_hooks, i, &pvh_current_hook)   );
					SG_ERR_CHECK(  SG_vhash__get__int32(pCtx, pvh_current_hook, "version", &nVersion)  );
					if (nVersion == nHighestVersion)
					{
						nAmbiguousVersion = nHighestVersion;
					}
					if (nVersion > nHighestVersion)
					{
						nHighestVersion = nVersion;
						SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh_current_hook, "hidrec", &hidRecToSave)  );
					}
				}
				if (nAmbiguousVersion == nHighestVersion)
					SG_ERR_THROW2( SG_ERR_VC_HOOK_AMBIGUOUS, (pCtx, "%s defined multiple times at version %d", psz_interface, nHighestVersion) );
				if (nHighestVersion > 0 && hidRecToSave != NULL)
				{
					SG_audit q;
					SG_ERR_CHECK(  SG_audit__init(pCtx,&q,pRepo,SG_AUDIT__WHEN__NOW,SG_AUDIT__WHO__FROM_SETTINGS)  );

					SG_ERR_CHECK(  SG_zing__get_leaf(pCtx, pRepo, NULL, SG_DAGNUM__VC_HOOKS, &psz_hid_cs_leaf)  );
    
					/* start a changeset */
					SG_ERR_CHECK(  SG_zing__begin_tx(pCtx, pRepo, SG_DAGNUM__VC_HOOKS, q.who_szUserId, psz_hid_cs_leaf, &pztx)  );
					SG_ERR_CHECK(  SG_zingtx__add_parent(pCtx, pztx, psz_hid_cs_leaf)  );

					for (i=0; i < count; i++)
					{
						SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pva_hooks, i, &pvh_current_hook)   );
						SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh_current_hook, "hidrec", &hidrec)  );
						if (SG_strcmp__null(hidrec, hidRecToSave) != 0)
						{
							//This isn't the recid to save.  Delete it!
							SG_ERR_CHECK(  SG_zingtx__delete_record__hid(pCtx, pztx, "hook", hidrec)  );
						}
					}

					/* commit the changes */
					SG_ERR_CHECK(  SG_zing__commit_tx(pCtx, q.when_int64, &pztx, &pcs, &pdn, NULL)  );
				}
            }
			else
			{
				//There's only one hook installed return it.
				SG_vhash * pvh_temp = NULL;
				SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pva_hooks, 0, &pvh_temp)  );
				SG_ERR_CHECK( SG_VHASH__ALLOC__COPY(pCtx, &pvh_latest_hook, pvh_temp)  );
			}
		}
	}
	SG_RETURN_AND_NULL(pvh_latest_hook, ppvh_latest_version);

fail:
	if (pztx)
    {
        SG_ERR_IGNORE(  SG_zing__abort_tx(pCtx, &pztx)  );
    }
	SG_NULLFREE(pCtx, psz_hid_cs_leaf);
	SG_DAGNODE_NULLFREE(pCtx, pdn);
	SG_CHANGESET_NULLFREE(pCtx, pcs);
	SG_VARRAY_NULLFREE(pCtx, pva_hooks);

}