Ejemplo n.º 1
0
static void _add_dagnodes_until_connected(SG_context* pCtx, 
										  SG_vhash** ppvhStagingStatus, 
										  sg_pull_instance_data* pMe, 
										  SG_client* pClient)
{
	SG_bool disconnected = SG_FALSE;
	SG_vhash* pvhFragballRequest = NULL;
	char* pszFragballName = NULL;
	SG_vhash* pvhRequestStatus = NULL;
	const SG_pathname* pStagingPathname;

	SG_ERR_CHECK(  SG_staging__get_pathname(pCtx, pMe->pStaging, &pStagingPathname)  );

	SG_ERR_CHECK(  SG_vhash__has(pCtx, *ppvhStagingStatus, SG_SYNC_STATUS_KEY__DAGS, &disconnected)  );
	while (disconnected)
	{

#if TRACE_PULL
		SG_ERR_CHECK(  SG_vhash_debug__dump_to_console__named(pCtx, *ppvhStagingStatus, "pull staging status")  );
#endif
		// There's at least one dag with connection problems.  
		
		// Convert the staging status vhash into a fragball request vhash.
		pvhFragballRequest = *ppvhStagingStatus;
		*ppvhStagingStatus = NULL;
		SG_ERR_CHECK(  SG_vhash__add__int64(pCtx, pvhFragballRequest, SG_SYNC_STATUS_KEY__GENERATIONS, GENERATIONS_PER_ROUNDTRIP)  );

		SG_ERR_CHECK(  SG_client__pull_request_fragball(pCtx, pClient, pvhFragballRequest, pStagingPathname, &pszFragballName, &pvhRequestStatus)  );

		/* Ian TODO: inspect pvhRequestStatus */

		SG_VHASH_NULLFREE(pCtx, pvhRequestStatus);
		SG_VHASH_NULLFREE(pCtx, pvhFragballRequest);

		SG_ERR_CHECK(  SG_staging__slurp_fragball(pCtx, pMe->pStaging, (const char*)pszFragballName)  );
		SG_NULLFREE(pCtx, pszFragballName);

		SG_ERR_CHECK(  SG_staging__check_status(pCtx, pMe->pStaging, SG_TRUE, SG_FALSE, SG_FALSE, SG_FALSE, SG_FALSE, ppvhStagingStatus)  );

		SG_ERR_CHECK(  SG_vhash__has(pCtx, *ppvhStagingStatus, SG_SYNC_STATUS_KEY__DAGS, &disconnected)  );

#if TRACE_PULL
		SG_ERR_CHECK(  SG_vhash_debug__dump_to_console__named(pCtx, *ppvhStagingStatus, "pull staging status")  );
#endif
	}

	SG_ERR_CHECK_RETURN(  SG_context__msg__emit(pCtx, "done")  );

	/* fall through */
fail:
	SG_VHASH_NULLFREE(pCtx, *ppvhStagingStatus);
	SG_VHASH_NULLFREE(pCtx, pvhFragballRequest);
	SG_NULLFREE(pCtx, pszFragballName);
	SG_VHASH_NULLFREE(pCtx, pvhRequestStatus);
	SG_ERR_IGNORE(  SG_context__msg__emit(pCtx, "\n")  );
}
Ejemplo n.º 2
0
static void _add_blobs_until_done(SG_context* pCtx, 
								  SG_staging* pStaging, 
								  SG_client* pClient) 
{
	SG_bool need_blobs = SG_FALSE;
	SG_vhash* pvhFragballRequest = NULL;
	char* pszFragballName = NULL;
	SG_vhash* pvhRequestStatus = NULL;
	const SG_pathname* pStagingPathname = NULL;
	SG_vhash* pvhStagingStatus = NULL;

	SG_ERR_CHECK_RETURN(  SG_context__msg__emit(pCtx, "Retrieving blobs...")  );

	SG_ERR_CHECK(  SG_staging__check_status(pCtx, pStaging, SG_FALSE, SG_FALSE, SG_FALSE, SG_TRUE, SG_TRUE, &pvhStagingStatus)  );

	SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhStagingStatus, SG_SYNC_STATUS_KEY__BLOBS, &need_blobs)  );
	
	if (need_blobs)
		SG_ERR_CHECK(  SG_staging__get_pathname(pCtx, pStaging, &pStagingPathname)  );

	while (need_blobs)
	{
		pvhFragballRequest = pvhStagingStatus;
		pvhStagingStatus = NULL;

		SG_ERR_CHECK(  SG_client__pull_request_fragball(pCtx, pClient, pvhFragballRequest, pStagingPathname, &pszFragballName, &pvhRequestStatus)  );

		/* Ian TODO: inspect pvhRequestStatus */

		SG_VHASH_NULLFREE(pCtx, pvhRequestStatus);
		SG_VHASH_NULLFREE(pCtx, pvhFragballRequest);

		SG_ERR_CHECK(  SG_staging__slurp_fragball(pCtx, pStaging, (const char*)pszFragballName)  );
		SG_NULLFREE(pCtx, pszFragballName);

		SG_ERR_CHECK(  SG_staging__check_status(pCtx, pStaging, SG_FALSE, SG_FALSE, SG_FALSE, SG_TRUE, SG_TRUE, &pvhStagingStatus)  );

#if TRACE_PULL
		SG_ERR_CHECK(  SG_vhash_debug__dump_to_console__named(pCtx, pvhStagingStatus, "pull staging status")  );
#endif

		SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhStagingStatus, SG_SYNC_STATUS_KEY__BLOBS, &need_blobs)  );
	}

	SG_ERR_CHECK_RETURN(  SG_context__msg__emit(pCtx, "done")  );

	/* fall through */
fail:
	SG_VHASH_NULLFREE(pCtx, pvhStagingStatus);
	SG_VHASH_NULLFREE(pCtx, pvhFragballRequest);
	SG_NULLFREE(pCtx, pszFragballName);
	SG_VHASH_NULLFREE(pCtx, pvhRequestStatus);
	SG_ERR_IGNORE(  SG_context__msg__emit(pCtx, "\n")  );
}
static void _dump_branch_name(
	SG_context* pCtx,
	SG_console_stream cs,
	const char* pszRefHid, 
	SG_bool bShowOnlyOpenBranchNames,
	const SG_vhash* pvhRefBranchValues,
	const SG_vhash* pvhRefClosedBranches)
{
	SG_vhash* pvhRefBranchNames = NULL;

	if (pvhRefBranchValues)
	{
		SG_bool b_has = SG_FALSE;

		SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRefBranchValues, pszRefHid, &b_has)  );
		if (b_has)
		{
			SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhRefBranchValues, pszRefHid, &pvhRefBranchNames)  );
		}
	}

	if (pvhRefBranchNames)
	{
		SG_uint32 count_branch_names = 0;
		SG_uint32 i;

		SG_ERR_CHECK(  SG_vhash__count(pCtx, pvhRefBranchNames, &count_branch_names)  );
		for (i=0; i<count_branch_names; i++)
		{
			const char* psz_branch_name = NULL;
			SG_bool bClosed = SG_FALSE;

			SG_ERR_CHECK(  SG_vhash__get_nth_pair(pCtx, pvhRefBranchNames, i, &psz_branch_name, NULL)  );

			if (pvhRefClosedBranches)
				SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRefClosedBranches, psz_branch_name, &bClosed)  );

			if ( !bShowOnlyOpenBranchNames || (bShowOnlyOpenBranchNames && !bClosed) )
			{
				SG_ERR_CHECK(  SG_console(pCtx, cs, "\t%8s:  %s%s\n", "branch", 
					psz_branch_name, bClosed ? " (closed)" : "")  );
			}
		}
	}

fail:
	;
}
static void _sg_jscontextpool__force_config_bool(SG_context * pCtx, SG_vhash * pConfig, const char * szSettingName, SG_bool * pValue)
{
	SG_bool has = SG_FALSE;
	SG_bool value = SG_FALSE;
	
	SG_ASSERT(pCtx!=NULL);
	SG_ASSERT(pConfig!=NULL);
	SG_ASSERT(szSettingName!=NULL);
	
	SG_ERR_CHECK(  SG_vhash__has(pCtx, pConfig, szSettingName, &has)  );
	if(has)
	{
		SG_uint16 type_of_value = 0;
		SG_ERR_CHECK(  SG_vhash__typeof(pCtx, pConfig, szSettingName, &type_of_value)  );
		if(type_of_value==SG_VARIANT_TYPE_SZ)
		{
			const char *szValue = NULL;
			SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pConfig, szSettingName, &szValue)  );
			value = strcmp(szValue, "true")==0;
		}
		else if(type_of_value==SG_VARIANT_TYPE_BOOL)
		{
			SG_ERR_CHECK(  SG_vhash__get__bool(pCtx, pConfig, szSettingName, &value)  );
		}
	}
	SG_ERR_CHECK(  SG_vhash__update__bool(pCtx, pConfig, szSettingName, value)  );
	if(pValue!=NULL)
	{
		*pValue = value;
	}
	
	return;
