Allow to use libgit2 for unified diff
authorFrank Li <lznuaa@gmail.com>
Sat, 20 Apr 2013 19:30:18 +0000 (20 21:30 +0200)
committerSven Strickroth <email@cs-ware.de>
Sat, 20 Apr 2013 19:50:47 +0000 (20 21:50 +0200)
Signed-off-by: Frank Li <lznuaa@gmail.com>
Signed-off-by: Sup Yut Sum <ch3cooli@gmail.com>
Signed-off-by: Sven Strickroth <email@cs-ware.de>
ext/libgit2
src/Git/Git.cpp
src/Git/Git.h
src/Git/GitStatusListCtrl.cpp
src/TortoiseProc/AppUtils.cpp
src/TortoiseProc/AppUtils.h
src/TortoiseProc/Commands/ShowCompareCommand.cpp
src/TortoiseProc/GitLogListAction.cpp

index 8cfd54f..2b63dbf 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 8cfd54f0d831922c58e62e5f69f364ede0cea89f
+Subproject commit 2b63dbfbc12746465f6b37aa2b050cdc77679657
index 9ef8104..6581bce 100644 (file)
@@ -2242,3 +2242,245 @@ bool CGit::UsingLibGit2(int cmd)
                return ((1 << cmd) & m_IsUseLibGit2_mask) ? true : false;\r
        return false;\r
 }\r
