void SG_password__forget_all(SG_context *pCtx)
{
	SG_string* pstrFilter = NULL;
	WCHAR* pwszFilter = NULL;
	DWORD numCreds = 0;
	PCREDENTIAL* paCreds = NULL;
	SG_uint32 i;

	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pstrFilter, KEY_PREFIX)  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrFilter, KEY_SEPARATOR)  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrFilter, "*")  );
	SG_ERR_CHECK(  SG_utf8__extern_to_os_buffer__wchar(pCtx, SG_string__sz(pstrFilter), &pwszFilter, NULL)  );

	if ( !CredEnumerateW( pwszFilter, 0, &numCreds, &paCreds) )
	{
		DWORD err = GetLastError();
		if ( ERROR_NOT_FOUND != err)
			SG_ERR_THROW2( SG_ERR_GETLASTERROR(err), (pCtx, "%s", "unable to enumerate saved credentials") );
	}

	for (i = 0; i < numCreds; i++)
	{
		if ( !CredDeleteW( paCreds[i]->TargetName, CRED_TYPE_GENERIC, 0) )
			SG_ERR_THROW2( SG_ERR_GETLASTERROR(GetLastError()), (pCtx, "%s", "unable to delete credential") );
	}

	/* fall through */
fail:
	SG_NULLFREE(pCtx, pwszFilter);
	SG_STRING_NULLFREE(pCtx, pstrFilter);
}
void sg_treediff_cache::NotifyExplorerToRefreshPath(SG_context * pCtx, wxFileName path, bool bSpecific)
{
	SG_string * pstrFullPath = NULL;
	wxString fullName = path.GetFullName();

	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pstrFullPath, path.GetFullPath().ToUTF8())  );

	//Use %s to handle getting a path that has a %s in it.
	//SG_ERR_CHECK(  SG_UTF8__INTERN_FROM_OS_BUFFER(pCtx, pstrFullPath, path.GetFullPath().ToStdWstring())  );
	//SG_log__report_verbose(pCtx, "FS_CHANGE(%s):	%s", (const char *)ExplainAction(dwAction), SG_string__sz(pstrFullPath)); 

	{
		clearCache(pCtx); 
		if (!m_bIsWin7) //if we're running on XP or Vista
		{
			wxCriticalSectionLocker lock(m_notify_critical_section);
			m_bHaveGottenNewFileChanges = true;
			//For some reason, on XP and Vista, we need to notify for every folder up to the root.
			if (path.FileExists() || path.DirExists())
			{
				if (m_aPathsToNotify.Index(path.GetFullPath()) == wxNOT_FOUND)
					m_aPathsToNotify.Add(path.GetFullPath());
			}

			wxFileName currentDir = wxFileName::DirName(path.GetPath(wxPATH_GET_VOLUME));
			wxFileName topDir = GetTopDir();
			//currentDir = topDir;
			
			while (topDir.GetDirCount() < currentDir.GetDirCount())
			{
				if (m_aPathsToNotify.Index(currentDir.GetFullPath()) == wxNOT_FOUND)
					m_aPathsToNotify.Add(currentDir.GetFullPath());
				currentDir.RemoveLastDir();
			}
		}
		else
		{
			wxCriticalSectionLocker lock(m_notify_critical_section);
			m_bHaveGottenNewFileChanges = true;
			//On Win 7, just notifying for the top of the working folder 
			//will cause a recursive refresh down the tree.
			if (m_aPathsToNotify.Index(GetTopDir().GetFullPath()) == wxNOT_FOUND)
				m_aPathsToNotify.Add(GetTopDir().GetFullPath());
			if (bSpecific)
				m_aPathsToNotify.Add(path.GetFullPath());
		}
	}
fail:
	return;
}
예제 #3
0
static void _getEncoded(
	SG_context *pCtx,
	const char *raw,
	SG_string *encoded)
{
	SG_string *rawstr = NULL;

	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &rawstr, raw)  );

	SG_ERR_CHECK(  SG_htmlencode(pCtx, rawstr, encoded)  );

fail:
	SG_STRING_NULLFREE(pCtx, rawstr);
}
/**
 * "Throws" SG_ERR_NOT_A_WORKING_COPY if no --repo was specified and the cwd's not inside a working copy.
 */
void SG_cmd_util__get_descriptor_name_from_options_or_cwd(SG_context* pCtx, SG_option_state* pOptSt, SG_string** ppstrDescriptorName, SG_pathname** ppPathCwd)
{
	SG_string* pstr = NULL;
	SG_pathname* pPathCwd = NULL;

	SG_NULLARGCHECK_RETURN(pOptSt);

	if (pOptSt->psz_repo)
		SG_STRING__ALLOC__SZ(pCtx, &pstr, pOptSt->psz_repo);
	else
		SG_ERR_CHECK(  SG_cmd_util__get_descriptor_name_from_cwd(pCtx, &pstr, &pPathCwd)  );

	SG_RETURN_AND_NULL(pstr, ppstrDescriptorName);
	SG_RETURN_AND_NULL(pPathCwd, ppPathCwd);

fail:
	SG_STRING_NULLFREE(pCtx, pstr);
	SG_PATHNAME_NULLFREE(pCtx, pPathCwd);
}
static void _get_key(SG_context* pCtx, const char* szRepoSpec, const char* szUserName, SG_string** ppstrKey)
{
	SG_string* pstrKey = NULL;
	SG_string* pstrHostname = NULL;

	SG_ERR_CHECK(  _get_host(pCtx, szRepoSpec, &pstrHostname)  );

	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pstrKey, KEY_PREFIX KEY_SEPARATOR)  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrKey, SG_string__sz(pstrHostname))  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrKey, KEY_SEPARATOR)  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrKey, szUserName)  );

	*ppstrKey = pstrKey;
	pstrKey = NULL;

	/* fall through */