fail:
	;
}
Ejemplo n.º 5
0
void SG_dbrecord__has_value(SG_context* pCtx, const SG_dbrecord* prec, const char* putf8Name, SG_bool* pb)
{
	SG_bool b = SG_FALSE;
	SG_NULLARGCHECK_RETURN(prec);
	SG_NULLARGCHECK_RETURN(putf8Name);
	SG_NULLARGCHECK_RETURN(pb);
	SG_ERR_CHECK_RETURN(  SG_vhash__has(pCtx, prec->pvh, putf8Name, &b)  );
	*pb = b;
}
Ejemplo n.º 6
0
void SG_pull__all__list_incoming(
	SG_context* pCtx,
	const char* pszPullIntoRepoDescriptorName,
	SG_client* pClient,
	SG_varray** ppvaInfo)
{
	sg_pull_instance_data* pMe = NULL;
	char* pszFragballName = NULL;
	SG_vhash* pvhStatus = NULL; // Used for both fragball request status returned by SG_client
	// and staging status returned by SG_staging.
	const SG_pathname* pStagingPathname;

	SG_NULLARGCHECK_RETURN(pszPullIntoRepoDescriptorName);
	SG_NULLARGCHECK_RETURN(pClient);

	SG_ERR_CHECK(  _pull_init(pCtx, pClient, pszPullIntoRepoDescriptorName, &pMe)  );
	SG_ERR_CHECK(  SG_staging__get_pathname(pCtx, pMe->pStaging, &pStagingPathname)  );

	/* Request a fragball containing leaves of every dag */
	SG_ERR_CHECK_RETURN(  SG_context__msg__emit(pCtx, "Retrieving dagnodes...")  );
	SG_ERR_CHECK(  SG_client__pull_request_fragball(pCtx, pClient, NULL, pStagingPathname, &pszFragballName, &pvhStatus)  );

	/* Ian TODO: inspect pvhStatus */

	SG_ERR_CHECK(  SG_VHASH_NULLFREE(pCtx, pvhStatus)  );

	SG_ERR_CHECK(  SG_staging__slurp_fragball(pCtx, pMe->pStaging, pszFragballName)  );
	SG_ERR_CHECK(  SG_staging__check_status(pCtx, pMe->pStaging, SG_TRUE, SG_FALSE, SG_FALSE, SG_FALSE, SG_FALSE, &pvhStatus)  );

	/* Check the status and use it to request more dagnodes until the dags connect */
	SG_ERR_CHECK(  _add_dagnodes_until_connected(pCtx, &pvhStatus, pMe, pClient)  );
	SG_ERR_CHECK(  SG_VHASH_NULLFREE(pCtx, pvhStatus)  );

	SG_ERR_CHECK(  SG_staging__check_status(pCtx, pMe->pStaging, SG_FALSE, SG_FALSE, SG_TRUE, SG_FALSE, SG_FALSE, &pvhStatus)  );
	{
		SG_bool b = SG_FALSE;
		SG_vhash* pvhRequest = NULL;
		SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhStatus, SG_SYNC_STATUS_KEY__NEW_NODES, &b)  );
		if (b)
		{
			/* There are incoming nodes.  Get their info. */
			SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhStatus, SG_SYNC_STATUS_KEY__NEW_NODES, &pvhRequest)  );
			SG_ERR_CHECK(  SG_client__get_dagnode_info(pCtx, pClient, pvhRequest, ppvaInfo)  );
		}
	}

	/* fall through */
fail:
	_NULLFREE_INSTANCE_DATA(pCtx, pMe);
	SG_VHASH_NULLFREE(pCtx, pvhStatus);
	SG_NULLFREE(pCtx, pszFragballName);
}
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_cmd_util__dump_log(
	SG_context * pCtx, 
	SG_console_stream cs,
	SG_repo* pRepo, 
	const char* psz_hid_cs, 
	SG_vhash* pvhCleanPileOfBranches, 
	SG_bool bShowOnlyOpenBranchNames,
	SG_bool bShowFullComments)
{
	SG_history_result* pHistResult = NULL;
	SG_stringarray * psaHids = NULL;
	
	SG_STRINGARRAY__ALLOC(pCtx, &psaHids, 1);
	SG_ERR_CHECK(  SG_stringarray__add(pCtx, psaHids, psz_hid_cs)  );
	SG_history__get_revision_details(pCtx, pRepo, psaHids, NULL, &pHistResult);

    if (SG_context__err_equals(pCtx, SG_ERR_NOT_FOUND))
    {
		/* There's a branch that references a changeset that doesn't exist. Show what we can. */

		SG_vhash* pvhRefClosedBranches = NULL;
		SG_vhash* pvhRefBranchValues = NULL;

		SG_context__err_reset(pCtx);

		if (pvhCleanPileOfBranches)
		{
			SG_bool bHas = SG_FALSE;
			SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhCleanPileOfBranches, "closed", &bHas)  );
			if (bHas)
				SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhCleanPileOfBranches, "closed", &pvhRefClosedBranches)  );

			SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhCleanPileOfBranches, "values", &pvhRefBranchValues)  );
		}

		SG_ERR_CHECK(  SG_console(pCtx, cs, "\n\t%8s:  %s\n", "revision", psz_hid_cs)  );
		SG_ERR_CHECK(  _dump_branch_name(pCtx, cs, psz_hid_cs, bShowOnlyOpenBranchNames, 
			pvhRefBranchValues, pvhRefClosedBranches)  );
		SG_ERR_CHECK(  SG_console(pCtx, cs, "\t%8s   %s\n", "", "(not present in repository)")  );
    }
    else
    {
		SG_ERR_CHECK_CURRENT;
        SG_ERR_CHECK(  SG_cmd_util__dump_history_results(pCtx, cs, pHistResult, pvhCleanPileOfBranches, bShowOnlyOpenBranchNames, bShowFullComments, SG_FALSE)  );
    }

fail:
	SG_HISTORY_RESULT_NULLFREE(pCtx, pHistResult);
	SG_STRINGARRAY_NULLFREE(pCtx, psaHids);
}
void SG_curl__throw_on_non200(SG_context* pCtx, SG_curl* pCurl)
{
	SG_int32 httpResponseCode = 0;
	SG_vhash* pvhErr = NULL;
	_sg_curl* p = (_sg_curl*)pCurl;

	SG_NULLARGCHECK_RETURN(pCurl);

	SG_ERR_CHECK(  SG_curl__getinfo__int32(pCtx, pCurl, CURLINFO_RESPONSE_CODE, &httpResponseCode)  );
	if (httpResponseCode != 200)
	{
		if ((httpResponseCode == 500 || httpResponseCode == 410) && p->pstrErr)
		{
			SG_bool bHas = SG_FALSE;
			const char* szMsg = NULL;
			
			SG_VHASH__ALLOC__FROM_JSON__SZ(pCtx, &pvhErr, SG_string__sz(p->pstrErr));
			if (SG_context__err_equals(pCtx, SG_ERR_JSONPARSER_SYNTAX))
			{
				// The server didn't return a JSON-formatted response.
				if (httpResponseCode == 500)
					SG_ERR_RESET_THROW2(SG_ERR_EXTENDED_HTTP_500, (pCtx, "%s", SG_string__sz(p->pstrErr)));
				else
					SG_ERR_THROW2(SG_ERR_SERVER_HTTP_ERROR, (pCtx, "%d", httpResponseCode));
			}
			SG_ERR_CHECK_CURRENT;

			SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhErr, "msg", &bHas)  );
			if (bHas)
				SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvhErr, "msg", &szMsg)  );
			if (szMsg)
				SG_ERR_THROW2(SG_ERR_EXTENDED_HTTP_500, (pCtx, "%s", szMsg));
			else
				SG_ERR_THROW2(SG_ERR_EXTENDED_HTTP_500, (pCtx, "%s", SG_string__sz(p->pstrErr)));
		}
		else if (httpResponseCode == 401)
		{
			SG_ERR_THROW(SG_ERR_AUTHORIZATION_REQUIRED);
		}
		else
			SG_ERR_THROW2(SG_ERR_SERVER_HTTP_ERROR, (pCtx, "%d", httpResponseCode));
	}

	/* common cleanup */
fail:
	SG_VHASH_NULLFREE(pCtx, pvhErr);
}
/**
 * Our caller is trying to create new repo and create a WD
 * mapped to it.  The destination directory may or may not
 * have already existed on disk before we started.  If we are
 * building upon an existing directory, verify that it doesn't
 * contain any submodules because we don't yet support them.
 *
 */
