Example #1
0
int _tmain(int argc, _TCHAR* argv[])
{
	// we have three parameters
	const TCHAR* src = nullptr;
	const TCHAR* dst = nullptr;
	const TCHAR* wc = nullptr;
	BOOL bErrResursively = FALSE;
	BOOL bErrOnMods = FALSE;
	BOOL bErrOnUnversioned = FALSE;
	BOOL bQuiet = FALSE;
	GitWCRev_t GitStat;

	SetDllDirectory(L"");
	CCrashReportTGit crasher(L"GitWCRev " _T(APP_X64_STRING), TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD, TGIT_VERDATE);

	if (argc >= 2 && argc <= 5)
	{
		// WC path is always first argument.
		wc = argv[1];
	}
	if (argc == 4 || argc == 5)
	{
		// GitWCRev Path Tmpl.in Tmpl.out [-params]
		src = argv[2];
		dst = argv[3];
		if (!PathFileExists(src))
		{
			_tprintf(L"File '%s' does not exist\n", src);
			return ERR_FNF; // file does not exist
		}
	}
	if (argc == 3 || argc == 5)
	{
		// GitWCRev Path -params
		// GitWCRev Path Tmpl.in Tmpl.out -params
		const TCHAR* Params = argv[argc - 1];
		if (Params[0] == L'-')
		{
			if (wcschr(Params, L'e') != 0)
				_setmode(_fileno(stdout), _O_U16TEXT);
			if (wcschr(Params, L'q') != 0)
				bQuiet = TRUE;
			if (wcschr(Params, L'm') != 0)
				bErrOnMods = TRUE;
			if (wcschr(Params, L'u') != 0)
				bErrOnUnversioned = TRUE;
			if (wcschr(Params, L'M') != 0)
			{
				bErrOnMods = TRUE;
				bErrResursively = TRUE;
			}
			if (wcschr(Params, L'U') != 0)
			{
				bErrOnUnversioned = TRUE;
				bErrResursively = TRUE;
			}
			if (wcschr(Params, L'd') != 0)
			{
				if (dst && PathFileExists(dst))
				{
					_tprintf(L"File '%s' already exists\n", dst);
					return ERR_OUT_EXISTS;
				}
			}
			if (wcschr(Params, L's') != 0)
				GitStat.bNoSubmodules = TRUE;
		}
		else
		{
			// Bad params - abort and display help.
			wc = nullptr;
		}
	}

	if (!wc)
	{
		_tprintf(L"GitWCRev %d.%d.%d, Build %d - %s\n\n", TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD, _T(TGIT_PLATFORM));
		_putts(_T(HelpText1));
		_putts(_T(HelpText2));
		_putts(_T(HelpText3));
		_putts(_T(HelpText4));
		_putts(_T(HelpText5));
		return ERR_SYNTAX;
	}

	DWORD reqLen = GetFullPathName(wc, 0, nullptr, nullptr);
	auto wcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
	GetFullPathName(wc, reqLen, wcfullPath.get(), nullptr);
	// GetFullPathName() sometimes returns the full path with the wrong
	// case. This is not a problem on Windows since its filesystem is
	// case-insensitive. But for Git that's a problem if the wrong case
	// is inside a working copy: the git index is case sensitive.
	// To fix the casing of the path, we use a trick:
	// convert the path to its short form, then back to its long form.
	// That will fix the wrong casing of the path.
	int shortlen = GetShortPathName(wcfullPath.get(), nullptr, 0);
	if (shortlen)
	{
		auto shortPath = std::make_unique<TCHAR[]>(shortlen + 1);
		if (GetShortPathName(wcfullPath.get(), shortPath.get(), shortlen + 1))
		{
			reqLen = GetLongPathName(shortPath.get(), nullptr, 0);
			wcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
			GetLongPathName(shortPath.get(), wcfullPath.get(), reqLen);
		}
	}
	wc = wcfullPath.get();
	std::unique_ptr<TCHAR[]> dstfullPath;
	if (dst)
	{
		reqLen = GetFullPathName(dst, 0, nullptr, nullptr);
		dstfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
		GetFullPathName(dst, reqLen, dstfullPath.get(), nullptr);
		shortlen = GetShortPathName(dstfullPath.get(), nullptr, 0);
		if (shortlen)
		{
			auto shortPath = std::make_unique<TCHAR[]>(shortlen + 1);
			if (GetShortPathName(dstfullPath.get(), shortPath.get(), shortlen+1))
			{
				reqLen = GetLongPathName(shortPath.get(), nullptr, 0);
				dstfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
				GetLongPathName(shortPath.get(), dstfullPath.get(), reqLen);
			}
		}
		dst = dstfullPath.get();
	}
	std::unique_ptr<TCHAR[]> srcfullPath;
	if (src)
	{
		reqLen = GetFullPathName(src, 0, nullptr, nullptr);
		srcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
		GetFullPathName(src, reqLen, srcfullPath.get(), nullptr);
		shortlen = GetShortPathName(srcfullPath.get(), nullptr, 0);
		if (shortlen)
		{
			auto shortPath = std::make_unique<TCHAR[]>(shortlen + 1);
			if (GetShortPathName(srcfullPath.get(), shortPath.get(), shortlen+1))
			{
				reqLen = GetLongPathName(shortPath.get(), nullptr, 0);
				srcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
				GetLongPathName(shortPath.get(), srcfullPath.get(), reqLen);
			}
		}
		src = srcfullPath.get();
	}

	if (!PathFileExists(wc))
	{
		_tprintf(L"Directory or file '%s' does not exist\n", wc);
		if (wcschr(wc, '\"')) // dir contains a quotation mark
		{
			_tprintf(L"The WorkingCopyPath contains a quotation mark.\n");
			_tprintf(L"this indicates a problem when calling GitWCRev from an interpreter which treats\n");
			_tprintf(L"a backslash char specially.\n");
			_tprintf(L"Try using double backslashes or insert a dot after the last backslash when\n");
			_tprintf(L"calling GitWCRev\n");
			_tprintf(L"Examples:\n");
			_tprintf(L"GitWCRev \"path to wc\\\\\"\n");
			_tprintf(L"GitWCRev \"path to wc\\.\"\n");
		}
		return ERR_FNF; // dir does not exist
	}
	std::unique_ptr<char[]> pBuf;
	DWORD readlength = 0;
	size_t filelength = 0;
	size_t maxlength  = 0;
	if (dst)
	{
		// open the file and read the contents
		CAutoFile hFile = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, 0);
		if (!hFile)
		{
			_tprintf(L"Unable to open input file '%s'\n", src);
			return ERR_OPEN; // error opening file
		}
		filelength = GetFileSize(hFile, nullptr);
		if (filelength == INVALID_FILE_SIZE)
		{
			_tprintf(L"Could not determine file size of '%s'\n", src);
			return ERR_READ;
		}
		maxlength = filelength + 8192; // We might be increasing file size.
		pBuf = std::make_unique<char[]>(maxlength);
		if (!pBuf)
		{
			_tprintf(L"Could not allocate enough memory!\n");
			return ERR_ALLOC;
		}
		if (!ReadFile(hFile, pBuf.get(), (DWORD)filelength, &readlength, nullptr))
		{
			_tprintf(L"Could not read the file '%s'\n", src);
			return ERR_READ;
		}
		if (readlength != filelength)
		{
			_tprintf(L"Could not read the file '%s' to the end!\n", src);
			return ERR_READ;
		}
	}

	git_libgit2_init();

	// Now check the status of every file in the working copy
	// and gather revision status information in GitStat.
	int err = GetStatus(wc, GitStat);

	git_libgit2_shutdown();
	if (err)
		return err;

	if (!bQuiet)
	{
		char wcfull_oem[MAX_PATH] = { 0 };
		CharToOem(wc, wcfull_oem);
		_tprintf(L"GitWCRev: '%hs'\n", wcfull_oem);
	}

	if (bErrOnMods && (GitStat.HasMods || GitStat.bHasSubmoduleNewCommits || (bErrResursively && GitStat.bHasSubmoduleMods)))
	{
		if (!bQuiet)
			_tprintf(L"Working tree has uncomitted modifications!\n");
		return ERR_GIT_MODS;
	}
	if (bErrOnUnversioned && (GitStat.HasUnversioned || (bErrResursively && GitStat.bHasSubmoduleUnversioned)))
	{
		if (!bQuiet)
			_tprintf(L"Working tree has unversioned items!\n");
		return ERR_GIT_UNVER;
	}

	if (!bQuiet)
	{
		_tprintf(L"HEAD is %s\n", CUnicodeUtils::StdGetUnicode(GitStat.HeadHashReadable).c_str());

		if (GitStat.HasMods)
			_tprintf(L"Uncommitted modifications found\n");

		if (GitStat.HasUnversioned)
			_tprintf(L"Unversioned items found\n");
	}

	if (!dst)
		return 0;

	// now parse the file contents for version defines.
	size_t index = 0;
	while (InsertRevision(VERDEF, pBuf.get(), index, filelength, maxlength, &GitStat));
	index = 0;
	while (InsertRevisionW(TEXT(VERDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, &GitStat));

	index = 0;
	while (InsertRevision(VERDEFSHORT, pBuf.get(), index, filelength, maxlength, &GitStat));
	index = 0;
	while (InsertRevisionW(TEXT(VERDEFSHORT), (wchar_t*)pBuf.get(), index, filelength, maxlength, &GitStat));

	index = 0;
	while (InsertDate(DATEDEF, pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
	index = 0;
	while (InsertDateW(TEXT(DATEDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));

	index = 0;
	while (InsertDate(DATEDEFUTC, pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
	index = 0;
	while (InsertDateW(TEXT(DATEDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));

	index = 0;
	while (InsertDate(DATEWFMTDEF, pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
	index = 0;
	while (InsertDateW(TEXT(DATEWFMTDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
	index = 0;
	while (InsertDate(DATEWFMTDEFUTC, pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
	index = 0;
	while (InsertDateW(TEXT(DATEWFMTDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));

	index = 0;
	while (InsertDate(NOWDEF, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
	index = 0;
	while (InsertDateW(TEXT(NOWDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));

	index = 0;
	while (InsertDate(NOWDEFUTC, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
	index = 0;
	while (InsertDateW(TEXT(NOWDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));

	index = 0;
	while (InsertDate(NOWWFMTDEF, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
	index = 0;
	while (InsertDateW(TEXT(NOWWFMTDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));

	index = 0;
	while (InsertDate(NOWWFMTDEFUTC, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
	index = 0;
	while (InsertDateW(TEXT(NOWWFMTDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));

	index = 0;
	while (InsertBoolean(MODDEF, pBuf.get(), index, filelength, GitStat.HasMods || GitStat.bHasSubmoduleNewCommits));
	index = 0;
	while (InsertBooleanW(TEXT(MODDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.HasMods || GitStat.bHasSubmoduleNewCommits));

	index = 0;
	while (InsertBoolean(UNVERDEF, pBuf.get(), index, filelength, GitStat.HasUnversioned));
	index = 0;
	while (InsertBooleanW(TEXT(UNVERDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.HasUnversioned));

	index = 0;
	while (InsertBoolean(ISTAGGED, pBuf.get(), index, filelength, GitStat.bIsTagged));
	index = 0;
	while (InsertBooleanW(TEXT(ISTAGGED), (wchar_t*)pBuf.get(), index, filelength, GitStat.bIsTagged));

	index = 0;
	while (InsertBoolean(ISINGIT, pBuf.get(), index, filelength, GitStat.bIsGitItem));
	index = 0;
	while (InsertBooleanW(TEXT(ISINGIT), (wchar_t*)pBuf.get(), index, filelength, GitStat.bIsGitItem));

	index = 0;
	while (InsertBoolean(SUBDEF, pBuf.get(), index, filelength, GitStat.bHasSubmodule));
	index = 0;
	while (InsertBooleanW(TEXT(SUBDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.bHasSubmodule));

	index = 0;
	while (InsertBoolean(SUBUP2DATEDEF, pBuf.get(), index, filelength, !GitStat.bHasSubmoduleNewCommits));
	index = 0;
	while (InsertBooleanW(TEXT(SUBUP2DATEDEF), (wchar_t*)pBuf.get(), index, filelength, !GitStat.bHasSubmoduleNewCommits));

	index = 0;
	while (InsertBoolean(MODINSUBDEF, pBuf.get(), index, filelength, GitStat.bHasSubmoduleMods));
	index = 0;
	while (InsertBooleanW(TEXT(MODINSUBDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.bHasSubmoduleMods));

	index = 0;
	while (InsertBoolean(UNVERINSUBDEF, pBuf.get(), index, filelength, GitStat.bHasSubmoduleUnversioned));
	index = 0;
	while (InsertBooleanW(TEXT(UNVERINSUBDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.bHasSubmoduleUnversioned));

	index = 0;
	while (InsertBoolean(UNVERFULLDEF, pBuf.get(), index, filelength, GitStat.HasUnversioned || GitStat.bHasSubmoduleUnversioned));
	index = 0;
	while (InsertBooleanW(TEXT(UNVERFULLDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.HasUnversioned || GitStat.bHasSubmoduleUnversioned));

	index = 0;
	while (InsertBoolean(MODFULLDEF, pBuf.get(), index, filelength, GitStat.HasMods || GitStat.bHasSubmoduleMods || GitStat.bHasSubmoduleUnversioned));
	index = 0;
	while (InsertBooleanW(TEXT(MODFULLDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.HasMods || GitStat.bHasSubmoduleMods || GitStat.bHasSubmoduleUnversioned));

	index = 0;
	while (InsertBoolean(MODSFILEDEF, pBuf.get(), index, filelength, GitStat.HasMods));
	index = 0;
	while (InsertBooleanW(TEXT(MODSFILEDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.HasMods));

	index = 0;
	while (InsertNumber(VALDEF, pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
	index = 0;
	while (InsertNumberW(TEXT(VALDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));

	index = 0;
	while (InsertNumber(VALDEFAND, pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
	index = 0;
	while (InsertNumberW(TEXT(VALDEFAND), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));

	index = 0;
	while (InsertNumber(VALDEFOFFSET1, pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
	index = 0;
	while (InsertNumberW(TEXT(VALDEFOFFSET1), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));

	index = 0;
	while (InsertNumber(VALDEFOFFSET2, pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
	index = 0;
	while (InsertNumberW(TEXT(VALDEFOFFSET2), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));

	index = 0;
	while (InsertText(BRANCHDEF, pBuf.get(), index, filelength, maxlength, GitStat.CurrentBranch));
	index = 0;
	while (InsertTextW(TEXT(BRANCHDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.CurrentBranch));

	CAutoFile hFile = CreateFile(dst, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, 0, 0);
	if (!hFile)
	{
		_tprintf(L"Unable to open output file '%s' for writing\n", dst);
		return ERR_OPEN;
	}

	size_t filelengthExisting = GetFileSize(hFile, nullptr);
	BOOL sameFileContent = FALSE;
	if (filelength == filelengthExisting)
	{
		DWORD readlengthExisting = 0;
		auto pBufExisting = std::make_unique<char[]>(filelength);
		if (!ReadFile(hFile, pBufExisting.get(), (DWORD)filelengthExisting, &readlengthExisting, nullptr))
		{
			_tprintf(L"Could not read the file '%s'\n", dst);
			return ERR_READ;
		}
		if (readlengthExisting != filelengthExisting)
		{
			_tprintf(L"Could not read the file '%s' to the end!\n", dst);
			return ERR_READ;
		}
		sameFileContent = (memcmp(pBuf.get(), pBufExisting.get(), filelength) == 0);
	}

	// The file is only written if its contents would change.
	// this object prevents the timestamp from changing.
	if (!sameFileContent)
	{
		SetFilePointer(hFile, 0, nullptr, FILE_BEGIN);

		WriteFile(hFile, pBuf.get(), (DWORD)filelength, &readlength, nullptr);
		if (readlength != filelength)
		{
			_tprintf(L"Could not write the file '%s' to the end!\n", dst);
			return ERR_READ;
		}

		if (!SetEndOfFile(hFile))
		{
			_tprintf(L"Could not truncate the file '%s' to the end!\n", dst);
			return ERR_READ;
		}
	}

	return 0;
}
Example #2
0
int _tmain(int argc, _TCHAR* argv[])
{
    // we have three parameters
    const TCHAR * src = NULL;
    const TCHAR * dst = NULL;
    const TCHAR * wc = NULL;
    BOOL bErrOnMods = FALSE;
    BOOL bErrOnUnversioned = FALSE;
    BOOL bErrOnMixed = FALSE;
    BOOL bQuiet = FALSE;
    BOOL bUseSubWCRevIgnore = TRUE;
    SubWCRev_t SubStat;

    SetDllDirectory(L"");
    CCrashReportTSVN crasher(L"SubWCRev " _T(APP_X64_STRING));

    if (argc >= 2 && argc <= 5)
    {
        // WC path is always first argument.
        wc = argv[1];
    }
    if (argc == 4 || argc == 5)
    {
        // SubWCRev Path Tmpl.in Tmpl.out [-params]
        src = argv[2];
        dst = argv[3];
        if (!PathFileExists(src))
        {
            _tprintf(L"File '%s' does not exist\n", src);
            return ERR_FNF;     // file does not exist
        }
    }
    if (argc == 3 || argc == 5)
    {
        // SubWCRev Path -params
        // SubWCRev Path Tmpl.in Tmpl.out -params
        const TCHAR * Params = argv[argc-1];
        if (Params[0] == '-')
        {
            if (wcschr(Params, 'q') != 0)
                bQuiet = TRUE;
            if (wcschr(Params, 'n') != 0)
                bErrOnMods = TRUE;
            if (wcschr(Params, 'N') != 0)
                bErrOnUnversioned = TRUE;
            if (wcschr(Params, 'm') != 0)
                bErrOnMixed = TRUE;
            if (wcschr(Params, 'd') != 0)
            {
                if ((dst != NULL) && PathFileExists(dst))
                {
                    _tprintf(L"File '%s' already exists\n", dst);
                    return ERR_OUT_EXISTS;
                }
            }
            // the 'f' option is useful to keep the revision which is inserted in
            // the file constant, even if there are commits on other branches.
            // For example, if you tag your working copy, then half a year later
            // do a fresh checkout of that tag, the folder in your working copy of
            // that tag will get the HEAD revision of the time you check out (or
            // do an update). The files alone however won't have their last-committed
            // revision changed at all.
            if (wcschr(Params, 'f') != 0)
                SubStat.bFolders = TRUE;
            if (wcschr(Params, 'e') != 0)
                SubStat.bExternals = TRUE;
            if (wcschr(Params, 'E') != 0)
                SubStat.bExternalsNoMixedRevision = TRUE;
            if (wcschr(Params, 'x') != 0)
                SubStat.bHexPlain = TRUE;
            if (wcschr(Params, 'X') != 0)
                SubStat.bHexX = TRUE;
            if (wcschr(Params, 'F') != 0)
                bUseSubWCRevIgnore = FALSE;
        }
        else
        {
            // Bad params - abort and display help.
            wc = NULL;
        }
    }

    if (wc == NULL)
    {
        _tprintf(L"SubWCRev %d.%d.%d, Build %d - %s\n\n",
            TSVN_VERMAJOR, TSVN_VERMINOR,
            TSVN_VERMICRO, TSVN_VERBUILD,
            _T(TSVN_PLATFORM));
        _putts(_T(HelpText1));
        _putts(_T(HelpText2));
        _putts(_T(HelpText3));
        _putts(_T(HelpText4));
        _putts(_T(HelpText5));
        return ERR_SYNTAX;
    }

    DWORD reqLen = GetFullPathName(wc, 0, NULL, NULL);
    auto wcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
    GetFullPathName(wc, reqLen, wcfullPath.get(), NULL);
    // GetFullPathName() sometimes returns the full path with the wrong
    // case. This is not a problem on Windows since its filesystem is
    // case-insensitive. But for SVN that's a problem if the wrong case
    // is inside a working copy: the svn wc database is case sensitive.
    // To fix the casing of the path, we use a trick:
    // convert the path to its short form, then back to its long form.
    // That will fix the wrong casing of the path.
    int shortlen = GetShortPathName(wcfullPath.get(), NULL, 0);
    if (shortlen)
    {
        auto shortPath = std::make_unique<TCHAR[]>(shortlen + 1);
        if (GetShortPathName(wcfullPath.get(), shortPath.get(), shortlen + 1))
        {
            reqLen = GetLongPathName(shortPath.get(), NULL, 0);
            wcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
            GetLongPathName(shortPath.get(), wcfullPath.get(), reqLen);
        }
    }
    wc = wcfullPath.get();
    std::unique_ptr<TCHAR[]> dstfullPath = nullptr;
    if (dst)
    {
        reqLen = GetFullPathName(dst, 0, NULL, NULL);
        dstfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
        GetFullPathName(dst, reqLen, dstfullPath.get(), NULL);
        shortlen = GetShortPathName(dstfullPath.get(), NULL, 0);
        if (shortlen)
        {
            auto shortPath = std::make_unique<TCHAR[]>(shortlen + 1);
            if (GetShortPathName(dstfullPath.get(), shortPath.get(), shortlen+1))
            {
                reqLen = GetLongPathName(shortPath.get(), NULL, 0);
                dstfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
                GetLongPathName(shortPath.get(), dstfullPath.get(), reqLen);
            }
        }
        dst = dstfullPath.get();
    }
    std::unique_ptr<TCHAR[]> srcfullPath = nullptr;
    if (src)
    {
        reqLen = GetFullPathName(src, 0, NULL, NULL);
        srcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
        GetFullPathName(src, reqLen, srcfullPath.get(), NULL);
        shortlen = GetShortPathName(srcfullPath.get(), NULL, 0);
        if (shortlen)
        {
            auto shortPath = std::make_unique<TCHAR[]>(shortlen + 1);
            if (GetShortPathName(srcfullPath.get(), shortPath.get(), shortlen+1))
            {
                reqLen = GetLongPathName(shortPath.get(), NULL, 0);
                srcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
                GetLongPathName(shortPath.get(), srcfullPath.get(), reqLen);
            }
        }
        src = srcfullPath.get();
    }

    if (!PathFileExists(wc))
    {
        _tprintf(L"Directory or file '%s' does not exist\n", wc);
        if (wcschr(wc, '\"') != NULL) // dir contains a quotation mark
        {
            _tprintf(L"The WorkingCopyPath contains a quotation mark.\n");
            _tprintf(L"this indicates a problem when calling SubWCRev from an interpreter which treats\n");
            _tprintf(L"a backslash char specially.\n");
            _tprintf(L"Try using double backslashes or insert a dot after the last backslash when\n");
            _tprintf(L"calling SubWCRev\n");
            _tprintf(L"Examples:\n");
            _tprintf(L"SubWCRev \"path to wc\\\\\"\n");
            _tprintf(L"SubWCRev \"path to wc\\.\"\n");
        }
        return ERR_FNF;         // dir does not exist
    }
    std::unique_ptr<char[]> pBuf = nullptr;
    DWORD readlength = 0;
    size_t filelength = 0;
    size_t maxlength  = 0;
    if (dst != NULL)
    {
        // open the file and read the contents
        CAutoFile hFile = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
        if (!hFile)
        {
            _tprintf(L"Unable to open input file '%s'\n", src);
            return ERR_OPEN;        // error opening file
        }
        filelength = GetFileSize(hFile, NULL);
        if (filelength == INVALID_FILE_SIZE)
        {
            _tprintf(L"Could not determine file size of '%s'\n", src);
            return ERR_READ;
        }
        maxlength = filelength+4096;    // We might be increasing file size.
        pBuf = std::make_unique<char[]>(maxlength);
        if (pBuf == NULL)
        {
            _tprintf(L"Could not allocate enough memory!\n");
            return ERR_ALLOC;
        }
        if (!ReadFile(hFile, pBuf.get(), (DWORD)filelength, &readlength, NULL))
        {
            _tprintf(L"Could not read the file '%s'\n", src);
            return ERR_READ;
        }
        if (readlength != filelength)
        {
            _tprintf(L"Could not read the file '%s' to the end!\n", src);
            return ERR_READ;
        }
    }
    // Now check the status of every file in the working copy
    // and gather revision status information in SubStat.

    apr_pool_t * pool;
    svn_error_t * svnerr = NULL;
    svn_client_ctx_t * ctx;
    const char * internalpath;

    apr_initialize();
    svn_dso_initialize2();
    apr_pool_create_ex (&pool, NULL, abort_on_pool_failure, NULL);
    svn_client_create_context2(&ctx, NULL, pool);

    size_t ret = 0;
    getenv_s(&ret, NULL, 0, "SVN_ASP_DOT_NET_HACK");
    if (ret)
    {
        svn_wc_set_adm_dir("_svn", pool);
    }

    char *wc_utf8 = Utf16ToUtf8(wc, pool);
    internalpath = svn_dirent_internal_style (wc_utf8, pool);
    if (bUseSubWCRevIgnore)
    {
        const char *wcroot;
        svnerr = svn_client_get_wc_root(&wcroot, internalpath, ctx, pool, pool);
        if ((svnerr == SVN_NO_ERROR) && wcroot)
            LoadIgnorePatterns(wcroot, &SubStat);
        svn_error_clear(svnerr);
        LoadIgnorePatterns(internalpath, &SubStat);
    }

    svnerr = svn_status(    internalpath,   //path
                            &SubStat,       //status_baton
                            TRUE,           //noignore
                            ctx,
                            pool);

    if (svnerr)
    {
        svn_handle_error2(svnerr, stdout, FALSE, "SubWCRev : ");
    }
    TCHAR wcfullpath[MAX_PATH] = { 0 };
    LPTSTR dummy;
    GetFullPathName(wc, MAX_PATH, wcfullpath, &dummy);
    apr_status_t e = 0;
    if (svnerr)
    {
        e = svnerr->apr_err;
        svn_error_clear(svnerr);
    }
    apr_terminate2();
    if (svnerr)
    {
        if (e == SVN_ERR_WC_NOT_DIRECTORY)
            return ERR_NOWC;
        return ERR_SVN_ERR;
    }

    char wcfull_oem[MAX_PATH] = { 0 };
    CharToOem(wcfullpath, wcfull_oem);
    _tprintf(L"SubWCRev: '%hs'\n", wcfull_oem);


    if (bErrOnMods && SubStat.HasMods)
    {
        _tprintf(L"Working copy has local modifications!\n");
        return ERR_SVN_MODS;
    }
    if (bErrOnUnversioned && SubStat.HasUnversioned)
    {
        _tprintf(L"Working copy has unversioned items!\n");
        return ERR_SVN_UNVER;
    }

    if (bErrOnMixed && (SubStat.MinRev != SubStat.MaxRev))
    {
        if (SubStat.bHexPlain)
            _tprintf(L"Working copy contains mixed revisions %lX:%lX!\n", SubStat.MinRev, SubStat.MaxRev);
        else if (SubStat.bHexX)
            _tprintf(L"Working copy contains mixed revisions %#lX:%#lX!\n", SubStat.MinRev, SubStat.MaxRev);
        else
            _tprintf(L"Working copy contains mixed revisions %ld:%ld!\n", SubStat.MinRev, SubStat.MaxRev);
        return ERR_SVN_MIXED;
    }

    if (!bQuiet)
    {
        if (SubStat.bHexPlain)
            _tprintf(L"Last committed at revision %lX\n", SubStat.CmtRev);
        else if (SubStat.bHexX)
            _tprintf(L"Last committed at revision %#lX\n", SubStat.CmtRev);
        else
            _tprintf(L"Last committed at revision %ld\n", SubStat.CmtRev);

        if (SubStat.MinRev != SubStat.MaxRev)
        {
            if (SubStat.bHexPlain)
                _tprintf(L"Mixed revision range %lX:%lX\n", SubStat.MinRev, SubStat.MaxRev);
            else if (SubStat.bHexX)
                _tprintf(L"Mixed revision range %#lX:%#lX\n", SubStat.MinRev, SubStat.MaxRev);
            else
                _tprintf(L"Mixed revision range %ld:%ld\n", SubStat.MinRev, SubStat.MaxRev);
        }
        else
        {
            if (SubStat.bHexPlain)
                _tprintf(L"Updated to revision %lX\n", SubStat.MaxRev);
            else if (SubStat.bHexX)
                _tprintf(L"Updated to revision %#lX\n", SubStat.MaxRev);
            else
                _tprintf(L"Updated to revision %ld\n", SubStat.MaxRev);
        }

        if (SubStat.HasMods)
        {
            _tprintf(L"Local modifications found\n");
        }

        if (SubStat.HasUnversioned)
        {
            _tprintf(L"Unversioned items found\n");
        }
    }

    if (dst == NULL)
    {
        return 0;
    }

    // now parse the file contents for version defines.

    size_t index = 0;

    while (InsertRevision(VERDEF, pBuf.get(), index, filelength, maxlength, -1, SubStat.CmtRev, &SubStat));
    index = 0;
    while (InsertRevisionW(TEXT(VERDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, -1, SubStat.CmtRev, &SubStat));

    index = 0;
    while (InsertRevision(VERDEFAND, pBuf.get(), index, filelength, maxlength, -1, SubStat.CmtRev, &SubStat));
    index = 0;
    while (InsertRevisionW(TEXT(VERDEFAND), (wchar_t*)pBuf.get(), index, filelength, maxlength, -1, SubStat.CmtRev, &SubStat));

    index = 0;
    while (InsertRevision(VERDEFOFFSET1, pBuf.get(), index, filelength, maxlength, -1, SubStat.CmtRev, &SubStat));
    index = 0;
    while (InsertRevisionW(TEXT(VERDEFOFFSET1), (wchar_t*)pBuf.get(), index, filelength, maxlength, -1, SubStat.CmtRev, &SubStat));

    index = 0;
    while (InsertRevision(VERDEFOFFSET2, pBuf.get(), index, filelength, maxlength, -1, SubStat.CmtRev, &SubStat));
    index = 0;
    while (InsertRevisionW(TEXT(VERDEFOFFSET2), (wchar_t*)pBuf.get(), index, filelength, maxlength, -1, SubStat.CmtRev, &SubStat));

    index = 0;
    while (InsertRevision(RANGEDEF, pBuf.get(), index, filelength, maxlength, SubStat.MinRev, SubStat.MaxRev, &SubStat));
    index = 0;
    while (InsertRevisionW(TEXT(RANGEDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, SubStat.MinRev, SubStat.MaxRev, &SubStat));

    index = 0;
    while (InsertDate(DATEDEF, pBuf.get(), index, filelength, maxlength, SubStat.CmtDate));
    index = 0;
    while (InsertDateW(TEXT(DATEDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, SubStat.CmtDate));

    index = 0;
    while (InsertDate(DATEDEFUTC, pBuf.get(), index, filelength, maxlength, SubStat.CmtDate));
    index = 0;
    while (InsertDateW(TEXT(DATEDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, SubStat.CmtDate));

    index = 0;
    while (InsertDate(DATEWFMTDEF, pBuf.get(), index, filelength, maxlength, SubStat.CmtDate));
    index = 0;
    while (InsertDateW(TEXT(DATEWFMTDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, SubStat.CmtDate));
    index = 0;
    while (InsertDate(DATEWFMTDEFUTC, pBuf.get(), index, filelength, maxlength, SubStat.CmtDate));
    index = 0;
    while (InsertDateW(TEXT(DATEWFMTDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, SubStat.CmtDate));

    index = 0;
    while (InsertDate(NOWDEF, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
    index = 0;
    while (InsertDateW(TEXT(NOWDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));

    index = 0;
    while (InsertDate(NOWDEFUTC, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
    index = 0;
    while (InsertDateW(TEXT(NOWDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));

    index = 0;
    while (InsertDate(NOWWFMTDEF, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
    index = 0;
    while (InsertDateW(TEXT(NOWWFMTDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));

    index = 0;
    while (InsertDate(NOWWFMTDEFUTC, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
    index = 0;
    while (InsertDateW(TEXT(NOWWFMTDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));

    index = 0;
    while (InsertBoolean(MODDEF, pBuf.get(), index, filelength, SubStat.HasMods));
    index = 0;
    while (InsertBooleanW(TEXT(MODDEF), (wchar_t*)pBuf.get(), index, filelength, SubStat.HasMods));

    index = 0;
    while (InsertBoolean(UNVERDEF, pBuf.get(), index, filelength, SubStat.HasUnversioned));
    index = 0;
    while (InsertBooleanW(TEXT(UNVERDEF), (wchar_t*)pBuf.get(), index, filelength, SubStat.HasUnversioned));

    index = 0;
    while (InsertBoolean(MIXEDDEF, pBuf.get(), index, filelength, (SubStat.MinRev != SubStat.MaxRev) || SubStat.bIsExternalMixed));
    index = 0;
    while (InsertBooleanW(TEXT(MIXEDDEF), (wchar_t*)pBuf.get(), index, filelength, (SubStat.MinRev != SubStat.MaxRev) || SubStat.bIsExternalMixed));

    index = 0;
    while (InsertBoolean(EXTALLFIXED, pBuf.get(), index, filelength, !SubStat.bIsExternalsNotFixed));
    index = 0;
    while (InsertBooleanW(TEXT(EXTALLFIXED), (wchar_t*)pBuf.get(), index, filelength, !SubStat.bIsExternalsNotFixed));

    index = 0;
    while (InsertBoolean(ISTAGGED, pBuf.get(), index, filelength, SubStat.bIsTagged));
    index = 0;
    while (InsertBooleanW(TEXT(ISTAGGED), (wchar_t*)pBuf.get(), index, filelength, SubStat.bIsTagged));

    index = 0;
    while (InsertUrl(URLDEF, pBuf.get(), index, filelength, maxlength, SubStat.Url));
    index = 0;
    while (InsertUrlW(TEXT(URLDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, Utf8ToWide(SubStat.Url).c_str()));

    index = 0;
    while (InsertBoolean(ISINSVN, pBuf.get(), index, filelength, SubStat.bIsSvnItem));
    index = 0;
    while (InsertBooleanW(TEXT(ISINSVN), (wchar_t*)pBuf.get(), index, filelength, SubStat.bIsSvnItem));

    index = 0;
    while (InsertBoolean(NEEDSLOCK, pBuf.get(), index, filelength, SubStat.LockData.NeedsLocks));
    index = 0;
    while (InsertBooleanW(TEXT(NEEDSLOCK), (wchar_t*)pBuf.get(), index, filelength, SubStat.LockData.NeedsLocks));

    index = 0;
    while (InsertBoolean(ISLOCKED, pBuf.get(), index, filelength, SubStat.LockData.IsLocked));
    index = 0;
    while (InsertBooleanW(TEXT(ISLOCKED), (wchar_t*)pBuf.get(), index, filelength, SubStat.LockData.IsLocked));

    index = 0;
    while (InsertDate(LOCKDATE, pBuf.get(), index, filelength, maxlength, SubStat.LockData.CreationDate));
    index = 0;
    while (InsertDateW(TEXT(LOCKDATE), (wchar_t*)pBuf.get(), index, filelength, maxlength, SubStat.LockData.CreationDate));

    index = 0;
    while (InsertDate(LOCKDATEUTC, pBuf.get(), index, filelength, maxlength, SubStat.LockData.CreationDate));
    index = 0;
    while (InsertDateW(TEXT(LOCKDATEUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, SubStat.LockData.CreationDate));

    index = 0;
    while (InsertDate(LOCKWFMTDEF, pBuf.get(), index, filelength, maxlength, SubStat.LockData.CreationDate));
    index = 0;
    while (InsertDateW(TEXT(LOCKWFMTDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, SubStat.LockData.CreationDate));

    index = 0;
    while (InsertDate(LOCKWFMTDEFUTC, pBuf.get(), index, filelength, maxlength, SubStat.LockData.CreationDate));
    index = 0;
    while (InsertDateW(TEXT(LOCKWFMTDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, SubStat.LockData.CreationDate));

    index = 0;
    while (InsertUrl(LOCKOWNER, pBuf.get(), index, filelength, maxlength, SubStat.LockData.Owner));
    index = 0;
    while (InsertUrlW(TEXT(LOCKOWNER), (wchar_t*)pBuf.get(), index, filelength, maxlength, Utf8ToWide(SubStat.LockData.Owner).c_str()));

    index = 0;
    while (InsertUrl(LOCKCOMMENT, pBuf.get(), index, filelength, maxlength, SubStat.LockData.Comment));
    index = 0;
    while (InsertUrlW(TEXT(LOCKCOMMENT), (wchar_t*)pBuf.get(), index, filelength, maxlength, Utf8ToWide(SubStat.LockData.Comment).c_str()));

    CAutoFile hFile = CreateFile(dst, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, NULL, NULL);
    if (!hFile)
    {
        _tprintf(L"Unable to open output file '%s' for writing\n", dst);
        return ERR_OPEN;
    }

    size_t filelengthExisting = GetFileSize(hFile, NULL);
    BOOL sameFileContent = FALSE;
    if (filelength == filelengthExisting)
    {
        DWORD readlengthExisting = 0;
        auto pBufExisting = std::make_unique<char[]>(filelength);
        if (!ReadFile(hFile, pBufExisting.get(), (DWORD)filelengthExisting, &readlengthExisting, NULL))
        {
            _tprintf(L"Could not read the file '%s'\n", dst);
            return ERR_READ;
        }
        if (readlengthExisting != filelengthExisting)
        {
            _tprintf(L"Could not read the file '%s' to the end!\n", dst);
            return ERR_READ;
        }
        sameFileContent = (memcmp(pBuf.get(), pBufExisting.get(), filelength) == 0);
    }

    // The file is only written if its contents would change.
    // this object prevents the timestamp from changing.
    if (!sameFileContent)
    {
        SetFilePointer(hFile, 0, NULL, FILE_BEGIN);

        WriteFile(hFile, pBuf.get(), (DWORD)filelength, &readlength, NULL);
        if (readlength != filelength)
        {
            _tprintf(L"Could not write the file '%s' to the end!\n", dst);
            return ERR_READ;
        }

        if (!SetEndOfFile(hFile))
        {
            _tprintf(L"Could not truncate the file '%s' to the end!\n", dst);
            return ERR_READ;
        }
    }

    return 0;
}