fail:
	SG_STRING_NULLFREE(pCtx, pstrKey);
	SG_STRING_NULLFREE(pCtx, pstrHostname);
}
예제 #6
0
void SG_workingdir__wdpath_to_repopath(
	SG_context* pCtx,
	const SG_pathname* pPathWorkingDirectoryTop,
	const SG_pathname* pPath,
	SG_bool bFinalSlash,
	SG_string** ppStrRepoPath)
{
	SG_string* pstrRoot = NULL;
	SG_string* pstrRelative = NULL;
	SG_string* pstrRepoPath = NULL;

    /* TODO fix the following line to get the real anchor path for this working
     * dir */
	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pstrRoot, "@/")  );

	SG_ERR_CHECK(  SG_pathname__make_relative(pCtx, pPathWorkingDirectoryTop, pPath, &pstrRelative)  );

	if (pstrRelative)
	{
		SG_ERR_CHECK(  SG_repopath__combine(pCtx, pstrRoot, pstrRelative, bFinalSlash, &pstrRepoPath)  );
		SG_STRING_NULLFREE(pCtx, pstrRelative);
		SG_STRING_NULLFREE(pCtx, pstrRoot);

		*ppStrRepoPath = pstrRepoPath;
	}
	else
	{
		*ppStrRepoPath = pstrRoot;
	}

	return;

