Exemplo n.º 1
0
void SG_dagfrag__alloc(SG_context * pCtx,
					   SG_dagfrag ** ppNew,
					   const char* psz_repo_id,
					   const char* psz_admin_id,
					   SG_uint32 iDagNum)
{
	SG_dagfrag * pFrag = NULL;

	SG_NULLARGCHECK_RETURN(ppNew);
	SG_ARGCHECK_RETURN( (iDagNum != SG_DAGNUM__NONE), iDagNum );
	SG_ERR_CHECK_RETURN(  SG_gid__argcheck(pCtx, psz_repo_id)  );
	SG_ERR_CHECK_RETURN(  SG_gid__argcheck(pCtx, psz_admin_id)  );

    SG_ERR_CHECK(  SG_alloc(pCtx, 1, sizeof(SG_dagfrag), &pFrag)  );

    SG_ERR_CHECK(  SG_strcpy(pCtx, pFrag->m_sz_repo_id, sizeof(pFrag->m_sz_repo_id), psz_repo_id)  );
    SG_ERR_CHECK(  SG_strcpy(pCtx, pFrag->m_sz_admin_id, sizeof(pFrag->m_sz_admin_id), psz_admin_id)  );

    pFrag->m_iDagNum = iDagNum;

	SG_ERR_CHECK(  SG_RBTREE__ALLOC(pCtx,&pFrag->m_pRB_Cache)  );

	pFrag->m_pRB_GenerationSortedMemberCache = NULL;		// only create this if needed

	*ppNew = pFrag;
	return;

fail:
	SG_DAGFRAG_NULLFREE(pCtx, pFrag);
}
Exemplo n.º 2
0
void SG_dagfrag__eat_other_frag(SG_context * pCtx,
								SG_dagfrag* pConsumerFrag,
								SG_dagfrag** ppFragToBeEaten)
{
	SG_rbtree_iterator* pit = NULL;
	SG_bool b = SG_FALSE;
	_my_data * pMyData = NULL;
	const char* psz_id = NULL;
	SG_dagfrag* pFragToBeEaten = NULL;

	SG_NULLARGCHECK_RETURN(pConsumerFrag);
	SG_NULLARGCHECK_RETURN(ppFragToBeEaten);
	SG_NULLARGCHECK_RETURN(*ppFragToBeEaten);

	pFragToBeEaten = *ppFragToBeEaten;

#if DEBUG && TRACE_DAGFRAG
	SG_ERR_CHECK(  SG_dagfrag_debug__dump__console(pCtx, pFragToBeEaten, "frag to be eaten", 0, SG_CS_STDOUT)  );
	SG_ERR_CHECK(  SG_rbtree_debug__dump_keys_to_console(pCtx, pFragToBeEaten->m_pRB_Cache)  );
	SG_ERR_CHECK(  SG_console__flush(pCtx, SG_CS_STDERR)  );
	SG_ERR_CHECK(  SG_dagfrag_debug__dump__console(pCtx, pConsumerFrag, "new frag before meal", 0, SG_CS_STDOUT)  );
	SG_ERR_CHECK(  SG_rbtree_debug__dump_keys_to_console(pCtx, pConsumerFrag->m_pRB_Cache)  );
	SG_ERR_CHECK(  SG_console__flush(pCtx, SG_CS_STDERR)  );
#endif

	if (
		(pConsumerFrag->m_iDagNum != pFragToBeEaten->m_iDagNum)
		|| (0 != strcmp(pConsumerFrag->m_sz_repo_id, pFragToBeEaten->m_sz_repo_id))
		|| (0 != strcmp(pConsumerFrag->m_sz_admin_id, pFragToBeEaten->m_sz_admin_id))
		)
	{
		SG_ERR_THROW_RETURN(  SG_ERR_REPO_MISMATCH  );
	}

	SG_ERR_CHECK(  SG_rbtree__iterator__first(pCtx, &pit,pFragToBeEaten->m_pRB_Cache,&b,&psz_id,(void **)&pMyData)  );
	while (b)
	{
		if (pMyData->m_pDagnode)
		{
			SG_ERR_CHECK(  SG_dagfrag__add_dagnode(pCtx, pConsumerFrag, &pMyData->m_pDagnode)  );
		}
		SG_ERR_CHECK(  SG_rbtree__iterator__next(pCtx, pit,&b,&psz_id,(void **)&pMyData)  );
	}
	SG_RBTREE_ITERATOR_NULLFREE(pCtx, pit);

	SG_DAGFRAG_NULLFREE(pCtx, pFragToBeEaten);
	*ppFragToBeEaten = NULL;

#if DEBUG && TRACE_DAGFRAG
	SG_ERR_CHECK(  SG_dagfrag_debug__dump__console(pCtx, pConsumerFrag, "new frag after meal", 0, SG_CS_STDOUT)  );
	SG_ERR_CHECK(  SG_rbtree_debug__dump_keys_to_console(pCtx, pConsumerFrag->m_pRB_Cache)  );
	SG_ERR_CHECK(  SG_console__flush(pCtx, SG_CS_STDERR)  );
#endif

	return;

fail:
	SG_RBTREE_ITERATOR_NULLFREE(pCtx, pit);
}
Exemplo n.º 3
0
void SG_dagfrag__alloc__from_vhash(SG_context * pCtx,
								   SG_dagfrag ** ppNew,
								   const SG_vhash * pvhFrag)
{
	const char * szVersion;
	SG_dagfrag * pFrag = NULL;
	struct _deserialize_data deserialize_data;
    SG_int64 iDagNum = 0;

	SG_NULLARGCHECK_RETURN(ppNew);
	SG_NULLARGCHECK_RETURN(pvhFrag);

	SG_ERR_CHECK(  SG_vhash__get__sz(pCtx,pvhFrag,KEY_VERSION,&szVersion)  );
	if (strcmp(szVersion,"1") == 0)
	{
		// handle dagfrags that were serialized by software compiled with
		// VALUE_VERSION == 1.

		SG_varray * pvaMyData;
        const char* psz_repo_id = NULL;
        const char* psz_admin_id = NULL;

        SG_ERR_CHECK(  SG_vhash__get__sz(pCtx,pvhFrag,KEY_REPO_ID,&psz_repo_id)  );
        SG_ERR_CHECK(  SG_vhash__get__sz(pCtx,pvhFrag,KEY_ADMIN_ID,&psz_admin_id)  );
        SG_ERR_CHECK(  SG_vhash__get__int64(pCtx,pvhFrag,KEY_DAGNUM,&iDagNum)  );

        SG_ERR_CHECK(  SG_dagfrag__alloc(pCtx,&pFrag,psz_repo_id,psz_admin_id,(SG_uint32) iDagNum)  );

		SG_ERR_CHECK(  SG_vhash__get__varray(pCtx,pvhFrag,KEY_DATA,&pvaMyData)  );

		deserialize_data.pFrag = pFrag;
		SG_ERR_CHECK(  SG_varray__foreach(pCtx,
										  pvaMyData,
										  _deserialize_data_ver_1_cb,
										  &deserialize_data)  );

		*ppNew = pFrag;
		return;
	}
	else
	{
		SG_ERR_THROW(  SG_ERR_DAGFRAG_DESERIALIZATION_VERSION  );
	}

fail:
	SG_DAGFRAG_NULLFREE(pCtx, pFrag);
}
Exemplo n.º 4
0
void SG_dagfrag__alloc_transient(SG_context * pCtx,
								 SG_dagfrag ** ppNew)
{
	SG_dagfrag * pFrag = NULL;

	SG_NULLARGCHECK_RETURN(ppNew);

    SG_ERR_CHECK(  SG_alloc(pCtx, 1, sizeof(SG_dagfrag), &pFrag)  );

	pFrag->m_sz_repo_id[0] = '*';
	pFrag->m_sz_admin_id[0] = '*';
    pFrag->m_iDagNum = SG_DAGNUM__NONE;

	SG_ERR_CHECK(  SG_RBTREE__ALLOC(pCtx,&pFrag->m_pRB_Cache)  );

	pFrag->m_pRB_GenerationSortedMemberCache = NULL;		// only create this if needed

	*ppNew = pFrag;
	return;

fail:
	SG_DAGFRAG_NULLFREE(pCtx, pFrag);
}
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);
}
void SG_dagquery__how_are_dagnodes_related(SG_context * pCtx,
										   SG_repo * pRepo,
                                           SG_uint64 dagnum,
										   const char * pszHid1,
										   const char * pszHid2,
										   SG_bool bSkipDescendantCheck,
										   SG_bool bSkipAncestorCheck,
										   SG_dagquery_relationship * pdqRel)
{
	SG_dagnode * pdn1 = NULL;
	SG_dagnode * pdn2 = NULL;
	SG_dagfrag * pFrag = NULL;
	SG_dagquery_relationship dqRel = SG_DAGQUERY_RELATIONSHIP__UNKNOWN;
	SG_int32 gen1, gen2;
	SG_bool bFound;

	SG_NULLARGCHECK_RETURN(pRepo);
	SG_NONEMPTYCHECK_RETURN(pszHid1);
	SG_NONEMPTYCHECK_RETURN(pszHid2);
	SG_NULLARGCHECK_RETURN(pdqRel);

	if (strcmp(pszHid1, pszHid2) == 0)
	{
		dqRel = SG_DAGQUERY_RELATIONSHIP__SAME;
		goto cleanup;
	}

	// fetch the dagnode for both HIDs.  this throws when the HID is not found.

	SG_ERR_CHECK(  SG_repo__fetch_dagnode(pCtx, pRepo, dagnum, pszHid1, &pdn1)  );
	SG_ERR_CHECK(  SG_repo__fetch_dagnode(pCtx, pRepo, dagnum, pszHid2, &pdn2)  );

	// we say that 2 nodes are either:
	// [1] ancestor/descendant of each other;
	// [2] or that they are peers (cousins) of each other (no matter
	//     how distant in the DAG).  (that have an LCA, but we don't
	//     care about it.)

	// get the generation of both dagnodes.  if they are the same, then they
	// cannot have an ancestor/descendant relationship and therefore must be
	// peers/cousins (we don't care how close/distant they are).

	SG_ERR_CHECK(  SG_dagnode__get_generation(pCtx, pdn1, &gen1)  );
	SG_ERR_CHECK(  SG_dagnode__get_generation(pCtx, pdn2, &gen2)  );
	if (gen1 == gen2)
	{
		dqRel = SG_DAGQUERY_RELATIONSHIP__PEER;
		goto cleanup;
	}

	// see if one is an ancestor of the other.  since we only have PARENT
	// edges in our DAG, we start with the deeper one and walk backwards
	// until we've visited all ancestors at the depth of the shallower one.
	//
	// i'm going to be lazy here and not reinvent a recursive-ish parent-edge
	// graph walker.  instead, i'm going to create a DAGFRAG using the
	// deeper one and request the generation difference as the "thickness".
	// in theory, if we have an ancestor/descendant relationship, the
	// shallower one should be in the END-FRINGE of the DAGFRAG.
	//
	// i'm going to pick an arbitrary direction "cs1 is R of cs2".

	SG_ERR_CHECK(  SG_dagfrag__alloc_transient(pCtx, dagnum, &pFrag)  );
	if (gen1 > gen2)		// cs1 is *DEEPER* than cs2
	{
		if (bSkipDescendantCheck)
		{
			dqRel = SG_DAGQUERY_RELATIONSHIP__UNKNOWN;
		}
		else
		{
			SG_ERR_CHECK(  SG_dagfrag__load_from_repo__one(pCtx, pFrag, pRepo, pszHid1, (gen1 - gen2))  );
			SG_ERR_CHECK(  SG_dagfrag__query(pCtx, pFrag, pszHid2, NULL, NULL, &bFound, NULL)  );

			if (bFound)			// pszHid2 is an ancestor of pszHid1.  READ pszHid1 is a descendent of pszHid2.
				dqRel = SG_DAGQUERY_RELATIONSHIP__DESCENDANT;
			else				// they are *distant* peers.
				dqRel = SG_DAGQUERY_RELATIONSHIP__PEER;
		}
		goto cleanup;
	}
	else
	{
		if (bSkipAncestorCheck)
		{
			dqRel = SG_DAGQUERY_RELATIONSHIP__UNKNOWN;
		}
		else
		{
			SG_ERR_CHECK(  SG_dagfrag__load_from_repo__one(pCtx, pFrag, pRepo, pszHid2, (gen2 - gen1))  );
			SG_ERR_CHECK(  SG_dagfrag__query(pCtx, pFrag, pszHid1, NULL, NULL, &bFound, NULL)  );

			if (bFound)			// pszHid1 is an ancestor of pszHid2.
				dqRel = SG_DAGQUERY_RELATIONSHIP__ANCESTOR;
			else				// they are *distant* peers.
				dqRel = SG_DAGQUERY_RELATIONSHIP__PEER;
		}
		goto cleanup;
	}

	/*NOTREACHED*/

cleanup:
	*pdqRel = dqRel;

fail:
	SG_DAGNODE_NULLFREE(pCtx, pdn1);
	SG_DAGNODE_NULLFREE(pCtx, pdn2);
	SG_DAGFRAG_NULLFREE(pCtx, pFrag);
}
Exemplo n.º 7
0
void SG_sync__build_best_guess_dagfrag(
	SG_context* pCtx,
	SG_repo* pRepo,
	SG_uint64 iDagNum,
	SG_rbtree* prbStartFromHids,
	SG_vhash* pvhConnectToHidsAndGens,
	SG_dagfrag** ppFrag)
{
	SG_uint32 i, countConnectTo;
	SG_rbtree_iterator* pit = NULL;
	SG_dagnode* pdn = NULL;
	SG_dagfrag* pFrag = NULL;
	SG_repo_fetch_dagnodes_handle* pdh = NULL;

	SG_int32 minGen = SG_INT32_MAX;
	SG_int32 maxGen = 0;
	SG_uint32 gensToFetch = 0;

	char* psz_repo_id = NULL;
	char* psz_admin_id = NULL;

	SG_bool bNextHid;
	const char* pszRefHid;
	SG_int32 gen;

#if TRACE_SYNC
	SG_int64 startTime;
	SG_int64 endTime;
#endif

	SG_NULLARGCHECK_RETURN(prbStartFromHids);

	/* Find the minimum generation in pertinent "connect to" nodes. */
	if (pvhConnectToHidsAndGens)
	{
		SG_ERR_CHECK(  SG_vhash__count(pCtx, pvhConnectToHidsAndGens, &countConnectTo)  );
		for (i = 0; i < countConnectTo; i++)
		{
			SG_int32 gen;
			SG_ERR_CHECK(  SG_vhash__get_nth_pair__int32(pCtx, pvhConnectToHidsAndGens, i, &pszRefHid, &gen)  );
			if (gen < minGen)
				minGen = gen;
		}
	}

	/* Happens when pulling into an empty repo, or when an entire dag is specifically requested. */
	if (minGen == SG_INT32_MAX)
		minGen = -1;

	SG_ERR_CHECK(  SG_repo__fetch_dagnodes__begin(pCtx, pRepo, iDagNum, &pdh)  );

	/* Find the maximum generation in pertinent "start from" nodes. */
	SG_ERR_CHECK(  SG_rbtree__iterator__first(pCtx, &pit, prbStartFromHids, &bNextHid, &pszRefHid, NULL)  );
	while (bNextHid)
	{
		SG_ERR_CHECK(  SG_repo__fetch_dagnodes__one(pCtx, pRepo, pdh, pszRefHid, &pdn)  );
		SG_ERR_CHECK(  SG_dagnode__get_generation(pCtx, pdn, &gen)  );
		if (gen > maxGen)
			maxGen = gen;
		SG_DAGNODE_NULLFREE(pCtx, pdn);

		SG_ERR_CHECK(  SG_rbtree__iterator__next(pCtx, pit, &bNextHid, &pszRefHid, NULL)  );
	}
	SG_RBTREE_ITERATOR_NULLFREE(pCtx, pit);

	if (maxGen <= minGen)
		gensToFetch = FALLBACK_GENS_PER_ROUNDTRIP;
	else
		gensToFetch = maxGen - minGen;

#if TRACE_SYNC
	{
		char buf_dagnum[SG_DAGNUM__BUF_MAX__HEX];
		SG_uint32 count;

		SG_ERR_CHECK(  SG_dagnum__to_sz__hex(pCtx, iDagNum, buf_dagnum, sizeof(buf_dagnum))  );

		SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, "Building best guess dagfrag for dag %s...\n", buf_dagnum)  );
		SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, "Starting from nodes:\n")  );
		SG_ERR_CHECK(  SG_rbtree_debug__dump_keys_to_console(pCtx, prbStartFromHids)  );
		SG_ERR_CHECK(  SG_vhash_debug__dump_to_console__named(pCtx, pvhConnectToHidsAndGens, "Connecting to nodes")  );

		SG_ERR_CHECK(  SG_rbtree__count(pCtx, prbStartFromHids, &count)  );

		SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, "result has %d generations from %u starting nodes.\n",
			gensToFetch, count)  );
		SG_ERR_CHECK(  SG_console__flush(pCtx, SG_CS_STDERR)  );

		SG_ERR_CHECK(  SG_time__get_milliseconds_since_1970_utc(pCtx, &startTime)  );
	}
