void SG_curl__set_headers_from_varray(SG_context * pCtx, SG_curl * pCurl, SG_varray * pvaHeaders, struct curl_slist ** ppHeaderList)
{
	CURLcode rc = CURLE_OK;
    _sg_curl* p = (_sg_curl*)pCurl;
	struct curl_slist* pHeaderList = NULL;
	SG_uint32 count = 0;
	SG_uint32 i = 0;
	
	SG_NULLARGCHECK_RETURN(pCurl);
	SG_NULLARGCHECK_RETURN(pvaHeaders);
	
	SG_ERR_CHECK( SG_varray__count(pCtx, pvaHeaders, &count) );
	for (i = 0; i < count; i++)
	{
		const char * psz = NULL;
		SG_ERR_CHECK_RETURN( SG_varray__get__sz(pCtx, pvaHeaders, i, &psz) );
		pHeaderList = curl_slist_append(pHeaderList, psz);
		if (!pHeaderList)
			SG_ERR_THROW2_RETURN(SG_ERR_UNSPECIFIED, (pCtx, "Failed to add HTTP header."));
		
	}
	
	rc = curl_easy_setopt(p->pCurl, CURLOPT_HTTPHEADER, pHeaderList);
	if (rc)
		SG_ERR_THROW2(SG_ERR_LIBCURL(rc), (pCtx, "Problem setting HTTP headers" ));

	SG_RETURN_AND_NULL(pHeaderList, ppHeaderList);
	
fail:
	if (pHeaderList)
		SG_CURL_HEADERS_NULLFREE(pCtx, pHeaderList);

}
Ejemplo n.º 2
0
void SG_vector_i64__alloc__from_varray(SG_context* pCtx, SG_varray* pva, SG_vector_i64 ** ppVector)
{
    SG_uint32 count = 0;
    SG_uint32 i = 0;
    SG_vector_i64* pvec = NULL;

    SG_ERR_CHECK(  SG_varray__count(pCtx, pva, &count)  );
    SG_ERR_CHECK(  SG_vector_i64__alloc(pCtx, &pvec, count)  );
    for (i=0; i<count; i++)
    {
        SG_int64 v = 0;

        SG_ERR_CHECK(  SG_varray__get__int64(pCtx, pva, i, &v)  );
        SG_ERR_CHECK(  SG_vector_i64__append(pCtx, pvec, v, NULL)  );
    }

    *ppVector = pvec;
    pvec = NULL;

    return;

fail:
    SG_VECTOR_I64_NULLFREE(pCtx, pvec);

    return;
}
Ejemplo n.º 3
0
/**
 * Given an issue that has a file-content conflict and has just been resolved,
 * go thru all of the steps in the merge plan and delete the ~mine files.
 */
static void _resolve__delete_temp_files(SG_context * pCtx,
										struct _resolve_data * pData,
										const char * pszGid,
										const SG_vhash * pvhIssue)
{
	_resolve__step_pathnames * pStepPathnames = NULL;
	const SG_varray * pvaPlan;
	SG_uint32 kStep, nrSteps;

	SG_UNUSED( pszGid );

	SG_ERR_CHECK(  SG_vhash__get__varray(pCtx, pvhIssue, "conflict_file_merge_plan", (SG_varray **)&pvaPlan)  );
	SG_ERR_CHECK(  SG_varray__count(pCtx, pvaPlan, &nrSteps)  );

	for (kStep=0; kStep<nrSteps; kStep++)
	{
		const SG_vhash * pvhStep;

		SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pvaPlan, kStep, (SG_vhash **)&pvhStep)  );

		SG_ERR_CHECK(  _resolve__step_pathnames__compute(pCtx, pData, pvhIssue, pvhStep, NULL, &pStepPathnames)  );
		SG_ERR_CHECK(  _resolve__step_pathnames__delete_temp_files(pCtx, pStepPathnames)  );
		_RESOLVE__STEP_PATHNAMES__NULLFREE(pCtx, pStepPathnames);
	}

fail:
	_RESOLVE__STEP_PATHNAMES__NULLFREE(pCtx, pStepPathnames);
}
void sg_wc__status__find_status__single_item(SG_context * pCtx,
									  const SG_varray * pva_statuses,
									  const char * psz_repo_path, //This must be the repo path, exactly as it appears in the status results.
									  SG_vhash ** ppvh_status_result)
{
	SG_vhash * pvh_status = NULL;
	SG_uint32 nArraySize = 0;
	SG_uint32 index = 0;
	const char * psz_repo_path_to_check = NULL;
	SG_bool bFound = SG_FALSE;
	SG_NULLARGCHECK(pva_statuses);

	SG_ERR_CHECK(  SG_varray__count(pCtx, pva_statuses, &nArraySize)  );

	for (index = 0; index < nArraySize; index++)
	{
		SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pva_statuses, index, &pvh_status)  );
		SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh_status, "path", &psz_repo_path_to_check)  );
		if (SG_strcmp__null(psz_repo_path, psz_repo_path_to_check) == 0)
		{
			bFound = SG_TRUE;
			break;
		}
	}
	if (bFound)
		SG_RETURN_AND_NULL(pvh_status, ppvh_status_result);
fail:
	return;
}
static void _user_match_found(SG_context * pCtx, SG_repo * pRepo, _node_t *pNode1, _node_t * pNode2, SG_bool *matchFound)
{
	SG_uint32 countAudits1 = 0;
	SG_uint32 countAudits2 = 0;
	SG_uint32 iAudit1;

	if(pNode1->pAudits==NULL)
	{
		SG_ERR_CHECK(  SG_repo__lookup_audits(pCtx, pRepo, SG_DAGNUM__VERSION_CONTROL, pNode1->pszHidRef, &pNode1->pAudits)  );
	}
	if(pNode2->pAudits==NULL)
	{
		SG_ERR_CHECK(  SG_repo__lookup_audits(pCtx, pRepo, SG_DAGNUM__VERSION_CONTROL, pNode2->pszHidRef, &pNode2->pAudits)  );
	}


	SG_ERR_CHECK(  SG_varray__count(pCtx, pNode1->pAudits, &countAudits1)  );
	SG_ERR_CHECK(  SG_varray__count(pCtx, pNode2->pAudits, &countAudits2)  );
	for(iAudit1=0; iAudit1<countAudits1; ++iAudit1)
	{
		SG_vhash * pAuditRef = NULL;
		SG_uint32 iAudit2;

		const char * szAudit1 = NULL;
		SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pNode1->pAudits, iAudit1, &pAuditRef)  );
		SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pAuditRef, "userid", &szAudit1)  );

		for(iAudit2=0; iAudit2<countAudits2; ++iAudit2)
		{
			const char * szAudit2 = NULL;
			SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pNode2->pAudits, iAudit2, &pAuditRef)  );
			SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pAuditRef, "userid", &szAudit2)  );
			if(strcmp(szAudit1, szAudit2)==0)
			{
				*matchFound = SG_TRUE;
				return;
			}
		}
	}

	*matchFound = SG_FALSE;

	return;
fail:
	;
}
/**
 * Take a domain-specific/relative repo-path and get
 * the GID ALIAS of the item.
 *
 * THIS IS STRICLTY BASED UPON THE FIXED CHANGESET
 * THAT WE ALREADY HAVE IN THE tne_* TABLE.  It does
 * not know about or account for any pending changes
 * in the WD; that is, it DOES NOT know about tbl_PC.
 *
 * We DO NOT know if the given domain is appropriate
 * for the given pCSetRow.  That is upto the caller.
 * For example, we expect them to map:
 *     'b' ==> "L0"
 *     'c' ==> "L1"
 * but we don't enforce that here.
 *
 */