fail:
	SG_STRING_NULLFREE(pCtx, pstrRoot);
	SG_STRING_NULLFREE(pCtx, pstrRelative);
	SG_STRING_NULLFREE(pCtx, pstrRepoPath);
}
/**
 * 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);
}
void SG_validate__sanitize(
    SG_context* pCtx,
    const char* szValue,
    SG_uint32   uMin,
    SG_uint32   uMax,
    const char* szInvalids,
    SG_uint32   uFixFlags,
    const char* szReplace,
    const char* szAdd,
    SG_string** ppSanitized
)
{
    SG_string* sSanitized = NULL;

    SG_NULLARGCHECK(ppSanitized);

    // treat NULL replacement string as empty
    if (szReplace == NULL)
    {
        szReplace = "";
    }

    // allocate our result string
    SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &sSanitized, szValue)  );

    // if we need to sanitize bad characters, do that
    // Note: We do this first because sanitizing characters might change the
    //       length of the string and affect the min/max length check.
    if (uFixFlags & SG_VALIDATE__RESULT__INVALID_CHARACTER)
    {
        SG_ERR_CHECK(  _replace_chars_with_string(pCtx, sSanitized, szInvalids, szReplace)  );
    }
    if (uFixFlags & SG_VALIDATE__RESULT__CONTROL_CHARACTER)
    {
        SG_ERR_CHECK(  _replace_chars_with_string(pCtx, sSanitized, SG_VALIDATE__CHARS__CONTROL, szReplace)  );
    }

    // if we need to lengthen the string, do that
    // Note: We do this prior to checking the max length because we have more fine
    //       grained control over reducing length than we do over expanding it.  We
    //       can remove individual characters, but only add characters in blocks of
    //       strlen(szAdd).  If uMin and uMax are close to each other, then adding
    //       a single szAdd might take us over uMax.  If that happens, we want to
    //       be able to trim that back down to uMax afterward.
    if (uFixFlags & SG_VALIDATE__RESULT__TOO_SHORT)
    {
        SG_uint32 uSanitized = 0u;
        SG_uint32 uAdd       = 0u;

        SG_ARGCHECK(szAdd != NULL && SG_STRLEN(szAdd) > 0u, szAdd);

        // get the length of both strings
        SG_ERR_CHECK(  SG_utf8__length_in_characters__sz(pCtx, SG_string__sz(sSanitized), &uSanitized)  );
        SG_ERR_CHECK(  SG_utf8__length_in_characters__sz(pCtx, szAdd, &uAdd)  );

        // keep adding until the sanitized string is long enough
        while (uSanitized < uMin)
        {
            SG_ERR_CHECK(  SG_string__append__sz(pCtx, sSanitized, szAdd)  );
            uSanitized += uAdd;
        }
    }

    // if we need to shorten the string, do that
    if (uFixFlags & SG_VALIDATE__RESULT__TOO_LONG)
    {
        SG_ERR_CHECK(  _truncate_string(pCtx, sSanitized, uMax)  );
    }

    // return the sanitized result
    *ppSanitized = sSanitized;
    sSanitized = NULL;

fail:
    SG_STRING_NULLFREE(pCtx, sSanitized);
    return;
}
static void _format_comment(SG_context* pCtx, SG_bool onlyIncludeFirstLine, const char* szLinePrefix, const char* szComment, char** ppszReturn)
{
	SG_bool bFoundLineBreak = SG_FALSE;
	SG_string* pstr = NULL;
	SG_uint32 lenPrefix = SG_STRLEN(szLinePrefix);
	SG_uint32 offset;

	{
		const char* pos;
		for (pos = szComment; *pos; pos++)
		{
			if (*pos == SG_CR || *pos == SG_LF)
			{
				bFoundLineBreak = SG_TRUE;
				break;
			}
		}
		if (!bFoundLineBreak)
			return;
		if(onlyIncludeFirstLine)
		{
			SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, &pstr, (const SG_byte*)szComment, (SG_uint32)(pos-szComment))  );
			SG_ERR_CHECK(  SG_string__sizzle(pCtx, &pstr, (SG_byte**)ppszReturn, NULL)  );
			return;
		}
		offset = (SG_uint32)(pos-szComment);
	}

	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pstr, szComment)  );
	while (offset < SG_string__length_in_bytes(pstr))
	{
		SG_byte current;
		SG_ERR_CHECK(  SG_string__get_byte_l(pCtx, pstr, offset, &current)  );
		if (current == SG_CR)
		{
			SG_byte next;
			bFoundLineBreak = SG_TRUE;
			SG_ERR_CHECK(  SG_string__get_byte_l(pCtx, pstr, offset+1, &next)  );
			if (next != SG_LF)
			{
				// Mac format, lines end with \r only. Consoles will not advance a line.
				SG_ERR_CHECK(  SG_string__insert__sz(pCtx, pstr, offset+1, "\n")  );
			}
			offset++;
		}
		else if (current == SG_LF)
		{
			bFoundLineBreak = SG_TRUE;
		}
		
		offset++;

		if (bFoundLineBreak)
		{
			SG_ERR_CHECK(  SG_string__insert__sz(pCtx, pstr, offset, szLinePrefix)  );
			offset += lenPrefix;
			bFoundLineBreak = SG_FALSE;
		}
	}

	SG_ERR_CHECK(  SG_string__sizzle(pCtx, &pstr, (SG_byte**)ppszReturn, NULL)  );

	return;

fail:
	SG_STRING_NULLFREE(pCtx, pstr);
}
void SG_cmd_util__get_username_and_password(
	SG_context *pCtx,
	const char *szWhoami,
	SG_bool force_whoami,
	SG_bool bHadSavedCredentials,
	SG_uint32 kAttempt,
	SG_string **ppUsername,
	SG_string **ppPassword
	)
{
	SG_string * pUsername = NULL;
	SG_string * pPassword = NULL;

	SG_NULLARGCHECK_RETURN(ppPassword);
	SG_NULLARGCHECK_RETURN(ppUsername);

	if (kAttempt == 0)
	{
		if (bHadSavedCredentials)
		{
			SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR,
									  "\nAuthorization required.  Saved username/password not valid.\n")  );
		}
		else
		{
			SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, "\nAuthorization required.")  );
			if (SG_password__supported())
				SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, " Use --remember to save this password.")  );
			SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, "\n")  );
		}
	}
	else if (kAttempt >= 3)
	{
		SG_ERR_THROW(  SG_ERR_AUTHORIZATION_TOO_MANY_ATTEMPTS  );
	}
	else
	{
		SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, "\nInvalid username or password. Please try again.\n")  );
	}

	if(szWhoami!=NULL && force_whoami)
	{
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pUsername, szWhoami)  );

		SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, "Enter password for %s: ", szWhoami)  );
		SG_ERR_CHECK(  SG_console__get_password(pCtx, &pPassword)  );
	}
	else
	{
		if(szWhoami)
		{
			SG_bool bAllWhitespace = SG_FALSE;
			SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, "Enter username [%s]: ", szWhoami)  );
			SG_ERR_CHECK(  SG_console__readline_stdin(pCtx, &pUsername)  );
			SG_ERR_CHECK(  SG_sz__is_all_whitespace(pCtx, SG_string__sz(pUsername), &bAllWhitespace)  );
			if(bAllWhitespace)
				SG_ERR_CHECK(  SG_string__set__sz(pCtx, pUsername, szWhoami)  );
		}
		else
		{
			SG_ERR_CHECK(  SG_console(pCtx, SG_CS_STDERR, "Enter username: "******"Enter password: ")  );
		SG_ERR_CHECK(  SG_console__get_password(pCtx, &pPassword)  );
	}

	*ppUsername = pUsername;
	*ppPassword = pPassword;

	return;
fail:
	SG_STRING_NULLFREE(pCtx, pUsername);
	SG_STRING_NULLFREE(pCtx, pPassword);
}
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);
}
예제 #12
0
void SG_time__parse(SG_context* pCtx,const char* pszInputString, SG_int64 * pTime, SG_bool bParseToMaximumValidValue)
{
	SG_string * pWorkingStr = NULL;
	char** splitOnSpaces = NULL;
	char** splitOnColons = NULL;
	char** splitOnDashes = NULL;
	SG_uint32 nCountAfterSplitOnSpaces = 0;
	SG_uint32 nCountAfterSplitOnDashes = 0;
	SG_uint32 nCountAfterSplitOnColons = 0;
	SG_string * pWorkingStringDate = NULL;
	SG_string * pWorkingStringTime = NULL;
	time_t resultTicks = 0;
	struct tm result;
	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pWorkingStr, pszInputString)  );
	//This understands exactly two formats
	//YYYY-MM-DD
	//
	//and
	//YYYY-MM-DD hh:mm:ss
	//The local time zone is always assumed.
	SG_ERR_CHECK(  SG_string__split__asciichar(pCtx, pWorkingStr, ' ', 2, &splitOnSpaces, &nCountAfterSplitOnSpaces) );
	if (nCountAfterSplitOnSpaces == 1)
	{
		//YYYY-M-DD

		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pWorkingStringDate, splitOnSpaces[0])  );
		SG_ERR_CHECK(  SG_string__split__asciichar(pCtx, pWorkingStringDate, '-', 3, &splitOnDashes, &nCountAfterSplitOnDashes) );

		if(nCountAfterSplitOnDashes == 3)
		{
			SG_uint32 year = 0;
			SG_uint32 month = 0;
			SG_uint32 dayOfMonth = 0;

			SG_ERR_CHECK(  SG_uint32__parse(pCtx, &year, splitOnDashes[0])  );
			SG_ERR_CHECK(  SG_uint32__parse(pCtx, &month, splitOnDashes[1])  );
			SG_ERR_CHECK(  SG_uint32__parse(pCtx, &dayOfMonth, splitOnDashes[2])  );
			if (month<= 0 || month > 12)
				SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
			if (dayOfMonth<= 0 || dayOfMonth > 31)
				SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
			result.tm_year = year - 1900;
			result.tm_mon = month - 1;
			result.tm_mday = dayOfMonth;
			if (bParseToMaximumValidValue == SG_TRUE)
			{
				result.tm_hour = 23;
				result.tm_min = 59;
				result.tm_sec = 59;
			}
			else
			{
				result.tm_hour = 0;
				result.tm_min = 0;
				result.tm_sec = 0;
			}
		}
		else
			SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
	}
	else if (nCountAfterSplitOnSpaces == 2)
	{
		//YYYY-MM-DD hh:mm:ss
		//YYYY-M-DD

		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pWorkingStringDate, splitOnSpaces[0])  );
		SG_ERR_CHECK(  SG_string__split__asciichar(pCtx, pWorkingStringDate, '-', 3, &splitOnDashes, &nCountAfterSplitOnDashes) );

		if(nCountAfterSplitOnDashes == 3)
		{
			SG_uint32 year = 0;
			SG_uint32 month = 0;
			SG_uint32 dayOfMonth = 0;

			SG_ERR_CHECK(  SG_uint32__parse(pCtx, &year, splitOnDashes[0])  );
			SG_ERR_CHECK(  SG_uint32__parse(pCtx, &month, splitOnDashes[1])  );
			SG_ERR_CHECK(  SG_uint32__parse(pCtx, &dayOfMonth, splitOnDashes[2])  );
			if (month<= 0 || month > 12)
				SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
			if (dayOfMonth<= 0 || dayOfMonth > 31)
				SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
			result.tm_year = year - 1900;
			result.tm_mon = month - 1;
			result.tm_mday = dayOfMonth;

		}
		else
			SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pWorkingStringTime, splitOnSpaces[1])  );
		SG_ERR_CHECK(  SG_string__split__asciichar(pCtx, pWorkingStringTime, ':', 3, &splitOnColons, &nCountAfterSplitOnColons) );

		if(nCountAfterSplitOnColons == 3)
		{
			SG_uint32 hour = 0;
			SG_uint32 minute = 0;
			SG_uint32 second = 0;

			SG_ERR_CHECK(  SG_uint32__parse(pCtx, &hour, splitOnColons[0])  );
			SG_ERR_CHECK(  SG_uint32__parse(pCtx, &minute, splitOnColons[1])  );
			SG_ERR_CHECK(  SG_uint32__parse(pCtx, &second, splitOnColons[2])  );
			if (hour > 23)
				SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
			if (minute > 59)
				SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
			if (second > 59)
				SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
			result.tm_hour = hour;
			result.tm_min = minute;
			result.tm_sec = second;
		}
		else
			SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
	}
	else
	{
		//Invalid date.
		SG_ERR_THROW(SG_ERR_DATE_PARSING_ERROR);
	}


	result.tm_isdst = -1;
	resultTicks = mktime(&result);
	if (resultTicks >= 0)
	{
		*pTime = resultTicks;
		*pTime = *pTime * MILLISECONDS_PER_SECOND;
		if (bParseToMaximumValidValue == SG_TRUE)
		{
			*pTime += 999;
		}
	}

	SG_STRING_NULLFREE(pCtx, pWorkingStringTime);
	SG_STRING_NULLFREE(pCtx, pWorkingStringDate);
	if (splitOnColons != NULL)
		SG_ERR_CHECK(  SG_freeStringList(pCtx, (const char ***)&splitOnColons, nCountAfterSplitOnColons)  );
	if (splitOnSpaces != NULL)
		SG_ERR_CHECK(  SG_freeStringList(pCtx, (const char ***)&splitOnSpaces, nCountAfterSplitOnSpaces)  );
	if (splitOnDashes != NULL)
		SG_ERR_CHECK(  SG_freeStringList(pCtx, (const char ***)&splitOnDashes, nCountAfterSplitOnDashes)  );
	SG_STRING_NULLFREE(pCtx, pWorkingStr);
	return;
fail:
	SG_STRING_NULLFREE(pCtx, pWorkingStringTime);
	SG_STRING_NULLFREE(pCtx, pWorkingStringDate);

	if (splitOnColons != NULL && nCountAfterSplitOnColons > 0)
		SG_ERR_IGNORE(  SG_freeStringList(pCtx, (const char ***)&splitOnColons, nCountAfterSplitOnColons)  );
	if (splitOnSpaces != NULL && nCountAfterSplitOnSpaces > 0)
		SG_ERR_IGNORE(  SG_freeStringList(pCtx, (const char ***)&splitOnSpaces, nCountAfterSplitOnSpaces)  );
	if (splitOnDashes != NULL)
		SG_ERR_IGNORE(  SG_freeStringList(pCtx, (const char ***)&splitOnDashes, nCountAfterSplitOnDashes)  );
	SG_STRING_NULLFREE(pCtx, pWorkingStr);
	return;
	}
void sg_wc_liveview_item__alloc__add_special(SG_context * pCtx,
											 sg_wc_liveview_item ** ppLVI,
											 SG_wc_tx * pWcTx,
											 SG_uint64 uiAliasGid,
											 SG_uint64 uiAliasGidParent,
											 const char * pszEntryname,
											 SG_treenode_entry_type tneType,
											 const char * pszHidMerge,
											 SG_int64 attrbits,
											 SG_wc_status_flags statusFlagsAddSpecialReason)
{
	sg_wc_liveview_item * pLVI = NULL;
	SG_bool bFoundIssue = SG_FALSE;

	SG_ERR_CHECK(  SG_alloc1(pCtx, pLVI)  );

	// caller needs to set the backptr if appropriate
	// if/when it adds this LVI to the LVD's vector.
	pLVI->pLiveViewDir = NULL;

	pLVI->uiAliasGid = uiAliasGid;

	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx,
										&pLVI->pStringEntryname,
										pszEntryname)  );

	// During the QUEUE phase where we are doing this
	// special-add, we have not yet actually created
	// the item on disk.  So we should indicate that
	// scandir/readdir didn't know anything about it.
	//
	// TODO 2012/01/31 During the APPLY phase, we need to
	// TODO            fix-up this field.  Because all of
	// TODO            the __get_original_ and __get_current_
	// TODO            routines below assume that this field
	// TODO            is set.  That is, if you want to do
	// TODO            additional operations (like status)
	// TODO            after a merge, for example.
	// TODO
	// TODO 2012/04/12 Think about using pLVI->queuedOverwrite
	// TODO            fields for this.
	// NOTE
	// NOTE 2012/05/16 Setting this field to NULL caused a problem
	// NOTE            in _deal_with_moved_out_list() and
	// NOTE            sg_wc_liveview_item__alter_structure__move_rename()
	// NOTE            during UPDATE when an ADD-SPECIAL item was initially
	// NOTE            PARKED because of a transient collision (because the
	// NOTE            final UNPARK step uses move/rename to do the work).

	pLVI->pPrescanRow = NULL;

	// because a liveview_item must start as an
	// exact clone of a scanrow, there cannot be
	// any in-tx changes yet for it.
	SG_ERR_CHECK(  SG_WC_DB__PC_ROW__ALLOC(pCtx, &pLVI->pPcRow_PC)  );

	if (statusFlagsAddSpecialReason & SG_WC_STATUS_FLAGS__S__MERGE_CREATED)
		pLVI->pPcRow_PC->flags_net = SG_WC_DB__PC_ROW__FLAGS_NET__ADD_SPECIAL_M;
	else if (statusFlagsAddSpecialReason & SG_WC_STATUS_FLAGS__S__UPDATE_CREATED)
		pLVI->pPcRow_PC->flags_net = SG_WC_DB__PC_ROW__FLAGS_NET__ADD_SPECIAL_U;
	else
		SG_ERR_THROW2(  SG_ERR_INVALIDARG,
						(pCtx, "Invalid statusFlagsAddSpecialReason for '%s'", pszEntryname)  );

	pLVI->pPcRow_PC->p_s->uiAliasGid = uiAliasGid;
	pLVI->pPcRow_PC->p_s->uiAliasGidParent = uiAliasGidParent;
	SG_ERR_CHECK(  SG_STRDUP(pCtx, pszEntryname, &pLVI->pPcRow_PC->p_s->pszEntryname)  );
	pLVI->pPcRow_PC->p_s->tneType = tneType;
	if (pszHidMerge)
		SG_ERR_CHECK(  SG_STRDUP(pCtx, pszHidMerge, &pLVI->pPcRow_PC->pszHidMerge)  );

	pLVI->tneType = tneType;
	pLVI->scan_flags_Live = SG_WC_PRESCAN_FLAGS__CONTROLLED_ACTIVE_POSTSCAN;

	if (statusFlagsAddSpecialReason & SG_WC_STATUS_FLAGS__A__SPARSE)
	{
		pLVI->pPcRow_PC->flags_net |= SG_WC_DB__PC_ROW__FLAGS_NET__SPARSE;
		SG_ERR_CHECK(  sg_wc_db__state_dynamic__alloc(pCtx, &pLVI->pPcRow_PC->p_d_sparse)  );
		pLVI->pPcRow_PC->p_d_sparse->attrbits = attrbits;
		if (tneType != SG_TREENODEENTRY_TYPE_DIRECTORY)
		{
			SG_ASSERT( pszHidMerge && *pszHidMerge );
			SG_ERR_CHECK(  SG_STRDUP(pCtx, pszHidMerge, &pLVI->pPcRow_PC->p_d_sparse->pszHid)  );
		}
		pLVI->scan_flags_Live = SG_WC_PRESCAN_FLAGS__CONTROLLED_ACTIVE_SPARSE;	// not |=
	}

	pLVI->pPcRow_PC->ref_attrbits = attrbits;

	SG_ERR_CHECK(  sg_wc_db__issue__get_issue(pCtx, pWcTx->pDb,
											  pLVI->uiAliasGid,
											  &bFoundIssue,
											  &pLVI->statusFlags_x_xr_xu,
											  &pLVI->pvhIssue,
											  &pLVI->pvhSavedResolutions)  );

	*ppLVI = pLVI;
	return;

fail:
	SG_WC_LIVEVIEW_ITEM__NULLFREE(pCtx, pLVI);
}
/**
 * This is a special case of __get_current_content_hid() that
 * basically does that and then fetches the blob and returns
 * the actual target string contained within.
 *
 * Normally, we wouldn't need this specialization, but if we
 * have a QUEUED overwrite-symlink-target, THEN WE NEED TO LIE
 * and return the new/queued value rather than the actual
 * target value that the existing symlink has in the WD.
 *
 */
