static void _deserialize_data_ver_1_cb(SG_context * pCtx, void * pVoidDeserializeData, SG_UNUSED_PARAM(const SG_varray * pva), SG_UNUSED_PARAM(SG_uint32 ndx), const SG_variant * pVariant) { struct _deserialize_data * pDeserializeData = (struct _deserialize_data *)pVoidDeserializeData; SG_vhash * pvhMyData; SG_vhash * pvhDagnode; SG_int64 gen64, state64; _my_data * pMyData; SG_dagnode * pDagnode = NULL; const char* psz_id = NULL; SG_UNUSED(pva); SG_UNUSED(ndx); SG_ERR_CHECK( SG_variant__get__vhash(pCtx,pVariant,&pvhMyData) ); #if DEBUG && TRACE_DAGFRAG && 0 SG_ERR_CHECK( SG_vhash_debug__dump_to_console(pCtx, pvhMyData) ); #endif SG_ERR_CHECK( SG_vhash__get__int64(pCtx,pvhMyData,KEY_DFS_STATE,&state64) ); if (SG_DFS_END_FRINGE == state64) { SG_ERR_CHECK( SG_vhash__get__sz(pCtx,pvhMyData,KEY_DAGNODE_ID,&psz_id) ); SG_ERR_CHECK( _cache__add__fringe(pCtx,pDeserializeData->pFrag, psz_id) ); } else { SG_ERR_CHECK( SG_vhash__get__vhash(pCtx,pvhMyData,KEY_ACTUAL_DAGNODE,&pvhDagnode) ); SG_ERR_CHECK( SG_vhash__get__int64(pCtx,pvhDagnode,KEY_GEN,&gen64) ); SG_ERR_CHECK( SG_dagnode__alloc__from_vhash(pCtx, &pDagnode, pvhDagnode) ); SG_ERR_CHECK( _cache__add__dagnode(pCtx, pDeserializeData->pFrag, (SG_int32)gen64, pDagnode, (SG_uint32)state64, &pMyData) ); pDagnode = NULL; // cache owns it now. } return; fail: SG_DAGNODE_NULLFREE(pCtx, pDagnode); }
/** * We need to check/recheck the RESOLVED/UNRESOLVED status each time * after we release the VFILE lock (because the user could have done * another RESOLVE in another shell window while the external merge * tool was running). */ static void _resolve__is_resolved(SG_context * pCtx, const SG_vhash * pvhIssue, SG_bool * pbIsResolved) { SG_int64 s64; SG_pendingtree_wd_issue_status status; SG_ERR_CHECK_RETURN( SG_vhash__get__int64(pCtx, pvhIssue, "status", &s64) ); status = (SG_pendingtree_wd_issue_status)s64; *pbIsResolved = ((status & SG_ISSUE_STATUS__MARKED_RESOLVED) == SG_ISSUE_STATUS__MARKED_RESOLVED); }
/** * 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; }
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); }
/** * 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); }
/** * Update the RESOLVED/UNRESOLVED status for this ISSUE and do an * incremental save on the pendingtree. */ static void _resolve__mark(SG_context * pCtx, struct _resolve_data * pData, const SG_vhash * pvhIssue, SG_bool bMarkResolved) { SG_int64 s; SG_pendingtree_wd_issue_status status; SG_ERR_CHECK_RETURN( SG_vhash__get__int64(pCtx, pvhIssue, "status", &s) ); status = (SG_pendingtree_wd_issue_status)s; if (bMarkResolved) status |= SG_ISSUE_STATUS__MARKED_RESOLVED; else status &= ~SG_ISSUE_STATUS__MARKED_RESOLVED; // update the status on the ISSUE and save the pendingtree now. // since this trashes stuff within in it, go ahead and free it // so no one trips over the trash. SG_ERR_CHECK_RETURN( SG_pendingtree__set_wd_issue_status(pCtx, pData->pPendingTree, pvhIssue, status) ); SG_PENDINGTREE_NULLFREE(pCtx, pData->pPendingTree); }
static void _resolve__do_mark_1(SG_context * pCtx, struct _resolve_data * pData, const char * pszGid, SG_bool bMarkResolved) { const SG_vhash * pvhIssue; SG_bool bNeedToDeleteTempFiles = SG_FALSE; SG_ERR_CHECK( _resolve__lookup_issue(pCtx, pData, pszGid, &pvhIssue) ); if (bMarkResolved) { SG_int64 i64; SG_mrg_cset_entry_conflict_flags conflict_flags; // see if there could possibly be ~mine files for this issue that we should delete. SG_ERR_CHECK( SG_vhash__get__int64(pCtx, pvhIssue, "conflict_flags", &i64) ); conflict_flags = (SG_mrg_cset_entry_conflict_flags)i64; if (conflict_flags & SG_MRG_CSET_ENTRY_CONFLICT_FLAGS__DIVERGENT_FILE_EDIT__MASK__NOT_OK) bNeedToDeleteTempFiles = SG_TRUE; } SG_ERR_CHECK( _resolve__mark(pCtx, pData, pvhIssue, bMarkResolved) ); // After the pendingtree has been written out, try to delete the temp files. if (bNeedToDeleteTempFiles) { SG_ERR_CHECK( _resolve__lookup_issue(pCtx, pData, pszGid, &pvhIssue) ); SG_ERR_CHECK( _resolve__delete_temp_files(pCtx, pData, pszGid, pvhIssue) ); } fail: return; }
/** * Do diff of an individual item. * When WC-based, we have a "DiffStep" vhash. * When historical, we have an item from a pvaStatus. * */ static void _do_diff1(SG_context * pCtx, SG_bool bWC, const SG_option_state * pOptSt, const SG_vhash * pvhItem, SG_uint32 * piResult) { SG_string * pStringGidRepoPath = NULL; SG_vhash * pvhResultCodes = NULL; SG_stringarray * psa1 = NULL; const char * pszGid; SG_int64 i64Result = 0; SG_string * pStringErr = NULL; SG_ERR_CHECK( SG_vhash__get__sz(pCtx, pvhItem, "gid", &pszGid) ); if (bWC) { SG_pathname * pPathWc = NULL; SG_bool bHasTool = SG_FALSE; // With the __diff__setup() and __diff__run() changes, we have already // examined the items during the __setup() step and recorded a tool for // the *FILE* that have changed content. So if "tool" isn't set in the // DiffStep/Item, we don't need to diff it -- it could be a structural // change, a non-file, a found item, etc. // // we do not use SG_wc__diff__throw() because we already have the diff info // and we want to control the result-code processing below. SG_ERR_CHECK( SG_vhash__has(pCtx, pvhItem, "tool", &bHasTool) ); if (bHasTool) SG_ERR_CHECK( SG_wc__diff__run(pCtx, pPathWc, pvhItem, &pvhResultCodes) ); } else { SG_ERR_CHECK( SG_STRING__ALLOC(pCtx, &pStringGidRepoPath) ); SG_ERR_CHECK( SG_string__sprintf(pCtx, pStringGidRepoPath, "@%s", pszGid) ); SG_ERR_CHECK( SG_STRINGARRAY__ALLOC(pCtx, &psa1, 1) ); SG_ERR_CHECK( SG_stringarray__add(pCtx, psa1, SG_string__sz(pStringGidRepoPath)) ); // we do not use the __throw() version of this routine so we can control // result-code processing below. SG_ERR_CHECK( SG_vv2__diff_to_stream(pCtx, pOptSt->psz_repo, pOptSt->pRevSpec, psa1, 0, SG_FALSE, // bNoSort SG_TRUE, // bInteractive, pOptSt->psz_tool, &pvhResultCodes) ); } if (pvhResultCodes) { SG_vhash * pvhResult; // we do not own this SG_ERR_CHECK( SG_vhash__check__vhash(pCtx, pvhResultCodes, pszGid, &pvhResult) ); if (pvhResult) { const char * pszTool; SG_ERR_CHECK( SG_vhash__get__sz(pCtx, pvhResult, "tool", &pszTool) ); SG_ERR_CHECK( SG_vhash__get__int64(pCtx, pvhResult, "result", &i64Result) ); SG_difftool__check_result_code__throw(pCtx, i64Result, pszTool); if (SG_context__has_err(pCtx)) { SG_context__err_to_string(pCtx, SG_FALSE, &pStringErr); SG_context__err_reset(pCtx); SG_ERR_CHECK( SG_console__raw(pCtx, SG_CS_STDERR, SG_string__sz(pStringErr)) ); // eat the tool error. the result code is set. } } } if (piResult) *piResult = (SG_uint32)i64Result; fail: SG_STRING_NULLFREE(pCtx, pStringGidRepoPath); SG_VHASH_NULLFREE(pCtx, pvhResultCodes); SG_STRINGARRAY_NULLFREE(pCtx, psa1); SG_STRING_NULLFREE(pCtx, pStringErr); }
/** * Assuming that we have something of the form: * * vv resolve [--foo] <arg_0> [<arg_1> [<arg_2> ...]] * * where each <arg_x> is an absolute or relative path in the WD * (probably not a repo-path). * * Use the PENDINGTREE to lookup each path and get the entry's GID. * Use the GID to search for an ISSUE in the list of issues. If we * find it, add the GID to the stringarray we are building. If not, * throw an error. */ static void _resolve__map_args_to_gids(SG_context * pCtx, struct _resolve_data * pData, SG_uint32 count_args, const char ** paszArgs, SG_bool bWantResolved, SG_bool bWantUnresolved) { SG_pathname * pPath_k = NULL; char * pszGid_k = NULL; SG_uint32 kArg; SG_bool bWantBoth = (bWantResolved && bWantUnresolved); SG_ERR_CHECK( SG_STRINGARRAY__ALLOC(pCtx, &pData->psaGids, count_args) ); for (kArg=0; kArg<count_args; kArg++) { const SG_vhash * pvhIssue_k; SG_bool bFound; SG_bool bDuplicate; SG_bool bWantThisOne; // take each <arg_k> and get a full pathname for it and // search for it in the pendingtree and get its GID. // in theory, if an entry has an issue, it is dirty and // should have a ptnode. if (paszArgs[kArg][0] == '@') SG_ERR_CHECK( SG_workingdir__construct_absolute_path_from_repo_path2(pCtx, pData->pPendingTree, paszArgs[kArg], &pPath_k) ); else SG_ERR_CHECK( SG_PATHNAME__ALLOC__SZ(pCtx, &pPath_k, paszArgs[kArg]) ); SG_ERR_CHECK( SG_pendingtree__get_gid_from_local_path(pCtx, pData->pPendingTree, pPath_k, &pszGid_k) ); #if 0 && defined(DEBUG) SG_ERR_IGNORE( SG_console(pCtx, SG_CS_STDERR, ("Mapped arg[%d] '%s' to:\n" "\t%s\n" "\t[gid %s]\n"), kArg, paszArgs[kArg], SG_pathname__sz(pPath_k), pszGid_k) ); #endif // see if there is an ISSUE for this GID. SG_ERR_CHECK( SG_pendingtree__find_wd_issue_by_gid(pCtx, pData->pPendingTree, pszGid_k, &bFound, &pvhIssue_k) ); if (!bFound) SG_ERR_THROW2( SG_ERR_ISSUE_NOT_FOUND, (pCtx, "No issue found for '%s': %s", paszArgs[kArg], SG_pathname__sz(pPath_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) { // check for duplicate args on command line. (or rather, args that // map to the same GID.) SG_ERR_CHECK( SG_stringarray__find(pCtx, pData->psaGids, pszGid_k, 0, &bDuplicate, NULL) ); if (bDuplicate) SG_ERR_THROW2( SG_ERR_DUPLICATE_ISSUE, (pCtx, "Argument '%s' maps to an issue already named.", paszArgs[kArg]) ); SG_ERR_CHECK( SG_stringarray__add(pCtx, pData->psaGids, pszGid_k) ); } SG_NULLFREE(pCtx, pszGid_k); SG_PATHNAME_NULLFREE(pCtx, pPath_k); } return; fail: SG_NULLFREE(pCtx, pszGid_k); SG_PATHNAME_NULLFREE(pCtx, pPath_k); }
/** * Try to FIX the ISSUE. * * Alter something in the pendingtree/issue/WD and then SAVE the pendingtree. * We allow this to be an incremental save after just this issue. We also * allow the VFILE lock to be released while the external merge (DiffMerge) * tool is running. (Not because DiffMerge needs it, but rather so that they * could do other STATUS/DIFF commands in another shell while doing the text * merge.) */ static void _resolve__fix(SG_context * pCtx, struct _resolve_data * pData, const char * pszGid, enum _fix_status * pFixStatus) { const SG_vhash * pvhIssue; SG_string * pStrRepoPath = NULL; SG_int64 i64; SG_mrg_cset_entry_conflict_flags conflict_flags; SG_portability_flags portability_flags; SG_bool bIsResolved = SG_FALSE; SG_bool bCollisions; // Fetch the ISSUE using the current pendingtree (allocating one if // necessary) and print detailed info about the ISSUE on the console. SG_ERR_CHECK( _resolve__lookup_issue(pCtx, pData, pszGid, &pvhIssue) ); SG_ERR_CHECK( _resolve__list(pCtx, pData, pvhIssue, &pStrRepoPath) ); // TODO 2010/07/12 We should have a --prompt option to allow them to // TODO skip an issue. Like "/bin/rm -i *". // Skip the issue if it is already resolved. In theory, we should not // get this (because we filtered the pData->psaGids by status as we // parsed the command line arguments), but if they did another resolve // in another shell while we didn't have the lock, it could happen. SG_ERR_CHECK( _resolve__is_resolved(pCtx, pvhIssue, &bIsResolved) ); if (bIsResolved) { SG_ERR_IGNORE( SG_console(pCtx, SG_CS_STDOUT, "Issue already resolved; nothing to be done for '%s'.\n", SG_string__sz(pStrRepoPath)) ); goto done; } // There are 2 main types of problems: // [1] Conflicts within the text of a file (where the builtin auto-merge // failed or was not used) and for which we need to ask them to manually // merge the content (using an external tool like DiffMerge). // [2] Structural changes, including: MOVEs, RENAMEs, CHMODs, XATTRs, // entryname collisions, potential entryname collisions, and etc. // // We could also have both -- both edit conflicts and rename conflicts, // for example. // // Do these in 2 steps so that we can release the VFILE lock while they // are editing the file. ////////////////////////////////////////////////////////////////// // [1] ////////////////////////////////////////////////////////////////// SG_ERR_CHECK( SG_vhash__get__int64(pCtx, pvhIssue, "conflict_flags", &i64) ); conflict_flags = (SG_mrg_cset_entry_conflict_flags)i64; if (conflict_flags & SG_MRG_CSET_ENTRY_CONFLICT_FLAGS__DIVERGENT_FILE_EDIT__MASK__NOT_OK) { SG_ERR_CHECK( _resolve__fix__run_external_file_merge(pCtx, pData, pszGid, pvhIssue, pStrRepoPath, pFixStatus) ); pvhIssue = NULL; if (*pFixStatus != FIX_USER_MERGED) goto done; // the above MAY have freed and reloaded the pendingtree (and // invalidated pvhIssue), so re-fetch it and/or re-set our variables. 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. *pFixStatus = FIX_LOST_RACE; goto done; } SG_ERR_CHECK( SG_vhash__get__int64(pCtx, pvhIssue, "conflict_flags", &i64) ); conflict_flags = (SG_mrg_cset_entry_conflict_flags)i64; } #if 0 && defined(DEBUG) SG_ERR_IGNORE( SG_console(pCtx, SG_CS_STDERR, "RESOLVE: Issue between [1] and [2]: '%s'\n", SG_string__sz(pStrRepoPath)) ); SG_ERR_IGNORE( SG_vhash_debug__dump_to_console(pCtx, pvhIssue) ); #endif ////////////////////////////////////////////////////////////////// // [2] ////////////////////////////////////////////////////////////////// SG_ERR_CHECK( SG_vhash__get__bool(pCtx, pvhIssue, "collision_flags", &bCollisions) ); SG_ERR_CHECK( SG_vhash__get__int64(pCtx, pvhIssue, "portability_flags", &i64) ); portability_flags = (SG_portability_flags)i64; if (conflict_flags & SG_MRG_CSET_ENTRY_CONFLICT_FLAGS__UNDELETE__MASK) { SG_ERR_CHECK( _resolve__fix__structural__delete(pCtx, pData, pszGid, pvhIssue, pStrRepoPath, conflict_flags, bCollisions, portability_flags, pFixStatus) ); if (*pFixStatus != FIX_USER_MERGED) goto done; } else if (bCollisions || (portability_flags) || (conflict_flags & ~SG_MRG_CSET_ENTRY_CONFLICT_FLAGS__DIVERGENT_FILE_EDIT__MASK)) { SG_ERR_CHECK( _resolve__fix__structural__non_delete(pCtx, pData, pszGid, pvhIssue, pStrRepoPath, conflict_flags, bCollisions, portability_flags, pFixStatus) ); if (*pFixStatus != FIX_USER_MERGED) goto done; } // mark the issue as RESOLVED and save the pendingtree. SG_ERR_CHECK( _resolve__mark(pCtx, pData, pvhIssue, SG_TRUE) ); *pFixStatus = FIX_USER_MERGED; ////////////////////////////////////////////////////////////////// // We've completely resolved the issue, if there were ~mine files, // we can delete them. if (conflict_flags & SG_MRG_CSET_ENTRY_CONFLICT_FLAGS__DIVERGENT_FILE_EDIT__MASK__NOT_OK) { SG_ERR_CHECK( _resolve__lookup_issue(pCtx, pData, pszGid, &pvhIssue) ); SG_ERR_CHECK( _resolve__delete_temp_files(pCtx, pData, pszGid, pvhIssue) ); } done: ; fail: SG_STRING_NULLFREE(pCtx, pStrRepoPath); }
/** * 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); }
/** * Get the current attrbits observed on the item *UNIONED* * with the baseline, if present. * * If the item is LOST/DELETED (not present in the working * directory), fall back to the baseline value. * */ void sg_wc_liveview_item__get_current_attrbits(SG_context * pCtx, sg_wc_liveview_item * pLVI, SG_wc_tx * pWcTx, SG_uint64 * pAttrbits) { if (pLVI->queuedOverwrites.pvhAttrbits) { // We have a QUEUED operation on this item that set the attrbits. // get the value from the journal. SG_ERR_CHECK_RETURN( SG_vhash__get__int64(pCtx, pLVI->queuedOverwrites.pvhAttrbits, "attrbits", (SG_int64 *)pAttrbits) ); #if TRACE_WC_LIE { SG_int_to_string_buffer bufui64; SG_ERR_IGNORE( SG_console(pCtx, SG_CS_STDERR, "GetCurrentAttrbits: using journal: %s\n", SG_uint64_to_sz__hex((*pAttrbits), bufui64)) ); } #endif return; } #if 1 && TRACE_WC_ATTRBITS { sg_wc_db__pc_row__flags_net flags_net = SG_WC_DB__PC_ROW__FLAGS_NET__ZERO; SG_ERR_IGNORE( sg_wc_liveview_item__get_flags_net(pCtx, pLVI, &flags_net) ); SG_ERR_IGNORE( SG_console(pCtx, SG_CS_STDERR, "GetCurrentAttrbits: [flagsLive %d][flagsNet 0x%02x][pPcRow_PC %04x][pPcRow_Ref %04x][pRD %04x][pTneRow %04x] %s\n", ((SG_uint32)pLVI->scan_flags_Live), ((SG_uint32)flags_net), (SG_uint32)((pLVI->pPcRow_PC) ? (pLVI->pPcRow_PC->ref_attrbits) : 0xffff), (SG_uint32)((pLVI->pPrescanRow->pPcRow_Ref) ? (pLVI->pPrescanRow->pPcRow_Ref->ref_attrbits) : 0xffff), (SG_uint32)((pLVI->pPrescanRow->pRD && pLVI->pPrescanRow->pRD->pAttrbits) ? (*pLVI->pPrescanRow->pRD->pAttrbits) : 0xffff), (SG_uint32)((pLVI->pPrescanRow->pTneRow && pLVI->pPrescanRow->pTneRow->p_d) ? (pLVI->pPrescanRow->pTneRow->p_d->attrbits) : 0xffff), SG_string__sz(pLVI->pStringEntryname)) ); } #endif SG_ASSERT_RELEASE_RETURN( (pLVI->pPrescanRow) ); if (SG_WC_PRESCAN_FLAGS__IS_CONTROLLED_SPARSE(pLVI->scan_flags_Live)) { if (pLVI->pPcRow_PC) { SG_ASSERT_RELEASE_RETURN( (pLVI->pPcRow_PC->p_d_sparse) ); *pAttrbits = pLVI->pPcRow_PC->p_d_sparse->attrbits; } else if (pLVI->pPrescanRow->pPcRow_Ref) { SG_ASSERT_RELEASE_RETURN( (pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse) ); *pAttrbits = pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse->attrbits; } else { SG_ERR_THROW2_RETURN( SG_ERR_NOTIMPLEMENTED, (pCtx, "GetCurrentAttrbits: unhandled case when sparse for '%s'.", SG_string__sz(pLVI->pStringEntryname)) ); } } else if (pLVI->pPrescanRow->pRD) { SG_ERR_CHECK_RETURN( sg_wc_readdir__row__get_attrbits(pCtx, pWcTx, pLVI->pPrescanRow->pRD) ); if (pLVI->pPcRow_PC) SG_ERR_CHECK_RETURN( sg_wc_attrbits__compute_effective_attrbits(pCtx, pWcTx->pDb->pAttrbitsData, pLVI->pPcRow_PC->ref_attrbits, *pLVI->pPrescanRow->pRD->pAttrbits, pAttrbits) ); else if (pLVI->pPrescanRow->pPcRow_Ref) SG_ERR_CHECK_RETURN( sg_wc_attrbits__compute_effective_attrbits(pCtx, pWcTx->pDb->pAttrbitsData, pLVI->pPrescanRow->pPcRow_Ref->ref_attrbits, *pLVI->pPrescanRow->pRD->pAttrbits, pAttrbits) ); else if (pLVI->pPrescanRow->pTneRow) SG_ERR_CHECK_RETURN( sg_wc_attrbits__compute_effective_attrbits(pCtx, pWcTx->pDb->pAttrbitsData, pLVI->pPrescanRow->pTneRow->p_d->attrbits, *pLVI->pPrescanRow->pRD->pAttrbits, pAttrbits) ); else *pAttrbits = *pLVI->pPrescanRow->pRD->pAttrbits; } else if (pLVI->pPrescanRow->pTneRow) { *pAttrbits = pLVI->pPrescanRow->pTneRow->p_d->attrbits; } else { *pAttrbits = SG_WC_ATTRBITS__ZERO; } #if 1 && TRACE_WC_ATTRBITS SG_ERR_IGNORE( SG_console(pCtx, SG_CS_STDERR, "GetCurrentAttrbits: yielded %x: %s\n", (SG_uint32)(*pAttrbits), SG_string__sz(pLVI->pStringEntryname)) ); #endif }
void sg_wc_tx__apply__store_symlink(SG_context * pCtx, SG_wc_tx * pWcTx, const SG_vhash * pvh) { SG_pathname * pPath = NULL; SG_string * pStringSymlink = NULL; const char * pszRepoPath; // we do not own this const char * pszHidExpected; // we do not own this char * pszHidObserved = NULL; sg_wc_liveview_item * pLVI; // we do not own this SG_int64 alias; SG_bool bKnown; SG_bool bDontBother_BlobEncoding; SG_bool bSrcIsSparse; SG_ERR_CHECK( SG_vhash__get__sz( pCtx, pvh, "src", &pszRepoPath) ); SG_ERR_CHECK( SG_vhash__get__int64(pCtx, pvh, "alias", &alias) ); SG_ERR_CHECK( SG_vhash__get__sz( pCtx, pvh, "hid", &pszHidExpected) ); SG_ERR_CHECK( SG_vhash__get__bool( pCtx, pvh, "src_sparse", &bSrcIsSparse) ); #if TRACE_WC_TX_APPLY SG_ERR_IGNORE( SG_console(pCtx, SG_CS_STDERR, ("sg_wc_tx__apply__store_symlink: '%s' [src-sparse %d]\n"), pszRepoPath, bSrcIsSparse) ); #endif SG_ERR_CHECK( sg_wc_tx__liveview__fetch_random_item(pCtx, pWcTx, alias, &bKnown, &pLVI) ); SG_ASSERT( (bSrcIsSparse == SG_WC_PRESCAN_FLAGS__IS_CONTROLLED_SPARSE(pLVI->scan_flags_Live)) ); if (bSrcIsSparse) { // We've been asked to store the target of the symlink ***during a COMMIT*** // and are given the *Expected-HID* (and we need to get the actual target // from the WD) and it is assumed that that will generate the same HID // that we were given. // // However, if the symlink is sparse (not populated) we can't do __readlink() // to get the (current) target. So we have to // assume that we already have a blob in the repo for it. // // Since sparse items now have p_d_sparse dynamic data in tbl_PC, we assume // that whoever last modified the content of the symlink and set p_d_sparse->pszHid // also recorded the blob we need to be present now. (See __apply__overwrite_symlink()) // // for sanity's sake verify that we already have this blob in the repo. SG_uint64 len = 0; SG_ERR_CHECK( SG_repo__fetch_blob__begin(pCtx, pWcTx->pDb->pRepo, pszHidExpected, SG_TRUE, NULL, NULL, NULL, &len, NULL) ); // so we don't need to do anything because we already // have a copy of this blob in the repo. return; } // We never bother compressing/encoding the symlink content // since it is so short. bDontBother_BlobEncoding = SG_TRUE; SG_ERR_CHECK( sg_wc_db__path__sz_repopath_to_absolute(pCtx, pWcTx->pDb, pszRepoPath, &pPath) ); SG_ERR_CHECK( SG_fsobj__readlink(pCtx, pPath, &pStringSymlink) ); SG_ERR_CHECK( SG_committing__add_bytes__string(pCtx, pWcTx->pCommittingInProgress, pStringSymlink, bDontBother_BlobEncoding, &pszHidObserved) ); // See note in __apply__store_file() about race condition. // If the HID computed now differs from what we thought // it should be, we lost the race. if (strcmp(pszHidObserved, pszHidExpected) != 0) SG_ERR_THROW2( SG_ERR_ASSERT, (pCtx, "The symlink '%s' changed during the commit.", pszRepoPath) ); fail: SG_PATHNAME_NULLFREE(pCtx, pPath); SG_STRING_NULLFREE(pCtx, pStringSymlink); SG_NULLFREE(pCtx, pszHidObserved); }