void sg_wc_db__tne__get_alias_from_extended_repo_path(SG_context * pCtx,
													  sg_wc_db * pDb,
													  const sg_wc_db__cset_row * pCSetRow,
													  const char * pszBaselineRepoPath,
													  SG_bool * pbFound,
													  SG_uint64 * puiAliasGid)
{
	SG_varray * pva = NULL;
	sg_wc_db__tne_row * pTneRow_k = NULL;
	SG_uint64 uiAlias;
	SG_uint32 k, count;

	*pbFound = SG_FALSE;
	*puiAliasGid = 0;

	// TODO 2012/01/04 For now, require that an extended-prefix be
	// TODO            present in the repo-path.
	// TODO
	// TODO            We may relax this to allow a '/' current/live
	// TODO            domain repo-path eventually.

	SG_ASSERT_RELEASE_FAIL( ((pszBaselineRepoPath[0]=='@')
							 && (pszBaselineRepoPath[1]!='/')) );

	SG_ERR_CHECK(  SG_repopath__split_into_varray(pCtx, pszBaselineRepoPath, &pva)  );

	// the root directory should be "@b" and be contained in pva[0].
	// we have a direct line to its alias.
	SG_ERR_CHECK(  sg_wc_db__tne__get_alias_of_root(pCtx, pDb, pCSetRow, &uiAlias)  );
	
	SG_ERR_CHECK(  SG_varray__count(pCtx, pva, &count)  );
	for (k=1; k<count; k++)
	{
		const char * pszEntryname_k;
		SG_bool bFound_k;

		SG_ERR_CHECK(  SG_varray__get__sz(pCtx, pva, k, &pszEntryname_k)  );
		SG_ERR_CHECK(  sg_wc_db__tne__get_row_by_parent_alias_and_entryname(pCtx, pDb, pCSetRow,
																			uiAlias, pszEntryname_k,
																			&bFound_k, &pTneRow_k)  );
		if (!bFound_k)
			goto fail;

		uiAlias = pTneRow_k->p_s->uiAliasGid;

		SG_WC_DB__TNE_ROW__NULLFREE(pCtx, pTneRow_k);
	}

	*pbFound = SG_TRUE;
	*puiAliasGid = uiAlias;

fail:
	SG_VARRAY_NULLFREE(pCtx, pva);
	SG_WC_DB__TNE_ROW__NULLFREE(pCtx, pTneRow_k);
}
Ejemplo n.º 7
0
void SG_localsettings__varray__remove_first_match(SG_context * pCtx, const char* psz_path, const char* psz_val)
{
	SG_jsondb* p = NULL;
    SG_string* pstr_path_element = NULL;
    SG_varray* pva = NULL;
    SG_uint32 ndx = 0;
    SG_uint32 count = 0;
    SG_uint32 i = 0;
    SG_bool b_found = SG_FALSE;
    SG_string* pstr_path_found = NULL;

	SG_ASSERT(pCtx);
	SG_NONEMPTYCHECK_RETURN(psz_path);

    SG_ERR_CHECK(  SG_closet__get_localsettings(pCtx, &p)  );
    SG_ERR_CHECK(  SG_localsettings__get__varray(pCtx, psz_path, NULL, &pva, &pstr_path_found)  );
    if (pva)
    {
        if (!pstr_path_found)
        {
            // this came from factory defaults.
            SG_ERR_CHECK(  SG_string__alloc(pCtx, &pstr_path_found)  );
            SG_ERR_CHECK(  SG_string__sprintf(pCtx, pstr_path_found, "%s/%s", SG_LOCALSETTING__SCOPE__MACHINE, psz_path)  );
            SG_ERR_CHECK(  SG_localsettings__update__varray(pCtx, SG_string__sz(pstr_path_found), pva)  );
        }

        SG_ERR_CHECK(  SG_varray__count(pCtx, pva, &count)  );
        for (i=0; i<count; i++)
        {
            const char* psz = NULL;

            SG_ERR_CHECK(  SG_varray__get__sz(pCtx, pva, i, &psz)  );
            if (0 == strcmp(psz, psz_val))
            {
                b_found = SG_TRUE;
                ndx = i;
                break;
            }
        }
        if (b_found)
        {
            SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pstr_path_element)  );
            SG_ERR_CHECK(  SG_string__sprintf(pCtx, pstr_path_element, "%s/%d", SG_string__sz(pstr_path_found), ndx)  );
            SG_ERR_CHECK(  SG_jsondb__remove(pCtx, p, SG_string__sz(pstr_path_element))  );
        }
    }

fail:
    SG_VARRAY_NULLFREE(pCtx, pva);
    SG_STRING_NULLFREE(pCtx, pstr_path_found);
    SG_STRING_NULLFREE(pCtx, pstr_path_element);
	SG_JSONDB_NULLFREE(pCtx, p);
}
Ejemplo n.º 8
0
/**
 * build an ordered stringarray of the GIDs of all of the issues.
 * i need to have the list of GIDs be independent of pPendingTree
 * and pvaIssues so that FIX can do incremental SAVES of the
 * pendingtree (which trashes the ptnodes and/or requires a reload).
 */
static void _resolve__get_all_issue_gids(SG_context * pCtx,
										 struct _resolve_data * pData,
										 SG_bool bWantResolved,
										 SG_bool bWantUnresolved)
{
	const SG_varray * pvaIssues;			// varray[pvhIssue *] of all issues -- we do not own this
	SG_bool bHaveIssues;
	SG_uint32 k;
	SG_uint32 nrIssues = 0;
	SG_bool bWantBoth = (bWantResolved && bWantUnresolved);

	SG_ERR_CHECK(  SG_pendingtree__get_wd_issues__ref(pCtx, pData->pPendingTree, &bHaveIssues, &pvaIssues)  );
	if (bHaveIssues)
		SG_ERR_CHECK(  SG_varray__count(pCtx, pvaIssues, &nrIssues)  );

	SG_ERR_CHECK(  SG_STRINGARRAY__ALLOC(pCtx, &pData->psaGids, nrIssues)  );

	for (k=0; k<nrIssues; k++)
	{
		const SG_vhash * pvhIssue_k;
		const char * pszGid_k;
		SG_bool bWantThisOne;

		SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pvaIssues, k, (SG_vhash **)&pvhIssue_k)  );

		if (bWantBoth)
			bWantThisOne = SG_TRUE;
		else
		{
			SG_int64 s;
			SG_pendingtree_wd_issue_status status;
			SG_bool bResolved;

			SG_ERR_CHECK_RETURN(  SG_vhash__get__int64(pCtx, pvhIssue_k, "status", &s)  );
			status = (SG_pendingtree_wd_issue_status)s;
			bResolved = ((status & SG_ISSUE_STATUS__MARKED_RESOLVED) == SG_ISSUE_STATUS__MARKED_RESOLVED);

			bWantThisOne = ((bWantResolved && bResolved) || (bWantUnresolved && !bResolved));
		}

		if (bWantThisOne)
		{
			SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvhIssue_k, "gid", &pszGid_k)  );
			SG_ERR_CHECK(  SG_stringarray__add(pCtx, pData->psaGids, pszGid_k)  );
		}
	}

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

    *ppvaDirtyFiles = NULL;

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

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

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

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

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

    SG_RETURN_AND_NULL( pvaDirtyFiles, ppvaDirtyFiles );

fail:
    SG_VARRAY_NULLFREE(pCtx, pvaDirtyFiles);
}
static void old_SG_db__make_delta_from_path(
    SG_context* pCtx,
    SG_repo* pRepo,
    SG_uint64 dagnum,
    SG_varray* pva_path,
    SG_vhash* pvh_add,
    SG_vhash* pvh_remove
    )
{
    SG_uint32 count = 0;
    SG_uint32 i_path_step = 0;
    SG_uint32 count_ops = 0;

    SG_UNUSED(dagnum);
    SG_NULLARGCHECK_RETURN(pRepo);

    SG_ERR_CHECK(  SG_varray__count(pCtx, pva_path, &count)  );
    SG_ASSERT(count >= 2);

    SG_ERR_CHECK(  SG_log__push_operation(pCtx, "Collecting deltas", SG_LOG__FLAG__NONE)  );
    count_ops++;

    SG_ERR_CHECK(  SG_log__set_steps(pCtx, count-1, "changesets")  );

    for (i_path_step=0; i_path_step<(count-1); i_path_step++)
    {
        SG_ERR_CHECK(  loop_innards_make_delta(pCtx, pRepo, pva_path, i_path_step, pvh_add, pvh_remove)  );
        SG_ERR_CHECK(  SG_log__finish_step(pCtx)  );
    }

    SG_ERR_CHECK(  SG_log__pop_operation(pCtx)  );
    count_ops--;

fail:
    while (count_ops)
    {
        SG_ERR_IGNORE(  SG_log__pop_operation(pCtx)  );
        count_ops--;
    }
}
/**
 * Compute DIFF on (baseline or arbitrary cset vs WC) and either
 * splat to console or launch a GUI tool for each.
 *
 */
static void _s01__do_cset_vs_wc(SG_context * pCtx,
                                const SG_option_state * pOptSt,
                                const SG_stringarray * psaArgs,
                                SG_uint32 * pNrErrors)
{
    SG_wc_tx * pWcTx = NULL;
    SG_varray * pvaDiffSteps = NULL;
    SG_varray * pvaDiffSteps_DirtyFiles = NULL;
    SG_pathname * pPathWc = NULL;
    SG_uint32 nrErrors = 0;

    SG_ERR_CHECK(  SG_WC_TX__ALLOC__BEGIN(pCtx, &pWcTx, pPathWc, SG_TRUE)  );
    SG_ERR_CHECK(  SG_wc_tx__diff__setup__stringarray(pCtx, pWcTx, pOptSt->pRevSpec,
                   psaArgs,
                   WC__GET_DEPTH(pOptSt),
                   SG_FALSE, // bNoIgnores
                   SG_FALSE,	// bNoTSC
                   SG_FALSE,	// bNoSort,
                   pOptSt->bInteractive,
                   pOptSt->psz_tool,
                   &pvaDiffSteps)  );
    // rollback/cancel the TX to release the SQL locks,
    // but don't free it yet (because that will auto-delete
    // the session temp files that we are using for the left
    // sides).
    //
    // This is like SG_wc__diff__throw() but we want to control
    // the diff-loop so we can optionally do the interactive prompt.

    SG_ERR_CHECK(  SG_wc_tx__cancel(pCtx, pWcTx)  );

    if (pvaDiffSteps)
    {
        if (pOptSt->bInteractive)
        {
            SG_ERR_CHECK(  _get_dirty_files(pCtx, pvaDiffSteps, &pvaDiffSteps_DirtyFiles)  );
            if (pvaDiffSteps_DirtyFiles)
                SG_ERR_CHECK(  _do_gui_diffs(pCtx, SG_TRUE, pOptSt, pvaDiffSteps_DirtyFiles, &nrErrors)  );
        }
        else
        {
            SG_uint32 k, nrItems;

            SG_ERR_CHECK(  SG_varray__count(pCtx, pvaDiffSteps, &nrItems)  );
            for (k=0; k<nrItems; k++)
            {
                SG_vhash * pvhItem;
                const char * pszHeader = NULL;
                SG_uint32 iResult;

                SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pvaDiffSteps, k, &pvhItem)  );
                SG_ERR_CHECK(  SG_vhash__check__sz(pCtx, pvhItem, "header", &pszHeader)  );
                if (pszHeader)
                    SG_ERR_IGNORE(  SG_console__raw(pCtx, SG_CS_STDOUT, pszHeader)  );
                SG_ERR_CHECK(  _do_diff1(pCtx, SG_TRUE, pOptSt, pvhItem, &iResult)  );

                switch (iResult)
                {
                default:
                case SG_FILETOOL__RESULT__SUCCESS:
                case SG_DIFFTOOL__RESULT__SAME:
                case SG_DIFFTOOL__RESULT__DIFFERENT:
                    break;

                case SG_DIFFTOOL__RESULT__CANCEL:
                case SG_FILETOOL__RESULT__FAILURE:
                case SG_FILETOOL__RESULT__ERROR:
                    nrErrors++;
                    break;
                }
            }
        }
    }

    *pNrErrors = nrErrors;