static void _check_for_nested_drawer(SG_context * pCtx,
                                     SG_wc_tx * pWcTx)
{
    SG_varray * pvaStatus = NULL;
    SG_string * pString_MyDrawerRepoPath = NULL;
    SG_string * pString_MatchedRepoPath = NULL;
    const char * psz_MyDrawerName = NULL;
    const char * psz_MyDrawerRepoPath = NULL;
    SG_uint32 k, nrItems;

    if (pWcTx->bWeCreated_WD || pWcTx->bWeCreated_WD_Contents)
        return;

    SG_ERR_CHECK(  SG_wc_tx__status(pCtx, pWcTx, NULL, SG_UINT32_MAX,
                                    SG_FALSE, // bListUnchanged
                                    SG_TRUE,  // bNoIgnores
                                    SG_TRUE,  // bNoTSC,
                                    SG_FALSE, // bListSparse
                                    SG_TRUE,  // bListReserved
                                    SG_TRUE,  // bNoSort,
                                    &pvaStatus,
                                    NULL)  );
    if (!pvaStatus)
        return;

    // TODO 2012/11/13 For now I'm just going to see if there is a
    // TODO            .sgdrawer somewhere within the directory tree.
    // TODO            In theory, we could have ADD/ADDREMOVE just
    // TODO            look for them and refuse to add its parent
    // TODO            directory, but I don't to even support that
    // TODO            until we've properly dealt with submodules.
    // TODO
    // TODO            So for now, if there is a WD deeply nested within
    // TODO            this directory, we just complain.  This is mainly
    // TODO            to prevent accidents.  (Because they can still
    // TODO            manually move a sub-WD somehere deep into this
    // TODO            directory at some point in the future.)

    SG_ERR_CHECK(  SG_workingdir__get_drawer_directory_name(pCtx, &psz_MyDrawerName)  );
    SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pString_MyDrawerRepoPath)  );
    SG_ERR_CHECK(  SG_string__sprintf(pCtx, pString_MyDrawerRepoPath, "@/%s", psz_MyDrawerName)  );
    SG_ERR_CHECK(  SG_repopath__ensure_final_slash(pCtx, pString_MyDrawerRepoPath)  );
    psz_MyDrawerRepoPath = SG_string__sz(pString_MyDrawerRepoPath);

    SG_ERR_CHECK(  SG_varray__count(pCtx, pvaStatus, &nrItems)  );
    for (k=0; k<nrItems; k++)
    {
        SG_vhash * pvhItem;
        SG_vhash * pvhItemStatus;
        SG_bool bIsReserved;
        SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pvaStatus, k, &pvhItem)  );
        SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhItem, "status", &pvhItemStatus)  );
        SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhItemStatus, "isReserved", &bIsReserved)  );
        if (bIsReserved)
        {
            // Don't freak out over the .sgdrawer that we just created in the root.
            const char * pszRepoPath;
            SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvhItem, "path", &pszRepoPath)  );
            if (strcmp(pszRepoPath, psz_MyDrawerRepoPath) != 0)
            {
                SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pString_MatchedRepoPath, pszRepoPath)  );
                SG_ERR_CHECK(  SG_repopath__remove_last(pCtx, pString_MatchedRepoPath)  );

                SG_ERR_THROW2(  SG_ERR_ENTRY_ALREADY_UNDER_VERSION_CONTROL,
                                (pCtx, "The directory '%s' contains a working copy and submodules are not yet supported.",
                                 SG_string__sz(pString_MatchedRepoPath))  );
            }
        }
    }

fail:
    SG_STRING_NULLFREE(pCtx, pString_MatchedRepoPath);
    SG_STRING_NULLFREE(pCtx, pString_MyDrawerRepoPath);
    SG_VARRAY_NULLFREE(pCtx, pvaStatus);
}
Ejemplo n.º 11
0
/**
 * Add to the fragball request vhash (see SG_server_prototypes.h for format).
 */
void SG_pull__add(
	SG_context* pCtx,
	SG_pull* pPull,
	SG_uint32 iDagnum,
	SG_rbtree* prbDagnodes,
	SG_rbtree* prbTags,
	SG_rbtree* prbDagnodePrefixes)
{
	_sg_pull* pMyPull = NULL;
	char bufDagnum[SG_DAGNUM__BUF_MAX__DEC];
	SG_bool found = SG_FALSE;
	SG_vhash* pvhDags = NULL; // Needs to be freed
	SG_vhash* pvhDagsRef = NULL; // Does not need to be freed, owned by parent vhash
	SG_vhash* pvhDagnum = NULL; // Needs to be freed
	SG_vhash* pvhDagnumRef = NULL; // Does not need to be freed, owned by parent vhash
	SG_rbtree_iterator* pit = NULL;

	SG_NULLARGCHECK_RETURN(pPull);
	SG_ARGCHECK_RETURN(iDagnum, iDagnum);
	pMyPull = (_sg_pull*)pPull;

	if (!pMyPull->pvhFragballRequest)
		SG_ERR_CHECK(  SG_VHASH__ALLOC(pCtx, &pMyPull->pvhFragballRequest)  );

	SG_ERR_CHECK(  SG_dagnum__to_sz__decimal(pCtx, iDagnum, bufDagnum, sizeof(bufDagnum))  );

	/* Get dagnum vhash, adding it if necessary. */
	SG_ERR_CHECK(  SG_vhash__has(pCtx, pMyPull->pvhFragballRequest, SG_SYNC_STATUS_KEY__DAGS, &found)  );
	if (found)
		SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pMyPull->pvhFragballRequest, SG_SYNC_STATUS_KEY__DAGS, &pvhDagsRef)  );
	else
	{
		SG_ERR_CHECK(  SG_VHASH__ALLOC(pCtx, &pvhDags)  );
		pvhDagsRef = pvhDags;
		SG_ERR_CHECK(  SG_vhash__add__vhash(pCtx, pMyPull->pvhFragballRequest, SG_SYNC_STATUS_KEY__DAGS, &pvhDags)  );
	}
	
	SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhDagsRef, bufDagnum, &found)  );
	if (found)
		SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhDagsRef, bufDagnum, &pvhDagnumRef)  );
	
	if (!pvhDagnumRef)
	{
		SG_ERR_CHECK(  SG_VHASH__ALLOC(pCtx, &pvhDagnum)  );
		pvhDagnumRef = pvhDagnum;
		SG_ERR_CHECK(  SG_vhash__add__vhash(pCtx, pvhDagsRef, bufDagnum, &pvhDagnum)  );
	}

	/* If dagnodes were provided, add them to the dagnum vhash */
	if (prbDagnodes)
	{
		const char* pszHid;
		SG_ERR_CHECK(  SG_rbtree__iterator__first(pCtx, &pit, prbDagnodes, &found, &pszHid, NULL)  );
		while (found)
		{
			SG_ERR_CHECK(  SG_vhash__update__null(pCtx, pvhDagnumRef, pszHid)  );
			SG_ERR_CHECK(  SG_rbtree__iterator__next(pCtx, pit, &found, &pszHid, NULL)  );
		}
		SG_RBTREE_ITERATOR_NULLFREE(pCtx, pit);
	}	
	/* If tags were provided, add them to the dagnum vhash */
	if (prbTags)
	{
		const char* pszTag;
		SG_ERR_CHECK(  SG_rbtree__iterator__first(pCtx, &pit, prbTags, &found, &pszTag, NULL)  );
		while (found)
		{
			SG_ERR_CHECK(  SG_vhash__update__string__sz(pCtx, pvhDagnumRef, pszTag, SG_SYNC_REQUEST_VALUE_TAG)  );
			SG_ERR_CHECK(  SG_rbtree__iterator__next(pCtx, pit, &found, &pszTag, NULL)  );
		}
		SG_RBTREE_ITERATOR_NULLFREE(pCtx, pit);
	}	
	/* If dagnode hid prefixes were provided, add them to the dagnum vhash */
	if (prbDagnodePrefixes)
	{
		const char* pszHidPrefix;
		SG_ERR_CHECK(  SG_rbtree__iterator__first(pCtx, &pit, prbDagnodePrefixes, &found, &pszHidPrefix, NULL)  );
		while (found)
		{
			SG_ERR_CHECK(  SG_vhash__update__string__sz(pCtx, pvhDagnumRef, pszHidPrefix, SG_SYNC_REQUEST_VALUE_HID_PREFIX)  );
			SG_ERR_CHECK(  SG_rbtree__iterator__next(pCtx, pit, &found, &pszHidPrefix, NULL)  );
		}
		SG_RBTREE_ITERATOR_NULLFREE(pCtx, pit);
	}	

	return;

fail:
	SG_VHASH_NULLFREE(pCtx, pvhDagnum);
	SG_VHASH_NULLFREE(pCtx, pvhDags);
	SG_RBTREE_ITERATOR_NULLFREE(pCtx, pit);
}
// 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);
}
/**
 * Do diff of an individual item.
 * When WC-based, we have a "DiffStep" vhash.
 * When historical, we have an item from a pvaStatus.
 *
 */
