VisualSvn 的 Repositories 下面,每个库都有hooks目录这里存放9种hook.
我比较关心post-commit,要限制文件扩展名,采用白名单.
hooks支持批处理和EXE,比如post-commit.bat或post-commit.exe放在Repositories\code\hooks下面,在提交后,svn server会自动调用,
如果程序返回0表示成功,返回1表示失败. 可以把错误信息写到stderr,server会把错误信息推送到客户端
批处理语法难懂,喜欢用lua.所以写个exe,转调到lua中,在lua中实现主要判断逻辑.
下面是c++代码
#define CP_GBK (936) // 简体中文code page
void Unicode_to_GBK(const wchar_t* in, size_t len, std::string& out)
{
int bufferlen = (int)::WideCharToMultiByte(CP_GBK,0,in,(int)len,NULL,0,NULL,NULL);
char* pBuffer = new char[bufferlen + 4];
if ( NULL == pBuffer )
{
return;
}
int out_len = ::WideCharToMultiByte(CP_GBK,0,in,(int)len,pBuffer,bufferlen+2,NULL,NULL);
pBuffer[bufferlen] = '\0';
out.assign( pBuffer, out_len );
delete[] pBuffer;
}
static int getShortPathName(lua_State* L)
{
const char* lpFullPath = luaL_checkstring(L, 1);
char strShortPath[MAX_PATH] = {0};
GetShortPathNameA(lpFullPath, strShortPath, MAX_PATH);
lua_pushstring(L, strShortPath);
return 1;
}
static int pathFindExtension(lua_State* L)
{
lua_pushstring(L, PathFindExtensionA(luaL_checkstring(L, 1)));
return 1;
}
int MainImpl()
{
int argc = 0;
LPWSTR * argv = CommandLineToArgvW(GetCommandLine(), &argc);
wchar_t* lpLuaFile = StrDup(argv[0]);
PathRenameExtension(lpLuaFile, L".lua") ;
int ret = 0;
if (PathFileExists(lpLuaFile) && argv[1] && argv[2])
{
std::string strLuaFile;
Unicode_to_GBK(lpLuaFile, wcslen(lpLuaFile), strLuaFile);
lua_State* L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, getShortPathName);
lua_setglobal(L, "getShortPathName");
lua_pushcfunction(L, pathFindExtension);
lua_setglobal(L, "pathFindExtension");
if (luaL_dofile(L, strLuaFile.c_str()) == 0)
{
lua_getglobal(L, "MainCheck");
if(!lua_isfunction(L, -1))
{
fprintf(stderr, "could not find MainCheck function");
return ret;
}
std::string strDir, strTxn;
Unicode_to_GBK(argv[1], wcslen(argv[1]), strDir);
Unicode_to_GBK(argv[2], wcslen(argv[2]), strTxn);
lua_pushstring(L, strDir.c_str());
lua_pushstring(L, strTxn.c_str());
if (lua_pcall(L, 2, 1, 0) == 0)
{
ret = lua_tointeger(L, -1);
}
else
{
fprintf(stderr, "%s", lua_tostring(L, -1));
}
}
lua_close(L);
}
LocalFree(lpLuaFile);
LocalFree(argv);
return ret;
}
int main()
{
return MainImpl();
}
int __stdcall WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPSTR lpCmdLine, __in int nShowCmd )
{
return MainImpl();
}
下面是lua代码
local svn_look = [[C:\Program Files\VisualSVN Server\bin\svnlook.exe]] -- 通过svn_look返回提交的所有文件列表
print = function(...)
log = log or io.open([[D:\Repositories\code\hooks\pre-commit.log]], "w")
for k, v in ipairs(arg) do
log:write(tostring(v), "\t")
end
log:write("\n")
end
-- 白名单
local allow_list = {
-- c,c++
[".c"] = true,
[".cpp"] = true,
[".h"] = true,
[".cxx"] = true,
[".hpp"] = true,
[".aps"] = true,
[".idl"] = true,
-- vs
[".sln"] = true,
[".vcproj"] = true,
[".dsw"] = true,
-- other
[".html"] = true,
[".htm"] = true,
[".xml"] = true,
[".lua"] = true,
[".txt"] = true,
[".bat"] = true,
-- res
[".ico"] = true,
[".rc"] = true,
-- office
[".doc"] = true,
[".docx"] = true,
[".ppt"] = true,
[".pptx"] = true,
--image
[".jpg"] = true,
[".png"] = true,
[".gif"] = true,
[".bmp"] = true,
--xar
[".cfg"] = true,
}
function MainCheck(dir, txn)
print("MainCheck", dir, txn)
local cmd = string.format("%s changed \"%s\" -t \"%s\"", getShortPathName(svn_look), dir, txn)
print(cmd)
local p = io.popen(cmd)
for line in p:lines() do
print(line, line:sub(4), pathFindExtension(line:sub(4)))
local ext = pathFindExtension(line:sub(4))
if ext:len() ~= 0 and not allow_list[ext:lower()] then
io.stderr:write(ext:lower().. " 不允许提交")
return 1
end
end
log:close()
return 0
end
用法:把生成的exe,改名为9中hook的任意一种,然后lua文件与exe同名,放在hooks目录下.
有编译好的文件,依赖vc2005和vc2008的运行时.