fail:
    SG_VARRAY_NULLFREE(pCtx, pvaDiffSteps);
    SG_VARRAY_NULLFREE(pCtx, pvaDiffSteps_DirtyFiles);
    SG_WC_TX__NULLFREE(pCtx, pWcTx);
}
static void _s2__do_cset_vs_cset(SG_context * pCtx,
                                 const SG_option_state * pOptSt,
                                 const SG_stringarray * psaArgs,
                                 SG_uint32 * pNrErrors)
{
    SG_varray * pvaStatus = NULL;
    SG_varray * pvaStatusDirtyFiles = NULL;
    SG_stringarray * psa1 = NULL;
    SG_string * pStringGidRepoPath = NULL;
    SG_string * pStringErr = NULL;
    SG_uint32 nrErrors = 0;

    SG_ERR_CHECK(  SG_vv2__status(pCtx,
                                  pOptSt->psz_repo,
                                  pOptSt->pRevSpec,
                                  psaArgs,
                                  WC__GET_DEPTH(pOptSt),
                                  SG_FALSE, // bNoSort
                                  &pvaStatus, NULL)  );
    if (pvaStatus)
    {
        if (pOptSt->bInteractive)
        {
            // Filter list down to just modified files and show them one-by-one.
            SG_ERR_CHECK(  _get_dirty_files(pCtx, pvaStatus, &pvaStatusDirtyFiles)  );
            if (pvaStatusDirtyFiles)
                SG_ERR_CHECK(  _do_gui_diffs(pCtx, SG_FALSE, pOptSt, pvaStatusDirtyFiles, &nrErrors)  );
        }
        else
        {
            SG_uint32 k, nrItems;

            // Print the changes with PATCH-like headers.
            // Accumulate any tool errors.
            SG_ERR_CHECK(  SG_varray__count(pCtx, pvaStatus, &nrItems)  );
            for (k=0; k<nrItems; k++)
            {
                SG_vhash * pvhItem;
                const char * pszGid = NULL;

                SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pvaStatus, k, &pvhItem)  );

                // TODO 2013/02/22 Our pvhItem has all of the details for the diff,
                // TODO            but we don't yet have a public API to let it be
                // TODO            used as is.  So we build a @gid repo-path and
                // TODO            run the old historical diff code on a 1-item array
                // TODO            containing this @gid.
                // TODO
                // TODO            We should fix this to just pass down the pvhItem
                // TOOD            so that it doesn't have to repeat the status lookup.

                SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvhItem, "gid", &pszGid)  );
                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))  );

                SG_vv2__diff_to_stream__throw(pCtx, pOptSt->psz_repo, pOptSt->pRevSpec,
                                              psa1, 0,
                                              SG_TRUE,		// bNoSort -- doesn't matter only 1 item in list
                                              SG_FALSE,		// bInteractive,
                                              pOptSt->psz_tool);
                // Don't throw the error from the tool.  Just print it on STDERR
                // and remember that we had an error so that don't stop showing
                // the diffs just because we stumble over a changed binary file
                // or mis-configured tool, for example.
                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))  );
                    SG_STRING_NULLFREE(pCtx, pStringErr);

                    nrErrors++;
                }

                SG_STRING_NULLFREE(pCtx, pStringGidRepoPath);
                SG_STRINGARRAY_NULLFREE(pCtx, psa1);
            }
        }
    }

    *pNrErrors = nrErrors;

fail:
    SG_VARRAY_NULLFREE(pCtx, pvaStatus);
    SG_VARRAY_NULLFREE(pCtx, pvaStatusDirtyFiles);
    SG_STRINGARRAY_NULLFREE(pCtx, psa1);
    SG_STRING_NULLFREE(pCtx, pStringGidRepoPath);
    SG_STRING_NULLFREE(pCtx, pStringErr);
}
/**
 * Iterate over all of the dirty files and prompt before diffing.
 * pvaDirtyFiles can either be a STATUS or a "DiffStep" varray.
 *
 */
static void _do_gui_diffs(SG_context * pCtx,
                          SG_bool bWC,
                          const SG_option_state * pOptSt,
                          const SG_varray * pvaDirtyFiles,
                          SG_uint32 * pNrErrors)
{
    SG_uint32 k, nrItems;
    SG_uint32 nrErrors = 0;

    SG_ERR_CHECK(  SG_varray__count(pCtx, pvaDirtyFiles, &nrItems)  );
    if (nrItems == 1)	// if only 1 item, no fancy prompt required.
    {
        SG_vhash * pvhItem_0;			// we do not own this
        SG_uint32 iResult = 0;
        SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pvaDirtyFiles, 0, &pvhItem_0)  );
        SG_ERR_CHECK(  _do_diff1(pCtx, bWC, pOptSt, pvhItem_0, &iResult)  );

        switch (iResult)
        {
        default:
        case SG_FILETOOL__RESULT__SUCCESS:
        case SG_DIFFTOOL__RESULT__SAME:
        case SG_DIFFTOOL__RESULT__DIFFERENT:
            break;

        case SG_DIFFTOOL__RESULT__CANCEL:
        case SG_FILETOOL__RESULT__FAILURE:
        case SG_FILETOOL__RESULT__ERROR:
            nrErrors++;
            break;
        }
    }
    else
    {
        k = 0;
        while (1)
        {
            SG_vhash * pvhItem;			// we do not own this
            const char * pszRepoPath;
            char chChoice = 'd';
            SG_uint32 iResult;

            SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pvaDirtyFiles, k, &pvhItem)  );
            SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvhItem, "path", &pszRepoPath)  );

            SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDOUT, "\n[%d/%d] %s:\n", k+1, nrItems, pszRepoPath)  );
            if (k == 0)
                SG_ERR_CHECK(  _do_prompt(pCtx, "(d)iff (n)ext (q)uit", "dnq", 'd', &chChoice)  );
            else if (k+1 == nrItems)
                SG_ERR_CHECK(  _do_prompt(pCtx, "(d)iff (p)rev (q)uit", "dpq", 'd', &chChoice)  );
            else
                SG_ERR_CHECK(  _do_prompt(pCtx, "(d)iff (n)ext (p)rev (q)uit", "dnpq", 'd', &chChoice)  );

            switch (chChoice)
            {
            case 'd':
                SG_ERR_CHECK(  _do_diff1(pCtx, bWC, pOptSt, pvhItem, &iResult)  );
                switch (iResult)
                {
                default:
                case SG_FILETOOL__RESULT__SUCCESS:
                case SG_DIFFTOOL__RESULT__SAME:
                case SG_DIFFTOOL__RESULT__DIFFERENT:
                    // advance to next pair of files or finish.
                    if (k+1 == nrItems)
                        goto done;
                    k++;
                    break;

                case SG_DIFFTOOL__RESULT__CANCEL:
                case SG_FILETOOL__RESULT__FAILURE:
                case SG_FILETOOL__RESULT__ERROR:
                    nrErrors++;
                    // stay on this pair of files (so that they see the
                    // error message and this filename again).
                    break;
                }
                break;

            case 'n':
                k++;
                break;

            case 'p':
                k--;
                break;

            default:
            case 'q':
                goto done;
            }
        }
    }

done:
    *pNrErrors = nrErrors;

