From 1c5766fe494a46547b5baaccda5473857a155894 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Sat, 20 Apr 2013 21:30:18 +0200 Subject: [PATCH] Allow to use libgit2 for unified diff Signed-off-by: Frank Li Signed-off-by: Sup Yut Sum Signed-off-by: Sven Strickroth --- ext/libgit2 | 2 +- src/Git/Git.cpp | 242 +++++++++++++++++++++++ src/Git/Git.h | 5 + src/Git/GitStatusListCtrl.cpp | 2 +- src/TortoiseProc/AppUtils.cpp | 32 +-- src/TortoiseProc/AppUtils.h | 3 +- src/TortoiseProc/Commands/ShowCompareCommand.cpp | 17 +- src/TortoiseProc/GitLogListAction.cpp | 39 +--- 8 files changed, 267 insertions(+), 75 deletions(-) diff --git a/ext/libgit2 b/ext/libgit2 index 8cfd54f0d..2b63dbfbc 160000 --- a/ext/libgit2 +++ b/ext/libgit2 @@ -1 +1 @@ -Subproject commit 8cfd54f0d831922c58e62e5f69f364ede0cea89f +Subproject commit 2b63dbfbc12746465f6b37aa2b050cdc77679657 diff --git a/src/Git/Git.cpp b/src/Git/Git.cpp index 9ef8104c6..6581bce91 100644 --- a/src/Git/Git.cpp +++ b/src/Git/Git.cpp @@ -2242,3 +2242,245 @@ bool CGit::UsingLibGit2(int cmd) return ((1 << cmd) & m_IsUseLibGit2_mask) ? true : false; return false; } + +CString CGit::GetUnifiedDiffCmd(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, bool bMerge, bool bCombine) +{ + CString cmd; + if (rev2 == GitRev::GetWorkingCopy()) + cmd.Format(_T("git.exe diff --stat -p %s"), rev1); + else if (rev1 == GitRev::GetWorkingCopy()) + cmd.Format(_T("git.exe diff -R --stat -p %s"), rev2); + else + { + CString merge; + if (bMerge) + merge += _T(" -m"); + + if (bCombine) + merge += _T(" -c"); + + cmd.Format(_T("git.exe diff-tree -r -p%s --stat %s %s"), merge, rev1, rev2); + } + + if (!path.IsEmpty()) + { + cmd += _T(" -- \""); + cmd += path.GetGitPathString(); + cmd += _T("\""); + } + + return cmd; +} + +static int UnifiedDiffToFile(const git_diff_delta * /* delta */, const git_diff_range * /* range */, char /* line_origin */, const char *content, size_t content_len, void *payload) +{ + ATLASSERT(payload && content); + fwrite(content, 1, content_len, (FILE *)payload); + return 0; +} + +static int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree) +{ + ATLASSERT(repo && identifier && tree); + + /* try to resolve identifier */ + git_object *obj = nullptr; + if (git_revparse_single(&obj, repo, identifier)) + return -1; + + if (obj == nullptr) + return GIT_ENOTFOUND; + + int err = 0; + switch (git_object_type(obj)) + { + case GIT_OBJ_TREE: + *tree = (git_tree *)obj; + break; + case GIT_OBJ_COMMIT: + err = git_commit_tree(tree, (git_commit *)obj); + git_object_free(obj); + break; + default: + err = GIT_ENOTFOUND; + } + + return err; +} + +/* use libgit2 get unified diff */ +static int GetUnifiedDiffLibGit2(const CTGitPath& /*path*/, const git_revnum_t& rev1, const git_revnum_t& rev2, git_diff_data_cb callback, void *data, bool /* bMerge */) +{ + git_repository *repo = nullptr; + CStringA gitdirA = CUnicodeUtils::GetMulti(CTGitPath(g_Git.m_CurrentDir).GetGitPathString(), CP_UTF8); + CStringA tree1 = CUnicodeUtils::GetMulti(rev1, CP_UTF8); + CStringA tree2 = CUnicodeUtils::GetMulti(rev2, CP_UTF8); + int ret = 0; + + if (git_repository_open(&repo, gitdirA.GetBuffer())) + { + gitdirA.ReleaseBuffer(); + return -1; + } + gitdirA.ReleaseBuffer(); + + int isHeadOrphan = git_repository_head_orphan(repo); + if (isHeadOrphan != 0) + { + git_repository_free(repo); + if (isHeadOrphan == 1) + return 0; + else + return -1; + } + + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff = nullptr; + + if (rev1 == GitRev::GetWorkingCopy() || rev2 == GitRev::GetWorkingCopy()) + { + git_tree *t1 = nullptr; + git_diff_list *diff2 = nullptr; + + do + { + if (rev1 != GitRev::GetWorkingCopy()) + { + if (resolve_to_tree(repo, tree1, &t1)) + { + ret = -1; + break; + } + } + if (rev2 != GitRev::GetWorkingCopy()) + { + if (resolve_to_tree(repo, tree2, &t1)) + { + ret = -1; + break; + } + } + + ret = git_diff_tree_to_index(&diff, repo, t1, nullptr, &opts); + if (ret) + break; + + ret = git_diff_index_to_workdir(&diff2, repo, nullptr, &opts); + if (ret) + break; + + ret = git_diff_merge(diff, diff2); + if (ret) + break; + + ret = git_diff_print_patch(diff, callback, data); + if (ret) + break; + } while(0); + if (diff) + git_diff_list_free(diff); + if (diff2) + git_diff_list_free(diff2); + if (t1) + git_tree_free(t1); + } + else + { + git_tree *t1 = nullptr, *t2 = nullptr; + do + { + if (tree1.IsEmpty() && tree2.IsEmpty()) + { + ret = -1; + break; + } + + if (tree1.IsEmpty()) + { + tree1 = tree2; + tree2.Empty(); + } + + if (!tree1.IsEmpty() && resolve_to_tree(repo, tree1, &t1)) + { + ret = -1; + break; + } + + if (tree2.IsEmpty()) + { + /* don't check return value, there are not parent commit at first commit*/ + resolve_to_tree(repo, tree1 + "~1", &t2); + } + else if (resolve_to_tree(repo, tree2, &t2)) + { + ret = -1; + break; + } + if (git_diff_tree_to_tree(&diff, repo, t2, t1, &opts)) + { + ret = -1; + break; + } + if(git_diff_print_patch(diff, callback, data)) + { + ret = -1; + break; + } + } while(0); + + if (diff) + git_diff_list_free(diff); + if (t1) + git_tree_free(t1); + if (t2) + git_tree_free(t2); + } + git_repository_free(repo); + + return ret; +} + +int CGit::GetUnifiedDiff(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, CString patchfile, bool bMerge, bool bCombine) +{ + if (UsingLibGit2(GIT_CMD_DIFF)) + { + FILE *file = nullptr; + _tfopen_s(&file, patchfile, _T("w")); + if (file == nullptr) + return -1; + int ret = GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffToFile, file, bMerge); + fclose(file); + return ret; + } + else + { + CString cmd; + cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine); + return g_Git.RunLogFile(cmd, patchfile); + } +} + +static int UnifiedDiffToStringA(const git_diff_delta * /*delta*/, const git_diff_range * /*range*/, char /*line_origin*/, const char *content, size_t content_len, void *payload) +{ + ATLASSERT(payload && content); + CStringA *str = (CStringA*) payload; + str->Append(content, content_len); + return 0; +} + +int CGit::GetUnifiedDiff(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, CStringA * buffer, bool bMerge, bool bCombine) +{ + if (UsingLibGit2(GIT_CMD_DIFF)) + return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffToStringA, buffer, bMerge); + else + { + CString cmd; + cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine); + BYTE_VECTOR vector; + int ret = Run(cmd, &vector); + if (!vector.empty()) + buffer->Append((char *)&vector[0]); + return ret; + } +} diff --git a/src/Git/Git.h b/src/Git/Git.h index 7da64cf0b..a36061b80 100644 --- a/src/Git/Git.h +++ b/src/Git/Git.h @@ -176,6 +176,7 @@ private: HANDLE fileHandle; CGitCall* pcall; } ASYNCREADSTDERRTHREADARGS, *PASYNCREADSTDERRTHREADARGS; + CString GetUnifiedDiffCmd(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, bool bMerge, bool bCombine); public: int RunAsync(CString cmd, PROCESS_INFORMATION *pi, HANDLE* hRead, HANDLE *hErrReadOut, CString *StdioFile = NULL); @@ -354,8 +355,12 @@ public: GIT_CMD_CLONE, GIT_CMD_FETCH, GIT_CMD_COMMIT_UPDATE_INDEX, + GIT_CMD_DIFF, }; bool UsingLibGit2(int cmd); + + int GetUnifiedDiff(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, CString patchfile, bool bMerge, bool bCombine); + int GetUnifiedDiff(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, CStringA * buffer, bool bMerge, bool bCombine); }; extern void GetTempPath(CString &path); extern CString GetTempFile(); diff --git a/src/Git/GitStatusListCtrl.cpp b/src/Git/GitStatusListCtrl.cpp index 1ed14368a..bf334ea27 100644 --- a/src/Git/GitStatusListCtrl.cpp +++ b/src/Git/GitStatusListCtrl.cpp @@ -1979,7 +1979,7 @@ void CGitStatusListCtrl::OnContextMenuList(CWnd * pWnd, CPoint point) if (!(selectedFilepath->m_ParentNo & MERGE_MASK)) str.Format(_T("%s^%d"), m_CurrentVersion, (selectedFilepath->m_ParentNo & PARENT_MASK) + 1); - CAppUtils::StartShowUnifiedDiff(m_hWnd, *selectedFilepath, str, *selectedFilepath, m_CurrentVersion, false, false, false, !!(selectedFilepath->m_ParentNo & MERGE_MASK)); + CAppUtils::StartShowUnifiedDiff(m_hWnd, *selectedFilepath, str, *selectedFilepath, m_CurrentVersion, false, false, false, false, !!(selectedFilepath->m_ParentNo & MERGE_MASK)); } } } diff --git a/src/TortoiseProc/AppUtils.cpp b/src/TortoiseProc/AppUtils.cpp index 964fbae1d..8d47eb4fd 100644 --- a/src/TortoiseProc/AppUtils.cpp +++ b/src/TortoiseProc/AppUtils.cpp @@ -943,38 +943,20 @@ CString CAppUtils::GetProjectNameFromURL(CString url) bool CAppUtils::StartShowUnifiedDiff(HWND /*hWnd*/, const CTGitPath& url1, const git_revnum_t& rev1, const CTGitPath& /*url2*/, const git_revnum_t& rev2, //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */, - bool /*bAlternateDiff*/ /* = false */, bool /*bIgnoreAncestry*/ /* = false */, bool /* blame = false */, bool bMerge) + bool /*bAlternateDiff*/ /* = false */, bool /*bIgnoreAncestry*/ /* = false */, + bool /* blame = false */, + bool bMerge, + bool bCombine) { CString tempfile=GetTempFile(); - CString cmd; - if(rev2 == GitRev::GetWorkingCopy()) - { - cmd.Format(_T("git.exe diff --stat -p %s "), rev1); - } - else if (rev1 == GitRev::GetWorkingCopy()) + if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine)) { - cmd.Format(_T("git.exe diff -R --stat -p %s "), rev2); - } - else - { - CString merge; - if(bMerge) - merge = _T("-c"); - - cmd.Format(_T("git.exe diff-tree -r -p %s --stat %s %s"),merge, rev1,rev2); - } - - if( !url1.IsEmpty() ) - { - cmd += _T(" -- \""); - cmd += url1.GetGitPathString(); - cmd += _T("\" "); + TRACE("Fail get unified diff\n"); + return false; } - g_Git.RunLogFile(cmd,tempfile); CAppUtils::StartUnifiedDiffViewer(tempfile, rev1 + _T(":") + rev2); - #if 0 CString sCmd; sCmd.Format(_T("%s /command:showcompare /unified"), diff --git a/src/TortoiseProc/AppUtils.h b/src/TortoiseProc/AppUtils.h index 0fb6ad3ad..d6eade68b 100644 --- a/src/TortoiseProc/AppUtils.h +++ b/src/TortoiseProc/AppUtils.h @@ -154,7 +154,8 @@ public: bool bAlternateDiff = false, bool bIgnoreAncestry = false, bool blame = false, - bool bMerge = false); + bool bMerge = false, + bool bCompact = false); static bool Export(CString *BashHash=NULL); static bool CreateBranchTag(bool IsTag=TRUE,CString *CommitHash=NULL, bool switch_new_brach=false); diff --git a/src/TortoiseProc/Commands/ShowCompareCommand.cpp b/src/TortoiseProc/Commands/ShowCompareCommand.cpp index 24728ce01..dff4af1f8 100644 --- a/src/TortoiseProc/Commands/ShowCompareCommand.cpp +++ b/src/TortoiseProc/Commands/ShowCompareCommand.cpp @@ -1,6 +1,6 @@ // TortoiseGit - a Windows shell extension for easy version control -// Copyright (C) 2012 - TortoiseGit +// Copyright (C) 2012-2013 - TortoiseGit // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -34,20 +34,7 @@ bool ShowCompareCommand::Execute() rev2 = parser.GetVal(_T("revision2")); if (unified) - { - CString tempfile = GetTempFile(); - CString cmd; - - if (rev1.IsEmpty()) - cmd.Format(_T("git.exe diff -r -p --stat %s"), rev2); - else if (rev2.IsEmpty()) - cmd.Format(_T("git.exe diff -r -p --stat %s"), rev1); - else - cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"), rev1, rev2); - - g_Git.RunLogFile(cmd, tempfile); - return !!CAppUtils::StartUnifiedDiffViewer(tempfile, rev1 + _T(":") + rev2); - } + return !!CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), rev1, CTGitPath(), rev2); else return !!CGitDiff::DiffCommit(cmdLinePath, rev2, rev1); } diff --git a/src/TortoiseProc/GitLogListAction.cpp b/src/TortoiseProc/GitLogListAction.cpp index 2d4ad7270..7d63c2a64 100644 --- a/src/TortoiseProc/GitLogListAction.cpp +++ b/src/TortoiseProc/GitLogListAction.cpp @@ -205,22 +205,18 @@ void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMe break; case ID_GNUDIFF1: // compare with WC, unified { - CString tempfile=GetTempFile(); - CString command; GitRev * r1 = reinterpret_cast(m_arShownList.GetAt(FirstSelect)); + bool bMerge = false, bCombine = false; + CString hash2; if(!r1->m_CommitHash.IsEmpty()) { CString merge; - CString hash2; cmd >>= 16; if( (cmd&0xFFFF) == 0xFFFF) - { - merge=_T("-m"); - } + bMerge = true; + else if((cmd&0xFFFF) == 0xFFFE) - { - merge=_T("-c"); - } + bCombine = true; else { if(cmd > r1->m_ParentHash.size()) @@ -236,37 +232,16 @@ void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMe hash2 = r1->m_ParentHash[cmd-1].ToString(); } } - command.Format(_T("git.exe diff-tree %s -r -p --stat %s %s"), merge, hash2, r1->m_CommitHash.ToString()); } - else - command.Format(_T("git.exe diff -r -p --stat")); - - g_Git.RunLogFile(command,tempfile); - CAppUtils::StartUnifiedDiffViewer(tempfile, r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->GetSubject()); + CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r1->m_CommitHash.ToString(), CTGitPath(), hash2, false, false, false, bMerge, bCombine); } break; case ID_GNUDIFF2: // compare two revisions, unified { - CString tempfile=GetTempFile(); - CString cmd; GitRev * r1 = reinterpret_cast(m_arShownList.GetAt(FirstSelect)); GitRev * r2 = reinterpret_cast(m_arShownList.GetAt(LastSelect)); - - if( r1->m_CommitHash.IsEmpty()) { - cmd.Format(_T("git.exe diff -r -p --stat %s"),r2->m_CommitHash.ToString()); - } - else if( r2->m_CommitHash.IsEmpty()) { - cmd.Format(_T("git.exe diff -r -p --stat %s"),r1->m_CommitHash.ToString()); - } - else - { - cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r2->m_CommitHash.ToString(),r1->m_CommitHash.ToString()); - } - - g_Git.RunLogFile(cmd,tempfile); - CAppUtils::StartUnifiedDiffViewer(tempfile, r2->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength())); - + CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r1->m_CommitHash.ToString(), CTGitPath(), r2->m_CommitHash.ToString()); } break; -- 2.11.4.GIT