1、加强注释
2、上传时,添加-f参数才会强制上传,否则需要根据当前Tag标签信息来判断是否需要上传。
This commit is contained in:
@@ -672,7 +672,6 @@ int HandleSetVerCommand(int argc, TCHAR* argv[])
|
||||
return RewriteSetVerByProjectType(lpRepoPath, strProductVersion, strFileVersion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// 准备C++重写内容。
|
||||
/// </summary>
|
||||
@@ -854,6 +853,14 @@ int HandleRewriteCommand(int argc, TCHAR* argv[])
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序入口:解析命令行参数并分发到各子命令处理函数。
|
||||
/// 无参数时进入交互式 tag 创建模式。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <param name="envp">环境变量数组。</param>
|
||||
/// <returns>命令执行退出码,0 表示成功。</returns>
|
||||
int main(int argc, TCHAR* argv[], TCHAR* envp[])
|
||||
{
|
||||
int nRetCode = 0;
|
||||
@@ -863,10 +870,6 @@ int main(int argc, TCHAR* argv[], TCHAR* envp[])
|
||||
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON_APP)));
|
||||
HMODULE hModule = ::GetModuleHandle(NULL);
|
||||
|
||||
#ifdef _DEBUG
|
||||
Sleep(15000);
|
||||
#endif
|
||||
|
||||
GetDirInfo();
|
||||
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
|
||||
{
|
||||
@@ -911,6 +914,9 @@ int main(int argc, TCHAR* argv[], TCHAR* envp[])
|
||||
|
||||
if (argc >= 2 && _tcsnicmp(argv[1], _T("setver="), 7) == 0)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
Sleep(10000);
|
||||
#endif
|
||||
DWORD dwStartTick = LogCommandStart(_T("setver"));
|
||||
int nCmdRet = HandleSetVerCommand(argc, argv);
|
||||
LogCommandEnd(_T("setver"), dwStartTick, nCmdRet);
|
||||
|
||||
@@ -5,11 +5,24 @@
|
||||
|
||||
extern TCHAR g_szCurModuleDir[MAX_PATH];
|
||||
|
||||
/// <summary>
|
||||
/// 打印无效仓库路径的错误信息。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">无效的路径字符串。</param>
|
||||
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath)
|
||||
{
|
||||
_tprintf(_T("错误: 无效的仓库路径 %s\n"), lpRepoPath == NULL ? _T("<null>") : lpRepoPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 setver / nuitka / pyinstaller 等命令的 repodir= 与 -test 可选参数。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <param name="nStartIndex">开始解析的下标。</param>
|
||||
/// <param name="lpRepoPath">输出:仓库路径,未指定时为当前模块目录。</param>
|
||||
/// <param name="bTestMode">输出:是否启用测试模式。</param>
|
||||
/// <returns>0 成功;3 多余参数;23 无效 repodir。</returns>
|
||||
int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepoPath, BOOL& bTestMode)
|
||||
{
|
||||
lpRepoPath = NULL;
|
||||
@@ -53,6 +66,14 @@ int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepo
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 rewrite 命令的可选 PE 类型(1=exe,2=dll)与 -f 强制标志。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组(argv[0] 为 "rewrite")。</param>
|
||||
/// <param name="nPEType">输出:PE 类型,默认 1。</param>
|
||||
/// <param name="bForceRewrite">输出:是否忽略重写失败。</param>
|
||||
/// <returns>0 成功;22 PE 类型无效或重复。</returns>
|
||||
int ParseRewriteOptions(int argc, TCHAR* argv[], int& nPEType, BOOL& bForceRewrite)
|
||||
{
|
||||
nPEType = 1;
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// 打印无效仓库路径的错误信息。
|
||||
/// </summary>
|
||||
void PrintInvalidRepoPathError(LPCTSTR lpRepoPath);
|
||||
|
||||
/// <summary>
|
||||
/// 解析 setver 等命令的 repodir= 与 -test 可选参数。
|
||||
/// </summary>
|
||||
int ParseSetVerOptions(int argc, TCHAR* argv[], int nStartIndex, LPCTSTR& lpRepoPath, BOOL& bTestMode);
|
||||
|
||||
/// <summary>
|
||||
/// 解析 rewrite 命令的 PE 类型与 -f 强制标志。
|
||||
/// </summary>
|
||||
int ParseRewriteOptions(int argc, TCHAR* argv[], int& nPEType, BOOL& bForceRewrite);
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
#include "gitver.h"
|
||||
#include "gitver_common.h"
|
||||
|
||||
/// <summary>
|
||||
/// 将命令行参数拼接为便于日志输出的字符串(含引号转义)。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <returns>空格分隔、已转义的参数字符串。</returns>
|
||||
CString BuildArgsForLog(int argc, TCHAR* argv[])
|
||||
{
|
||||
CString strArgs;
|
||||
@@ -16,6 +22,11 @@ CString BuildArgsForLog(int argc, TCHAR* argv[])
|
||||
return strArgs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录命令开始时间并输出开始日志。
|
||||
/// </summary>
|
||||
/// <param name="lpCommandName">命令名称。</param>
|
||||
/// <returns>当前 GetTickCount 值,供 LogCommandEnd 计算耗时。</returns>
|
||||
DWORD LogCommandStart(LPCTSTR lpCommandName)
|
||||
{
|
||||
DWORD dwStartTick = GetTickCount();
|
||||
@@ -23,6 +34,11 @@ DWORD LogCommandStart(LPCTSTR lpCommandName)
|
||||
return dwStartTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为命令行参数添加引号转义(含空格、制表符或双引号时加引号)。
|
||||
/// </summary>
|
||||
/// <param name="strArg">原始参数字符串。</param>
|
||||
/// <returns>可直接用于命令行的参数字符串。</returns>
|
||||
CString QuoteCmdArg(const CString& strArg)
|
||||
{
|
||||
if (strArg.Find(_T(' ')) == -1 && strArg.Find(_T('\t')) == -1 && strArg.Find(_T('"')) == -1)
|
||||
@@ -37,6 +53,12 @@ CString QuoteCmdArg(const CString& strArg)
|
||||
return strQuoted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试将字符串解析为 0-65535 范围内的无符号整数。
|
||||
/// </summary>
|
||||
/// <param name="strValue">待解析字符串。</param>
|
||||
/// <param name="nValue">输出:解析结果。</param>
|
||||
/// <returns>解析成功返回 TRUE。</returns>
|
||||
BOOL TryParseUInt16(const CString& strValue, UINT& nValue)
|
||||
{
|
||||
CString strTrimmed = strValue;
|
||||
@@ -64,6 +86,15 @@ BOOL TryParseUInt16(const CString& strValue, UINT& nValue)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 argv[nIndex] 为 UInt16,失败时打印错误并返回指定错误码。
|
||||
/// </summary>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <param name="nIndex">要解析的参数下标。</param>
|
||||
/// <param name="lpArgName">参数名(用于错误提示)。</param>
|
||||
/// <param name="nValue">输出:解析结果。</param>
|
||||
/// <param name="nErrorCode">解析失败时的退出码。</param>
|
||||
/// <returns>0 成功,否则返回 nErrorCode。</returns>
|
||||
int ParseUInt16ArgOrError(TCHAR* argv[], int nIndex, LPCTSTR lpArgName, UINT& nValue, int nErrorCode)
|
||||
{
|
||||
if (!TryParseUInt16(argv[nIndex], nValue))
|
||||
@@ -75,6 +106,11 @@ int ParseUInt16ArgOrError(TCHAR* argv[], int nIndex, LPCTSTR lpArgName, UINT& nV
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断参数字符串是否像路径(含 \、/、: 或以 . 开头)。
|
||||
/// </summary>
|
||||
/// <param name="lpArg">待判断的参数字符串。</param>
|
||||
/// <returns>像路径则返回 TRUE。</returns>
|
||||
BOOL IsLikelyPathArg(LPCTSTR lpArg)
|
||||
{
|
||||
if (lpArg == NULL)
|
||||
|
||||
@@ -1,8 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// 将命令行参数拼接为便于日志输出的字符串。
|
||||
/// </summary>
|
||||
CString BuildArgsForLog(int argc, TCHAR* argv[]);
|
||||
|
||||
/// <summary>
|
||||
/// 记录命令开始时间并输出开始日志,返回 tick 供计算耗时。
|
||||
/// </summary>
|
||||
DWORD LogCommandStart(LPCTSTR lpCommandName);
|
||||
|
||||
/// <summary>
|
||||
/// 为命令行参数添加引号转义。
|
||||
/// </summary>
|
||||
CString QuoteCmdArg(const CString& strArg);
|
||||
|
||||
/// <summary>
|
||||
/// 尝试将字符串解析为 0-65535 范围内的无符号整数。
|
||||
/// </summary>
|
||||
BOOL TryParseUInt16(const CString& strValue, UINT& nValue);
|
||||
|
||||
/// <summary>
|
||||
/// 解析 argv[nIndex] 为 UInt16,失败时打印错误并返回指定错误码。
|
||||
/// </summary>
|
||||
int ParseUInt16ArgOrError(TCHAR* argv[], int nIndex, LPCTSTR lpArgName, UINT& nValue, int nErrorCode);
|
||||
|
||||
/// <summary>
|
||||
/// 判断参数字符串是否像路径。
|
||||
/// </summary>
|
||||
BOOL IsLikelyPathArg(LPCTSTR lpArg);
|
||||
|
||||
@@ -11,6 +11,15 @@ extern TCHAR g_szCurModuleDir[MAX_PATH];
|
||||
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_NUITKA = 1;
|
||||
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_NUITKA = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 解析 nuitkabuild / nuitkapydbuild 命令的 repodir=、params= 与 -test 参数。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <param name="lpRepoPath">输出:仓库路径。</param>
|
||||
/// <param name="strExtraArgs">输出:传给 Nuitka 的额外参数。</param>
|
||||
/// <param name="bTestMode">输出:是否测试模式。</param>
|
||||
/// <returns>0 成功;3 未知/重复参数;23 无效 repodir。</returns>
|
||||
static int ParseNuitkaOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, CString& strExtraArgs, BOOL& bTestMode)
|
||||
{
|
||||
lpRepoPath = NULL;
|
||||
@@ -71,6 +80,14 @@ static int ParseNuitkaOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, CStr
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行 Nuitka 构建命令并输出版本信息与构建结果。
|
||||
/// </summary>
|
||||
/// <param name="lpStartMessage">构建开始提示语。</param>
|
||||
/// <param name="strNuitkaCmd">完整 Nuitka 命令行。</param>
|
||||
/// <param name="strProductVersion">产品版本号(仅日志)。</param>
|
||||
/// <param name="strFileVersion">文件版本号(仅日志)。</param>
|
||||
/// <param name="lpRepoPath">工作目录。</param>
|
||||
static void RunNuitkaBuild(LPCTSTR lpStartMessage, CString& strNuitkaCmd, const CString& strProductVersion, const CString& strFileVersion, LPCTSTR lpRepoPath)
|
||||
{
|
||||
_tprintf(_T("%s\n"), lpStartMessage);
|
||||
@@ -86,6 +103,20 @@ static void RunNuitkaBuild(LPCTSTR lpStartMessage, CString& strNuitkaCmd, const
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// nuitkabuild / nuitkapydbuild 命令的公共实现:解析 pid、生成版本、调用 Nuitka。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <param name="nCmdPrefixLen">argv[1] 中 "xxx=" 前缀长度。</param>
|
||||
/// <param name="bBuildModule">TRUE 时使用 --module 模式。</param>
|
||||
/// <param name="lpStartMessage">构建开始提示语。</param>
|
||||
/// <param name="lpUsageName">命令名(用于错误提示)。</param>
|
||||
/// <param name="nPidErrorCode">pid 无效时的退出码。</param>
|
||||
/// <param name="nBidErrorCode">获取 bid 失败时的退出码。</param>
|
||||
/// <param name="nTagErrorCode">获取 Tag 失败时的退出码。</param>
|
||||
/// <param name="nCommitErrorCode">获取提交信息失败时的退出码。</param>
|
||||
/// <returns>0 成功,否则为对应错误码。</returns>
|
||||
static int HandleNuitkaBuildCommandCore(
|
||||
int argc,
|
||||
TCHAR* argv[],
|
||||
@@ -165,6 +196,12 @@ static int HandleNuitkaBuildCommandCore(
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 nuitkabuild=pid 命令:standalone 可执行文件打包。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <returns>0 成功,17-21 等为各类错误码。</returns>
|
||||
int HandleNuitkaBuildCommand(int argc, TCHAR* argv[])
|
||||
{
|
||||
return HandleNuitkaBuildCommandCore(
|
||||
@@ -180,6 +217,12 @@ int HandleNuitkaBuildCommand(int argc, TCHAR* argv[])
|
||||
21);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 nuitkapydbuild=pid 命令:Python 扩展模块(.pyd)打包。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <returns>0 成功,24-28 等为各类错误码。</returns>
|
||||
int HandleNuitkaPydBuildCommand(int argc, TCHAR* argv[])
|
||||
{
|
||||
return HandleNuitkaBuildCommandCore(
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// 处理 nuitkabuild=pid 命令:standalone 可执行文件打包。
|
||||
/// </summary>
|
||||
int HandleNuitkaBuildCommand(int argc, TCHAR* argv[]);
|
||||
|
||||
/// <summary>
|
||||
/// 处理 nuitkapydbuild=pid 命令:Python 扩展模块(.pyd)打包。
|
||||
/// </summary>
|
||||
int HandleNuitkaPydBuildCommand(int argc, TCHAR* argv[]);
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
#include "gitver.h"
|
||||
#include "gitver_process.h"
|
||||
|
||||
/// <summary>
|
||||
/// 按顺序将文件中多处旧字符串替换为新字符串并写回。
|
||||
/// </summary>
|
||||
/// <param name="lpFile">目标文件路径。</param>
|
||||
/// <param name="vtOldContent">待查找的旧内容列表。</param>
|
||||
/// <param name="vtNewContent">对应的新内容列表。</param>
|
||||
/// <returns>全部替换成功返回 TRUE。</returns>
|
||||
BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent)
|
||||
{
|
||||
if (vtOldContent.size() != vtNewContent.size())
|
||||
@@ -69,6 +76,12 @@ BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将子进程管道输出的原始字节按指定代码页解码为 CString。
|
||||
/// </summary>
|
||||
/// <param name="strRawOutput">管道原始输出字节。</param>
|
||||
/// <param name="nOutputCodePage">解码代码页(CP_UTF8 或 CP_ACP 等)。</param>
|
||||
/// <returns>解码后的字符串。</returns>
|
||||
static CString DecodeProcessOutput(const std::string& strRawOutput, UINT nOutputCodePage)
|
||||
{
|
||||
if (strRawOutput.empty())
|
||||
@@ -120,6 +133,14 @@ static CString DecodeProcessOutput(const std::string& strRawOutput, UINT nOutput
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动子进程并捕获 stdout/stderr,按代码页解码后返回输出。
|
||||
/// </summary>
|
||||
/// <param name="program">可执行程序路径,NULL 时使用 args 中的命令。</param>
|
||||
/// <param name="args">命令行参数。</param>
|
||||
/// <param name="lpCurrentDirectory">工作目录。</param>
|
||||
/// <param name="nOutputCodePage">输出解码代码页,默认 CP_ACP。</param>
|
||||
/// <returns>子进程的标准输出/错误合并文本。</returns>
|
||||
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory, UINT nOutputCodePage)
|
||||
{
|
||||
std::string strRawOutput;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// 按顺序将文件中多处旧字符串替换为新字符串并写回。
|
||||
/// </summary>
|
||||
BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent);
|
||||
|
||||
/// <summary>
|
||||
/// 启动子进程并捕获 stdout/stderr。
|
||||
/// nOutputCodePage 为管道原始字节的解码代码页,默认 CP_ACP(按系统 ANSI/GBK 直接使用);
|
||||
|
||||
@@ -11,6 +11,15 @@ extern TCHAR g_szCurModuleDir[MAX_PATH];
|
||||
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_PYINSTALLER = 1;
|
||||
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_PYINSTALLER = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 解析 pyinstaller 命令的 repodir=、params= 与 -test 参数。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <param name="lpRepoPath">输出:仓库路径。</param>
|
||||
/// <param name="strExtraArgs">输出:传给 PyInstaller 的参数。</param>
|
||||
/// <param name="bTestMode">输出:是否测试模式。</param>
|
||||
/// <returns>0 成功;3 未知/重复参数;23 无效 repodir。</returns>
|
||||
static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath, CString& strExtraArgs, BOOL& bTestMode)
|
||||
{
|
||||
lpRepoPath = NULL;
|
||||
@@ -71,7 +80,15 @@ static int ParsePyinstallerOptions(int argc, TCHAR* argv[], LPCTSTR& lpRepoPath,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 解析版本字符串 "a.b.c.d" → 四个分量
|
||||
/// <summary>
|
||||
/// 解析版本字符串 "a.b.c.d" 为四个无符号整数分量。
|
||||
/// </summary>
|
||||
/// <param name="lpVer">版本字符串。</param>
|
||||
/// <param name="v1">输出:第 1 段。</param>
|
||||
/// <param name="v2">输出:第 2 段。</param>
|
||||
/// <param name="v3">输出:第 3 段。</param>
|
||||
/// <param name="v4">输出:第 4 段。</param>
|
||||
/// <returns>格式正确返回 TRUE。</returns>
|
||||
static BOOL ParseVersionTuple(LPCTSTR lpVer, UINT& v1, UINT& v2, UINT& v3, UINT& v4)
|
||||
{
|
||||
CString str(lpVer);
|
||||
@@ -88,7 +105,11 @@ static BOOL ParseVersionTuple(LPCTSTR lpVer, UINT& v1, UINT& v2, UINT& v3, UINT&
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 提取 params 中第一个空格分隔的 token(支持双引号括起的路径)
|
||||
/// <summary>
|
||||
/// 提取 params 字符串中第一个 token(支持双引号括起的路径)。
|
||||
/// </summary>
|
||||
/// <param name="strParams">完整 params 参数字符串。</param>
|
||||
/// <returns>第一个 token,空输入返回空字符串。</returns>
|
||||
static CString ExtractFirstToken(const CString& strParams)
|
||||
{
|
||||
CString str = strParams;
|
||||
@@ -105,7 +126,14 @@ static CString ExtractFirstToken(const CString& strParams)
|
||||
return str.Left(nSpace);
|
||||
}
|
||||
|
||||
// 生成 version_info.txt 到 lpDir 目录(PyInstaller VSVersionInfo 格式)
|
||||
/// <summary>
|
||||
/// 在指定目录生成 PyInstaller VSVersionInfo 格式的 version_info.txt。
|
||||
/// </summary>
|
||||
/// <param name="lpDir">输出目录。</param>
|
||||
/// <param name="lpProductVersion">产品版本号。</param>
|
||||
/// <param name="lpFileVersion">文件版本号。</param>
|
||||
/// <param name="strOutPath">输出:生成的文件完整路径。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
static BOOL WriteVersionInfoFile(LPCTSTR lpDir, LPCTSTR lpProductVersion, LPCTSTR lpFileVersion, CString& strOutPath)
|
||||
{
|
||||
UINT pv1 = 0, pv2 = 0, pv3 = 0, pv4 = 0;
|
||||
@@ -165,8 +193,12 @@ static BOOL WriteVersionInfoFile(LPCTSTR lpDir, LPCTSTR lpProductVersion, LPCTST
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 向 .spec 文件注入或更新 version= 参数
|
||||
// 若已有 version= 则更新其值,否则在 name= 参数后新增一行
|
||||
/// <summary>
|
||||
/// 向 .spec 文件注入或更新 version='version_info.txt' 参数。
|
||||
/// 已有 version= 则更新整行,否则在 name= 行后插入。
|
||||
/// </summary>
|
||||
/// <param name="lpSpecPath">.spec 文件路径。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
|
||||
{
|
||||
if (!PathFileExists(lpSpecPath))
|
||||
@@ -260,8 +292,12 @@ static BOOL InjectVersionIntoSpec(LPCTSTR lpSpecPath)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 从 .spec 文件读取 version= 参数的值(单/双引号括起的路径)
|
||||
// 返回 FALSE 表示未找到、值为 None 或值为空
|
||||
/// <summary>
|
||||
/// 从 .spec 文件读取 version= 参数引号内的外部版本信息文件路径。
|
||||
/// </summary>
|
||||
/// <param name="lpSpecPath">.spec 文件路径。</param>
|
||||
/// <param name="strVersionPath">输出:version= 指向的路径。</param>
|
||||
/// <returns>找到有效引号路径返回 TRUE;None 或空值返回 FALSE。</returns>
|
||||
static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath)
|
||||
{
|
||||
strVersionPath.Empty();
|
||||
@@ -310,7 +346,13 @@ static BOOL ReadVersionPathFromSpec(LPCTSTR lpSpecPath, CString& strVersionPath)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 更新已有 version_info 文件的版本字段;若文件不存在则创建
|
||||
/// <summary>
|
||||
/// 更新已有 version_info 文件的版本字段;文件不存在时按模板创建。
|
||||
/// </summary>
|
||||
/// <param name="lpPath">version_info.txt 或内联 spec 路径。</param>
|
||||
/// <param name="lpProductVersion">产品版本号。</param>
|
||||
/// <param name="lpFileVersion">文件版本号。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCTSTR lpFileVersion)
|
||||
{
|
||||
UINT pv1 = 0, pv2 = 0, pv3 = 0, pv4 = 0;
|
||||
@@ -462,7 +504,11 @@ static BOOL UpdateVersionInfoFile(LPCTSTR lpPath, LPCTSTR lpProductVersion, LPCT
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 检查 spec 文件中 version= 是否为无引号标识符(表示 spec 内内联 VSVersionInfo 对象)
|
||||
/// <summary>
|
||||
/// 检查 .spec 中 version= 是否为无引号的 Python 标识符(内联 VSVersionInfo 对象名)。
|
||||
/// </summary>
|
||||
/// <param name="lpSpecPath">.spec 文件路径。</param>
|
||||
/// <returns>是内联版本块引用返回 TRUE。</returns>
|
||||
static BOOL HasInlineVersionInfoBlock(LPCTSTR lpSpecPath)
|
||||
{
|
||||
if (!PathFileExists(lpSpecPath)) return FALSE;
|
||||
@@ -504,8 +550,12 @@ static BOOL HasInlineVersionInfoBlock(LPCTSTR lpSpecPath)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 修改 spec 文件中 version= 的引号值(内联版本号字符串场景)
|
||||
// 当 version= 后是非外部文件路径的字符串字面量时,直接替换该值为新版本号
|
||||
/// <summary>
|
||||
/// 替换 .spec 文件中 version= 引号内的内联版本号字符串。
|
||||
/// </summary>
|
||||
/// <param name="lpSpecPath">.spec 文件路径。</param>
|
||||
/// <param name="lpNewValue">新版本号字符串。</param>
|
||||
/// <returns>找到并替换返回 TRUE。</returns>
|
||||
static BOOL UpdateVersionStringInSpec(LPCTSTR lpSpecPath, LPCTSTR lpNewValue)
|
||||
{
|
||||
if (!PathFileExists(lpSpecPath))
|
||||
@@ -581,6 +631,12 @@ static BOOL UpdateVersionStringInSpec(LPCTSTR lpSpecPath, LPCTSTR lpNewValue)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 pyinstaller=pid 命令:生成版本、处理 spec/version_info、调用 PyInstaller 打包。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <returns>0 成功;40-46 等为各类错误码。</returns>
|
||||
int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[])
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// 处理 pyinstaller=pid 命令:生成版本信息并调用 PyInstaller 打包。
|
||||
/// </summary>
|
||||
int HandlePyinstallerBuildCommand(int argc, TCHAR* argv[]);
|
||||
|
||||
@@ -14,6 +14,12 @@ LPCTSTR GetProjectCodeTypeName(int nCodeType);
|
||||
/// <summary>
|
||||
/// 替换指定范围内包含指定标记的整行内容。
|
||||
/// </summary>
|
||||
/// <param name="strContent">文件内容(原地修改)。</param>
|
||||
/// <param name="strToken">行内查找标记。</param>
|
||||
/// <param name="strNewLine">替换后的整行文本。</param>
|
||||
/// <param name="nRangeStart">搜索范围起始偏移。</param>
|
||||
/// <param name="nRangeEnd">搜索范围结束偏移。</param>
|
||||
/// <returns>找到并替换返回 TRUE。</returns>
|
||||
BOOL ReplaceWholeLineByTokenInRange(std::string& strContent, const std::string& strToken, const std::string& strNewLine, size_t nRangeStart, size_t nRangeEnd)
|
||||
{
|
||||
if (nRangeStart == std::string::npos || nRangeEnd == std::string::npos || nRangeStart >= nRangeEnd)
|
||||
@@ -54,11 +60,17 @@ BOOL ReplaceWholeLineByTokenInRange(std::string& strContent, const std::string&
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在全文件范围内替换包含指定标记的整行。
|
||||
/// </summary>
|
||||
BOOL ReplaceWholeLineByToken(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
|
||||
{
|
||||
return ReplaceWholeLineByTokenInRange(strContent, strToken, strNewLine, 0, strContent.size());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 替换首个匹配行,并删除后续所有含同一标记的重复行。
|
||||
/// </summary>
|
||||
BOOL EnsureSingleLineByToken(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
|
||||
{
|
||||
std::string::size_type nPos = strContent.find(strToken);
|
||||
@@ -127,6 +139,9 @@ BOOL EnsureSingleLineByToken(std::string& strContent, const std::string& strToke
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 若存在含标记的行则替换并去重,否则在文件末尾追加新行。
|
||||
/// </summary>
|
||||
BOOL EnsureSingleLineByTokenOrAppend(std::string& strContent, const std::string& strToken, const std::string& strNewLine)
|
||||
{
|
||||
if (strContent.find(strToken) != std::string::npos)
|
||||
@@ -147,6 +162,13 @@ BOOL EnsureSingleLineByTokenOrAppend(std::string& strContent, const std::string&
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定位 .rc 文件中 VS_VERSION_INFO VERSIONINFO 块的 BEGIN…END 字节范围。
|
||||
/// </summary>
|
||||
/// <param name="strContent">.rc 文件内容。</param>
|
||||
/// <param name="nBlockStart">输出:块起始偏移。</param>
|
||||
/// <param name="nBlockEnd">输出:块结束偏移(不含末尾换行之后)。</param>
|
||||
/// <returns>找到完整块返回 TRUE。</returns>
|
||||
BOOL FindVersionInfoBlockRange(const std::string& strContent, size_t& nBlockStart, size_t& nBlockEnd)
|
||||
{
|
||||
nBlockStart = std::string::npos;
|
||||
@@ -210,6 +232,12 @@ BOOL FindVersionInfoBlockRange(const std::string& strContent, size_t& nBlockStar
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测文本文件 BOM 类型;UTF-16 BOM 视为不支持。
|
||||
/// </summary>
|
||||
/// <param name="strData">文件原始字节。</param>
|
||||
/// <param name="nBomLength">输出:BOM 长度(无 BOM 时为 0)。</param>
|
||||
/// <returns>支持的编码返回 TRUE,UTF-16 返回 FALSE。</returns>
|
||||
BOOL DetectSupportedTextBom(const std::string& strData, size_t& nBomLength)
|
||||
{
|
||||
nBomLength = 0;
|
||||
@@ -236,6 +264,13 @@ BOOL DetectSupportedTextBom(const std::string& strData, size_t& nBomLength)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以 ANSI/UTF-8(带 BOM 时保留前缀)读取文本文件。
|
||||
/// </summary>
|
||||
/// <param name="lpFile">文件路径。</param>
|
||||
/// <param name="strContent">输出:不含 BOM 的正文。</param>
|
||||
/// <param name="strBomPrefix">输出:原始 BOM 前缀,写回时需保留。</param>
|
||||
/// <returns>读取成功返回 TRUE。</returns>
|
||||
BOOL ReadTextFileAsAnsi(LPCTSTR lpFile, std::string& strContent, std::string& strBomPrefix)
|
||||
{
|
||||
strContent.clear();
|
||||
@@ -284,6 +319,13 @@ BOOL ReadTextFileAsAnsi(LPCTSTR lpFile, std::string& strContent, std::string& st
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将文本内容与 BOM 前缀写回文件。
|
||||
/// </summary>
|
||||
/// <param name="lpFile">文件路径。</param>
|
||||
/// <param name="strContent">正文内容。</param>
|
||||
/// <param name="strBomPrefix">BOM 前缀(可为空)。</param>
|
||||
/// <returns>写入成功返回 TRUE。</returns>
|
||||
BOOL WriteTextFileAsAnsi(LPCTSTR lpFile, const std::string& strContent, const std::string& strBomPrefix)
|
||||
{
|
||||
CFile myFile;
|
||||
@@ -303,6 +345,13 @@ BOOL WriteTextFileAsAnsi(LPCTSTR lpFile, const std::string& strContent, const st
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 ProductVersion / FileVersion 写入 C++ .rc 文件的 VERSIONINFO 块。
|
||||
/// </summary>
|
||||
/// <param name="strRcFile">.rc 文件路径。</param>
|
||||
/// <param name="strProductVersion">产品版本号。</param>
|
||||
/// <param name="strFileVersion">文件版本号。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
BOOL RewriteSetVerToCppRc(const CString& strRcFile, const CString& strProductVersion, const CString& strFileVersion)
|
||||
{
|
||||
std::string strContent;
|
||||
@@ -356,6 +405,13 @@ BOOL RewriteSetVerToCppRc(const CString& strRcFile, const CString& strProductVer
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 ProductVersion / FileVersion 写入 C# AssemblyInfo.cs。
|
||||
/// </summary>
|
||||
/// <param name="strAssemblyInfoFile">AssemblyInfo.cs 路径。</param>
|
||||
/// <param name="strProductVersion">产品版本号(AssemblyVersion)。</param>
|
||||
/// <param name="strFileVersion">文件版本号(AssemblyFileVersion)。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
BOOL RewriteSetVerToAssemblyInfo(const CString& strAssemblyInfoFile, const CString& strProductVersion, const CString& strFileVersion)
|
||||
{
|
||||
std::string strContent;
|
||||
@@ -392,6 +448,13 @@ BOOL RewriteSetVerToAssemblyInfo(const CString& strAssemblyInfoFile, const CStri
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按项目类型(C++ / C#)将版本号写入对应资源文件。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">项目根目录,NULL 时使用当前模块目录。</param>
|
||||
/// <param name="strProductVersion">产品版本号。</param>
|
||||
/// <param name="strFileVersion">文件版本号。</param>
|
||||
/// <returns>0 成功;15 不支持的项目类型;35 写版本失败。</returns>
|
||||
int RewriteSetVerByProjectType(LPCTSTR lpRepoPath, const CString& strProductVersion, const CString& strFileVersion)
|
||||
{
|
||||
LPCTSTR lpBaseDir = lpRepoPath == NULL ? g_szCurModuleDir : lpRepoPath;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// 按项目类型(C++ / C#)将版本号写入对应资源文件。
|
||||
/// </summary>
|
||||
int RewriteSetVerByProjectType(LPCTSTR lpRepoPath, const CString& strProductVersion, const CString& strFileVersion);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,14 @@ extern TCHAR g_szCurModuleDir[MAX_PATH];
|
||||
const UINT DEFAULT_MAJOR_WHEN_NO_TAG_FOR_TAG = 1;
|
||||
const UINT DEFAULT_MINOR_WHEN_NO_TAG_FOR_TAG = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前分支下最近 nMaxCount 个符合 major.minor 格式的 Tag。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strBranch">输出:当前分支名。</param>
|
||||
/// <param name="vtTags">输出:Tag 列表(按版本升序,取最后 nMaxCount 个)。</param>
|
||||
/// <param name="nMaxCount">最多返回的 Tag 数量。</param>
|
||||
/// <returns>成功返回 TRUE(无 Tag 时 vtTags 为空亦视为成功)。</returns>
|
||||
BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector<BranchTagInfo>& vtTags, int nMaxCount)
|
||||
{
|
||||
vtTags.clear();
|
||||
@@ -73,6 +81,10 @@ BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 交互式 Tag 创建流程:展示最近 Tag、选择 major+1 或 minor+1、创建并推送。
|
||||
/// </summary>
|
||||
/// <returns>0 成功;30-34 交互/创建/验证失败;36 推送失败。</returns>
|
||||
int HandleCreateTagInteractive()
|
||||
{
|
||||
CString strBranch;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// 分支 Tag 的 major/minor 解析结果。
|
||||
/// </summary>
|
||||
struct BranchTagInfo
|
||||
{
|
||||
CString strTag;
|
||||
@@ -7,5 +10,12 @@ struct BranchTagInfo
|
||||
UINT nMinor;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前分支下最近 nMaxCount 个符合 major.minor 格式的 Tag。
|
||||
/// </summary>
|
||||
BOOL GetRecentMajorMinorTags(LPCTSTR lpRepoPath, CString& strBranch, std::vector<BranchTagInfo>& vtTags, int nMaxCount);
|
||||
|
||||
/// <summary>
|
||||
/// 交互式 Tag 创建流程:展示最近 Tag、选择递增方式、创建并推送。
|
||||
/// </summary>
|
||||
int HandleCreateTagInteractive();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// 项目源码类型枚举。
|
||||
/// </summary>
|
||||
enum ProjectCodeType
|
||||
{
|
||||
PROJECT_UNKNOWN = 0,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "pch.h"
|
||||
#include "GitVer_upload.h"
|
||||
#include "gitver_version.h"
|
||||
|
||||
#include "httplib.h"
|
||||
|
||||
@@ -10,6 +11,7 @@
|
||||
|
||||
extern std::string ToAnsiString(LPCTSTR lpText);
|
||||
extern BOOL ReadTextFileAsAnsi(LPCTSTR lpFile, std::string& strContent, std::string& strBomPrefix);
|
||||
extern TCHAR g_szCurModuleDir[MAX_PATH];
|
||||
|
||||
static const LPCTSTR DEFAULT_BASE_URL = _T("http://tools-center.mokadisplay.com/");
|
||||
|
||||
@@ -31,6 +33,11 @@ struct UploadInfoConfig
|
||||
// 字符串 / 编码工具
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 将宽字符(或 ANSI)字符串转换为 UTF-8 编码的 std::string。
|
||||
/// </summary>
|
||||
/// <param name="lpText">输入字符串。</param>
|
||||
/// <returns>UTF-8 编码结果,空输入返回空字符串。</returns>
|
||||
static std::string ToUtf8String(LPCTSTR lpText)
|
||||
{
|
||||
if (lpText == NULL || lpText[0] == _T('\0'))
|
||||
@@ -74,6 +81,11 @@ static std::string ToUtf8String(LPCTSTR lpText)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 UTF-8 编码的 std::string 转换为 CString。
|
||||
/// </summary>
|
||||
/// <param name="strUtf8">UTF-8 字符串。</param>
|
||||
/// <returns>转换后的 CString。</returns>
|
||||
static CString Utf8ToCString(const std::string& strUtf8)
|
||||
{
|
||||
if (strUtf8.empty())
|
||||
@@ -116,6 +128,11 @@ static CString Utf8ToCString(const std::string& strUtf8)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 ANSI 编码的 std::string 转换为 CString。
|
||||
/// </summary>
|
||||
/// <param name="strAnsi">ANSI 字符串。</param>
|
||||
/// <returns>转换后的 CString。</returns>
|
||||
static CString AnsiToCString(const std::string& strAnsi)
|
||||
{
|
||||
if (strAnsi.empty())
|
||||
@@ -139,6 +156,11 @@ static CString AnsiToCString(const std::string& strAnsi)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 粗略检测字节序列是否为合法 UTF-8 编码。
|
||||
/// </summary>
|
||||
/// <param name="strValue">待检测的字节串。</param>
|
||||
/// <returns>看起来像合法 UTF-8 则返回 TRUE。</returns>
|
||||
static BOOL IsLikelyValidUtf8(const std::string& strValue)
|
||||
{
|
||||
size_t i = 0;
|
||||
@@ -188,6 +210,11 @@ static BOOL IsLikelyValidUtf8(const std::string& strValue)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 info 文件中的原始字节值转为 CString,自动区分 UTF-8 与 ANSI。
|
||||
/// </summary>
|
||||
/// <param name="strValue">原始字节值。</param>
|
||||
/// <returns>解码后的 CString。</returns>
|
||||
static CString InfoValueToCString(const std::string& strValue)
|
||||
{
|
||||
if (IsLikelyValidUtf8(strValue))
|
||||
@@ -198,6 +225,11 @@ static CString InfoValueToCString(const std::string& strValue)
|
||||
return AnsiToCString(strValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对 UTF-8 字符串进行 application/x-www-form-urlencoded 风格 URL 编码。
|
||||
/// </summary>
|
||||
/// <param name="strValue">待编码的 UTF-8 字符串。</param>
|
||||
/// <returns>编码后的字符串。</returns>
|
||||
static std::string UrlEncodeUtf8(const std::string& strValue)
|
||||
{
|
||||
static const char* kHex = "0123456789ABCDEF";
|
||||
@@ -228,6 +260,11 @@ static std::string UrlEncodeUtf8(const std::string& strValue)
|
||||
return strEncoded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反转义 info 文件值中的 \n 为换行符。
|
||||
/// </summary>
|
||||
/// <param name="strValue">含转义序列的原始值。</param>
|
||||
/// <returns>反转义后的字符串。</returns>
|
||||
static std::string UnescapeInfoValue(const std::string& strValue)
|
||||
{
|
||||
std::string strResult;
|
||||
@@ -249,6 +286,11 @@ static std::string UnescapeInfoValue(const std::string& strValue)
|
||||
return strResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转义后写入 info 文件(换行 → \n,反斜杠 → \\)。
|
||||
/// </summary>
|
||||
/// <param name="strValue">原始字符串。</param>
|
||||
/// <returns>适合写入 info 文件的转义字符串。</returns>
|
||||
static std::string EscapeInfoValueForFile(const std::string& strValue)
|
||||
{
|
||||
std::string strResult;
|
||||
@@ -277,6 +319,10 @@ static std::string EscapeInfoValueForFile(const std::string& strValue)
|
||||
return strResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去除 ANSI 字符串首尾的空格、制表符和回车。
|
||||
/// </summary>
|
||||
/// <param name="strValue">待修剪的字符串(原地修改)。</param>
|
||||
static void TrimAnsiInPlace(std::string& strValue)
|
||||
{
|
||||
while (!strValue.empty() && (strValue.front() == ' ' || strValue.front() == '\t' || strValue.front() == '\r'))
|
||||
@@ -289,6 +335,12 @@ static void TrimAnsiInPlace(std::string& strValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取二进制文件的全部内容到字节向量。
|
||||
/// </summary>
|
||||
/// <param name="lpFile">文件路径。</param>
|
||||
/// <param name="data">输出:文件字节内容。</param>
|
||||
/// <returns>读取成功返回 TRUE。</returns>
|
||||
static BOOL ReadBinaryFile(LPCTSTR lpFile, std::vector<BYTE>& data)
|
||||
{
|
||||
data.clear();
|
||||
@@ -312,6 +364,11 @@ static BOOL ReadBinaryFile(LPCTSTR lpFile, std::vector<BYTE>& data)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 ASCII 字母转为小写(仅 A-Z)。
|
||||
/// </summary>
|
||||
/// <param name="strValue">输入字符串。</param>
|
||||
/// <returns>小写化后的新字符串。</returns>
|
||||
static std::string ToLowerAscii(std::string strValue)
|
||||
{
|
||||
for (char& ch : strValue)
|
||||
@@ -328,6 +385,10 @@ static std::string ToLowerAscii(std::string strValue)
|
||||
// Cookie 管理(httplib 不自动维护 Session)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 去除 std::string 首尾的空格和制表符。
|
||||
/// </summary>
|
||||
/// <param name="strValue">待修剪的字符串(原地修改)。</param>
|
||||
static void TrimStdStringInPlace(std::string& strValue)
|
||||
{
|
||||
while (!strValue.empty() && (strValue.front() == ' ' || strValue.front() == '\t'))
|
||||
@@ -340,6 +401,11 @@ static void TrimStdStringInPlace(std::string& strValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Set-Cookie 响应头中的 name=value 合并到 Cookie 字符串,同名则覆盖。
|
||||
/// </summary>
|
||||
/// <param name="strHeaderValue">Set-Cookie 头的值部分。</param>
|
||||
/// <param name="strCookies">输入/输出:累积的 Cookie 字符串。</param>
|
||||
static void MergeSetCookieValue(const std::string& strHeaderValue, std::string& strCookies)
|
||||
{
|
||||
std::string strLine = strHeaderValue;
|
||||
@@ -431,6 +497,11 @@ static void MergeSetCookieValue(const std::string& strHeaderValue, std::string&
|
||||
strCookies = strResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 HTTP 响应的所有 Set-Cookie 头中收集并合并 Cookie。
|
||||
/// </summary>
|
||||
/// <param name="response">HTTP 响应对象。</param>
|
||||
/// <param name="strCookies">输出:合并后的 Cookie 字符串。</param>
|
||||
static void CollectResponseCookies(const httplib::Response& response, std::string& strCookies)
|
||||
{
|
||||
for (const auto& header : response.headers)
|
||||
@@ -442,6 +513,11 @@ static void CollectResponseCookies(const httplib::Response& response, std::strin
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 Cookie 字符串构造 httplib 请求头。
|
||||
/// </summary>
|
||||
/// <param name="strCookies">Cookie 字符串。</param>
|
||||
/// <returns>含 Cookie 头的 Headers 对象。</returns>
|
||||
static httplib::Headers MakeCookieHeaders(const std::string& strCookies)
|
||||
{
|
||||
httplib::Headers headers;
|
||||
@@ -452,6 +528,11 @@ static httplib::Headers MakeCookieHeaders(const std::string& strCookies)
|
||||
return headers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建指向 PMC 服务的 httplib 客户端,配置超时且不自动跟随重定向。
|
||||
/// </summary>
|
||||
/// <param name="lpBaseUrl">PMC 基址 URL。</param>
|
||||
/// <returns>有效客户端指针,无效 URL 时返回 nullptr。</returns>
|
||||
static std::unique_ptr<httplib::Client> CreatePmcClient(LPCTSTR lpBaseUrl)
|
||||
{
|
||||
std::string strBaseUrl = ToUtf8String(lpBaseUrl);
|
||||
@@ -472,6 +553,12 @@ static std::unique_ptr<httplib::Client> CreatePmcClient(LPCTSTR lpBaseUrl)
|
||||
// info 文件解析
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 根据键名将 info 文件中的一行键值对写入 UploadInfoConfig 结构体。
|
||||
/// </summary>
|
||||
/// <param name="config">输出配置对象。</param>
|
||||
/// <param name="strKey">键名。</param>
|
||||
/// <param name="strRawValue">原始值(含转义序列)。</param>
|
||||
static void ApplyInfoKeyValue(UploadInfoConfig& config, const std::string& strKey, const std::string& strRawValue)
|
||||
{
|
||||
std::string strValue = UnescapeInfoValue(strRawValue);
|
||||
@@ -519,6 +606,12 @@ static void ApplyInfoKeyValue(UploadInfoConfig& config, const std::string& strKe
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 upload_info.txt 等 info 配置文件加载上传参数。
|
||||
/// </summary>
|
||||
/// <param name="lpInfoFile">info 文件路径。</param>
|
||||
/// <param name="config">输出:解析后的配置。</param>
|
||||
/// <returns>文件读取成功返回 TRUE。</returns>
|
||||
static BOOL LoadUploadInfoFile(LPCTSTR lpInfoFile, UploadInfoConfig& config)
|
||||
{
|
||||
std::string strContent;
|
||||
@@ -571,6 +664,11 @@ static BOOL LoadUploadInfoFile(LPCTSTR lpInfoFile, UploadInfoConfig& config)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验上传配置是否包含必填字段。
|
||||
/// </summary>
|
||||
/// <param name="config">待校验的配置。</param>
|
||||
/// <returns>必填字段齐全返回 TRUE,缺失时打印错误并返回 FALSE。</returns>
|
||||
static BOOL ValidateUploadConfig(const UploadInfoConfig& config)
|
||||
{
|
||||
if (config.strPackageName.IsEmpty())
|
||||
@@ -601,6 +699,12 @@ static BOOL ValidateUploadConfig(const UploadInfoConfig& config)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从与用户名同名的环境变量中读取登录密码。
|
||||
/// </summary>
|
||||
/// <param name="lpUsername">PMC 登录用户名(亦作环境变量名)。</param>
|
||||
/// <param name="strPassword">输出:读取到的密码。</param>
|
||||
/// <returns>环境变量存在且非空返回 TRUE。</returns>
|
||||
static BOOL ResolvePasswordFromEnv(LPCTSTR lpUsername, CString& strPassword)
|
||||
{
|
||||
if (lpUsername == NULL || lpUsername[0] == _T('\0'))
|
||||
@@ -635,6 +739,13 @@ static BOOL ResolvePasswordFromEnv(LPCTSTR lpUsername, CString& strPassword)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新 upload_info.txt 中指定键的值,保留其他行和 BOM 前缀。
|
||||
/// 文件中不存在的键会追加到末尾。
|
||||
/// </summary>
|
||||
/// <param name="lpInfoFile">info 文件路径。</param>
|
||||
/// <param name="updates">要更新的键值对映射。</param>
|
||||
/// <returns>写入成功返回 TRUE。</returns>
|
||||
static BOOL UpdateUploadInfoKeys(LPCTSTR lpInfoFile, const std::map<std::string, std::string>& updates)
|
||||
{
|
||||
std::vector<std::string> outLines;
|
||||
@@ -729,19 +840,21 @@ static BOOL UpdateUploadInfoKeys(LPCTSTR lpInfoFile, const std::map<std::string,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL WriteUploadInfoVersions(
|
||||
LPCTSTR lpInfoFile,
|
||||
LPCTSTR lpProductVersion,
|
||||
LPCTSTR lpFileVersion,
|
||||
LPCTSTR lpFileChangeLog)
|
||||
/// <summary>
|
||||
/// 更新 upload_info.txt 中的版本号与变更日志字段。
|
||||
/// </summary>
|
||||
BOOL WriteUploadInfoVersions(LPCTSTR lpInfoFile, LPCTSTR lpProductVersion, LPCTSTR lpFileVersion, LPCTSTR lpFileChangeLog)
|
||||
{
|
||||
std::map<std::string, std::string> updates;
|
||||
updates["product_version"] = ToUtf8String(lpProductVersion);
|
||||
updates["file_version"] = ToUtf8String(lpFileVersion);
|
||||
updates["file_change_log"] = EscapeInfoValueForFile(ToUtf8String(lpFileChangeLog));
|
||||
updates["product_version"] = lpProductVersion;
|
||||
updates["file_version"] = lpFileVersion;
|
||||
updates["file_change_log"] = EscapeInfoValueForFile(lpFileChangeLog);
|
||||
return UpdateUploadInfoKeys(lpInfoFile, updates);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新 upload_info.txt 中的 file 字段(安装包路径)。
|
||||
/// </summary>
|
||||
BOOL WriteUploadInfoPackageFile(LPCTSTR lpInfoFile, LPCTSTR lpPackageFile)
|
||||
{
|
||||
std::map<std::string, std::string> updates;
|
||||
@@ -749,18 +862,64 @@ BOOL WriteUploadInfoPackageFile(LPCTSTR lpInfoFile, LPCTSTR lpPackageFile)
|
||||
return UpdateUploadInfoKeys(lpInfoFile, updates);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打印 upload 命令的用法说明。
|
||||
/// </summary>
|
||||
static void PrintUploadUsage()
|
||||
{
|
||||
_tprintf(_T("用法:gitver upload username=<用户名> [file=<上传的文件>] [info=<配置.txt>] [url=<PMC基址>]\n"));
|
||||
_tprintf(_T("用法:gitver upload username=<用户名> [file=<上传的文件>] [info=<配置.txt>] [url=<PMC基址>] [-f]\n"));
|
||||
_tprintf(_T("说明:info= 缺省为 upload_info.txt;file= 可省略,从 info 的 file 字段读取\n"));
|
||||
_tprintf(_T("说明:登录密码从与 username 同名的环境变量读取\n"));
|
||||
_tprintf(_T("说明:默认要求当前在分支上且 HEAD 指向 Tag;加 -f 可忽略 Tag 校验\n"));
|
||||
_tprintf(_T("示例:gitver upload username=admin\n"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验当前 Git 仓库是否处于可上传状态(在分支上且 HEAD 指向 Tag)。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <returns>0 表示通过;54 表示 HEAD 游离或未指向 Tag。</returns>
|
||||
static int ValidateUploadGitTagState(LPCTSTR lpRepoPath)
|
||||
{
|
||||
CString strStatus;
|
||||
if (!TryGetGitStatus(lpRepoPath, strStatus))
|
||||
{
|
||||
CString strTag;
|
||||
if (TryGetDetectedTag(lpRepoPath, strTag))
|
||||
{
|
||||
_tprintf(_T("错误: 当前处于 HEAD 游离状态(Tag: %s),不允许上传\n"), strTag.GetString());
|
||||
}
|
||||
else
|
||||
{
|
||||
_tprintf(_T("错误: 当前处于 HEAD 游离状态,不允许上传\n"));
|
||||
}
|
||||
_tprintf(_T("提示: 使用 upload ... -f 可强制上传\n"));
|
||||
return 54;
|
||||
}
|
||||
|
||||
CString strTag;
|
||||
if (!TryGetDetectedTag(lpRepoPath, strTag))
|
||||
{
|
||||
_tprintf(_T("错误: 当前 commit 未指向 Tag 标签,不允许上传\n"));
|
||||
_tprintf(_T("提示: 使用 upload ... -f 可强制上传\n"));
|
||||
return 54;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// PMC 交互(cpp-httplib)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 向 PMC 发送登录请求,获取 Session Cookie。
|
||||
/// </summary>
|
||||
/// <param name="cli">httplib 客户端。</param>
|
||||
/// <param name="lpUsername">登录用户名。</param>
|
||||
/// <param name="lpPassword">登录密码。</param>
|
||||
/// <param name="strCookies">输出:登录成功后的 Cookie 字符串。</param>
|
||||
/// <returns>0 成功;50 登录失败;53 网络错误。</returns>
|
||||
static int PmcLogin(httplib::Client& cli, LPCTSTR lpUsername, LPCTSTR lpPassword, std::string& strCookies)
|
||||
{
|
||||
strCookies.clear();
|
||||
@@ -805,6 +964,14 @@ static int PmcLogin(httplib::Client& cli, LPCTSTR lpUsername, LPCTSTR lpPassword
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以 multipart/form-data 方式上传安装包到 PMC。
|
||||
/// </summary>
|
||||
/// <param name="cli">httplib 客户端。</param>
|
||||
/// <param name="strCookies">登录 Session Cookie。</param>
|
||||
/// <param name="config">上传配置(包名、版本、变更日志等)。</param>
|
||||
/// <param name="lpFilePath">待上传的文件路径。</param>
|
||||
/// <returns>0 成功;48 文件读取失败;51 上传 HTTP 失败;53 网络错误。</returns>
|
||||
static int PmcUploadPackage(httplib::Client& cli, const std::string& strCookies, const UploadInfoConfig& config, LPCTSTR lpFilePath)
|
||||
{
|
||||
std::vector<BYTE> fileData;
|
||||
@@ -856,6 +1023,13 @@ static int PmcUploadPackage(httplib::Client& cli, const std::string& strCookies,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 JSON 响应体中查找 "field":"value" 形式的字段值(简单字符串匹配)。
|
||||
/// </summary>
|
||||
/// <param name="strBody">JSON 响应正文。</param>
|
||||
/// <param name="lpField">字段名。</param>
|
||||
/// <param name="lpValue">期望的字段值。</param>
|
||||
/// <returns>找到匹配则返回 TRUE。</returns>
|
||||
static BOOL JsonContainsFieldValue(const std::string& strBody, LPCSTR lpField, LPCSTR lpValue)
|
||||
{
|
||||
if (lpField == NULL || lpValue == NULL)
|
||||
@@ -867,6 +1041,13 @@ static BOOL JsonContainsFieldValue(const std::string& strBody, LPCSTR lpField, L
|
||||
return strBody.find(strNeedle) != std::string::npos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用 PMC API 验证上传后是否存在对应包名和版本记录。
|
||||
/// </summary>
|
||||
/// <param name="cli">httplib 客户端。</param>
|
||||
/// <param name="strCookies">登录 Session Cookie。</param>
|
||||
/// <param name="config">含包名和版本信息的配置。</param>
|
||||
/// <returns>0 验证通过;52 未找到版本;53 网络错误。</returns>
|
||||
static int VerifyUploadResult(httplib::Client& cli, const std::string& strCookies, const UploadInfoConfig& config)
|
||||
{
|
||||
std::string strPath = "/api/packages/latest?keyword=" +
|
||||
@@ -907,16 +1088,28 @@ static int VerifyUploadResult(httplib::Client& cli, const std::string& strCookie
|
||||
// 命令入口
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 处理 upload 命令:解析参数、登录 PMC、上传包并验证结果。
|
||||
/// </summary>
|
||||
/// <param name="argc">参数个数。</param>
|
||||
/// <param name="argv">参数数组。</param>
|
||||
/// <returns>0 成功;47-54 各类错误,详见头文件说明。</returns>
|
||||
int HandleUploadCommand(int argc, TCHAR* argv[])
|
||||
{
|
||||
CString strUsername;
|
||||
CString strFilePath;
|
||||
CString strInfoPath;
|
||||
CString strUrlOverride;
|
||||
BOOL bForceUpload = FALSE;
|
||||
|
||||
for (int i = 2; i < argc; ++i)
|
||||
{
|
||||
CString strArg = argv[i];
|
||||
if (strArg.CompareNoCase(_T("-f")) == 0)
|
||||
{
|
||||
bForceUpload = TRUE;
|
||||
continue;
|
||||
}
|
||||
if (strArg.GetLength() > 9 && strArg.Left(9).CompareNoCase(_T("username=")) == 0)
|
||||
{
|
||||
strUsername = strArg.Mid(9);
|
||||
@@ -1001,6 +1194,15 @@ int HandleUploadCommand(int argc, TCHAR* argv[])
|
||||
return 49;
|
||||
}
|
||||
|
||||
if (!bForceUpload)
|
||||
{
|
||||
int nTagRet = ValidateUploadGitTagState(g_szCurModuleDir);
|
||||
if (nTagRet != 0)
|
||||
{
|
||||
return nTagRet;
|
||||
}
|
||||
}
|
||||
|
||||
CString strPassword;
|
||||
if (!ResolvePasswordFromEnv(strUsername, strPassword))
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// 处理 upload 命令:登录 PMC 并上传包文件。
|
||||
/// 退出码:0 成功,47 缺少参数,48 文件不存在,49 info 无效或未设置同名环境变量密码,
|
||||
/// 50 登录失败,51 上传 HTTP 失败,52 验证未找到版本,53 网络错误。
|
||||
/// 50 登录失败,51 上传 HTTP 失败,52 验证未找到版本,53 网络错误,54 Tag/分支校验失败。
|
||||
/// </summary>
|
||||
int HandleUploadCommand(int argc, TCHAR* argv[]);
|
||||
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
#include "gitver_process.h"
|
||||
#include "gitver_version.h"
|
||||
|
||||
/// <summary>
|
||||
/// 检测 Git 仓库是否处于正常分支状态(非 HEAD 游离)。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strStatus">输出:symbolic-ref HEAD 结果。</param>
|
||||
/// <returns>在分支上返回 TRUE,游离状态返回 FALSE。</returns>
|
||||
BOOL TryGetGitStatus(LPCTSTR lpRepoPath, CString& strStatus)
|
||||
{
|
||||
strStatus = StartProcess(NULL, _T("cmd /c git symbolic-ref --quiet HEAD"), lpRepoPath);
|
||||
@@ -19,6 +25,12 @@ BOOL TryGetGitStatus(LPCTSTR lpRepoPath, CString& strStatus)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前检出的本地分支名称。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strBranch">输出:分支名。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
BOOL TryGetCurrentBranch(LPCTSTR lpRepoPath, CString& strBranch)
|
||||
{
|
||||
_tprintf(_T("获取当前分支名称: \n"));
|
||||
@@ -35,6 +47,12 @@ BOOL TryGetCurrentBranch(LPCTSTR lpRepoPath, CString& strBranch)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 HEAD 游离时,通过远程分支推断当前所属分支名(origin/ 前缀已去除)。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strBranch">输出:推断的分支名。</param>
|
||||
/// <returns>找到远程分支返回 TRUE。</returns>
|
||||
BOOL TryGetDetectedBranch(LPCTSTR lpRepoPath, CString& strBranch)
|
||||
{
|
||||
// 远程仓库名必须以 origin 开头;
|
||||
@@ -54,6 +72,12 @@ BOOL TryGetDetectedBranch(LPCTSTR lpRepoPath, CString& strBranch)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指向当前 HEAD 的 Tag 名称。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strTag">输出:Tag 名(多个时取 git 输出首行)。</param>
|
||||
/// <returns>存在 Tag 返回 TRUE。</returns>
|
||||
BOOL TryGetDetectedTag(LPCTSTR lpRepoPath, CString& strTag)
|
||||
{
|
||||
strTag = StartProcess(NULL, _T("cmd /c git tag --points-at HEAD"), lpRepoPath);
|
||||
@@ -70,6 +94,12 @@ BOOL TryGetDetectedTag(LPCTSTR lpRepoPath, CString& strTag)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从分支名解析 bid(分支 ID):main/master=0,release=1,dev=2,其余取末段数字。
|
||||
/// </summary>
|
||||
/// <param name="strBranch">分支名称。</param>
|
||||
/// <param name="nBid">输出:分支 bid。</param>
|
||||
/// <returns>解析成功返回 TRUE。</returns>
|
||||
BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid)
|
||||
{
|
||||
nBid = 0;
|
||||
@@ -106,6 +136,14 @@ BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid)
|
||||
return TryParseUInt16(strNum, nBid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析符合「分支名.major.minor」格式的 Tag,提取 major 与 minor。
|
||||
/// </summary>
|
||||
/// <param name="strBranch">分支名称。</param>
|
||||
/// <param name="strTag">Tag 名称。</param>
|
||||
/// <param name="nMajor">输出:主版本号。</param>
|
||||
/// <param name="nMinor">输出:次版本号。</param>
|
||||
/// <returns>格式匹配且解析成功返回 TRUE。</returns>
|
||||
BOOL TryParseTagMajorMinor(const CString& strBranch, const CString& strTag, UINT& nMajor, UINT& nMinor)
|
||||
{
|
||||
if (strBranch.IsEmpty())
|
||||
@@ -141,6 +179,15 @@ BOOL TryParseTagMajorMinor(const CString& strBranch, const CString& strTag, UINT
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前分支下版本号最大的 major.minor Tag(按 v:refname 排序遍历)。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strBranch">分支名称。</param>
|
||||
/// <param name="strTag">输出:最新 Tag 全名。</param>
|
||||
/// <param name="nMajor">输出:主版本号。</param>
|
||||
/// <param name="nMinor">输出:次版本号。</param>
|
||||
/// <returns>找到匹配 Tag 返回 TRUE。</returns>
|
||||
BOOL GetLastMajorMinorTag(LPCTSTR lpRepoPath, const CString& strBranch, CString& strTag, UINT& nMajor, UINT& nMinor)
|
||||
{
|
||||
strTag.Empty();
|
||||
@@ -184,6 +231,13 @@ BOOL GetLastMajorMinorTag(LPCTSTR lpRepoPath, const CString& strBranch, CString&
|
||||
return !strTag.IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 SYSTEMTIME 转换为 FileVersion 中的 yy 与 mmdd 分量。
|
||||
/// 月份 1-12 映射为 11-22(mmdd = (month+10)*100 + day)。
|
||||
/// </summary>
|
||||
/// <param name="stLocal">本地时间。</param>
|
||||
/// <param name="nYY">输出:年份后两位。</param>
|
||||
/// <param name="nMMDD">输出:编码后的月日。</param>
|
||||
void GetFileVersionDateFromSystemTime(const SYSTEMTIME& stLocal, UINT& nYY, UINT& nMMDD)
|
||||
{
|
||||
nYY = stLocal.wYear % 100;
|
||||
@@ -191,6 +245,14 @@ void GetFileVersionDateFromSystemTime(const SYSTEMTIME& stLocal, UINT& nYY, UINT
|
||||
nMMDD = (stLocal.wMonth + 10) * 100 + stLocal.wDay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统计指定日期内 origin/分支 的提交次数(用于 FileVersion 末段 id)。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strBranch">分支名称。</param>
|
||||
/// <param name="stDate">目标日期。</param>
|
||||
/// <param name="nId">输出:当日提交数。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
BOOL TryGetBranchCommitCountByDate(LPCTSTR lpRepoPath, const CString& strBranch, const SYSTEMTIME& stDate, UINT& nId)
|
||||
{
|
||||
nId = 0;
|
||||
@@ -229,6 +291,13 @@ BOOL TryGetBranchCommitCountByDate(LPCTSTR lpRepoPath, const CString& strBranch,
|
||||
return TryParseUInt16(strCount, nId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取分支最近一次提交的日期(git log -1 --date=short)。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strBranch">分支名称。</param>
|
||||
/// <param name="stDate">输出:提交日期。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
BOOL TryGetLastBranchCommitDate(LPCTSTR lpRepoPath, const CString& strBranch, SYSTEMTIME& stDate)
|
||||
{
|
||||
::ZeroMemory(&stDate, sizeof(stDate));
|
||||
@@ -271,6 +340,13 @@ BOOL TryGetLastBranchCommitDate(LPCTSTR lpRepoPath, const CString& strBranch, SY
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Tag 对应提交的日期(git log -1 --date=short)。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strTag">Tag 名称。</param>
|
||||
/// <param name="stDate">输出:提交日期。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
BOOL TryGetTagCommitDate(LPCTSTR lpRepoPath, const CString& strTag, SYSTEMTIME& stDate)
|
||||
{
|
||||
::ZeroMemory(&stDate, sizeof(stDate));
|
||||
@@ -313,6 +389,17 @@ BOOL TryGetTagCommitDate(LPCTSTR lpRepoPath, const CString& strTag, SYSTEMTIME&
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 Tag 提交日期解析 FileVersion 的 yy、mmdd 与 id(当日提交数)。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strBranch">分支名称。</param>
|
||||
/// <param name="strTag">Tag 名称。</param>
|
||||
/// <param name="nYY">输出:年份后两位。</param>
|
||||
/// <param name="nMMDD">输出:编码月日。</param>
|
||||
/// <param name="nId">输出:提交序号。</param>
|
||||
/// <param name="pOutDate">可选输出:Tag 提交日期。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
BOOL ResolveTagFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, const CString& strTag, UINT& nYY, UINT& nMMDD, UINT& nId, SYSTEMTIME* pOutDate)
|
||||
{
|
||||
nYY = 0;
|
||||
@@ -358,6 +445,17 @@ BOOL ResolveTagFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString&
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于当前日期(或最近提交日)解析 FileVersion 的 yy、mmdd 与 id。
|
||||
/// 当天提交数为 0 时回退到最近一次提交日期。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strBranch">分支名称。</param>
|
||||
/// <param name="nYY">输出:年份后两位。</param>
|
||||
/// <param name="nMMDD">输出:编码月日。</param>
|
||||
/// <param name="nId">输出:提交序号。</param>
|
||||
/// <param name="pOutDate">可选输出:采用的日期。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
BOOL ResolveFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, UINT& nYY, UINT& nMMDD, UINT& nId, SYSTEMTIME* pOutDate)
|
||||
{
|
||||
nYY = 0;
|
||||
@@ -407,6 +505,14 @@ BOOL ResolveFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& str
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定日期内 origin/分支 的提交摘要列表(UTF-8 输出,一行一条)。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="strBranch">分支名称。</param>
|
||||
/// <param name="stDate">目标日期。</param>
|
||||
/// <param name="strLog">输出:提交摘要,多行以换行分隔。</param>
|
||||
/// <returns>成功返回 TRUE。</returns>
|
||||
BOOL TryGetGitLogByBranchDate(LPCTSTR lpRepoPath, const CString& strBranch, const SYSTEMTIME& stDate, CString& strLog)
|
||||
{
|
||||
strLog.Empty();
|
||||
@@ -444,6 +550,21 @@ BOOL TryGetGitLogByBranchDate(LPCTSTR lpRepoPath, const CString& strBranch, cons
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 Git 仓库信息构建 ProductVersion 与 FileVersion 字符串。
|
||||
/// 正常分支:ProductVersion = pid.bid.major.minor,FileVersion = pid.yy.mmdd.id。
|
||||
/// HEAD 游离时改用远程分支与 Tag 解析。
|
||||
/// </summary>
|
||||
/// <param name="lpRepoPath">仓库根目录。</param>
|
||||
/// <param name="nPid">产品 ID。</param>
|
||||
/// <param name="errorCodes">各步骤失败时的退出码映射。</param>
|
||||
/// <param name="strProductVersion">输出:产品版本号。</param>
|
||||
/// <param name="strFileVersion">输出:文件版本号。</param>
|
||||
/// <param name="nDefaultMajor">无 Tag 时的默认 major。</param>
|
||||
/// <param name="nDefaultMinor">无 Tag 时的默认 minor。</param>
|
||||
/// <param name="pOutBranch">可选输出:使用的分支名。</param>
|
||||
/// <param name="pOutFileVersionDate">可选输出:FileVersion 采用的日期。</param>
|
||||
/// <returns>0 成功,否则返回 errorCodes 中对应错误码。</returns>
|
||||
int BuildVersionsFromRepo(
|
||||
LPCTSTR lpRepoPath,
|
||||
UINT nPid,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// BuildVersionsFromRepo 各步骤失败时使用的退出码映射。
|
||||
/// </summary>
|
||||
struct VersionBuildErrorCodes
|
||||
{
|
||||
int nBidErrorCode;
|
||||
@@ -8,13 +11,44 @@ struct VersionBuildErrorCodes
|
||||
int nStatusErrorCode;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 检测 Git 仓库是否处于正常分支状态(非 HEAD 游离)。
|
||||
/// </summary>
|
||||
BOOL TryGetGitStatus(LPCTSTR lpRepoPath, CString& strStatus);
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前检出的本地分支名称。
|
||||
/// </summary>
|
||||
BOOL TryGetCurrentBranch(LPCTSTR lpRepoPath, CString& strBranch);
|
||||
|
||||
/// <summary>
|
||||
/// 在 HEAD 游离时推断当前所属远程分支名。
|
||||
/// </summary>
|
||||
BOOL TryGetDetectedBranch(LPCTSTR lpRepoPath, CString& strBranch);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指向当前 HEAD 的 Tag 名称。
|
||||
/// </summary>
|
||||
BOOL TryGetDetectedTag(LPCTSTR lpRepoPath, CString& strTag);
|
||||
|
||||
/// <summary>
|
||||
/// 从分支名解析 bid(分支 ID)。
|
||||
/// </summary>
|
||||
BOOL TryGetBidFromBranch(const CString& strBranch, UINT& nBid);
|
||||
|
||||
/// <summary>
|
||||
/// 解析符合「分支名.major.minor」格式的 Tag。
|
||||
/// </summary>
|
||||
BOOL TryParseTagMajorMinor(const CString& strBranch, const CString& strTag, UINT& nMajor, UINT& nMinor);
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前分支下版本号最大的 major.minor Tag。
|
||||
/// </summary>
|
||||
BOOL GetLastMajorMinorTag(LPCTSTR lpRepoPath, const CString& strBranch, CString& strTag, UINT& nMajor, UINT& nMinor);
|
||||
|
||||
/// <summary>
|
||||
/// 基于 Tag 提交日期解析 FileVersion 的 yy、mmdd 与 id。
|
||||
/// </summary>
|
||||
BOOL ResolveTagFileVersionDateAndCommitCount(
|
||||
LPCTSTR lpRepoPath,
|
||||
const CString& strBranch,
|
||||
@@ -23,6 +57,10 @@ BOOL ResolveTagFileVersionDateAndCommitCount(
|
||||
UINT& nMMDD,
|
||||
UINT& nId,
|
||||
SYSTEMTIME* pOutDate = NULL);
|
||||
|
||||
/// <summary>
|
||||
/// 基于当前或最近提交日解析 FileVersion 的 yy、mmdd 与 id。
|
||||
/// </summary>
|
||||
BOOL ResolveFileVersionDateAndCommitCount(
|
||||
LPCTSTR lpRepoPath,
|
||||
const CString& strBranch,
|
||||
@@ -30,11 +68,19 @@ BOOL ResolveFileVersionDateAndCommitCount(
|
||||
UINT& nMMDD,
|
||||
UINT& nId,
|
||||
SYSTEMTIME* pOutDate = NULL);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定日期内 origin/分支 的提交摘要列表。
|
||||
/// </summary>
|
||||
BOOL TryGetGitLogByBranchDate(
|
||||
LPCTSTR lpRepoPath,
|
||||
const CString& strBranch,
|
||||
const SYSTEMTIME& stDate,
|
||||
CString& strLog);
|
||||
|
||||
/// <summary>
|
||||
/// 从 Git 仓库信息构建 ProductVersion 与 FileVersion 字符串。
|
||||
/// </summary>
|
||||
int BuildVersionsFromRepo(
|
||||
LPCTSTR lpRepoPath,
|
||||
UINT nPid,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ 生成的包含文件。
|
||||
// 供 GitVer.rc 使用
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ 生成的包含文件。
|
||||
// 供 GitVer.rc 使用
|
||||
//
|
||||
#define IDI_ICON_APP 100
|
||||
#define IDI_ICON1 101
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
// // 包含 SDKDDKVer.h 可定义可用的最高版本的 Windows 平台。
|
||||
// 如果希望为之前的 Windows 平台构建应用程序,在包含 SDKDDKVer.h 之前请先包含 WinSDKVer.h 并
|
||||
// 将 _WIN32_WINNT 宏设置为想要支持的平台。
|
||||
// // 包含 SDKDDKVer.h 可定义可用的最高版本的 Windows 平台。
|
||||
// 如果希望为之前的 Windows 平台构建应用程序,在包含 SDKDDKVer.h 之前请先包含 WinSDKVer.h 并
|
||||
// 将 _WIN32_WINNT 宏设置为想要支持的平台。
|
||||
#include <SDKDDKVer.h>
|
||||
|
||||
@@ -298,6 +298,9 @@ gitver setup=1 pid=5 repodir=E:\Code\MyProj
|
||||
|
||||
```
|
||||
gitver upload username=<用户名> [file=<上传文件>] [info=<配置.txt>] [url=<PMC基址>]
|
||||
gitver upload username=<用户名> # 需在分支上且 HEAD 指向 Tag
|
||||
gitver upload username=<用户名> -f # 强制上传,忽略 Tag 校验
|
||||
gitver upload -f username=<用户名> [file=<上传文件>] # -f 位置任意
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
|
||||
Reference in New Issue
Block a user