void SG_vc_hooks__ASK__WIT__LIST_ITEMS(
    SG_context* pCtx, 
    SG_repo* pRepo,
	const char * psz_search_term,
	SG_varray *pBugs
	)
{
	SG_vhash* pvh_params = NULL;
    SG_vhash* pvh_result = NULL;
	SG_vhash* pvh_hook = NULL;
	const char* psz_js = NULL;
	const char* psz_descriptor_name = NULL;
	SG_bool hasBugs = SG_FALSE;

	SG_ERR_CHECK(  sg_vc_hooks__lookup_by_interface__single_result(
                pCtx, 
                pRepo, 
                SG_VC_HOOK__INTERFACE__ASK__WIT__LIST_ITEMS,
                &pvh_hook
                )  );

    if (!pvh_hook)
		return;

	SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh_hook, "js", &psz_js)  );

	SG_ERR_CHECK(  SG_VHASH__ALLOC(pCtx, &pvh_params)  );

	

	SG_ERR_CHECK(  SG_repo__get_descriptor_name(pCtx, pRepo, &psz_descriptor_name)  );
	SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "descriptor_name", psz_descriptor_name)  );
	SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "text", psz_search_term)  );

	SG_ERR_CHECK(  SG_vc_hooks__execute(pCtx, psz_js, pvh_params, &pvh_result)  );

	SG_ERR_CHECK(  SG_vhash__has(pCtx, pvh_result, "items", &hasBugs)  );

	if (hasBugs && pBugs)
	{
		SG_varray *bugs = NULL;
		SG_ERR_CHECK(  SG_vhash__get__varray(pCtx, pvh_result, "items", &bugs)  );

		SG_ERR_CHECK(  SG_varray__copy_items(pCtx, bugs, pBugs)  );
	}
