1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2011 - 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.
22 //#include "resource.h"
23 #include "..\TortoiseShell\resource.h"
24 //#include "git_config.h"
25 #include "GitStatus.h"
26 #include "UnicodeUtils.h"
27 //#include "GitGlobal.h"
28 //#include "GitHelpers.h"
31 //# include "MessageBox.h"
32 //# include "registry.h"
33 //# include "TGitPath.h"
34 //# include "PathUtils.h"
38 #include "shellcache.h"
40 CGitIndexFileMap g_IndexFileMap
;
41 CGitHeadFileMap g_HeadFileMap
;
42 CGitIgnoreList g_IgnoreList
;
44 GitStatus::GitStatus(bool * /*pbCanceled*/)
48 m_pool
= git_pool_create (NULL
);
50 git_error_clear(git_client_create_context(&ctx
, m_pool
));
54 ctx
->cancel_func
= cancel
;
55 ctx
->cancel_baton
= pbCanceled
;
59 git_error_clear(git_config_ensure(NULL
, m_pool
));
61 // set up authentication
62 m_prompt
.Init(m_pool
, ctx
);
64 // set up the configuration
65 m_err
= git_config_get_config (&(ctx
->config
), g_pConfigDir
, m_pool
);
69 ::MessageBox(NULL
, this->GetLastErrorMsg(), _T("TortoiseGit"), MB_ICONERROR
);
70 git_error_clear(m_err
);
71 git_pool_destroy (m_pool
); // free the allocated memory
75 // set up the Git_SSH param
76 CString tgit_ssh
= CRegString(_T("Software\\TortoiseGit\\SSH"));
77 if (tgit_ssh
.IsEmpty())
78 tgit_ssh
= CPathUtils::GetAppDirectory() + _T("TortoisePlink.exe");
79 tgit_ssh
.Replace('\\', '/');
80 if (!tgit_ssh
.IsEmpty())
82 git_config_t
* cfg
= (git_config_t
*)apr_hash_get ((apr_hash_t
*)ctx
->config
, Git_CONFIG_CATEGORY_CONFIG
,
84 git_config_set(cfg
, Git_CONFIG_SECTION_TUNNELS
, "ssh", CUnicodeUtils::GetUTF8(tgit_ssh
));
87 git_error_clear(git_config_ensure(NULL
, m_pool
));
89 // set up the configuration
90 m_err
= git_config_get_config (&(ctx
->config
), g_pConfigDir
, m_pool
);
96 GitStatus::~GitStatus(void)
99 git_error_clear(m_err
);
100 git_pool_destroy (m_pool
); // free the allocated memory
104 void GitStatus::ClearPool()
107 git_pool_clear(m_pool
);
112 CString
GitStatus::GetLastErrorMsg() const
114 // return Git::GetErrorString(m_err);
118 stdstring
GitStatus::GetLastErrorMsg() const
127 git_error_t
* ErrPtr
= m_err
;
130 msg
= CUnicodeUtils::StdGetUnicode(ErrPtr
->message
);
134 /* Is this a Subversion-specific error code? */
135 if ((ErrPtr
->apr_err
> APR_OS_START_USEERR
)
136 && (ErrPtr
->apr_err
<= APR_OS_START_CANONERR
))
137 msg
= CUnicodeUtils::StdGetUnicode(git_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)));
138 /* Otherwise, this must be an APR error code. */
141 git_error_t
*temp_err
= NULL
;
142 const char * err_string
= NULL
;
143 temp_err
= git_utf_cstring_to_utf8(&err_string
, apr_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)-1), ErrPtr
->pool
);
146 git_error_clear (temp_err
);
147 msg
= _T("Can't recode error string from APR");
151 msg
= CUnicodeUtils::StdGetUnicode(err_string
);
157 while (ErrPtr
->child
)
159 ErrPtr
= ErrPtr
->child
;
163 msg
+= CUnicodeUtils::StdGetUnicode(ErrPtr
->message
);
167 /* Is this a Subversion-specific error code? */
168 if ((ErrPtr
->apr_err
> APR_OS_START_USEERR
)
169 && (ErrPtr
->apr_err
<= APR_OS_START_CANONERR
))
170 msg
+= CUnicodeUtils::StdGetUnicode(git_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)));
171 /* Otherwise, this must be an APR error code. */
174 git_error_t
*temp_err
= NULL
;
175 const char * err_string
= NULL
;
176 temp_err
= git_utf_cstring_to_utf8(&err_string
, apr_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)-1), ErrPtr
->pool
);
179 git_error_clear (temp_err
);
180 msg
+= _T("Can't recode error string from APR");
184 msg
+= CUnicodeUtils::StdGetUnicode(err_string
);
191 } // if (m_err != NULL)
198 git_wc_status_kind
GitStatus::GetAllStatus(const CTGitPath
& path
, git_depth_t depth
)
200 git_wc_status_kind statuskind
;
201 // git_client_ctx_t * ctx;
203 // apr_pool_t * pool;
204 // git_error_t * err;
207 CString sProjectRoot
;
209 isDir
= path
.IsDirectory();
210 if (!path
.HasAdminDir(&sProjectRoot
))
211 return git_wc_status_none
;
213 // pool = git_pool_create (NULL); // create the memory pool
215 // git_error_clear(git_client_create_context(&ctx, pool));
217 // git_revnum_t youngest = Git_INVALID_REVNUM;
218 // git_opt_revision_t rev;
219 // rev.kind = git_opt_revision_unspecified;
220 statuskind
= git_wc_status_none
;
222 const BOOL bIsRecursive
= (depth
== git_depth_infinity
|| depth
== git_depth_unknown
); // taken from SVN source
225 CString s
= path
.GetWinPathString();
226 if (s
.GetLength() > sProjectRoot
.GetLength())
228 if (sProjectRoot
.GetLength() == 3 && sProjectRoot
[1] == _T(':'))
229 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
231 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength() - 1/*otherwise it gets initial slash*/);
234 bool isfull
= ((DWORD
)CRegStdWORD(_T("Software\\TortoiseGit\\CacheType"),
235 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
239 err
= GetDirStatus(sProjectRoot
,sSubPath
,&statuskind
, isfull
,bIsRecursive
,isfull
,NULL
, NULL
);
243 err
= GetFileStatus(sProjectRoot
,sSubPath
,&statuskind
,isfull
, false,isfull
, NULL
,NULL
);
250 git_wc_status_kind
GitStatus::GetAllStatusRecursive(const CTGitPath
& path
)
252 return GetAllStatus(path
, git_depth_infinity
);
256 git_wc_status_kind
GitStatus::GetMoreImportant(git_wc_status_kind status1
, git_wc_status_kind status2
)
258 if (GetStatusRanking(status1
) >= GetStatusRanking(status2
))
262 // static private method
263 int GitStatus::GetStatusRanking(git_wc_status_kind status
)
267 case git_wc_status_none
:
269 case git_wc_status_unversioned
:
271 case git_wc_status_ignored
:
273 case git_wc_status_incomplete
:
275 case git_wc_status_normal
:
276 case git_wc_status_external
:
278 case git_wc_status_added
:
280 case git_wc_status_missing
:
282 case git_wc_status_deleted
:
284 case git_wc_status_replaced
:
286 case git_wc_status_modified
:
288 case git_wc_status_merged
:
290 case git_wc_status_conflicted
:
292 case git_wc_status_obstructed
:
298 git_revnum_t
GitStatus::GetStatus(const CTGitPath
& path
, bool update
/* = false */, bool noignore
/* = false */, bool /*noexternals*/ /* = false */)
300 // NOTE: unlike the SVN version this one does not cache the enumerated files, because in practice no code in all of
301 // Tortoise uses this, all places that call GetStatus create a temp GitStatus object which gets destroyed right
302 // after the call again
304 // apr_hash_t * statushash;
305 // apr_hash_t * exthash;
306 // apr_array_header_t * statusarray;
307 // const sort_item* item;
309 // git_error_clear(m_err);
310 // statushash = apr_hash_make(m_pool);
311 // exthash = apr_hash_make(m_pool);
312 git_revnum_t youngest
= GIT_INVALID_REVNUM
;
313 // git_opt_revision_t rev;
314 // rev.kind = git_opt_revision_unspecified;
316 CString sProjectRoot
;
317 if ( !path
.HasAdminDir(&sProjectRoot
) )
320 struct hashbaton_t hashbaton
;
321 // hashbaton.hash = statushash;
322 // hashbaton.exthash = exthash;
323 hashbaton
.pThis
= this;
325 bool isfull
= ((DWORD
)CRegStdWORD(_T("Software\\TortoiseGit\\CacheType"),
326 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
329 LPCTSTR lpszSubPath
= NULL
;
331 CString s
= path
.GetWinPathString();
332 if (s
.GetLength() > sProjectRoot
.GetLength())
334 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
335 lpszSubPath
= sSubPath
;
336 // skip initial slash if necessary
337 if (*lpszSubPath
== _T('\\'))
341 m_status
.prop_status
= m_status
.text_status
= git_wc_status_none
;
343 if(path
.IsDirectory())
345 m_err
= GetDirStatus(sProjectRoot
,CString(lpszSubPath
),&m_status
.text_status
, isfull
, false,!noignore
, NULL
, NULL
);
349 m_err
= GetFileStatus(sProjectRoot
,CString(lpszSubPath
),&m_status
.text_status
,isfull
, false,!noignore
, NULL
,NULL
);
353 // Error present if function is not under version control
354 if (m_err
) /*|| (apr_hash_count(statushash) == 0)*/
357 return GIT_INVALID_REVNUM
;
360 // Convert the unordered hash to an ordered, sorted array
361 /*statusarray = sort_hash (statushash,
362 sort_compare_items_as_paths,
365 // only the first entry is needed (no recurse)
366 // item = &APR_ARRAY_IDX (statusarray, 0, const sort_item);
368 // status = (git_wc_status2_t *) item->value;
373 // done to match TSVN functionality of this function (not sure if any code uses the reutrn val)
374 // if TGit does not need this, then change the return type of function
375 youngest
= g_Git
.GetHash(_T("HEAD"));
381 git_wc_status2_t
* GitStatus::GetFirstFileStatus(const CTGitPath
& /*path*/, CTGitPath
& /*retPath*/, bool /*update*/, git_depth_t
/*depth*/, bool /*bNoIgnore*/ /* = true */, bool /*bNoExternals*/ /* = false */)
383 static git_wc_status2 st
;
387 m_fileCache.Init( CStringA( path.GetWinPathString().GetString() ) );
388 MessageBox(NULL, path.GetWinPathString(), _T("GetFirstFile"), MB_OK);
389 m_fileCache.m_pFileIter = m_fileCache.m_pFiles;
390 st.text_status = git_wc_status_none;
392 if (m_fileCache.m_pFileIter)
394 switch(m_fileCache.m_pFileIter->nStatus)
396 case WGFS_Normal: st.text_status = git_wc_status_normal; break;
397 case WGFS_Modified: st.text_status = git_wc_status_modified; break;
398 case WGFS_Deleted: st.text_status = git_wc_status_deleted; break;
401 //retPath.SetFromGit((const char*)item->key);
403 m_fileCache.m_pFileIter = m_fileCache.m_pFileIter->pNext;
409 const sort_item
* item
;
411 git_error_clear(m_err
);
412 m_statushash
= apr_hash_make(m_pool
);
413 m_externalhash
= apr_hash_make(m_pool
);
414 headrev
= Git_INVALID_REVNUM
;
415 git_opt_revision_t rev
;
416 rev
.kind
= git_opt_revision_unspecified
;
417 struct hashbaton_t hashbaton
;
418 hashbaton
.hash
= m_statushash
;
419 hashbaton
.exthash
= m_externalhash
;
420 hashbaton
.pThis
= this;
421 m_statushashindex
= 0;
422 m_err
= git_client_status4 (&headrev
,
423 path
.GetGitApiPath(m_pool
),
430 bNoIgnore
, //noignore
431 bNoExternals
, //noexternals
437 // Error present if function is not under version control
438 if ((m_err
!= NULL
) || (apr_hash_count(m_statushash
) == 0))
443 // Convert the unordered hash to an ordered, sorted array
444 m_statusarray
= sort_hash (m_statushash
,
445 sort_compare_items_as_paths
,
448 // only the first entry is needed (no recurse)
449 m_statushashindex
= 0;
450 item
= &APR_ARRAY_IDX (m_statusarray
, m_statushashindex
, const sort_item
);
451 retPath
.SetFromGit((const char*)item
->key
);
452 return (git_wc_status2_t
*) item
->value
;
458 unsigned int GitStatus::GetVersionedCount() const
460 // return /**/m_fileCache.GetFileCount();
462 unsigned int count
= 0;
464 const sort_item
* item
;
465 for (unsigned int i
=0; i
<apr_hash_count(m_statushash
); ++i
)
467 item
= &APR_ARRAY_IDX(m_statusarray
, i
, const sort_item
);
470 if (GitStatus::GetMoreImportant(((git_wc_status_t
*)item
->value
)->text_status
, git_wc_status_ignored
)!=git_wc_status_ignored
)
478 git_wc_status2_t
* GitStatus::GetNextFileStatus(CTGitPath
& /*retPath*/)
480 static git_wc_status2 st
;
482 st
.text_status
= git_wc_status_none
;
484 /*if (m_fileCache.m_pFileIter)
486 switch(m_fileCache.m_pFileIter->nStatus)
488 case WGFS_Normal: st.text_status = git_wc_status_normal; break;
489 case WGFS_Modified: st.text_status = git_wc_status_modified; break;
490 case WGFS_Deleted: st.text_status = git_wc_status_deleted; break;
493 m_fileCache.m_pFileIter = m_fileCache.m_pFileIter->pNext;
499 const sort_item
* item
;
501 if ((m_statushashindex
+1) >= apr_hash_count(m_statushash
))
505 item
= &APR_ARRAY_IDX (m_statusarray
, m_statushashindex
, const sort_item
);
506 retPath
.SetFromGit((const char*)item
->key
);
507 return (git_wc_status2_t
*) item
->value
;
512 bool GitStatus::IsExternal(const CTGitPath
& /*path*/) const
515 if (apr_hash_get(m_externalhash
, path
.GetGitApiPath(m_pool
), APR_HASH_KEY_STRING
))
521 bool GitStatus::IsInExternal(const CTGitPath
& /*path*/) const
524 if (apr_hash_count(m_statushash
) == 0)
527 GitPool
localpool(m_pool
);
528 apr_hash_index_t
*hi
;
530 for (hi
= apr_hash_first(localpool
, m_externalhash
); hi
; hi
= apr_hash_next(hi
))
532 apr_hash_this(hi
, (const void**)&key
, NULL
, NULL
);
535 if (CTGitPath(CUnicodeUtils::GetUnicode(key
)).IsAncestorOf(path
))
544 void GitStatus::GetStatusString(git_wc_status_kind status
, size_t buflen
, TCHAR
* string
)
549 case git_wc_status_none
:
552 case git_wc_status_unversioned
:
553 buf
= _T("unversioned\0");
555 case git_wc_status_normal
:
556 buf
= _T("normal\0");
558 case git_wc_status_added
:
561 case git_wc_status_missing
:
562 buf
= _T("missing\0");
564 case git_wc_status_deleted
:
565 buf
= _T("deleted\0");
567 case git_wc_status_replaced
:
568 buf
= _T("replaced\0");
570 case git_wc_status_modified
:
571 buf
= _T("modified\0");
573 case git_wc_status_merged
:
574 buf
= _T("merged\0");
576 case git_wc_status_conflicted
:
577 buf
= _T("conflicted\0");
579 case git_wc_status_obstructed
:
580 buf
= _T("obstructed\0");
582 case git_wc_status_ignored
:
585 case git_wc_status_external
:
586 buf
= _T("external");
588 case git_wc_status_incomplete
:
589 buf
= _T("incomplete\0");
595 _stprintf_s(string
, buflen
, _T("%s"), buf
);
598 void GitStatus::GetStatusString(HINSTANCE hInst
, git_wc_status_kind status
, TCHAR
* string
, int size
, WORD lang
)
602 case git_wc_status_none
:
603 LoadStringEx(hInst
, IDS_STATUSNONE
, string
, size
, lang
);
605 case git_wc_status_unversioned
:
606 LoadStringEx(hInst
, IDS_STATUSUNVERSIONED
, string
, size
, lang
);
608 case git_wc_status_normal
:
609 LoadStringEx(hInst
, IDS_STATUSNORMAL
, string
, size
, lang
);
611 case git_wc_status_added
:
612 LoadStringEx(hInst
, IDS_STATUSADDED
, string
, size
, lang
);
614 case git_wc_status_missing
:
615 LoadStringEx(hInst
, IDS_STATUSABSENT
, string
, size
, lang
);
617 case git_wc_status_deleted
:
618 LoadStringEx(hInst
, IDS_STATUSDELETED
, string
, size
, lang
);
620 case git_wc_status_replaced
:
621 LoadStringEx(hInst
, IDS_STATUSREPLACED
, string
, size
, lang
);
623 case git_wc_status_modified
:
624 LoadStringEx(hInst
, IDS_STATUSMODIFIED
, string
, size
, lang
);
626 case git_wc_status_merged
:
627 LoadStringEx(hInst
, IDS_STATUSMERGED
, string
, size
, lang
);
629 case git_wc_status_conflicted
:
630 LoadStringEx(hInst
, IDS_STATUSCONFLICTED
, string
, size
, lang
);
632 case git_wc_status_ignored
:
633 LoadStringEx(hInst
, IDS_STATUSIGNORED
, string
, size
, lang
);
635 case git_wc_status_obstructed
:
636 LoadStringEx(hInst
, IDS_STATUSOBSTRUCTED
, string
, size
, lang
);
638 case git_wc_status_external
:
639 LoadStringEx(hInst
, IDS_STATUSEXTERNAL
, string
, size
, lang
);
641 case git_wc_status_incomplete
:
642 LoadStringEx(hInst
, IDS_STATUSINCOMPLETE
, string
, size
, lang
);
645 LoadStringEx(hInst
, IDS_STATUSNONE
, string
, size
, lang
);
651 CString
GitStatus::GetDepthString(git_depth_t depth
)
657 case git_depth_unknown
:
658 sDepth
.LoadString(IDS_Git_DEPTH_UNKNOWN
);
660 case git_depth_empty
:
661 sDepth
.LoadString(IDS_Git_DEPTH_EMPTY
);
663 case git_depth_files
:
664 sDepth
.LoadString(IDS_Git_DEPTH_FILES
);
666 case git_depth_immediates
:
667 sDepth
.LoadString(IDS_Git_DEPTH_IMMEDIATE
);
669 case git_depth_infinity
:
670 sDepth
.LoadString(IDS_Git_DEPTH_INFINITE
);
679 void GitStatus::GetDepthString(HINSTANCE
/*hInst*/, git_depth_t
/*depth*/, TCHAR
* /*string*/, int /*size*/, WORD
/*lang*/)
684 case git_depth_unknown
:
685 LoadStringEx(hInst
, IDS_SVN_DEPTH_UNKNOWN
, string
, size
, lang
);
687 case git_depth_empty
:
688 LoadStringEx(hInst
, IDS_SVN_DEPTH_EMPTY
, string
, size
, lang
);
690 case git_depth_files
:
691 LoadStringEx(hInst
, IDS_SVN_DEPTH_FILES
, string
, size
, lang
);
693 case git_depth_immediates
:
694 LoadStringEx(hInst
, IDS_SVN_DEPTH_IMMEDIATE
, string
, size
, lang
);
696 case git_depth_infinity
:
697 LoadStringEx(hInst
, IDS_SVN_DEPTH_INFINITE
, string
, size
, lang
);
704 int GitStatus::LoadStringEx(HINSTANCE hInstance
, UINT uID
, LPTSTR lpBuffer
, int nBufferMax
, WORD wLanguage
)
706 const STRINGRESOURCEIMAGE
* pImage
;
707 const STRINGRESOURCEIMAGE
* pImageEnd
;
713 HRSRC hResource
= FindResourceEx(hInstance
, RT_STRING
, MAKEINTRESOURCE(((uID
>>4)+1)), wLanguage
);
716 // try the default language before giving up!
717 hResource
= FindResource(hInstance
, MAKEINTRESOURCE(((uID
>>4)+1)), RT_STRING
);
721 hGlobal
= LoadResource(hInstance
, hResource
);
724 pImage
= (const STRINGRESOURCEIMAGE
*)::LockResource(hGlobal
);
728 nResourceSize
= ::SizeofResource(hInstance
, hResource
);
729 pImageEnd
= (const STRINGRESOURCEIMAGE
*)(LPBYTE(pImage
)+nResourceSize
);
732 while ((iIndex
> 0) && (pImage
< pImageEnd
))
734 pImage
= (const STRINGRESOURCEIMAGE
*)(LPBYTE(pImage
)+(sizeof(STRINGRESOURCEIMAGE
)+(pImage
->nLength
*sizeof(WCHAR
))));
737 if (pImage
>= pImageEnd
)
739 if (pImage
->nLength
== 0)
742 ret
= pImage
->nLength
;
743 if (pImage
->nLength
> nBufferMax
)
745 wcsncpy_s(lpBuffer
, nBufferMax
, pImage
->achString
, pImage
->nLength
-1);
746 lpBuffer
[nBufferMax
-1] = 0;
750 wcsncpy_s(lpBuffer
, nBufferMax
, pImage
->achString
, pImage
->nLength
);
756 BOOL
GitStatus::getallstatus(const struct wgFile_s
*pFile
, void *pUserData
)
758 git_wc_status_kind
* s
= (git_wc_status_kind
*)pUserData
;
759 *s
= GitStatus::GetMoreImportant(*s
, GitStatusFromWingit(pFile
->nStatus
));
763 BOOL
GitStatus::getstatus(const struct wgFile_s
*pFile
, void *pUserData
)
765 git_wc_status2_t
* s
= (git_wc_status2_t
*)pUserData
;
766 s
->prop_status
= s
->text_status
= GitStatus::GetMoreImportant(s
->prop_status
, GitStatusFromWingit(pFile
->nStatus
));
771 git_error_t
* GitStatus::getallstatus(void * baton
, const char * /*path*/, git_wc_status2_t
* status
, apr_pool_t
* /*pool*/)
773 git_wc_status_kind
* s
= (git_wc_status_kind
*)baton
;
774 *s
= GitStatus::GetMoreImportant(*s
, status
->text_status
);
775 *s
= GitStatus::GetMoreImportant(*s
, status
->prop_status
);
781 git_error_t
* GitStatus::getstatushash(void * baton
, const char * path
, git_wc_status2_t
* status
, apr_pool_t
* /*pool*/)
783 hashbaton_t
* hash
= (hashbaton_t
*)baton
;
784 const StdStrAVector
& filterList
= hash
->pThis
->m_filterFileList
;
785 if (status
->text_status
== git_wc_status_external
)
787 apr_hash_set (hash
->exthash
, apr_pstrdup(hash
->pThis
->m_pool
, path
), APR_HASH_KEY_STRING
, (const void*)1);
790 if(filterList
.size() > 0)
792 // We have a filter active - we're only interested in files which are in
794 if(!binary_search(filterList
.begin(), filterList
.end(), path
))
796 // This item is not in the filter - don't store it
800 git_wc_status2_t
* statuscopy
= git_wc_dup_status2 (status
, hash
->pThis
->m_pool
);
801 apr_hash_set (hash
->hash
, apr_pstrdup(hash
->pThis
->m_pool
, path
), APR_HASH_KEY_STRING
, statuscopy
);
805 apr_array_header_t
* GitStatus::sort_hash (apr_hash_t
*ht
,
806 int (*comparison_func
) (const GitStatus::sort_item
*, const GitStatus::sort_item
*),
809 apr_hash_index_t
*hi
;
810 apr_array_header_t
*ary
;
812 /* allocate an array with only one element to begin with. */
813 ary
= apr_array_make (pool
, 1, sizeof(sort_item
));
815 /* loop over hash table and push all keys into the array */
816 for (hi
= apr_hash_first (pool
, ht
); hi
; hi
= apr_hash_next (hi
))
818 sort_item
*item
= (sort_item
*)apr_array_push (ary
);
820 apr_hash_this (hi
, &item
->key
, &item
->klen
, &item
->value
);
823 /* now quick sort the array. */
824 qsort (ary
->elts
, ary
->nelts
, ary
->elt_size
,
825 (int (*)(const void *, const void *))comparison_func
);
830 int GitStatus::sort_compare_items_as_paths (const sort_item
*a
, const sort_item
*b
)
832 const char *astr
, *bstr
;
834 astr
= (const char*)a
->key
;
835 bstr
= (const char*)b
->key
;
836 return git_path_compare_paths (astr
, bstr
);
840 git_error_t
* GitStatus::cancel(void * /*baton*/)
843 volatile bool * canceled
= (bool *)baton
;
847 temp
.LoadString(IDS_Git_USERCANCELLED
);
848 return git_error_create(Git_ERR_CANCELLED
, NULL
, CUnicodeUtils::GetUTF8(temp
));
857 // Set-up a filter to restrict the files which will have their status stored by a get-status
858 void GitStatus::SetFilter(const CTGitPathList
& fileList
)
860 m_filterFileList
.clear();
861 for(int fileIndex
= 0; fileIndex
< fileList
.GetCount(); fileIndex
++)
863 // m_filterFileList.push_back(fileList[fileIndex].GetGitApiPath(m_pool));
865 // Sort the list so that we can do binary searches
866 std::sort(m_filterFileList
.begin(), m_filterFileList
.end());
869 void GitStatus::ClearFilter()
871 m_filterFileList
.clear();
876 typedef CComCritSecLock
<CComCriticalSection
> CAutoLocker
;
878 int GitStatus::GetFileStatus(const CString
&gitdir
,const CString
&pathParam
,git_wc_status_kind
* status
,BOOL IsFull
, BOOL
/*IsRecursive*/,BOOL IsIgnore
, FIll_STATUS_CALLBACK callback
,void *pData
)
882 CString path
= pathParam
;
884 TCHAR oldpath
[MAX_PATH
+1];
885 memset(oldpath
,0,MAX_PATH
+1);
887 path
.Replace(_T('\\'),_T('/'));
889 CString lowcasepath
=path
;
890 lowcasepath
.MakeLower();
894 git_wc_status_kind st
= git_wc_status_none
;
897 g_IndexFileMap
.GetFileStatus(gitdir
,path
,&st
,IsFull
,false,callback
,pData
,&hash
);
899 if( st
== git_wc_status_conflicted
)
903 callback(gitdir
+_T("/")+path
,st
,false,pData
);
907 if( st
== git_wc_status_unversioned
)
911 *status
= git_wc_status_unversioned
;
913 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
917 if( g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
919 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
921 if( g_IgnoreList
.IsIgnore(path
, gitdir
) )
923 st
= git_wc_status_ignored
;
927 callback(gitdir
+_T("/")+path
,st
, false, pData
);
932 if( st
== git_wc_status_normal
&& IsFull
)
935 g_HeadFileMap
.CheckHeadUpdate(gitdir
);
938 SHARED_TREE_PTR treeptr
;
940 treeptr
=g_HeadFileMap
.SafeGet(gitdir
);
942 b
= treeptr
->m_Head
!= treeptr
->m_TreeHash
;
946 treeptr
->ReadHeadHash(gitdir
);
949 if( treeptr
->m_HeadFile
.IsEmpty() )
951 *status
=st
=git_wc_status_added
;
953 callback(gitdir
+_T("/")+path
,st
,false,pData
);
956 if(treeptr
->ReadTree())
958 treeptr
->m_LastModifyTimeHead
= 0;
959 //Check if init repository
960 *status
= treeptr
->m_Head
.IsEmpty()? git_wc_status_added
: st
;
962 callback(gitdir
+_T("/")+path
,*status
,false,pData
);
965 g_HeadFileMap
.SafeSet(gitdir
, treeptr
);
968 // Check Head Tree Hash;
972 int start
=SearchInSortVector(*treeptr
,lowcasepath
.GetBuffer(),-1);
976 *status
=st
=git_wc_status_added
;
977 ATLTRACE(_T("File miss in head tree %s"), path
);
979 callback(gitdir
+_T("/")+path
,st
,false, pData
);
983 //staged and not commit
984 if( treeptr
->at(start
).m_Hash
!= hash
)
986 *status
=st
=git_wc_status_modified
;
988 callback(gitdir
+_T("/")+path
,st
, false, pData
);
996 callback(gitdir
+_T("/")+path
,st
,false, pData
);
1003 *status
= git_wc_status_none
;
1011 int GitStatus::GetHeadHash(const CString
&gitdir
, CGitHash
&hash
)
1013 return g_HeadFileMap
.GetHeadHash(gitdir
, hash
);
1016 bool GitStatus::IsGitReposChanged(const CString
&gitdir
,const CString
&subpaths
, int mode
)
1018 if( mode
& GIT_MODE_INDEX
)
1020 return g_IndexFileMap
.CheckAndUpdate(gitdir
, true);
1023 if( mode
& GIT_MODE_HEAD
)
1025 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
))
1029 if( mode
& GIT_MODE_IGNORE
)
1031 if(g_IgnoreList
.CheckIgnoreChanged(gitdir
,subpaths
))
1037 int GitStatus::LoadIgnoreFile(const CString
&gitdir
,const CString
&subpaths
)
1039 return g_IgnoreList
.LoadAllIgnoreFile(gitdir
,subpaths
);
1041 int GitStatus::IsUnderVersionControl(const CString
&gitdir
, const CString
&path
, bool isDir
,bool *isVersion
)
1043 return g_IndexFileMap
.IsUnderVersionControl(gitdir
, path
, isDir
, isVersion
);
1046 __int64
GitStatus::GetIndexFileTime(const CString
&gitdir
)
1048 SHARED_INDEX_PTR ptr
=g_IndexFileMap
.SafeGet(gitdir
);
1049 if(ptr
.get() == NULL
)
1052 return ptr
->m_LastModifyTime
;
1055 int GitStatus::GetIgnoreFileChangeTimeList(const CString
&dir
, std::vector
<__int64
> &timelist
)
1057 return g_IgnoreList
.GetIgnoreFileChangeTimeList(dir
,timelist
);
1059 int GitStatus::IsIgnore(const CString
&gitdir
, const CString
&path
, bool *isIgnore
)
1061 if(::g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
1062 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
1064 *isIgnore
= g_IgnoreList
.IsIgnore(path
,gitdir
);
1069 static bool SortFileName(CGitFileName
&Item1
, CGitFileName
&Item2
)
1071 return Item1
.m_FileName
.Compare(Item2
.m_FileName
)<0;
1074 int GitStatus::GetFileList(const CString
&gitdir
, const CString
&subpath
, std::vector
<CGitFileName
> &list
)
1076 WIN32_FIND_DATA data
;
1077 HANDLE handle
=::FindFirstFile(gitdir
+_T("\\")+subpath
+_T("\\*.*"), &data
);
1080 if(_tcscmp(data
.cFileName
, _T(".git")) == 0)
1083 if(_tcscmp(data
.cFileName
, _T(".")) == 0)
1086 if(_tcscmp(data
.cFileName
, _T("..")) == 0)
1089 CGitFileName filename
;
1091 filename
.m_CaseFileName
= filename
.m_FileName
= data
.cFileName
;
1092 filename
.m_FileName
.MakeLower();
1094 if(data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
1096 filename
.m_FileName
+=_T('/');
1099 list
.push_back(filename
);
1101 }while(::FindNextFile(handle
, &data
));
1103 std::sort(list
.begin(), list
.end(), SortFileName
);
1107 int GitStatus::EnumDirStatus(const CString
&gitdir
,const CString
&subpath
,git_wc_status_kind
* status
,BOOL IsFul
, BOOL IsRecursive
, BOOL IsIgnore
, FIll_STATUS_CALLBACK callback
, void *pData
)
1111 TCHAR oldpath
[MAX_PATH
+1];
1112 memset(oldpath
,0,MAX_PATH
+1);
1114 CString path
=subpath
;
1116 path
.Replace(_T('\\'),_T('/'));
1118 if(path
[path
.GetLength()-1] != _T('/'))
1119 path
+= _T('/'); //Add trail / to show it is directory, not file name.
1121 CString lowcasepath
= path
;
1122 lowcasepath
.MakeLower();
1124 std::vector
<CGitFileName
> filelist
;
1125 GetFileList(gitdir
, subpath
, filelist
);
1129 g_IndexFileMap
.CheckAndUpdate(gitdir
,true);
1131 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
) || g_HeadFileMap
.IsHashChanged(gitdir
))
1133 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1134 treeptr
->ReadHeadHash(gitdir
);
1135 if(!treeptr
->ReadTree())
1137 g_HeadFileMap
.SafeSet(gitdir
, treeptr
);
1141 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.SafeGet(gitdir
);
1142 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1144 if( indexptr
.get() == NULL
)
1147 std::vector
<CGitFileName
>::iterator it
;
1150 for(it
= filelist
.begin(); it
<filelist
.end();it
++)
1152 casepath
=onepath
= path
;
1153 onepath
+= it
->m_FileName
;
1154 casepath
+= it
->m_CaseFileName
;
1156 int pos
= SearchInSortVector(*indexptr
, onepath
.GetBuffer(), onepath
.GetLength());
1157 int posintree
= SearchInSortVector(*treeptr
, onepath
.GetBuffer(), onepath
.GetLength());
1159 if(pos
<0 && posintree
<0)
1161 if(onepath
.GetLength() ==0)
1164 if(onepath
[onepath
.GetLength()-1] == _T('/')) /*check if it is directory*/
1166 if(::PathFileExists(gitdir
+onepath
+_T("/.git")))
1167 { /* That is git submodule */
1168 *status
= git_wc_status_unknown
;
1170 callback(gitdir
+_T("/")+casepath
, *status
, false,pData
);
1177 *status
= git_wc_status_unversioned
;
1179 callback(gitdir
+_T("/")+casepath
, *status
, false,pData
);
1183 if(::g_IgnoreList
.CheckIgnoreChanged(gitdir
,casepath
))
1184 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,casepath
);
1186 if(g_IgnoreList
.IsIgnore(casepath
,gitdir
))
1187 *status
= git_wc_status_ignored
;
1189 *status
= git_wc_status_unversioned
;
1192 callback(gitdir
+_T("/")+casepath
, *status
, false,pData
);
1194 }else if(pos
<0 && posintree
>=0) /* check if file delete in index */
1196 *status
= git_wc_status_deleted
;
1198 callback(gitdir
+_T("/")+casepath
, *status
, false,pData
);
1200 }else if(pos
>=0 && posintree
<0) /* Check if file added */
1202 *status
= git_wc_status_added
;
1204 callback(gitdir
+_T("/")+casepath
, *status
, false,pData
);
1207 if(onepath
.GetLength() ==0)
1210 if(onepath
[onepath
.GetLength()-1] == _T('/'))
1212 *status
= git_wc_status_normal
;
1214 callback(gitdir
+_T("/")+casepath
, *status
, false,pData
);
1218 git_wc_status_kind filestatus
;
1219 GetFileStatus(gitdir
,casepath
, &filestatus
,IsFul
, IsRecursive
,IsIgnore
, callback
,pData
);
1225 /* Check deleted file in system */
1227 int pos
=SearchInSortVector(*indexptr
, lowcasepath
.GetBuffer(), lowcasepath
.GetLength());
1228 GetRangeInSortVector(*indexptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength(),&start
,&end
,pos
);
1231 CGitIndexList::iterator it
;
1234 for(it
= indexptr
->begin()+start
; it
<= indexptr
->begin()+end
; it
++)
1236 int start
= lowcasepath
.GetLength();
1237 int index
= (*it
).m_FileName
.Find(_T('/'), start
);
1239 index
= (*it
).m_FileName
.GetLength();
1241 CString filename
= (*it
).m_FileName
.Mid(start
, index
);
1242 if(oldstring
!= filename
)
1244 oldstring
= filename
;
1245 if(SearchInSortVector(filelist
, filename
.GetBuffer(), filename
.GetLength())<0)
1247 *status
= git_wc_status_deleted
;
1249 callback(gitdir
+_T("/")+filename
, *status
, false,pData
);
1256 GetRangeInSortVector(*treeptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength(),&start
,&end
,pos
);
1259 CGitHeadFileList::iterator it
;
1262 for(it
= treeptr
->begin()+start
; it
<= treeptr
->begin()+end
; it
++)
1264 int start
= lowcasepath
.GetLength();
1265 int index
= (*it
).m_FileName
.Find(_T('/'), start
);
1267 index
= (*it
).m_FileName
.GetLength();
1269 CString filename
= (*it
).m_FileName
.Mid(start
, index
);
1270 if(oldstring
!= filename
)
1272 oldstring
= filename
;
1273 if(SearchInSortVector(filelist
, filename
.GetBuffer(), filename
.GetLength())<0)
1275 *status
= git_wc_status_deleted
;
1277 callback(gitdir
+_T("/")+filename
, *status
, false,pData
);
1283 }/*End of if status*/
1291 int GitStatus::GetDirStatus(const CString
&gitdir
,const CString
&subpath
,git_wc_status_kind
* status
,BOOL IsFul
, BOOL IsRecursive
,BOOL IsIgnore
,FIll_STATUS_CALLBACK callback
,void *pData
)
1295 TCHAR oldpath
[MAX_PATH
+1];
1296 memset(oldpath
,0,MAX_PATH
+1);
1298 CString path
=subpath
;
1300 path
.Replace(_T('\\'),_T('/'));
1302 if(path
[path
.GetLength()-1] != _T('/'))
1303 path
+= _T('/'); //Add trail / to show it is directory, not file name.
1305 CString lowcasepath
= path
;
1306 lowcasepath
.MakeLower();
1310 g_IndexFileMap
.CheckAndUpdate(gitdir
, true);
1312 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.SafeGet(gitdir
);
1314 int pos
=SearchInSortVector(*indexptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength());
1316 if(subpath
.IsEmpty() && pos
<0)
1317 { // for new init repository
1318 *status
= git_wc_status_normal
;
1320 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
1324 //Not In Version Contorl
1329 *status
= git_wc_status_unversioned
;
1331 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
1334 //Check ignore always.
1336 if(::g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
1337 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
1339 if(g_IgnoreList
.IsIgnore(path
,gitdir
))
1340 *status
= git_wc_status_ignored
;
1342 *status
= git_wc_status_unversioned
;
1344 g_HeadFileMap
.CheckHeadUpdate(gitdir
);
1346 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1347 //Check init repository
1348 if(treeptr
->m_Head
.IsEmpty() && path
.IsEmpty())
1349 *status
= git_wc_status_normal
;
1352 }else // In version control
1354 *status
= git_wc_status_normal
;
1361 end
=indexptr
->size()-1;
1363 GetRangeInSortVector(*indexptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength(),&start
,&end
,pos
);
1364 CGitIndexList::iterator it
;
1366 it
= indexptr
->begin()+start
;
1369 for(int i
=start
;i
<=end
;i
++)
1371 if( ((*it
).m_Flags
& CE_STAGEMASK
) !=0)
1373 *status
= git_wc_status_conflicted
;
1376 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1377 if(dirpos
<0 || IsRecursive
)
1378 callback(gitdir
+_T("\\")+ it
->m_FileName
,git_wc_status_conflicted
,false,pData
);
1385 if( IsFul
&& (*status
!= git_wc_status_conflicted
))
1387 *status
= git_wc_status_normal
;
1389 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
))
1391 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1393 treeptr
->ReadHeadHash(gitdir
);
1395 if(treeptr
->ReadTree())
1397 g_HeadFileMap
.SafeSet(gitdir
, treeptr
);
1401 it
= indexptr
->begin()+start
;
1405 //Check if new init repository
1406 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1408 if( treeptr
->size() > 0 || treeptr
->m_Head
.IsEmpty() )
1410 for(int i
=start
;i
<=end
;i
++)
1412 pos
=SearchInSortVector(*treeptr
, (*it
).m_FileName
.GetBuffer(), -1);
1416 *status
= *status
= max(git_wc_status_added
, *status
) ;
1419 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1420 if(dirpos
<0 || IsRecursive
)
1421 callback(gitdir
+_T("\\")+ it
->m_FileName
,git_wc_status_added
,false, pData
);
1427 if( pos
>=0 && treeptr
->at(pos
).m_Hash
!= (*it
).m_IndexHash
)
1429 *status
= *status
= max(git_wc_status_modified
, *status
) ;
1432 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1433 if(dirpos
<0 || IsRecursive
)
1434 callback(gitdir
+_T("\\")+ it
->m_FileName
, git_wc_status_modified
,false, pData
);
1444 if( *status
== git_wc_status_normal
)
1446 pos
= SearchInSortVector(*treeptr
, lowcasepath
.GetBuffer(), lowcasepath
.GetLength());
1449 *status
= git_wc_status_added
;
1454 GetRangeInSortVector(*treeptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength(),&hstart
,&hend
,pos
);
1455 CGitHeadFileList::iterator hit
;
1456 hit
= treeptr
->begin()+start
;
1457 for(int i
=hstart
;i
<=hend
;i
++)
1459 if( SearchInSortVector(*indexptr
,(*hit
).m_FileName
.GetBuffer(),-1) < 0)
1461 *status
= git_wc_status_deleted
;
1471 // If define callback, it need update each file status.
1472 // If not define callback, status == git_wc_status_conflicted, needn't check each file status
1473 // because git_wc_status_conflicted is highest.s
1474 if(callback
|| (*status
!= git_wc_status_conflicted
))
1479 CString sub
, currentPath
;
1480 it
= indexptr
->begin()+start
;
1481 for(int i
=start
;i
<=end
;i
++,it
++)
1485 //skip child directory
1486 int pos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1490 currentPath
= (*it
).m_FileName
.Left(pos
);
1491 if( callback
&& (sub
!= currentPath
) )
1494 ATLTRACE(_T("index subdir %s\n"),sub
);
1495 if(callback
) callback(gitdir
+ _T("\\")+sub
,
1496 git_wc_status_normal
,true, pData
);
1502 git_wc_status_kind filestatus
= git_wc_status_none
;
1504 GetFileStatus(gitdir
,(*it
).m_FileName
, &filestatus
,IsFul
, IsRecursive
,IsIgnore
, callback
,pData
);
1506 *status
= max(filestatus
, *status
) ;
1512 if(callback
) callback(gitdir
+_T("/")+subpath
,*status
,true, pData
);
1518 *status
= git_wc_status_none
;
1525 bool GitStatus::IsExistIndexLockFile(const CString
&gitdir
)
1527 CString sDirName
= gitdir
;
1531 if(PathFileExists(sDirName
+ _T("\\.git")))
1533 if(PathFileExists(sDirName
+ _T("\\.git\\index.lock")))
1539 int x
= sDirName
.ReverseFind(_T('\\'));
1543 sDirName
= sDirName
.Left(x
);