void u0052_zip__test_2(SG_context * pCtx)
{
    const char* psz_text = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

    SG_uint32 len_orig = SG_STRLEN(psz_text) + 1;
    SG_byte* p_compressed = NULL;
    SG_byte* p_uncompressed = NULL;
    SG_uint32 len_compressed = 0;

    SG_ERR_CHECK(  SG_zlib__deflate__memory(
                pCtx, 
                (SG_byte*) psz_text,
                len_orig,
                &p_compressed,
                &len_compressed
                )  );

    SG_ERR_CHECK(  SG_allocN(pCtx, len_orig, p_uncompressed)  );
    SG_ERR_CHECK(  SG_zlib__inflate__memory(
                pCtx,
                p_compressed,
                len_compressed,
                p_uncompressed,
                len_orig
                )  );

    VERIFY_COND("name", (0 == strcmp(psz_text, (char*) p_uncompressed)));

fail:
    SG_NULLFREE(pCtx, p_compressed);
    SG_NULLFREE(pCtx, p_uncompressed);
}
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);
}
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);
}
// Insert one or more nodes into a node list. Does not assume the list is taking ownership of the nodes.
static void _node_list__insert_at(SG_context * pCtx, _node_list_t * pList, SG_uint32 index, _node_t ** ppNewNodes, SG_uint32 countNewNodes)
{
	if (pList->countAllocated < pList->count+countNewNodes)
	{
		_node_t ** tmp = NULL;
		while(pList->countAllocated < pList->count+countNewNodes)
			pList->countAllocated *= 2;
		SG_ERR_CHECK_RETURN(  SG_allocN(pCtx, pList->countAllocated, tmp)  );
		(void)memmove(tmp, pList->p, pList->count*sizeof(pList->p[0]));
		SG_NULLFREE(pCtx, pList->p);
		pList->p = tmp;
	}
	
	if (index < pList->count)
		(void)memmove(&pList->p[index+countNewNodes], &pList->p[index], (pList->count-index)*sizeof(pList->p[0]));
	
	memcpy(&pList->p[index], ppNewNodes, countNewNodes*sizeof(ppNewNodes[0]));
	pList->count += countNewNodes;
}
// Add a new dagnode to the work queue, having already determined that it needs
// to be added at a particular location in the list.
static void _hrca_work_queue__insert_at(
	SG_context * pCtx,
	_hrca_work_queue_t * pWorkQueue,
	SG_uint32 index,
	const char * pszHid,
	SG_uint32 revno,
	SG_dagnode ** ppDagnode, // Takes ownership and nulls the caller's copy.
	SG_bitvector * pIsAncestorOf
	)
{
	SG_ERR_CHECK(  SG_rbtree__add__with_assoc(pCtx, pWorkQueue->pRevnoCache, pszHid, ((char*)NULL)+revno)  );

	if (pWorkQueue->length==pWorkQueue->allocatedLength)
	{
		_hrca_work_queue_item_t * tmp = NULL;
		SG_ERR_CHECK(  SG_allocN(pCtx, pWorkQueue->allocatedLength*2, tmp)  );
		(void)memmove(tmp, pWorkQueue->p,
			pWorkQueue->length*sizeof(_hrca_work_queue_item_t));
		SG_NULLFREE(pCtx, pWorkQueue->p);
		pWorkQueue->p = tmp;
		pWorkQueue->allocatedLength *= 2;
	}

	if (index < pWorkQueue->length)
	{
		(void)memmove(&pWorkQueue->p[index+1], &pWorkQueue->p[index],
			(pWorkQueue->length-index)*sizeof(_hrca_work_queue_item_t));
	}

	pWorkQueue->p[index].revno = revno;
	SG_ERR_CHECK(  SG_BITVECTOR__ALLOC__COPY(pCtx, &pWorkQueue->p[index].pIsAncestorOf, pIsAncestorOf)  );
	pWorkQueue->p[index].pDagnode = *ppDagnode;

	*ppDagnode = NULL;

	++pWorkQueue->length;

	return;
fail:
	;
}
// Add a new dagnode to the work queue, having already determined that it needs
// to be added at a particular location in the list.
static void _fnsc_work_queue__insert_at(
	SG_context * pCtx,
	_fnsc_work_queue_t * pWorkQueue,
	SG_uint32 index,
	const char * pszHid,
	SG_uint32 revno,
	SG_dagnode ** ppDagnode, // Takes ownership and nulls the caller's copy.
	SG_byte isAncestorOf
	)
{
	SG_ERR_CHECK_RETURN(  SG_rbtree__add__with_assoc(pCtx, pWorkQueue->pRevnoCache, pszHid, ((char*)NULL)+revno)  );
	
	if (pWorkQueue->length==pWorkQueue->allocatedLength)
	{
		_fnsc_work_queue_item_t * tmp = NULL;
		SG_ERR_CHECK_RETURN(  SG_allocN(pCtx, pWorkQueue->allocatedLength*2, tmp)  );
		(void)memmove(tmp, pWorkQueue->p,
			pWorkQueue->length*sizeof(_fnsc_work_queue_item_t));
		SG_NULLFREE(pCtx, pWorkQueue->p);
		pWorkQueue->p = tmp;
		pWorkQueue->allocatedLength *= 2;
	}
	
	if (index < pWorkQueue->length)
	{
		(void)memmove(&pWorkQueue->p[index+1], &pWorkQueue->p[index],
			(pWorkQueue->length-index)*sizeof(_fnsc_work_queue_item_t));
	}
	
	pWorkQueue->p[index].revno = revno;
	pWorkQueue->p[index].isAncestorOf = isAncestorOf;
	pWorkQueue->p[index].pDagnode = *ppDagnode;
	
	*ppDagnode = NULL;
	
	++pWorkQueue->length;
	if(isAncestorOf==_ANCESTOR_OF_NEW)
	{
		++pWorkQueue->numAncestorsOfNewOnTheQueue;
	}
}
Beispiel #7
0
void SG_sync__add_blobs_to_fragball(SG_context* pCtx, SG_repo* pRepo, SG_pathname* pPath_fragball, SG_vhash* pvh_missing_blobs)
{
	SG_uint32 iMissingBlobCount;
	const char** paszHids = NULL;

	SG_NULLARGCHECK_RETURN(pPath_fragball);
	SG_NULLARGCHECK_RETURN(pvh_missing_blobs);

	SG_ERR_CHECK_RETURN(  SG_vhash__count(pCtx, pvh_missing_blobs, &iMissingBlobCount)  );
	if (iMissingBlobCount > 0)
	{
		SG_uint32 i;
		SG_ERR_CHECK(  SG_allocN(pCtx, iMissingBlobCount, paszHids)  );
		for (i = 0; i < iMissingBlobCount; i++)
			SG_ERR_CHECK(  SG_vhash__get_nth_pair(pCtx, pvh_missing_blobs, i, &paszHids[i], NULL)  );
	}

	SG_ERR_CHECK(  SG_fragball__append__blobs(pCtx, pPath_fragball, pRepo, paszHids, iMissingBlobCount)  );

	/* fall through */
fail:
	SG_NULLFREE(pCtx, paszHids);
}
static void _node_list__init(SG_context * pCtx, _node_list_t * pList, SG_uint32 countAllocateUpfront)
{
	SG_ERR_CHECK_RETURN(  SG_allocN(pCtx, countAllocateUpfront, pList->p)  );
	pList->countAllocated = countAllocateUpfront;
}
/**
 * Finds any character from a given set within a string and replaces them with a
 * specified replacement string.
 */
