使用 .NET Git 程式庫 LibGit2Sharp 檔案不落地生成異動對照表
0 |
遇到一個小需求,想用 .NET 出一份檔案修改前後對照表,我心中最理想的方案是用 git diff + diff2html 產生 HTML 報表,省時省力又好看。
git diff 指令跟產生 diff2html 網頁技能是現成的,最不花腦的解決方法是用 .NET 將修改前後檔案雙雙寫成檔案,呼叫外部程式 git.exe 跑 git diff a.txt b.txt
取得結果,再交給 diff2html 顯示對照表。有沒有更輕巧簡潔的解法?
libgit2 是一個用 C 開發的開源 Git 可攜程式庫,讓你不用安裝 Git 直接引用 Git 的各項功能,libgit2sharp 則為 libgit2 提供了 .NET 相容介面,會比呼叫 git.exe 更優雅。
在專案執行 dotnet add package libgit2sharp
或從 NuGet 安裝後,就可以在 .NET 程式裡直接進行各項 Git 操作。
以下是個簡單範例,示範 用 C# 建立一個 Git 儲存庫,加入 test.txt、Commit、修改 test.txt、再次 Commit、列出 Commit 記錄、顯示 Commit 的異動內容:
using LibGit2Sharp;
Func<string> CreateTempRepo = () =>
{
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(path);
Repository.Init(path, false);
return path;
};
var userName = "Tester";
var email = "tester@mail.net";
// Signature 物件,代表 Commit 的作者或提交者
Func<Signature> getSignature = () => new Signature(userName, email, DateTimeOffset.Now);
var repoPath = CreateTempRepo();
using (var repo = new Repository(repoPath))
{
var fileName = "test.txt";
var path = Path.Combine(repoPath, fileName);
File.WriteAllText(path, "Hello, World!\nFirst Commit");
Commands.Stage(repo, fileName);
repo.Commit("Initial commit",
// Author Committer
getSignature(), getSignature());
File.AppendAllText(path, "\nSecond Commit");
Commands.Stage(repo, fileName);
Thread.Sleep(1000);
repo.Commit("Second commit", getSignature(), getSignature());
print("**** Commit 紀錄 ****", ConsoleColor.Magenta);
foreach (var commit in repo.Commits)
{
print("Commit: " + commit.Id, ConsoleColor.Cyan);
print(" " + commit.Message, noNewLine: true);
print($" by {commit.Author.Name} at {commit.Author.When:HH:mm:ss}", ConsoleColor.Green);
}
var commit1 = repo.Commits.Last();
var commit2 = repo.Commits.First();
var changes = repo.Diff.Compare<Patch>(commit1.Tree, commit2.Tree);
print("**** 異動比對 ****", ConsoleColor.Magenta);
foreach (var change in changes)
{
print(change.Patch, ConsoleColor.DarkYellow);
}
}
void print(string text, ConsoleColor color = ConsoleColor.White, bool noNewLine = false)
{
Console.ForegroundColor = color;
if (noNewLine)
Console.Write(text);
else
Console.WriteLine(text);
Console.ResetColor();
}
輕輕鬆鬆搞定。
最後,來看一下一開始的 XML 修改前後對照是怎麼做出來的?
public static void Run()
{
var xml= XDocument.Parse(@"
<list>
<item id=""N1"">Item 1</item>
<item id=""N2"">Item 2</item>
<item id=""N3"">Item 3</item>
<item id=""N4"">Item 4</item>
</list>
");
var origin = xml.ToString();
xml.Root.Elements("item").Skip(1).First().SetAttributeValue("id", "N2-new");
xml.Root.Elements("item").Skip(2).First().Value = "Item 3 (modified)";
xml.Root.Add(new XElement("item", new XAttribute("id", "N5"), "Item 5"));
var modified = xml.ToString();
var html = GitDiffTool.GenGitDiffHtmlReport(origin, modified, "origin.xml", "modified.xml");
File.WriteAllText("D:\\Report.html", html);
// 用預設瀏覽器開啟
System.Diagnostics.Process.Start("cmd", @"/c ""start D:\Report.html""");
}
關鍵的 GitDiffTool.GenGitDiffHtmlReport 如下,建立一個暫存目錄,直接將修改前後字串內容轉成 Blob 交由 Diff.Comapre() 比對。結果字串交給 diff2html 顯示,過程我用了 HTML 單檔文件挑戰 - 內嵌 .js GZIP 壓縮檔展示過的技巧,將差異結果用 Base64 內嵌在網頁裡。
using System.Text;
using LibGit2Sharp;
public class GitDiffTool
{
static string CreateTempRepo()
{
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(path);
Repository.Init(path, false);
return path;
}
const string html = @"
<!DOCTYPE html>
<html>
<head>
<link rel=""stylesheet"" type=""text/css""
href=""https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css"" />
<script src=""https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html.min.js""></script>
</head>
<body>
<script id='diff.txt' type=""text/base64"">
[$diff.txt$]
</script>
<div id=report>
</div>
<script>
var diffText = new TextDecoder().decode(
new Uint8Array(
atob(
document.getElementById('diff.txt').innerHTML.replace(/[\n\r \t]/g, '')
).split('').map(c => c.charCodeAt(0))
)
);
var diffHtml = Diff2Html.html(diffText, {
drawFileList: true,
matching: 'lines',
outputFormat: 'side-by-side',
});
document.getElementById('report').innerHTML = diffHtml;
</script>
</body>
</html>";
public static string GenGitDiff(string oldText, string newText, string oldFile = "old", string newFile = "new")
{
using (var repo = new Repository(CreateTempRepo()))
{
var lines = new[] { "Line 1", "Line 2", "Line 3" };
var blob1 = repo.ObjectDatabase.CreateBlob(
// 由字串建立 Blob 物件 (也可改成讀檔案傳入 FileStream)
new MemoryStream(Encoding.UTF8.GetBytes(oldText)));
var blob2 = repo.ObjectDatabase.CreateBlob(
new MemoryStream(Encoding.UTF8.GetBytes(newText)));
// 比較兩個 Blob 物件差異
var patch = $@"
diff --git a/{oldFile} b/{newFile}
index aaaaaaa..bbbbbbb 100644
--- a/{oldFile}
+++ b/{newFile}
{repo.Diff.Compare(blob1, blob2).Patch}";
return patch;
}
}
public static string GenGitDiffHtmlReport(string oldText, string newText, string oldFile = "old", string newFile = "new")
{
var diff = GenGitDiff(oldText, newText, oldFile, newFile);
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(diff));
var pem = new StringBuilder();
const int maxWidth = 160;
for (int i = 0; i < base64.Length; i += maxWidth)
{
pem.AppendLine(base64.Substring(i, Math.Min(maxWidth, base64.Length - i)));
}
return html.Replace("[$diff.txt$]", pem.ToString());
}
}
就醬,又解掉一件任務。
Comments
Be the first to post a comment