+\r
+CString CGit::GetUnifiedDiffCmd(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, bool bMerge, bool bCombine)\r
+{\r
+       CString cmd;\r
+       if (rev2 == GitRev::GetWorkingCopy())\r
+               cmd.Format(_T("git.exe diff --stat -p %s"), rev1);\r
+       else if (rev1 == GitRev::GetWorkingCopy())\r
+               cmd.Format(_T("git.exe diff -R --stat -p %s"), rev2);\r
+       else\r
+       {\r
+               CString merge;\r
+               if (bMerge)\r
+                       merge += _T(" -m");\r
+\r
+               if (bCombine)\r
+                       merge += _T(" -c");\r
+\r
+               cmd.Format(_T("git.exe diff-tree -r -p%s --stat %s %s"), merge, rev1, rev2);\r
+       }\r
+\r
+       if (!path.IsEmpty())\r
+       {\r
+               cmd += _T(" -- \"");\r
+               cmd += path.GetGitPathString();\r
+               cmd += _T("\"");\r
+       }\r
+\r
+       return cmd;\r
+}\r
+\r
+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)\r
+{\r
+       ATLASSERT(payload && content);\r
+       fwrite(content, 1, content_len, (FILE *)payload);\r
+       return 0;\r
+}\r
+\r
+static int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree)\r
+{\r
+       ATLASSERT(repo && identifier && tree);\r
+\r
+       /* try to resolve identifier */\r
+       git_object *obj = nullptr;\r
+       if (git_revparse_single(&obj, repo, identifier))\r
+               return -1;\r
+\r
+       if (obj == nullptr)\r
+               return GIT_ENOTFOUND;\r
+\r
+       int err = 0;\r
+       switch (git_object_type(obj))\r
+       {\r
+       case GIT_OBJ_TREE:\r
+               *tree = (git_tree *)obj;\r
+               break;\r
+       case GIT_OBJ_COMMIT:\r
+               err = git_commit_tree(tree, (git_commit *)obj);\r
+               git_object_free(obj);\r
+               break;\r
+       default:\r
+               err = GIT_ENOTFOUND;\r
+       }\r
+\r
+       return err;\r
+}\r
+\r
+/* use libgit2 get unified diff */\r
+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 */)\r
+{\r
+       git_repository *repo = nullptr;\r
+       CStringA gitdirA = CUnicodeUtils::GetMulti(CTGitPath(g_Git.m_CurrentDir).GetGitPathString(), CP_UTF8);\r
+       CStringA tree1 = CUnicodeUtils::GetMulti(rev1, CP_UTF8);\r
+       CStringA tree2 = CUnicodeUtils::GetMulti(rev2, CP_UTF8);\r
+       int ret = 0;\r
+\r
+       if (git_repository_open(&repo, gitdirA.GetBuffer()))\r
+       {\r
+               gitdirA.ReleaseBuffer();\r
+               return -1;\r
+       }\r
+       gitdirA.ReleaseBuffer();\r
+\r
+       int isHeadOrphan = git_repository_head_orphan(repo);\r
+       if (isHeadOrphan != 0)\r
+       {\r
+               git_repository_free(repo);\r
+               if (isHeadOrphan == 1)\r
+                       return 0;\r
+               else\r
+                       return -1;\r
+       }\r
+\r
+       git_diff_options opts = GIT_DIFF_OPTIONS_INIT;\r
+       git_diff_list *diff = nullptr;\r
+\r
+       if (rev1 == GitRev::GetWorkingCopy() || rev2 == GitRev::GetWorkingCopy())\r
+       {\r
+               git_tree *t1 = nullptr;\r
+               git_diff_list *diff2 = nullptr;\r
+\r
+               do\r
+               {\r
+                       if (rev1 != GitRev::GetWorkingCopy())\r
+                       {\r
+                               if (resolve_to_tree(repo, tree1, &t1))\r
+                               {\r
+                                       ret = -1;\r
+                                       break;\r
+                               }\r
+                       }\r
+                       if (rev2 != GitRev::GetWorkingCopy())\r
+                       {\r
+                               if (resolve_to_tree(repo, tree2, &t1))\r
+                               {\r
+                                       ret = -1;\r
+                                       break;\r
+                               }\r
+                       }\r
+\r
+                       ret = git_diff_tree_to_index(&diff, repo, t1, nullptr, &opts);\r
+                       if (ret)\r
+                               break;\r
+\r
+                       ret = git_diff_index_to_workdir(&diff2, repo, nullptr, &opts);\r
+                       if (ret) \r
+                               break;\r
+\r
+                       ret = git_diff_merge(diff, diff2);\r
+                       if (ret) \r
+                               break;\r
+\r
+                       ret = git_diff_print_patch(diff, callback, data);\r
+                       if (ret)\r
+                               break;\r
+               } while(0);\r
+               if (diff)\r
+                       git_diff_list_free(diff);\r
+               if (diff2)\r
+                       git_diff_list_free(diff2);\r
+               if (t1)\r
+                       git_tree_free(t1);\r
+       }\r
+       else\r
+       {\r
+               git_tree *t1 = nullptr, *t2 = nullptr;\r
+               do\r
+               {\r
+                       if (tree1.IsEmpty() && tree2.IsEmpty())\r
+                       {\r
+                               ret = -1;\r
+                               break;\r
+                       }\r
+\r
+                       if (tree1.IsEmpty())\r
+                       {\r
+                               tree1 = tree2;\r
+                               tree2.Empty();\r
+                       }\r
+\r
+                       if (!tree1.IsEmpty() && resolve_to_tree(repo, tree1, &t1))\r
+                       {\r
+                               ret = -1;\r
+                               break;\r
+                       }\r
+\r
+                       if (tree2.IsEmpty())\r
+                       {\r
+                               /* don't check return value, there are not parent commit at first commit*/\r
+                               resolve_to_tree(repo, tree1 + "~1", &t2);\r
+                       }\r
+                       else if (resolve_to_tree(repo, tree2, &t2))\r
+                       {\r
+                               ret = -1;\r
+                               break;\r
+                       }\r
+                       if (git_diff_tree_to_tree(&diff, repo, t2, t1, &opts))\r
+                       {\r
+                               ret = -1;\r
+                               break;\r
+                       }\r
+                       if(git_diff_print_patch(diff, callback, data))\r
+                       {\r
+                               ret = -1;\r
+                               break;\r
+                       }\r
+               } while(0);\r
+\r
+               if (diff)\r
+                       git_diff_list_free(diff);\r
+               if (t1)\r
+                       git_tree_free(t1);\r
+               if (t2)\r
+                       git_tree_free(t2);\r
+       }\r
+       git_repository_free(repo);\r
+\r
+       return ret;\r
+}\r
+\r
+int CGit::GetUnifiedDiff(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, CString patchfile, bool bMerge, bool bCombine)\r
+{\r
+       if (UsingLibGit2(GIT_CMD_DIFF))\r
+       {\r
+               FILE *file = nullptr;\r
+               _tfopen_s(&file, patchfile, _T("w"));\r
+               if (file == nullptr)\r
+                       return -1;\r
+               int ret = GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffToFile, file, bMerge);\r
+               fclose(file);\r
+               return ret;\r
+       }\r
+       else\r
+       {\r
+               CString cmd;\r
+               cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine);\r
+               return g_Git.RunLogFile(cmd, patchfile);\r
+       }\r
+}\r
+\r
+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)\r
+{\r
+       ATLASSERT(payload && content);\r
+       CStringA *str = (CStringA*) payload;\r
+       str->Append(content, content_len);\r
+       return 0;\r
+}\r
+\r
+int CGit::GetUnifiedDiff(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, CStringA * buffer, bool bMerge, bool bCombine)\r
+{\r
+       if (UsingLibGit2(GIT_CMD_DIFF))\r
+               return GetUnifiedDiffLibGit2(path, rev1, rev2, UnifiedDiffToStringA, buffer, bMerge);\r
+       else\r
+       {\r
+               CString cmd;\r
+               cmd = GetUnifiedDiffCmd(path, rev1, rev2, bMerge, bCombine);\r
+               BYTE_VECTOR vector;\r
+               int ret = Run(cmd, &vector);\r
+               if (!vector.empty())\r
+                       buffer->Append((char *)&vector[0]);\r
+               return ret;\r
+       }\r
+}\r
index 7da64cf..a36061b 100644 (file)
@@ -176,6 +176,7 @@ private:
                HANDLE fileHandle;\r
                CGitCall* pcall;\r
        } ASYNCREADSTDERRTHREADARGS, *PASYNCREADSTDERRTHREADARGS;\r