fail:
	SG_VHASH_NULLFREE(pCtx, pvh_params);
    SG_VHASH_NULLFREE(pCtx, pvh_result);
    SG_VHASH_NULLFREE(pCtx, pvh_hook);
}
void SG_vc_hooks__BROADCAST__AFTER_COMMIT(
    SG_context* pCtx, 
    SG_repo* pRepo, 
    SG_changeset* pcs,
    const char* psz_tied_branch_name,
    const SG_audit* pq,
    const char* psz_comment,
    const char* const* paszAssocs,
    SG_uint32 count_assocs,
    const SG_stringarray* psa_stamps
    )
{
    SG_varray* pva_hooks = NULL;
    SG_vhash* pvh_params = NULL;
    char* psz_repo_id = NULL;
    char* psz_admin_id = NULL;

    SG_ERR_CHECK(  SG_vc_hooks__lookup_by_interface(
                pCtx, 
                pRepo, 
                SG_VC_HOOK__INTERFACE__BROADCAST__AFTER_COMMIT,
                &pva_hooks
                )  );
    if (pva_hooks)
    {
        SG_uint32 count_hooks = 0;
        SG_uint32 i_hook = 0;
        const char* psz_descriptor_name = NULL;

        SG_ERR_CHECK(  SG_repo__get_admin_id(pCtx, pRepo, &psz_admin_id)  );
        SG_ERR_CHECK(  SG_repo__get_repo_id( pCtx, pRepo, &psz_repo_id )  );
        SG_ERR_CHECK(  SG_repo__get_descriptor_name(pCtx, pRepo, &psz_descriptor_name)  );

        SG_ERR_CHECK(  SG_varray__count(pCtx, pva_hooks, &count_hooks)  );
        for (i_hook=0; i_hook<count_hooks; i_hook++)
        {
            SG_vhash* pvh_hook = NULL;
            const char* psz_js = NULL;
            const char* psz_csid = NULL;
            SG_vhash* pvh_changeset = NULL;

            SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pva_hooks, i_hook, &pvh_hook)  );

            SG_ERR_CHECK(  SG_changeset__get_id_ref(pCtx, pcs, &psz_csid)  );
            SG_ERR_CHECK(  SG_changeset__get_vhash_ref(pCtx, pcs, &pvh_changeset)  );

            SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh_hook, "js", &psz_js)  );

            SG_ERR_CHECK(  SG_VHASH__ALLOC(pCtx, &pvh_params)  );
            SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "csid", psz_csid)  );
            SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "repo_id", psz_repo_id)  );
            SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "admin_id", psz_admin_id)  );
            if (psz_descriptor_name)
            {
                SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "descriptor_name", psz_descriptor_name)  );
            }
            if (pq)
            {
                SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "userid", pq->who_szUserId)  );
            }
            if (psz_comment)
            {
                SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "comment", psz_comment)  );
            }
            if (psz_tied_branch_name)
            {
                SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "branch", psz_tied_branch_name)  );
            }

            SG_ERR_CHECK(  SG_vhash__addcopy__vhash(pCtx, pvh_params, "changeset", pvh_changeset)  );

            if (paszAssocs && count_assocs)
            {
                SG_uint32 i = 0;
                SG_varray* pva_ids = NULL;

                SG_ERR_CHECK(  SG_vhash__addnew__varray(pCtx, pvh_params, "wit_ids", &pva_ids)  );
                for (i=0; i<count_assocs; i++)
                {
                    SG_ERR_CHECK(  SG_varray__append__string__sz(pCtx, pva_ids, paszAssocs[i])  );
                }
            }

            if (psa_stamps)
            {
                SG_uint32 count = 0;
                SG_uint32 i = 0;
                SG_varray* pva_stamps = NULL;

                SG_ERR_CHECK(  SG_vhash__addnew__varray(pCtx, pvh_params, "stamps", &pva_stamps)  );
                SG_ERR_CHECK(  SG_stringarray__count(pCtx, psa_stamps, &count)  );
                for (i=0; i<count; i++)
                {
                    const char* psz_stamp = NULL;

                    SG_ERR_CHECK(  SG_stringarray__get_nth(pCtx, psa_stamps, i, &psz_stamp)  );
                    SG_ERR_CHECK(  SG_varray__append__string__sz(pCtx, pva_stamps, psz_stamp)  );
                }
            }

            SG_ERR_CHECK(  SG_vc_hooks__execute(pCtx, psz_js, pvh_params, NULL)  );
            SG_VHASH_NULLFREE(pCtx, pvh_params);
        }
    }

fail:
    SG_VHASH_NULLFREE(pCtx, pvh_params);
    SG_VARRAY_NULLFREE(pCtx, pva_hooks);
	SG_NULLFREE(pCtx, psz_repo_id);
	SG_NULLFREE(pCtx, psz_admin_id);
}
// TODO not sure we really want to pass this much stuff to this interface
void SG_vc_hooks__ASK__WIT__VALIDATE_ASSOCIATIONS(
    SG_context* pCtx, 
    SG_repo* pRepo, 
    const char* const* paszAssocs,
    SG_uint32 count_assocs,
	SG_varray *pBugs
    )
{
    SG_vhash* pvh_params = NULL;
    SG_vhash* pvh_result = NULL;
    char* psz_repo_id = NULL;
    char* psz_admin_id = NULL;
	SG_vhash* pvh_hook = NULL;
	const char* psz_js = NULL;
	SG_uint32 i = 0;
	SG_varray* pva_ids = NULL;
	const char* psz_descriptor_name = NULL;

    SG_ERR_CHECK(  sg_vc_hooks__lookup_by_interface__single_result(
                pCtx, 
                pRepo, 
                SG_VC_HOOK__INTERFACE__ASK__WIT__VALIDATE_ASSOCIATIONS,
                &pvh_hook
                )  );

    if (!pvh_hook)
		return;

	SG_ERR_CHECK(  SG_repo__get_admin_id(pCtx, pRepo, &psz_admin_id)  );
	SG_ERR_CHECK(  SG_repo__get_repo_id( pCtx, pRepo, &psz_repo_id )  );
	SG_ERR_CHECK(  SG_repo__get_descriptor_name(pCtx, pRepo, &psz_descriptor_name)  );

	SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh_hook, "js", &psz_js)  );

	SG_ERR_CHECK(  SG_VHASH__ALLOC(pCtx, &pvh_params)  );
	SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "repo_id", psz_repo_id)  );
	SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "admin_id", psz_admin_id)  );
	if (psz_descriptor_name)
	{
		SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "descriptor_name", psz_descriptor_name)  );
	}
	SG_ERR_CHECK(  SG_vhash__addnew__varray(pCtx, pvh_params, "wit_ids", &pva_ids)  );
	for (i=0; i<count_assocs; i++)
	{
		SG_ERR_CHECK(  SG_varray__append__string__sz(pCtx, pva_ids, paszAssocs[i])  );
	}

	SG_ERR_CHECK(  SG_vc_hooks__execute(pCtx, psz_js, pvh_params, &pvh_result)  );

	// TODO process the result
	if (pvh_result)
	{
		SG_bool hasErrors = SG_FALSE;
		SG_bool hasBugs = SG_FALSE;

		SG_ERR_CHECK(  SG_vhash__has(pCtx, pvh_result, "error", &hasErrors)  );

		if (hasErrors)
		{
			const char *emsg = NULL;

			SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh_result, "error", &emsg)  );

			SG_ERR_THROW2( SG_ERR_VC_HOOK_REFUSED, (pCtx, "%s", emsg) );
		}

		SG_ERR_CHECK(  SG_vhash__has(pCtx, pvh_result, "bugs", &hasBugs)  );

		if (hasBugs && pBugs)
		{
			SG_varray *bugs = NULL;
			SG_ERR_CHECK(  SG_vhash__get__varray(pCtx, pvh_result, "bugs", &bugs)  );

			SG_ERR_CHECK(  SG_varray__copy_items(pCtx, bugs, pBugs)  );
		}
	}