#endif

	/* Return a frag with the corresponding generations filled in. */
	SG_ERR_CHECK(  SG_repo__get_repo_id(pCtx, pRepo, &psz_repo_id)  );
	SG_ERR_CHECK(  SG_repo__get_admin_id(pCtx, pRepo, &psz_admin_id)  );
	SG_ERR_CHECK(  SG_dagfrag__alloc(pCtx, &pFrag, psz_repo_id, psz_admin_id, iDagNum)  );

	SG_ERR_CHECK(  SG_dagfrag__load_from_repo__multi(pCtx, pFrag, pRepo, prbStartFromHids, gensToFetch)  );
#if TRACE_SYNC
	SG_ERR_CHECK(  SG_time__get_milliseconds_since_1970_utc(pCtx, &endTime)  );
	{
		SG_uint32 dagnodeCount;
		double seconds = ((double)endTime-(double)startTime)/1000;

		SG_ERR_CHECK(  SG_dagfrag__dagnode_count(pCtx, pFrag, &dagnodeCount)  );
		SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, " - %u nodes in frag, built in %1.3f seconds\n", dagnodeCount, seconds)  );
		SG_ERR_CHECK(  SG_dagfrag_debug__dump__console(pCtx, pFrag, "best-guess dagfrag", 0, SG_CS_STDERR)  );
	}
#endif

	*ppFrag = pFrag;
	pFrag = NULL;

	/* Common cleanup */
fail:
	SG_NULLFREE(pCtx, psz_repo_id);
	SG_NULLFREE(pCtx, psz_admin_id);
	SG_RBTREE_ITERATOR_NULLFREE(pCtx, pit);
	SG_DAGNODE_NULLFREE(pCtx, pdn);
	SG_DAGFRAG_NULLFREE(pCtx, pFrag);
	SG_ERR_IGNORE(  SG_repo__fetch_dagnodes__end(pCtx, pRepo, &pdh)  );
}