static void _do_diff1(SG_context * pCtx,
                      SG_bool bWC,
                      const SG_option_state * pOptSt,
                      const SG_vhash * pvhItem,
                      SG_uint32 * piResult)
{
    SG_string * pStringGidRepoPath = NULL;
    SG_vhash * pvhResultCodes = NULL;
    SG_stringarray * psa1 = NULL;
    const char * pszGid;
    SG_int64 i64Result = 0;
    SG_string * pStringErr = NULL;

    SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvhItem, "gid", &pszGid)  );

    if (bWC)
    {
        SG_pathname * pPathWc = NULL;
        SG_bool bHasTool = SG_FALSE;

        // With the __diff__setup() and __diff__run() changes, we have already
        // examined the items during the __setup() step and recorded a tool for
        // the *FILE* that have changed content.  So if "tool" isn't set in the
        // DiffStep/Item, we don't need to diff it -- it could be a structural
        // change, a non-file, a found item, etc.
        //
        // we do not use SG_wc__diff__throw() because we already have the diff info
        // and we want to control the result-code processing below.

        SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhItem, "tool", &bHasTool)  );
        if (bHasTool)
            SG_ERR_CHECK(  SG_wc__diff__run(pCtx, pPathWc, pvhItem, &pvhResultCodes)  );
    }
    else
    {
        SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pStringGidRepoPath)  );
        SG_ERR_CHECK(  SG_string__sprintf(pCtx, pStringGidRepoPath, "@%s", pszGid)  );

        SG_ERR_CHECK(  SG_STRINGARRAY__ALLOC(pCtx, &psa1, 1)  );
        SG_ERR_CHECK(  SG_stringarray__add(pCtx, psa1, SG_string__sz(pStringGidRepoPath))  );
        // we do not use the __throw() version of this routine so we can control
        // result-code processing below.
        SG_ERR_CHECK(  SG_vv2__diff_to_stream(pCtx, pOptSt->psz_repo, pOptSt->pRevSpec,
                                              psa1, 0,
                                              SG_FALSE,		// bNoSort
                                              SG_TRUE,		// bInteractive,
                                              pOptSt->psz_tool,
                                              &pvhResultCodes)  );
    }

    if (pvhResultCodes)
    {
        SG_vhash * pvhResult;				// we do not own this
        SG_ERR_CHECK(  SG_vhash__check__vhash(pCtx, pvhResultCodes, pszGid, &pvhResult)  );
        if (pvhResult)
        {
            const char * pszTool;

            SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvhResult, "tool", &pszTool)  );
            SG_ERR_CHECK(  SG_vhash__get__int64(pCtx, pvhResult, "result", &i64Result)  );

            SG_difftool__check_result_code__throw(pCtx, i64Result, pszTool);
            if (SG_context__has_err(pCtx))
            {
                SG_context__err_to_string(pCtx, SG_FALSE, &pStringErr);
                SG_context__err_reset(pCtx);
                SG_ERR_CHECK(  SG_console__raw(pCtx, SG_CS_STDERR, SG_string__sz(pStringErr))  );

                // eat the tool error. the result code is set.
            }
        }
    }

    if (piResult)
        *piResult = (SG_uint32)i64Result;

fail:
    SG_STRING_NULLFREE(pCtx, pStringGidRepoPath);
    SG_VHASH_NULLFREE(pCtx, pvhResultCodes);
    SG_STRINGARRAY_NULLFREE(pCtx, psa1);
    SG_STRING_NULLFREE(pCtx, pStringErr);
}
void sg_vv2__history__working_folder(
	SG_context * pCtx,
	const SG_stringarray * psaInputs,
	const SG_rev_spec* pRevSpec,
	const SG_rev_spec* pRevSpec_single_revisions,
	const char* pszUser,
	const char* pszStamp,
	SG_bool bDetectCurrentBranch,
	SG_uint32 nResultLimit,
	SG_bool bHideObjectMerges,
	SG_int64 nFromDate,
	SG_int64 nToDate,
	SG_bool bListAll,
	SG_bool* pbHasResult, 
	SG_vhash** ppvhBranchPile,
	SG_history_result ** ppResult,
	SG_history_token ** ppHistoryToken)
{
	SG_repo * pRepo = NULL;
	SG_stringarray * pStringArrayGIDs = NULL;
	SG_stringarray * pStringArrayChangesets = NULL;
	SG_stringarray * pStringArrayChangesetsMissing = NULL;
	SG_stringarray * pStringArrayChangesets_single_revisions = NULL;
	SG_bool bRecommendDagWalk = SG_FALSE;
	SG_bool bLeaves = SG_FALSE;
	const char * pszBranchName = NULL;	// we do not own this
	SG_vhash* pvhBranchPile = NULL;
	SG_varray* pvaParents = NULL;	// we do not own this
	SG_bool bMyBranchWalkRecommendation = SG_FALSE;
	
	SG_rev_spec* pRevSpec_Allocated = NULL;
	SG_wc_tx * pWcTx = NULL;
	SG_vhash * pvhInfo = NULL;
	SG_uint32 count_args = 0;
	SG_uint32 countRevsSpecified = 0;

	if (psaInputs)
		SG_ERR_CHECK(  SG_stringarray__count(pCtx, psaInputs, &count_args)  );

	// Use the WD to try to get the initial info.
	// I'm going to deviate from the model and use
	// a read-only TX here so that I can get a bunch
	// of fields that we need later.
	SG_ERR_CHECK(  SG_WC_TX__ALLOC__BEGIN(pCtx, &pWcTx, NULL, SG_TRUE)  );

	if (count_args > 0)
		SG_ERR_CHECK(  SG_wc_tx__get_item_gid__stringarray(pCtx, pWcTx, psaInputs, &pStringArrayGIDs)  );

	SG_ERR_CHECK(  SG_wc_tx__get_wc_info(pCtx, pWcTx, &pvhInfo)  );
	SG_ERR_CHECK(  SG_wc_tx__get_repo_and_wd_top(pCtx, pWcTx, &pRepo, NULL)  );

	/* If no revisions were specified, and the caller wants us to use the current branch,
	 * create a revision spec with the current branch. */

	if (pRevSpec)
	{
		SG_ERR_CHECK(  SG_REV_SPEC__ALLOC__COPY(pCtx, pRevSpec, &pRevSpec_Allocated)  );
		SG_ERR_CHECK(  SG_rev_spec__count(pCtx, pRevSpec_Allocated, &countRevsSpecified)  );
	}
	else
	{
		SG_ERR_CHECK(  SG_REV_SPEC__ALLOC(pCtx, &pRevSpec_Allocated)  );
	}

	if (pRevSpec_single_revisions != NULL)
	{
		SG_uint32 countRevsSpecified_singles = 0;
		SG_ERR_CHECK(  SG_rev_spec__count(pCtx, pRevSpec_single_revisions, &countRevsSpecified_singles)  );
		countRevsSpecified += countRevsSpecified_singles;
	}

	if (bDetectCurrentBranch && countRevsSpecified == 0) 
	{
		SG_ERR_CHECK(  SG_vhash__check__sz(pCtx, pvhInfo, "branch", &pszBranchName)  );
		if (pszBranchName)
		{
			/* The working folder is attached to a branch. Does it exist? */
			SG_bool bHasBranches = SG_FALSE;
			SG_bool bBranchExists = SG_FALSE;

			SG_ERR_CHECK(  SG_vc_branches__cleanup(pCtx, pRepo, &pvhBranchPile)  );
			SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhBranchPile, "branches", &bHasBranches)  );
			if (bHasBranches)
			{
				SG_vhash* pvhRefBranches;
				SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhBranchPile, "branches", &pvhRefBranches)  );
				SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRefBranches, pszBranchName, &bBranchExists)  );
			}
				
			if (bBranchExists)
			{
				SG_uint32 numParents, i;
				const char* pszRefParent;

				/* If that branch exists, just add to our rev spec. */
				SG_ERR_CHECK(  SG_rev_spec__add_branch(pCtx, pRevSpec_Allocated, pszBranchName)  );

				/* Plus, if the working folder's parents are not in the branch (yet), add them as well
				 * (they'll be in it after the user commits something...). */
				SG_ERR_CHECK(  SG_vhash__get__varray(pCtx, pvhInfo, "parents", &pvaParents)  );
				SG_ERR_CHECK(  SG_varray__count(pCtx, pvaParents, &numParents)  );
				for (i = 0; i < numParents; i++)
				{
					SG_bool already_in_rev_spec = SG_FALSE;
					SG_ERR_CHECK(  SG_varray__get__sz(pCtx, pvaParents, i, &pszRefParent)  );
					SG_ERR_CHECK(  SG_rev_spec__contains(pCtx, pRepo, pRevSpec_Allocated, pszRefParent,
														 &already_in_rev_spec)  );
					if(!already_in_rev_spec)
						SG_ERR_CHECK(  SG_rev_spec__add_rev(pCtx, pRevSpec_Allocated, pszRefParent)  );
				}
			}
			else
			{
				/* If the branch doesn't exist, add the working folder's baseline(s) to the rev spec
				 * and force a dag walk. */
				SG_uint32 numParents, i;
				const char* pszRefParent;

				SG_ERR_CHECK(  SG_vhash__get__varray(pCtx, pvhInfo, "parents", &pvaParents)  );
				SG_ERR_CHECK(  SG_varray__count(pCtx, pvaParents, &numParents)  );
				for (i = 0; i < numParents; i++)
				{
					SG_ERR_CHECK(  SG_varray__get__sz(pCtx, pvaParents, i, &pszRefParent)  );
					SG_ERR_CHECK(  SG_rev_spec__add_rev(pCtx, pRevSpec_Allocated, pszRefParent)  );
				}
				bMyBranchWalkRecommendation = SG_TRUE;
			}

		}
	}

	// Determine the starting changeset IDs.  strBranch and bLeaves control this.
	// We do this step here, so that repo paths can be looked up before we call into history__core.
	SG_ERR_CHECK( sg_vv2__history__get_starting_changesets(pCtx, pRepo, pRevSpec_Allocated,
														   &pStringArrayChangesets,
														   &pStringArrayChangesetsMissing,
														   &bRecommendDagWalk,
														   &bLeaves) );
	if (pStringArrayChangesetsMissing)
	{
		// See K2177, K1322, W0836, W8132.  We requested specific starting
		// points and ran into some csets that were referenced (by --tag
		// or --branch) that are not present in the local repo.  Try to
		// silently ignore them.
		SG_uint32 nrFound = 0;
		SG_ERR_CHECK(  SG_stringarray__count(pCtx, pStringArrayChangesets, &nrFound)  );
		if (nrFound > 0)
		{
			// Yes there were missing csets, but we still found some
			// of the referenced ones.  Just ignore the missing ones.
			// This should behave just like we had the older tag/branch
			// dag prior to the push -r on the vc dag.
		}
		else
		{
			const char * psz_0;
			// TODO 2012/10/19 Do we want a different message if the number of missing is > 1 ?
			SG_ERR_CHECK(  SG_stringarray__get_nth(pCtx, pStringArrayChangesetsMissing, 0, &psz_0)  );
			SG_ERR_THROW2(  SG_ERR_CHANGESET_BLOB_NOT_FOUND,
							(pCtx, "%s", psz_0)  );
		}
	}

	bRecommendDagWalk = bRecommendDagWalk || bMyBranchWalkRecommendation;
	
	//This hack is here to  detect when we're being asked for the parent of a certain
	//object from the sg_parents code.  parents always wants the dag walk.
	//The better solution would be to allow users to pass in a flag about their dagwalk
	//preferences
	if (count_args == 1 && nResultLimit == 1)
		bRecommendDagWalk = SG_TRUE;

	if (bListAll)
	{
		// See W8493.  If they gave us a --list-all along with a --rev or --tag, they
		// want to force us to show the full history rather than just the info for the
		// named cset.
		bRecommendDagWalk = SG_TRUE;
	}

	if (pRevSpec_single_revisions)
	{
		// We DO NOT pass a psaMissingHids here because we want
		// it to throw if the user names a missing cset.
		SG_ERR_CHECK(  SG_rev_spec__get_all__repo__dedup(pCtx, pRepo, pRevSpec_single_revisions, SG_TRUE,
														 &pStringArrayChangesets_single_revisions, NULL)  );
	}

	// TODO 2012/07/03 The deviates from the model.  This call directly returns the
	// TODO            allocated data into the caller's pointers.  If anything fails
	// TODO            (such as the call to get the branches below), we'll probably
	// TODO            leak the result and token.

	SG_ERR_CHECK( SG_history__run(pCtx, pRepo, pStringArrayGIDs,
					pStringArrayChangesets, pStringArrayChangesets_single_revisions,
					pszUser, pszStamp, nResultLimit, bLeaves, bHideObjectMerges,
					nFromDate, nToDate, bRecommendDagWalk, SG_FALSE, pbHasResult, ppResult, ppHistoryToken) );

	/* This is kind of a hack. History callers often need branch data to format ouput.
	 * But we open the repo down here. I didn't want to open/close it again. And there's logic
	 * in here about which repo to open. So instead, we do this. */
	if (ppvhBranchPile)
	{
		if (pvhBranchPile)
		{
			*ppvhBranchPile = pvhBranchPile;
			pvhBranchPile = NULL;
		}
		else
			SG_ERR_CHECK(  SG_vc_branches__cleanup(pCtx, pRepo, ppvhBranchPile)  );
	}