+       CString GetUnifiedDiffCmd(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, bool bMerge, bool bCombine);\r
 \r
 public:\r
        int RunAsync(CString cmd, PROCESS_INFORMATION *pi, HANDLE* hRead, HANDLE *hErrReadOut, CString *StdioFile = NULL);\r
@@ -354,8 +355,12 @@ public:
                GIT_CMD_CLONE,\r
                GIT_CMD_FETCH,\r
                GIT_CMD_COMMIT_UPDATE_INDEX,\r
+               GIT_CMD_DIFF,\r
        };\r
        bool UsingLibGit2(int cmd);\r
+\r
+       int GetUnifiedDiff(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, CString patchfile, bool bMerge, bool bCombine);\r
+       int GetUnifiedDiff(const CTGitPath& path, const git_revnum_t& rev1, const git_revnum_t& rev2, CStringA * buffer, bool bMerge, bool bCombine);\r
 };\r
 extern void GetTempPath(CString &path);\r
 extern CString GetTempFile();\r
index 1ed1436..bf334ea 100644 (file)
@@ -1979,7 +1979,7 @@ void CGitStatusListCtrl::OnContextMenuList(CWnd * pWnd, CPoint point)
                                                                if (!(selectedFilepath->m_ParentNo & MERGE_MASK))\r
                                                                        str.Format(_T("%s^%d"), m_CurrentVersion, (selectedFilepath->m_ParentNo & PARENT_MASK) + 1);\r
 \r
-                                                               CAppUtils::StartShowUnifiedDiff(m_hWnd, *selectedFilepath, str, *selectedFilepath, m_CurrentVersion, false, false, false, !!(selectedFilepath->m_ParentNo & MERGE_MASK));\r
+                                                               CAppUtils::StartShowUnifiedDiff(m_hWnd, *selectedFilepath, str, *selectedFilepath, m_CurrentVersion, false, false, false, false, !!(selectedFilepath->m_ParentNo & MERGE_MASK));\r
                                                        }\r
                                                }\r
                                        }\r