void sg_wc_liveview_item__get_current_symlink_target(SG_context * pCtx,
													 sg_wc_liveview_item * pLVI,
													 SG_wc_tx * pWcTx,
													 SG_string ** ppStringTarget)
{
	SG_byte*    pBuffer   = NULL;
	SG_uint64   uSize     = 0u;

	if (pLVI->tneType != SG_TREENODEENTRY_TYPE_SYMLINK)
		SG_ERR_THROW2_RETURN(  SG_ERR_INVALIDARG,
							   (pCtx, "GetCurrentSymlinkTarget: '%s' is not a symlink.",
								SG_string__sz(pLVI->pStringEntryname))  );

	if (pLVI->queuedOverwrites.pvhContent)
	{
		// We have a QUEUED operation on this item that changed the
		// contents.  Get the 'current' value from the journal.

		const char * psz = NULL;

		SG_ERR_CHECK(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "target", &psz)  );
		if (psz)
		{
			// last overwrite-type operation gave us a SYMLINK-TARGET.

#if TRACE_WC_LIE
			SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDERR,
									   "GetCurrentSymlinkTarget: using journal '%s' for: %s\n",
									   psz, SG_string__sz(pLVI->pStringEntryname))  );
#endif
			SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, ppStringTarget, psz)  );
			return;
		}

		SG_ERR_CHECK(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "file", &psz)  );
		if (psz)
		{
			// last overwrite-type operation used a TEMP file.
			// this cannot/should not happen.
			SG_ERR_THROW2(  SG_ERR_INVALIDARG,
							(pCtx,
							 "GetCurrentSymlinkTarget: journal contains temp file '%s' for: %s",
							 psz, SG_string__sz(pLVI->pStringEntryname))  );
		}

		SG_ERR_CHECK(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "hid", &psz)  );
		if (psz)
		{
			// last overwrite-type operation used an HID.
			SG_ERR_CHECK(  SG_repo__fetch_blob_into_memory(pCtx, pWcTx->pDb->pRepo, psz, &pBuffer, &uSize)  );
			SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, ppStringTarget, pBuffer, (SG_uint32)uSize)  );
			SG_NULLFREE(pCtx, pBuffer);
			return;
		}
		
		SG_ERR_THROW2(  SG_ERR_NOTIMPLEMENTED,
						(pCtx,
						 "GetCurrentSymlinkTarget: required field missing from vhash for: %s",
						 SG_string__sz(pLVI->pStringEntryname))  );
	}

	// Otherwise, return the current pre-tx value.

	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) );
			SG_ERR_CHECK(  SG_repo__fetch_blob_into_memory(pCtx, pWcTx->pDb->pRepo,
														   pLVI->pPcRow_PC->p_d_sparse->pszHid,
														   &pBuffer, &uSize)  );
			SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, ppStringTarget, pBuffer, (SG_uint32)uSize)  );
			SG_NULLFREE(pCtx, pBuffer);
			return;
		}
		else if (pLVI->pPrescanRow->pPcRow_Ref)
		{
			SG_ASSERT_RELEASE_RETURN( (pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse) );
			SG_ERR_CHECK(  SG_repo__fetch_blob_into_memory(pCtx, pWcTx->pDb->pRepo,
														   pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse->pszHid,
														   &pBuffer, &uSize)  );
			SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, ppStringTarget, pBuffer, (SG_uint32)uSize)  );
			SG_NULLFREE(pCtx, pBuffer);
			return;
		}
		else
		{
			// With the addition of {sparse_hid,sparse_attrbits} to tbl_PC,
			// we should not get here.
			SG_ERR_THROW2_RETURN(  SG_ERR_NOTIMPLEMENTED,
								   (pCtx, "GetCurrentSymlinkTarget: unhandled case when sparse for '%s'.",
									SG_string__sz(pLVI->pStringEntryname))  );
		}
	}
	else if (pLVI->pPrescanRow->pRD)
	{
		const SG_string * pStringTargetRef;
		SG_ERR_CHECK(  sg_wc_readdir__row__get_content_symlink_target(pCtx, pWcTx, pLVI->pPrescanRow->pRD,
																	  &pStringTargetRef)  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__COPY(pCtx, ppStringTarget, pStringTargetRef)  );
		return;
	}
	else if (pLVI->pPrescanRow->pTneRow)
	{
		SG_ERR_CHECK(  SG_repo__fetch_blob_into_memory(pCtx, pWcTx->pDb->pRepo,
													   pLVI->pPrescanRow->pTneRow->p_d->pszHid,
													   &pBuffer, &uSize)  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, ppStringTarget, pBuffer, (SG_uint32)uSize)  );
		SG_NULLFREE(pCtx, pBuffer);
		return;
	}
	else
	{
		// perhaps an ADD-SPECIAL + DELETE
		// or an ADDED+LOST in an UPDATE ?
		SG_ERR_THROW2_RETURN(  SG_ERR_NOTIMPLEMENTED,
							   (pCtx, "GetCurrentSymlinkTarget: unhandled case for '%s'.",
								SG_string__sz(pLVI->pStringEntryname))  );
	}