fail:
    return;
}
Ejemplo n.º 14
0
void SG_tag__add_tags(SG_context * pCtx, SG_repo * pRepo, SG_pendingtree * pPendingTree, const char* psz_spec_cs, SG_bool bRev, SG_bool bForce, const char** ppszTags, SG_uint32 count_args)
{
	SG_pathname* pPathCwd = NULL;
	char* psz_hid_cs = NULL;
	SG_audit q;
	SG_uint32 i = 0;
	char * psz_current_hid_with_that_tag = NULL;
	SG_bool bFreePendingTree = SG_FALSE;

	SG_ERR_CHECK(  SG_audit__init(pCtx,&q,pRepo,SG_AUDIT__WHEN__NOW,SG_AUDIT__WHO__FROM_SETTINGS)  );

	// TODO 4/21/10 pendingtree contains a pRepo inside it.  we should
	// TODO 4/21/10 refactor this to alloc the pendingtree first and then
	// TODO 4/21/10 just borrow the pRepo from it.



	if (psz_spec_cs)
	{
		if (bRev)
		{
			SG_ERR_CHECK(  SG_repo__hidlookup__dagnode(pCtx, pRepo, SG_DAGNUM__VERSION_CONTROL, psz_spec_cs, &psz_hid_cs)  );
		}
		else
		{
			SG_ERR_CHECK(  SG_vc_tags__lookup__tag(pCtx, pRepo, psz_spec_cs, &psz_hid_cs)  );
			if (psz_hid_cs == NULL)
				SG_ERR_THROW(SG_ERR_TAG_NOT_FOUND);
		}
	}
	else
	{
		// tag the current baseline.
		//
		// when we have an uncomitted merge, we will have more than one parent.
		// what does this command mean then?  It feels like we we should throw
		// an error and say that you have to commit first.

		const SG_varray * pva_wd_parents;		// we do not own this
		const char * psz_hid_parent_0;			// we do not own this
		SG_uint32 nrParents;

		if (pPendingTree == NULL)
		{

			SG_ERR_CHECK(  SG_pendingtree__alloc_from_cwd(pCtx, SG_TRUE, &pPendingTree)  );
			bFreePendingTree = SG_TRUE;
		}
		SG_ERR_CHECK(  SG_pendingtree__get_wd_parents__ref(pCtx, pPendingTree, &pva_wd_parents)  );
		SG_ERR_CHECK(  SG_varray__count(pCtx, pva_wd_parents, &nrParents)  );
		if (nrParents > 1)
			SG_ERR_THROW(  SG_ERR_CANNOT_DO_WHILE_UNCOMMITTED_MERGE  );

		SG_ERR_CHECK(  SG_varray__get__sz(pCtx, pva_wd_parents, 0, &psz_hid_parent_0)  );
		SG_ERR_CHECK(  SG_strdup(pCtx, psz_hid_parent_0, &psz_hid_cs)  );
	}

	if (!bForce)
	{
		//Go through and check all tags to make sure that they are not already applied.
		for (i = 0; i < count_args; i++)
		{
			const char * pszTag = ppszTags[i];
			SG_ERR_IGNORE(  SG_vc_tags__lookup__tag(pCtx, pRepo, pszTag, &psz_current_hid_with_that_tag)  );
			if (psz_current_hid_with_that_tag != NULL && 0 != strcmp(psz_current_hid_with_that_tag, psz_hid_cs)) //The tag has been applied, but not to the given changeset.
				SG_ERR_THROW(SG_ERR_TAG_ALREADY_EXISTS);
			SG_NULLFREE(pCtx, psz_current_hid_with_that_tag);
		}
	}
	for (i = 0; i < count_args; i++)
	{
		const char * pszTag = ppszTags[i];
		SG_ERR_CHECK(  SG_vc_tags__lookup__tag(pCtx, pRepo, pszTag, &psz_current_hid_with_that_tag)  );
		if (psz_current_hid_with_that_tag == NULL || 0 != strcmp(psz_current_hid_with_that_tag, psz_hid_cs))
		{
			//The tag has not been applied, or it's been applied to a different dagnode.
			if ( psz_current_hid_with_that_tag != NULL && bForce)  //Remove it, if it's already there
					SG_ERR_CHECK(  SG_vc_tags__remove(pCtx, pRepo, &q, 1, &pszTag)  );
			SG_ERR_CHECK(  SG_vc_tags__add(pCtx, pRepo, psz_hid_cs, pszTag, &q)  );
		}
		SG_NULLFREE(pCtx, psz_current_hid_with_that_tag);
	}

fail:
	SG_NULLFREE(pCtx, psz_current_hid_with_that_tag);
	if (bFreePendingTree == SG_TRUE)
		SG_PENDINGTREE_NULLFREE(pCtx, pPendingTree);
	SG_NULLFREE(pCtx, psz_hid_cs);
	SG_PATHNAME_NULLFREE(pCtx, pPathCwd);
}
// Helper function for _tree__init__continuation()
static void _tree__add_subtoken(SG_context * pCtx, _tree_t * pTree, _node_t * pParent, SG_varray * pSubtoken)
{
	SG_int64 revno = 0;
	char * szHid = NULL;
	_node_t * pNodeRef = NULL;
	SG_uint32 count = 0;
	SG_uint32 i = 0;
	SG_uint16 lastElementType = 0;
	SG_varray * pSubsubtoken = NULL;
	SG_stringarray * psa = NULL;

	++pTree->indentLevel;

	SG_ERR_CHECK(  SG_varray__get__int64(pCtx, pSubtoken, 0, &revno)  );
	SG_ERR_CHECK(  SG_repo__find_dagnode_by_rev_id(pCtx, pTree->pRepoRef, SG_DAGNUM__VERSION_CONTROL, (SG_uint32)revno, &szHid)  );
	SG_ERR_CHECK(  _tree__add_new_node(pCtx, pTree, pParent, szHid, &pNodeRef)  );
	pNodeRef->isPending = SG_FALSE;
	SG_NULLFREE(pCtx, szHid);

	SG_ERR_CHECK(  SG_varray__count(pCtx, pSubtoken, &count)  );
	SG_ERR_CHECK(  SG_varray__typeof(pCtx, pSubtoken, count-1, &lastElementType)  );
	if(lastElementType==SG_VARIANT_TYPE_VARRAY)
	{
		SG_ERR_CHECK(  SG_varray__get__varray(pCtx, pSubtoken, count-1, &pSubsubtoken)  );
		--count;
	}

	for(i=1; i<count; ++i)
	{
		_node_t * pRefChildNode = NULL;
		SG_ERR_CHECK(  SG_varray__get__int64(pCtx, pSubtoken, i, &revno)  );
		if(i==1 && revno==0)
		{
			// Revno 0 in the as the first child means "Use the LCA for the baseline".
			SG_uint32 j = 0;

			if(pSubsubtoken!=NULL)
			{
				// LCA would not include nodes in the sub-token. Just error out.
				SG_ERR_THROW(SG_ERR_INVALIDARG);
			}

			if(count-2<2)
			{
				// This would be interpreted as merging a node with itself or with nothing.
				SG_ERR_THROW(SG_ERR_INVALIDARG);
			}

			SG_ERR_CHECK(  SG_STRINGARRAY__ALLOC(pCtx, &psa, count-2)  );
			for(j=2; j<count; ++j)
			{
				SG_ERR_CHECK(  SG_varray__get__int64(pCtx, pSubtoken, j, &revno)  );
				SG_ERR_CHECK(  SG_repo__find_dagnode_by_rev_id(pCtx, pTree->pRepoRef, SG_DAGNUM__VERSION_CONTROL, (SG_uint32)revno, &szHid)  );
				SG_ERR_CHECK(  SG_stringarray__add(pCtx, psa, szHid)  );
				SG_NULLFREE(pCtx, szHid);
			}
			SG_ERR_CHECK(  SG_dagquery__highest_revno_common_ancestor(pCtx, pTree->pRepoRef, SG_DAGNUM__VERSION_CONTROL, psa, &szHid)  );
			SG_STRINGARRAY_NULLFREE(pCtx, psa);
		}
		else
		{
			SG_ERR_CHECK(  SG_repo__find_dagnode_by_rev_id(pCtx, pTree->pRepoRef, SG_DAGNUM__VERSION_CONTROL, (SG_uint32)revno, &szHid)  );
		}
		SG_ERR_CHECK(  _tree__add_new_node(pCtx, pTree, pNodeRef, szHid, &pRefChildNode)  );
		pTree->pNextResult = pRefChildNode;
		SG_ERR_CHECK(  _node_list__append(pCtx, &pTree->pending, &pRefChildNode)  );
		SG_NULLFREE(pCtx, szHid);
	}

	if(pSubsubtoken!=NULL)
	{
		SG_ERR_CHECK(  _tree__add_subtoken(pCtx, pTree, pNodeRef, pSubsubtoken)  );
	}

	return;
fail:
	SG_NULLFREE(pCtx, szHid);
	SG_STRINGARRAY_NULLFREE(pCtx, psa);
}
Ejemplo n.º 16
0
int u0050_logstuff_test__1(SG_context * pCtx, SG_pathname* pPathTopDir)
{
	char bufName[SG_TID_MAX_BUFFER_LENGTH];
	SG_pathname* pPathWorkingDir = NULL;
	SG_pathname* pPathFile = NULL;
	SG_vhash* pvh = NULL;
    SG_dagnode* pdn = NULL;
    const char* psz_hid_cs = NULL;
    SG_repo* pRepo = NULL;
    SG_uint32 count;
    SG_rbtree* prb = NULL;
    SG_varray* pva = NULL;
    SG_rbtree* prb_reversed = NULL;
    const char* psz_val = NULL;
    SG_audit q;

	VERIFY_ERR_CHECK(  SG_tid__generate2(pCtx, bufName, sizeof(bufName), 32)  );

	/* create the working dir */
	VERIFY_ERR_CHECK(  SG_PATHNAME__ALLOC__PATHNAME_SZ(pCtx, &pPathWorkingDir, pPathTopDir, bufName)  );
	VERIFY_ERR_CHECK(  SG_fsobj__mkdir__pathname(pCtx, pPathWorkingDir)  );

    /* add stuff */
	VERIFY_ERR_CHECK(  u0050_logstuff__create_file__numbers(pCtx, pPathWorkingDir, "aaa", 20)  );

    /* create the repo */
	VERIFY_ERR_CHECK(  _ut_pt__new_repo(pCtx, bufName, pPathWorkingDir)  );
	VERIFY_ERR_CHECK(  _ut_pt__addremove(pCtx, pPathWorkingDir)  );
	VERIFY_ERR_CHECK(  u0050_logstuff__commit_all(pCtx, pPathWorkingDir, &pdn)  );

    VERIFY_ERR_CHECK(  SG_dagnode__get_id_ref(pCtx, pdn, &psz_hid_cs)  );

	SG_ERR_CHECK(  SG_repo__open_repo_instance(pCtx, bufName, &pRepo)  );

#define MY_COMMENT "The name of this new file sucks!  What kind of a name is 'aaa'?"

    VERIFY_ERR_CHECK(  SG_audit__init(pCtx, &q, pRepo, SG_AUDIT__WHEN__NOW, SG_AUDIT__WHO__FROM_SETTINGS)  );
    VERIFY_ERR_CHECK(  SG_vc_comments__add(pCtx, pRepo, psz_hid_cs, MY_COMMENT, &q)  );
    VERIFY_ERR_CHECK(  SG_vc_stamps__add(pCtx, pRepo, psz_hid_cs, "crap", &q)  );
    VERIFY_ERR_CHECK(  SG_vc_tags__add(pCtx, pRepo, psz_hid_cs, "tcrap", &q)  );

    VERIFY_ERR_CHECK(  SG_vc_comments__lookup(pCtx, pRepo, psz_hid_cs, &pva)  );
    VERIFY_ERR_CHECK(  SG_varray__count(pCtx, pva, &count)  );
    VERIFY_COND("count", (1 == count));
    VERIFY_ERR_CHECK(  SG_varray__get__vhash(pCtx, pva, 0, &pvh)  );
    VERIFY_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh, "text", &psz_val)  );
    VERIFY_COND("match", (0 == strcmp(psz_val, MY_COMMENT))  );
    SG_VARRAY_NULLFREE(pCtx, pva);

    VERIFY_ERR_CHECK(  SG_vc_stamps__lookup(pCtx, pRepo, psz_hid_cs, &pva)  );
    VERIFY_ERR_CHECK(  SG_varray__count(pCtx, pva, &count)  );
    VERIFY_COND("count", (1 == count));
    VERIFY_ERR_CHECK(  SG_varray__get__vhash(pCtx, pva, 0, &pvh)  );
    VERIFY_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh, "stamp", &psz_val)  );
    VERIFY_COND("match", (0 == strcmp(psz_val, "crap"))  );
    SG_VARRAY_NULLFREE(pCtx, pva);

    VERIFY_ERR_CHECK(  SG_vc_tags__lookup(pCtx, pRepo, psz_hid_cs, &pva)  );
    VERIFY_ERR_CHECK(  SG_varray__count(pCtx, pva, &count)  );
    VERIFY_COND("count", (1 == count));
    VERIFY_ERR_CHECK(  SG_varray__get__vhash(pCtx, pva, 0, &pvh)  );
    VERIFY_ERR_CHECK(  SG_vhash__get__sz(pCtx, pvh, "tag", &psz_val)  );
    VERIFY_COND("match", (0 == strcmp(psz_val, "tcrap"))  );
    SG_VARRAY_NULLFREE(pCtx, pva);

    VERIFY_ERR_CHECK(  SG_vc_tags__list(pCtx, pRepo, &prb)  );
    VERIFY_ERR_CHECK(  SG_rbtree__count(pCtx, prb, &count)  );
    VERIFY_COND("count", (1 == count));
    VERIFY_ERR_CHECK(  SG_vc_tags__build_reverse_lookup(pCtx, prb, &prb_reversed)  );
    VERIFY_ERR_CHECK(  SG_rbtree__count(pCtx, prb_reversed, &count)  );
    VERIFY_COND("count", (1 == count));

    {
        const char* psz_my_key = NULL;
        const char* psz_my_val = NULL;
        SG_bool b;

        VERIFY_ERR_CHECK(  SG_rbtree__iterator__first(pCtx, NULL, prb_reversed, &b, &psz_my_key, (void**) &psz_my_val)  );
        VERIFY_COND("ok", (0 == strcmp(psz_my_val, "tcrap"))  );
        VERIFY_COND("ok", (0 == strcmp(psz_my_key, psz_hid_cs))  );
    }
    SG_RBTREE_NULLFREE(pCtx, prb_reversed);

    SG_RBTREE_NULLFREE(pCtx, prb);

    VERIFY_ERR_CHECK(  SG_vc_tags__add(pCtx, pRepo, psz_hid_cs, "whatever", &q)  );

    VERIFY_ERR_CHECK(  SG_vc_tags__lookup(pCtx, pRepo, psz_hid_cs, &pva)  );
    VERIFY_ERR_CHECK(  SG_varray__count(pCtx, pva, &count)  );
    VERIFY_COND("count", (2 == count));
    SG_VARRAY_NULLFREE(pCtx, pva);

    VERIFY_ERR_CHECK(  SG_vc_tags__list(pCtx, pRepo, &prb)  );
    VERIFY_ERR_CHECK(  SG_rbtree__count(pCtx, prb, &count)  );
    VERIFY_COND("count", (2 == count));

    VERIFY_ERR_CHECK(  SG_vc_tags__build_reverse_lookup(pCtx, prb, &prb_reversed)  );
    VERIFY_ERR_CHECK(  SG_rbtree__count(pCtx, prb_reversed, &count)  );
    VERIFY_COND("count", (1 == count));

    {
        const char* psz_my_key = NULL;
        const char* psz_my_val = NULL;
        SG_bool b;

        VERIFY_ERR_CHECK(  SG_rbtree__iterator__first(pCtx, NULL, prb_reversed, &b, &psz_my_key, (void**) &psz_my_val)  );
        VERIFY_COND("ok", (0 == strcmp(psz_my_key, psz_hid_cs))  );
        /* we don't know whether psz_my_val is tcrap or whatever. */
        // VERIFY_COND("ok", (0 == strcmp(psz_my_val, "tcrap"))  );
    }
    SG_RBTREE_NULLFREE(pCtx, prb_reversed);

    SG_RBTREE_NULLFREE(pCtx, prb);

    {
        const char* psz_remove = "whatever";

        VERIFY_ERR_CHECK(  SG_vc_tags__remove(pCtx, pRepo, &q, 1, &psz_remove)  );
        /* Note that by removing whatever, we are bringing the tags list back
         * to a state where it has been before (just tcrap).  This changeset in
         * the tags table will have its own csid, because the parentage is
         * different, but it's root idtrie HID will be the same as a previous
         * node. */
    }

    VERIFY_ERR_CHECK(  SG_vc_tags__lookup(pCtx, pRepo, psz_hid_cs, &pva)  );
    VERIFY_ERR_CHECK(  SG_varray__count(pCtx, pva, &count)  );
    VERIFY_COND("count", (1 == count));
    SG_VARRAY_NULLFREE(pCtx, pva);

    VERIFY_ERR_CHECK(  SG_vc_tags__list(pCtx, pRepo, &prb)  );
    VERIFY_ERR_CHECK(  SG_rbtree__count(pCtx, prb, &count)  );
    VERIFY_COND("count", (1 == count));
    SG_RBTREE_NULLFREE(pCtx, prb);

    SG_REPO_NULLFREE(pCtx, pRepo);
    SG_DAGNODE_NULLFREE(pCtx, pdn);
	SG_PATHNAME_NULLFREE(pCtx, pPathWorkingDir);
	SG_PATHNAME_NULLFREE(pCtx, pPathFile);

	return 1;

fail:
	SG_VHASH_NULLFREE(pCtx, pvh);

	SG_PATHNAME_NULLFREE(pCtx, pPathWorkingDir);
	SG_PATHNAME_NULLFREE(pCtx, pPathFile);

	return 0;
}
Ejemplo n.º 17
0
void sg_pack__do_changeset(SG_context* pCtx, SG_repo* pRepo, const char* psz_hid_cs, SG_rbtree* prb_blobs)
{
	SG_changeset* pcs = NULL;
    SG_int32 gen = 0;
    SG_uint32 count_blobs = 0;
    SG_uint32 count_parents = 0;
    SG_varray* pva_parents = NULL;
    SG_uint32 i;
    SG_rbtree* prb_new = NULL;
	const char* psz_hid_root_treenode = NULL;
    const char* psz_key = NULL;
    SG_vhash* pvh_lbl = NULL;
    SG_vhash* pvh_blobs = NULL;

	SG_ERR_CHECK(  SG_changeset__load_from_repo(pCtx, pRepo, psz_hid_cs, &pcs)  );
	SG_ERR_CHECK(  SG_changeset__get_root(pCtx, pcs, &psz_hid_root_treenode)  );
    SG_ERR_CHECK(  SG_changeset__get_generation(pCtx, pcs, &gen)  );

    SG_ERR_CHECK(  SG_RBTREE__ALLOC__PARAMS(pCtx, &prb_new, count_blobs, NULL)  );

    SG_ERR_CHECK(  SG_changeset__get_list_of_bloblists(pCtx, pcs, &pvh_lbl)  );

    /* add all the tree user file blobs */
    SG_ERR_CHECK(  SG_changeset__get_bloblist_name(pCtx, SG_BLOB_REFTYPE__TREEUSERFILE, &psz_key)  );
    SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvh_lbl, psz_key, &pvh_blobs)  );
    SG_ERR_CHECK(  SG_vhash__count(pCtx, pvh_blobs, &count_blobs)  );
    /* now write all the blobs */
    for (i=0; i<count_blobs; i++)
    {
        const char* psz_hid = NULL;

        SG_ERR_CHECK(  SG_vhash__get_nth_pair(pCtx, pvh_blobs, i, &psz_hid, NULL)  );
        SG_ERR_CHECK(  SG_rbtree__add(pCtx, prb_new, psz_hid)  );
    }

    /* and the treenode blobs */
    SG_ERR_CHECK(  SG_changeset__get_bloblist_name(pCtx, SG_BLOB_REFTYPE__TREENODE, &psz_key)  );
    SG_ERR_CHECK(  SG_vhash__get__vhash(pCtx, pvh_lbl, psz_key, &pvh_blobs)  );
    SG_ERR_CHECK(  SG_vhash__count(pCtx, pvh_blobs, &count_blobs)  );
    /* now write all the blobs */
    for (i=0; i<count_blobs; i++)
    {
        const char* psz_hid = NULL;

        SG_ERR_CHECK(  SG_rbtree__add(pCtx, prb_new, psz_hid)  );
    }

    SG_ERR_CHECK(  sg_pack__do_get_dir__top(pCtx, pRepo, gen, psz_hid_root_treenode, prb_blobs, prb_new)  );

    SG_RBTREE_NULLFREE(pCtx, prb_new);

    SG_ERR_CHECK(  SG_changeset__get_parents(pCtx, pcs, &pva_parents)  );
    if (pva_parents)
    {
        SG_ERR_CHECK(  SG_varray__count(pCtx, pva_parents, &count_parents)  );
        for (i=0; i<count_parents; i++)
        {
            const char* psz_hid = NULL;

            SG_ERR_CHECK(  SG_varray__get__sz(pCtx, pva_parents, i, &psz_hid)  );

            SG_ERR_CHECK(  sg_pack__do_changeset(pCtx, pRepo, psz_hid, prb_blobs)  );
        }
    }

    SG_CHANGESET_NULLFREE(pCtx, pcs);

    return;