fail:
	SG_ERR_IGNORE(  SG_wc_tx__cancel(pCtx, pWcTx)  );
	SG_WC_TX__NULLFREE(pCtx, pWcTx);
	SG_REV_SPEC_NULLFREE(pCtx, pRevSpec_Allocated);
	SG_STRINGARRAY_NULLFREE(pCtx, pStringArrayChangesets);
	SG_STRINGARRAY_NULLFREE(pCtx, pStringArrayChangesetsMissing);
	SG_STRINGARRAY_NULLFREE(pCtx, pStringArrayChangesets_single_revisions);
	SG_STRINGARRAY_NULLFREE(pCtx, pStringArrayGIDs);
	SG_VHASH_NULLFREE(pCtx, pvhBranchPile);
	SG_VHASH_NULLFREE(pCtx, pvhInfo);
	SG_REPO_NULLFREE(pCtx, pRepo);
}
void SG_sync_remote__get_repo_info(
	SG_context* pCtx,
	SG_repo* pRepo,
	SG_bool bIncludeBranches,
    SG_bool b_include_areas,
    SG_vhash** ppvh)
{
    SG_vhash* pvh = NULL;
	char* pszRepoId = NULL;
	char* pszAdminId = NULL;
	char* pszHashMethod = NULL;
	SG_uint32  count_dagnums = 0;
	SG_uint64* paDagNums     = NULL;
    SG_uint32 i = 0;
    SG_vhash* pvh_dags = NULL;
    SG_vhash* pvh_areas = NULL;
	SG_vhash* pvhBranchPile = NULL;

	SG_bool bHasBranchDag = SG_FALSE;

	SG_NULLARGCHECK_RETURN(pRepo);

    SG_ERR_CHECK(  SG_VHASH__ALLOC(pCtx, &pvh)  );

	/* Protocol version */
	SG_ERR_CHECK(  SG_vhash__add__int64(pCtx, pvh, SG_SYNC_REPO_INFO_KEY__PROTOCOL_VERSION, 1)  );


	/* Basic repository info */
	SG_ERR_CHECK(  SG_repo__get_repo_id(pCtx, pRepo, &pszRepoId)  );
    SG_ERR_CHECK(  SG_repo__get_admin_id(pCtx, pRepo, &pszAdminId)  );
    SG_ERR_CHECK(  SG_repo__get_hash_method(pCtx, pRepo, &pszHashMethod)  );
    SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh, SG_SYNC_REPO_INFO_KEY__REPO_ID, pszRepoId)  );
    SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh, SG_SYNC_REPO_INFO_KEY__ADMIN_ID, pszAdminId)  );
    SG_ERR_CHECK(  SG_vhash__add__string__sz(pCtx, pvh, SG_SYNC_REPO_INFO_KEY__HASH_METHOD, pszHashMethod)  );

	/* All DAGs in the repository */
	SG_ERR_CHECK(  SG_repo__list_dags(pCtx, pRepo, &count_dagnums, &paDagNums)  );
    SG_ERR_CHECK(  SG_vhash__addnew__vhash(pCtx, pvh, "dags", &pvh_dags)  );
    for (i=0; i<count_dagnums; i++)
    {
        char buf_dagnum[SG_DAGNUM__BUF_MAX__HEX];

        SG_ERR_CHECK_RETURN(  SG_dagnum__to_sz__hex(pCtx, paDagNums[i], buf_dagnum, sizeof(buf_dagnum))  );
        SG_ERR_CHECK(  SG_vhash__add__null(pCtx, pvh_dags, buf_dagnum)  );

		/* Asking for a DAG for the first time in a repo will create that DAG.
		 * When pushing into an empty repo, we don't want this initial query to create 
		 * empty new DAGs, so we make sure they exist before we query them. */
		if (paDagNums[i] == SG_DAGNUM__VC_BRANCHES)
			bHasBranchDag = SG_TRUE;
    }
    
    // TODO the following code is a problem, because it requires that this repo
    // instance have indexes, and we would prefer to preserve the ability of
    // an index-free instance to support push, pull, and clone.

	/* All areas in the repository */
    if (b_include_areas)
    {
        SG_ERR_CHECK(  SG_area__list(pCtx, pRepo, &pvh_areas)  );
        if (pvh_areas)
        {
            SG_ERR_CHECK(  SG_vhash__add__vhash(pCtx, pvh, "areas", &pvh_areas)  );
        }
    }

	/* Branches */
	if (bIncludeBranches && bHasBranchDag)
	{
		SG_ERR_CHECK(  SG_vc_branches__cleanup(pCtx, pRepo, &pvhBranchPile)  );
		if (pvhBranchPile)
		{
			SG_bool bHasBranches;

			SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhBranchPile, "branches", &bHasBranches)  );
			if (bHasBranches)
				SG_ERR_CHECK(  SG_vhash__add__vhash(pCtx, pvh, "branches", &pvhBranchPile)  );
		}
	}

    *ppvh = pvh;
    pvh = NULL;

	/* fall through */
fail:
	SG_NULLFREE(pCtx, paDagNums);
    SG_VHASH_NULLFREE(pCtx, pvh);
	SG_NULLFREE(pCtx, pszRepoId);
	SG_NULLFREE(pCtx, pszAdminId);
	SG_NULLFREE(pCtx, pszHashMethod);
	SG_VHASH_NULLFREE(pCtx, pvh_areas);
	SG_VHASH_NULLFREE(pCtx, pvhBranchPile);
}
void SG_sync_remote__request_fragball(
	SG_context* pCtx,
	SG_repo* pRepo,
	const SG_pathname* pFragballDirPathname,
	SG_vhash* pvhRequest,
	char** ppszFragballName)
{
	SG_pathname* pFragballPathname = NULL;
	SG_uint64* paDagNums = NULL;
	SG_string* pstrFragballName = NULL;
	SG_rbtree* prbDagnodes = NULL;
	SG_rbtree_iterator* pit = NULL;
	SG_rev_spec* pRevSpec = NULL;
	SG_stringarray* psaFullHids = NULL;
	SG_rbtree* prbDagnums = NULL;
	SG_dagfrag* pFrag = NULL;
	char* pszRepoId = NULL;
	char* pszAdminId = NULL;
    SG_fragball_writer* pfb = NULL;

	SG_NULLARGCHECK_RETURN(pRepo);
	SG_NULLARGCHECK_RETURN(pFragballDirPathname);

    {
        char buf_filename[SG_TID_MAX_BUFFER_LENGTH];
        SG_ERR_CHECK(  SG_tid__generate(pCtx, buf_filename, sizeof(buf_filename))  );
        SG_ERR_CHECK(  SG_PATHNAME__ALLOC__PATHNAME_SZ(pCtx, &pFragballPathname, pFragballDirPathname, buf_filename)  );
    }

	if (!pvhRequest)
	{
		// Add leaves from every dag to the fragball.
		SG_uint32 count_dagnums;
		SG_uint32 i;

        SG_ERR_CHECK(  SG_fragball_writer__alloc(pCtx, pRepo, pFragballPathname, SG_TRUE, 2, &pfb)  );
		SG_ERR_CHECK(  SG_repo__list_dags(pCtx, pRepo, &count_dagnums, &paDagNums)  );

		for (i=0; i<count_dagnums; i++)
		{
			SG_ERR_CHECK(  SG_repo__fetch_dag_leaves(pCtx, pRepo, paDagNums[i], &prbDagnodes)  );
			SG_ERR_CHECK(  SG_fragball__write__dagnodes(pCtx, pfb, paDagNums[i], prbDagnodes)  );
			SG_RBTREE_NULLFREE(pCtx, prbDagnodes);
		}

		SG_ERR_CHECK(  SG_pathname__get_last(pCtx, pFragballPathname, &pstrFragballName)  );
		SG_ERR_CHECK(  SG_STRDUP(pCtx, SG_string__sz(pstrFragballName), ppszFragballName)  );
        SG_ERR_CHECK(  SG_fragball_writer__close(pCtx, pfb)  );
	}
	else
	{
		// Specific dags/nodes were requested. Build that fragball.
		SG_bool found;

#if TRACE_SYNC_REMOTE && 0
		SG_ERR_CHECK(  SG_vhash_debug__dump_to_console__named(pCtx, pvhRequest, "fragball request")  );
#endif

		SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__CLONE, &found)  );
		if (found)
		{
            // SG_SYNC_STATUS_KEY__CLONE_REQUEST is currently ignored
            SG_ERR_CHECK(  SG_repo__fetch_repo__fragball(pCtx, pRepo, 3, pFragballDirPathname, ppszFragballName) );
		}
		else
		{
			// Not a full clone.

            SG_ERR_CHECK(  SG_fragball_writer__alloc(pCtx, pRepo, pFragballPathname, SG_TRUE, 2, &pfb)  );
			SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__SINCE, &found)  );
			if (found)
			{
                SG_vhash* pvh_since = NULL;

                SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__SINCE, &pvh_since)  );

                SG_ERR_CHECK(  _do_since(pCtx, pRepo, pvh_since, pfb)  );
            }

			SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__DAGS, &found)  );
			if (found)
			{
				// Specific Dagnodes were requested. Add just those nodes to our "start from" rbtree.

				SG_vhash* pvhDags;
				SG_uint32 count_requested_dagnums;
				SG_uint32 i;
				const SG_variant* pvRevSpecs = NULL;
				SG_vhash* pvhRevSpec = NULL;

				// For each requested dag, get rev spec request.
				SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__DAGS, &pvhDags)  );
				SG_ERR_CHECK(  SG_vhash__count(pCtx, pvhDags, &count_requested_dagnums)  );
				if (count_requested_dagnums)
					SG_ERR_CHECK(  SG_repo__list_dags__rbtree(pCtx, pRepo, &prbDagnums)  );
				for (i=0; i<count_requested_dagnums; i++)
				{
					SG_bool isValidDagnum = SG_FALSE;
					SG_bool bSpecificNodesRequested = SG_FALSE;
					const char* pszRefDagNum = NULL;
					SG_uint64 iDagnum;

					// Get the dag's missing node vhash.
					SG_ERR_CHECK(  SG_vhash__get_nth_pair(pCtx, pvhDags, i, &pszRefDagNum, &pvRevSpecs)  );

					// Verify that requested dagnum exists
					SG_ERR_CHECK(  SG_rbtree__find(pCtx, prbDagnums, pszRefDagNum, &isValidDagnum, NULL)  );
					if (!isValidDagnum)
                        continue;

					SG_ERR_CHECK(  SG_dagnum__from_sz__hex(pCtx, pszRefDagNum, &iDagnum)  );
					
					if (pvRevSpecs && pvRevSpecs->type != SG_VARIANT_TYPE_NULL)
					{
						SG_uint32 countRevSpecs = 0;
						
						SG_ERR_CHECK(  SG_variant__get__vhash(pCtx, pvRevSpecs, &pvhRevSpec)  );
						SG_ERR_CHECK(  SG_rev_spec__from_vash(pCtx, pvhRevSpec, &pRevSpec)  );

						// Process the rev spec for each dag
						SG_ERR_CHECK(  SG_rev_spec__count(pCtx, pRevSpec, &countRevSpecs)  );
						if (countRevSpecs > 0)
						{
							bSpecificNodesRequested = SG_TRUE;

							SG_ERR_CHECK(  SG_rev_spec__get_all__repo(pCtx, pRepo, pRevSpec, SG_TRUE, &psaFullHids, NULL)  );
							SG_ERR_CHECK(  SG_stringarray__to_rbtree_keys(pCtx, psaFullHids, &prbDagnodes)  );
							SG_STRINGARRAY_NULLFREE(pCtx, psaFullHids);
						}
						SG_REV_SPEC_NULLFREE(pCtx, pRevSpec);
					}

					if (!bSpecificNodesRequested)
					{
						// When no specific nodes are in the request, add all leaves.
						SG_ERR_CHECK(  SG_repo__fetch_dag_leaves(pCtx, pRepo, iDagnum, &prbDagnodes)  );
					}

					if (prbDagnodes) // can be null when leaves of an empty dag are requested
					{
						// Get the leaves of the other repo, which we need to connect to.
						SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__LEAVES, &found)  );
						if (found)
						{
							SG_vhash* pvhRefAllLeaves;
							SG_vhash* pvhRefDagLeaves;
							SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__LEAVES, &pvhRefAllLeaves)  );
							SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRequest, pszRefDagNum, &found)  );
							{
								SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhRefAllLeaves, pszRefDagNum, &pvhRefDagLeaves)  );
								SG_ERR_CHECK(  SG_sync__build_best_guess_dagfrag(pCtx, pRepo, iDagnum, 
									prbDagnodes, pvhRefDagLeaves, &pFrag)  );
							}
						}
						else
						{
							// The other repo's leaves weren't provided: add just the requested nodes, make no attempt to connect.
							SG_ERR_CHECK(  SG_repo__get_repo_id(pCtx, pRepo, &pszRepoId)  );
							SG_ERR_CHECK(  SG_repo__get_admin_id(pCtx, pRepo, &pszAdminId)  );
							SG_ERR_CHECK(  SG_dagfrag__alloc(pCtx, &pFrag, pszRepoId, pszAdminId, iDagnum)  );
							SG_ERR_CHECK(  SG_dagfrag__load_from_repo__simple(pCtx, pFrag, pRepo, prbDagnodes)  );
							SG_NULLFREE(pCtx, pszRepoId);
							SG_NULLFREE(pCtx, pszAdminId);
						}

						SG_ERR_CHECK(  SG_fragball__write__frag(pCtx, pfb, pFrag)  );
						
						SG_RBTREE_NULLFREE(pCtx, prbDagnodes);
						SG_DAGFRAG_NULLFREE(pCtx, pFrag);
					}

				} // dagnum loop
			} // if "dags" exists

			/* Add requested blobs to the fragball */
			SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__BLOBS, &found)  );
			if (found)
			{
				// Blobs were requested.
				SG_vhash* pvhBlobs;
				SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__BLOBS, &pvhBlobs)  );
				SG_ERR_CHECK(  SG_sync__add_blobs_to_fragball(pCtx, pfb, pvhBlobs)  );
			}

			SG_ERR_CHECK(  SG_pathname__get_last(pCtx, pFragballPathname, &pstrFragballName)  );
			SG_ERR_CHECK(  SG_STRDUP(pCtx, SG_string__sz(pstrFragballName), ppszFragballName)  );
		}
        SG_ERR_CHECK(  SG_fragball_writer__close(pCtx, pfb)  );
	}

	/* fallthru */