fail:
    SG_VHASH_NULLFREE(pCtx, pvh_params);
    SG_VHASH_NULLFREE(pCtx, pvh_result);
    SG_VHASH_NULLFREE(pCtx, pvh_hook);
	SG_NULLFREE(pCtx, psz_repo_id);
	SG_NULLFREE(pCtx, psz_admin_id);
}
// TODO not sure we really want to pass this much stuff to this interface
void SG_vc_hooks__ASK__WIT__ADD_ASSOCIATIONS(
    SG_context* pCtx, 
    SG_repo* pRepo, 
    SG_changeset* pcs,
    const char* psz_tied_branch_name,
    const SG_audit* pq,
    const char* psz_comment,
    const char* const* paszAssocs,
    SG_uint32 count_assocs,
    const SG_stringarray* psa_stamps
    )
{
    SG_vhash* pvh_hook = NULL;
    SG_vhash* pvh_params = NULL;
    SG_vhash* pvh_result = NULL;
    char* psz_repo_id = NULL;
    char* psz_admin_id = NULL;

    SG_ERR_CHECK(  sg_vc_hooks__lookup_by_interface__single_result(
                pCtx, 
                pRepo, 
                SG_VC_HOOK__INTERFACE__ASK__WIT__ADD_ASSOCIATIONS,
                &pvh_hook
                )  );

	if (pvh_hook)
    {
        const char* psz_js = NULL;
        SG_uint32 i = 0;
        SG_varray* pva_ids = NULL;
        const char* psz_descriptor_name = NULL;
        const char* psz_csid = NULL;
        SG_vhash* pvh_changeset = NULL;

        SG_ERR_CHECK(  SG_repo__get_admin_id(pCtx, pRepo, &psz_admin_id)  );
        SG_ERR_CHECK(  SG_repo__get_repo_id( pCtx, pRepo, &psz_repo_id )  );
        SG_ERR_CHECK(  SG_repo__get_descriptor_name(pCtx, pRepo, &psz_descriptor_name)  );
        SG_ERR_CHECK(  SG_changeset__get_id_ref(pCtx, pcs, &psz_csid)  );
        SG_ERR_CHECK(  SG_changeset__get_vhash_ref(pCtx, pcs, &pvh_changeset)  );

        SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh_hook, "js", &psz_js)  );

        SG_ERR_CHECK(  SG_VHASH__ALLOC(pCtx, &pvh_params)  );
        SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "csid", psz_csid)  );
        SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "repo_id", psz_repo_id)  );
        SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "admin_id", psz_admin_id)  );
        if (psz_descriptor_name)
        {
            SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "descriptor_name", psz_descriptor_name)  );
        }
        if (pq)
        {
            SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "userid", pq->who_szUserId)  );
        }
        if (psz_comment)
        {
            SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "comment", psz_comment)  );
        }
        if (psz_tied_branch_name)
        {
            SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh_params, "branch", psz_tied_branch_name)  );
        }

        SG_ERR_CHECK(  SG_vhash__addcopy__vhash(pCtx, pvh_params, "changeset", pvh_changeset)  );

        SG_ERR_CHECK(  SG_vhash__addnew__varray(pCtx, pvh_params, "wit_ids", &pva_ids)  );
        for (i=0; i<count_assocs; i++)
        {
            SG_ERR_CHECK(  SG_varray__append__string__sz(pCtx, pva_ids, paszAssocs[i])  );
        }

        if (psa_stamps)
        {
            SG_uint32 count = 0;
            SG_uint32 i = 0;
            SG_varray* pva_stamps = NULL;

            SG_ERR_CHECK(  SG_vhash__addnew__varray(pCtx, pvh_params, "stamps", &pva_stamps)  );
            SG_ERR_CHECK(  SG_stringarray__count(pCtx, psa_stamps, &count)  );
            for (i=0; i<count; i++)
            {
                const char* psz_stamp = NULL;

                SG_ERR_CHECK(  SG_stringarray__get_nth(pCtx, psa_stamps, i, &psz_stamp)  );
                SG_ERR_CHECK(  SG_varray__append__string__sz(pCtx, pva_stamps, psz_stamp)  );
            }
        }

        SG_ERR_CHECK(  SG_vc_hooks__execute(pCtx, psz_js, pvh_params, &pvh_result)  );
        // TODO process the result

		if (pvh_result)
		{
			SG_bool hasErrors = SG_FALSE;

			SG_ERR_CHECK(  SG_vhash__has(pCtx, pvh_result, "error", &hasErrors)  );

			if (hasErrors)
			{
				const char *emsg = NULL;

				SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh_result, "error", &emsg)  );

				SG_ERR_THROW2( SG_ERR_VC_HOOK_REFUSED, (pCtx, "\n:%s: %s", SG_VC_HOOK__INTERFACE__ASK__WIT__ADD_ASSOCIATIONS, emsg) );
			}
		}
    }
    