fail:
    SG_RBTREE_NULLFREE(pCtx, prb_new);
}
void sg_vc_hooks__lookup_by_interface__single_result(
        SG_context* pCtx, 
        SG_repo* pRepo, 
        const char* psz_interface, 
        SG_vhash** ppvh_latest_version
        )
{
	//This version will return only the hook with the largest version.
	//If multiple versions of the hook are defined,
	//all old versions will be deleted.
	SG_varray* pva_hooks = NULL;
	SG_vhash* pvh_latest_hook = NULL;
	SG_zingtx* pztx = NULL;
	char* psz_hid_cs_leaf = NULL;
	SG_dagnode* pdn = NULL;
	SG_changeset* pcs = NULL;

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

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

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

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

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

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

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

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

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

}
void SG_vc_hooks__install(
	SG_context* pCtx,
    SG_repo* pRepo,
    const char* psz_interface,
    const char* psz_js,
	const char *module,
	SG_uint32 version,
	SG_bool replaceOld,
    const SG_audit* pq
    )
{
    char* psz_hid_cs_leaf = NULL;
    SG_zingtx* pztx = NULL;
    SG_zingrecord* prec = NULL;
    SG_dagnode* pdn = NULL;
    SG_changeset* pcs = NULL;
    SG_zingtemplate* pzt = NULL;
    SG_zingfieldattributes* pzfa = NULL;
	SG_varray *oldRecs = NULL;

	SG_UNUSED(module);
	SG_UNUSED(version);

	if (replaceOld)
	{
		SG_ERR_CHECK(  SG_vc_hooks__lookup_by_interface(pCtx, pRepo, psz_interface, &oldRecs)  );
	}

    // TODO consider validating the JS by compiling it
    SG_ERR_CHECK(  SG_zing__get_leaf(pCtx, pRepo, NULL, SG_DAGNUM__VC_HOOKS, &psz_hid_cs_leaf)  );
    
    /* start a changeset */
    SG_ERR_CHECK(  SG_zing__begin_tx(pCtx, pRepo, SG_DAGNUM__VC_HOOKS, pq->who_szUserId, psz_hid_cs_leaf, &pztx)  );
    SG_ERR_CHECK(  SG_zingtx__add_parent(pCtx, pztx, psz_hid_cs_leaf)  );

	if (replaceOld)
	{
		SG_uint32 i, count;

		SG_ERR_CHECK(  SG_varray__count(pCtx, oldRecs, &count)  );

		for ( i = 0; i < count; ++i )
		{
			const char *hidrec = NULL;
			SG_vhash *rec = NULL;

			SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, oldRecs, i, &rec)  );
			SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, rec, "hidrec", &hidrec)  );
			SG_ERR_CHECK(  SG_zingtx__delete_record__hid(pCtx, pztx, "hook", hidrec)  );
		}
	}

	SG_ERR_CHECK(  SG_zingtx__get_template(pCtx, pztx, &pzt)  );

	SG_ERR_CHECK(  SG_zingtx__create_new_record(pCtx, pztx, "hook", &prec)  );

    SG_ERR_CHECK(  SG_zingtemplate__get_field_attributes(pCtx, pzt, "hook", "interface", &pzfa)  );
    SG_ERR_CHECK(  SG_zingrecord__set_field__string(pCtx, prec, pzfa, psz_interface) );

    SG_ERR_CHECK(  SG_zingtemplate__get_field_attributes(pCtx, pzt, "hook", "js", &pzfa)  );
    SG_ERR_CHECK(  SG_zingrecord__set_field__string(pCtx, prec, pzfa, psz_js) );

	if (module)
	{
		SG_ERR_CHECK(  SG_zingtemplate__get_field_attributes(pCtx, pzt, "hook", "module", &pzfa)  );
		SG_ERR_CHECK(  SG_zingrecord__set_field__string(pCtx, prec, pzfa, module) );
	}
	if (version)
	{
		SG_ERR_CHECK(  SG_zingtemplate__get_field_attributes(pCtx, pzt, "hook", "version", &pzfa)  );
		SG_ERR_CHECK(  SG_zingrecord__set_field__int(pCtx, prec, pzfa, (SG_int64)version) );
	}

    /* commit the changes */
	SG_ERR_CHECK(  SG_zing__commit_tx(pCtx, pq->when_int64, &pztx, &pcs, &pdn, NULL)  );

    // fall thru