fail:
	// If we had an error, delete the half-baked fragball.
	if (pFragballPathname && SG_context__has_err(pCtx))
    {
		SG_ERR_IGNORE(  SG_fsobj__remove__pathname(pCtx, pFragballPathname)  );
    }

	SG_PATHNAME_NULLFREE(pCtx, pFragballPathname);
	SG_NULLFREE(pCtx, paDagNums);
	SG_STRING_NULLFREE(pCtx, pstrFragballName);
	SG_RBTREE_NULLFREE(pCtx, prbDagnodes);
	SG_RBTREE_ITERATOR_NULLFREE(pCtx, pit);
	SG_RBTREE_NULLFREE(pCtx, prbDagnums);
	SG_REV_SPEC_NULLFREE(pCtx, pRevSpec);
	SG_STRINGARRAY_NULLFREE(pCtx, psaFullHids);
	SG_DAGFRAG_NULLFREE(pCtx, pFrag);
	SG_NULLFREE(pCtx, pszRepoId);
	SG_NULLFREE(pCtx, pszAdminId);
    SG_FRAGBALL_WRITER_NULLFREE(pCtx, pfb);
}
// 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);
}
Ejemplo n.º 18
0
void SG_server__pull_request_fragball(SG_context* pCtx,
									  SG_repo* pRepo,
									  SG_vhash* pvhRequest,
									  const SG_pathname* pFragballDirPathname,
									  char** ppszFragballName,
									  SG_vhash** ppvhStatus)
{
	SG_pathname* pFragballPathname = NULL;
	SG_uint32* paDagNums = NULL;
    SG_rbtree* prbDagnodes = NULL;
	SG_string* pstrFragballName = NULL;
	char* pszRevFullHid = NULL;
	SG_rbtree_iterator* pit = NULL;
	SG_uint32* repoDagnums = NULL;

	SG_NULLARGCHECK_RETURN(pRepo);
	SG_NULLARGCHECK_RETURN(pFragballDirPathname);
	SG_NULLARGCHECK_RETURN(ppvhStatus);

#if TRACE_SERVER
	SG_ERR_CHECK(  SG_vhash_debug__dump_to_console__named(pCtx, pvhRequest, "pull fragball request")  );
#endif

	SG_ERR_CHECK(  SG_fragball__create(pCtx, pFragballDirPathname, &pFragballPathname)  );

	if (!pvhRequest)
	{
		// Add leaves from every dag to the fragball.
		SG_uint32 count_dagnums;
		SG_uint32 i;
		SG_ERR_CHECK(  SG_repo__list_dags(pCtx, pRepo, &count_dagnums, &paDagNums)  );

		for (i=0; i<count_dagnums; i++)
		{
			SG_ERR_CHECK(  SG_repo__fetch_dag_leaves(pCtx, pRepo, paDagNums[i], &prbDagnodes)  );
			SG_ERR_CHECK(  SG_fragball__append__dagnodes(pCtx, pFragballPathname, pRepo, paDagNums[i], prbDagnodes)  );
			SG_RBTREE_NULLFREE(pCtx, prbDagnodes);
		}

		SG_ERR_CHECK(  SG_pathname__get_last(pCtx, pFragballPathname, &pstrFragballName)  );
		SG_ERR_CHECK(  SG_STRDUP(pCtx, SG_string__sz(pstrFragballName), ppszFragballName)  );
	}
	else
	{
		// Build the requested fragball.
		SG_bool found;

		SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__CLONE, &found)  );
		if (found)
		{
			// Full clone requested.
			SG_ERR_CHECK(  SG_repo__fetch_repo__fragball(pCtx, pRepo, pFragballDirPathname, ppszFragballName) );
		}
		else
		{
			// Not a full clone.

			SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__DAGS, &found)  );
			if (found)
			{
				// Dagnodes were requested.

				SG_uint32 generations = 0;
				SG_vhash* pvhDags;
				SG_uint32 count_requested_dagnums;
				SG_uint32 count_repo_dagnums = 0;
				SG_uint32 i;
				const char* pszDagNum = NULL;
				const SG_variant* pvRequestedNodes = NULL;
				SG_vhash* pvhRequestedNodes = NULL;
				const char* pszHidRequestedDagnode = NULL;

				// Were additional generations requested?
				SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__GENERATIONS, &found)  );
				if (found)
					SG_ERR_CHECK(  SG_vhash__get__uint32(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__GENERATIONS, &generations)  );

				SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__DAGS, &pvhDags)  );
				SG_ERR_CHECK(  SG_vhash__count(pCtx, pvhDags, &count_requested_dagnums)  );
				if (count_requested_dagnums)
					SG_ERR_CHECK(  SG_repo__list_dags(pCtx, pRepo, &count_repo_dagnums, &repoDagnums)  );

				// For each requested dag, get the requested nodes.
				for (i=0; i<count_requested_dagnums; i++)
				{
					SG_uint32 iMissingNodeCount;
					SG_uint32 iDagnum;
					SG_uint32 j;
					SG_bool isValidDagnum = SG_FALSE;
					SG_bool bSpecificNodesRequested = SG_FALSE;

					// Get the dag's missing node vhash.
					SG_ERR_CHECK(  SG_vhash__get_nth_pair(pCtx, pvhDags, i, &pszDagNum, &pvRequestedNodes)  );
					SG_ERR_CHECK(  SG_dagnum__from_sz__decimal(pCtx, pszDagNum, &iDagnum)  );

					// Verify that requested dagnum exists
					for (j = 0; j < count_repo_dagnums; j++)
					{
						if (repoDagnums[j] == iDagnum)
						{
							isValidDagnum = SG_TRUE;
							break;
						}
					}
					if (!isValidDagnum)
					{
						char buf[SG_DAGNUM__BUF_MAX__NAME];
						SG_ERR_CHECK(  SG_dagnum__to_name(pCtx, iDagnum, buf, sizeof(buf))  );
						SG_ERR_THROW2(SG_ERR_NO_SUCH_DAG, (pCtx, "%s", buf));
					}

					if (pvRequestedNodes)
					{
						SG_ERR_CHECK(  SG_variant__get__vhash(pCtx, pvRequestedNodes, &pvhRequestedNodes)  );

						// Get each node listed for the dag
						SG_ERR_CHECK(  SG_vhash__count(pCtx, pvhRequestedNodes, &iMissingNodeCount)  );
						if (iMissingNodeCount > 0)
						{
							SG_uint32 j;
							const SG_variant* pvVal;

							bSpecificNodesRequested = SG_TRUE;

							SG_ERR_CHECK(  SG_RBTREE__ALLOC__PARAMS(pCtx, &prbDagnodes, iMissingNodeCount, NULL)  );
							for (j=0; j<iMissingNodeCount; j++)
							{
								SG_ERR_CHECK(  SG_vhash__get_nth_pair(pCtx, pvhRequestedNodes, j, &pszHidRequestedDagnode, &pvVal)  );

								if (pvVal)
								{
									const char* pszVal;
									SG_ERR_CHECK(  SG_variant__get__sz(pCtx, pvVal, &pszVal)  );
									if (pszVal)
									{
										if (0 == strcmp(pszVal, SG_SYNC_REQUEST_VALUE_HID_PREFIX))
										{
											SG_ERR_CHECK(  SG_repo__hidlookup__dagnode(pCtx, pRepo, iDagnum, pszHidRequestedDagnode, &pszRevFullHid)  );
											pszHidRequestedDagnode = pszRevFullHid;
										}
										else if (0 == strcmp(pszVal, SG_SYNC_REQUEST_VALUE_TAG))
										{
											SG_ERR_CHECK(  SG_vc_tags__lookup__tag(pCtx, pRepo, pszHidRequestedDagnode, &pszRevFullHid)  );
											if (!pszRevFullHid)
												SG_ERR_THROW(SG_ERR_TAG_NOT_FOUND);
											pszHidRequestedDagnode = pszRevFullHid;
										}
										else
											SG_ERR_THROW(SG_ERR_PULL_INVALID_FRAGBALL_REQUEST);
									}
								}
								
								SG_ERR_CHECK(  SG_rbtree__update(pCtx, prbDagnodes, pszHidRequestedDagnode)  );
								// Get additional dagnode generations, if requested.
								SG_ERR_CHECK(  SG_sync__add_n_generations(pCtx, pRepo, pszHidRequestedDagnode, prbDagnodes, generations)  );
								SG_NULLFREE(pCtx, pszRevFullHid);
							}
						}
					}

					if (!bSpecificNodesRequested)
					{
						// When no specific nodes are in the request, add all leaves.
						SG_ERR_CHECK(  SG_repo__fetch_dag_leaves(pCtx, pRepo, iDagnum, &prbDagnodes)  );

						// Get additional dagnode generations, if requested.
						if (generations)
						{
							SG_bool found;
							const char* hid;
							
							SG_ERR_CHECK(  SG_rbtree__iterator__first(pCtx, &pit, prbDagnodes, &found, &hid, NULL)  );
							while (found)
							{
								SG_ERR_CHECK(  SG_sync__add_n_generations(pCtx, pRepo, hid, prbDagnodes, generations)  );
								SG_ERR_CHECK(  SG_rbtree__iterator__next(pCtx, pit, &found, &hid, NULL)  );
							}
						}
					}

					if (prbDagnodes) // can be null when leaves of an empty dag are requested
					{
						SG_ERR_CHECK(  SG_fragball__append__dagnodes(pCtx, pFragballPathname, pRepo, iDagnum, prbDagnodes)  );
						SG_RBTREE_NULLFREE(pCtx, prbDagnodes);
					}

				} // dagnum loop
			} // if "dags" exists

			/* Add requested blobs to the fragball */
			SG_ERR_CHECK(  SG_vhash__has(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__BLOBS, &found)  );
			if (found)
			{
				// Blobs were requested.
				SG_vhash* pvhBlobs;
				SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvhRequest, SG_SYNC_STATUS_KEY__BLOBS, &pvhBlobs)  );
				SG_ERR_CHECK(  SG_sync__add_blobs_to_fragball(pCtx, pRepo, pFragballPathname, pvhBlobs)  );
			}

			SG_ERR_CHECK(  SG_pathname__get_last(pCtx, pFragballPathname, &pstrFragballName)  );
			SG_ERR_CHECK(  SG_STRDUP(pCtx, SG_string__sz(pstrFragballName), ppszFragballName)  );
		}
	}
	
	/* fallthru */