static void _replace_chars_with_string(
    SG_context* pCtx,         //< [in] [out] Error and context info.
    SG_string*  sValue,       //< [in] [out] String to perform replacements in.
    const char* szChars,      //< [in] Set of characters to replace, as a string.
    //<      NULL is treated as an empty string.
    const char* szReplacement //< [in] String to use as a replacement for the characters.
    //<      This whole string is a replacement for each found character.
    //<      NULL is treated as an empty string.
)
{
    SG_int32* pValue32       = NULL;
    SG_uint32 uValue32       = 0u;
    SG_int32* pChars32       = NULL;
    SG_uint32 uChars32       = 0u;
    SG_int32* pReplacement32 = NULL;
    SG_uint32 uReplacement32 = 0u;
    SG_int32* pResult32      = NULL;
    SG_uint32 uResult32      = 0u;
    char*     szResult       = NULL;
    SG_uint32 uResult        = 0u;
    SG_uint32 uValueIndex    = 0u;

    SG_NULLARGCHECK(sValue);

    // treat NULLs as empty strings
    if (szChars == NULL)
    {
        szChars = "";
    }
    if (szReplacement == NULL)
    {
        szReplacement = "";
    }

    // convert everything to UTF32
    // I couldn't come up with a way to do this directly in UTF8 using the APIs
    // available in sg_utf8.
    SG_ERR_CHECK(  _utf8_to_utf32(pCtx, SG_string__sz(sValue), &pValue32, &uValue32)  );
    SG_ERR_CHECK(  _utf8_to_utf32(pCtx, szChars, &pChars32, &uChars32)  );
    SG_ERR_CHECK(  _utf8_to_utf32(pCtx, szReplacement, &pReplacement32, &uReplacement32)  );

    // allocate a result buffer
    if (uReplacement32 > 1u)
    {
        // largest possible size we could end up with is if we replace every single
        // character in the value with the replacement string
        SG_ERR_CHECK(  SG_allocN(pCtx, (uReplacement32 * uValue32) + 1u, pResult32)  );
    }
    else
    {
        // largest possible size we could end up with is if we do no replacements
        // at all and are left with exactly the input value
        SG_ERR_CHECK(  SG_allocN(pCtx, uValue32 + 1u, pResult32)  );
    }

    // run through each character in the value
    for (uValueIndex = 0u; uValueIndex < uValue32; ++uValueIndex)
    {
        SG_int32  iValueChar  = pValue32[uValueIndex];
        SG_bool   bReplace    = SG_FALSE;
        SG_uint32 uCharsIndex = 0u;

        // check if this character should be replaced
        for (uCharsIndex = 0u; uCharsIndex < uChars32; ++uCharsIndex)
        {
            if (iValueChar == pChars32[uCharsIndex])
            {
                bReplace = SG_TRUE;
                break;
            }
        }
        if (bReplace == SG_FALSE)
        {
            // append the character to the output
            pResult32[uResult32] = iValueChar;
            ++uResult32;
        }
        else
        {
            // append the replacement string to the output
            memcpy((void*)(pResult32 + uResult32), (void*)pReplacement32, uReplacement32 * sizeof(SG_int32));
            uResult32 += uReplacement32;
        }
    }

    // NULL-terminate the result and convert it back to UTF8
    pResult32[uResult32] = 0;
    SG_ERR_CHECK(  _utf32_to_utf8(pCtx, pResult32, &szResult, &uResult)  );

    // return the result by replacing the original value's contents
    SG_ERR_CHECK(  SG_string__adopt_buffer(pCtx, sValue, szResult, uResult)  );
    szResult = NULL;

fail:
    SG_NULLFREE(pCtx, pValue32);
    SG_NULLFREE(pCtx, pChars32);
    SG_NULLFREE(pCtx, pReplacement32);
    SG_NULLFREE(pCtx, pResult32);
    SG_NULLFREE(pCtx, szResult);
    return;
}
void SG_dagquery__highest_revno_common_ancestor(
	SG_context * pCtx,
	SG_repo * pRepo,
	SG_uint64 dagnum,
	const SG_stringarray * pInputNodeHids,
	char ** ppOutputNodeHid
	)
{
	const char * const * paszInputNodeHids = NULL;
	SG_uint32 countInputNodes = 0;
	SG_repo_fetch_dagnodes_handle * pDagnodeFetcher = NULL;
	_hrca_work_queue_t workQueue = {NULL, 0, 0, NULL};
	SG_uint32 i;
	SG_dagnode * pDagnode = NULL;
	const char * pszHidRef = NULL;
	SG_bitvector * pIsAncestorOf = NULL;
	SG_uint32 countIsAncestorOf = 0;

	SG_ASSERT(pCtx!=NULL);
	SG_NULLARGCHECK(pRepo);
	SG_NULLARGCHECK(pInputNodeHids);
	SG_ERR_CHECK(  SG_stringarray__sz_array_and_count(pCtx, pInputNodeHids, &paszInputNodeHids, &countInputNodes)  );
	SG_ARGCHECK(countInputNodes>0, pInputNodeHids);
	SG_NULLARGCHECK(ppOutputNodeHid);

	SG_ERR_CHECK(  SG_repo__fetch_dagnodes__begin(pCtx, pRepo, dagnum, &pDagnodeFetcher)  );

	SG_ERR_CHECK(  SG_allocN(pCtx, _HRCA_WORK_QUEUE_INIT_LENGTH, workQueue.p)  );
	workQueue.allocatedLength = _HRCA_WORK_QUEUE_INIT_LENGTH;
	SG_ERR_CHECK(  SG_RBTREE__ALLOC(pCtx, &workQueue.pRevnoCache)  );

	SG_ERR_CHECK(  SG_BITVECTOR__ALLOC(pCtx, &pIsAncestorOf, countInputNodes)  );
	for(i=0; i<countInputNodes; ++i)
	{
		SG_ERR_CHECK(  SG_bitvector__zero(pCtx, pIsAncestorOf)  );
		SG_ERR_CHECK(  SG_bitvector__set_bit(pCtx, pIsAncestorOf, i, SG_TRUE)  );
		SG_ERR_CHECK(  _hrca_work_queue__insert(pCtx, &workQueue, paszInputNodeHids[i], pRepo, pDagnodeFetcher, pIsAncestorOf)  );
	}
	SG_BITVECTOR_NULLFREE(pCtx, pIsAncestorOf);

	SG_ERR_CHECK(  _hrca_work_queue__pop(pCtx, &workQueue, &pDagnode, &pszHidRef, &pIsAncestorOf)  );
	SG_ERR_CHECK(  SG_bitvector__count_set_bits(pCtx, pIsAncestorOf, &countIsAncestorOf)  );
	while(countIsAncestorOf < countInputNodes)
	{
		SG_uint32 count_parents = 0;
		const char** parents = NULL;
		SG_ERR_CHECK(  SG_dagnode__get_parents__ref(pCtx, pDagnode, &count_parents, &parents)  );
		for(i=0; i<count_parents; ++i)
			SG_ERR_CHECK(  _hrca_work_queue__insert(pCtx, &workQueue, parents[i], pRepo, pDagnodeFetcher, pIsAncestorOf)  );
		
		SG_DAGNODE_NULLFREE(pCtx, pDagnode);
		SG_BITVECTOR_NULLFREE(pCtx, pIsAncestorOf);

		SG_ERR_CHECK(  _hrca_work_queue__pop(pCtx, &workQueue, &pDagnode, &pszHidRef, &pIsAncestorOf)  );
		SG_ERR_CHECK(  SG_bitvector__count_set_bits(pCtx, pIsAncestorOf, &countIsAncestorOf)  );
	}

	SG_ERR_CHECK(  SG_strdup(pCtx, pszHidRef, ppOutputNodeHid)  );

	SG_DAGNODE_NULLFREE(pCtx, pDagnode);
	SG_BITVECTOR_NULLFREE(pCtx, pIsAncestorOf);

	for(i=0; i<workQueue.length; ++i)
	{
		SG_DAGNODE_NULLFREE(pCtx, workQueue.p[i].pDagnode);
		SG_BITVECTOR_NULLFREE(pCtx, workQueue.p[i].pIsAncestorOf);
	}
	SG_NULLFREE(pCtx, workQueue.p);
	SG_RBTREE_NULLFREE(pCtx, workQueue.pRevnoCache);

	SG_ERR_CHECK(  SG_repo__fetch_dagnodes__end(pCtx, pRepo, &pDagnodeFetcher)  );

	return;
fail:
	for(i=0; i<workQueue.length; ++i)
	{
		SG_DAGNODE_NULLFREE(pCtx, workQueue.p[i].pDagnode);
		SG_BITVECTOR_NULLFREE(pCtx, workQueue.p[i].pIsAncestorOf);
	}
	SG_NULLFREE(pCtx, workQueue.p);
	SG_RBTREE_NULLFREE(pCtx, workQueue.pRevnoCache);

	SG_DAGNODE_NULLFREE(pCtx, pDagnode);
	SG_BITVECTOR_NULLFREE(pCtx, pIsAncestorOf);

	if(pDagnodeFetcher!=NULL)
	{
		SG_ERR_IGNORE(  SG_repo__fetch_dagnodes__end(pCtx, pRepo, &pDagnodeFetcher)  );
	}
}
void SG_dagquery__find_new_since_common(
	SG_context * pCtx,
	SG_repo * pRepo,
	SG_uint64 dagnum,
	const char * pszOldNodeHid,
	const char * pszNewNodeHid,
	SG_stringarray ** ppResults
	)
{
	_fnsc_work_queue_t workQueue = {NULL, 0, 0, 0, NULL};
	SG_uint32 i;
	SG_dagnode * pDagnode = NULL;
	SG_stringarray * pResults = NULL;
	
	SG_ASSERT(pCtx!=NULL);
	SG_NULLARGCHECK(pRepo);
	SG_NONEMPTYCHECK(pszOldNodeHid);
	SG_NONEMPTYCHECK(pszNewNodeHid);
	SG_NULLARGCHECK(ppResults);
	
	SG_ERR_CHECK(  SG_allocN(pCtx, _FNSC_WORK_QUEUE_INIT_LENGTH, workQueue.p)  );
	workQueue.allocatedLength = _FNSC_WORK_QUEUE_INIT_LENGTH;
	SG_ERR_CHECK(  SG_RBTREE__ALLOC(pCtx, &workQueue.pRevnoCache)  );
	
	SG_ERR_CHECK(  _fnsc_work_queue__insert(pCtx, &workQueue, pszOldNodeHid, dagnum, pRepo, _ANCESTOR_OF_OLD)  );
	SG_ERR_CHECK(  _fnsc_work_queue__insert(pCtx, &workQueue, pszNewNodeHid, dagnum, pRepo, _ANCESTOR_OF_NEW)  );
	
	SG_ERR_CHECK(  SG_STRINGARRAY__ALLOC(pCtx, &pResults, 32)  );
	while(workQueue.numAncestorsOfNewOnTheQueue > 0)
	{
		const char * pszHidRef = NULL;
		SG_byte isAncestorOf = 0;
		
		SG_ERR_CHECK(  _fnsc_work_queue__pop(pCtx, &workQueue, &pDagnode, &pszHidRef, &isAncestorOf)  );
		if (isAncestorOf==_ANCESTOR_OF_NEW)
			SG_ERR_CHECK(  SG_stringarray__add(pCtx, pResults, pszHidRef)  );
		
		{
			SG_uint32 count_parents = 0;
			const char** parents = NULL;
			SG_ERR_CHECK(  SG_dagnode__get_parents__ref(pCtx, pDagnode, &count_parents, &parents)  );
			for(i=0; i<count_parents; ++i)
				SG_ERR_CHECK(  _fnsc_work_queue__insert(pCtx, &workQueue, parents[i], dagnum, pRepo, isAncestorOf)  );
		}
		
		SG_DAGNODE_NULLFREE(pCtx, pDagnode);
	}
	
	for(i=0; i<workQueue.length; ++i)
		SG_DAGNODE_NULLFREE(pCtx, workQueue.p[i].pDagnode);
	SG_NULLFREE(pCtx, workQueue.p);
	SG_RBTREE_NULLFREE(pCtx, workQueue.pRevnoCache);
	
	*ppResults = pResults;

	return;
fail:
	for(i=0; i<workQueue.length; ++i)
		SG_DAGNODE_NULLFREE(pCtx, workQueue.p[i].pDagnode);
	SG_NULLFREE(pCtx, workQueue.p);
	SG_RBTREE_NULLFREE(pCtx, workQueue.pRevnoCache);

	SG_DAGNODE_NULLFREE(pCtx, pDagnode);

	SG_STRINGARRAY_NULLFREE(pCtx, pResults);
}
void SG_password__get(
	SG_context *pCtx,
	const char *szRepoSpec,
	const char *szUsername,
	SG_string **ppstrPassword)
{
	SG_string* pstrTarget = NULL;
	SG_string* pstrPassword = NULL;
	LPWSTR pwszTarget = NULL;
	SG_byte* pbPassword = NULL;
	PCREDENTIAL pCred = NULL;
	BOOL result = FALSE;

	SG_NULLARGCHECK_RETURN(szRepoSpec);
	SG_NULLARGCHECK_RETURN(szUsername);
	SG_NULLARGCHECK_RETURN(ppstrPassword);

	_get_key(pCtx, szRepoSpec, szUsername, &pstrTarget);
	if (SG_CONTEXT__HAS_ERR(pCtx))
	{
		SG_error err, err2;
		err2 = SG_context__get_err(pCtx, &err);
		if (SG_IS_ERROR(err2))
		{
			SG_ERR_DISCARD;
			SG_ERR_THROW(err2);
		}

		if (err & __SG_ERR__GETLASTERROR__)
			SG_ERR_DISCARD;
		else
			SG_ERR_RETHROW;
	}

	if (pstrTarget)
	{
		SG_ERR_CHECK(  SG_utf8__extern_to_os_buffer__wchar(pCtx, SG_string__sz(pstrTarget), &pwszTarget, NULL)  );

		result = CredReadW(pwszTarget, CRED_TYPE_GENERIC, 0, &pCred);
		if (!result)
		{
			DWORD err = GetLastError();
			if (err != ERROR_NOT_FOUND && err != ERROR_NO_SUCH_LOGON_SESSION)
				SG_ERR_THROW2( SG_ERR_GETLASTERROR(GetLastError()), (pCtx, "%s", "unable to retrieve saved credentials") );
		}
		else
		{
			SG_uint32 size = pCred->CredentialBlobSize+sizeof(wchar_t);
			SG_ERR_CHECK(  SG_allocN(pCtx, pCred->CredentialBlobSize+sizeof(wchar_t), pbPassword)  );
			memcpy(pbPassword, pCred->CredentialBlob, size);
			SG_ERR_CHECK(  SG_string__alloc(pCtx, &pstrPassword)  );
			SG_ERR_CHECK(  SG_utf8__intern_from_os_buffer__wchar(pCtx, pstrPassword, (const LPWSTR)pbPassword)  );

			*ppstrPassword = pstrPassword;
			pstrPassword = NULL;
		}
	}

	/* fall through */
fail:
	SG_STRING_NULLFREE(pCtx, pstrTarget);
	SG_STRING_NULLFREE(pCtx, pstrPassword);
	SG_NULLFREE(pCtx, pwszTarget);
	SG_NULLFREE(pCtx, pbPassword);
	if (pCred)
		CredFree(pCred);
}