fail:
	SG_NULLFREE(pCtx, pBuffer);
}
/**
 * Import a raw string (probably as the user typed it) and
 * transform it into a repo-path.  And scrub it as appropriate.
 *
 * This routine is inside WC and takes a "pDb" because we need
 * to be able to interpret a null or relative path and convert
 * them into a wd-root-based current/live repo-path.
 *
 * But we also need to be able to call it for arbitrary-cset-based
 * repo-paths that when we don't have a WD.
 *
 * We return both the observed/computed repo-path and the
 * observed/computed repo-path-domain.  The returned repo-path
 * will contain the repo-path-domain so that they are always
 * in normal form.
 *
 * It is up to the caller to decide whether the domain is
 * valid for their purposes and/or throw.
 *
 */
void sg_wc_db__path__anything_to_repopath(SG_context * pCtx,
										  const sg_wc_db * pDb,
										  const char * pszInput,
										  sg_wc_db__path__import_flags flags,
										  SG_string ** ppStringRepoPath,
										  char * pcDomain)
{
	SG_pathname * pPathTemp = NULL;
	SG_string * pStringTemp = NULL;
	SG_bool bValid;

	if (!pszInput || !*pszInput)	// when null, assume either CWD or ROOT.
	{
		switch (flags)
		{
		default:
		case SG_WC_DB__PATH__IMPORT_FLAGS__TREAT_NULL_AS_ERROR:
			SG_ERR_THROW_RETURN(  SG_ERR_INVALIDARG  );

		case SG_WC_DB__PATH__IMPORT_FLAGS__TREAT_NULL_AS_ROOT:
			SG_ERR_CHECK(  SG_PATHNAME__ALLOC__COPY(pCtx, &pPathTemp, pDb->pPathWorkingDirectoryTop)  );
			SG_ERR_CHECK(  sg_wc_db__path__absolute_to_repopath(pCtx, pDb, pPathTemp, ppStringRepoPath)  );
			SG_PATHNAME_NULLFREE(pCtx, pPathTemp);
			*pcDomain = SG_WC__REPO_PATH_DOMAIN__LIVE;
			return;

		case SG_WC_DB__PATH__IMPORT_FLAGS__TREAT_NULL_AS_CWD:
			SG_ERR_CHECK(  SG_PATHNAME__ALLOC(pCtx, &pPathTemp)  );
			SG_ERR_CHECK(  SG_pathname__set__from_cwd(pCtx, pPathTemp)  );
			SG_ERR_CHECK(  sg_wc_db__path__absolute_to_repopath(pCtx, pDb, pPathTemp, ppStringRepoPath)  );
			SG_PATHNAME_NULLFREE(pCtx, pPathTemp);
			*pcDomain = SG_WC__REPO_PATH_DOMAIN__LIVE;
			return;
		}
	}

	if (pszInput[0] != '@')			// a non-repo pathname
	{
		SG_bool bPathIsRelative = SG_FALSE;
		SG_ERR_CHECK(  SG_pathname__is_relative(pCtx, pszInput, &bPathIsRelative)  );
		if (bPathIsRelative)
			SG_ERR_CHECK(  _check_if_relative_paths_allowed(pCtx, pDb)  );

		SG_ERR_CHECK(  SG_PATHNAME__ALLOC__SZ(pCtx, &pPathTemp, pszInput)  );
		SG_ERR_CHECK(  sg_wc_db__path__absolute_to_repopath(pCtx, pDb, pPathTemp, ppStringRepoPath)  );
		SG_PATHNAME_NULLFREE(pCtx, pPathTemp);
		*pcDomain = SG_WC__REPO_PATH_DOMAIN__LIVE;
		return;
	}

	// if they gave us something that started with a "@" we
	// either return it as is or throw if we can't parse it.
	// this ensures that any error messages don't leave the
	// domain out of the repo-path.

	switch (pszInput[1])
	{
	case 0:
		SG_ERR_THROW2_RETURN(  SG_ERR_INVALIDARG,
							   (pCtx, "The input '%s' is not a valid repo-path input.", pszInput)  );

	case SG_WC__REPO_PATH_DOMAIN__G:		// domain "@g..." is a GID and not a path.
		// A '@g' domain input should look like
		// "@gae9ce8e7eaf04b58aef9470b439329f32249aae2358511e1b77b002500da2b78"
		// That is, the 'g' domain specifier is shared with the 'g' in the
		// full GID.  For now a full GID is required; we don't try to do the
		// unique prefix trick like we do for CSET HIDs in a REV-SPEC.  We
		// may think about adding that later.

		SG_ERR_CHECK(  SG_gid__verify_format(pCtx, &pszInput[1], &bValid)  );
		if (!bValid)
			SG_ERR_THROW2(  SG_ERR_INVALIDARG,
							(pCtx, "The input '%s' is not a valid gid-domain input.", pszInput)  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, ppStringRepoPath, pszInput)  );
		*pcDomain = SG_WC__REPO_PATH_DOMAIN__G;
		return;

	case SG_WC__REPO_PATH_DOMAIN__T:		// domain "@t..." is a temporary GID and not a path.
		// A '@t' domain input should look like
		// "@tae9ce8e7eaf04b58aef9470b439329f32249aae2358511e1b77b002500da2b78"
		// That is, the 't' domain specifier replaces the normal 'g' in the
		// full GID.  For now a full GID is required; we don't try to do the
		// unique prefix trick like we do for CSET HIDs in a REV-SPEC.  We
		// may think about adding that later.  We use a 't' when reporting
		// STATUS rather than a 'g' as a reminder that the ID is only valid
		// until the end of the transaction.
		//
		// make a g-version of the string so we can validate it.
		// but still return the t-version.

		SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pStringTemp)  );
		SG_ERR_CHECK(  SG_string__sprintf(pCtx, pStringTemp, "%c%s",
										  SG_WC__REPO_PATH_DOMAIN__G,
										  &pszInput[2])  );
		SG_ERR_CHECK(  SG_gid__verify_format(pCtx, SG_string__sz(pStringTemp), &bValid)  );
		SG_STRING_NULLFREE(pCtx, pStringTemp);
		if (!bValid)
			SG_ERR_THROW2(  SG_ERR_INVALIDARG,
							(pCtx, "The input '%s' is not a valid temp-gid-domain input.", pszInput)  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, ppStringRepoPath, pszInput)  );
		*pcDomain = SG_WC__REPO_PATH_DOMAIN__T;
		return;

	case SG_WC__REPO_PATH_DOMAIN__LIVE:		// domain "@/foo..." is a normal current/live repo-path
		SG_ERR_CHECK(  _complain_if_unnormalized(pCtx, &pszInput[1])  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, ppStringRepoPath, pszInput)  );
		*pcDomain = SG_WC__REPO_PATH_DOMAIN__LIVE;
		return;

	default:		// we allow any other SINGLE character to be a domain.
		if (pszInput[2] != '/')
			SG_ERR_THROW2_RETURN(  SG_ERR_INVALIDARG,
								   (pCtx, "The input '%s' is not a valid repo-path input.", pszInput)  );

		// we have "@x/foo...." for some character x.
		SG_ERR_CHECK(  _complain_if_unnormalized(pCtx, &pszInput[2])  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, ppStringRepoPath, pszInput)  );
		*pcDomain = pszInput[1];
		return;
	}

fail:
	SG_PATHNAME_NULLFREE(pCtx, pPathTemp);
	SG_STRING_NULLFREE(pCtx, pStringTemp);
}
static void _sg_password__parse_url(
	SG_context *pCtx,
	const char *url,
	SG_bool *pIsValid,
	SG_string **ppProto,
	SG_string **ppServer,
	SG_string **ppPath,
	SG_uint32 *pPort
	)
{
	const char *slash, *colon, *ss;
	SG_string *server = NULL;
	SG_string *path = NULL;
	SG_string *proto = NULL;
	SG_NULLARGCHECK(url);

	*pIsValid = SG_FALSE;

	if (strncmp("http://", url, 7) == 0)
	{
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &proto, "http")  );
		*pPort = 80;
		url += 7;
	}
	else if (strncmp("https://", url, 8) == 0)
	{
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &proto, "https")  );
		*pPort = 443;
		url += 8;
	}
	else
		goto fail;

	slash = strchr(url, '/');

	if (! slash)
		goto fail;

	SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, &server, (const SG_byte *)url, (SG_uint32)(slash - url))  );

	ss = SG_string__sz(server);
	colon = strchr(ss, ':');
	if (colon)
	{
		SG_uint32 port = atoi(colon + 1);
		SG_uint32 plen = colon - ss;
		if (port == 0)
			goto fail;

		*pPort = port;

		SG_ERR_CHECK(  SG_string__remove(pCtx, server, plen, SG_STRLEN(ss) - plen)  );
	}

	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &path, "/")  );

	*ppPath = path;
	path = NULL;
	*ppServer = server;
	server = NULL;
	*ppProto = proto;
	proto = NULL;

	*pIsValid = SG_TRUE;

fail:
	SG_STRING_NULLFREE(pCtx, server);
	SG_STRING_NULLFREE(pCtx, path);
	SG_STRING_NULLFREE(pCtx, proto);
}