index 964fbae..8d47eb4 100644 (file)
@@ -943,38 +943,20 @@ CString CAppUtils::GetProjectNameFromURL(CString url)
 bool CAppUtils::StartShowUnifiedDiff(HWND /*hWnd*/, const CTGitPath& url1, const git_revnum_t& rev1,\r
                                                                                                const CTGitPath& /*url2*/, const git_revnum_t& rev2,\r
                                                                                                //const GitRev& peg /* = GitRev */, const GitRev& headpeg /* = GitRev */,\r
-                                                                                               bool /*bAlternateDiff*/ /* = false */, bool /*bIgnoreAncestry*/ /* = false */, bool /* blame = false */, bool bMerge)\r
+                                                                                               bool /*bAlternateDiff*/ /* = false */, bool /*bIgnoreAncestry*/ /* = false */,\r
+                                                                                               bool /* blame = false */, \r
+                                                                                               bool bMerge,\r
+                                                                                               bool bCombine)\r
 {\r
 \r
        CString tempfile=GetTempFile();\r
-       CString cmd;\r
-       if(rev2 == GitRev::GetWorkingCopy())\r
-       {\r
-               cmd.Format(_T("git.exe diff --stat -p %s "), rev1);\r
-       }\r
-       else if (rev1 == GitRev::GetWorkingCopy())\r
+       if (g_Git.GetUnifiedDiff(url1, rev1, rev2, tempfile, bMerge, bCombine))\r
        {\r
-               cmd.Format(_T("git.exe diff -R --stat -p %s "), rev2);\r
-       }\r
-       else\r
-       {\r
-               CString merge;\r
-               if(bMerge)\r
-                               merge = _T("-c");\r
-\r
-               cmd.Format(_T("git.exe diff-tree -r -p %s --stat %s %s"),merge, rev1,rev2);\r
-       }\r
-\r
-       if( !url1.IsEmpty() )\r
-       {\r
-               cmd += _T(" -- \"");\r
-               cmd += url1.GetGitPathString();\r
-               cmd += _T("\" ");\r
+               TRACE("Fail get unified diff\n");\r
+               return false;\r
        }\r
-       g_Git.RunLogFile(cmd,tempfile);\r
        CAppUtils::StartUnifiedDiffViewer(tempfile, rev1 + _T(":") + rev2);\r
 \r
-\r
 #if 0\r
        CString sCmd;\r
        sCmd.Format(_T("%s /command:showcompare /unified"),\r
index 0fb6ad3..d6eade6 100644 (file)
@@ -154,7 +154,8 @@ public:
                                                                                                bool bAlternateDiff = false,\r
                                                                                                bool bIgnoreAncestry = false,\r
                                                                                                bool blame  = false,\r
-                                                                                               bool bMerge = false);\r
+                                                                                               bool bMerge = false,\r
+                                                                                               bool bCompact = false);\r
 \r
        static bool Export(CString *BashHash=NULL);\r
        static bool CreateBranchTag(bool IsTag=TRUE,CString *CommitHash=NULL, bool switch_new_brach=false);\r
index 24728ce..dff4af1 100644 (file)
@@ -1,6 +1,6 @@
 // TortoiseGit - a Windows shell extension for easy version control\r
 \r
-// Copyright (C) 2012 - TortoiseGit\r
+// Copyright (C) 2012-2013 - TortoiseGit\r
 \r
 // This program is free software; you can redistribute it and/or\r
 // modify it under the terms of the GNU General Public License\r
@@ -34,20 +34,7 @@ bool ShowCompareCommand::Execute()
                rev2 = parser.GetVal(_T("revision2"));\r
 \r
        if (unified)\r