fail:
    if (pztx)
    {
        SG_ERR_IGNORE(  SG_zing__abort_tx(pCtx, &pztx)  );
    }
	SG_VARRAY_NULLFREE(pCtx, oldRecs);
    SG_NULLFREE(pCtx, psz_hid_cs_leaf);
    SG_DAGNODE_NULLFREE(pCtx, pdn);
    SG_CHANGESET_NULLFREE(pCtx, pcs);
}
static void SG_db__make_delta_from_path(
    SG_context* pCtx,
    SG_repo* pRepo,
    SG_uint64 dagnum,
    SG_varray* pva_path,
    SG_uint32 flags,
    SG_vhash* pvh_add,
    SG_vhash* pvh_remove
    )
{
#if SG_DOUBLE_CHECK__CALC_DELTA
    SG_int64 t1 = -1;
    SG_int64 t2 = -1;
    SG_vhash* new_pvh_add = NULL;
    SG_vhash* new_pvh_remove = NULL;
    SG_vhash* old_pvh_add = NULL;
    SG_vhash* old_pvh_remove = NULL;
    SG_string* old_pstr = NULL;
    SG_string* new_pstr = NULL;

    SG_ERR_CHECK(  SG_vhash__alloc__copy(pCtx, &new_pvh_add, pvh_add)  );
    SG_ERR_CHECK(  SG_vhash__alloc__copy(pCtx, &new_pvh_remove, pvh_remove)  );
    SG_ERR_CHECK(  SG_vhash__alloc__copy(pCtx, &old_pvh_add, pvh_add)  );
    SG_ERR_CHECK(  SG_vhash__alloc__copy(pCtx, &old_pvh_remove, pvh_remove)  );

    SG_ERR_CHECK(  SG_time__get_milliseconds_since_1970_utc(pCtx, &t1)  );
    SG_ERR_CHECK(  old_SG_db__make_delta_from_path(
                pCtx,
                pRepo,
                dagnum,
                pva_path,
                old_pvh_add,
                old_pvh_remove
                )  );
    SG_ERR_CHECK(  SG_time__get_milliseconds_since_1970_utc(pCtx, &t2)  );
    {
        SG_uint32 path_length = 0;
        SG_ERR_CHECK(  SG_varray__count(pCtx, pva_path, &path_length)  );
        fprintf(stderr, "make_delta_from_path (%d)\n", path_length);
    }
    fprintf(stderr, "  time old %d ms\n", (int) (t2 - t1));

    SG_ERR_CHECK(  SG_vhash__sort(pCtx, old_pvh_add, SG_FALSE, SG_vhash_sort_callback__increasing)  );
    SG_ERR_CHECK(  SG_vhash__sort(pCtx, old_pvh_remove, SG_FALSE, SG_vhash_sort_callback__increasing)  );

    SG_ERR_CHECK(  SG_time__get_milliseconds_since_1970_utc(pCtx, &t1)  );
    SG_ERR_CHECK(  SG_repo__dbndx__make_delta_from_path(
                pCtx,
                pRepo,
                dagnum,
                pva_path,
                0,
                new_pvh_add,
                new_pvh_remove
                )  );
    SG_ERR_CHECK(  SG_time__get_milliseconds_since_1970_utc(pCtx, &t2)  );
    fprintf(stderr, "  time new %d ms\n", (int) (t2 - t1));
    SG_ERR_CHECK(  SG_vhash__sort(pCtx, new_pvh_add, SG_FALSE, SG_vhash_sort_callback__increasing)  );
    SG_ERR_CHECK(  SG_vhash__sort(pCtx, new_pvh_remove, SG_FALSE, SG_vhash_sort_callback__increasing)  );

    SG_ERR_CHECK(  SG_string__alloc(pCtx, &old_pstr)  );
    SG_ERR_CHECK(  SG_vhash__to_json(pCtx, old_pvh_add, old_pstr)  );
    SG_ERR_CHECK(  SG_string__alloc(pCtx, &new_pstr)  );
    SG_ERR_CHECK(  SG_vhash__to_json(pCtx, new_pvh_add, new_pstr)  );

    if (0 != strcmp(SG_string__sz(old_pstr), SG_string__sz(new_pstr)))
    {
        fprintf(stderr, "oldway:\n");
        SG_VHASH_STDERR(old_pvh_add);
        fprintf(stderr, "new:\n");
        SG_VHASH_STDERR(new_pvh_add);

        SG_ERR_THROW(  SG_ERR_UNSPECIFIED  );
    }

    SG_STRING_NULLFREE(pCtx, old_pstr);
    SG_STRING_NULLFREE(pCtx, new_pstr);

    SG_ERR_CHECK(  SG_string__alloc(pCtx, &old_pstr)  );
    SG_ERR_CHECK(  SG_vhash__to_json(pCtx, old_pvh_remove, old_pstr)  );
    SG_ERR_CHECK(  SG_string__alloc(pCtx, &new_pstr)  );
    SG_ERR_CHECK(  SG_vhash__to_json(pCtx, new_pvh_remove, new_pstr)  );

    if (0 != strcmp(SG_string__sz(old_pstr), SG_string__sz(new_pstr)))
    {
        fprintf(stderr, "oldway:\n");
        SG_VHASH_STDERR(old_pvh_remove);
        fprintf(stderr, "new:\n");
        SG_VHASH_STDERR(new_pvh_remove);

        SG_ERR_THROW(  SG_ERR_UNSPECIFIED  );
    }
#endif

#if SG_DOUBLE_CHECK__CALC_DELTA
    SG_ERR_CHECK(  SG_time__get_milliseconds_since_1970_utc(pCtx, &t1)  );
#endif
    SG_ERR_CHECK(  SG_repo__dbndx__make_delta_from_path(
                pCtx,
                pRepo,
                dagnum,
                pva_path,
                flags,
                pvh_add,
                pvh_remove
                )  );
#if SG_DOUBLE_CHECK__CALC_DELTA
    SG_ERR_CHECK(  SG_time__get_milliseconds_since_1970_utc(pCtx, &t2)  );
    fprintf(stderr, "  time NEW %d ms\n", (int) (t2 - t1));
#endif

fail:
#if SG_DOUBLE_CHECK__CALC_DELTA
    SG_STRING_NULLFREE(pCtx, old_pstr);
    SG_STRING_NULLFREE(pCtx, new_pstr);

    SG_VHASH_NULLFREE(pCtx, old_pvh_add);
    SG_VHASH_NULLFREE(pCtx, old_pvh_remove);
    SG_VHASH_NULLFREE(pCtx, new_pvh_add);
    SG_VHASH_NULLFREE(pCtx, new_pvh_remove);
#endif
    ;
}
/**
 * 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.º 22
0
static void _fillInUserSelection(
	SG_context *pCtx,
	SG_string *pstrRepoDescriptorName,
	SG_string *replacement)
{
	SG_repo *repo = NULL;
	SG_varray *users = NULL;
	SG_vhash *user = NULL;
	SG_uint32 i = 0;
	SG_uint32 count;
	SG_string *semail = NULL;
	SG_string *suid = NULL;
	SG_string *entry = NULL;
	SG_string *curuid = NULL;

	SG_ERR_CHECK(  SG_string__clear(pCtx, replacement)  );
	SG_ERR_CHECK(  SG_repo__open_repo_instance(pCtx, SG_string__sz(pstrRepoDescriptorName), &repo)  );

	SG_ERR_CHECK(  SG_user__list_all(pCtx, repo, &users)  );
	SG_ERR_CHECK(  SG_varray__count(pCtx, users, &count)  );

	SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &semail)  );
	SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &suid)  );
	SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &entry)  );

	SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &curuid)  );
	SG_ERR_CHECK(  _getUserId(pCtx, repo, curuid)  );

	for ( i = 0; i < count; ++i )
	{
		const char *uid = NULL;
		const char *email = NULL;
		const char *selected = NULL;

		SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, users, i, &user)  );

		SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, user, "recid", &uid)  );
		SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, user, "email", &email)  );

		SG_ERR_CHECK(  _getEncoded(pCtx, uid, suid)  );
		SG_ERR_CHECK(  _getEncoded(pCtx, email, semail)  );

		if (eq(SG_string__sz(curuid), uid))
		{
			selected = " selected='selected' ";
		}
		else
		{
			selected = "";
		}

		SG_ERR_CHECK(  SG_string__sprintf(pCtx, entry, "<option value=\"%s\" %s>%s</option>",
											SG_string__sz(suid), selected, SG_string__sz(semail))  );

		SG_ERR_CHECK(  SG_string__append__string(pCtx, replacement, entry)  );
	}
fail:
	SG_VARRAY_NULLFREE(pCtx, users);
	SG_REPO_NULLFREE(pCtx, repo);
	SG_STRING_NULLFREE(pCtx, semail);
	SG_STRING_NULLFREE(pCtx, suid);
	SG_STRING_NULLFREE(pCtx, entry);
	SG_STRING_NULLFREE(pCtx, curuid);
}
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);
}
Ejemplo n.º 24
0
/**
 * Release VFILE lock and invoke external merge tool for this file.
 *
 * TODO 2010/07/12 The MERGE-PLAN is an array and allows for
 * TODO            multiple steps (for an n-way sub-merge cascade).
 * TODO            But we don't have that part turned on yet in
 * TODO            sg_mrg__private_biuld_wd_issues.h:_make_file_merge_plan(),
 * TODO            so for now, we only expect 1 step.
 * TODO
 * TODO            Also, when we do have multiple steps, we might want to
 * TODO            be able to use the 'status' field to see which steps
 * TODO            were already performed in an earlier RESOLVE.
 * TODO
 * TODO            Also, when we want to support more than 1 step we need
 * TODO            to copy pvaPlan because when we release the pendingtree
 * TODO            the pvhIssue becomes invalidated too.
 */
static void _resolve__fix__run_external_file_merge(SG_context * pCtx,
												   struct _resolve_data * pData,
												   const char * pszGid,
												   const SG_vhash * pvhIssue,
												   SG_string * pStrRepoPath,
												   enum _fix_status * pFixStatus)
{
	_resolve__step_pathnames * pStepPathnames = NULL;
	_resolve__external_tool * pET = NULL;
	const SG_varray * pvaPlan;
	const SG_vhash * pvhStep_0;
	SG_int64 r64;
	SG_uint32 nrSteps;
	SG_mrg_automerge_result result;
	SG_bool bMerged = SG_FALSE;
	SG_bool bIsResolved = SG_FALSE;

	SG_ERR_CHECK(  SG_vhash__get__varray(pCtx, pvhIssue, "conflict_file_merge_plan", (SG_varray **)&pvaPlan)  );
	SG_ERR_CHECK(  SG_varray__count(pCtx, pvaPlan, &nrSteps)  );

	if (nrSteps > 1)
		SG_ERR_THROW2(  SG_ERR_ASSERT,
						(pCtx, "TODO RESOLVE more than 1 step in auto-merge plan for '%s'.", SG_string__sz(pStrRepoPath))  );

	//////////////////////////////////////////////////////////////////
	// Get Step[0]

	SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pvaPlan, 0, (SG_vhash **)&pvhStep_0)  );

	// see if the user has already performed the merge and maybe got interrupted.

	SG_ERR_CHECK(  SG_vhash__get__int64(pCtx, pvhStep_0, "status", &r64)  );
	result = (SG_mrg_automerge_result)r64;
	if (result == SG_MRG_AUTOMERGE_RESULT__SUCCESSFUL)
	{
		SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDERR,
								   "TODO Print message about previous successful manual merge of the file content and ask if they want to redo it for '%s'.\n",
								   SG_string__sz(pStrRepoPath))  );

		*pFixStatus = FIX_USER_MERGED;
		goto done;
	}

	SG_ERR_CHECK(  _resolve__step_pathnames__compute(pCtx, pData, pvhIssue, pvhStep_0, pStrRepoPath, &pStepPathnames)  );

	// While we still have a handle to the pendingtree, lookup the
	// specifics on the external tool that we should invoke.  these
	// details come from localsettings.

	SG_ERR_CHECK(  _resolve__external_tool__lookup(pCtx, pData, pszGid, pvhIssue, pStrRepoPath, &pET)  );

	// Free the PENDINGTREE so that we release the VFILE lock.

	pvhIssue = NULL;
	pvaPlan = NULL;
	pvhStep_0 = NULL;
	SG_PENDINGTREE_NULLFREE(pCtx, pData->pPendingTree);

	//////////////////////////////////////////////////////////////////
	// Invoke the external tool.

	SG_ERR_CHECK(  _resolve__fix__run_external_file_merge_1(pCtx,
															pData,
															pET,
															pStepPathnames,
															pStrRepoPath,
															&bMerged)  );
	if (!bMerged)
	{
		SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDOUT, "RESOLVE: Aborting the merge of this file.\n")  );
		*pFixStatus = FIX_USER_ABORTED;
		goto done;
	}

	//////////////////////////////////////////////////////////////////
	// Reload the PENDINGTREE and re-fetch the ISSUE and updated the STATUS on
	// this step in the PLAN.
	// 
	// We duplicate some of the "see if someone else resolved this issue while
	// we were without the lock" stuff.

	SG_ERR_CHECK(  _resolve__lookup_issue(pCtx, pData, pszGid, &pvhIssue)  );
	SG_ERR_CHECK(  _resolve__is_resolved(pCtx, pvhIssue, &bIsResolved)  );
	if (bIsResolved)
	{
		// Someone else marked it resolved while were waiting for
		// the user to edit the file and while we didn't have the
		// file lock.  We should stop here.

		SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDOUT, "RESOLVE: Aborting the merge of this file (due to race condition).\n")  );
		*pFixStatus = FIX_LOST_RACE;
		goto done;
	}

	// re-fetch the current step and update the "result" status for it
	// and flush the pendingtree back disk.
	//
	// we only update the step status -- we DO NOT alter the __DIVERGENT_FILE_EDIT__
	// conflict_flags.

	SG_ERR_CHECK(  SG_vhash__get__varray(pCtx, pvhIssue, "conflict_file_merge_plan", (SG_varray **)&pvaPlan)  );
	SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pvaPlan, 0, (SG_vhash **)&pvhStep_0)  );
	SG_ERR_CHECK(  SG_pendingtree__set_wd_issue_plan_step_status__dont_save_pendingtree(pCtx, pData->pPendingTree,
																						pvhStep_0,
																						SG_MRG_AUTOMERGE_RESULT__SUCCESSFUL)  );
	SG_ERR_CHECK(  SG_pendingtree__save(pCtx, pData->pPendingTree)  );
	SG_PENDINGTREE_NULLFREE(pCtx, pData->pPendingTree);

	SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDOUT, "RESOLVE: The file content portion of the merge was successful.\n")  );
	*pFixStatus = FIX_USER_MERGED;

	// we defer the delete of the temp input files until we completely
	// resolve the issue.  (This gives us more options if we allow the
	// resolve to be restarted after interruptions.)

done:
	;
fail:
	_RESOLVE__EXTERNAL_TOOL__NULLFREE(pCtx, pET);
	_RESOLVE__STEP_PATHNAMES__NULLFREE(pCtx, pStepPathnames);
}
void SG_cmd_util__get_username_for_repo(
	SG_context *pCtx,
	const char *szRepoName,
	char **ppUsername
	)
{
	SG_string * pUsername = NULL;
	SG_repo * pRepo = NULL;
	char * psz_username = NULL;
	SG_curl * pCurl = NULL;
	SG_string * pUri = NULL;
	SG_string * pResponse = NULL;
	SG_int32 responseStatusCode = 0;
	SG_vhash * pRepoInfo = NULL;
	char * psz_userid = NULL;
	SG_varray * pUsers = NULL;

	SG_NULLARGCHECK_RETURN(ppUsername);

	if(!szRepoName)
	{
		// Look up username based on 'whoami' of repo associated with cwd.

		SG_ERR_IGNORE(  SG_cmd_util__get_repo_from_cwd(pCtx, &pRepo, NULL)  );
		if(pRepo)
			SG_ERR_IGNORE(  SG_user__get_username_for_repo(pCtx, pRepo, &psz_username)  );
		SG_REPO_NULLFREE(pCtx, pRepo);
	}
	else if(SG_sz__starts_with(szRepoName, "http://") || SG_sz__starts_with(szRepoName, "https://"))
	{
		// Look up username based on 'whoami' of admin id of remote repo.

		SG_ERR_CHECK(  SG_curl__alloc(pCtx, &pCurl)  );

		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pUri, szRepoName)  );
		SG_ERR_CHECK(  SG_string__append__sz(pCtx, pUri, ".json")  );

		SG_ERR_CHECK(  SG_curl__reset(pCtx, pCurl)  );
		SG_ERR_CHECK(  SG_curl__setopt__sz(pCtx, pCurl, CURLOPT_URL, SG_string__sz(pUri))  );

		SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pResponse)  );
		SG_ERR_CHECK(  SG_curl__set__write_string(pCtx, pCurl, pResponse)  );

		SG_ERR_CHECK(  SG_curl__perform(pCtx, pCurl)  );
		SG_ERR_CHECK(  SG_curl__getinfo__int32(pCtx, pCurl, CURLINFO_RESPONSE_CODE, &responseStatusCode)  );

		if(responseStatusCode==200)
		{
			const char * szAdminId = NULL;
			SG_ERR_CHECK(  SG_VHASH__ALLOC__FROM_JSON__STRING(pCtx, &pRepoInfo, pResponse)  );
			SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pRepoInfo, SG_SYNC_REPO_INFO_KEY__ADMIN_ID, &szAdminId)  );

			SG_ERR_CHECK(  SG_string__clear(pCtx, pUri)  );
			SG_ERR_CHECK(  SG_string__append__format(pCtx, pUri, "/admin/%s/whoami/userid", szAdminId)  );
			SG_ERR_IGNORE(  SG_localsettings__get__sz(pCtx, SG_string__sz(pUri), NULL, &psz_userid, NULL)  );

			if(psz_userid)
			{
				// We now have the userid. Look up the username.


				SG_ERR_CHECK(  SG_string__clear(pCtx, pUri)  );
				SG_ERR_CHECK(  SG_string__append__format(pCtx, pUri, "%s/users.json", szRepoName)  );

				SG_ERR_CHECK(  SG_curl__reset(pCtx, pCurl)  );
				SG_ERR_CHECK(  SG_curl__setopt__sz(pCtx, pCurl, CURLOPT_URL, SG_string__sz(pUri))  );

				SG_ERR_CHECK(  SG_string__clear(pCtx, pResponse)  );
				SG_ERR_CHECK(  SG_curl__set__write_string(pCtx, pCurl, pResponse)  );

				SG_ERR_CHECK(  SG_curl__perform(pCtx, pCurl)  );
				SG_ERR_CHECK(  SG_curl__getinfo__int32(pCtx, pCurl, CURLINFO_RESPONSE_CODE, &responseStatusCode)  );

				if(responseStatusCode==200)
				{
					SG_uint32 i, nUsers;
					SG_ERR_CHECK(  SG_VARRAY__ALLOC__FROM_JSON__STRING(pCtx, &pUsers, pResponse)  );
					SG_ERR_CHECK(  SG_varray__count(pCtx, pUsers, &nUsers)  );
					for(i=0; i<nUsers; ++i)
					{
						SG_vhash * pUser = NULL;
						const char * psz_recid = NULL;
						SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pUsers, i, &pUser)  );
						SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pUser, "recid", &psz_recid)  );
						if(!strcmp(psz_recid, psz_userid))
						{
							const char * psz_name = NULL;
							SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pUser, "name", &psz_name)  );
							SG_ERR_CHECK(  SG_STRDUP(pCtx, psz_name, &psz_username)  );
							break;
						}
					}
					SG_VARRAY_NULLFREE(pCtx, pUsers);
				}
				
				SG_NULLFREE(pCtx, psz_userid);
			}

			SG_VHASH_NULLFREE(pCtx, pRepoInfo);
		}

		SG_STRING_NULLFREE(pCtx, pResponse);
		SG_STRING_NULLFREE(pCtx, pUri);
		SG_CURL_NULLFREE(pCtx, pCurl);
	}
	else
	{
		// Look up username based on 'whoami' of repo provided.

		SG_ERR_CHECK(  SG_REPO__OPEN_REPO_INSTANCE(pCtx, szRepoName, &pRepo)  );
		SG_ERR_IGNORE(  SG_user__get_username_for_repo(pCtx, pRepo, &psz_username)  );
		SG_REPO_NULLFREE(pCtx, pRepo);
	}

	*ppUsername = psz_username;

	return;
fail:
	SG_STRING_NULLFREE(pCtx, pUsername);
	SG_REPO_NULLFREE(pCtx, pRepo);
	SG_NULLFREE(pCtx, psz_username);
	SG_CURL_NULLFREE(pCtx, pCurl);
	SG_STRING_NULLFREE(pCtx, pUri);
	SG_STRING_NULLFREE(pCtx, pResponse);
	SG_VHASH_NULLFREE(pCtx, pRepoInfo);
	SG_NULLFREE(pCtx, psz_userid);
	SG_VARRAY_NULLFREE(pCtx, pUsers);
}
void SG_vc_hooks__BROADCAST__AFTER_COMMIT(
    SG_context* pCtx, 
    SG_repo* pRepo, 
    SG_changeset* pcs,
    const char* psz_tied_branch_name,
    const SG_audit* pq,
    const char* psz_comment,
    const char* const* paszAssocs,
    SG_uint32 count_assocs,
    const SG_stringarray* psa_stamps
    )
{
    SG_varray* pva_hooks = NULL;
    SG_vhash* pvh_params = NULL;
    char* psz_repo_id = NULL;
    char* psz_admin_id = NULL;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

fail:
    SG_VHASH_NULLFREE(pCtx, pvh_params);
    SG_VARRAY_NULLFREE(pCtx, pva_hooks);
	SG_NULLFREE(pCtx, psz_repo_id);
	SG_NULLFREE(pCtx, psz_admin_id);
}