fail:
    SG_VHASH_NULLFREE(pCtx, pvh_params);
    SG_VHASH_NULLFREE(pCtx, pvh_result);
    SG_VHASH_NULLFREE(pCtx, pvh_hook);
	SG_NULLFREE(pCtx, psz_repo_id);
	SG_NULLFREE(pCtx, psz_admin_id);
}
void SG_sync__closet_user_dags(
	SG_context* pCtx, 
	SG_repo* pRepoSrcNotMine, 
	const char* pszRefHidLeafSrc,
	SG_varray** ppvaSyncedUserList)
{
	char* pszSrcAdminId = NULL;
	char* pszHidLeafSrc = NULL;
	char* pszHidLeafDest = NULL;
	SG_vhash* pvhDescriptors = NULL;
	SG_repo* pRepoDest = NULL;
	SG_repo* pRepoSrcMine = NULL;
	char* pszDestAdminId = NULL;

	/* Using disallowed characters to ensure no collision with an actual repo name.
	 * Not that this isn't actually stored anywhere--we just use it as a key in the
	 * vhash below where the /real/ repos have descriptor names. */
	const char* pszRefUserMasterFakeName = "\\/USER_MASTER\\/"; 

	/* The repo routines do a null arg check of pRepoSrcNotMine.
	   The other args are optional. */

	if (!pszRefHidLeafSrc)
	{
		SG_ERR_CHECK(  SG_zing__get_leaf(pCtx, pRepoSrcNotMine, NULL, SG_DAGNUM__USERS, &pszHidLeafSrc)  );
		pszRefHidLeafSrc = pszHidLeafSrc;
	}

	/* Add all repositories in "normal" status, to the list we'll iterate over. */
	SG_ERR_CHECK(  SG_closet__descriptors__list(pCtx, &pvhDescriptors)  );

	/* If it exists, add the user master repo to the list. */
	{
		SG_bool bExists = SG_FALSE;
		SG_ERR_CHECK(  SG_repo__user_master__exists(pCtx, &bExists)  );
		if (bExists)
			SG_ERR_CHECK(  SG_vhash__add__null(pCtx, pvhDescriptors, pszRefUserMasterFakeName)  );
	}

	/* Iterate over the repositories, syncing the user database. */
	{
		SG_int32 i = 0;
		SG_uint32 numDescriptors = 0;

		SG_ERR_CHECK(  SG_vhash__count(pCtx, pvhDescriptors, &numDescriptors)  );
		for(i = 0; i < (SG_int32)numDescriptors; i++)
		{
			const char* pszRefNameDest = NULL;
			SG_bool bAdminIdsMatch = SG_TRUE;
			const SG_variant* pvRefDest = NULL;

			/* Note that the source repo will be in this loop, too, but we don't need to check for 
			 * it, adding another strcmp, because the leaf hid comparison below will effectively 
			 * skip it. So we do one extra leaf fetch and comparison, total, rather than an extra 
			 * strcmp for every repo in the closet. */

			SG_ERR_CHECK(  SG_vhash__get_nth_pair(pCtx, pvhDescriptors, i, &pszRefNameDest, &pvRefDest)  );

			if (SG_VARIANT_TYPE_NULL == pvRefDest->type)
				SG_ERR_CHECK(  SG_REPO__USER_MASTER__OPEN(pCtx, &pRepoDest)  );
			else
				SG_ERR_CHECK(  SG_REPO__OPEN_REPO_INSTANCE(pCtx, pszRefNameDest, &pRepoDest)  );

			SG_ERR_CHECK(  SG_zing__get_leaf(pCtx, pRepoDest, NULL, SG_DAGNUM__USERS, &pszHidLeafDest)  );

			if (strcmp(pszRefHidLeafSrc, pszHidLeafDest))
			{
				/* Pull from source to dest. 
				 * Pull is generally faster than push, so we're using it on purpose. */
				SG_pull__admin__local(pCtx, pRepoDest, pRepoSrcNotMine, NULL);
				if (SG_context__has_err(pCtx))
				{
					/* If there's an admin id mismatch, don't die. Log a warning and move on. */
					if (SG_context__err_equals(pCtx, SG_ERR_ADMIN_ID_MISMATCH))
					{
						const char* pszRefNameSrc = NULL;

						SG_ERR_DISCARD;

						SG_ERR_CHECK(  SG_repo__get_descriptor_name(pCtx, pRepoSrcNotMine, &pszRefNameSrc)  );
						if (!pszRefNameSrc)
							pszRefNameSrc = pszRefUserMasterFakeName;
						SG_ERR_CHECK(  SG_repo__get_admin_id(pCtx, pRepoSrcNotMine, &pszSrcAdminId)  );

						SG_ERR_CHECK(  SG_repo__get_admin_id(pCtx, pRepoDest, &pszDestAdminId)  );

						SG_ERR_CHECK(  SG_log__report_warning(pCtx, 
							"admin-id mismatch when syncing users: source repo %s has %s, dest repo %s has %s",
							pszRefNameSrc, pszSrcAdminId, pszRefNameDest, pszDestAdminId)  );

						bAdminIdsMatch = SG_FALSE;

						SG_NULLFREE(pCtx, pszDestAdminId);
						SG_NULLFREE(pCtx, pszSrcAdminId);
					}
					else
						SG_ERR_RETHROW;
				}

				if (bAdminIdsMatch)
				{
					SG_NULLFREE(pCtx, pszHidLeafDest);
					SG_ERR_CHECK(  SG_zing__get_leaf(pCtx, pRepoDest, NULL, SG_DAGNUM__USERS, &pszHidLeafDest)  );

					if (strcmp(pszRefHidLeafSrc, pszHidLeafDest))
					{
						/* The pull from source to dest resulted in a new leaf. 
						 * Use the new leaf and restart the loop. */
						SG_NULLFREE(pCtx, pszHidLeafSrc);
						pszRefHidLeafSrc = pszHidLeafSrc = pszHidLeafDest;
						pszHidLeafDest = NULL;

						SG_REPO_NULLFREE(pCtx, pRepoSrcMine);
						pRepoSrcNotMine = pRepoSrcMine = pRepoDest;
						pRepoDest = NULL;

						i = -1; /* start again at the first descriptor */
					}

				}
			}

			SG_NULLFREE(pCtx, pszHidLeafDest);
			SG_REPO_NULLFREE(pCtx, pRepoDest);
		}
	}

	if (ppvaSyncedUserList)
		SG_ERR_CHECK(  SG_user__list_all(pCtx, pRepoSrcNotMine, ppvaSyncedUserList)  );

	/* fall through */
fail:
	SG_NULLFREE(pCtx, pszSrcAdminId);
	SG_NULLFREE(pCtx, pszHidLeafSrc);
	SG_NULLFREE(pCtx, pszHidLeafDest);
	SG_VHASH_NULLFREE(pCtx, pvhDescriptors);
	SG_REPO_NULLFREE(pCtx, pRepoDest);
	SG_REPO_NULLFREE(pCtx, pRepoSrcMine);
	SG_NULLFREE(pCtx, pszDestAdminId);
}
/**
 * Request to UNLOCK on one or more files.
 *
 * WARNING: This routine deviates from the model of most
 * WARNING: of the SG_wc__ level-8 and/or SG_wc_tx level-7
 * WARNING: API routines because we cannot just "queue" an
 * WARNING: unlock like we do a RENAME with everything
 * WARNING: contained within the pWcTx; we actually have
 * WARNING: to update the locks dag (which is outside of
 * WARNING: the scope of the WC TX).
 * WARNING:
 * WARNING: So we only have a level-8 API
 * WARNING: so that we can completely control/bound the TX.
 *
 * We also deviate in that we don't take a --test
 * nor --verbose option.  Which means we don't have a
 * JOURNAL to mess with.
 *
 */