-       {\r
-               CString tempfile = GetTempFile();\r
-               CString cmd;\r
-\r
-               if (rev1.IsEmpty())\r
-                       cmd.Format(_T("git.exe diff -r -p --stat %s"), rev2);\r
-               else if (rev2.IsEmpty())\r
-                       cmd.Format(_T("git.exe diff -r -p --stat %s"), rev1);\r
-               else\r
-                       cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"), rev1, rev2);\r
-\r
-               g_Git.RunLogFile(cmd, tempfile);\r
-               return !!CAppUtils::StartUnifiedDiffViewer(tempfile, rev1 + _T(":") + rev2);\r
-       }\r
+               return !!CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), rev1, CTGitPath(), rev2);\r
        else\r
                return !!CGitDiff::DiffCommit(cmdLinePath, rev2, rev1);\r
 }\r
index 2d4ad72..7d63c2a 100644 (file)
@@ -205,22 +205,18 @@ void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMe
                        break;\r
                        case ID_GNUDIFF1: // compare with WC, unified\r
                        {\r
-                               CString tempfile=GetTempFile();\r
-                               CString command;\r
                                GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));\r
+                               bool bMerge = false, bCombine = false;\r
+                               CString hash2;\r
                                if(!r1->m_CommitHash.IsEmpty())\r
                                {\r
                                        CString merge;\r
-                                       CString hash2;\r
                                        cmd >>= 16;\r
                                        if( (cmd&0xFFFF) == 0xFFFF)\r
-                                       {\r
-                                               merge=_T("-m");\r
-                                       }\r
+                                               bMerge = true;\r
+\r
                                        else if((cmd&0xFFFF) == 0xFFFE)\r
-                                       {\r
-                                               merge=_T("-c");\r
-                                       }\r
+                                               bCombine = true;\r
                                        else\r
                                        {\r
                                                if(cmd > r1->m_ParentHash.size())\r
@@ -236,37 +232,16 @@ void CGitLogList::ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMe
                                                                hash2 = r1->m_ParentHash[cmd-1].ToString();\r
                                                }\r
                                        }\r
-                                       command.Format(_T("git.exe diff-tree %s -r -p --stat %s %s"), merge, hash2, r1->m_CommitHash.ToString());\r
                                }\r
-                               else\r
-                                       command.Format(_T("git.exe diff -r -p --stat"));\r
-\r
-                               g_Git.RunLogFile(command,tempfile);\r
-                               CAppUtils::StartUnifiedDiffViewer(tempfile, r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->GetSubject());\r
+                               CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r1->m_CommitHash.ToString(), CTGitPath(), hash2, false, false, false, bMerge, bCombine);\r
                        }\r
                        break;\r
 \r
                        case ID_GNUDIFF2: // compare two revisions, unified\r
                        {\r
-                               CString tempfile=GetTempFile();\r
-                               CString cmd;\r
                                GitRev * r1 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(FirstSelect));\r
                                GitRev * r2 = reinterpret_cast<GitRev*>(m_arShownList.GetAt(LastSelect));\r
-\r
-                               if( r1->m_CommitHash.IsEmpty()) {\r
-                                       cmd.Format(_T("git.exe diff -r -p --stat %s"),r2->m_CommitHash.ToString());\r
-                               }\r
-                               else if( r2->m_CommitHash.IsEmpty()) {\r
-                                       cmd.Format(_T("git.exe diff -r -p --stat %s"),r1->m_CommitHash.ToString());\r
-                               }\r
-                               else\r
-                               {\r
-                                       cmd.Format(_T("git.exe diff-tree -r -p --stat %s %s"),r2->m_CommitHash.ToString(),r1->m_CommitHash.ToString());\r
-                               }\r
-\r
-                               g_Git.RunLogFile(cmd,tempfile);\r
-                               CAppUtils::StartUnifiedDiffViewer(tempfile, r2->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + _T(":") + r1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()));\r
-\r
+                               CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r1->m_CommitHash.ToString(), CTGitPath(), r2->m_CommitHash.ToString());\r
                        }\r
                        break;\r
 \r