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
)CRegStdDWORD(_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
);
244 err
= GetFileStatus(sProjectRoot
,sSubPath
,&statuskind
,isfull
, false,isfull
, NULL
,NULL
);
251 git_wc_status_kind
GitStatus::GetAllStatusRecursive(const CTGitPath
& path
)
253 return GetAllStatus(path
, git_depth_infinity
);
257 git_wc_status_kind
GitStatus::GetMoreImportant(git_wc_status_kind status1
, git_wc_status_kind status2
)
259 if (GetStatusRanking(status1
) >= GetStatusRanking(status2
))
263 // static private method
264 int GitStatus::GetStatusRanking(git_wc_status_kind status
)
268 case git_wc_status_none
:
270 case git_wc_status_unversioned
:
272 case git_wc_status_ignored
:
274 case git_wc_status_incomplete
:
276 case git_wc_status_normal
:
277 case git_wc_status_external
:
279 case git_wc_status_added
:
281 case git_wc_status_missing
:
283 case git_wc_status_deleted
:
285 case git_wc_status_replaced
:
287 case git_wc_status_modified
:
289 case git_wc_status_merged
:
291 case git_wc_status_conflicted
:
293 case git_wc_status_obstructed
:
299 git_revnum_t
GitStatus::GetStatus(const CTGitPath
& path
, bool update
/* = false */, bool noignore
/* = false */, bool /*noexternals*/ /* = false */)
301 // NOTE: unlike the SVN version this one does not cache the enumerated files, because in practice no code in all of
302 // Tortoise uses this, all places that call GetStatus create a temp GitStatus object which gets destroyed right
303 // after the call again
305 // apr_hash_t * statushash;
306 // apr_hash_t * exthash;
307 // apr_array_header_t * statusarray;
308 // const sort_item* item;
310 // git_error_clear(m_err);
311 // statushash = apr_hash_make(m_pool);
312 // exthash = apr_hash_make(m_pool);
313 git_revnum_t youngest
= GIT_INVALID_REVNUM
;
314 // git_opt_revision_t rev;
315 // rev.kind = git_opt_revision_unspecified;
317 CString sProjectRoot
;
318 if ( !path
.HasAdminDir(&sProjectRoot
) )
321 struct hashbaton_t hashbaton
;
322 // hashbaton.hash = statushash;
323 // hashbaton.exthash = exthash;
324 hashbaton
.pThis
= this;
326 bool isfull
= ((DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"),
327 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
330 LPCTSTR lpszSubPath
= NULL
;
332 CString s
= path
.GetWinPathString();
333 if (s
.GetLength() > sProjectRoot
.GetLength())
335 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
336 lpszSubPath
= sSubPath
;
337 // skip initial slash if necessary
338 if (*lpszSubPath
== _T('\\'))
342 m_status
.prop_status
= m_status
.text_status
= git_wc_status_none
;
344 if(path
.IsDirectory())
346 m_err
= GetDirStatus(sProjectRoot
,CString(lpszSubPath
),&m_status
.text_status
, isfull
, false,!noignore
, NULL
, NULL
);
351 m_err
= GetFileStatus(sProjectRoot
,CString(lpszSubPath
),&m_status
.text_status
,isfull
, false,!noignore
, NULL
,NULL
);
355 // Error present if function is not under version control
356 if (m_err
) /*|| (apr_hash_count(statushash) == 0)*/
359 return GIT_INVALID_REVNUM
;
362 // Convert the unordered hash to an ordered, sorted array
363 /*statusarray = sort_hash (statushash,
364 sort_compare_items_as_paths,
367 // only the first entry is needed (no recurse)
368 // item = &APR_ARRAY_IDX (statusarray, 0, const sort_item);
370 // status = (git_wc_status2_t *) item->value;
375 // done to match TSVN functionality of this function (not sure if any code uses the reutrn val)
376 // if TGit does not need this, then change the return type of function
377 youngest
= g_Git
.GetHash(_T("HEAD"));
383 git_wc_status2_t
* GitStatus::GetFirstFileStatus(const CTGitPath
& /*path*/, CTGitPath
& /*retPath*/, bool /*update*/, git_depth_t
/*depth*/, bool /*bNoIgnore*/ /* = true */, bool /*bNoExternals*/ /* = false */)
385 static git_wc_status2 st
;
389 m_fileCache.Init( CStringA( path.GetWinPathString().GetString() ) );
390 MessageBox(NULL, path.GetWinPathString(), _T("GetFirstFile"), MB_OK);
391 m_fileCache.m_pFileIter = m_fileCache.m_pFiles;
392 st.text_status = git_wc_status_none;
394 if (m_fileCache.m_pFileIter)
396 switch(m_fileCache.m_pFileIter->nStatus)
398 case WGFS_Normal: st.text_status = git_wc_status_normal; break;
399 case WGFS_Modified: st.text_status = git_wc_status_modified; break;
400 case WGFS_Deleted: st.text_status = git_wc_status_deleted; break;
403 //retPath.SetFromGit((const char*)item->key);
405 m_fileCache.m_pFileIter = m_fileCache.m_pFileIter->pNext;
411 const sort_item
* item
;
413 git_error_clear(m_err
);
414 m_statushash
= apr_hash_make(m_pool
);
415 m_externalhash
= apr_hash_make(m_pool
);
416 headrev
= Git_INVALID_REVNUM
;
417 git_opt_revision_t rev
;
418 rev
.kind
= git_opt_revision_unspecified
;
419 struct hashbaton_t hashbaton
;
420 hashbaton
.hash
= m_statushash
;
421 hashbaton
.exthash
= m_externalhash
;
422 hashbaton
.pThis
= this;
423 m_statushashindex
= 0;
424 m_err
= git_client_status4 (&headrev
,
425 path
.GetGitApiPath(m_pool
),
432 bNoIgnore
, //noignore
433 bNoExternals
, //noexternals
439 // Error present if function is not under version control
440 if ((m_err
!= NULL
) || (apr_hash_count(m_statushash
) == 0))
445 // Convert the unordered hash to an ordered, sorted array
446 m_statusarray
= sort_hash (m_statushash
,
447 sort_compare_items_as_paths
,
450 // only the first entry is needed (no recurse)
451 m_statushashindex
= 0;
452 item
= &APR_ARRAY_IDX (m_statusarray
, m_statushashindex
, const sort_item
);
453 retPath
.SetFromGit((const char*)item
->key
);
454 return (git_wc_status2_t
*) item
->value
;
460 unsigned int GitStatus::GetVersionedCount() const
462 // return /**/m_fileCache.GetFileCount();
464 unsigned int count
= 0;
466 const sort_item
* item
;
467 for (unsigned int i
=0; i
<apr_hash_count(m_statushash
); ++i
)
469 item
= &APR_ARRAY_IDX(m_statusarray
, i
, const sort_item
);
472 if (GitStatus::GetMoreImportant(((git_wc_status_t
*)item
->value
)->text_status
, git_wc_status_ignored
)!=git_wc_status_ignored
)
480 git_wc_status2_t
* GitStatus::GetNextFileStatus(CTGitPath
& /*retPath*/)
482 static git_wc_status2 st
;
484 st
.text_status
= git_wc_status_none
;
486 /*if (m_fileCache.m_pFileIter)
488 switch(m_fileCache.m_pFileIter->nStatus)
490 case WGFS_Normal: st.text_status = git_wc_status_normal; break;
491 case WGFS_Modified: st.text_status = git_wc_status_modified; break;
492 case WGFS_Deleted: st.text_status = git_wc_status_deleted; break;
495 m_fileCache.m_pFileIter = m_fileCache.m_pFileIter->pNext;
501 const sort_item
* item
;
503 if ((m_statushashindex
+1) >= apr_hash_count(m_statushash
))
507 item
= &APR_ARRAY_IDX (m_statusarray
, m_statushashindex
, const sort_item
);
508 retPath
.SetFromGit((const char*)item
->key
);
509 return (git_wc_status2_t
*) item
->value
;
514 bool GitStatus::IsExternal(const CTGitPath
& /*path*/) const
517 if (apr_hash_get(m_externalhash
, path
.GetGitApiPath(m_pool
), APR_HASH_KEY_STRING
))
523 bool GitStatus::IsInExternal(const CTGitPath
& /*path*/) const
526 if (apr_hash_count(m_statushash
) == 0)
529 GitPool
localpool(m_pool
);
530 apr_hash_index_t
*hi
;
532 for (hi
= apr_hash_first(localpool
, m_externalhash
); hi
; hi
= apr_hash_next(hi
))
534 apr_hash_this(hi
, (const void**)&key
, NULL
, NULL
);
537 if (CTGitPath(CUnicodeUtils::GetUnicode(key
)).IsAncestorOf(path
))
546 void GitStatus::GetStatusString(git_wc_status_kind status
, size_t buflen
, TCHAR
* string
)
551 case git_wc_status_none
:
554 case git_wc_status_unversioned
:
555 buf
= _T("unversioned\0");
557 case git_wc_status_normal
:
558 buf
= _T("normal\0");
560 case git_wc_status_added
:
563 case git_wc_status_missing
:
564 buf
= _T("missing\0");
566 case git_wc_status_deleted
:
567 buf
= _T("deleted\0");
569 case git_wc_status_replaced
:
570 buf
= _T("replaced\0");
572 case git_wc_status_modified
:
573 buf
= _T("modified\0");
575 case git_wc_status_merged
:
576 buf
= _T("merged\0");
578 case git_wc_status_conflicted
:
579 buf
= _T("conflicted\0");
581 case git_wc_status_obstructed
:
582 buf
= _T("obstructed\0");
584 case git_wc_status_ignored
:
587 case git_wc_status_external
:
588 buf
= _T("external");
590 case git_wc_status_incomplete
:
591 buf
= _T("incomplete\0");
597 _stprintf_s(string
, buflen
, _T("%s"), buf
);
600 void GitStatus::GetStatusString(HINSTANCE hInst
, git_wc_status_kind status
, TCHAR
* string
, int size
, WORD lang
)
604 case git_wc_status_none
:
605 LoadStringEx(hInst
, IDS_STATUSNONE
, string
, size
, lang
);
607 case git_wc_status_unversioned
:
608 LoadStringEx(hInst
, IDS_STATUSUNVERSIONED
, string
, size
, lang
);
610 case git_wc_status_normal
:
611 LoadStringEx(hInst
, IDS_STATUSNORMAL
, string
, size
, lang
);
613 case git_wc_status_added
:
614 LoadStringEx(hInst
, IDS_STATUSADDED
, string
, size
, lang
);
616 case git_wc_status_missing
:
617 LoadStringEx(hInst
, IDS_STATUSABSENT
, string
, size
, lang
);
619 case git_wc_status_deleted
:
620 LoadStringEx(hInst
, IDS_STATUSDELETED
, string
, size
, lang
);
622 case git_wc_status_replaced
:
623 LoadStringEx(hInst
, IDS_STATUSREPLACED
, string
, size
, lang
);
625 case git_wc_status_modified
:
626 LoadStringEx(hInst
, IDS_STATUSMODIFIED
, string
, size
, lang
);
628 case git_wc_status_merged
:
629 LoadStringEx(hInst
, IDS_STATUSMERGED
, string
, size
, lang
);
631 case git_wc_status_conflicted
:
632 LoadStringEx(hInst
, IDS_STATUSCONFLICTED
, string
, size
, lang
);
634 case git_wc_status_ignored
:
635 LoadStringEx(hInst
, IDS_STATUSIGNORED
, string
, size
, lang
);
637 case git_wc_status_obstructed
:
638 LoadStringEx(hInst
, IDS_STATUSOBSTRUCTED
, string
, size
, lang
);
640 case git_wc_status_external
:
641 LoadStringEx(hInst
, IDS_STATUSEXTERNAL
, string
, size
, lang
);
643 case git_wc_status_incomplete
:
644 LoadStringEx(hInst
, IDS_STATUSINCOMPLETE
, string
, size
, lang
);
647 LoadStringEx(hInst
, IDS_STATUSNONE
, string
, size
, lang
);
653 CString
GitStatus::GetDepthString(git_depth_t depth
)
659 case git_depth_unknown
:
660 sDepth
.LoadString(IDS_Git_DEPTH_UNKNOWN
);
662 case git_depth_empty
:
663 sDepth
.LoadString(IDS_Git_DEPTH_EMPTY
);
665 case git_depth_files
:
666 sDepth
.LoadString(IDS_Git_DEPTH_FILES
);
668 case git_depth_immediates
:
669 sDepth
.LoadString(IDS_Git_DEPTH_IMMEDIATE
);
671 case git_depth_infinity
:
672 sDepth
.LoadString(IDS_Git_DEPTH_INFINITE
);
681 void GitStatus::GetDepthString(HINSTANCE
/*hInst*/, git_depth_t
/*depth*/, TCHAR
* /*string*/, int /*size*/, WORD
/*lang*/)
686 case git_depth_unknown
:
687 LoadStringEx(hInst
, IDS_SVN_DEPTH_UNKNOWN
, string
, size
, lang
);
689 case git_depth_empty
:
690 LoadStringEx(hInst
, IDS_SVN_DEPTH_EMPTY
, string
, size
, lang
);
692 case git_depth_files
:
693 LoadStringEx(hInst
, IDS_SVN_DEPTH_FILES
, string
, size
, lang
);
695 case git_depth_immediates
:
696 LoadStringEx(hInst
, IDS_SVN_DEPTH_IMMEDIATE
, string
, size
, lang
);
698 case git_depth_infinity
:
699 LoadStringEx(hInst
, IDS_SVN_DEPTH_INFINITE
, string
, size
, lang
);
706 int GitStatus::LoadStringEx(HINSTANCE hInstance
, UINT uID
, LPTSTR lpBuffer
, int nBufferMax
, WORD wLanguage
)
708 const STRINGRESOURCEIMAGE
* pImage
;
709 const STRINGRESOURCEIMAGE
* pImageEnd
;
715 HRSRC hResource
= FindResourceEx(hInstance
, RT_STRING
, MAKEINTRESOURCE(((uID
>>4)+1)), wLanguage
);
718 // try the default language before giving up!
719 hResource
= FindResource(hInstance
, MAKEINTRESOURCE(((uID
>>4)+1)), RT_STRING
);
723 hGlobal
= LoadResource(hInstance
, hResource
);
726 pImage
= (const STRINGRESOURCEIMAGE
*)::LockResource(hGlobal
);
730 nResourceSize
= ::SizeofResource(hInstance
, hResource
);
731 pImageEnd
= (const STRINGRESOURCEIMAGE
*)(LPBYTE(pImage
)+nResourceSize
);
734 while ((iIndex
> 0) && (pImage
< pImageEnd
))
736 pImage
= (const STRINGRESOURCEIMAGE
*)(LPBYTE(pImage
)+(sizeof(STRINGRESOURCEIMAGE
)+(pImage
->nLength
*sizeof(WCHAR
))));
739 if (pImage
>= pImageEnd
)
741 if (pImage
->nLength
== 0)
744 ret
= pImage
->nLength
;
745 if (pImage
->nLength
> nBufferMax
)
747 wcsncpy_s(lpBuffer
, nBufferMax
, pImage
->achString
, pImage
->nLength
-1);
748 lpBuffer
[nBufferMax
-1] = 0;
752 wcsncpy_s(lpBuffer
, nBufferMax
, pImage
->achString
, pImage
->nLength
);
758 BOOL
GitStatus::getallstatus(const struct wgFile_s
*pFile
, void *pUserData
)
760 git_wc_status_kind
* s
= (git_wc_status_kind
*)pUserData
;
761 *s
= GitStatus::GetMoreImportant(*s
, GitStatusFromWingit(pFile
->nStatus
));
765 BOOL
GitStatus::getstatus(const struct wgFile_s
*pFile
, void *pUserData
)
767 git_wc_status2_t
* s
= (git_wc_status2_t
*)pUserData
;
768 s
->prop_status
= s
->text_status
= GitStatus::GetMoreImportant(s
->prop_status
, GitStatusFromWingit(pFile
->nStatus
));
773 git_error_t
* GitStatus::getallstatus(void * baton
, const char * /*path*/, git_wc_status2_t
* status
, apr_pool_t
* /*pool*/)
775 git_wc_status_kind
* s
= (git_wc_status_kind
*)baton
;
776 *s
= GitStatus::GetMoreImportant(*s
, status
->text_status
);
777 *s
= GitStatus::GetMoreImportant(*s
, status
->prop_status
);
783 git_error_t
* GitStatus::getstatushash(void * baton
, const char * path
, git_wc_status2_t
* status
, apr_pool_t
* /*pool*/)
785 hashbaton_t
* hash
= (hashbaton_t
*)baton
;
786 const StdStrAVector
& filterList
= hash
->pThis
->m_filterFileList
;
787 if (status
->text_status
== git_wc_status_external
)
789 apr_hash_set (hash
->exthash
, apr_pstrdup(hash
->pThis
->m_pool
, path
), APR_HASH_KEY_STRING
, (const void*)1);
792 if(filterList
.size() > 0)
794 // We have a filter active - we're only interested in files which are in
796 if(!binary_search(filterList
.begin(), filterList
.end(), path
))
798 // This item is not in the filter - don't store it
802 git_wc_status2_t
* statuscopy
= git_wc_dup_status2 (status
, hash
->pThis
->m_pool
);
803 apr_hash_set (hash
->hash
, apr_pstrdup(hash
->pThis
->m_pool
, path
), APR_HASH_KEY_STRING
, statuscopy
);
807 apr_array_header_t
* GitStatus::sort_hash (apr_hash_t
*ht
,
808 int (*comparison_func
) (const GitStatus::sort_item
*, const GitStatus::sort_item
*),
811 apr_hash_index_t
*hi
;
812 apr_array_header_t
*ary
;
814 /* allocate an array with only one element to begin with. */
815 ary
= apr_array_make (pool
, 1, sizeof(sort_item
));
817 /* loop over hash table and push all keys into the array */
818 for (hi
= apr_hash_first (pool
, ht
); hi
; hi
= apr_hash_next (hi
))
820 sort_item
*item
= (sort_item
*)apr_array_push (ary
);
822 apr_hash_this (hi
, &item
->key
, &item
->klen
, &item
->value
);
825 /* now quick sort the array. */
826 qsort (ary
->elts
, ary
->nelts
, ary
->elt_size
,
827 (int (*)(const void *, const void *))comparison_func
);
832 int GitStatus::sort_compare_items_as_paths (const sort_item
*a
, const sort_item
*b
)
834 const char *astr
, *bstr
;
836 astr
= (const char*)a
->key
;
837 bstr
= (const char*)b
->key
;
838 return git_path_compare_paths (astr
, bstr
);
842 git_error_t
* GitStatus::cancel(void * /*baton*/)
845 volatile bool * canceled
= (bool *)baton
;
849 temp
.LoadString(IDS_Git_USERCANCELLED
);
850 return git_error_create(Git_ERR_CANCELLED
, NULL
, CUnicodeUtils::GetUTF8(temp
));
859 // Set-up a filter to restrict the files which will have their status stored by a get-status
860 void GitStatus::SetFilter(const CTGitPathList
& fileList
)
862 m_filterFileList
.clear();
863 for(int fileIndex
= 0; fileIndex
< fileList
.GetCount(); fileIndex
++)
865 // m_filterFileList.push_back(fileList[fileIndex].GetGitApiPath(m_pool));
867 // Sort the list so that we can do binary searches
868 std::sort(m_filterFileList
.begin(), m_filterFileList
.end());
871 void GitStatus::ClearFilter()
873 m_filterFileList
.clear();
878 typedef CComCritSecLock
<CComCriticalSection
> CAutoLocker
;
880 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
)
884 CString path
= pathParam
;
886 TCHAR oldpath
[MAX_PATH
+1];
887 memset(oldpath
,0,MAX_PATH
+1);
889 path
.Replace(_T('\\'),_T('/'));
891 CString lowcasepath
=path
;
892 lowcasepath
.MakeLower();
896 git_wc_status_kind st
= git_wc_status_none
;
899 g_IndexFileMap
.GetFileStatus(gitdir
,path
,&st
,IsFull
,false,callback
,pData
,&hash
);
901 if( st
== git_wc_status_conflicted
)
905 callback(gitdir
+_T("/")+path
,st
,false,pData
);
909 if( st
== git_wc_status_unversioned
)
913 *status
= git_wc_status_unversioned
;
915 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
919 if( g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
921 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
923 if( g_IgnoreList
.IsIgnore(path
, gitdir
) )
925 st
= git_wc_status_ignored
;
929 callback(gitdir
+_T("/")+path
,st
, false, pData
);
934 if( st
== git_wc_status_normal
&& IsFull
)
937 g_HeadFileMap
.CheckHeadUpdate(gitdir
);
940 SHARED_TREE_PTR treeptr
;
942 treeptr
=g_HeadFileMap
.SafeGet(gitdir
);
944 b
= treeptr
->m_Head
!= treeptr
->m_TreeHash
;
948 treeptr
->ReadHeadHash(gitdir
);
951 if( treeptr
->m_HeadFile
.IsEmpty() )
953 *status
=st
=git_wc_status_added
;
955 callback(gitdir
+_T("/")+path
,st
,false,pData
);
958 if(treeptr
->ReadTree())
960 treeptr
->m_LastModifyTimeHead
= 0;
961 //Check if init repository
962 *status
= treeptr
->m_Head
.IsEmpty()? git_wc_status_added
: st
;
964 callback(gitdir
+_T("/")+path
,*status
,false,pData
);
967 g_HeadFileMap
.SafeSet(gitdir
, treeptr
);
970 // Check Head Tree Hash;
974 int start
=SearchInSortVector(*treeptr
,lowcasepath
.GetBuffer(),-1);
978 *status
=st
=git_wc_status_added
;
979 ATLTRACE(_T("File miss in head tree %s"), path
);
981 callback(gitdir
+_T("/")+path
,st
,false, pData
);
985 //staged and not commit
986 if( treeptr
->at(start
).m_Hash
!= hash
)
988 *status
=st
=git_wc_status_modified
;
990 callback(gitdir
+_T("/")+path
,st
, false, pData
);
998 callback(gitdir
+_T("/")+path
,st
,false, pData
);
1005 *status
= git_wc_status_none
;
1013 int GitStatus::GetHeadHash(const CString
&gitdir
, CGitHash
&hash
)
1015 return g_HeadFileMap
.GetHeadHash(gitdir
, hash
);
1018 bool GitStatus::IsGitReposChanged(const CString
&gitdir
,const CString
&subpaths
, int mode
)
1020 if( mode
& GIT_MODE_INDEX
)
1022 return g_IndexFileMap
.CheckAndUpdate(gitdir
, true);
1025 if( mode
& GIT_MODE_HEAD
)
1027 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
))
1031 if( mode
& GIT_MODE_IGNORE
)
1033 if(g_IgnoreList
.CheckIgnoreChanged(gitdir
,subpaths
))
1039 int GitStatus::LoadIgnoreFile(const CString
&gitdir
,const CString
&subpaths
)
1041 return g_IgnoreList
.LoadAllIgnoreFile(gitdir
,subpaths
);
1043 int GitStatus::IsUnderVersionControl(const CString
&gitdir
, const CString
&path
, bool isDir
,bool *isVersion
)
1045 return g_IndexFileMap
.IsUnderVersionControl(gitdir
, path
, isDir
, isVersion
);
1048 __int64
GitStatus::GetIndexFileTime(const CString
&gitdir
)
1050 SHARED_INDEX_PTR ptr
=g_IndexFileMap
.SafeGet(gitdir
);
1051 if(ptr
.get() == NULL
)
1054 return ptr
->m_LastModifyTime
;
1057 int GitStatus::GetIgnoreFileChangeTimeList(const CString
&dir
, std::vector
<__int64
> &timelist
)
1059 return g_IgnoreList
.GetIgnoreFileChangeTimeList(dir
,timelist
);
1061 int GitStatus::IsIgnore(const CString
&gitdir
, const CString
&path
, bool *isIgnore
)
1063 if(::g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
1064 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
1066 *isIgnore
= g_IgnoreList
.IsIgnore(path
,gitdir
);
1071 static bool SortFileName(CGitFileName
&Item1
, CGitFileName
&Item2
)
1073 return Item1
.m_FileName
.Compare(Item2
.m_FileName
)<0;
1076 int GitStatus::GetFileList(const CString
&gitdir
, const CString
&subpath
, std::vector
<CGitFileName
> &list
)
1078 WIN32_FIND_DATA data
;
1079 HANDLE handle
=::FindFirstFile(gitdir
+_T("\\")+subpath
+_T("\\*.*"), &data
);
1082 if(_tcscmp(data
.cFileName
, _T(".git")) == 0)
1085 if(_tcscmp(data
.cFileName
, _T(".")) == 0)
1088 if(_tcscmp(data
.cFileName
, _T("..")) == 0)
1091 CGitFileName filename
;
1093 filename
.m_CaseFileName
= filename
.m_FileName
= data
.cFileName
;
1094 filename
.m_FileName
.MakeLower();
1096 if(data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
1098 filename
.m_FileName
+= _T('/');
1101 list
.push_back(filename
);
1103 }while(::FindNextFile(handle
, &data
));
1107 std::sort(list
.begin(), list
.end(), SortFileName
);
1111 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
)
1115 TCHAR oldpath
[MAX_PATH
+1];
1116 memset(oldpath
,0,MAX_PATH
+1);
1118 CString path
=subpath
;
1120 path
.Replace(_T('\\'),_T('/'));
1122 if(path
[path
.GetLength()-1] != _T('/'))
1123 path
+= _T('/'); //Add trail / to show it is directory, not file name.
1125 CString lowcasepath
= path
;
1126 lowcasepath
.MakeLower();
1128 std::vector
<CGitFileName
> filelist
;
1129 GetFileList(gitdir
, subpath
, filelist
);
1133 g_IndexFileMap
.CheckAndUpdate(gitdir
,true);
1135 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
) || g_HeadFileMap
.IsHashChanged(gitdir
))
1137 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1138 treeptr
->ReadHeadHash(gitdir
);
1139 if(!treeptr
->ReadTree())
1141 g_HeadFileMap
.SafeSet(gitdir
, treeptr
);
1145 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.SafeGet(gitdir
);
1146 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1148 if( indexptr
.get() == NULL
)
1151 std::vector
<CGitFileName
>::iterator it
;
1154 for(it
= filelist
.begin(); it
<filelist
.end();it
++)
1156 casepath
=onepath
= path
;
1157 onepath
.MakeLower();
1158 onepath
+= it
->m_FileName
;
1159 casepath
+= it
->m_CaseFileName
;
1161 int pos
= SearchInSortVector(*indexptr
, onepath
.GetBuffer(), onepath
.GetLength());
1162 int posintree
= SearchInSortVector(*treeptr
, onepath
.GetBuffer(), onepath
.GetLength());
1165 if(onepath
.GetLength()>0 && onepath
[onepath
.GetLength()-1] == _T('/'))
1168 if(pos
<0 && posintree
<0)
1170 if(onepath
.GetLength() ==0)
1173 if(bIsDir
) /*check if it is directory*/
1175 if(::PathFileExists(gitdir
+onepath
+_T("/.git")))
1176 { /* That is git submodule */
1177 *status
= git_wc_status_unknown
;
1179 callback(gitdir
+_T("/")+casepath
, *status
, bIsDir
, pData
);
1186 *status
= git_wc_status_unversioned
;
1188 callback(gitdir
+_T("/")+casepath
, *status
, bIsDir
,pData
);
1192 if(::g_IgnoreList
.CheckIgnoreChanged(gitdir
,casepath
))
1193 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,casepath
);
1195 if(g_IgnoreList
.IsIgnore(casepath
,gitdir
))
1196 *status
= git_wc_status_ignored
;
1198 *status
= git_wc_status_unversioned
;
1201 callback(gitdir
+_T("/")+casepath
, *status
, bIsDir
,pData
);
1204 else if(pos
<0 && posintree
>=0) /* check if file delete in index */
1206 *status
= git_wc_status_deleted
;
1208 callback(gitdir
+_T("/")+casepath
, *status
, bIsDir
,pData
);
1211 else if(pos
>=0 && posintree
<0) /* Check if file added */
1213 *status
= git_wc_status_added
;
1215 callback(gitdir
+_T("/")+casepath
, *status
, bIsDir
,pData
);
1219 if(onepath
.GetLength() ==0)
1222 if(onepath
[onepath
.GetLength()-1] == _T('/'))
1224 *status
= git_wc_status_normal
;
1226 callback(gitdir
+_T("/")+casepath
, *status
, bIsDir
,pData
);
1230 git_wc_status_kind filestatus
;
1231 GetFileStatus(gitdir
,casepath
, &filestatus
,IsFul
, IsRecursive
,IsIgnore
, callback
,pData
);
1237 /* Check deleted file in system */
1239 int pos
=SearchInSortVector(*indexptr
, lowcasepath
.GetBuffer(), lowcasepath
.GetLength());
1241 if(pos
>=0 && GetRangeInSortVector(*indexptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength(),&start
,&end
,pos
))
1243 CGitIndexList::iterator it
;
1246 for(it
= indexptr
->begin()+start
; it
<= indexptr
->begin()+end
; it
++)
1248 int start
= lowcasepath
.GetLength();
1249 int index
= (*it
).m_FileName
.Find(_T('/'), start
);
1251 index
= (*it
).m_FileName
.GetLength();
1253 CString filename
= (*it
).m_FileName
.Mid(start
, index
-start
);
1254 if(oldstring
!= filename
)
1256 oldstring
= filename
;
1257 if(SearchInSortVector(filelist
, filename
.GetBuffer(), filename
.GetLength())<0)
1259 *status
= git_wc_status_deleted
;
1261 callback(gitdir
+_T("/")+filename
, *status
, false,pData
);
1268 pos
=SearchInSortVector(*treeptr
, lowcasepath
.GetBuffer(), lowcasepath
.GetLength());
1269 if(pos
>=0 && GetRangeInSortVector(*treeptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength(),&start
,&end
,pos
) == 0)
1271 CGitHeadFileList::iterator it
;
1274 for(it
= treeptr
->begin()+start
; it
<= treeptr
->begin()+end
; it
++)
1276 int start
= lowcasepath
.GetLength();
1277 int index
= (*it
).m_FileName
.Find(_T('/'), start
);
1279 index
= (*it
).m_FileName
.GetLength();
1281 CString filename
= (*it
).m_FileName
.Mid(start
, index
-start
);
1282 if(oldstring
!= filename
)
1284 oldstring
= filename
;
1285 if(SearchInSortVector(filelist
, filename
.GetBuffer(), filename
.GetLength())<0)
1287 *status
= git_wc_status_deleted
;
1289 callback(gitdir
+_T("/")+(*it
).m_FileName
, *status
, false,pData
);
1295 }/*End of if status*/
1303 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
)
1307 TCHAR oldpath
[MAX_PATH
+1];
1308 memset(oldpath
,0,MAX_PATH
+1);
1310 CString path
=subpath
;
1312 path
.Replace(_T('\\'),_T('/'));
1314 if(path
[path
.GetLength()-1] != _T('/'))
1315 path
+= _T('/'); //Add trail / to show it is directory, not file name.
1317 CString lowcasepath
= path
;
1318 lowcasepath
.MakeLower();
1322 g_IndexFileMap
.CheckAndUpdate(gitdir
, true);
1324 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.SafeGet(gitdir
);
1326 if (indexptr
== NULL
)
1328 *status
= git_wc_status_unversioned
;
1332 if(subpath
.IsEmpty() && (!indexptr
.use_count()))
1333 { // for new init repository
1334 *status
= git_wc_status_normal
;
1336 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
1340 int pos
=SearchInSortVector(*indexptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength());
1342 //Not In Version Contorl
1347 *status
= git_wc_status_unversioned
;
1349 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
1352 //Check ignore always.
1354 if(::g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
1355 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
1357 if(g_IgnoreList
.IsIgnore(path
,gitdir
))
1358 *status
= git_wc_status_ignored
;
1360 *status
= git_wc_status_unversioned
;
1362 g_HeadFileMap
.CheckHeadUpdate(gitdir
);
1364 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1365 //Check init repository
1366 if(treeptr
->m_Head
.IsEmpty() && path
.IsEmpty())
1367 *status
= git_wc_status_normal
;
1371 else // In version control
1373 *status
= git_wc_status_normal
;
1380 end
=indexptr
->size()-1;
1382 GetRangeInSortVector(*indexptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength(),&start
,&end
,pos
);
1383 CGitIndexList::iterator it
;
1385 it
= indexptr
->begin()+start
;
1388 for(int i
=start
;i
<=end
;i
++)
1390 if( ((*it
).m_Flags
& CE_STAGEMASK
) !=0)
1392 *status
= git_wc_status_conflicted
;
1395 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1396 if(dirpos
<0 || IsRecursive
)
1397 callback(gitdir
+_T("\\")+ it
->m_FileName
,git_wc_status_conflicted
,false,pData
);
1405 if( IsFul
&& (*status
!= git_wc_status_conflicted
))
1407 *status
= git_wc_status_normal
;
1409 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
))
1411 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1413 treeptr
->ReadHeadHash(gitdir
);
1415 if(treeptr
->ReadTree())
1417 g_HeadFileMap
.SafeSet(gitdir
, treeptr
);
1421 it
= indexptr
->begin()+start
;
1425 //Check if new init repository
1426 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1428 if( treeptr
->size() > 0 || treeptr
->m_Head
.IsEmpty() )
1430 for(int i
=start
;i
<=end
;i
++)
1432 pos
=SearchInSortVector(*treeptr
, (*it
).m_FileName
.GetBuffer(), -1);
1436 *status
= max(git_wc_status_modified
, *status
); // added file found
1439 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1440 if(dirpos
<0 || IsRecursive
)
1441 callback(gitdir
+_T("\\")+ it
->m_FileName
,git_wc_status_added
,false, pData
);
1448 if( pos
>=0 && treeptr
->at(pos
).m_Hash
!= (*it
).m_IndexHash
)
1450 *status
= max(git_wc_status_modified
, *status
); // modified file found
1453 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1454 if(dirpos
<0 || IsRecursive
)
1455 callback(gitdir
+_T("\\")+ it
->m_FileName
, git_wc_status_modified
,false, pData
);
1466 if( *status
== git_wc_status_normal
)
1468 pos
= SearchInSortVector(*treeptr
, lowcasepath
.GetBuffer(), lowcasepath
.GetLength());
1471 *status
= max(git_wc_status_modified
, *status
); // added file found
1477 GetRangeInSortVector(*treeptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength(),&hstart
,&hend
,pos
);
1478 CGitHeadFileList::iterator hit
;
1479 hit
= treeptr
->begin()+start
;
1480 CGitHeadFileList::iterator lastElement
= treeptr
->end();
1481 for(int i
=hstart
; i
<= hend
&& hit
!= lastElement
; i
++)
1483 if( SearchInSortVector(*indexptr
,(*hit
).m_FileName
.GetBuffer(),-1) < 0)
1485 *status
= max(git_wc_status_modified
, *status
); // deleted file found
1495 // If define callback, it need update each file status.
1496 // If not define callback, status == git_wc_status_conflicted, needn't check each file status
1497 // because git_wc_status_conflicted is highest.s
1498 if(callback
|| (*status
!= git_wc_status_conflicted
))
1503 CString sub
, currentPath
;
1504 it
= indexptr
->begin()+start
;
1505 for(int i
=start
;i
<=end
;i
++,it
++)
1509 //skip child directory
1510 int pos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1514 currentPath
= (*it
).m_FileName
.Left(pos
);
1515 if( callback
&& (sub
!= currentPath
) )
1518 ATLTRACE(_T("index subdir %s\n"),sub
);
1519 if(callback
) callback(gitdir
+ _T("\\")+sub
,
1520 git_wc_status_normal
,true, pData
);
1526 git_wc_status_kind filestatus
= git_wc_status_none
;
1528 GetFileStatus(gitdir
,(*it
).m_FileName
, &filestatus
,IsFul
, IsRecursive
,IsIgnore
, callback
,pData
);
1530 if (filestatus
> git_wc_status_normal
&& filestatus
!= git_wc_status_conflicted
)
1531 *status
= git_wc_status_modified
; // folders can only be modified or conflicted
1537 if(callback
) callback(gitdir
+_T("/")+subpath
,*status
,true, pData
);
1543 *status
= git_wc_status_none
;
1550 bool GitStatus::IsExistIndexLockFile(const CString
&gitdir
)
1552 CString sDirName
= gitdir
;
1556 if(PathFileExists(sDirName
+ _T("\\.git")))
1558 if(PathFileExists(sDirName
+ _T("\\.git\\index.lock")))
1564 int x
= sDirName
.ReverseFind(_T('\\'));
1568 sDirName
= sDirName
.Left(x
);