void SG_wc__unlock(SG_context * pCtx,
                   const SG_pathname* pPathWc,
                   const SG_stringarray * psaInputs,
                   SG_bool bForce,
                   const char * psz_username,
                   const char * psz_password,
                   const char * psz_repo_upstream)
{
    SG_wc_tx * pWcTx = NULL;
    SG_audit q;
    SG_uint32 nrInputs = 0;
    SG_uint32 k;
    char * psz_tied_branch_name = NULL;
    char * psz_repo_upstream_allocated = NULL;
    SG_vhash * pvh_gids = NULL;
    const char * pszRepoDescriptorName = NULL;	// we do not own this

    if (psaInputs)
        SG_ERR_CHECK(  SG_stringarray__count(pCtx, psaInputs, &nrInputs)  );
    if (nrInputs == 0)
        SG_ERR_THROW2(  SG_ERR_INVALIDARG,
                        (pCtx, "Nothing to unlock")  );

    // psz_username is optional (assume no auth required)
    // psz_password is optional (assume no auth required)
    // psz_server is optional (assume default server)

    // Begin a WC TX so that we get all of the good stuff
    // (like mapping the CWD into a REPO handle and mapping
    // the inputs into GIDs).
    //
    // At this point I don't believe that setting a lock
    // will actually make any changes in WC.DB, so I'm
    // making it a READ-ONLY TX.
    //
    // My assumption is that the lock actually gets
    // written to the Locks DAG and shared with the server.
    // But I still want a TX handle for all of the other stuff.

    SG_ERR_CHECK(  SG_WC_TX__ALLOC__BEGIN(pCtx, &pWcTx, pPathWc, SG_FALSE)  );

    // We need the repo descriptor name later for the push/pull
    // and to optionally look up the default destination for
    // this repo.  The pRepo stores this *IFF* it was properly
    // opened (using a name).

    SG_ERR_CHECK(  SG_repo__get_descriptor_name(pCtx, pWcTx->pDb->pRepo,
                   &pszRepoDescriptorName)  );
    SG_ASSERT_RELEASE_FAIL2(  (pszRepoDescriptorName && *pszRepoDescriptorName),
                              (pCtx, "SG_wc__unlock: Could not get repo descriptor name.")  );

    // now we need to know what branch we are tied to.
    // if we're not tied, fail
    SG_ERR_CHECK(  SG_wc_tx__branch__get(pCtx, pWcTx, &psz_tied_branch_name)  );
    if (!psz_tied_branch_name)
        SG_ERR_THROW(  SG_ERR_NOT_TIED  );

    SG_ERR_CHECK(  SG_VHASH__ALLOC(pCtx, &pvh_gids)  );
    for (k=0; k<nrInputs; k++)
    {
        const char * pszInput_k;

        SG_ERR_CHECK(  SG_stringarray__get_nth(pCtx, psaInputs, k, &pszInput_k)  );
        SG_ERR_CHECK(  _map_input(pCtx, pWcTx, pvh_gids, pszInput_k)  );
    }

    if (!psz_repo_upstream)
    {
        SG_localsettings__descriptor__get__sz(pCtx, pszRepoDescriptorName,
                                              "paths/default",
                                              &psz_repo_upstream_allocated);
        if (SG_context__err_equals(pCtx, SG_ERR_NOT_FOUND))
            SG_ERR_REPLACE_ANY_RETHROW(  SG_ERR_NO_SERVER_SPECIFIED  );
        else
            SG_ERR_CHECK_CURRENT;

        psz_repo_upstream = psz_repo_upstream_allocated;
    }

    SG_ERR_CHECK(  SG_audit__init(pCtx, &q, pWcTx->pDb->pRepo,
                                  SG_AUDIT__WHEN__NOW,
                                  SG_AUDIT__WHO__FROM_SETTINGS)  );

    // OK, we have all the pieces.  Time to call the unlock code
    SG_ERR_CHECK(  SG_vc_locks__unlock(
                       pCtx,
                       pszRepoDescriptorName,
                       psz_repo_upstream,
                       psz_username,
                       psz_password,
                       psz_tied_branch_name,
                       pvh_gids,
                       bForce,
                       &q
                   )  );

    // Fall through and let the normal fail code discard/cancel
    // the read-only WC TX.  This will not affect the Locks DAG
    // nor the server.

fail:
    SG_ERR_IGNORE(  SG_wc_tx__cancel(pCtx, pWcTx)  );
    SG_WC_TX__NULLFREE(pCtx, pWcTx);
    SG_NULLFREE(pCtx, psz_tied_branch_name);
    SG_NULLFREE(pCtx, psz_repo_upstream_allocated);
    SG_VHASH_NULLFREE(pCtx, pvh_gids);
}
示例#7
0
void SG_localsettings__get__variant(SG_context * pCtx, const char * psz_path, SG_repo* pRepo, SG_variant** ppv, SG_string** ppstr_where_found)
{
    SG_jsondb* p = NULL;
    SG_string* pstr_path = NULL;
    SG_variant* pv = NULL;
    char* psz_repo_id = NULL;
    char* psz_admin_id = NULL;
    const char* psz_ref_descriptor_name = NULL;

    SG_ASSERT(pCtx);
    SG_NONEMPTYCHECK_RETURN(psz_path);

    SG_ERR_CHECK(  SG_closet__get_localsettings(pCtx, &p)  );

    SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pstr_path)  );
    if ('/' == psz_path[0])
    {
        SG_bool b = SG_FALSE;

        SG_ERR_CHECK(  SG_string__sprintf(pCtx, pstr_path, "%s", psz_path)  );
        SG_ERR_CHECK(  SG_jsondb__has(pCtx, p, SG_string__sz(pstr_path), &b)  );
        if (b)
        {
            SG_ERR_CHECK(  SG_jsondb__get__variant(pCtx, p, SG_string__sz(pstr_path), &pv)  );
        }
    }
    else
    {
        SG_bool b_has_val = SG_FALSE;

        // try the instance of the repo
        if (!b_has_val && pRepo)
        {
            SG_ERR_CHECK(  SG_repo__get_descriptor_name(pCtx, pRepo, &psz_ref_descriptor_name)  );

            SG_ERR_CHECK(  SG_string__sprintf(pCtx, pstr_path, "%s/%s/%s",
                        SG_LOCALSETTING__SCOPE__INSTANCE,
                        psz_ref_descriptor_name,
                        psz_path
                        )  );
            SG_ERR_CHECK(  SG_jsondb__has(pCtx, p, SG_string__sz(pstr_path), &b_has_val)  );
            if (b_has_val)
            {
                SG_ERR_CHECK(  SG_jsondb__get__variant(pCtx, p, SG_string__sz(pstr_path), &pv)  );
            }
        }

        // then the repo
        if (!b_has_val && pRepo)
        {
            SG_ERR_CHECK(  SG_repo__get_repo_id(pCtx, pRepo, &psz_repo_id)  );

            SG_ERR_CHECK(  SG_string__sprintf(pCtx, pstr_path, "%s/%s/%s",
                        SG_LOCALSETTING__SCOPE__REPO,
                        psz_repo_id,
                        psz_path
                        )  );
            SG_ERR_CHECK(  SG_jsondb__has(pCtx, p, SG_string__sz(pstr_path), &b_has_val)  );
            if (b_has_val)
            {
                SG_ERR_CHECK(  SG_jsondb__get__variant(pCtx, p, SG_string__sz(pstr_path), &pv)  );
            }
        }

        // then the admin group of repos
        if (!b_has_val && pRepo)
        {
            SG_ERR_CHECK(  SG_repo__get_admin_id(pCtx, pRepo, &psz_admin_id)  );

            SG_ERR_CHECK(  SG_string__sprintf(pCtx, pstr_path, "%s/%s/%s",
                        SG_LOCALSETTING__SCOPE__ADMIN,
                        psz_admin_id,
                        psz_path
                        )  );
            SG_ERR_CHECK(  SG_jsondb__has(pCtx, p, SG_string__sz(pstr_path), &b_has_val)  );
            if (b_has_val)
            {
                SG_ERR_CHECK(  SG_jsondb__get__variant(pCtx, p, SG_string__sz(pstr_path), &pv)  );
            }
        }

        // then the machine
        if (!b_has_val)
        {
            SG_ERR_CHECK(  SG_string__sprintf(pCtx, pstr_path, "%s/%s", SG_LOCALSETTING__SCOPE__MACHINE, psz_path)  );
            SG_ERR_CHECK(  SG_jsondb__has(pCtx, p, SG_string__sz(pstr_path), &b_has_val)  );
            if (b_has_val)
            {
                SG_ERR_CHECK(  SG_jsondb__get__variant(pCtx, p, SG_string__sz(pstr_path), &pv)  );
            }
        }

        // then the factory default
        if (!b_has_val)
        {
            SG_STRING_NULLFREE(pCtx, pstr_path);
            SG_ERR_CHECK(  SG_localsettings__factory__get__variant(pCtx, psz_path, &pv)  );
        }
    }

    *ppv = pv;
    pv = NULL;

    if (ppstr_where_found)
    {
        *ppstr_where_found = pstr_path;
        pstr_path = NULL;
    }

fail:
    SG_NULLFREE(pCtx, psz_repo_id);
    SG_NULLFREE(pCtx, psz_admin_id);
    SG_VARIANT_NULLFREE(pCtx, pv);
    SG_STRING_NULLFREE(pCtx, pstr_path);
    SG_JSONDB_NULLFREE(pCtx, p);
}