fail:
	// If we had an error, delete the half-baked fragball.
	if (pFragballPathname && SG_context__has_err(pCtx))
		SG_ERR_IGNORE(  SG_fsobj__remove__pathname(pCtx, pFragballPathname)  );

	SG_PATHNAME_NULLFREE(pCtx, pFragballPathname);
	SG_NULLFREE(pCtx, paDagNums);
	SG_RBTREE_NULLFREE(pCtx, prbDagnodes);
	SG_STRING_NULLFREE(pCtx, pstrFragballName);
	SG_NULLFREE(pCtx, pszRevFullHid);
	SG_RBTREE_ITERATOR_NULLFREE(pCtx, pit);
	SG_NULLFREE(pCtx, repoDagnums);
}
void SG_cmd_util__dump_history_results(
	SG_context * pCtx, 
	SG_console_stream cs,
	SG_history_result* pHistResult, 
	SG_vhash* pvh_pile,
	SG_bool bShowOnlyOpenBranchNames,
	SG_bool bShowFullComments,
	SG_bool bHideRevnums)
{
	//print the information for each
	SG_bool bFound = (pHistResult != NULL);
	const char* currentInfoItem = NULL;
	SG_uint32 revno;
	SG_uint32 nCount = 0;
	SG_uint32 nIndex = 0;
	const char * pszTag = NULL;
	const char * pszComment = NULL;
	const char * pszStamp = NULL;
	const char * pszParent = NULL;
	SG_uint32 nResultCount = 0;
	SG_vhash* pvhRefBranchValues = NULL;
	SG_vhash* pvhRefClosedBranches = NULL;
	char* pszMyComment = NULL;

	if (pvh_pile)
	{
		SG_bool bHas = SG_FALSE;
		SG_ERR_CHECK(  SG_vhash__has(pCtx, pvh_pile, "closed", &bHas)  );
		if (bHas)
			SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvh_pile, "closed", &pvhRefClosedBranches)  );

		SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvh_pile, "values", &pvhRefBranchValues)  );
	}

	SG_ERR_CHECK(  SG_history_result__count(pCtx, pHistResult, &nResultCount)  );
	while (nResultCount != 0 && bFound)
	{
		SG_ERR_CHECK(  SG_history_result__get_revno(pCtx, pHistResult, &revno)  );
		SG_ERR_CHECK(  SG_history_result__get_cshid(pCtx, pHistResult, &currentInfoItem)  );
		if(!bHideRevnums)
			SG_ERR_CHECK(  SG_console(pCtx, cs, "\n\t%8s:  %d:%s\n", "revision", revno, currentInfoItem)  );
		else
			SG_ERR_CHECK(  SG_console(pCtx, cs, "\n\t%8s:  %s\n", "revision", currentInfoItem)  );

		SG_ERR_CHECK(  _dump_branch_name(pCtx, cs, currentInfoItem, bShowOnlyOpenBranchNames, 
			pvhRefBranchValues, pvhRefClosedBranches)  );

		SG_ERR_CHECK(  SG_history_result__get_audit__count(pCtx, pHistResult, &nCount)  );
		for (nIndex = 0; nIndex < nCount; nIndex++)
		{
			SG_int64 itime = -1;
			char buf_time_formatted[256];
			const char * pszUser = NULL;

			SG_ERR_CHECK(  SG_history_result__get_audit__who(pCtx, pHistResult, nIndex, &pszUser)  );
			SG_ERR_CHECK(  SG_history_result__get_audit__when(pCtx, pHistResult, nIndex, &itime)  );
			SG_ERR_CHECK(  SG_time__format_local__i64(pCtx, itime, buf_time_formatted, sizeof(buf_time_formatted))  );
			SG_ERR_CHECK(  SG_console(pCtx, cs, "\t%8s:  %s\n", "who", pszUser)  );
			SG_ERR_CHECK(  SG_console(pCtx, cs, "\t%8s:  %s\n", "when", buf_time_formatted)  );
		}

		SG_ERR_CHECK(  SG_history_result__get_tag__count(pCtx, pHistResult, &nCount)  );
		for (nIndex = 0; nIndex < nCount; nIndex++)
		{
			SG_ERR_CHECK(  SG_history_result__get_tag__text(pCtx, pHistResult, nIndex, &pszTag)  );
			SG_ERR_CHECK(  SG_console(pCtx, cs, "\t%8s:  %s\n", "tag", pszTag)  );
		}

		SG_ERR_CHECK(  SG_history_result__get_comment__count(pCtx, pHistResult, &nCount)  );
		for (nIndex = 0; nIndex < nCount; nIndex++)
		{
			SG_ERR_CHECK(  SG_history_result__get_comment__text(pCtx, pHistResult, nIndex, &pszComment)  );
			if (pszComment)
			{
				SG_ERR_CHECK(  _format_comment(pCtx, !bShowFullComments, "\t           ", pszComment, &pszMyComment)  );
				if (pszMyComment)
					pszComment = pszMyComment;
			}
			SG_ERR_CHECK(  SG_console(pCtx, cs, "\t%8s:  %s\n", "comment", pszComment)  );
			SG_NULLFREE(pCtx, pszMyComment);
		}

		SG_ERR_CHECK(  SG_history_result__get_stamp__count(pCtx, pHistResult, &nCount)  );
		for (nIndex = 0; nIndex < nCount; nIndex++)
		{
			SG_ERR_CHECK(  SG_history_result__get_stamp__text(pCtx, pHistResult, nIndex, &pszStamp)  );
			SG_ERR_CHECK(  SG_console(pCtx, cs, "\t%8s:  %s\n", "stamp", pszStamp)  );
		}

		SG_ERR_CHECK(  SG_history_result__get_parent__count(pCtx, pHistResult, &nCount)  );
		for (nIndex = 0; nIndex < nCount; nIndex++)
		{
			SG_ERR_CHECK(  SG_history_result__get_parent(pCtx, pHistResult, nIndex, &pszParent, &revno)  );
			SG_ERR_CHECK(  SG_console(pCtx, cs, "\t%8s:  %d:%s\n", "parent", revno, pszParent)  );
		}

		SG_ERR_CHECK(  SG_history_result__next(pCtx, pHistResult, &bFound)  );
	}
fail:
	SG_NULLFREE(pCtx, pszMyComment);
}