1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2024 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "GitAdminDir.h"
26 #include "StringUtils.h"
27 #include "PathUtils.h"
29 #define REG_MSYSGIT_PATH L"Software\\TortoiseGit\\MSysGit"
30 #define REG_SYSTEM_GITCONFIGPATH L"Software\\TortoiseGit\\SystemConfig"
31 #define REG_MSYSGIT_EXTRA_PATH L"Software\\TortoiseGit\\MSysGitExtra"
33 #define DEFAULT_USE_LIBGIT2_MASK (1 << CGit::GIT_CMD_MERGE_BASE) | (1 << CGit::GIT_CMD_DELETETAGBRANCH) | (1 << CGit::GIT_CMD_GETONEFILE) | (1 << CGit::GIT_CMD_ADD) | (1 << CGit::GIT_CMD_CHECKCONFLICTS) | (1 << CGit::GIT_CMD_GET_COMMIT) | (1 << CGit::GIT_CMD_GETCONFLICTINFO)
35 struct git_repository
;
37 using CAutoLocker
= CComCritSecLock
<CComCriticalSection
>;
39 constexpr static inline int ConvertVersionToInt(unsigned __int8 major
, unsigned __int8 minor
, unsigned __int8 patchlevel
, unsigned __int8 build
= 0)
41 return (major
<< 24) + (minor
<< 16) + (patchlevel
<< 8) + build
;
50 SHOW_NO_LIMIT
, // NOTE: no limitation does not mean "without all limitations", it's just without the following limitations. That say, the log still could be limited by author, committer, etc.
61 m_NumberOfLogsScale
= SHOW_NO_LIMIT
;
65 DWORD m_NumberOfLogsScale
;
75 CGitCall(CString cmd
):m_Cmd(cmd
){}
76 virtual ~CGitCall() {}
78 CString
GetCmd()const{return m_Cmd
;}
79 void SetCmd(CString cmd
){m_Cmd
=cmd
;}
81 //This function is called when command output data is available.
82 //When this function returns 'true' the git command should be aborted.
83 //This behavior is not implemented yet.
84 virtual bool OnOutputData(const char* data
, size_t size
) = 0;
85 virtual bool OnOutputErrData(const char* data
, size_t size
) = 0;
86 virtual void OnEnd(){}
92 template <typename GitReceiverFunc
>
93 class CGitCallCb
: public CGitCall
96 CGitCallCb(CString cmd
, const GitReceiverFunc recv
, BYTE_VECTOR
* pvectorErr
= nullptr)
99 , m_pvectorErr(pvectorErr
)
101 static_assert(std::is_convertible_v
<GitReceiverFunc
, std::function
<void(const CStringA
&)>>, "Wrong signature for GitReceiverFunc!");
104 bool OnOutputData(const char* data
, size_t size
) override
108 if (size
== 0 || size
>= INT_MAX
)
110 const int oldEndPos
= m_buffer
.GetLength();
112 if (IntAdd(oldEndPos
, static_cast<int>(size
), &newLength
) != S_OK
)
114 memcpy(CStrBufA(m_buffer
, newLength
, 0) + oldEndPos
, data
, size
);
116 // Break into lines and feed to m_recv
119 while ((eolPos
= m_buffer
.Find('\n')) >= 0)
121 memcpy(CStrBufA(line
, eolPos
, 0), static_cast<const char*>(m_buffer
), eolPos
);
122 auto oldLen
= m_buffer
.GetLength();
123 memmove(m_buffer
.GetBuffer(oldLen
), static_cast<const char*>(m_buffer
) + eolPos
+ 1, m_buffer
.GetLength() - eolPos
- 1);
124 m_buffer
.ReleaseBuffer(oldLen
- eolPos
- 1);
130 bool OnOutputErrData(const char* data
, size_t size
) override
133 if (!m_pvectorErr
|| size
== 0 || size
>= INT_MAX
)
135 const size_t oldsize
= m_pvectorErr
->size();
137 if (SizeTAdd(oldsize
, size
, &newLength
) != S_OK
)
139 m_pvectorErr
->resize(newLength
);
140 memcpy(&*(m_pvectorErr
->begin() + oldsize
), data
, size
);
144 void OnEnd() override
146 if (!m_buffer
.IsEmpty())
148 m_buffer
.Empty(); // Just for sure
152 GitReceiverFunc m_recv
;
154 BYTE_VECTOR
* m_pvectorErr
;
157 class CEnvironment
: protected std::vector
<wchar_t>
160 CEnvironment() : baseptr(nullptr) {}
161 CEnvironment(const CEnvironment
& env
) : std::vector
<wchar_t>(env
)
165 CEnvironment
& operator =(const CEnvironment
& env
)
167 __super::operator=(env
);
174 void CopyProcessEnvironment();
175 CString
GetEnv(const wchar_t* name
) const;
176 void SetEnv(const wchar_t* name
, const wchar_t* value
);
177 void AddToPath(CString value
);
181 operator const LPWSTR
*() const;
183 CEnvironment(CEnvironment
&& env
) = delete;
184 CEnvironment
& operator =(CEnvironment
&& env
) = delete;
191 GIT_DIFF m_GitDiff
= nullptr;
192 GIT_DIFF m_GitSimpleListDiff
= nullptr;
193 #ifdef GOOGLETEST_INCLUDE_GTEST_GTEST_H_
196 bool m_IsGitDllInited
= false;
198 CComAutoCriticalSection m_critGitDllSec
;
201 DWORD m_IsUseLibGit2_mask
;
203 CEnvironment m_Environment
;
205 static BOOL
GitPathFileExists(const CString
&path
)
207 if (path
[0] == L
'\\' && path
[1] == L
'\\')
208 //it is netshare \\server\sharefoldername
209 // \\server\.git will create smb error log.
211 const int length
= path
.GetLength();
216 int start
= path
.Find(L
'\\', 2);
220 start
= path
.Find(L
'\\', start
+ 1);
224 return PathFileExists(path
);
228 return PathFileExists(path
);
231 inline void ForceReInitDll()
234 ATLASSERT("we should never get here");
236 m_IsGitDllInited
= false;
239 void CheckAndInitDll()
242 ATLASSERT("we should never get here");
244 if(!m_IsGitDllInited
)
246 git_init(m_Environment
);
247 m_IsGitDllInited
=true;
251 GIT_DIFF
GetGitDiff()
254 ATLASSERT("we should never get here");
260 // cf. GitRevLoglist::SafeFetchFullInfo
262 params
.Format("-C%d%% -M%d%% -r", ms_iSimilarityIndexThreshold
, ms_iSimilarityIndexThreshold
);
263 git_open_diff(&m_GitDiff
, params
);
268 GIT_DIFF
GetGitSimpleListDiff()
271 ATLASSERT("we should never get here");
273 if(m_GitSimpleListDiff
)
274 return m_GitSimpleListDiff
;
277 git_open_diff(&m_GitSimpleListDiff
, "-r");
278 return m_GitSimpleListDiff
;
282 BOOL
CheckMsysGitDir(BOOL bFallback
= TRUE
);
283 BOOL
FindAndSetGitExePath(BOOL bFallback
);
284 bool m_bInitialized
= false;
290 GIT_CMD_COMMIT_UPDATE_INDEX
,
295 GIT_CMD_DELETETAGBRANCH
,
299 GIT_CMD_CHECK_CLEAN_WT
,
300 GIT_CMD_CHECKCONFLICTS
,
303 GIT_CMD_BRANCH_CONTAINS
,
304 GIT_CMD_GETCONFLICTINFO
,
307 static_assert(LIBGIT2_CMD::LAST_VALUE
< sizeof(DWORD
) * 8, "too many flags for storing them in a DWORD bitfield");
308 bool UsingLibGit2(LIBGIT2_CMD cmd
) const;
310 * callback type should be git_cred_acquire_cb
312 static void SetGit2CredentialCallback(void* callback
);
313 static void SetGit2CertificateCheckCertificate(void* callback
);
315 CString
GetHomeDirectory() const;
316 CString
GetGitLocalConfig() const;
317 CString
GetGitGlobalConfig() const;
318 CString
GetGitGlobalXDGConfig(bool returnDirectory
= false) const;
319 CString
GetGitSystemConfig() const;
320 CAutoRepository
GetGitRepository() const;
321 static CStringA
GetGitPathStringA(const CString
&path
);
322 static CString ms_LastMsysGitDir
; // the last msysgitdir added to the path, blank if none
323 static CString ms_MsysGitRootDir
;
324 static int ms_LastMsysGitVersion
;
325 static bool ms_bCygwinGit
;
326 static bool ms_bMsys2Git
;
327 static int ms_iSimilarityIndexThreshold
;
328 static int m_LogEncode
;
329 CString
GetNotesRef() const;
330 static bool IsBranchNameValid(const CString
& branchname
);
331 bool IsLocalBranch(const CString
& shortName
);
332 bool IsBranchTagNameUnique(const CString
& name
);
334 * Checks if a branch or tag with the given name exists
335 *isBranch is true -> branch, tag otherwise
337 bool BranchTagExists(const CString
& name
, bool isBranch
= true);
338 unsigned int Hash2int(const CGitHash
&hash
);
340 PROCESS_INFORMATION m_CurrentGitPi
{};
345 int Run(CString cmd
, CString
* output
, int code
);
346 int Run(CString cmd
, CString
* output
, CString
* outputErr
, int code
);
347 int Run(CString cmd
, BYTE_VECTOR
* byte_array
, BYTE_VECTOR
* byte_arrayErr
= nullptr);
348 int Run(CGitCall
& pcall
);
349 template<typename GitReceiverFunc
>
350 int Run(CString cmd
, GitReceiverFunc recv
, CString
* outputErr
= nullptr)
354 BYTE_VECTOR vectorErr
;
355 CGitCallCb
call(cmd
, recv
, &vectorErr
);
356 const int ret
= Run(call
);
357 vectorErr
.push_back(0);
358 StringAppend(*outputErr
, vectorErr
.data());
362 CGitCallCb
call(cmd
, recv
);
367 CComAutoCriticalSection m_critSecThreadMap
;
368 std::map
<DWORD
, HANDLE
> m_AsyncReadStdErrThreadMap
;
369 static DWORD WINAPI
AsyncReadStdErrThread(LPVOID lpParam
);
370 struct ASYNCREADSTDERRTHREADARGS
375 CString
GetUnifiedDiffCmd(const CTGitPath
& path
, const CString
& rev1
, const CString
& rev2
, bool bMerge
, bool bCombine
, int diffContext
, bool bNoPrefix
= false);
379 void KillRelatedThreads(CWinThread
* thread
);
381 int RunAsync(CString cmd
, PROCESS_INFORMATION
& pi
, HANDLE
* hRead
, HANDLE
* hErrReadOut
, const CString
* StdioFile
= nullptr);
382 int RunLogFile(CString cmd
, const CString
&filename
, CString
*stdErr
);
384 bool IsFastForward(const CString
& from
, const CString
& to
, CGitHash
* commonAncestor
= nullptr);
385 CString
GetConfigValue(const CString
& name
, const CString
& def
= CString(), bool wantBool
= false);
386 bool GetConfigValueBool(const CString
& name
, const bool def
= false);
387 int GetConfigValueInt32(const CString
& name
, const int def
= 0);
389 int SetConfigValue(const CString
& key
, const CString
& value
, CONFIG_TYPE type
= CONFIG_LOCAL
);
390 int UnsetConfigValue(const CString
& key
, CONFIG_TYPE type
= CONFIG_LOCAL
);
392 CString
GetUserName();
393 CString
GetUserEmail();
394 CString
GetCommitterName();
395 CString
GetCommitterEmail();
396 CString
GetCurrentBranch(bool fallback
= false);
397 void GetRemoteTrackedBranch(const CString
& localBranch
, CString
& remote
, CString
& branch
);
398 void GetRemoteTrackedBranchForHEAD(CString
& remote
, CString
& branch
);
399 void GetRemotePushBranch(const CString
& localBranch
, CString
& pushRemote
, CString
& pushBranch
);
400 // read current branch name from HEAD file, returns 0 on success, -1 on failure, 1 detached (branch name "HEAD" returned)
401 static int GetCurrentBranchFromFile(const CString
&sProjectRoot
, CString
&sBranchOut
, bool fallback
= false);
403 Use this method only when the HEAD is exist.
405 BOOL
CheckCleanWorkTree(bool stagedOk
= false);
406 BOOL
IsResultingCommitBecomeEmpty(bool amend
= false);
407 int DeleteRef(const CString
& reference
);
409 Use this method only if m_IsUseLibGit2 is used for fallbacks.
410 If you directly use libgit2 methods, use GetLibGit2LastErr instead.
412 CString
GetGitLastErr(const CString
& msg
);
413 CString
GetGitLastErr(const CString
& msg
, LIBGIT2_CMD cmd
);
414 static CString
GetLibGit2LastErr();
415 static CString
GetLibGit2LastErr(const CString
& msg
);
416 bool SetCurrentDir(CString path
, bool submodule
= false)
418 bool b
= GitAdminDir::HasAdminDir(path
, submodule
? false : !!PathIsDirectory(path
), &m_CurrentDir
);
419 if (!b
&& GitAdminDir::IsBareRepo(path
))
424 if (m_CurrentDir
.GetLength() == 2 && m_CurrentDir
[1] == L
':') //C: D:
425 m_CurrentDir
+= L
'\\';
429 CString m_CurrentDir
;
433 LOG_ORDER_CHRONOLOGIALREVERSED
,
436 LOG_ORDER_AUTHORDATEORDER
,
443 BRANCH_FETCH_HEAD
= 0x4,
444 BRANCH_LOCAL_F
= BRANCH_LOCAL
| BRANCH_FETCH_HEAD
,
445 BRANCH_ALL
= BRANCH_LOCAL
| BRANCH_REMOTE
,
446 BRANCH_ALL_F
= BRANCH_ALL
| BRANCH_FETCH_HEAD
,
452 LOG_INFO_FILESTATE
=0x2,
453 LOG_INFO_BOUNDARY
=0x10,
454 LOG_INFO_ALL_BRANCH
=0x20,
455 LOG_INFO_ONLY_HASH
=0x40,
456 LOG_INFO_DETECT_RENAME
=0x80,
457 LOG_INFO_DETECT_COPYRENAME
=0x100,
458 LOG_INFO_FIRST_PARENT
= 0x200,
459 LOG_INFO_NO_MERGE
= 0x400,
460 LOG_INFO_FOLLOW
= 0x800,
461 LOG_INFO_SHOW_MERGEDFILE
=0x1000,
462 LOG_INFO_FULL_DIFF
= 0x2000,
463 LOG_INFO_SIMPILFY_BY_DECORATION
= 0x4000,
464 LOG_INFO_LOCAL_BRANCHES
= 0x8000,
465 LOG_INFO_BASIC_REFS
= 0x10000,
466 LOG_INFO_SPARSE
= 0x20000,
467 LOG_INFO_ALWAYS_APPLY_RANGE
= 0x40000,
468 LOG_INFO_FULL_HISTORY
= 0x80000,
486 int GetRemoteList(STRING_VECTOR
&list
);
487 int GetBranchList(STRING_VECTOR
& list
, int* current
, BRANCH_TYPE type
= BRANCH_LOCAL
, bool skipCurrent
= false);
488 int GetTagList(STRING_VECTOR
&list
);
489 int GetRefsCommitIsOn(STRING_VECTOR
& list
, const CGitHash
& hash
, bool includeTags
, bool includeBranches
, BRANCH_TYPE type
= BRANCH_LOCAL
);
490 int GetRemoteRefs(const CString
& remote
, REF_VECTOR
& list
, bool includeTags
, bool includeBranches
);
491 int DeleteRemoteRefs(const CString
& remote
, const STRING_VECTOR
& list
);
492 int GetBranchDescriptions(MAP_STRING_STRING
& map
);
493 int GuessRefForHash(CString
& ref
, const CGitHash
& hash
);
494 int GetMapHashToFriendName(MAP_HASH_NAME
&map
);
495 static int GetMapHashToFriendName(git_repository
* repo
, MAP_HASH_NAME
&map
);
497 CString
DerefFetchHead();
500 // When branchName == FETCH_HEAD, dereference it.
501 // A selected branch name got from GetBranchList(), with flag BRANCH_FETCH_HEAD enabled,
502 // should go through this function before it is used.
503 CString
FixBranchName_Mod(CString
& branchName
);
504 CString
FixBranchName(const CString
& branchName
);
506 CString
GetLogCmd(CString range
, const CTGitPath
* path
, int InfoMask
, CFilterData
* filter
, int logOrderBy
);
508 int GetHash(CGitHash
&hash
, const CString
& friendname
);
509 static int GetHash(git_repository
* repo
, CGitHash
&hash
, const CString
& friendname
, bool skipFastCheck
= false);
511 static void StringAppend(CString
& str
, const char* p
, int code
= CP_UTF8
, int length
= -1);
513 BOOL
CanParseRev(CString ref
);
515 Checks if HEAD points to an unborn branch
516 This method assumes, that we already know that we are in a working tree.
519 /** Returns 0 if no conflict, if a conflict was found and -1 in case of a failure */
520 int HasWorkingTreeConflicts();
521 /** Returns 0 if no conflict, if a conflict was found and -1 in case of a failure */
522 int HasWorkingTreeConflicts(git_repository
* repo
);
523 void GetBisectTerms(CString
* good
, CString
* bad
);
524 int GetRefList(STRING_VECTOR
&list
);
529 CGitHash superProjectHash
;
530 CGitHash mergeconflictMineHash
;
531 CGitHash mergeconflictTheirsHash
;
535 bool AnyMatches(const CGitHash
& hash
) const
537 return !superProjectHash
.IsEmpty() && superProjectHash
== hash
|| !mergeconflictMineHash
.IsEmpty() && mergeconflictMineHash
== hash
|| !mergeconflictTheirsHash
.IsEmpty() && mergeconflictTheirsHash
== hash
;
541 superProjectHash
.Empty();
542 mergeconflictMineHash
.Empty();
543 mergeconflictTheirsHash
.Empty();
546 int GetSubmodulePointer(SubmoduleInfo
& mergeInfo
) const;
548 int ApplyPatchToIndex(const CString
& patchPath
, CString
* out
);
549 int ApplyPatchToIndexReverse(const CString
& patchPath
, CString
* out
);
551 int RefreshGitIndex();
552 int GetOneFile(const CString
&Refname
, const CTGitPath
&path
, const CString
&outputfile
);
554 //Example: master -> refs/heads/master
555 CString
GetFullRefName(const CString
& shortRefName
);
556 //Removes 'refs/heads/' or just 'refs'. Example: refs/heads/master -> master
557 static CString
StripRefName(CString refName
);
559 int GetCommitDiffList(const CString
&rev1
, const CString
&rev2
, CTGitPathList
&outpathlist
, bool ignoreSpaceAtEol
= false, bool ignoreSpaceChange
= false, bool ignoreAllSpace
= false, bool ignoreBlankLines
= false);
560 int GetInitAddList(CTGitPathList
&outpathlist
, bool getStagingStatus
= false);
561 int GetWorkingTreeChanges(CTGitPathList
& result
, bool amend
= false, const CTGitPathList
* filterlist
= nullptr, bool includedStaged
= false, bool getStagingStatus
= false);
563 static int ParseConflictHashesFromLsFile(const BYTE_VECTOR
& out
, CGitHash
& baseHash
, bool& baseIsFile
, CGitHash
& mineHash
, bool& mineIsFile
, CGitHash
& remoteHash
, bool& remoteIsFile
);
565 constexpr static __int64
filetime_to_time_t(__int64 winTime
) noexcept
567 winTime
-= 116444736000000000LL; /* Windows to Unix Epoch conversion */
568 winTime
/= 10000000; /* Nano to seconds resolution */
569 return static_cast<time_t>(winTime
);
572 static int GetFileModifyTime(LPCWSTR filename
, __int64
* time
, bool* isDir
= nullptr, __int64
* size
= nullptr, bool* isSymlink
= nullptr)
574 WIN32_FILE_ATTRIBUTE_DATA fdata
;
575 if (GetFileAttributesEx(filename
, GetFileExInfoStandard
, &fdata
))
578 *time
= static_cast<__int64
>(fdata
.ftLastWriteTime
.dwHighDateTime
) << 32 | fdata
.ftLastWriteTime
.dwLowDateTime
;
581 *size
= static_cast<__int64
>(fdata
.nFileSizeHigh
) << 32 | fdata
.nFileSizeLow
;
584 *isDir
= !!( fdata
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
);
587 *isSymlink
= (fdata
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) && !CPathUtils::ReadLink(filename
);
594 int GetShortHASHLength() const;
596 static BOOL
GetShortName(const CString
& ref
, CString
& shortname
, const CString
& prefix
)
598 //TRACE(L"%s %s\r\n", ref, prefix);
599 if (CStringUtils::StartsWith(ref
, prefix
))
601 shortname
= ref
.Right(ref
.GetLength() - prefix
.GetLength());
602 if (CStringUtils::EndsWith(shortname
, L
"^{}"))
603 shortname
.Truncate(shortname
.GetLength() - static_cast<int>(wcslen(L
"^{}")));
609 static CString
GetShortName(const CString
& ref
, REF_TYPE
*type
);
611 static bool LoadTextFile(const CString
&filename
, CString
&msg
);
613 int GetGitNotes(const CGitHash
& hash
, CString
& notes
);
614 int SetGitNotes(const CGitHash
& hash
, const CString
& notes
);
616 int GetUnifiedDiff(const CTGitPath
& path
, const CString
& rev1
, const CString
& rev2
, CString patchfile
, bool bMerge
, bool bCombine
, int diffContext
, bool bNoPrefix
= false);
617 int GetUnifiedDiff(const CTGitPath
& path
, const CString
& rev1
, const CString
& rev2
, CStringA
& buffer
, bool bMerge
, bool bCombine
, int diffContext
);
619 int GitRevert(int parent
, const CGitHash
&hash
);
621 int GetGitVersion(CString
* versiondebug
, CString
* errStr
);
623 CString
CombinePath(const CString
&path
) const
627 if (m_CurrentDir
.IsEmpty())
629 return m_CurrentDir
+ (CStringUtils::EndsWith(m_CurrentDir
, L
'\\') ? L
"" : L
"\\") + path
;
632 CString
CombinePath(const CTGitPath
&path
) const
634 return CombinePath(path
.GetWinPath());
637 CString
CombinePath(const CTGitPath
*path
) const
640 return CombinePath(path
->GetWinPath());
643 extern void GetTempPath(CString
&path
);
644 extern CString
GetTempFile();
645 extern DWORD
GetTortoiseGitTempPath(DWORD nBufferLength
, LPWSTR lpBuffer
);