0

使用 .NET Git 程式庫 LibGit2Sharp 檔案不落地生成異動對照表

 6 months ago
source link: https://blog.darkthread.net/blog/libgit2sharp/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

使用 .NET Git 程式庫 LibGit2Sharp 檔案不落地生成異動對照表-黑暗執行緒

遇到一個小需求,想用 .NET 出一份檔案修改前後對照表,我心中最理想的方案是用 git diff + diff2html 產生 HTML 報表,省時省力又好看。

Fig1_638452451361125707.png

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 = "[email protected]";
// 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();
}

輕輕鬆鬆搞定。

Fig2_638452451363215435.png

最後,來看一下一開始的 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());
    }
}

就醬,又解掉一件任務。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK